Improve overal codebase, use modern tech like esbuild and TypeScript 4! (#1055)
* use esbuild for React instead of tsdx * remove tsdx from Vue * use consistent names * add jest and prettier * update scripts * ignore some folders for prettier * run lint script instead of tsdx lint * run prettier en-masse This has a few changes because of the new prettier version. * bump typescript to latest version * make typescript happy * cleanup playground package.json * make esbuild a dev dependency * make scripts consistent * fix husky hooks * add dedicated watch script * add `yarn playground-react` and `yarn react-playground` (alias) This will make sure to run a watcher for the actual @headlessui/react package, and start a development server in the playground-react package. * ignore formatting in the .next folder * run prettier on playground-react package * setup playground-vue Still not 100% working, but getting there! * add playground aliases in @headlessui/vue and @headlessui/react This allows you to run `yarn react playground` or `yarn vue playground` from the root. * add `clean` script * move examples folder in playground-vue to root * ensure new lines for consistency in scripts * fix typescript issue * fix typescript issues in playgrounds * make sure to run prettier on everything it can * run prettier on all files * improve error output If you minify the code, then it could happen that the errors are a bit obscure. This will hardcode the component name to improve errors. * add the `prettier-plugin-tailwindcss` plugin, party! * update changelog
This commit is contained in:
@@ -46,4 +46,3 @@ yarn vue test
|
|||||||
```
|
```
|
||||||
|
|
||||||
Please ensure that the tests are passing when submitting a pull request. If you're adding new features to Headless UI, please include tests.
|
Please ensure that the tests are passing when submitting a pull request. If you're adding new features to Headless UI, please include tests.
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,3 @@ contact_links:
|
|||||||
- name: Documentation Issue
|
- name: Documentation Issue
|
||||||
url: https://github.com/tailwindlabs/headlessui/issues/new?title=%5BDOCS%5D:%20
|
url: https://github.com/tailwindlabs/headlessui/issues/new?title=%5BDOCS%5D:%20
|
||||||
about: 'For documentation issues, suggest changes on our documentation repository.'
|
about: 'For documentation issues, suggest changes on our documentation repository.'
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
id: vars
|
id: vars
|
||||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
- name: "Version based on commit: 0.0.0-insiders.${{ steps.vars.outputs.sha_short }}"
|
- name: 'Version based on commit: 0.0.0-insiders.${{ steps.vars.outputs.sha_short }}'
|
||||||
run: npm version -w packages 0.0.0-insiders.${{ steps.vars.outputs.sha_short }} --force --no-git-tag-version
|
run: npm version -w packages 0.0.0-insiders.${{ steps.vars.outputs.sha_short }} --force --no-git-tag-version
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
@@ -52,4 +52,3 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
coverage/
|
||||||
|
.next/
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"minify": false,
|
||||||
|
"jsc": {
|
||||||
|
"parser": {
|
||||||
|
"syntax": "typescript",
|
||||||
|
"tsx": true,
|
||||||
|
"decorators": false,
|
||||||
|
"dynamicImport": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-4
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Ensure correct order when conditionally rendering `Menu.Item`, `Listbox.Option` and `RadioGroup.Option` ([#1045](https://github.com/tailwindlabs/headlessui/pull/1045))
|
- Ensure correct order when conditionally rendering `Menu.Item`, `Listbox.Option` and `RadioGroup.Option` ([#1045](https://github.com/tailwindlabs/headlessui/pull/1045))
|
||||||
- Improve controlled Tabs behaviour ([#1050](https://github.com/tailwindlabs/headlessui/pull/1050))
|
- Improve controlled Tabs behaviour ([#1050](https://github.com/tailwindlabs/headlessui/pull/1050))
|
||||||
- Improve typeahead search logic ([#1051](https://github.com/tailwindlabs/headlessui/pull/1051))
|
- Improve typeahead search logic ([#1051](https://github.com/tailwindlabs/headlessui/pull/1051))
|
||||||
|
- Improve overal codebase, use modern tech like `esbuild` and TypeScript 4! ([#1055](https://github.com/tailwindlabs/headlessui/pull/1055))
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Ensure correct order when conditionally rendering `MenuItem`, `ListboxOption` and `RadioGroupOption` ([#1045](https://github.com/tailwindlabs/headlessui/pull/1045))
|
- Ensure correct order when conditionally rendering `MenuItem`, `ListboxOption` and `RadioGroupOption` ([#1045](https://github.com/tailwindlabs/headlessui/pull/1045))
|
||||||
- Improve typeahead search logic ([#1051](https://github.com/tailwindlabs/headlessui/pull/1051))
|
- Improve typeahead search logic ([#1051](https://github.com/tailwindlabs/headlessui/pull/1051))
|
||||||
|
- Improve overal codebase, use modern tech like `esbuild` and TypeScript 4! ([#1055](https://github.com/tailwindlabs/headlessui/pull/1055))
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -107,7 +109,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [@headlessui/react@v1.3.0] - 2021-06-21
|
## [@headlessui/react@v1.3.0] - 2021-06-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Ensure that you can use `Transition.Child` when using implicit Transitions ([#503](https://github.com/tailwindlabs/headlessui/pull/503))
|
- Ensure that you can use `Transition.Child` when using implicit Transitions ([#503](https://github.com/tailwindlabs/headlessui/pull/503))
|
||||||
- Add new `entered` prop for `Transition` and `Transition.Child` components ([#504](https://github.com/tailwindlabs/headlessui/pull/504))
|
- Add new `entered` prop for `Transition` and `Transition.Child` components ([#504](https://github.com/tailwindlabs/headlessui/pull/504))
|
||||||
@@ -127,7 +129,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [@headlessui/vue@v1.3.0] - 2021-06-21
|
## [@headlessui/vue@v1.3.0] - 2021-06-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Ensure that you can use `TransitionChild` when using implicit Transitions ([#503](https://github.com/tailwindlabs/headlessui/pull/503))
|
- Ensure that you can use `TransitionChild` when using implicit Transitions ([#503](https://github.com/tailwindlabs/headlessui/pull/503))
|
||||||
- Add new `entered` prop for `Transition` and `TransitionChild` components ([#504](https://github.com/tailwindlabs/headlessui/pull/504))
|
- Add new `entered` prop for `Transition` and `TransitionChild` components ([#504](https://github.com/tailwindlabs/headlessui/pull/504))
|
||||||
@@ -141,7 +143,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [@headlessui/react@v1.2.0] - 2021-05-10
|
## [@headlessui/react@v1.2.0] - 2021-05-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Introduce Open/Closed state, to simplify component communication ([#466](https://github.com/tailwindlabs/headlessui/pull/466))
|
- Introduce Open/Closed state, to simplify component communication ([#466](https://github.com/tailwindlabs/headlessui/pull/466))
|
||||||
|
|
||||||
@@ -153,7 +155,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [@headlessui/vue@v1.2.0] - 2021-05-10
|
## [@headlessui/vue@v1.2.0] - 2021-05-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Introduce Open/Closed state, to simplify component communication ([#466](https://github.com/tailwindlabs/headlessui/pull/466))
|
- Introduce Open/Closed state, to simplify component communication ([#466](https://github.com/tailwindlabs/headlessui/pull/466))
|
||||||
|
|
||||||
|
|||||||
@@ -49,4 +49,3 @@ For casual chit-chat with others using the library:
|
|||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
If you're interested in contributing to Headless UI, please read our [contributing docs](https://github.com/tailwindlabs/headlessui/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**.
|
If you're interested in contributing to Headless UI, please read our [contributing docs](https://github.com/tailwindlabs/headlessui/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**.
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
const { createJestConfig: create } = require('tsdx/dist/createJestConfig')
|
|
||||||
|
|
||||||
module.exports = function createJestConfig(root, options) {
|
module.exports = function createJestConfig(root, options) {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
{},
|
|
||||||
create(undefined, root),
|
|
||||||
{
|
{
|
||||||
rootDir: root,
|
rootDir: root,
|
||||||
setupFilesAfterEnv: ['<rootDir>../../jest/custom-matchers.ts'],
|
setupFilesAfterEnv: ['<rootDir>../../jest/custom-matchers.ts'],
|
||||||
globals: {
|
transform: {
|
||||||
'ts-jest': {
|
'^.+\\.(t|j)sx?$': '@swc/jest',
|
||||||
isolatedModules: true,
|
|
||||||
tsConfig: '<rootDir>/tsconfig.tsdx.json',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options
|
options
|
||||||
|
|||||||
+18
-6
@@ -14,10 +14,13 @@
|
|||||||
"react-playground": "yarn workspace playground-react dev",
|
"react-playground": "yarn workspace playground-react dev",
|
||||||
"playground-react": "yarn workspace playground-react dev",
|
"playground-react": "yarn workspace playground-react dev",
|
||||||
"vue": "yarn workspace @headlessui/vue",
|
"vue": "yarn workspace @headlessui/vue",
|
||||||
"shared": "yarn workspace @headlessui/shared",
|
"playground-vue": "yarn workspace playground-vue dev",
|
||||||
|
"vue-playground": "yarn workspace playground-vue dev",
|
||||||
|
"clean": "yarn workspaces run clean",
|
||||||
"build": "yarn workspaces run build",
|
"build": "yarn workspaces run build",
|
||||||
"test": "./scripts/test.sh",
|
"test": "./scripts/test.sh",
|
||||||
"lint": "./scripts/lint.sh"
|
"lint": "./scripts/lint.sh",
|
||||||
|
"lint-check": "CI=true ./scripts/lint.sh"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@@ -25,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,jsx,ts,tsx}": "tsdx lint"
|
"*": "yarn lint-check"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
@@ -34,12 +37,21 @@
|
|||||||
"trailingComma": "es5"
|
"trailingComma": "es5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@swc/core": "^1.2.131",
|
||||||
|
"@swc/jest": "^0.2.17",
|
||||||
"@testing-library/jest-dom": "^5.11.9",
|
"@testing-library/jest-dom": "^5.11.9",
|
||||||
"@types/node": "^14.14.22",
|
"@types/node": "^14.14.22",
|
||||||
|
"esbuild": "^0.14.11",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
|
"jest": "26",
|
||||||
"lint-staged": "^12.2.1",
|
"lint-staged": "^12.2.1",
|
||||||
"tsdx": "^0.14.1",
|
"npm-run-all": "^4.1.5",
|
||||||
"tslib": "^2.1.0",
|
"prettier": "^2.5.1",
|
||||||
"typescript": "^3.9.7"
|
"rimraf": "^3.0.2",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
|
"typescript": "^4.5.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"prettier-plugin-tailwindcss": "^0.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,4 +36,3 @@ For help, discussion about best practices, or any other conversation that would
|
|||||||
For casual chit-chat with others using the library:
|
For casual chit-chat with others using the library:
|
||||||
|
|
||||||
[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe)
|
[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
module.exports = require('./headlessui.prod.cjs.js')
|
||||||
|
} else {
|
||||||
|
module.exports = require('./headlessui.dev.cjs.js')
|
||||||
|
}
|
||||||
@@ -4,12 +4,21 @@
|
|||||||
"description": "A set of completely unstyled, fully accessible UI components for React, designed to integrate beautifully with Tailwind CSS.",
|
"description": "A set of completely unstyled, fully accessible UI components for React, designed to integrate beautifully with Tailwind CSS.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"module": "dist/index.esm.js",
|
"module": "dist/headlessui.esm.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
"README.md",
|
"README.md",
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": {
|
||||||
|
"default": "./dist/headlessui.esm.js"
|
||||||
|
},
|
||||||
|
"require": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -24,10 +33,12 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepublishOnly": "npm run build",
|
"prepublishOnly": "npm run build",
|
||||||
|
"build": "../../scripts/build.sh --external:react --external:react-dom",
|
||||||
|
"watch": "../../scripts/watch.sh --external:react --external:react-dom",
|
||||||
"test": "../../scripts/test.sh",
|
"test": "../../scripts/test.sh",
|
||||||
"build": "../../scripts/build.sh",
|
"lint": "../../scripts/lint.sh",
|
||||||
"watch": "../../scripts/watch.sh",
|
"playground": "yarn workspace playground-react dev",
|
||||||
"lint": "../../scripts/lint.sh"
|
"clean": "rimraf ./dist"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16 || ^17 || ^18",
|
"react": "^16 || ^17 || ^18",
|
||||||
@@ -39,6 +50,7 @@
|
|||||||
"@types/react-dom": "^16.9.10",
|
"@types/react-dom": "^16.9.10",
|
||||||
"react": "^16.14.0",
|
"react": "^16.14.0",
|
||||||
"react-dom": "^16.14.0",
|
"react-dom": "^16.14.0",
|
||||||
"snapshot-diff": "^0.8.1"
|
"snapshot-diff": "^0.8.1",
|
||||||
|
"esbuild": "^0.11.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -481,7 +481,7 @@ describe('Rendering', () => {
|
|||||||
<Combobox.Input onChange={NOOP} />
|
<Combobox.Input onChange={NOOP} />
|
||||||
<Combobox.Button>Trigger</Combobox.Button>
|
<Combobox.Button>Trigger</Combobox.Button>
|
||||||
<Combobox.Options>
|
<Combobox.Options>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<Combobox.Option value="a">{JSON.stringify(data)}</Combobox.Option>
|
<Combobox.Option value="a">{JSON.stringify(data)}</Combobox.Option>
|
||||||
</>
|
</>
|
||||||
@@ -639,10 +639,10 @@ describe('Rendering composition', () => {
|
|||||||
<Combobox.Input onChange={NOOP} />
|
<Combobox.Input onChange={NOOP} />
|
||||||
<Combobox.Button>Trigger</Combobox.Button>
|
<Combobox.Button>Trigger</Combobox.Button>
|
||||||
<Combobox.Options>
|
<Combobox.Options>
|
||||||
<Combobox.Option value="a" className={bag => JSON.stringify(bag)}>
|
<Combobox.Option value="a" className={(bag) => JSON.stringify(bag)}>
|
||||||
Option A
|
Option A
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
<Combobox.Option value="b" disabled className={bag => JSON.stringify(bag)}>
|
<Combobox.Option value="b" disabled className={(bag) => JSON.stringify(bag)}>
|
||||||
Option B
|
Option B
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
<Combobox.Option value="c" className="no-special-treatment">
|
<Combobox.Option value="c" className="no-special-treatment">
|
||||||
@@ -738,7 +738,7 @@ describe('Rendering composition', () => {
|
|||||||
await click(getComboboxButton())
|
await click(getComboboxButton())
|
||||||
|
|
||||||
// Verify options are buttons now
|
// Verify options are buttons now
|
||||||
getComboboxOptions().forEach(option => assertComboboxOption(option, { tag: 'button' }))
|
getComboboxOptions().forEach((option) => assertComboboxOption(option, { tag: 'button' }))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -767,7 +767,7 @@ describe('Composition', () => {
|
|||||||
<Debug name="Transition" fn={orderFn} />
|
<Debug name="Transition" fn={orderFn} />
|
||||||
<Combobox.Options>
|
<Combobox.Options>
|
||||||
<Combobox.Option value="a">
|
<Combobox.Option value="a">
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
{JSON.stringify(data)}
|
{JSON.stringify(data)}
|
||||||
<Debug name="Combobox.Option" fn={orderFn} />
|
<Debug name="Combobox.Option" fn={orderFn} />
|
||||||
@@ -855,7 +855,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option, { selected: false }))
|
options.forEach((option) => assertComboboxOption(option, { selected: false }))
|
||||||
|
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
assertNoSelectedComboboxOption()
|
assertNoSelectedComboboxOption()
|
||||||
@@ -1026,7 +1026,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<Combobox.Input onChange={NOOP} />
|
<Combobox.Input onChange={NOOP} />
|
||||||
<Combobox.Button>Trigger</Combobox.Button>
|
<Combobox.Button>Trigger</Combobox.Button>
|
||||||
<Combobox.Options>
|
<Combobox.Options>
|
||||||
{myOptions.map(myOption => (
|
{myOptions.map((myOption) => (
|
||||||
<Combobox.Option key={myOption.id} value={myOption}>
|
<Combobox.Option key={myOption.id} value={myOption}>
|
||||||
{myOption.name}
|
{myOption.name}
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
@@ -1142,7 +1142,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1383,7 +1383,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// Verify that the first combobox option is active
|
// Verify that the first combobox option is active
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
@@ -1539,7 +1539,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// Verify that the first combobox option is active
|
// Verify that the first combobox option is active
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
@@ -1695,7 +1695,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
@@ -1843,7 +1843,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[0])
|
assertActiveComboboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1890,7 +1890,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
@@ -2039,7 +2039,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[0])
|
assertActiveComboboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -2059,7 +2059,7 @@ describe('Keyboard interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Combobox
|
<Combobox
|
||||||
value={value}
|
value={value}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -2305,7 +2305,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// Verify that the first combobox option is active
|
// Verify that the first combobox option is active
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
@@ -2446,7 +2446,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2496,7 +2496,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2536,7 +2536,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// Open combobox
|
// Open combobox
|
||||||
@@ -2587,7 +2587,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// Verify that the first combobox option is active
|
// Verify that the first combobox option is active
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
@@ -2729,7 +2729,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2779,7 +2779,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2819,7 +2819,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// Open combobox
|
// Open combobox
|
||||||
@@ -2869,7 +2869,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
@@ -3017,7 +3017,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[0])
|
assertActiveComboboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -3053,7 +3053,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// Going up or down should select the single available option
|
// Going up or down should select the single available option
|
||||||
@@ -3108,7 +3108,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -3167,7 +3167,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
@@ -3316,7 +3316,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[0])
|
assertActiveComboboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -3894,14 +3894,16 @@ describe('Keyboard interactions', () => {
|
|||||||
let filteredPeople =
|
let filteredPeople =
|
||||||
query === ''
|
query === ''
|
||||||
? props.people
|
? props.people
|
||||||
: props.people.filter(person => person.name.toLowerCase().includes(query.toLowerCase()))
|
: props.people.filter((person) =>
|
||||||
|
person.name.toLowerCase().includes(query.toLowerCase())
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Combobox value={value} onChange={setValue}>
|
<Combobox value={value} onChange={setValue}>
|
||||||
<Combobox.Input onChange={event => setQuery(event.target.value)} />
|
<Combobox.Input onChange={(event) => setQuery(event.target.value)} />
|
||||||
<Combobox.Button>Trigger</Combobox.Button>
|
<Combobox.Button>Trigger</Combobox.Button>
|
||||||
<Combobox.Options>
|
<Combobox.Options>
|
||||||
{filteredPeople.map(person => (
|
{filteredPeople.map((person) => (
|
||||||
<Combobox.Option key={person.value} value={person.value} disabled={person.disabled}>
|
<Combobox.Option key={person.value} value={person.value} disabled={person.disabled}>
|
||||||
{person.name}
|
{person.name}
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
@@ -4207,7 +4209,7 @@ describe('Mouse interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -4752,7 +4754,7 @@ describe('Mouse interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Combobox
|
<Combobox
|
||||||
value={value}
|
value={value}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -4804,7 +4806,7 @@ describe('Mouse interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Combobox
|
<Combobox
|
||||||
value={value}
|
value={value}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -123,8 +123,8 @@ let reducers: {
|
|||||||
let activeOptionIndex = calculateActiveIndex(action, {
|
let activeOptionIndex = calculateActiveIndex(action, {
|
||||||
resolveItems: () => state.options,
|
resolveItems: () => state.options,
|
||||||
resolveActiveIndex: () => state.activeOptionIndex,
|
resolveActiveIndex: () => state.activeOptionIndex,
|
||||||
resolveId: item => item.id,
|
resolveId: (item) => item.id,
|
||||||
resolveDisabled: item => item.dataRef.current.disabled,
|
resolveDisabled: (item) => item.dataRef.current.disabled,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (state.activeOptionIndex === activeOptionIndex) return state
|
if (state.activeOptionIndex === activeOptionIndex) return state
|
||||||
@@ -163,7 +163,7 @@ let reducers: {
|
|||||||
let currentActiveOption =
|
let currentActiveOption =
|
||||||
state.activeOptionIndex !== null ? nextOptions[state.activeOptionIndex] : null
|
state.activeOptionIndex !== null ? nextOptions[state.activeOptionIndex] : null
|
||||||
|
|
||||||
let idx = nextOptions.findIndex(a => a.id === action.id)
|
let idx = nextOptions.findIndex((a) => a.id === action.id)
|
||||||
|
|
||||||
if (idx !== -1) nextOptions.splice(idx, 1)
|
if (idx !== -1) nextOptions.splice(idx, 1)
|
||||||
|
|
||||||
@@ -284,12 +284,13 @@ export function Combobox<TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG,
|
|||||||
}, [onChange, propsRef])
|
}, [onChange, propsRef])
|
||||||
|
|
||||||
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetDisabled, disabled }), [disabled])
|
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetDisabled, disabled }), [disabled])
|
||||||
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetOrientation, orientation }), [
|
useIsoMorphicEffect(
|
||||||
orientation,
|
() => dispatch({ type: ActionTypes.SetOrientation, orientation }),
|
||||||
])
|
[orientation]
|
||||||
|
)
|
||||||
|
|
||||||
// Handle outside click
|
// Handle outside click
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
|
|
||||||
if (comboboxState !== ComboboxStates.Open) return
|
if (comboboxState !== ComboboxStates.Open) return
|
||||||
@@ -333,7 +334,7 @@ export function Combobox<TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG,
|
|||||||
|
|
||||||
let selectOption = useCallback(
|
let selectOption = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
let option = options.find(item => item.id === id)
|
let option = options.find((item) => item.id === id)
|
||||||
if (!option) return
|
if (!option) return
|
||||||
|
|
||||||
let { dataRef } = option
|
let { dataRef } = option
|
||||||
@@ -418,7 +419,7 @@ let Input = forwardRefWithAs(function Input<
|
|||||||
ref: Ref<HTMLInputElement>
|
ref: Ref<HTMLInputElement>
|
||||||
) {
|
) {
|
||||||
let { value, onChange, displayValue, ...passThroughProps } = props
|
let { value, onChange, displayValue, ...passThroughProps } = props
|
||||||
let [state, dispatch] = useComboboxContext([Combobox.name, Input.name].join('.'))
|
let [state, dispatch] = useComboboxContext('Combobox.Input')
|
||||||
let actions = useComboboxActions()
|
let actions = useComboboxActions()
|
||||||
|
|
||||||
let inputRef = useSyncRefs(state.inputRef, ref)
|
let inputRef = useSyncRefs(state.inputRef, ref)
|
||||||
@@ -579,7 +580,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
|
|||||||
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
||||||
ref: Ref<HTMLButtonElement>
|
ref: Ref<HTMLButtonElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = useComboboxContext([Combobox.name, Button.name].join('.'))
|
let [state, dispatch] = useComboboxContext('Combobox.Button')
|
||||||
let actions = useComboboxActions()
|
let actions = useComboboxActions()
|
||||||
let buttonRef = useSyncRefs(state.buttonRef, ref)
|
let buttonRef = useSyncRefs(state.buttonRef, ref)
|
||||||
|
|
||||||
@@ -693,12 +694,13 @@ type LabelPropsWeControl = 'id' | 'ref' | 'onClick'
|
|||||||
function Label<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
|
function Label<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
|
||||||
props: Props<TTag, LabelRenderPropArg, LabelPropsWeControl>
|
props: Props<TTag, LabelRenderPropArg, LabelPropsWeControl>
|
||||||
) {
|
) {
|
||||||
let [state] = useComboboxContext([Combobox.name, Label.name].join('.'))
|
let [state] = useComboboxContext('Combobox.Label')
|
||||||
let id = `headlessui-combobox-label-${useId()}`
|
let id = `headlessui-combobox-label-${useId()}`
|
||||||
|
|
||||||
let handleClick = useCallback(() => state.inputRef.current?.focus({ preventScroll: true }), [
|
let handleClick = useCallback(
|
||||||
state.inputRef,
|
() => state.inputRef.current?.focus({ preventScroll: true }),
|
||||||
])
|
[state.inputRef]
|
||||||
|
)
|
||||||
|
|
||||||
let slot = useMemo<LabelRenderPropArg>(
|
let slot = useMemo<LabelRenderPropArg>(
|
||||||
() => ({ open: state.comboboxState === ComboboxStates.Open, disabled: state.disabled }),
|
() => ({ open: state.comboboxState === ComboboxStates.Open, disabled: state.disabled }),
|
||||||
@@ -737,7 +739,7 @@ let Options = forwardRefWithAs(function Options<
|
|||||||
PropsForFeatures<typeof OptionsRenderFeatures>,
|
PropsForFeatures<typeof OptionsRenderFeatures>,
|
||||||
ref: Ref<HTMLUListElement>
|
ref: Ref<HTMLUListElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = useComboboxContext([Combobox.name, Options.name].join('.'))
|
let [state, dispatch] = useComboboxContext('Combobox.Options')
|
||||||
let optionsRef = useSyncRefs(state.optionsRef, ref)
|
let optionsRef = useSyncRefs(state.optionsRef, ref)
|
||||||
|
|
||||||
let id = `headlessui-combobox-options-${useId()}`
|
let id = `headlessui-combobox-options-${useId()}`
|
||||||
@@ -751,10 +753,10 @@ let Options = forwardRefWithAs(function Options<
|
|||||||
return state.comboboxState === ComboboxStates.Open
|
return state.comboboxState === ComboboxStates.Open
|
||||||
})()
|
})()
|
||||||
|
|
||||||
let labelledby = useComputed(() => state.labelRef.current?.id ?? state.buttonRef.current?.id, [
|
let labelledby = useComputed(
|
||||||
state.labelRef.current,
|
() => state.labelRef.current?.id ?? state.buttonRef.current?.id,
|
||||||
state.buttonRef.current,
|
[state.labelRef.current, state.buttonRef.current]
|
||||||
])
|
)
|
||||||
|
|
||||||
let handleLeave = useCallback(() => {
|
let handleLeave = useCallback(() => {
|
||||||
if (state.comboboxState !== ComboboxStates.Open) return
|
if (state.comboboxState !== ComboboxStates.Open) return
|
||||||
@@ -820,7 +822,7 @@ function Option<
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
let { disabled = false, value, ...passthroughProps } = props
|
let { disabled = false, value, ...passthroughProps } = props
|
||||||
let [state, dispatch] = useComboboxContext([Combobox.name, Option.name].join('.'))
|
let [state, dispatch] = useComboboxContext('Combobox.Option')
|
||||||
let actions = useComboboxActions()
|
let actions = useComboboxActions()
|
||||||
let id = `headlessui-combobox-option-${useId()}`
|
let id = `headlessui-combobox-option-${useId()}`
|
||||||
let active =
|
let active =
|
||||||
@@ -886,11 +888,10 @@ function Option<
|
|||||||
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Nothing })
|
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Nothing })
|
||||||
}, [disabled, active, dispatch])
|
}, [disabled, active, dispatch])
|
||||||
|
|
||||||
let slot = useMemo<OptionRenderPropArg>(() => ({ active, selected, disabled }), [
|
let slot = useMemo<OptionRenderPropArg>(
|
||||||
active,
|
() => ({ active, selected, disabled }),
|
||||||
selected,
|
[active, selected, disabled]
|
||||||
disabled,
|
)
|
||||||
])
|
|
||||||
|
|
||||||
let propsWeControl = {
|
let propsWeControl = {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ export function useDescriptions(): [
|
|||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
return function DescriptionProvider(props: DescriptionProviderProps) {
|
return function DescriptionProvider(props: DescriptionProviderProps) {
|
||||||
let register = useCallback((value: string) => {
|
let register = useCallback((value: string) => {
|
||||||
setDescriptionIds(existing => [...existing, value])
|
setDescriptionIds((existing) => [...existing, value])
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
setDescriptionIds(existing => {
|
setDescriptionIds((existing) => {
|
||||||
let clone = existing.slice()
|
let clone = existing.slice()
|
||||||
let idx = clone.indexOf(value)
|
let idx = clone.indexOf(value)
|
||||||
if (idx !== -1) clone.splice(idx, 1)
|
if (idx !== -1) clone.splice(idx, 1)
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ describe('Rendering', () => {
|
|||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre>{JSON.stringify(data)}</pre>
|
<pre>{JSON.stringify(data)}</pre>
|
||||||
<TabSentinel />
|
<TabSentinel />
|
||||||
@@ -204,7 +204,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
|
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
|
||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen} unmount={false}>
|
<Dialog open={isOpen} onClose={setIsOpen} unmount={false}>
|
||||||
@@ -239,7 +239,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
|
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
|
||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ describe('Rendering', () => {
|
|||||||
let [isOpen, setIsOpen] = useState(false)
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
|
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
|
||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
@@ -400,7 +400,7 @@ describe('Keyboard interactions', () => {
|
|||||||
let [isOpen, setIsOpen] = useState(false)
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
|
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
|
||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
@@ -438,7 +438,7 @@ describe('Keyboard interactions', () => {
|
|||||||
let [isOpen, setIsOpen] = useState(false)
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
|
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
|
||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
@@ -477,14 +477,14 @@ describe('Keyboard interactions', () => {
|
|||||||
let [isOpen, setIsOpen] = useState(false)
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
|
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
|
||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
Contents
|
Contents
|
||||||
<input
|
<input
|
||||||
id="name"
|
id="name"
|
||||||
onKeyDown={event => {
|
onKeyDown={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
}}
|
}}
|
||||||
@@ -525,7 +525,7 @@ describe('Mouse interactions', () => {
|
|||||||
let [isOpen, setIsOpen] = useState(false)
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
|
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
|
||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
@@ -559,7 +559,7 @@ describe('Mouse interactions', () => {
|
|||||||
let [isOpen, setIsOpen] = useState(false)
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
|
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
|
||||||
Trigger
|
Trigger
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
@@ -595,7 +595,7 @@ describe('Mouse interactions', () => {
|
|||||||
let [isOpen, setIsOpen] = useState(false)
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button onClick={() => setIsOpen(v => !v)}>Trigger</button>
|
<button onClick={() => setIsOpen((v) => !v)}>Trigger</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
Contents
|
Contents
|
||||||
<TabSentinel />
|
<TabSentinel />
|
||||||
@@ -630,7 +630,7 @@ describe('Mouse interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button>Hello</button>
|
<button>Hello</button>
|
||||||
<button onClick={() => setIsOpen(v => !v)}>Trigger</button>
|
<button onClick={() => setIsOpen((v) => !v)}>Trigger</button>
|
||||||
<Dialog open={isOpen} onClose={setIsOpen}>
|
<Dialog open={isOpen} onClose={setIsOpen}>
|
||||||
Contents
|
Contents
|
||||||
<TabSentinel />
|
<TabSentinel />
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ let DialogRoot = forwardRefWithAs(function Dialog<
|
|||||||
useInertOthers(internalDialogRef, hasNestedDialogs ? enabled : false)
|
useInertOthers(internalDialogRef, hasNestedDialogs ? enabled : false)
|
||||||
|
|
||||||
// Handle outside click
|
// Handle outside click
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
|
|
||||||
if (dialogState !== DialogStates.Open) return
|
if (dialogState !== DialogStates.Open) return
|
||||||
@@ -217,7 +217,7 @@ let DialogRoot = forwardRefWithAs(function Dialog<
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Handle `Escape` to close
|
// Handle `Escape` to close
|
||||||
useWindowEvent('keydown', event => {
|
useWindowEvent('keydown', (event) => {
|
||||||
if (event.key !== Keys.Escape) return
|
if (event.key !== Keys.Escape) return
|
||||||
if (dialogState !== DialogStates.Open) return
|
if (dialogState !== DialogStates.Open) return
|
||||||
if (hasNestedDialogs) return
|
if (hasNestedDialogs) return
|
||||||
@@ -250,7 +250,7 @@ let DialogRoot = forwardRefWithAs(function Dialog<
|
|||||||
if (dialogState !== DialogStates.Open) return
|
if (dialogState !== DialogStates.Open) return
|
||||||
if (!internalDialogRef.current) return
|
if (!internalDialogRef.current) return
|
||||||
|
|
||||||
let observer = new IntersectionObserver(entries => {
|
let observer = new IntersectionObserver((entries) => {
|
||||||
for (let entry of entries) {
|
for (let entry of entries) {
|
||||||
if (
|
if (
|
||||||
entry.boundingClientRect.x === 0 &&
|
entry.boundingClientRect.x === 0 &&
|
||||||
@@ -277,9 +277,10 @@ let DialogRoot = forwardRefWithAs(function Dialog<
|
|||||||
[dialogState, state, close, setTitleId]
|
[dialogState, state, close, setTitleId]
|
||||||
)
|
)
|
||||||
|
|
||||||
let slot = useMemo<DialogRenderPropArg>(() => ({ open: dialogState === DialogStates.Open }), [
|
let slot = useMemo<DialogRenderPropArg>(
|
||||||
dialogState,
|
() => ({ open: dialogState === DialogStates.Open }),
|
||||||
])
|
[dialogState]
|
||||||
|
)
|
||||||
|
|
||||||
let propsWeControl = {
|
let propsWeControl = {
|
||||||
ref: dialogRef,
|
ref: dialogRef,
|
||||||
@@ -304,11 +305,11 @@ let DialogRoot = forwardRefWithAs(function Dialog<
|
|||||||
match(message, {
|
match(message, {
|
||||||
[StackMessage.Add]() {
|
[StackMessage.Add]() {
|
||||||
containers.current.add(element)
|
containers.current.add(element)
|
||||||
setNestedDialogCount(count => count + 1)
|
setNestedDialogCount((count) => count + 1)
|
||||||
},
|
},
|
||||||
[StackMessage.Remove]() {
|
[StackMessage.Remove]() {
|
||||||
containers.current.add(element)
|
containers.current.add(element)
|
||||||
setNestedDialogCount(count => count - 1)
|
setNestedDialogCount((count) => count - 1)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [])}
|
}, [])}
|
||||||
@@ -348,7 +349,7 @@ type OverlayPropsWeControl = 'id' | 'aria-hidden' | 'onClick'
|
|||||||
let Overlay = forwardRefWithAs(function Overlay<
|
let Overlay = forwardRefWithAs(function Overlay<
|
||||||
TTag extends ElementType = typeof DEFAULT_OVERLAY_TAG
|
TTag extends ElementType = typeof DEFAULT_OVERLAY_TAG
|
||||||
>(props: Props<TTag, OverlayRenderPropArg, OverlayPropsWeControl>, ref: Ref<HTMLDivElement>) {
|
>(props: Props<TTag, OverlayRenderPropArg, OverlayPropsWeControl>, ref: Ref<HTMLDivElement>) {
|
||||||
let [{ dialogState, close }] = useDialogContext([Dialog.displayName, Overlay.name].join('.'))
|
let [{ dialogState, close }] = useDialogContext('Dialog.Overlay')
|
||||||
let overlayRef = useSyncRefs(ref)
|
let overlayRef = useSyncRefs(ref)
|
||||||
|
|
||||||
let id = `headlessui-dialog-overlay-${useId()}`
|
let id = `headlessui-dialog-overlay-${useId()}`
|
||||||
@@ -364,9 +365,10 @@ let Overlay = forwardRefWithAs(function Overlay<
|
|||||||
[close]
|
[close]
|
||||||
)
|
)
|
||||||
|
|
||||||
let slot = useMemo<OverlayRenderPropArg>(() => ({ open: dialogState === DialogStates.Open }), [
|
let slot = useMemo<OverlayRenderPropArg>(
|
||||||
dialogState,
|
() => ({ open: dialogState === DialogStates.Open }),
|
||||||
])
|
[dialogState]
|
||||||
|
)
|
||||||
let propsWeControl = {
|
let propsWeControl = {
|
||||||
ref: overlayRef,
|
ref: overlayRef,
|
||||||
id,
|
id,
|
||||||
@@ -394,7 +396,7 @@ type TitlePropsWeControl = 'id'
|
|||||||
function Title<TTag extends ElementType = typeof DEFAULT_TITLE_TAG>(
|
function Title<TTag extends ElementType = typeof DEFAULT_TITLE_TAG>(
|
||||||
props: Props<TTag, TitleRenderPropArg, TitlePropsWeControl>
|
props: Props<TTag, TitleRenderPropArg, TitlePropsWeControl>
|
||||||
) {
|
) {
|
||||||
let [{ dialogState, setTitleId }] = useDialogContext([Dialog.displayName, Title.name].join('.'))
|
let [{ dialogState, setTitleId }] = useDialogContext('Dialog.Title')
|
||||||
|
|
||||||
let id = `headlessui-dialog-title-${useId()}`
|
let id = `headlessui-dialog-title-${useId()}`
|
||||||
|
|
||||||
@@ -403,9 +405,10 @@ function Title<TTag extends ElementType = typeof DEFAULT_TITLE_TAG>(
|
|||||||
return () => setTitleId(null)
|
return () => setTitleId(null)
|
||||||
}, [id, setTitleId])
|
}, [id, setTitleId])
|
||||||
|
|
||||||
let slot = useMemo<TitleRenderPropArg>(() => ({ open: dialogState === DialogStates.Open }), [
|
let slot = useMemo<TitleRenderPropArg>(
|
||||||
dialogState,
|
() => ({ open: dialogState === DialogStates.Open }),
|
||||||
])
|
[dialogState]
|
||||||
|
)
|
||||||
let propsWeControl = { id }
|
let propsWeControl = { id }
|
||||||
let passthroughProps = props
|
let passthroughProps = props
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jest.mock('../../hooks/use-id')
|
|||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function nextFrame() {
|
function nextFrame() {
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
resolve()
|
resolve()
|
||||||
|
|||||||
@@ -68,14 +68,14 @@ let reducers: {
|
|||||||
action: Extract<Actions, { type: P }>
|
action: Extract<Actions, { type: P }>
|
||||||
) => StateDefinition
|
) => StateDefinition
|
||||||
} = {
|
} = {
|
||||||
[ActionTypes.ToggleDisclosure]: state => ({
|
[ActionTypes.ToggleDisclosure]: (state) => ({
|
||||||
...state,
|
...state,
|
||||||
disclosureState: match(state.disclosureState, {
|
disclosureState: match(state.disclosureState, {
|
||||||
[DisclosureStates.Open]: DisclosureStates.Closed,
|
[DisclosureStates.Open]: DisclosureStates.Closed,
|
||||||
[DisclosureStates.Closed]: DisclosureStates.Open,
|
[DisclosureStates.Closed]: DisclosureStates.Open,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[ActionTypes.CloseDisclosure]: state => {
|
[ActionTypes.CloseDisclosure]: (state) => {
|
||||||
if (state.disclosureState === DisclosureStates.Closed) return state
|
if (state.disclosureState === DisclosureStates.Closed) return state
|
||||||
return { ...state, disclosureState: DisclosureStates.Closed }
|
return { ...state, disclosureState: DisclosureStates.Closed }
|
||||||
},
|
},
|
||||||
@@ -227,7 +227,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
|
|||||||
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
||||||
ref: Ref<HTMLButtonElement>
|
ref: Ref<HTMLButtonElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = useDisclosureContext([Disclosure.name, Button.name].join('.'))
|
let [state, dispatch] = useDisclosureContext('Disclosure.Button')
|
||||||
let internalButtonRef = useRef<HTMLButtonElement | null>(null)
|
let internalButtonRef = useRef<HTMLButtonElement | null>(null)
|
||||||
let buttonRef = useSyncRefs(internalButtonRef, ref)
|
let buttonRef = useSyncRefs(internalButtonRef, ref)
|
||||||
|
|
||||||
@@ -334,8 +334,8 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
|
|||||||
PropsForFeatures<typeof PanelRenderFeatures>,
|
PropsForFeatures<typeof PanelRenderFeatures>,
|
||||||
ref: Ref<HTMLDivElement>
|
ref: Ref<HTMLDivElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = useDisclosureContext([Disclosure.name, Panel.name].join('.'))
|
let [state, dispatch] = useDisclosureContext('Disclosure.Panel')
|
||||||
let { close } = useDisclosureAPIContext([Disclosure.name, Panel.name].join('.'))
|
let { close } = useDisclosureAPIContext('Disclosure.Panel')
|
||||||
|
|
||||||
let panelRef = useSyncRefs(ref, () => {
|
let panelRef = useSyncRefs(ref, () => {
|
||||||
if (state.linkedPanel) return
|
if (state.linkedPanel) return
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ export function useLabels(): [string | undefined, (props: LabelProviderProps) =>
|
|||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
return function LabelProvider(props: LabelProviderProps) {
|
return function LabelProvider(props: LabelProviderProps) {
|
||||||
let register = useCallback((value: string) => {
|
let register = useCallback((value: string) => {
|
||||||
setLabelIds(existing => [...existing, value])
|
setLabelIds((existing) => [...existing, value])
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
setLabelIds(existing => {
|
setLabelIds((existing) => {
|
||||||
let clone = existing.slice()
|
let clone = existing.slice()
|
||||||
let idx = clone.indexOf(value)
|
let idx = clone.indexOf(value)
|
||||||
if (idx !== -1) clone.splice(idx, 1)
|
if (idx !== -1) clone.splice(idx, 1)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ describe('safeguards', () => {
|
|||||||
])(
|
])(
|
||||||
'should error when we are using a <%s /> without a parent <Listbox />',
|
'should error when we are using a <%s /> without a parent <Listbox />',
|
||||||
suppressConsoleLogs((name, Component) => {
|
suppressConsoleLogs((name, Component) => {
|
||||||
|
// @ts-expect-error This is fine
|
||||||
expect(() => render(createElement(Component))).toThrowError(
|
expect(() => render(createElement(Component))).toThrowError(
|
||||||
`<${name} /> is missing a parent <Listbox /> component.`
|
`<${name} /> is missing a parent <Listbox /> component.`
|
||||||
)
|
)
|
||||||
@@ -396,7 +397,7 @@ describe('Rendering', () => {
|
|||||||
<Listbox value={undefined} onChange={console.log}>
|
<Listbox value={undefined} onChange={console.log}>
|
||||||
<Listbox.Button>Trigger</Listbox.Button>
|
<Listbox.Button>Trigger</Listbox.Button>
|
||||||
<Listbox.Options>
|
<Listbox.Options>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<Listbox.Option value="a">{JSON.stringify(data)}</Listbox.Option>
|
<Listbox.Option value="a">{JSON.stringify(data)}</Listbox.Option>
|
||||||
</>
|
</>
|
||||||
@@ -547,10 +548,10 @@ describe('Rendering composition', () => {
|
|||||||
<Listbox value={undefined} onChange={console.log}>
|
<Listbox value={undefined} onChange={console.log}>
|
||||||
<Listbox.Button>Trigger</Listbox.Button>
|
<Listbox.Button>Trigger</Listbox.Button>
|
||||||
<Listbox.Options>
|
<Listbox.Options>
|
||||||
<Listbox.Option value="a" className={bag => JSON.stringify(bag)}>
|
<Listbox.Option value="a" className={(bag) => JSON.stringify(bag)}>
|
||||||
Option A
|
Option A
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
<Listbox.Option value="b" disabled className={bag => JSON.stringify(bag)}>
|
<Listbox.Option value="b" disabled className={(bag) => JSON.stringify(bag)}>
|
||||||
Option B
|
Option B
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
<Listbox.Option value="c" className="no-special-treatment">
|
<Listbox.Option value="c" className="no-special-treatment">
|
||||||
@@ -645,7 +646,7 @@ describe('Rendering composition', () => {
|
|||||||
await click(getListboxButton())
|
await click(getListboxButton())
|
||||||
|
|
||||||
// Verify options are buttons now
|
// Verify options are buttons now
|
||||||
getListboxOptions().forEach(option => assertListboxOption(option, { tag: 'button' }))
|
getListboxOptions().forEach((option) => assertListboxOption(option, { tag: 'button' }))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -673,7 +674,7 @@ describe('Composition', () => {
|
|||||||
<Debug name="Transition" fn={orderFn} />
|
<Debug name="Transition" fn={orderFn} />
|
||||||
<Listbox.Options>
|
<Listbox.Options>
|
||||||
<Listbox.Option value="a">
|
<Listbox.Option value="a">
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
{JSON.stringify(data)}
|
{JSON.stringify(data)}
|
||||||
<Debug name="Listbox.Option" fn={orderFn} />
|
<Debug name="Listbox.Option" fn={orderFn} />
|
||||||
@@ -756,7 +757,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option, { selected: false }))
|
options.forEach((option) => assertListboxOption(option, { selected: false }))
|
||||||
|
|
||||||
// Verify that the first listbox option is active
|
// Verify that the first listbox option is active
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
@@ -918,7 +919,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox value={selectedOption} onChange={console.log}>
|
<Listbox value={selectedOption} onChange={console.log}>
|
||||||
<Listbox.Button>Trigger</Listbox.Button>
|
<Listbox.Button>Trigger</Listbox.Button>
|
||||||
<Listbox.Options>
|
<Listbox.Options>
|
||||||
{myOptions.map(myOption => (
|
{myOptions.map((myOption) => (
|
||||||
<Listbox.Option key={myOption.id} value={myOption}>
|
<Listbox.Option key={myOption.id} value={myOption}>
|
||||||
{myOption.name}
|
{myOption.name}
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
@@ -1139,7 +1140,7 @@ describe('Keyboard interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Listbox
|
<Listbox
|
||||||
value={value}
|
value={value}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -1234,7 +1235,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1462,7 +1463,7 @@ describe('Keyboard interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Listbox
|
<Listbox
|
||||||
value={value}
|
value={value}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -1600,7 +1601,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
|
|
||||||
// Try to tab
|
// Try to tab
|
||||||
@@ -1651,7 +1652,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
|
|
||||||
// Try to Shift+Tab
|
// Try to Shift+Tab
|
||||||
@@ -1704,7 +1705,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
|
|
||||||
// Verify that the first listbox option is active
|
// Verify that the first listbox option is active
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
@@ -1844,7 +1845,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -1892,7 +1893,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[1])
|
assertActiveListboxOption(options[1])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -1934,7 +1935,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1970,7 +1971,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
|
|
||||||
// We should be able to go right once
|
// We should be able to go right once
|
||||||
@@ -2027,7 +2028,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
@@ -2171,7 +2172,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -2209,7 +2210,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
|
|
||||||
// We should not be able to go up (because those are disabled)
|
// We should not be able to go up (because those are disabled)
|
||||||
@@ -2260,7 +2261,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2318,7 +2319,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
|
|
||||||
// We should be able to go left once
|
// We should be able to go left once
|
||||||
@@ -3198,7 +3199,7 @@ describe('Mouse interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -3726,7 +3727,7 @@ describe('Mouse interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Listbox
|
<Listbox
|
||||||
value={value}
|
value={value}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -3777,7 +3778,7 @@ describe('Mouse interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Listbox
|
<Listbox
|
||||||
value={value}
|
value={value}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ let reducers: {
|
|||||||
let activeOptionIndex = calculateActiveIndex(action, {
|
let activeOptionIndex = calculateActiveIndex(action, {
|
||||||
resolveItems: () => state.options,
|
resolveItems: () => state.options,
|
||||||
resolveActiveIndex: () => state.activeOptionIndex,
|
resolveActiveIndex: () => state.activeOptionIndex,
|
||||||
resolveId: item => item.id,
|
resolveId: (item) => item.id,
|
||||||
resolveDisabled: item => item.dataRef.current.disabled,
|
resolveDisabled: (item) => item.dataRef.current.disabled,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (state.searchQuery === '' && state.activeOptionIndex === activeOptionIndex) return state
|
if (state.searchQuery === '' && state.activeOptionIndex === activeOptionIndex) return state
|
||||||
@@ -140,7 +140,7 @@ let reducers: {
|
|||||||
: state.options
|
: state.options
|
||||||
|
|
||||||
let matchingOption = reOrderedOptions.find(
|
let matchingOption = reOrderedOptions.find(
|
||||||
option =>
|
(option) =>
|
||||||
!option.dataRef.current.disabled &&
|
!option.dataRef.current.disabled &&
|
||||||
option.dataRef.current.textValue?.startsWith(searchQuery)
|
option.dataRef.current.textValue?.startsWith(searchQuery)
|
||||||
)
|
)
|
||||||
@@ -175,7 +175,7 @@ let reducers: {
|
|||||||
let currentActiveOption =
|
let currentActiveOption =
|
||||||
state.activeOptionIndex !== null ? nextOptions[state.activeOptionIndex] : null
|
state.activeOptionIndex !== null ? nextOptions[state.activeOptionIndex] : null
|
||||||
|
|
||||||
let idx = nextOptions.findIndex(a => a.id === action.id)
|
let idx = nextOptions.findIndex((a) => a.id === action.id)
|
||||||
|
|
||||||
if (idx !== -1) nextOptions.splice(idx, 1)
|
if (idx !== -1) nextOptions.splice(idx, 1)
|
||||||
|
|
||||||
@@ -251,12 +251,13 @@ export function Listbox<TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG, T
|
|||||||
propsRef.current.onChange = onChange
|
propsRef.current.onChange = onChange
|
||||||
}, [onChange, propsRef])
|
}, [onChange, propsRef])
|
||||||
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetDisabled, disabled }), [disabled])
|
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetDisabled, disabled }), [disabled])
|
||||||
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetOrientation, orientation }), [
|
useIsoMorphicEffect(
|
||||||
orientation,
|
() => dispatch({ type: ActionTypes.SetOrientation, orientation }),
|
||||||
])
|
[orientation]
|
||||||
|
)
|
||||||
|
|
||||||
// Handle outside click
|
// Handle outside click
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
|
|
||||||
if (listboxState !== ListboxStates.Open) return
|
if (listboxState !== ListboxStates.Open) return
|
||||||
@@ -318,7 +319,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
|
|||||||
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
||||||
ref: Ref<HTMLButtonElement>
|
ref: Ref<HTMLButtonElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = useListboxContext([Listbox.name, Button.name].join('.'))
|
let [state, dispatch] = useListboxContext('Listbox.Button')
|
||||||
let buttonRef = useSyncRefs(state.buttonRef, ref)
|
let buttonRef = useSyncRefs(state.buttonRef, ref)
|
||||||
|
|
||||||
let id = `headlessui-listbox-button-${useId()}`
|
let id = `headlessui-listbox-button-${useId()}`
|
||||||
@@ -422,12 +423,13 @@ type LabelPropsWeControl = 'id' | 'ref' | 'onClick'
|
|||||||
function Label<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
|
function Label<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
|
||||||
props: Props<TTag, LabelRenderPropArg, LabelPropsWeControl>
|
props: Props<TTag, LabelRenderPropArg, LabelPropsWeControl>
|
||||||
) {
|
) {
|
||||||
let [state] = useListboxContext([Listbox.name, Label.name].join('.'))
|
let [state] = useListboxContext('Listbox.Label')
|
||||||
let id = `headlessui-listbox-label-${useId()}`
|
let id = `headlessui-listbox-label-${useId()}`
|
||||||
|
|
||||||
let handleClick = useCallback(() => state.buttonRef.current?.focus({ preventScroll: true }), [
|
let handleClick = useCallback(
|
||||||
state.buttonRef,
|
() => state.buttonRef.current?.focus({ preventScroll: true }),
|
||||||
])
|
[state.buttonRef]
|
||||||
|
)
|
||||||
|
|
||||||
let slot = useMemo<LabelRenderPropArg>(
|
let slot = useMemo<LabelRenderPropArg>(
|
||||||
() => ({ open: state.listboxState === ListboxStates.Open, disabled: state.disabled }),
|
() => ({ open: state.listboxState === ListboxStates.Open, disabled: state.disabled }),
|
||||||
@@ -466,7 +468,7 @@ let Options = forwardRefWithAs(function Options<
|
|||||||
PropsForFeatures<typeof OptionsRenderFeatures>,
|
PropsForFeatures<typeof OptionsRenderFeatures>,
|
||||||
ref: Ref<HTMLUListElement>
|
ref: Ref<HTMLUListElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = useListboxContext([Listbox.name, Options.name].join('.'))
|
let [state, dispatch] = useListboxContext('Listbox.Options')
|
||||||
let optionsRef = useSyncRefs(state.optionsRef, ref)
|
let optionsRef = useSyncRefs(state.optionsRef, ref)
|
||||||
|
|
||||||
let id = `headlessui-listbox-options-${useId()}`
|
let id = `headlessui-listbox-options-${useId()}`
|
||||||
@@ -561,10 +563,10 @@ let Options = forwardRefWithAs(function Options<
|
|||||||
[d, dispatch, searchDisposables, state]
|
[d, dispatch, searchDisposables, state]
|
||||||
)
|
)
|
||||||
|
|
||||||
let labelledby = useComputed(() => state.labelRef.current?.id ?? state.buttonRef.current?.id, [
|
let labelledby = useComputed(
|
||||||
state.labelRef.current,
|
() => state.labelRef.current?.id ?? state.buttonRef.current?.id,
|
||||||
state.buttonRef.current,
|
[state.labelRef.current, state.buttonRef.current]
|
||||||
])
|
)
|
||||||
|
|
||||||
let slot = useMemo<OptionsRenderPropArg>(
|
let slot = useMemo<OptionsRenderPropArg>(
|
||||||
() => ({ open: state.listboxState === ListboxStates.Open }),
|
() => ({ open: state.listboxState === ListboxStates.Open }),
|
||||||
@@ -625,7 +627,7 @@ function Option<
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
let { disabled = false, value, ...passthroughProps } = props
|
let { disabled = false, value, ...passthroughProps } = props
|
||||||
let [state, dispatch] = useListboxContext([Listbox.name, Option.name].join('.'))
|
let [state, dispatch] = useListboxContext('Listbox.Option')
|
||||||
let id = `headlessui-listbox-option-${useId()}`
|
let id = `headlessui-listbox-option-${useId()}`
|
||||||
let active =
|
let active =
|
||||||
state.activeOptionIndex !== null ? state.options[state.activeOptionIndex].id === id : false
|
state.activeOptionIndex !== null ? state.options[state.activeOptionIndex].id === id : false
|
||||||
@@ -692,11 +694,10 @@ function Option<
|
|||||||
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Nothing })
|
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Nothing })
|
||||||
}, [disabled, active, dispatch])
|
}, [disabled, active, dispatch])
|
||||||
|
|
||||||
let slot = useMemo<OptionRenderPropArg>(() => ({ active, selected, disabled }), [
|
let slot = useMemo<OptionRenderPropArg>(
|
||||||
active,
|
() => ({ active, selected, disabled }),
|
||||||
selected,
|
[active, selected, disabled]
|
||||||
disabled,
|
)
|
||||||
])
|
|
||||||
let propsWeControl = {
|
let propsWeControl = {
|
||||||
id,
|
id,
|
||||||
role: 'option',
|
role: 'option',
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ describe('Rendering', () => {
|
|||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Button>Trigger</Menu.Button>
|
<Menu.Button>Trigger</Menu.Button>
|
||||||
<Menu.Items>
|
<Menu.Items>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<Menu.Item as="a">{JSON.stringify(data)}</Menu.Item>
|
<Menu.Item as="a">{JSON.stringify(data)}</Menu.Item>
|
||||||
</>
|
</>
|
||||||
@@ -403,10 +403,10 @@ describe('Rendering composition', () => {
|
|||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Button>Trigger</Menu.Button>
|
<Menu.Button>Trigger</Menu.Button>
|
||||||
<Menu.Items>
|
<Menu.Items>
|
||||||
<Menu.Item as="a" className={bag => JSON.stringify(bag)}>
|
<Menu.Item as="a" className={(bag) => JSON.stringify(bag)}>
|
||||||
Item A
|
Item A
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item as="a" disabled className={bag => JSON.stringify(bag)}>
|
<Menu.Item as="a" disabled className={(bag) => JSON.stringify(bag)}>
|
||||||
Item B
|
Item B
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item as="a" className="no-special-treatment">
|
<Menu.Item as="a" className="no-special-treatment">
|
||||||
@@ -484,7 +484,7 @@ describe('Rendering composition', () => {
|
|||||||
|
|
||||||
// Verify items are buttons now
|
// Verify items are buttons now
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
items.forEach(item => assertMenuItem(item, { tag: 'button' }))
|
items.forEach((item) => assertMenuItem(item, { tag: 'button' }))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -496,11 +496,11 @@ describe('Rendering composition', () => {
|
|||||||
<Menu.Button>Trigger</Menu.Button>
|
<Menu.Button>Trigger</Menu.Button>
|
||||||
<div className="outer">
|
<div className="outer">
|
||||||
<Menu.Items>
|
<Menu.Items>
|
||||||
<div className="py-1 inner">
|
<div className="inner py-1">
|
||||||
<Menu.Item as="button">Item A</Menu.Item>
|
<Menu.Item as="button">Item A</Menu.Item>
|
||||||
<Menu.Item as="button">Item B</Menu.Item>
|
<Menu.Item as="button">Item B</Menu.Item>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-1 inner">
|
<div className="inner py-1">
|
||||||
<Menu.Item as="button">Item C</Menu.Item>
|
<Menu.Item as="button">Item C</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<div>
|
<div>
|
||||||
@@ -508,7 +508,7 @@ describe('Rendering composition', () => {
|
|||||||
</div>
|
</div>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-1 inner">
|
<div className="inner py-1">
|
||||||
<form className="inner">
|
<form className="inner">
|
||||||
<Menu.Item as="button">Item E</Menu.Item>
|
<Menu.Item as="button">Item E</Menu.Item>
|
||||||
</form>
|
</form>
|
||||||
@@ -523,11 +523,11 @@ describe('Rendering composition', () => {
|
|||||||
|
|
||||||
expect.hasAssertions()
|
expect.hasAssertions()
|
||||||
|
|
||||||
document.querySelectorAll('.outer').forEach(element => {
|
document.querySelectorAll('.outer').forEach((element) => {
|
||||||
expect(element).not.toHaveAttribute('role', 'none')
|
expect(element).not.toHaveAttribute('role', 'none')
|
||||||
})
|
})
|
||||||
|
|
||||||
document.querySelectorAll('.inner').forEach(element => {
|
document.querySelectorAll('.inner').forEach((element) => {
|
||||||
expect(element).toHaveAttribute('role', 'none')
|
expect(element).toHaveAttribute('role', 'none')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -557,7 +557,7 @@ describe('Composition', () => {
|
|||||||
<Debug name="Transition" fn={orderFn} />
|
<Debug name="Transition" fn={orderFn} />
|
||||||
<Menu.Items>
|
<Menu.Items>
|
||||||
<Menu.Item as="a">
|
<Menu.Item as="a">
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
{JSON.stringify(data)}
|
{JSON.stringify(data)}
|
||||||
<Debug name="Menu.Item" fn={orderFn} />
|
<Debug name="Menu.Item" fn={orderFn} />
|
||||||
@@ -611,7 +611,7 @@ describe('Composition', () => {
|
|||||||
<Debug name="Transition" fn={orderFn} />
|
<Debug name="Transition" fn={orderFn} />
|
||||||
<Menu.Items>
|
<Menu.Items>
|
||||||
<Menu.Item as="a">
|
<Menu.Item as="a">
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
{JSON.stringify(data)}
|
{JSON.stringify(data)}
|
||||||
<Debug name="Menu.Item" fn={orderFn} />
|
<Debug name="Menu.Item" fn={orderFn} />
|
||||||
@@ -693,7 +693,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
|
|
||||||
// Verify that the first menu item is active
|
// Verify that the first menu item is active
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
@@ -1057,7 +1057,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1395,7 +1395,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
|
|
||||||
// Try to tab
|
// Try to tab
|
||||||
@@ -1444,7 +1444,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
|
|
||||||
// Try to Shift+Tab
|
// Try to Shift+Tab
|
||||||
@@ -1495,7 +1495,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
|
|
||||||
// Verify that the first menu item is active
|
// Verify that the first menu item is active
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
@@ -1589,7 +1589,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -1637,7 +1637,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[1])
|
assertMenuLinkedWithMenuItem(items[1])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -1679,7 +1679,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[2])
|
assertMenuLinkedWithMenuItem(items[2])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1723,7 +1723,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
|
|
||||||
// ! ALERT: The LAST item should now be active
|
// ! ALERT: The LAST item should now be active
|
||||||
assertMenuLinkedWithMenuItem(items[2])
|
assertMenuLinkedWithMenuItem(items[2])
|
||||||
@@ -1821,7 +1821,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1859,7 +1859,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[2])
|
assertMenuLinkedWithMenuItem(items[2])
|
||||||
|
|
||||||
// We should not be able to go up (because those are disabled)
|
// We should not be able to go up (because those are disabled)
|
||||||
@@ -1909,7 +1909,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[2])
|
assertMenuLinkedWithMenuItem(items[2])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2736,7 +2736,7 @@ describe('Mouse interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ let reducers: {
|
|||||||
let activeItemIndex = calculateActiveIndex(action, {
|
let activeItemIndex = calculateActiveIndex(action, {
|
||||||
resolveItems: () => state.items,
|
resolveItems: () => state.items,
|
||||||
resolveActiveIndex: () => state.activeItemIndex,
|
resolveActiveIndex: () => state.activeItemIndex,
|
||||||
resolveId: item => item.id,
|
resolveId: (item) => item.id,
|
||||||
resolveDisabled: item => item.dataRef.current.disabled,
|
resolveDisabled: (item) => item.dataRef.current.disabled,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (state.searchQuery === '' && state.activeItemIndex === activeItemIndex) return state
|
if (state.searchQuery === '' && state.activeItemIndex === activeItemIndex) return state
|
||||||
@@ -109,7 +109,7 @@ let reducers: {
|
|||||||
: state.items
|
: state.items
|
||||||
|
|
||||||
let matchingItem = reOrderedItems.find(
|
let matchingItem = reOrderedItems.find(
|
||||||
item =>
|
(item) =>
|
||||||
item.dataRef.current.textValue?.startsWith(searchQuery) && !item.dataRef.current.disabled
|
item.dataRef.current.textValue?.startsWith(searchQuery) && !item.dataRef.current.disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ let reducers: {
|
|||||||
let nextItems = state.items.slice()
|
let nextItems = state.items.slice()
|
||||||
let currentActiveItem = state.activeItemIndex !== null ? nextItems[state.activeItemIndex] : null
|
let currentActiveItem = state.activeItemIndex !== null ? nextItems[state.activeItemIndex] : null
|
||||||
|
|
||||||
let idx = nextItems.findIndex(a => a.id === action.id)
|
let idx = nextItems.findIndex((a) => a.id === action.id)
|
||||||
|
|
||||||
if (idx !== -1) nextItems.splice(idx, 1)
|
if (idx !== -1) nextItems.splice(idx, 1)
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ export function Menu<TTag extends ElementType = typeof DEFAULT_MENU_TAG>(
|
|||||||
let [{ menuState, itemsRef, buttonRef }, dispatch] = reducerBag
|
let [{ menuState, itemsRef, buttonRef }, dispatch] = reducerBag
|
||||||
|
|
||||||
// Handle outside click
|
// Handle outside click
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
|
|
||||||
if (menuState !== MenuStates.Open) return
|
if (menuState !== MenuStates.Open) return
|
||||||
@@ -212,9 +212,10 @@ export function Menu<TTag extends ElementType = typeof DEFAULT_MENU_TAG>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let slot = useMemo<MenuRenderPropArg>(() => ({ open: menuState === MenuStates.Open }), [
|
let slot = useMemo<MenuRenderPropArg>(
|
||||||
menuState,
|
() => ({ open: menuState === MenuStates.Open }),
|
||||||
])
|
[menuState]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuContext.Provider value={reducerBag}>
|
<MenuContext.Provider value={reducerBag}>
|
||||||
@@ -249,7 +250,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
|
|||||||
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
||||||
ref: Ref<HTMLButtonElement>
|
ref: Ref<HTMLButtonElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = useMenuContext([Menu.name, Button.name].join('.'))
|
let [state, dispatch] = useMenuContext('Menu.Button')
|
||||||
let buttonRef = useSyncRefs(state.buttonRef, ref)
|
let buttonRef = useSyncRefs(state.buttonRef, ref)
|
||||||
|
|
||||||
let id = `headlessui-menu-button-${useId()}`
|
let id = `headlessui-menu-button-${useId()}`
|
||||||
@@ -307,9 +308,10 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
|
|||||||
[dispatch, d, state, props.disabled]
|
[dispatch, d, state, props.disabled]
|
||||||
)
|
)
|
||||||
|
|
||||||
let slot = useMemo<ButtonRenderPropArg>(() => ({ open: state.menuState === MenuStates.Open }), [
|
let slot = useMemo<ButtonRenderPropArg>(
|
||||||
state,
|
() => ({ open: state.menuState === MenuStates.Open }),
|
||||||
])
|
[state]
|
||||||
|
)
|
||||||
let passthroughProps = props
|
let passthroughProps = props
|
||||||
let propsWeControl = {
|
let propsWeControl = {
|
||||||
ref: buttonRef,
|
ref: buttonRef,
|
||||||
@@ -352,7 +354,7 @@ let Items = forwardRefWithAs(function Items<TTag extends ElementType = typeof DE
|
|||||||
PropsForFeatures<typeof ItemsRenderFeatures>,
|
PropsForFeatures<typeof ItemsRenderFeatures>,
|
||||||
ref: Ref<HTMLDivElement>
|
ref: Ref<HTMLDivElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = useMenuContext([Menu.name, Items.name].join('.'))
|
let [state, dispatch] = useMenuContext('Menu.Items')
|
||||||
let itemsRef = useSyncRefs(state.itemsRef, ref)
|
let itemsRef = useSyncRefs(state.itemsRef, ref)
|
||||||
|
|
||||||
let id = `headlessui-menu-items-${useId()}`
|
let id = `headlessui-menu-items-${useId()}`
|
||||||
@@ -471,9 +473,10 @@ let Items = forwardRefWithAs(function Items<TTag extends ElementType = typeof DE
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
let slot = useMemo<ItemsRenderPropArg>(() => ({ open: state.menuState === MenuStates.Open }), [
|
let slot = useMemo<ItemsRenderPropArg>(
|
||||||
state,
|
() => ({ open: state.menuState === MenuStates.Open }),
|
||||||
])
|
[state]
|
||||||
|
)
|
||||||
let propsWeControl = {
|
let propsWeControl = {
|
||||||
'aria-activedescendant':
|
'aria-activedescendant':
|
||||||
state.activeItemIndex === null ? undefined : state.items[state.activeItemIndex]?.id,
|
state.activeItemIndex === null ? undefined : state.items[state.activeItemIndex]?.id,
|
||||||
@@ -522,7 +525,7 @@ function Item<TTag extends ElementType = typeof DEFAULT_ITEM_TAG>(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
let { disabled = false, onClick, ...passthroughProps } = props
|
let { disabled = false, onClick, ...passthroughProps } = props
|
||||||
let [state, dispatch] = useMenuContext([Menu.name, Item.name].join('.'))
|
let [state, dispatch] = useMenuContext('Menu.Item')
|
||||||
let id = `headlessui-menu-item-${useId()}`
|
let id = `headlessui-menu-item-${useId()}`
|
||||||
let active = state.activeItemIndex !== null ? state.items[state.activeItemIndex].id === id : false
|
let active = state.activeItemIndex !== null ? state.items[state.activeItemIndex].id === id : false
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jest.mock('../../hooks/use-id')
|
|||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function nextFrame() {
|
function nextFrame() {
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
resolve()
|
resolve()
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ let reducers: {
|
|||||||
action: Extract<Actions, { type: P }>
|
action: Extract<Actions, { type: P }>
|
||||||
) => StateDefinition
|
) => StateDefinition
|
||||||
} = {
|
} = {
|
||||||
[ActionTypes.TogglePopover]: state => ({
|
[ActionTypes.TogglePopover]: (state) => ({
|
||||||
...state,
|
...state,
|
||||||
popoverState: match(state.popoverState, {
|
popoverState: match(state.popoverState, {
|
||||||
[PopoverStates.Open]: PopoverStates.Closed,
|
[PopoverStates.Open]: PopoverStates.Closed,
|
||||||
@@ -217,7 +217,7 @@ export function Popover<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Handle outside click
|
// Handle outside click
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
|
|
||||||
if (popoverState !== PopoverStates.Open) return
|
if (popoverState !== PopoverStates.Open) return
|
||||||
@@ -296,7 +296,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
|
|||||||
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
|
||||||
ref: Ref<HTMLButtonElement>
|
ref: Ref<HTMLButtonElement>
|
||||||
) {
|
) {
|
||||||
let [state, dispatch] = usePopoverContext([Popover.name, Button.name].join('.'))
|
let [state, dispatch] = usePopoverContext('Popover.Button')
|
||||||
let internalButtonRef = useRef<HTMLButtonElement | null>(null)
|
let internalButtonRef = useRef<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
let groupContext = usePopoverGroupContext()
|
let groupContext = usePopoverGroupContext()
|
||||||
@@ -308,7 +308,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
|
|||||||
let buttonRef = useSyncRefs(
|
let buttonRef = useSyncRefs(
|
||||||
internalButtonRef,
|
internalButtonRef,
|
||||||
ref,
|
ref,
|
||||||
isWithinPanel ? null : button => dispatch({ type: ActionTypes.SetButton, button })
|
isWithinPanel ? null : (button) => dispatch({ type: ActionTypes.SetButton, button })
|
||||||
)
|
)
|
||||||
let withinPanelButtonRef = useSyncRefs(internalButtonRef, ref)
|
let withinPanelButtonRef = useSyncRefs(internalButtonRef, ref)
|
||||||
|
|
||||||
@@ -517,7 +517,7 @@ let Overlay = forwardRefWithAs(function Overlay<
|
|||||||
PropsForFeatures<typeof OverlayRenderFeatures>,
|
PropsForFeatures<typeof OverlayRenderFeatures>,
|
||||||
ref: Ref<HTMLDivElement>
|
ref: Ref<HTMLDivElement>
|
||||||
) {
|
) {
|
||||||
let [{ popoverState }, dispatch] = usePopoverContext([Popover.name, Overlay.name].join('.'))
|
let [{ popoverState }, dispatch] = usePopoverContext('Popover.Overlay')
|
||||||
let overlayRef = useSyncRefs(ref)
|
let overlayRef = useSyncRefs(ref)
|
||||||
|
|
||||||
let id = `headlessui-popover-overlay-${useId()}`
|
let id = `headlessui-popover-overlay-${useId()}`
|
||||||
@@ -539,9 +539,10 @@ let Overlay = forwardRefWithAs(function Overlay<
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
)
|
)
|
||||||
|
|
||||||
let slot = useMemo<OverlayRenderPropArg>(() => ({ open: popoverState === PopoverStates.Open }), [
|
let slot = useMemo<OverlayRenderPropArg>(
|
||||||
popoverState,
|
() => ({ open: popoverState === PopoverStates.Open }),
|
||||||
])
|
[popoverState]
|
||||||
|
)
|
||||||
let propsWeControl = {
|
let propsWeControl = {
|
||||||
ref: overlayRef,
|
ref: overlayRef,
|
||||||
id,
|
id,
|
||||||
@@ -580,11 +581,11 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
|
|||||||
) {
|
) {
|
||||||
let { focus = false, ...passthroughProps } = props
|
let { focus = false, ...passthroughProps } = props
|
||||||
|
|
||||||
let [state, dispatch] = usePopoverContext([Popover.name, Panel.name].join('.'))
|
let [state, dispatch] = usePopoverContext('Popover.Panel')
|
||||||
let { close } = usePopoverAPIContext([Popover.name, Panel.name].join('.'))
|
let { close } = usePopoverAPIContext('Popover.Panel')
|
||||||
|
|
||||||
let internalPanelRef = useRef<HTMLDivElement | null>(null)
|
let internalPanelRef = useRef<HTMLDivElement | null>(null)
|
||||||
let panelRef = useSyncRefs(internalPanelRef, ref, panel => {
|
let panelRef = useSyncRefs(internalPanelRef, ref, (panel) => {
|
||||||
dispatch({ type: ActionTypes.SetPanel, panel })
|
dispatch({ type: ActionTypes.SetPanel, panel })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -639,7 +640,7 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
|
|||||||
}, [focus, internalPanelRef, state.popoverState])
|
}, [focus, internalPanelRef, state.popoverState])
|
||||||
|
|
||||||
// Handle Tab / Shift+Tab focus positioning
|
// Handle Tab / Shift+Tab focus positioning
|
||||||
useWindowEvent('keydown', event => {
|
useWindowEvent('keydown', (event) => {
|
||||||
if (state.popoverState !== PopoverStates.Open) return
|
if (state.popoverState !== PopoverStates.Open) return
|
||||||
if (!internalPanelRef.current) return
|
if (!internalPanelRef.current) return
|
||||||
if (event.key !== Keys.Tab) return
|
if (event.key !== Keys.Tab) return
|
||||||
@@ -665,7 +666,7 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
|
|||||||
|
|
||||||
let nextElements = elements
|
let nextElements = elements
|
||||||
.splice(buttonIdx + 1) // Elements after button
|
.splice(buttonIdx + 1) // Elements after button
|
||||||
.filter(element => !internalPanelRef.current?.contains(element)) // Ignore items in panel
|
.filter((element) => !internalPanelRef.current?.contains(element)) // Ignore items in panel
|
||||||
|
|
||||||
// Try to focus the next element, however it could fail if we are in a
|
// Try to focus the next element, however it could fail if we are in a
|
||||||
// Portal that happens to be the very last one in the DOM. In that
|
// Portal that happens to be the very last one in the DOM. In that
|
||||||
@@ -730,7 +731,7 @@ function Group<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
|||||||
|
|
||||||
let unregisterPopover = useCallback(
|
let unregisterPopover = useCallback(
|
||||||
(registerbag: PopoverRegisterBag) => {
|
(registerbag: PopoverRegisterBag) => {
|
||||||
setPopovers(existing => {
|
setPopovers((existing) => {
|
||||||
let idx = existing.indexOf(registerbag)
|
let idx = existing.indexOf(registerbag)
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
let clone = existing.slice()
|
let clone = existing.slice()
|
||||||
@@ -745,7 +746,7 @@ function Group<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
|||||||
|
|
||||||
let registerPopover = useCallback(
|
let registerPopover = useCallback(
|
||||||
(registerbag: PopoverRegisterBag) => {
|
(registerbag: PopoverRegisterBag) => {
|
||||||
setPopovers(existing => [...existing, registerbag])
|
setPopovers((existing) => [...existing, registerbag])
|
||||||
return () => unregisterPopover(registerbag)
|
return () => unregisterPopover(registerbag)
|
||||||
},
|
},
|
||||||
[setPopovers, unregisterPopover]
|
[setPopovers, unregisterPopover]
|
||||||
@@ -757,7 +758,7 @@ function Group<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
|||||||
if (groupRef.current?.contains(element)) return true
|
if (groupRef.current?.contains(element)) return true
|
||||||
|
|
||||||
// Check if the focus is in one of the button or panel elements. This is important in case you are rendering inside a Portal.
|
// Check if the focus is in one of the button or panel elements. This is important in case you are rendering inside a Portal.
|
||||||
return popovers.some(bag => {
|
return popovers.some((bag) => {
|
||||||
return (
|
return (
|
||||||
document.getElementById(bag.buttonId)?.contains(element) ||
|
document.getElementById(bag.buttonId)?.contains(element) ||
|
||||||
document.getElementById(bag.panelId)?.contains(element)
|
document.getElementById(bag.panelId)?.contains(element)
|
||||||
|
|||||||
@@ -82,10 +82,10 @@ it('should cleanup the Portal root when the last Portal is unmounted', async ()
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main id="parent">
|
<main id="parent">
|
||||||
<button id="a" onClick={() => setRenderA(v => !v)}>
|
<button id="a" onClick={() => setRenderA((v) => !v)}>
|
||||||
Toggle A
|
Toggle A
|
||||||
</button>
|
</button>
|
||||||
<button id="b" onClick={() => setRenderB(v => !v)}>
|
<button id="b" onClick={() => setRenderB((v) => !v)}>
|
||||||
Toggle B
|
Toggle B
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -151,21 +151,21 @@ it('should be possible to render multiple portals at the same time', async () =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main id="parent">
|
<main id="parent">
|
||||||
<button id="a" onClick={() => setRenderA(v => !v)}>
|
<button id="a" onClick={() => setRenderA((v) => !v)}>
|
||||||
Toggle A
|
Toggle A
|
||||||
</button>
|
</button>
|
||||||
<button id="b" onClick={() => setRenderB(v => !v)}>
|
<button id="b" onClick={() => setRenderB((v) => !v)}>
|
||||||
Toggle B
|
Toggle B
|
||||||
</button>
|
</button>
|
||||||
<button id="c" onClick={() => setRenderC(v => !v)}>
|
<button id="c" onClick={() => setRenderC((v) => !v)}>
|
||||||
Toggle C
|
Toggle C
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="double"
|
id="double"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRenderA(v => !v)
|
setRenderA((v) => !v)
|
||||||
setRenderB(v => !v)
|
setRenderB((v) => !v)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Toggle A & B{' '}
|
Toggle A & B{' '}
|
||||||
@@ -231,10 +231,10 @@ it('should be possible to tamper with the modal root and restore correctly', asy
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main id="parent">
|
<main id="parent">
|
||||||
<button id="a" onClick={() => setRenderA(v => !v)}>
|
<button id="a" onClick={() => setRenderA((v) => !v)}>
|
||||||
Toggle A
|
Toggle A
|
||||||
</button>
|
</button>
|
||||||
<button id="b" onClick={() => setRenderB(v => !v)}>
|
<button id="b" onClick={() => setRenderB((v) => !v)}>
|
||||||
Toggle B
|
Toggle B
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button onClick={() => setShowFirst(v => !v)}>Toggle</button>
|
<button onClick={() => setShowFirst((v) => !v)}>Toggle</button>
|
||||||
<RadioGroup value={active} onChange={setActive}>
|
<RadioGroup value={active} onChange={setActive}>
|
||||||
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
|
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
|
||||||
{showFirst && <RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>}
|
{showFirst && <RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>}
|
||||||
@@ -145,7 +145,7 @@ describe('Rendering', () => {
|
|||||||
let [disabled, setDisabled] = useState(true)
|
let [disabled, setDisabled] = useState(true)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button onClick={() => setDisabled(v => !v)}>Toggle</button>
|
<button onClick={() => setDisabled((v) => !v)}>Toggle</button>
|
||||||
<RadioGroup value={undefined} onChange={changeFn} disabled={disabled}>
|
<RadioGroup value={undefined} onChange={changeFn} disabled={disabled}>
|
||||||
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
|
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
|
||||||
<RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>
|
<RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>
|
||||||
@@ -208,7 +208,7 @@ describe('Rendering', () => {
|
|||||||
let [disabled, setDisabled] = useState(true)
|
let [disabled, setDisabled] = useState(true)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button onClick={() => setDisabled(v => !v)}>Toggle</button>
|
<button onClick={() => setDisabled((v) => !v)}>Toggle</button>
|
||||||
<RadioGroup value={undefined} onChange={changeFn}>
|
<RadioGroup value={undefined} onChange={changeFn}>
|
||||||
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
|
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
|
||||||
<RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>
|
<RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>
|
||||||
@@ -745,7 +745,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<button>Before</button>
|
<button>Before</button>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={value}
|
value={value}
|
||||||
onChange={v => {
|
onChange={(v) => {
|
||||||
setValue(v)
|
setValue(v)
|
||||||
changeFn(v)
|
changeFn(v)
|
||||||
}}
|
}}
|
||||||
@@ -815,7 +815,7 @@ describe('Mouse interactions', () => {
|
|||||||
<button>Before</button>
|
<button>Before</button>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={value}
|
value={value}
|
||||||
onChange={v => {
|
onChange={(v) => {
|
||||||
setValue(v)
|
setValue(v)
|
||||||
changeFn(v)
|
changeFn(v)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ let reducers: {
|
|||||||
},
|
},
|
||||||
[ActionTypes.UnregisterOption](state, action) {
|
[ActionTypes.UnregisterOption](state, action) {
|
||||||
let options = state.options.slice()
|
let options = state.options.slice()
|
||||||
let idx = state.options.findIndex(radio => radio.id === action.id)
|
let idx = state.options.findIndex((radio) => radio.id === action.id)
|
||||||
if (idx === -1) return state
|
if (idx === -1) return state
|
||||||
options.splice(idx, 1)
|
options.splice(idx, 1)
|
||||||
return { ...state, options }
|
return { ...state, options }
|
||||||
@@ -123,23 +123,23 @@ export function RadioGroup<
|
|||||||
|
|
||||||
let firstOption = useMemo(
|
let firstOption = useMemo(
|
||||||
() =>
|
() =>
|
||||||
options.find(option => {
|
options.find((option) => {
|
||||||
if (option.propsRef.current.disabled) return false
|
if (option.propsRef.current.disabled) return false
|
||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
[options]
|
[options]
|
||||||
)
|
)
|
||||||
let containsCheckedOption = useMemo(
|
let containsCheckedOption = useMemo(
|
||||||
() => options.some(option => option.propsRef.current.value === value),
|
() => options.some((option) => option.propsRef.current.value === value),
|
||||||
[options, value]
|
[options, value]
|
||||||
)
|
)
|
||||||
|
|
||||||
let triggerChange = useCallback(
|
let triggerChange = useCallback(
|
||||||
nextValue => {
|
(nextValue) => {
|
||||||
if (disabled) return false
|
if (disabled) return false
|
||||||
if (nextValue === value) return false
|
if (nextValue === value) return false
|
||||||
let nextOption = options.find(option => option.propsRef.current.value === nextValue)?.propsRef
|
let nextOption = options.find((option) => option.propsRef.current.value === nextValue)
|
||||||
.current
|
?.propsRef.current
|
||||||
if (nextOption?.disabled) return false
|
if (nextOption?.disabled) return false
|
||||||
|
|
||||||
onChange(nextValue)
|
onChange(nextValue)
|
||||||
@@ -166,8 +166,8 @@ export function RadioGroup<
|
|||||||
if (!container) return
|
if (!container) return
|
||||||
|
|
||||||
let all = options
|
let all = options
|
||||||
.filter(option => option.propsRef.current.disabled === false)
|
.filter((option) => option.propsRef.current.disabled === false)
|
||||||
.map(radio => radio.element.current) as HTMLElement[]
|
.map((radio) => radio.element.current) as HTMLElement[]
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Keys.ArrowLeft:
|
case Keys.ArrowLeft:
|
||||||
@@ -180,7 +180,7 @@ export function RadioGroup<
|
|||||||
|
|
||||||
if (result === FocusResult.Success) {
|
if (result === FocusResult.Success) {
|
||||||
let activeOption = options.find(
|
let activeOption = options.find(
|
||||||
option => option.element.current === document.activeElement
|
(option) => option.element.current === document.activeElement
|
||||||
)
|
)
|
||||||
if (activeOption) triggerChange(activeOption.propsRef.current.value)
|
if (activeOption) triggerChange(activeOption.propsRef.current.value)
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ export function RadioGroup<
|
|||||||
|
|
||||||
if (result === FocusResult.Success) {
|
if (result === FocusResult.Success) {
|
||||||
let activeOption = options.find(
|
let activeOption = options.find(
|
||||||
option => option.element.current === document.activeElement
|
(option) => option.element.current === document.activeElement
|
||||||
)
|
)
|
||||||
if (activeOption) triggerChange(activeOption.propsRef.current.value)
|
if (activeOption) triggerChange(activeOption.propsRef.current.value)
|
||||||
}
|
}
|
||||||
@@ -210,7 +210,7 @@ export function RadioGroup<
|
|||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
let activeOption = options.find(
|
let activeOption = options.find(
|
||||||
option => option.element.current === document.activeElement
|
(option) => option.element.current === document.activeElement
|
||||||
)
|
)
|
||||||
if (activeOption) triggerChange(activeOption.propsRef.current.value)
|
if (activeOption) triggerChange(activeOption.propsRef.current.value)
|
||||||
}
|
}
|
||||||
@@ -322,14 +322,12 @@ function Option<
|
|||||||
firstOption,
|
firstOption,
|
||||||
containsCheckedOption,
|
containsCheckedOption,
|
||||||
value: radioGroupValue,
|
value: radioGroupValue,
|
||||||
} = useRadioGroupContext([RadioGroup.name, Option.name].join('.'))
|
} = useRadioGroupContext('RadioGroup.Option')
|
||||||
|
|
||||||
useIsoMorphicEffect(() => registerOption({ id, element: optionRef, propsRef }), [
|
useIsoMorphicEffect(
|
||||||
id,
|
() => registerOption({ id, element: optionRef, propsRef }),
|
||||||
registerOption,
|
[id, registerOption, optionRef, props]
|
||||||
optionRef,
|
)
|
||||||
props,
|
|
||||||
])
|
|
||||||
|
|
||||||
let handleClick = useCallback(() => {
|
let handleClick = useCallback(() => {
|
||||||
if (!change(value)) return
|
if (!change(value)) return
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ describe('Keyboard interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
checked={state}
|
checked={state}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setState(value)
|
setState(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -297,7 +297,7 @@ describe('Mouse interactions', () => {
|
|||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
checked={state}
|
checked={state}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setState(value)
|
setState(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -331,7 +331,7 @@ describe('Mouse interactions', () => {
|
|||||||
<Switch.Group>
|
<Switch.Group>
|
||||||
<Switch
|
<Switch
|
||||||
checked={state}
|
checked={state}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setState(value)
|
setState(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -373,7 +373,7 @@ describe('Mouse interactions', () => {
|
|||||||
<Switch.Group>
|
<Switch.Group>
|
||||||
<Switch
|
<Switch
|
||||||
checked={state}
|
checked={state}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setState(value)
|
setState(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button onClick={() => setHide(v => !v)}>toggle</button>
|
<button onClick={() => setHide((v) => !v)}>toggle</button>
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List>
|
<Tab.List>
|
||||||
<Tab>Tab 1</Tab>
|
<Tab>Tab 1</Tab>
|
||||||
@@ -118,7 +118,7 @@ describe('Rendering', () => {
|
|||||||
it('should expose the `selectedIndex` on the `Tab.Group` component', async () => {
|
it('should expose the `selectedIndex` on the `Tab.Group` component', async () => {
|
||||||
render(
|
render(
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre id="exposed">{JSON.stringify(data)}</pre>
|
<pre id="exposed">{JSON.stringify(data)}</pre>
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ describe('Rendering', () => {
|
|||||||
render(
|
render(
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List>
|
<Tab.List>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre id="exposed">{JSON.stringify(data)}</pre>
|
<pre id="exposed">{JSON.stringify(data)}</pre>
|
||||||
<Tab>Tab 1</Tab>
|
<Tab>Tab 1</Tab>
|
||||||
@@ -192,7 +192,7 @@ describe('Rendering', () => {
|
|||||||
</Tab.List>
|
</Tab.List>
|
||||||
|
|
||||||
<Tab.Panels>
|
<Tab.Panels>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre id="exposed">{JSON.stringify(data)}</pre>
|
<pre id="exposed">{JSON.stringify(data)}</pre>
|
||||||
<Tab.Panel>Content 1</Tab.Panel>
|
<Tab.Panel>Content 1</Tab.Panel>
|
||||||
@@ -220,7 +220,7 @@ describe('Rendering', () => {
|
|||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List>
|
<Tab.List>
|
||||||
<Tab>
|
<Tab>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre data-tab={0}>{JSON.stringify(data)}</pre>
|
<pre data-tab={0}>{JSON.stringify(data)}</pre>
|
||||||
<span>Tab 1</span>
|
<span>Tab 1</span>
|
||||||
@@ -228,7 +228,7 @@ describe('Rendering', () => {
|
|||||||
)}
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab>
|
<Tab>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre data-tab={1}>{JSON.stringify(data)}</pre>
|
<pre data-tab={1}>{JSON.stringify(data)}</pre>
|
||||||
<span>Tab 2</span>
|
<span>Tab 2</span>
|
||||||
@@ -236,7 +236,7 @@ describe('Rendering', () => {
|
|||||||
)}
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab>
|
<Tab>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre data-tab={2}>{JSON.stringify(data)}</pre>
|
<pre data-tab={2}>{JSON.stringify(data)}</pre>
|
||||||
<span>Tab 3</span>
|
<span>Tab 3</span>
|
||||||
@@ -287,7 +287,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
<Tab.Panels>
|
<Tab.Panels>
|
||||||
<Tab.Panel unmount={false}>
|
<Tab.Panel unmount={false}>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre data-panel={0}>{JSON.stringify(data)}</pre>
|
<pre data-panel={0}>{JSON.stringify(data)}</pre>
|
||||||
<span>Content 1</span>
|
<span>Content 1</span>
|
||||||
@@ -295,7 +295,7 @@ describe('Rendering', () => {
|
|||||||
)}
|
)}
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
<Tab.Panel unmount={false}>
|
<Tab.Panel unmount={false}>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre data-panel={1}>{JSON.stringify(data)}</pre>
|
<pre data-panel={1}>{JSON.stringify(data)}</pre>
|
||||||
<span>Content 2</span>
|
<span>Content 2</span>
|
||||||
@@ -303,7 +303,7 @@ describe('Rendering', () => {
|
|||||||
)}
|
)}
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
<Tab.Panel unmount={false}>
|
<Tab.Panel unmount={false}>
|
||||||
{data => (
|
{(data) => (
|
||||||
<>
|
<>
|
||||||
<pre data-panel={2}>{JSON.stringify(data)}</pre>
|
<pre data-panel={2}>{JSON.stringify(data)}</pre>
|
||||||
<span>Content 3</span>
|
<span>Content 3</span>
|
||||||
@@ -514,7 +514,7 @@ describe('Rendering', () => {
|
|||||||
<>
|
<>
|
||||||
<Tab.Group
|
<Tab.Group
|
||||||
selectedIndex={selectedIndex}
|
selectedIndex={selectedIndex}
|
||||||
onChange={value => {
|
onChange={(value) => {
|
||||||
setSelectedIndex(value)
|
setSelectedIndex(value)
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}}
|
}}
|
||||||
@@ -533,7 +533,7 @@ describe('Rendering', () => {
|
|||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
|
|
||||||
<button>after</button>
|
<button>after</button>
|
||||||
<button onClick={() => setSelectedIndex(prev => prev + 1)}>setSelectedIndex</button>
|
<button onClick={() => setSelectedIndex((prev) => prev + 1)}>setSelectedIndex</button>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,14 +83,14 @@ let reducers: {
|
|||||||
return { ...state, tabs: [...state.tabs, action.tab] }
|
return { ...state, tabs: [...state.tabs, action.tab] }
|
||||||
},
|
},
|
||||||
[ActionTypes.UnregisterTab](state, action) {
|
[ActionTypes.UnregisterTab](state, action) {
|
||||||
return { ...state, tabs: state.tabs.filter(tab => tab !== action.tab) }
|
return { ...state, tabs: state.tabs.filter((tab) => tab !== action.tab) }
|
||||||
},
|
},
|
||||||
[ActionTypes.RegisterPanel](state, action) {
|
[ActionTypes.RegisterPanel](state, action) {
|
||||||
if (state.panels.includes(action.panel)) return state
|
if (state.panels.includes(action.panel)) return state
|
||||||
return { ...state, panels: [...state.panels, action.panel] }
|
return { ...state, panels: [...state.panels, action.panel] }
|
||||||
},
|
},
|
||||||
[ActionTypes.UnregisterPanel](state, action) {
|
[ActionTypes.UnregisterPanel](state, action) {
|
||||||
return { ...state, panels: state.panels.filter(panel => panel !== action.panel) }
|
return { ...state, panels: state.panels.filter((panel) => panel !== action.panel) }
|
||||||
},
|
},
|
||||||
[ActionTypes.ForceRerender](state) {
|
[ActionTypes.ForceRerender](state) {
|
||||||
return { ...state }
|
return { ...state }
|
||||||
@@ -171,8 +171,8 @@ function Tabs<TTag extends ElementType = typeof DEFAULT_TABS_TAG>(
|
|||||||
if (state.tabs.length <= 0) return
|
if (state.tabs.length <= 0) return
|
||||||
if (selectedIndex === null && state.selectedIndex !== null) return
|
if (selectedIndex === null && state.selectedIndex !== null) return
|
||||||
|
|
||||||
let tabs = state.tabs.map(tab => tab.current).filter(Boolean) as HTMLElement[]
|
let tabs = state.tabs.map((tab) => tab.current).filter(Boolean) as HTMLElement[]
|
||||||
let focusableTabs = tabs.filter(tab => !tab.hasAttribute('disabled'))
|
let focusableTabs = tabs.filter((tab) => !tab.hasAttribute('disabled'))
|
||||||
|
|
||||||
let indexToSet = selectedIndex ?? defaultIndex
|
let indexToSet = selectedIndex ?? defaultIndex
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ function Tabs<TTag extends ElementType = typeof DEFAULT_TABS_TAG>(
|
|||||||
let before = tabs.slice(0, indexToSet)
|
let before = tabs.slice(0, indexToSet)
|
||||||
let after = tabs.slice(indexToSet)
|
let after = tabs.slice(indexToSet)
|
||||||
|
|
||||||
let next = [...after, ...before].find(tab => focusableTabs.includes(tab))
|
let next = [...after, ...before].find((tab) => focusableTabs.includes(tab))
|
||||||
if (!next) return
|
if (!next) return
|
||||||
|
|
||||||
dispatch({ type: ActionTypes.SetSelectedIndex, index: tabs.indexOf(next) })
|
dispatch({ type: ActionTypes.SetSelectedIndex, index: tabs.indexOf(next) })
|
||||||
@@ -245,7 +245,7 @@ type ListPropsWeControl = 'role' | 'aria-orientation'
|
|||||||
function List<TTag extends ElementType = typeof DEFAULT_LIST_TAG>(
|
function List<TTag extends ElementType = typeof DEFAULT_LIST_TAG>(
|
||||||
props: Props<TTag, ListRenderPropArg, ListPropsWeControl> & {}
|
props: Props<TTag, ListRenderPropArg, ListPropsWeControl> & {}
|
||||||
) {
|
) {
|
||||||
let [{ selectedIndex, orientation }] = useTabsContext([Tab.name, List.name].join('.'))
|
let [{ selectedIndex, orientation }] = useTabsContext('Tab.List')
|
||||||
|
|
||||||
let slot = { selectedIndex }
|
let slot = { selectedIndex }
|
||||||
let propsWeControl = {
|
let propsWeControl = {
|
||||||
@@ -275,13 +275,11 @@ export function Tab<TTag extends ElementType = typeof DEFAULT_TAB_TAG>(
|
|||||||
) {
|
) {
|
||||||
let id = `headlessui-tabs-tab-${useId()}`
|
let id = `headlessui-tabs-tab-${useId()}`
|
||||||
|
|
||||||
let [
|
let [{ selectedIndex, tabs, panels, orientation, activation }, { dispatch, change }] =
|
||||||
{ selectedIndex, tabs, panels, orientation, activation },
|
useTabsContext(Tab.name)
|
||||||
{ dispatch, change },
|
|
||||||
] = useTabsContext(Tab.name)
|
|
||||||
|
|
||||||
let internalTabRef = useRef<HTMLElement>(null)
|
let internalTabRef = useRef<HTMLElement>(null)
|
||||||
let tabRef = useSyncRefs(internalTabRef, element => {
|
let tabRef = useSyncRefs(internalTabRef, (element) => {
|
||||||
if (!element) return
|
if (!element) return
|
||||||
dispatch({ type: ActionTypes.ForceRerender })
|
dispatch({ type: ActionTypes.ForceRerender })
|
||||||
})
|
})
|
||||||
@@ -296,7 +294,7 @@ export function Tab<TTag extends ElementType = typeof DEFAULT_TAB_TAG>(
|
|||||||
|
|
||||||
let handleKeyDown = useCallback(
|
let handleKeyDown = useCallback(
|
||||||
(event: ReactKeyboardEvent<HTMLElement>) => {
|
(event: ReactKeyboardEvent<HTMLElement>) => {
|
||||||
let list = tabs.map(tab => tab.current).filter(Boolean) as HTMLElement[]
|
let list = tabs.map((tab) => tab.current).filter(Boolean) as HTMLElement[]
|
||||||
|
|
||||||
if (event.key === Keys.Space || event.key === Keys.Enter) {
|
if (event.key === Keys.Space || event.key === Keys.Enter) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -380,7 +378,7 @@ interface PanelsRenderPropArg {
|
|||||||
function Panels<TTag extends ElementType = typeof DEFAULT_PANELS_TAG>(
|
function Panels<TTag extends ElementType = typeof DEFAULT_PANELS_TAG>(
|
||||||
props: Props<TTag, PanelsRenderPropArg>
|
props: Props<TTag, PanelsRenderPropArg>
|
||||||
) {
|
) {
|
||||||
let [{ selectedIndex }] = useTabsContext([Tab.name, Panels.name].join('.'))
|
let [{ selectedIndex }] = useTabsContext('Tab.Panels')
|
||||||
|
|
||||||
let slot = useMemo(() => ({ selectedIndex }), [selectedIndex])
|
let slot = useMemo(() => ({ selectedIndex }), [selectedIndex])
|
||||||
|
|
||||||
@@ -405,13 +403,11 @@ function Panel<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
|||||||
props: Props<TTag, PanelRenderPropArg, PanelPropsWeControl> &
|
props: Props<TTag, PanelRenderPropArg, PanelPropsWeControl> &
|
||||||
PropsForFeatures<typeof PanelRenderFeatures>
|
PropsForFeatures<typeof PanelRenderFeatures>
|
||||||
) {
|
) {
|
||||||
let [{ selectedIndex, tabs, panels }, { dispatch }] = useTabsContext(
|
let [{ selectedIndex, tabs, panels }, { dispatch }] = useTabsContext('Tab.Panel')
|
||||||
[Tab.name, Panel.name].join('.')
|
|
||||||
)
|
|
||||||
|
|
||||||
let id = `headlessui-tabs-panel-${useId()}`
|
let id = `headlessui-tabs-panel-${useId()}`
|
||||||
let internalPanelRef = useRef<HTMLElement>(null)
|
let internalPanelRef = useRef<HTMLElement>(null)
|
||||||
let panelRef = useSyncRefs(internalPanelRef, element => {
|
let panelRef = useSyncRefs(internalPanelRef, (element) => {
|
||||||
if (!element) return
|
if (!element) return
|
||||||
dispatch({ type: ActionTypes.ForceRerender })
|
dispatch({ type: ActionTypes.ForceRerender })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ it(
|
|||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
render(
|
render(
|
||||||
// @ts-expect-error Disabling TS because it does require us to use a show prop. But non
|
|
||||||
// TypeScript projects won't benefit from this.
|
|
||||||
<Transition>
|
<Transition>
|
||||||
<div className="hello">Children</div>
|
<div className="hello">Children</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -445,7 +443,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -488,14 +486,15 @@ describe('Transitions', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<style>{`.enter { transition-duration: ${enterDuration /
|
<style>{`.enter { transition-duration: ${
|
||||||
1000}s; } .from { opacity: 0%; } .to { opacity: 100%; }`}</style>
|
enterDuration / 1000
|
||||||
|
}s; } .from { opacity: 0%; } .to { opacity: 100%; }`}</style>
|
||||||
|
|
||||||
<Transition show={show} enter="enter" enterFrom="from" enterTo="to">
|
<Transition show={show} enter="enter" enterFrom="from" enterTo="to">
|
||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -538,14 +537,15 @@ describe('Transitions', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<style>{`.enter { transition-duration: ${enterDuration /
|
<style>{`.enter { transition-duration: ${
|
||||||
1000}s; } .from { opacity: 0%; } .to { opacity: 100%; }`}</style>
|
enterDuration / 1000
|
||||||
|
}s; } .from { opacity: 0%; } .to { opacity: 100%; }`}</style>
|
||||||
|
|
||||||
<Transition show={show} unmount={false} enter="enter" enterFrom="from" enterTo="to">
|
<Transition show={show} unmount={false} enter="enter" enterFrom="from" enterTo="to">
|
||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -591,7 +591,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -642,7 +642,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -696,7 +696,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -757,7 +757,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -843,7 +843,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -943,7 +943,7 @@ describe('Transitions', () => {
|
|||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -1027,7 +1027,7 @@ describe('Transitions', () => {
|
|||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -1138,7 +1138,7 @@ describe('Events', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
|
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
|
||||||
Toggle
|
Toggle
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ import { useServerHandoffComplete } from '../../hooks/use-server-handoff-complet
|
|||||||
type ID = ReturnType<typeof useId>
|
type ID = ReturnType<typeof useId>
|
||||||
|
|
||||||
function useSplitClasses(classes: string = '') {
|
function useSplitClasses(classes: string = '') {
|
||||||
return useMemo(() => classes.split(' ').filter(className => className.trim().length > 1), [
|
return useMemo(
|
||||||
classes,
|
() => classes.split(' ').filter((className) => className.trim().length > 1),
|
||||||
])
|
[classes]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TransitionContextValues {
|
interface TransitionContextValues {
|
||||||
@@ -287,23 +288,37 @@ function TransitionChild<TTag extends ElementType = typeof DEFAULT_TRANSITION_CH
|
|||||||
if (!show) events.current.beforeLeave()
|
if (!show) events.current.beforeLeave()
|
||||||
|
|
||||||
return show
|
return show
|
||||||
? transition(node, enterClasses, enterFromClasses, enterToClasses, enteredClasses, reason => {
|
? transition(
|
||||||
isTransitioning.current = false
|
node,
|
||||||
if (reason === Reason.Finished) events.current.afterEnter()
|
enterClasses,
|
||||||
})
|
enterFromClasses,
|
||||||
: transition(node, leaveClasses, leaveFromClasses, leaveToClasses, enteredClasses, reason => {
|
enterToClasses,
|
||||||
isTransitioning.current = false
|
enteredClasses,
|
||||||
|
(reason) => {
|
||||||
if (reason !== Reason.Finished) return
|
isTransitioning.current = false
|
||||||
|
if (reason === Reason.Finished) events.current.afterEnter()
|
||||||
// When we don't have children anymore we can safely unregister from the parent and hide
|
|
||||||
// ourselves.
|
|
||||||
if (!hasChildren(nesting)) {
|
|
||||||
setState(TreeStates.Hidden)
|
|
||||||
unregister(id)
|
|
||||||
events.current.afterLeave()
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
: transition(
|
||||||
|
node,
|
||||||
|
leaveClasses,
|
||||||
|
leaveFromClasses,
|
||||||
|
leaveToClasses,
|
||||||
|
enteredClasses,
|
||||||
|
(reason) => {
|
||||||
|
isTransitioning.current = false
|
||||||
|
|
||||||
|
if (reason !== Reason.Finished) return
|
||||||
|
|
||||||
|
// When we don't have children anymore we can safely unregister from the parent and hide
|
||||||
|
// ourselves.
|
||||||
|
if (!hasChildren(nesting)) {
|
||||||
|
setState(TreeStates.Hidden)
|
||||||
|
unregister(id)
|
||||||
|
events.current.afterLeave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}, [
|
}, [
|
||||||
events,
|
events,
|
||||||
id,
|
id,
|
||||||
@@ -359,7 +374,7 @@ export function Transition<TTag extends ElementType = typeof DEFAULT_TRANSITION_
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (![true, false].includes((show as unknown) as boolean)) {
|
if (![true, false].includes(show as unknown as boolean)) {
|
||||||
throw new Error('A <Transition /> is used but it is missing a `show={true | false}` prop.')
|
throw new Error('A <Transition /> is used but it is missing a `show={true | false}` prop.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ it('should be possible to transition', async () => {
|
|||||||
d.add(
|
d.add(
|
||||||
reportChanges(
|
reportChanges(
|
||||||
() => document.body.innerHTML,
|
() => document.body.innerHTML,
|
||||||
content => {
|
(content) => {
|
||||||
snapshots.push({
|
snapshots.push({
|
||||||
content,
|
content,
|
||||||
recordedAt: process.hrtime.bigint(),
|
recordedAt: process.hrtime.bigint(),
|
||||||
@@ -26,11 +26,11 @@ it('should be possible to transition', async () => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
await new Promise(resolve => {
|
await new Promise((resolve) => {
|
||||||
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
|
|
||||||
// Initial render:
|
// Initial render:
|
||||||
expect(snapshots[0].content).toEqual('<div></div>')
|
expect(snapshots[0].content).toEqual('<div></div>')
|
||||||
@@ -61,7 +61,7 @@ it('should wait the correct amount of time to finish a transition', async () =>
|
|||||||
d.add(
|
d.add(
|
||||||
reportChanges(
|
reportChanges(
|
||||||
() => document.body.innerHTML,
|
() => document.body.innerHTML,
|
||||||
content => {
|
(content) => {
|
||||||
snapshots.push({
|
snapshots.push({
|
||||||
content,
|
content,
|
||||||
recordedAt: process.hrtime.bigint(),
|
recordedAt: process.hrtime.bigint(),
|
||||||
@@ -70,11 +70,11 @@ it('should wait the correct amount of time to finish a transition', async () =>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let reason = await new Promise(resolve => {
|
let reason = await new Promise((resolve) => {
|
||||||
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
expect(reason).toBe(Reason.Finished)
|
expect(reason).toBe(Reason.Finished)
|
||||||
|
|
||||||
// Initial render:
|
// Initial render:
|
||||||
@@ -118,7 +118,7 @@ it('should keep the delay time into account', async () => {
|
|||||||
d.add(
|
d.add(
|
||||||
reportChanges(
|
reportChanges(
|
||||||
() => document.body.innerHTML,
|
() => document.body.innerHTML,
|
||||||
content => {
|
(content) => {
|
||||||
snapshots.push({
|
snapshots.push({
|
||||||
content,
|
content,
|
||||||
recordedAt: process.hrtime.bigint(),
|
recordedAt: process.hrtime.bigint(),
|
||||||
@@ -127,11 +127,11 @@ it('should keep the delay time into account', async () => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let reason = await new Promise(resolve => {
|
let reason = await new Promise((resolve) => {
|
||||||
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
expect(reason).toBe(Reason.Finished)
|
expect(reason).toBe(Reason.Finished)
|
||||||
|
|
||||||
let estimatedDuration = Number(
|
let estimatedDuration = Number(
|
||||||
@@ -161,7 +161,7 @@ it('should be possible to cancel a transition at any time', async () => {
|
|||||||
d.add(
|
d.add(
|
||||||
reportChanges(
|
reportChanges(
|
||||||
() => document.body.innerHTML,
|
() => document.body.innerHTML,
|
||||||
content => {
|
(content) => {
|
||||||
let recordedAt = process.hrtime.bigint()
|
let recordedAt = process.hrtime.bigint()
|
||||||
let total = snapshots.length
|
let total = snapshots.length
|
||||||
|
|
||||||
@@ -178,16 +178,16 @@ it('should be possible to cancel a transition at any time', async () => {
|
|||||||
expect.assertions(2)
|
expect.assertions(2)
|
||||||
|
|
||||||
// Setup the transition
|
// Setup the transition
|
||||||
let cancel = transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], reason => {
|
let cancel = transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], (reason) => {
|
||||||
expect(reason).toBe(Reason.Cancelled)
|
expect(reason).toBe(Reason.Cancelled)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for a bit
|
// Wait for a bit
|
||||||
await new Promise(resolve => setTimeout(resolve, 20))
|
await new Promise((resolve) => setTimeout(resolve, 20))
|
||||||
|
|
||||||
// Cancel the transition
|
// Cancel the transition
|
||||||
cancel()
|
cancel()
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
|
|
||||||
expect(snapshots.map(snapshot => snapshot.content).join('\n')).not.toContain('enterTo')
|
expect(snapshots.map((snapshot) => snapshot.content).join('\n')).not.toContain('enterTo')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ function waitForTransition(node: HTMLElement, done: (reason: Reason) => void) {
|
|||||||
// Safari returns a comma separated list of values, so let's sort them and take the highest value.
|
// Safari returns a comma separated list of values, so let's sort them and take the highest value.
|
||||||
let { transitionDuration, transitionDelay } = getComputedStyle(node)
|
let { transitionDuration, transitionDelay } = getComputedStyle(node)
|
||||||
|
|
||||||
let [durationMs, delaysMs] = [transitionDuration, transitionDelay].map(value => {
|
let [durationMs, delaysMs] = [transitionDuration, transitionDelay].map((value) => {
|
||||||
let [resolvedValue = 0] = value
|
let [resolvedValue = 0] = value
|
||||||
.split(',')
|
.split(',')
|
||||||
// Remove falsy we can't work with
|
// Remove falsy we can't work with
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
// Values are returned as `0.3s` or `75ms`
|
// Values are returned as `0.3s` or `75ms`
|
||||||
.map(v => (v.includes('ms') ? parseFloat(v) : parseFloat(v) * 1000))
|
.map((v) => (v.includes('ms') ? parseFloat(v) : parseFloat(v) * 1000))
|
||||||
.sort((a, z) => z - a)
|
.sort((a, z) => z - a)
|
||||||
|
|
||||||
return resolvedValue
|
return resolvedValue
|
||||||
@@ -74,7 +74,7 @@ export function transition(
|
|||||||
addClasses(node, ...to)
|
addClasses(node, ...to)
|
||||||
|
|
||||||
d.add(
|
d.add(
|
||||||
waitForTransition(node, reason => {
|
waitForTransition(node, (reason) => {
|
||||||
removeClasses(node, ...to, ...base)
|
removeClasses(node, ...to, ...base)
|
||||||
addClasses(node, ...entered)
|
addClasses(node, ...entered)
|
||||||
return _done(reason)
|
return _done(reason)
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { useState, useCallback } from 'react'
|
|||||||
export function useFlags(initialFlags = 0) {
|
export function useFlags(initialFlags = 0) {
|
||||||
let [flags, setFlags] = useState(initialFlags)
|
let [flags, setFlags] = useState(initialFlags)
|
||||||
|
|
||||||
let addFlag = useCallback((flag: number) => setFlags(flags => flags | flag), [setFlags])
|
let addFlag = useCallback((flag: number) => setFlags((flags) => flags | flag), [setFlags])
|
||||||
let hasFlag = useCallback((flag: number) => Boolean(flags & flag), [flags])
|
let hasFlag = useCallback((flag: number) => Boolean(flags & flag), [flags])
|
||||||
let removeFlag = useCallback((flag: number) => setFlags(flags => flags & ~flag), [setFlags])
|
let removeFlag = useCallback((flag: number) => setFlags((flags) => flags & ~flag), [setFlags])
|
||||||
let toggleFlag = useCallback((flag: number) => setFlags(flags => flags ^ flag), [setFlags])
|
let toggleFlag = useCallback((flag: number) => setFlags((flags) => flags ^ flag), [setFlags])
|
||||||
|
|
||||||
return { addFlag, hasFlag, removeFlag, toggleFlag }
|
return { addFlag, hasFlag, removeFlag, toggleFlag }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export function useFocusTrap(
|
|||||||
}, [container, initialFocus, featuresInitialFocus])
|
}, [container, initialFocus, featuresInitialFocus])
|
||||||
|
|
||||||
// Handle `Tab` & `Shift+Tab` keyboard events
|
// Handle `Tab` & `Shift+Tab` keyboard events
|
||||||
useWindowEvent('keydown', event => {
|
useWindowEvent('keydown', (event) => {
|
||||||
if (!(features & Features.TabLock)) return
|
if (!(features & Features.TabLock)) return
|
||||||
|
|
||||||
if (!container.current) return
|
if (!container.current) return
|
||||||
@@ -118,7 +118,7 @@ export function useFocusTrap(
|
|||||||
// Prevent programmatically escaping the container
|
// Prevent programmatically escaping the container
|
||||||
useWindowEvent(
|
useWindowEvent(
|
||||||
'focus',
|
'focus',
|
||||||
event => {
|
(event) => {
|
||||||
if (!(features & Features.FocusLock)) return
|
if (!(features & Features.FocusLock)) return
|
||||||
|
|
||||||
let allContainers = new Set(containers?.current)
|
let allContainers = new Set(containers?.current)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ it('should be possible to inert other elements', async () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} id="main">
|
<div ref={ref} id="main">
|
||||||
<button onClick={() => setEnabled(v => !v)}>toggle</button>
|
<button onClick={() => setEnabled((v) => !v)}>toggle</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ it('should restore inert elements, when all useInertOthers calls are disabled',
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} id={id}>
|
<div ref={ref} id={id}>
|
||||||
<button onClick={() => setEnabled(v => !v)}>{toggle}</button>
|
<button onClick={() => setEnabled((v) => !v)}>{toggle}</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ it('should restore inert elements, when all useInertOthers calls are disabled (i
|
|||||||
return (
|
return (
|
||||||
<div id={`parent-${id}`}>
|
<div id={`parent-${id}`}>
|
||||||
<div ref={ref} id={id}>
|
<div ref={ref} id={id}>
|
||||||
<button onClick={() => setEnabled(v => !v)}>{toggle}</button>
|
<button onClick={() => setEnabled((v) => !v)}>{toggle}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -221,7 +221,7 @@ it('should handle inert others correctly when 2 useInertOthers are used in a sha
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} id={id}>
|
<div ref={ref} id={id}>
|
||||||
<button onClick={() => setEnabled(v => !v)}>{toggle}</button>
|
<button onClick={() => setEnabled((v) => !v)}>{toggle}</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function useInertOthers<TElement extends HTMLElement>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect direct children of the body
|
// Collect direct children of the body
|
||||||
document.querySelectorAll('body > *').forEach(child => {
|
document.querySelectorAll('body > *').forEach((child) => {
|
||||||
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
|
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
|
||||||
|
|
||||||
// Skip the interactables, and the parents of the interactables
|
// Skip the interactables, and the parents of the interactables
|
||||||
@@ -71,7 +71,7 @@ export function useInertOthers<TElement extends HTMLElement>(
|
|||||||
// will become inert as well.
|
// will become inert as well.
|
||||||
if (interactables.size > 0) {
|
if (interactables.size > 0) {
|
||||||
// Collect direct children of the body
|
// Collect direct children of the body
|
||||||
document.querySelectorAll('body > *').forEach(child => {
|
document.querySelectorAll('body > *').forEach((child) => {
|
||||||
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
|
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
|
||||||
|
|
||||||
// Skip already inert parents
|
// Skip already inert parents
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export function useTreeWalker({
|
|||||||
let walk = walkRef.current
|
let walk = walkRef.current
|
||||||
|
|
||||||
let acceptNode = Object.assign((node: HTMLElement) => accept(node), { acceptNode: accept })
|
let acceptNode = Object.assign((node: HTMLElement) => accept(node), { acceptNode: accept })
|
||||||
|
// @ts-expect-error This `false` is a simple small fix for older browsers
|
||||||
let walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, acceptNode, false)
|
let walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, acceptNode, false)
|
||||||
|
|
||||||
while (walker.nextNode()) walk(walker.currentNode as HTMLElement)
|
while (walker.nextNode()) walk(walker.currentNode as HTMLElement)
|
||||||
|
|||||||
@@ -1256,7 +1256,7 @@ export function assertLabelValue(element: HTMLElement | null, value: string) {
|
|||||||
|
|
||||||
if (element.hasAttribute('aria-labelledby')) {
|
if (element.hasAttribute('aria-labelledby')) {
|
||||||
let ids = element.getAttribute('aria-labelledby')!.split(' ')
|
let ids = element.getAttribute('aria-labelledby')!.split(' ')
|
||||||
expect(ids.map(id => document.getElementById(id)?.textContent).join(' ')).toEqual(value)
|
expect(ids.map((id) => document.getElementById(id)?.textContent).join(' ')).toEqual(value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1612,7 +1612,7 @@ export function assertTabs(
|
|||||||
expect(list).toHaveAttribute('aria-orientation', orientation)
|
expect(list).toHaveAttribute('aria-orientation', orientation)
|
||||||
|
|
||||||
let activeTab = Array.from(list.querySelectorAll('[id^="headlessui-tabs-tab-"]'))[active]
|
let activeTab = Array.from(list.querySelectorAll('[id^="headlessui-tabs-tab-"]'))[active]
|
||||||
let activePanel = panels.find(panel => panel.id === activeTab.getAttribute('aria-controls'))
|
let activePanel = panels.find((panel) => panel.id === activeTab.getAttribute('aria-controls'))
|
||||||
|
|
||||||
for (let tab of tabs) {
|
for (let tab of tabs) {
|
||||||
expect(tab).toHaveAttribute('id')
|
expect(tab).toHaveAttribute('id')
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function redentSnapshot(input: string) {
|
|||||||
|
|
||||||
return input
|
return input
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(line =>
|
.map((line) =>
|
||||||
line.trim() === '---' ? line : line.replace(replacer, (_, sign, rest) => `${sign} ${rest}`)
|
line.trim() === '---' ? line : line.replace(replacer, (_, sign, rest) => `${sign} ${rest}`)
|
||||||
)
|
)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
@@ -69,13 +69,13 @@ export async function executeTimeline(
|
|||||||
.reduce((total, current) => total + current, 0)
|
.reduce((total, current) => total + current, 0)
|
||||||
|
|
||||||
// Changes happen in the next frame
|
// Changes happen in the next frame
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
|
|
||||||
// We wait for the amount of the duration
|
// We wait for the amount of the duration
|
||||||
await new Promise(resolve => d.setTimeout(resolve, totalDuration))
|
await new Promise((resolve) => d.setTimeout(resolve, totalDuration))
|
||||||
|
|
||||||
// We wait an additional next frame so that we know that we are done
|
// We wait an additional next frame so that we know that we are done
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
}, Promise.resolve())
|
}, Promise.resolve())
|
||||||
|
|
||||||
if (snapshots.length <= 0) {
|
if (snapshots.length <= 0) {
|
||||||
@@ -127,7 +127,7 @@ export async function executeTimeline(
|
|||||||
.replace(/Snapshot Diff:\n/g, '')
|
.replace(/Snapshot Diff:\n/g, '')
|
||||||
)
|
)
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(line => ` ${line}`)
|
.map((line) => ` ${line}`)
|
||||||
.join('\n')}`
|
.join('\n')}`
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ describe('Keyboard', () => {
|
|||||||
|
|
||||||
await type([key(input)])
|
await type([key(input)])
|
||||||
|
|
||||||
let expected = result.map(e => event(e))
|
let expected = result.map((e) => event(e))
|
||||||
|
|
||||||
expect(fired.length).toEqual(result.length)
|
expect(fired.length).toEqual(result.length)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function shift(event: Partial<KeyboardEvent>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function word(input: string): Partial<KeyboardEvent>[] {
|
export function word(input: string): Partial<KeyboardEvent>[] {
|
||||||
let result = input.split('').map(key => ({ key }))
|
let result = input.split('').map((key) => ({ key }))
|
||||||
|
|
||||||
d.enqueue(() => {
|
d.enqueue(() => {
|
||||||
let element = document.activeElement
|
let element = document.activeElement
|
||||||
@@ -152,7 +152,7 @@ export async function type(events: Partial<KeyboardEvent>[], element = document.
|
|||||||
let actions = order[event.key!] ?? order[Default as any]
|
let actions = order[event.key!] ?? order[Default as any]
|
||||||
for (let action of actions) {
|
for (let action of actions) {
|
||||||
let checks = action.name.split('And')
|
let checks = action.name.split('And')
|
||||||
if (checks.some(check => skip.has(check))) continue
|
if (checks.some((check) => skip.has(check))) continue
|
||||||
|
|
||||||
let result = action(element, {
|
let result = action(element, {
|
||||||
type: action.name,
|
type: action.name,
|
||||||
@@ -344,8 +344,8 @@ let focusableSelector = [
|
|||||||
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
||||||
// "hidden" can be the document.activeElement, because this is not possible
|
// "hidden" can be the document.activeElement, because this is not possible
|
||||||
// in real browsers.
|
// in real browsers.
|
||||||
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
||||||
: selector => `${selector}:not([tabindex='-1'])`
|
: (selector) => `${selector}:not([tabindex='-1'])`
|
||||||
)
|
)
|
||||||
.join(',')
|
.join(',')
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ type FunctionPropertyNames<T> = {
|
|||||||
|
|
||||||
export function suppressConsoleLogs<T extends unknown[]>(
|
export function suppressConsoleLogs<T extends unknown[]>(
|
||||||
cb: (...args: T) => unknown,
|
cb: (...args: T) => unknown,
|
||||||
type: FunctionPropertyNames<typeof global.console> = 'error'
|
type: FunctionPropertyNames<typeof globalThis.console> = 'error'
|
||||||
) {
|
) {
|
||||||
return (...args: T) => {
|
return (...args: T) => {
|
||||||
let spy = jest.spyOn(global.console, type).mockImplementation(jest.fn())
|
let spy = jest.spyOn(globalThis.console, type).mockImplementation(jest.fn())
|
||||||
|
|
||||||
return new Promise<unknown>((resolve, reject) => {
|
return new Promise<unknown>((resolve, reject) => {
|
||||||
Promise.resolve(cb(...args)).then(resolve, reject)
|
Promise.resolve(cb(...args)).then(resolve, reject)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function calculateActiveIndex<TItem>(
|
|||||||
let nextActiveIndex = (() => {
|
let nextActiveIndex = (() => {
|
||||||
switch (action.focus) {
|
switch (action.focus) {
|
||||||
case Focus.First:
|
case Focus.First:
|
||||||
return items.findIndex(item => !resolvers.resolveDisabled(item))
|
return items.findIndex((item) => !resolvers.resolveDisabled(item))
|
||||||
|
|
||||||
case Focus.Previous: {
|
case Focus.Previous: {
|
||||||
let idx = items
|
let idx = items
|
||||||
@@ -64,13 +64,13 @@ export function calculateActiveIndex<TItem>(
|
|||||||
let idx = items
|
let idx = items
|
||||||
.slice()
|
.slice()
|
||||||
.reverse()
|
.reverse()
|
||||||
.findIndex(item => !resolvers.resolveDisabled(item))
|
.findIndex((item) => !resolvers.resolveDisabled(item))
|
||||||
if (idx === -1) return idx
|
if (idx === -1) return idx
|
||||||
return items.length - 1 - idx
|
return items.length - 1 - idx
|
||||||
}
|
}
|
||||||
|
|
||||||
case Focus.Specific:
|
case Focus.Specific:
|
||||||
return items.findIndex(item => resolvers.resolveId(item) === action.id)
|
return items.findIndex((item) => resolvers.resolveId(item) === action.id)
|
||||||
|
|
||||||
case Focus.Nothing:
|
case Focus.Nothing:
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ let focusableSelector = [
|
|||||||
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
||||||
// "hidden" can be the document.activeElement, because this is not possible
|
// "hidden" can be the document.activeElement, because this is not possible
|
||||||
// in real browsers.
|
// in real browsers.
|
||||||
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
||||||
: selector => `${selector}:not([tabindex='-1'])`
|
: (selector) => `${selector}:not([tabindex='-1'])`
|
||||||
)
|
)
|
||||||
.join(',')
|
.join(',')
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function match<TValue extends string | number = string, TReturnValue = un
|
|||||||
`Tried to handle "${value}" but there is no handler defined. Only defined handlers are: ${Object.keys(
|
`Tried to handle "${value}" but there is no handler defined. Only defined handlers are: ${Object.keys(
|
||||||
lookup
|
lookup
|
||||||
)
|
)
|
||||||
.map(key => `"${key}"`)
|
.map((key) => `"${key}"`)
|
||||||
.join(', ')}.`
|
.join(', ')}.`
|
||||||
)
|
)
|
||||||
if (Error.captureStackTrace) Error.captureStackTrace(error, match)
|
if (Error.captureStackTrace) Error.captureStackTrace(error, match)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ describe('Default functionality', () => {
|
|||||||
|
|
||||||
testRender(
|
testRender(
|
||||||
<Dummy>
|
<Dummy>
|
||||||
{data => {
|
{(data) => {
|
||||||
expect(data).toBe(slot)
|
expect(data).toBe(slot)
|
||||||
|
|
||||||
return <span>Contents</span>
|
return <span>Contents</span>
|
||||||
|
|||||||
@@ -102,10 +102,12 @@ function _render<TTag extends ElementType, TSlot>(
|
|||||||
tag: ElementType,
|
tag: ElementType,
|
||||||
name: string
|
name: string
|
||||||
) {
|
) {
|
||||||
let { as: Component = tag, children, refName = 'ref', ...passThroughProps } = omit(props, [
|
let {
|
||||||
'unmount',
|
as: Component = tag,
|
||||||
'static',
|
children,
|
||||||
])
|
refName = 'ref',
|
||||||
|
...passThroughProps
|
||||||
|
} = omit(props, ['unmount', 'static'])
|
||||||
|
|
||||||
// This allows us to use `<HeadlessUIComponent as={MyComponent} refName="innerRef" />`
|
// This allows us to use `<HeadlessUIComponent as={MyComponent} refName="innerRef" />`
|
||||||
let refRelatedProps = props.ref !== undefined ? { [refName]: props.ref } : {}
|
let refRelatedProps = props.ref !== undefined ? { [refName]: props.ref } : {}
|
||||||
@@ -132,7 +134,7 @@ function _render<TTag extends ElementType, TSlot>(
|
|||||||
`The current component <${name} /> is rendering a "Fragment".`,
|
`The current component <${name} /> is rendering a "Fragment".`,
|
||||||
`However we need to passthrough the following props:`,
|
`However we need to passthrough the following props:`,
|
||||||
Object.keys(passThroughProps)
|
Object.keys(passThroughProps)
|
||||||
.map(line => ` - ${line}`)
|
.map((line) => ` - ${line}`)
|
||||||
.join('\n'),
|
.join('\n'),
|
||||||
'',
|
'',
|
||||||
'You can apply a few solutions:',
|
'You can apply a few solutions:',
|
||||||
@@ -140,7 +142,7 @@ function _render<TTag extends ElementType, TSlot>(
|
|||||||
'Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',
|
'Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',
|
||||||
'Render a single element as the child so that we can forward the props onto that element.',
|
'Render a single element as the child so that we can forward the props onto that element.',
|
||||||
]
|
]
|
||||||
.map(line => ` - ${line}`)
|
.map((line) => ` - ${line}`)
|
||||||
.join('\n'),
|
.join('\n'),
|
||||||
].join('\n')
|
].join('\n')
|
||||||
)
|
)
|
||||||
@@ -211,7 +213,7 @@ function mergeEventFunctions(
|
|||||||
export function forwardRefWithAs<T extends { name: string; displayName?: string }>(
|
export function forwardRefWithAs<T extends { name: string; displayName?: string }>(
|
||||||
component: T
|
component: T
|
||||||
): T & { displayName: string } {
|
): T & { displayName: string } {
|
||||||
return Object.assign(forwardRef((component as unknown) as any) as any, {
|
return Object.assign(forwardRef(component as unknown as any) as any, {
|
||||||
displayName: component.displayName ?? component.name,
|
displayName: component.displayName ?? component.name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,12 @@
|
|||||||
},
|
},
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"target": "es5",
|
"target": "ESNext",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noEmit": true,
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true
|
"isolatedModules": true
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "**/*.test.tsx?"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
|
|
||||||
"compilerOptions": {
|
|
||||||
"jsx": "react"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
rollup(config, opts) {
|
|
||||||
if (opts.format === 'esm') {
|
|
||||||
config = { ...config, preserveModules: true }
|
|
||||||
config.output = { ...config.output, dir: 'dist/', entryFileNames: '[name].esm.js' }
|
|
||||||
delete config.output.file
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
},
|
|
||||||
}
|
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
export {}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace jest {
|
||||||
|
interface Matchers<R> {
|
||||||
|
toBeWithinRenderFrame(actual: number): R
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
rules: {
|
|
||||||
'react-hooks/rules-of-hooks': 'off',
|
|
||||||
'react-hooks/exhaustive-deps': 'off',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
module.exports = require('./headlessui.prod.cjs.js')
|
||||||
|
} else {
|
||||||
|
module.exports = require('./headlessui.dev.cjs.js')
|
||||||
|
}
|
||||||
@@ -4,12 +4,21 @@
|
|||||||
"description": "A set of completely unstyled, fully accessible UI components for Vue 3, designed to integrate beautifully with Tailwind CSS.",
|
"description": "A set of completely unstyled, fully accessible UI components for Vue 3, designed to integrate beautifully with Tailwind CSS.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"module": "dist/index.esm.js",
|
"module": "dist/headlessui.esm.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
"README.md",
|
"README.md",
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": {
|
||||||
|
"default": "./dist/headlessui.esm.js"
|
||||||
|
},
|
||||||
|
"require": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -27,14 +36,16 @@
|
|||||||
"build": "../../scripts/build.sh",
|
"build": "../../scripts/build.sh",
|
||||||
"watch": "../../scripts/watch.sh",
|
"watch": "../../scripts/watch.sh",
|
||||||
"test": "../../scripts/test.sh",
|
"test": "../../scripts/test.sh",
|
||||||
"lint": "../../scripts/lint.sh"
|
"lint": "../../scripts/lint.sh",
|
||||||
|
"playground": "yarn workspace playground-vue dev",
|
||||||
|
"clean": "rimraf ./dist"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.0.0"
|
"vue": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/vue": "^5.1.0",
|
"@testing-library/vue": "^5.8.2",
|
||||||
"@vue/test-utils": "^2.0.0-beta.7",
|
"@vue/test-utils": "^2.0.0-rc.18",
|
||||||
"vue": "3.0.7"
|
"vue": "^3.2.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
module.exports = require('../../postcss.config.js')
|
|
||||||
@@ -66,7 +66,7 @@ beforeAll(() => {
|
|||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function nextFrame() {
|
function nextFrame() {
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
resolve()
|
resolve()
|
||||||
@@ -95,9 +95,9 @@ function renderTemplate(input: string | Partial<DefineComponent>) {
|
|||||||
|
|
||||||
return render(
|
return render(
|
||||||
defineComponent(
|
defineComponent(
|
||||||
(Object.assign({}, input, {
|
Object.assign({}, input, {
|
||||||
components: { ...defaultComponents, ...input.components },
|
components: { ...defaultComponents, ...input.components },
|
||||||
}) as unknown) as DefineComponent
|
}) as Parameters<typeof defineComponent>[0]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -491,9 +491,7 @@ describe('Rendering', () => {
|
|||||||
template: html`
|
template: html`
|
||||||
<Combobox v-model="value">
|
<Combobox v-model="value">
|
||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton type="submit">
|
<ComboboxButton type="submit"> Trigger </ComboboxButton>
|
||||||
Trigger
|
|
||||||
</ComboboxButton>
|
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
setup: () => ({ value: ref(null) }),
|
setup: () => ({ value: ref(null) }),
|
||||||
@@ -506,16 +504,14 @@ describe('Rendering', () => {
|
|||||||
'should set the `type` to "button" when using the `as` prop which resolves to a "button"',
|
'should set the `type` to "button" when using the `as` prop which resolves to a "button"',
|
||||||
suppressConsoleLogs(async () => {
|
suppressConsoleLogs(async () => {
|
||||||
let CustomButton = defineComponent({
|
let CustomButton = defineComponent({
|
||||||
setup: props => () => h('button', { ...props }),
|
setup: (props) => () => h('button', { ...props }),
|
||||||
})
|
})
|
||||||
|
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Combobox v-model="value">
|
<Combobox v-model="value">
|
||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton :as="CustomButton">
|
<ComboboxButton :as="CustomButton"> Trigger </ComboboxButton>
|
||||||
Trigger
|
|
||||||
</ComboboxButton>
|
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
@@ -535,9 +531,7 @@ describe('Rendering', () => {
|
|||||||
template: html`
|
template: html`
|
||||||
<Combobox v-model="value">
|
<Combobox v-model="value">
|
||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton as="div">
|
<ComboboxButton as="div"> Trigger </ComboboxButton>
|
||||||
Trigger
|
|
||||||
</ComboboxButton>
|
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
setup: () => ({ value: ref(null) }),
|
setup: () => ({ value: ref(null) }),
|
||||||
@@ -550,16 +544,14 @@ describe('Rendering', () => {
|
|||||||
'should not set the `type` to "button" when using the `as` prop which resolves to a "div"',
|
'should not set the `type` to "button" when using the `as` prop which resolves to a "div"',
|
||||||
suppressConsoleLogs(async () => {
|
suppressConsoleLogs(async () => {
|
||||||
let CustomButton = defineComponent({
|
let CustomButton = defineComponent({
|
||||||
setup: props => () => h('div', props),
|
setup: (props) => () => h('div', props),
|
||||||
})
|
})
|
||||||
|
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Combobox v-model="value">
|
<Combobox v-model="value">
|
||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton :as="CustomButton">
|
<ComboboxButton :as="CustomButton"> Trigger </ComboboxButton>
|
||||||
Trigger
|
|
||||||
</ComboboxButton>
|
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
@@ -765,15 +757,9 @@ describe('Rendering composition', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption as="button" value="a">
|
<ComboboxOption as="button" value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption as="button" value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption as="button" value="c"> Option C </ComboboxOption>
|
||||||
<ComboboxOption as="button" value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption as="button" value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -790,7 +776,7 @@ describe('Rendering composition', () => {
|
|||||||
await click(getComboboxButton())
|
await click(getComboboxButton())
|
||||||
|
|
||||||
// Verify options are buttons now
|
// Verify options are buttons now
|
||||||
getComboboxOptions().forEach(option => assertComboboxOption(option, { tag: 'button' }))
|
getComboboxOptions().forEach((option) => assertComboboxOption(option, { tag: 'button' }))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -823,9 +809,7 @@ describe('Composition', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<OpenClosedWrite :open="true">
|
<OpenClosedWrite :open="true">
|
||||||
<ComboboxOptions v-slot="data">
|
<ComboboxOptions v-slot="data"> {{JSON.stringify(data)}} </ComboboxOptions>
|
||||||
{{JSON.stringify(data)}}
|
|
||||||
</ComboboxOptions>
|
|
||||||
</OpenClosedWrite>
|
</OpenClosedWrite>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -854,9 +838,7 @@ describe('Composition', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<OpenClosedWrite :open="false">
|
<OpenClosedWrite :open="false">
|
||||||
<ComboboxOptions v-slot="data">
|
<ComboboxOptions v-slot="data"> {{JSON.stringify(data)}} </ComboboxOptions>
|
||||||
{{JSON.stringify(data)}}
|
|
||||||
</ComboboxOptions>
|
|
||||||
</OpenClosedWrite>
|
</OpenClosedWrite>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -966,7 +948,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option, { selected: false }))
|
options.forEach((option) => assertComboboxOption(option, { selected: false }))
|
||||||
|
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
assertNoSelectedComboboxOption()
|
assertNoSelectedComboboxOption()
|
||||||
@@ -1274,7 +1256,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1408,15 +1390,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -1533,7 +1509,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// Verify that the first combobox option is active
|
// Verify that the first combobox option is active
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
@@ -1701,7 +1677,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// Verify that the first combobox option is active
|
// Verify that the first combobox option is active
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
@@ -1869,7 +1845,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
@@ -2002,12 +1978,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="a">Option A</ComboboxOption>
|
<ComboboxOption value="a">Option A</ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
Option B
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -2029,7 +2001,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[0])
|
assertActiveComboboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -2079,7 +2051,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
@@ -2213,12 +2185,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="a">Option A</ComboboxOption>
|
<ComboboxOption value="a">Option A</ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
Option B
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -2240,7 +2208,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[0])
|
assertActiveComboboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -2496,7 +2464,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// Verify that the first combobox option is active
|
// Verify that the first combobox option is active
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
@@ -2649,7 +2617,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2679,9 +2647,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="b">Option B</ComboboxOption>
|
<ComboboxOption value="b">Option B</ComboboxOption>
|
||||||
<ComboboxOption value="c">Option C</ComboboxOption>
|
<ComboboxOption value="c">Option C</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
@@ -2702,7 +2668,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2720,12 +2686,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="c">Option C</ComboboxOption>
|
<ComboboxOption value="c">Option C</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -2745,7 +2707,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// Open combobox
|
// Open combobox
|
||||||
@@ -2799,7 +2761,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// Verify that the first combobox option is active
|
// Verify that the first combobox option is active
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
@@ -2968,7 +2930,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2998,9 +2960,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="b">Option B</ComboboxOption>
|
<ComboboxOption value="b">Option B</ComboboxOption>
|
||||||
<ComboboxOption value="c">Option C</ComboboxOption>
|
<ComboboxOption value="c">Option C</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
@@ -3021,7 +2981,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -3039,12 +2999,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="c">Option C</ComboboxOption>
|
<ComboboxOption value="c">Option C</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -3064,7 +3020,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// Open combobox
|
// Open combobox
|
||||||
@@ -3117,7 +3073,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
@@ -3250,12 +3206,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="a">Option A</ComboboxOption>
|
<ComboboxOption value="a">Option A</ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
Option B
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -3277,7 +3229,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[0])
|
assertActiveComboboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -3291,12 +3243,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="c">Option C</ComboboxOption>
|
<ComboboxOption value="c">Option C</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -3316,7 +3264,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertNoActiveComboboxOption()
|
assertNoActiveComboboxOption()
|
||||||
|
|
||||||
// Going up or down should select the single available option
|
// Going up or down should select the single available option
|
||||||
@@ -3374,7 +3322,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -3436,7 +3384,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveComboboxOption(options[2])
|
assertActiveComboboxOption(options[2])
|
||||||
@@ -3570,12 +3518,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="a">Option A</ComboboxOption>
|
<ComboboxOption value="a">Option A</ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
Option B
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -3597,7 +3541,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
assertActiveComboboxOption(options[0])
|
assertActiveComboboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -3647,12 +3591,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="a">Option A</ComboboxOption>
|
<ComboboxOption value="a">Option A</ComboboxOption>
|
||||||
<ComboboxOption value="b">Option B</ComboboxOption>
|
<ComboboxOption value="b">Option B</ComboboxOption>
|
||||||
<ComboboxOption disabled value="c">
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
Option C
|
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -3683,15 +3623,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="a">Option A</ComboboxOption>
|
<ComboboxOption value="a">Option A</ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
Option B
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -3721,18 +3655,10 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -3797,12 +3723,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="a">Option A</ComboboxOption>
|
<ComboboxOption value="a">Option A</ComboboxOption>
|
||||||
<ComboboxOption value="b">Option B</ComboboxOption>
|
<ComboboxOption value="b">Option B</ComboboxOption>
|
||||||
<ComboboxOption disabled value="c">
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
Option C
|
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -3836,15 +3758,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="a">Option A</ComboboxOption>
|
<ComboboxOption value="a">Option A</ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
Option B
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -3874,18 +3790,10 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -3951,12 +3859,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="c">Option C</ComboboxOption>
|
<ComboboxOption value="c">Option C</ComboboxOption>
|
||||||
<ComboboxOption value="d">Option D</ComboboxOption>
|
<ComboboxOption value="d">Option D</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
@@ -3990,15 +3894,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="d">Option D</ComboboxOption>
|
<ComboboxOption value="d">Option D</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -4029,18 +3927,10 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -4106,12 +3996,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="c">Option C</ComboboxOption>
|
<ComboboxOption value="c">Option C</ComboboxOption>
|
||||||
<ComboboxOption value="d">Option D</ComboboxOption>
|
<ComboboxOption value="d">Option D</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
@@ -4145,15 +4031,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="d">Option D</ComboboxOption>
|
<ComboboxOption value="d">Option D</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -4184,18 +4064,10 @@ describe('Keyboard interactions', () => {
|
|||||||
<ComboboxInput />
|
<ComboboxInput />
|
||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption disabled value="a">
|
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
|
||||||
Option A
|
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
|
||||||
</ComboboxOption>
|
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
|
||||||
<ComboboxOption disabled value="b">
|
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
|
||||||
Option B
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
`,
|
`,
|
||||||
@@ -4250,7 +4122,7 @@ describe('Keyboard interactions', () => {
|
|||||||
let filteredPeople = computed(() => {
|
let filteredPeople = computed(() => {
|
||||||
return query.value === ''
|
return query.value === ''
|
||||||
? props.people
|
? props.people
|
||||||
: props.people.filter(person =>
|
: props.people.filter((person) =>
|
||||||
person.name.toLowerCase().includes(query.value.toLowerCase())
|
person.name.toLowerCase().includes(query.value.toLowerCase())
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -4586,7 +4458,7 @@ describe('Mouse interactions', () => {
|
|||||||
// Verify we have combobox options
|
// Verify we have combobox options
|
||||||
let options = getComboboxOptions()
|
let options = getComboboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertComboboxOption(option))
|
options.forEach((option) => assertComboboxOption(option))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -5036,9 +4908,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="alice">alice</ComboboxOption>
|
<ComboboxOption value="alice">alice</ComboboxOption>
|
||||||
<ComboboxOption disabled value="bob">
|
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
|
||||||
bob
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -5066,9 +4936,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="alice">alice</ComboboxOption>
|
<ComboboxOption value="alice">alice</ComboboxOption>
|
||||||
<ComboboxOption disabled value="bob">
|
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
|
||||||
bob
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -5145,9 +5013,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="alice">alice</ComboboxOption>
|
<ComboboxOption value="alice">alice</ComboboxOption>
|
||||||
<ComboboxOption disabled value="bob">
|
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
|
||||||
bob
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -5227,9 +5093,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="alice">alice</ComboboxOption>
|
<ComboboxOption value="alice">alice</ComboboxOption>
|
||||||
<ComboboxOption disabled value="bob">
|
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
|
||||||
bob
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
@@ -5309,9 +5173,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ComboboxButton>Trigger</ComboboxButton>
|
<ComboboxButton>Trigger</ComboboxButton>
|
||||||
<ComboboxOptions>
|
<ComboboxOptions>
|
||||||
<ComboboxOption value="alice">alice</ComboboxOption>
|
<ComboboxOption value="alice">alice</ComboboxOption>
|
||||||
<ComboboxOption disabled value="bob">
|
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
|
||||||
bob
|
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
<ComboboxOption value="charlie">charlie</ComboboxOption>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ export let Combobox = defineComponent({
|
|||||||
{
|
{
|
||||||
resolveItems: () => options.value,
|
resolveItems: () => options.value,
|
||||||
resolveActiveIndex: () => activeOptionIndex.value,
|
resolveActiveIndex: () => activeOptionIndex.value,
|
||||||
resolveId: option => option.id,
|
resolveId: (option) => option.id,
|
||||||
resolveDisabled: option => option.dataRef.disabled,
|
resolveDisabled: (option) => option.dataRef.disabled,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ export let Combobox = defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectOption(id: string) {
|
selectOption(id: string) {
|
||||||
let option = options.value.find(item => item.id === id)
|
let option = options.value.find((item) => item.id === id)
|
||||||
if (!option) return
|
if (!option) return
|
||||||
|
|
||||||
let { dataRef } = option
|
let { dataRef } = option
|
||||||
@@ -193,7 +193,7 @@ export let Combobox = defineComponent({
|
|||||||
let nextOptions = options.value.slice()
|
let nextOptions = options.value.slice()
|
||||||
let currentActiveOption =
|
let currentActiveOption =
|
||||||
activeOptionIndex.value !== null ? nextOptions[activeOptionIndex.value] : null
|
activeOptionIndex.value !== null ? nextOptions[activeOptionIndex.value] : null
|
||||||
let idx = nextOptions.findIndex(a => a.id === id)
|
let idx = nextOptions.findIndex((a) => a.id === id)
|
||||||
if (idx !== -1) nextOptions.splice(idx, 1)
|
if (idx !== -1) nextOptions.splice(idx, 1)
|
||||||
options.value = nextOptions
|
options.value = nextOptions
|
||||||
activeOptionIndex.value = (() => {
|
activeOptionIndex.value = (() => {
|
||||||
@@ -207,7 +207,7 @@ export let Combobox = defineComponent({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
let active = document.activeElement
|
let active = document.activeElement
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import { html } from '../../test-utils/html'
|
|||||||
import { click } from '../../test-utils/interactions'
|
import { click } from '../../test-utils/interactions'
|
||||||
import { getByText } from '../../test-utils/accessibility-assertions'
|
import { getByText } from '../../test-utils/accessibility-assertions'
|
||||||
|
|
||||||
function format(input: Element | string) {
|
function format(input: Element | null | string) {
|
||||||
|
if (input === null) throw new Error('input is null')
|
||||||
let contents = (typeof input === 'string' ? input : (input as HTMLElement).outerHTML).trim()
|
let contents = (typeof input === 'string' ? input : (input as HTMLElement).outerHTML).trim()
|
||||||
return prettier.format(contents, { parser: 'babel' })
|
return prettier.format(contents, { parser: 'babel' })
|
||||||
}
|
}
|
||||||
@@ -22,59 +23,47 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
|
||||||
let defaultComponents = { Description }
|
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
|
||||||
return render(defineComponent({ template: input, components: defaultComponents }))
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(
|
|
||||||
defineComponent(
|
|
||||||
Object.assign({}, input, {
|
|
||||||
components: { ...defaultComponents, ...input.components },
|
|
||||||
}) as Parameters<typeof defineComponent>[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should be possible to use useDescriptions without using a Description', async () => {
|
it('should be possible to use useDescriptions without using a Description', async () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = render(
|
||||||
render() {
|
defineComponent({
|
||||||
return h('div', [h('div', { 'aria-describedby': this.describedby }, ['No description'])])
|
components: { Description },
|
||||||
},
|
render() {
|
||||||
setup() {
|
return h('div', [h('div', { 'aria-describedby': this.describedby }, ['No description'])])
|
||||||
let describedby = useDescriptions()
|
},
|
||||||
return { describedby }
|
setup() {
|
||||||
},
|
let describedby = useDescriptions()
|
||||||
})
|
return { describedby }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
expect(format(container.firstElementChild)).toEqual(
|
expect(format(container.firstElementChild)).toEqual(
|
||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>No description</div>
|
||||||
No description
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to use useDescriptions and a single Description, and have them linked', async () => {
|
it('should be possible to use useDescriptions and a single Description, and have them linked', async () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = render(
|
||||||
render() {
|
defineComponent({
|
||||||
return h('div', [
|
components: { Description },
|
||||||
h('div', { 'aria-describedby': this.describedby }, [
|
render() {
|
||||||
h(Description, () => 'I am a description'),
|
return h('div', [
|
||||||
h('span', 'Contents'),
|
h('div', { 'aria-describedby': this.describedby }, [
|
||||||
]),
|
h(Description, () => 'I am a description'),
|
||||||
])
|
h('span', 'Contents'),
|
||||||
},
|
]),
|
||||||
setup() {
|
])
|
||||||
let describedby = useDescriptions()
|
},
|
||||||
return { describedby }
|
setup() {
|
||||||
},
|
let describedby = useDescriptions()
|
||||||
})
|
return { describedby }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
await new Promise<void>(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
@@ -82,12 +71,8 @@ it('should be possible to use useDescriptions and a single Description, and have
|
|||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div aria-describedby="headlessui-description-1">
|
<div aria-describedby="headlessui-description-1">
|
||||||
<p id="headlessui-description-1">
|
<p id="headlessui-description-1">I am a description</p>
|
||||||
I am a description
|
<span>Contents</span>
|
||||||
</p>
|
|
||||||
<span>
|
|
||||||
Contents
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
@@ -95,21 +80,24 @@ it('should be possible to use useDescriptions and a single Description, and have
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to use useDescriptions and multiple Description components, and have them linked', async () => {
|
it('should be possible to use useDescriptions and multiple Description components, and have them linked', async () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = render(
|
||||||
render() {
|
defineComponent({
|
||||||
return h('div', [
|
components: { Description },
|
||||||
h('div', { 'aria-describedby': this.describedby }, [
|
render() {
|
||||||
h(Description, () => 'I am a description'),
|
return h('div', [
|
||||||
h('span', 'Contents'),
|
h('div', { 'aria-describedby': this.describedby }, [
|
||||||
h(Description, () => 'I am also a description'),
|
h(Description, () => 'I am a description'),
|
||||||
]),
|
h('span', 'Contents'),
|
||||||
])
|
h(Description, () => 'I am also a description'),
|
||||||
},
|
]),
|
||||||
setup() {
|
])
|
||||||
let describedby = useDescriptions()
|
},
|
||||||
return { describedby }
|
setup() {
|
||||||
},
|
let describedby = useDescriptions()
|
||||||
})
|
return { describedby }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
await new Promise<void>(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
@@ -117,15 +105,9 @@ it('should be possible to use useDescriptions and multiple Description component
|
|||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div aria-describedby="headlessui-description-1 headlessui-description-2">
|
<div aria-describedby="headlessui-description-1 headlessui-description-2">
|
||||||
<p id="headlessui-description-1">
|
<p id="headlessui-description-1">I am a description</p>
|
||||||
I am a description
|
<span>Contents</span>
|
||||||
</p>
|
<p id="headlessui-description-2">I am also a description</p>
|
||||||
<span>
|
|
||||||
Contents
|
|
||||||
</span>
|
|
||||||
<p id="headlessui-description-2">
|
|
||||||
I am also a description
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
@@ -133,21 +115,24 @@ it('should be possible to use useDescriptions and multiple Description component
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to update a prop from the parent and it should reflect in the Description component', async () => {
|
it('should be possible to update a prop from the parent and it should reflect in the Description component', async () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = render(
|
||||||
render() {
|
defineComponent({
|
||||||
return h('div', [
|
components: { Description },
|
||||||
h('div', { 'aria-describedby': this.describedby }, [
|
render() {
|
||||||
h(Description, () => 'I am a description'),
|
return h('div', [
|
||||||
h('button', { onClick: () => this.count++ }, '+1'),
|
h('div', { 'aria-describedby': this.describedby }, [
|
||||||
]),
|
h(Description, () => 'I am a description'),
|
||||||
])
|
h('button', { onClick: () => this.count++ }, '+1'),
|
||||||
},
|
]),
|
||||||
setup() {
|
])
|
||||||
let count = ref(0)
|
},
|
||||||
let describedby = useDescriptions({ props: { 'data-count': count } })
|
setup() {
|
||||||
return { count, describedby }
|
let count = ref(0)
|
||||||
},
|
let describedby = useDescriptions({ props: { 'data-count': count } })
|
||||||
})
|
return { count, describedby }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
await new Promise<void>(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
@@ -155,9 +140,7 @@ it('should be possible to update a prop from the parent and it should reflect in
|
|||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div aria-describedby="headlessui-description-1">
|
<div aria-describedby="headlessui-description-1">
|
||||||
<p data-count="0" id="headlessui-description-1">
|
<p data-count="0" id="headlessui-description-1">I am a description</p>
|
||||||
I am a description
|
|
||||||
</p>
|
|
||||||
<button>+1</button>
|
<button>+1</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,9 +153,7 @@ it('should be possible to update a prop from the parent and it should reflect in
|
|||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div aria-describedby="headlessui-description-1">
|
<div aria-describedby="headlessui-description-1">
|
||||||
<p data-count="1" id="headlessui-description-1">
|
<p data-count="1" id="headlessui-description-1">I am a description</p>
|
||||||
I am a description
|
|
||||||
</p>
|
|
||||||
<button>+1</button>
|
<button>+1</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, ref, nextTick, h } from 'vue'
|
import { defineComponent, ref, nextTick, h, ComponentOptionsWithoutProps } from 'vue'
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
|
|
||||||
import { Dialog, DialogOverlay, DialogTitle, DialogDescription } from './dialog'
|
import { Dialog, DialogOverlay, DialogTitle, DialogDescription } from './dialog'
|
||||||
@@ -30,9 +30,7 @@ afterAll(() => jest.restoreAllMocks())
|
|||||||
|
|
||||||
let TabSentinel = defineComponent({
|
let TabSentinel = defineComponent({
|
||||||
name: 'TabSentinel',
|
name: 'TabSentinel',
|
||||||
template: html`
|
template: html` <div :tabindex="0"></div> `,
|
||||||
<div :tabindex="0"></div>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
jest.mock('../../hooks/use-id')
|
jest.mock('../../hooks/use-id')
|
||||||
@@ -44,7 +42,7 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { Dialog, DialogOverlay, DialogTitle, DialogDescription, TabSentinel }
|
let defaultComponents = { Dialog, DialogOverlay, DialogTitle, DialogDescription, TabSentinel }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export let Dialog = defineComponent({
|
|||||||
provide(DialogContext, api)
|
provide(DialogContext, api)
|
||||||
|
|
||||||
// Handle outside click
|
// Handle outside click
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
|
|
||||||
if (dialogState.value !== DialogStates.Open) return
|
if (dialogState.value !== DialogStates.Open) return
|
||||||
@@ -205,7 +205,7 @@ export let Dialog = defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Handle `Escape` to close
|
// Handle `Escape` to close
|
||||||
useWindowEvent('keydown', event => {
|
useWindowEvent('keydown', (event) => {
|
||||||
if (event.key !== Keys.Escape) return
|
if (event.key !== Keys.Escape) return
|
||||||
if (dialogState.value !== DialogStates.Open) return
|
if (dialogState.value !== DialogStates.Open) return
|
||||||
if (containers.value.size > 1) return // 1 is myself, otherwise other elements in the Stack
|
if (containers.value.size > 1) return // 1 is myself, otherwise other elements in the Stack
|
||||||
@@ -215,7 +215,7 @@ export let Dialog = defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Scroll lock
|
// Scroll lock
|
||||||
watchEffect(onInvalidate => {
|
watchEffect((onInvalidate) => {
|
||||||
if (dialogState.value !== DialogStates.Open) return
|
if (dialogState.value !== DialogStates.Open) return
|
||||||
|
|
||||||
let overflow = document.documentElement.style.overflow
|
let overflow = document.documentElement.style.overflow
|
||||||
@@ -233,12 +233,12 @@ export let Dialog = defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Trigger close when the FocusTrap gets hidden
|
// Trigger close when the FocusTrap gets hidden
|
||||||
watchEffect(onInvalidate => {
|
watchEffect((onInvalidate) => {
|
||||||
if (dialogState.value !== DialogStates.Open) return
|
if (dialogState.value !== DialogStates.Open) return
|
||||||
let container = dom(internalDialogRef)
|
let container = dom(internalDialogRef)
|
||||||
if (!container) return
|
if (!container) return
|
||||||
|
|
||||||
let observer = new IntersectionObserver(entries => {
|
let observer = new IntersectionObserver((entries) => {
|
||||||
for (let entry of entries) {
|
for (let entry of entries) {
|
||||||
if (
|
if (
|
||||||
entry.boundingClientRect.x === 0 &&
|
entry.boundingClientRect.x === 0 &&
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, nextTick, ref, watch, h } from 'vue'
|
import { defineComponent, nextTick, ref, watch, h, ComponentOptionsWithoutProps } from 'vue'
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from './disclosure'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from './disclosure'
|
||||||
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
||||||
@@ -19,7 +19,7 @@ jest.mock('../../hooks/use-id')
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { Disclosure, DisclosureButton, DisclosurePanel }
|
let defaultComponents = { Disclosure, DisclosureButton, DisclosurePanel }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -297,9 +297,7 @@ describe('Rendering', () => {
|
|||||||
renderTemplate(
|
renderTemplate(
|
||||||
html`
|
html`
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
<DisclosureButton>
|
<DisclosureButton> Trigger </DisclosureButton>
|
||||||
Trigger
|
|
||||||
</DisclosureButton>
|
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@@ -311,9 +309,7 @@ describe('Rendering', () => {
|
|||||||
renderTemplate(
|
renderTemplate(
|
||||||
html`
|
html`
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
<DisclosureButton type="submit">
|
<DisclosureButton type="submit"> Trigger </DisclosureButton>
|
||||||
Trigger
|
|
||||||
</DisclosureButton>
|
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@@ -327,14 +323,12 @@ describe('Rendering', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
<DisclosureButton :as="CustomButton">
|
<DisclosureButton :as="CustomButton"> Trigger </DisclosureButton>
|
||||||
Trigger
|
|
||||||
</DisclosureButton>
|
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('button', { ...props }),
|
setup: (props) => () => h('button', { ...props }),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -349,9 +343,7 @@ describe('Rendering', () => {
|
|||||||
renderTemplate(
|
renderTemplate(
|
||||||
html`
|
html`
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
<DisclosureButton as="div">
|
<DisclosureButton as="div"> Trigger </DisclosureButton>
|
||||||
Trigger
|
|
||||||
</DisclosureButton>
|
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@@ -365,14 +357,12 @@ describe('Rendering', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
<DisclosureButton :as="CustomButton">
|
<DisclosureButton :as="CustomButton"> Trigger </DisclosureButton>
|
||||||
Trigger
|
|
||||||
</DisclosureButton>
|
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('div', props),
|
setup: (props) => () => h('div', props),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, ref, nextTick, onMounted } from 'vue'
|
import { defineComponent, ref, nextTick, onMounted, ComponentOptionsWithoutProps } from 'vue'
|
||||||
|
|
||||||
import { FocusTrap } from './focus-trap'
|
import { FocusTrap } from './focus-trap'
|
||||||
import { assertActiveElement, getByText } from '../../test-utils/accessibility-assertions'
|
import { assertActiveElement, getByText } from '../../test-utils/accessibility-assertions'
|
||||||
@@ -16,7 +16,7 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { FocusTrap }
|
let defaultComponents = { FocusTrap }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -41,7 +41,7 @@ it('should focus the first focusable element inside the FocusTrap', async () =>
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
await new Promise(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
assertActiveElement(getByText('Trigger'))
|
assertActiveElement(getByText('Trigger'))
|
||||||
})
|
})
|
||||||
@@ -64,7 +64,7 @@ it('should focus the autoFocus element inside the FocusTrap if that exists', asy
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
assertActiveElement(document.getElementById('b'))
|
assertActiveElement(document.getElementById('b'))
|
||||||
})
|
})
|
||||||
@@ -84,7 +84,7 @@ it('should focus the initialFocus element inside the FocusTrap if that exists',
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
assertActiveElement(document.getElementById('c'))
|
assertActiveElement(document.getElementById('c'))
|
||||||
})
|
})
|
||||||
@@ -104,7 +104,7 @@ it('should focus the initialFocus element inside the FocusTrap even if another e
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
assertActiveElement(document.getElementById('c'))
|
assertActiveElement(document.getElementById('c'))
|
||||||
})
|
})
|
||||||
@@ -121,7 +121,7 @@ it('should warn when there is no focusable element inside the FocusTrap', async
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
await new Promise(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
expect(spy.mock.calls[0][0]).toBe('There are no focusable elements inside the <FocusTrap />')
|
expect(spy.mock.calls[0][0]).toBe('There are no focusable elements inside the <FocusTrap />')
|
||||||
})
|
})
|
||||||
@@ -143,7 +143,7 @@ it(
|
|||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
let [a, b, c, d] = Array.from(document.querySelectorAll('input'))
|
let [a, b, c, d] = Array.from(document.querySelectorAll('input'))
|
||||||
|
|
||||||
@@ -193,14 +193,10 @@ it('should restore the previously focused element, before entering the FocusTrap
|
|||||||
template: html`
|
template: html`
|
||||||
<div>
|
<div>
|
||||||
<input id="item-1" ref="autoFocusRef" />
|
<input id="item-1" ref="autoFocusRef" />
|
||||||
<button id="item-2" @click="visible = true">
|
<button id="item-2" @click="visible = true">Open modal</button>
|
||||||
Open modal
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<FocusTrap v-if="visible">
|
<FocusTrap v-if="visible">
|
||||||
<button id="item-3" @click="visible = false">
|
<button id="item-3" @click="visible = false">Close</button>
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
@@ -214,7 +210,7 @@ it('should restore the previously focused element, before entering the FocusTrap
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
// The input should have focus by default because of the autoFocus prop
|
// The input should have focus by default because of the autoFocus prop
|
||||||
assertActiveElement(document.getElementById('item-1'))
|
assertActiveElement(document.getElementById('item-1'))
|
||||||
@@ -247,7 +243,7 @@ it('should be possible to tab to the next focusable element within the focus tra
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
await new Promise(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
// Item A should be focused because the FocusTrap will focus the first item
|
// Item A should be focused because the FocusTrap will focus the first item
|
||||||
assertActiveElement(document.getElementById('item-a'))
|
assertActiveElement(document.getElementById('item-a'))
|
||||||
@@ -302,12 +298,8 @@ it('should skip the initial "hidden" elements within the focus trap', async () =
|
|||||||
<div>
|
<div>
|
||||||
<button id="before">Before</button>
|
<button id="before">Before</button>
|
||||||
<FocusTrap>
|
<FocusTrap>
|
||||||
<button id="item-a" style="display:none">
|
<button id="item-a" style="display:none">Item A</button>
|
||||||
Item A
|
<button id="item-b" style="display:none">Item B</button>
|
||||||
</button>
|
|
||||||
<button id="item-b" style="display:none">
|
|
||||||
Item B
|
|
||||||
</button>
|
|
||||||
<button id="item-c">Item C</button>
|
<button id="item-c">Item C</button>
|
||||||
<button id="item-d">Item D</button>
|
<button id="item-d">Item D</button>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
@@ -328,9 +320,7 @@ it('should be possible skip "hidden" elements within the focus trap', async () =
|
|||||||
<FocusTrap>
|
<FocusTrap>
|
||||||
<button id="item-a">Item A</button>
|
<button id="item-a">Item A</button>
|
||||||
<button id="item-b">Item B</button>
|
<button id="item-b">Item B</button>
|
||||||
<button id="item-c" style="display:none">
|
<button id="item-c" style="display:none">Item C</button>
|
||||||
Item C
|
|
||||||
</button>
|
|
||||||
<button id="item-d">Item D</button>
|
<button id="item-d">Item D</button>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
<button>After</button>
|
<button>After</button>
|
||||||
@@ -364,9 +354,7 @@ it('should be possible skip disabled elements within the focus trap', async () =
|
|||||||
<FocusTrap>
|
<FocusTrap>
|
||||||
<button id="item-a">Item A</button>
|
<button id="item-a">Item A</button>
|
||||||
<button id="item-b">Item B</button>
|
<button id="item-b">Item B</button>
|
||||||
<button id="item-c" disabled>
|
<button id="item-c" disabled>Item C</button>
|
||||||
Item C
|
|
||||||
</button>
|
|
||||||
<button id="item-d">Item D</button>
|
<button id="item-d">Item D</button>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
<button>After</button>
|
<button>After</button>
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import { html } from '../../test-utils/html'
|
|||||||
import { click } from '../../test-utils/interactions'
|
import { click } from '../../test-utils/interactions'
|
||||||
import { getByText } from '../../test-utils/accessibility-assertions'
|
import { getByText } from '../../test-utils/accessibility-assertions'
|
||||||
|
|
||||||
function format(input: Element | string) {
|
function format(input: Element | null | string) {
|
||||||
|
if (input === null) throw new Error('input is null')
|
||||||
let contents = (typeof input === 'string' ? input : (input as HTMLElement).outerHTML).trim()
|
let contents = (typeof input === 'string' ? input : (input as HTMLElement).outerHTML).trim()
|
||||||
return prettier.format(contents, { parser: 'babel' })
|
return prettier.format(contents, { parser: 'babel' })
|
||||||
}
|
}
|
||||||
@@ -22,59 +23,47 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
|
||||||
let defaultComponents = { Label }
|
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
|
||||||
return render(defineComponent({ template: input, components: defaultComponents }))
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(
|
|
||||||
defineComponent(
|
|
||||||
Object.assign({}, input, {
|
|
||||||
components: { ...defaultComponents, ...input.components },
|
|
||||||
}) as Parameters<typeof defineComponent>[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should be possible to use useLabels without using a Label', async () => {
|
it('should be possible to use useLabels without using a Label', async () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = render(
|
||||||
render() {
|
defineComponent({
|
||||||
return h('div', [h('div', { 'aria-labelledby': this.labelledby }, ['No label'])])
|
components: { Label },
|
||||||
},
|
render() {
|
||||||
setup() {
|
return h('div', [h('div', { 'aria-labelledby': this.labelledby }, ['No label'])])
|
||||||
let labelledby = useLabels()
|
},
|
||||||
return { labelledby }
|
setup() {
|
||||||
},
|
let labelledby = useLabels()
|
||||||
})
|
return { labelledby }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
expect(format(container.firstElementChild)).toEqual(
|
expect(format(container.firstElementChild)).toEqual(
|
||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>No label</div>
|
||||||
No label
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to use useLabels and a single Label, and have them linked', async () => {
|
it('should be possible to use useLabels and a single Label, and have them linked', async () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = render(
|
||||||
render() {
|
defineComponent({
|
||||||
return h('div', [
|
components: { Label },
|
||||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
render() {
|
||||||
h(Label, () => 'I am a label'),
|
return h('div', [
|
||||||
h('span', 'Contents'),
|
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||||
]),
|
h(Label, () => 'I am a label'),
|
||||||
])
|
h('span', 'Contents'),
|
||||||
},
|
]),
|
||||||
setup() {
|
])
|
||||||
let labelledby = useLabels()
|
},
|
||||||
return { labelledby }
|
setup() {
|
||||||
},
|
let labelledby = useLabels()
|
||||||
})
|
return { labelledby }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
await new Promise<void>(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
@@ -82,12 +71,8 @@ it('should be possible to use useLabels and a single Label, and have them linked
|
|||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div aria-labelledby="headlessui-label-1">
|
<div aria-labelledby="headlessui-label-1">
|
||||||
<label id="headlessui-label-1">
|
<label id="headlessui-label-1">I am a label</label>
|
||||||
I am a label
|
<span>Contents</span>
|
||||||
</label>
|
|
||||||
<span>
|
|
||||||
Contents
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
@@ -95,21 +80,24 @@ it('should be possible to use useLabels and a single Label, and have them linked
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to use useLabels and multiple Label components, and have them linked', async () => {
|
it('should be possible to use useLabels and multiple Label components, and have them linked', async () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = render(
|
||||||
render() {
|
defineComponent({
|
||||||
return h('div', [
|
components: { Label },
|
||||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
render() {
|
||||||
h(Label, () => 'I am a label'),
|
return h('div', [
|
||||||
h('span', 'Contents'),
|
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||||
h(Label, () => 'I am also a label'),
|
h(Label, () => 'I am a label'),
|
||||||
]),
|
h('span', 'Contents'),
|
||||||
])
|
h(Label, () => 'I am also a label'),
|
||||||
},
|
]),
|
||||||
setup() {
|
])
|
||||||
let labelledby = useLabels()
|
},
|
||||||
return { labelledby }
|
setup() {
|
||||||
},
|
let labelledby = useLabels()
|
||||||
})
|
return { labelledby }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
await new Promise<void>(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
@@ -117,15 +105,9 @@ it('should be possible to use useLabels and multiple Label components, and have
|
|||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div aria-labelledby="headlessui-label-1 headlessui-label-2">
|
<div aria-labelledby="headlessui-label-1 headlessui-label-2">
|
||||||
<label id="headlessui-label-1">
|
<label id="headlessui-label-1">I am a label</label>
|
||||||
I am a label
|
<span>Contents</span>
|
||||||
</label>
|
<label id="headlessui-label-2">I am also a label</label>
|
||||||
<span>
|
|
||||||
Contents
|
|
||||||
</span>
|
|
||||||
<label id="headlessui-label-2">
|
|
||||||
I am also a label
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
@@ -133,21 +115,24 @@ it('should be possible to use useLabels and multiple Label components, and have
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to update a prop from the parent and it should reflect in the Label component', async () => {
|
it('should be possible to update a prop from the parent and it should reflect in the Label component', async () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = render(
|
||||||
render() {
|
defineComponent({
|
||||||
return h('div', [
|
components: { Label },
|
||||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
render() {
|
||||||
h(Label, () => 'I am a label'),
|
return h('div', [
|
||||||
h('button', { onClick: () => this.count++ }, '+1'),
|
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||||
]),
|
h(Label, () => 'I am a label'),
|
||||||
])
|
h('button', { onClick: () => this.count++ }, '+1'),
|
||||||
},
|
]),
|
||||||
setup() {
|
])
|
||||||
let count = ref(0)
|
},
|
||||||
let labelledby = useLabels({ props: { 'data-count': count } })
|
setup() {
|
||||||
return { count, labelledby }
|
let count = ref(0)
|
||||||
},
|
let labelledby = useLabels({ props: { 'data-count': count } })
|
||||||
})
|
return { count, labelledby }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
await new Promise<void>(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
@@ -155,9 +140,7 @@ it('should be possible to update a prop from the parent and it should reflect in
|
|||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div aria-labelledby="headlessui-label-1">
|
<div aria-labelledby="headlessui-label-1">
|
||||||
<label data-count="0" id="headlessui-label-1">
|
<label data-count="0" id="headlessui-label-1">I am a label</label>
|
||||||
I am a label
|
|
||||||
</label>
|
|
||||||
<button>+1</button>
|
<button>+1</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,9 +153,7 @@ it('should be possible to update a prop from the parent and it should reflect in
|
|||||||
format(html`
|
format(html`
|
||||||
<div>
|
<div>
|
||||||
<div aria-labelledby="headlessui-label-1">
|
<div aria-labelledby="headlessui-label-1">
|
||||||
<label data-count="1" id="headlessui-label-1">
|
<label data-count="1" id="headlessui-label-1">I am a label</label>
|
||||||
I am a label
|
|
||||||
</label>
|
|
||||||
<button>+1</button>
|
<button>+1</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { defineComponent, nextTick, ref, watch, h, reactive } from 'vue'
|
import {
|
||||||
|
defineComponent,
|
||||||
|
nextTick,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
h,
|
||||||
|
reactive,
|
||||||
|
ComponentOptionsWithoutProps,
|
||||||
|
} from 'vue'
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
import { Listbox, ListboxLabel, ListboxButton, ListboxOptions, ListboxOption } from './listbox'
|
import { Listbox, ListboxLabel, ListboxButton, ListboxOptions, ListboxOption } from './listbox'
|
||||||
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
||||||
@@ -48,7 +56,7 @@ beforeAll(() => {
|
|||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function nextFrame() {
|
function nextFrame() {
|
||||||
return new Promise(resolve => {
|
return new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
resolve()
|
resolve()
|
||||||
@@ -57,7 +65,7 @@ function nextFrame() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { Listbox, ListboxLabel, ListboxButton, ListboxOptions, ListboxOption }
|
let defaultComponents = { Listbox, ListboxLabel, ListboxButton, ListboxOptions, ListboxOption }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -388,9 +396,7 @@ describe('Rendering', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton type="submit">
|
<ListboxButton type="submit"> Trigger </ListboxButton>
|
||||||
Trigger
|
|
||||||
</ListboxButton>
|
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
setup: () => ({ value: ref(null) }),
|
setup: () => ({ value: ref(null) }),
|
||||||
@@ -405,15 +411,13 @@ describe('Rendering', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton :as="CustomButton">
|
<ListboxButton :as="CustomButton"> Trigger </ListboxButton>
|
||||||
Trigger
|
|
||||||
</ListboxButton>
|
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
value: ref(null),
|
value: ref(null),
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('button', { ...props }),
|
setup: (props) => () => h('button', { ...props }),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -428,9 +432,7 @@ describe('Rendering', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton as="div">
|
<ListboxButton as="div"> Trigger </ListboxButton>
|
||||||
Trigger
|
|
||||||
</ListboxButton>
|
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
setup: () => ({ value: ref(null) }),
|
setup: () => ({ value: ref(null) }),
|
||||||
@@ -445,15 +447,13 @@ describe('Rendering', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton :as="CustomButton">
|
<ListboxButton :as="CustomButton"> Trigger </ListboxButton>
|
||||||
Trigger
|
|
||||||
</ListboxButton>
|
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
value: ref(null),
|
value: ref(null),
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('div', props),
|
setup: (props) => () => h('div', props),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -647,15 +647,9 @@ describe('Rendering composition', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption as="button" value="a">
|
<ListboxOption as="button" value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption as="button" value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption as="button" value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption as="button" value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption as="button" value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -672,7 +666,7 @@ describe('Rendering composition', () => {
|
|||||||
await click(getListboxButton())
|
await click(getListboxButton())
|
||||||
|
|
||||||
// Verify options are buttons now
|
// Verify options are buttons now
|
||||||
getListboxOptions().forEach(option => assertListboxOption(option, { tag: 'button' }))
|
getListboxOptions().forEach((option) => assertListboxOption(option, { tag: 'button' }))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -704,9 +698,7 @@ describe('Composition', () => {
|
|||||||
<Listbox>
|
<Listbox>
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<OpenClosedWrite :open="true">
|
<OpenClosedWrite :open="true">
|
||||||
<ListboxOptions v-slot="data">
|
<ListboxOptions v-slot="data"> {{JSON.stringify(data)}} </ListboxOptions>
|
||||||
{{JSON.stringify(data)}}
|
|
||||||
</ListboxOptions>
|
|
||||||
</OpenClosedWrite>
|
</OpenClosedWrite>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -734,9 +726,7 @@ describe('Composition', () => {
|
|||||||
<Listbox>
|
<Listbox>
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<OpenClosedWrite :open="false">
|
<OpenClosedWrite :open="false">
|
||||||
<ListboxOptions v-slot="data">
|
<ListboxOptions v-slot="data"> {{JSON.stringify(data)}} </ListboxOptions>
|
||||||
{{JSON.stringify(data)}}
|
|
||||||
</ListboxOptions>
|
|
||||||
</OpenClosedWrite>
|
</OpenClosedWrite>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -840,7 +830,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option, { selected: false }))
|
options.forEach((option) => assertListboxOption(option, { selected: false }))
|
||||||
|
|
||||||
// Verify that the first listbox option is active
|
// Verify that the first listbox option is active
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
@@ -1092,9 +1082,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="b">Option B</ListboxOption>
|
<ListboxOption value="b">Option B</ListboxOption>
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
@@ -1130,12 +1118,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -1170,15 +1154,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -1345,7 +1323,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1471,9 +1449,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="b">Option B</ListboxOption>
|
<ListboxOption value="b">Option B</ListboxOption>
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
@@ -1509,12 +1485,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -1549,15 +1521,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -1729,7 +1695,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
|
|
||||||
// Try to tab
|
// Try to tab
|
||||||
@@ -1783,7 +1749,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
|
|
||||||
// Try to Shift+Tab
|
// Try to Shift+Tab
|
||||||
@@ -1839,7 +1805,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
|
|
||||||
// Verify that the first listbox option is active
|
// Verify that the first listbox option is active
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
@@ -1991,7 +1957,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2016,9 +1982,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="b">Option B</ListboxOption>
|
<ListboxOption value="b">Option B</ListboxOption>
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
@@ -2042,7 +2006,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[1])
|
assertActiveListboxOption(options[1])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2059,12 +2023,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -2087,7 +2047,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -2126,7 +2086,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
|
|
||||||
// We should be able to go right once
|
// We should be able to go right once
|
||||||
@@ -2186,7 +2146,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
|
|
||||||
// ! ALERT: The LAST option should now be active
|
// ! ALERT: The LAST option should now be active
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
@@ -2315,12 +2275,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="a">Option A</ListboxOption>
|
<ListboxOption value="a">Option A</ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
Option B
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -2342,7 +2298,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[0])
|
assertActiveListboxOption(options[0])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -2355,12 +2311,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -2383,7 +2335,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
|
|
||||||
// We should not be able to go up (because those are disabled)
|
// We should not be able to go up (because those are disabled)
|
||||||
@@ -2437,7 +2389,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2498,7 +2450,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
assertActiveListboxOption(options[2])
|
assertActiveListboxOption(options[2])
|
||||||
|
|
||||||
// We should be able to go left once
|
// We should be able to go left once
|
||||||
@@ -2561,12 +2513,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="a">Option A</ListboxOption>
|
<ListboxOption value="a">Option A</ListboxOption>
|
||||||
<ListboxOption value="b">Option B</ListboxOption>
|
<ListboxOption value="b">Option B</ListboxOption>
|
||||||
<ListboxOption disabled value="c">
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
Option C
|
<ListboxOption disabled value="d"> Option D </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -2599,15 +2547,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="a">Option A</ListboxOption>
|
<ListboxOption value="a">Option A</ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
Option B
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="d"> Option D </ListboxOption>
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -2636,18 +2578,10 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
<ListboxOption disabled value="d"> Option D </ListboxOption>
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -2713,12 +2647,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="a">Option A</ListboxOption>
|
<ListboxOption value="a">Option A</ListboxOption>
|
||||||
<ListboxOption value="b">Option B</ListboxOption>
|
<ListboxOption value="b">Option B</ListboxOption>
|
||||||
<ListboxOption disabled value="c">
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
Option C
|
<ListboxOption disabled value="d"> Option D </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -2751,15 +2681,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="a">Option A</ListboxOption>
|
<ListboxOption value="a">Option A</ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
Option B
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="d"> Option D </ListboxOption>
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -2788,18 +2712,10 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
<ListboxOption disabled value="d"> Option D </ListboxOption>
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -2863,12 +2779,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
<ListboxOption value="d">Option D</ListboxOption>
|
<ListboxOption value="d">Option D</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
@@ -2901,15 +2813,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="d">Option D</ListboxOption>
|
<ListboxOption value="d">Option D</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -2939,18 +2845,10 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
<ListboxOption disabled value="d"> Option D </ListboxOption>
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -3014,12 +2912,8 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="c">Option C</ListboxOption>
|
<ListboxOption value="c">Option C</ListboxOption>
|
||||||
<ListboxOption value="d">Option D</ListboxOption>
|
<ListboxOption value="d">Option D</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
@@ -3052,15 +2946,9 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="d">Option D</ListboxOption>
|
<ListboxOption value="d">Option D</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -3090,18 +2978,10 @@ describe('Keyboard interactions', () => {
|
|||||||
<Listbox v-model="value">
|
<Listbox v-model="value">
|
||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption disabled value="a">
|
<ListboxOption disabled value="a"> Option A </ListboxOption>
|
||||||
Option A
|
<ListboxOption disabled value="b"> Option B </ListboxOption>
|
||||||
</ListboxOption>
|
<ListboxOption disabled value="c"> Option C </ListboxOption>
|
||||||
<ListboxOption disabled value="b">
|
<ListboxOption disabled value="d"> Option D </ListboxOption>
|
||||||
Option B
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="c">
|
|
||||||
Option C
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption disabled value="d">
|
|
||||||
Option D
|
|
||||||
</ListboxOption>
|
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
`,
|
`,
|
||||||
@@ -3252,9 +3132,7 @@ describe('Keyboard interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="alice">alice</ListboxOption>
|
<ListboxOption value="alice">alice</ListboxOption>
|
||||||
<ListboxOption disabled value="bob">
|
<ListboxOption disabled value="bob"> bob </ListboxOption>
|
||||||
bob
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="charlie">charlie</ListboxOption>
|
<ListboxOption value="charlie">charlie</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -3453,7 +3331,7 @@ describe('Mouse interactions', () => {
|
|||||||
// Verify we have listbox options
|
// Verify we have listbox options
|
||||||
let options = getListboxOptions()
|
let options = getListboxOptions()
|
||||||
expect(options).toHaveLength(3)
|
expect(options).toHaveLength(3)
|
||||||
options.forEach(option => assertListboxOption(option))
|
options.forEach((option) => assertListboxOption(option))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -3845,9 +3723,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="alice">alice</ListboxOption>
|
<ListboxOption value="alice">alice</ListboxOption>
|
||||||
<ListboxOption disabled value="bob">
|
<ListboxOption disabled value="bob"> bob </ListboxOption>
|
||||||
bob
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="charlie">charlie</ListboxOption>
|
<ListboxOption value="charlie">charlie</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -3874,9 +3750,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="alice">alice</ListboxOption>
|
<ListboxOption value="alice">alice</ListboxOption>
|
||||||
<ListboxOption disabled value="bob">
|
<ListboxOption disabled value="bob"> bob </ListboxOption>
|
||||||
bob
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="charlie">charlie</ListboxOption>
|
<ListboxOption value="charlie">charlie</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -3951,9 +3825,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="alice">alice</ListboxOption>
|
<ListboxOption value="alice">alice</ListboxOption>
|
||||||
<ListboxOption disabled value="bob">
|
<ListboxOption disabled value="bob"> bob </ListboxOption>
|
||||||
bob
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="charlie">charlie</ListboxOption>
|
<ListboxOption value="charlie">charlie</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -4031,9 +3903,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="alice">alice</ListboxOption>
|
<ListboxOption value="alice">alice</ListboxOption>
|
||||||
<ListboxOption disabled value="bob">
|
<ListboxOption disabled value="bob"> bob </ListboxOption>
|
||||||
bob
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="charlie">charlie</ListboxOption>
|
<ListboxOption value="charlie">charlie</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
@@ -4111,9 +3981,7 @@ describe('Mouse interactions', () => {
|
|||||||
<ListboxButton>Trigger</ListboxButton>
|
<ListboxButton>Trigger</ListboxButton>
|
||||||
<ListboxOptions>
|
<ListboxOptions>
|
||||||
<ListboxOption value="alice">alice</ListboxOption>
|
<ListboxOption value="alice">alice</ListboxOption>
|
||||||
<ListboxOption disabled value="bob">
|
<ListboxOption disabled value="bob"> bob </ListboxOption>
|
||||||
bob
|
|
||||||
</ListboxOption>
|
|
||||||
<ListboxOption value="charlie">charlie</ListboxOption>
|
<ListboxOption value="charlie">charlie</ListboxOption>
|
||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ export let Listbox = defineComponent({
|
|||||||
{
|
{
|
||||||
resolveItems: () => options.value,
|
resolveItems: () => options.value,
|
||||||
resolveActiveIndex: () => activeOptionIndex.value,
|
resolveActiveIndex: () => activeOptionIndex.value,
|
||||||
resolveId: option => option.id,
|
resolveId: (option) => option.id,
|
||||||
resolveDisabled: option => option.dataRef.disabled,
|
resolveDisabled: (option) => option.dataRef.disabled,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ export let Listbox = defineComponent({
|
|||||||
: options.value
|
: options.value
|
||||||
|
|
||||||
let matchingOption = reOrderedOptions.find(
|
let matchingOption = reOrderedOptions.find(
|
||||||
option =>
|
(option) =>
|
||||||
option.dataRef.textValue.startsWith(searchQuery.value) && !option.dataRef.disabled
|
option.dataRef.textValue.startsWith(searchQuery.value) && !option.dataRef.disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ export let Listbox = defineComponent({
|
|||||||
let nextOptions = options.value.slice()
|
let nextOptions = options.value.slice()
|
||||||
let currentActiveOption =
|
let currentActiveOption =
|
||||||
activeOptionIndex.value !== null ? nextOptions[activeOptionIndex.value] : null
|
activeOptionIndex.value !== null ? nextOptions[activeOptionIndex.value] : null
|
||||||
let idx = nextOptions.findIndex(a => a.id === id)
|
let idx = nextOptions.findIndex((a) => a.id === id)
|
||||||
if (idx !== -1) nextOptions.splice(idx, 1)
|
if (idx !== -1) nextOptions.splice(idx, 1)
|
||||||
options.value = nextOptions
|
options.value = nextOptions
|
||||||
activeOptionIndex.value = (() => {
|
activeOptionIndex.value = (() => {
|
||||||
@@ -204,7 +204,7 @@ export let Listbox = defineComponent({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
let active = document.activeElement
|
let active = document.activeElement
|
||||||
|
|
||||||
@@ -529,10 +529,7 @@ export let ListboxOption = defineComponent({
|
|||||||
textValue: '',
|
textValue: '',
|
||||||
})
|
})
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let textValue = document
|
let textValue = document.getElementById(id)?.textContent?.toLowerCase().trim()
|
||||||
.getElementById(id)
|
|
||||||
?.textContent?.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
if (textValue !== undefined) dataRef.value.textValue = textValue
|
if (textValue !== undefined) dataRef.value.textValue = textValue
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { defineComponent, h, nextTick, reactive, ref, watch } from 'vue'
|
import {
|
||||||
|
ComponentOptionsWithoutProps,
|
||||||
|
defineComponent,
|
||||||
|
h,
|
||||||
|
nextTick,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
} from 'vue'
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
import { Menu, MenuButton, MenuItems, MenuItem } from './menu'
|
import { Menu, MenuButton, MenuItems, MenuItem } from './menu'
|
||||||
import { TransitionChild } from '../transitions/transition'
|
import { TransitionChild } from '../transitions/transition'
|
||||||
@@ -44,7 +52,7 @@ beforeAll(() => {
|
|||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function nextFrame() {
|
function nextFrame() {
|
||||||
return new Promise(resolve => {
|
return new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
resolve()
|
resolve()
|
||||||
@@ -53,7 +61,7 @@ function nextFrame() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { Menu, MenuButton, MenuItems, MenuItem }
|
let defaultComponents = { Menu, MenuButton, MenuItems, MenuItem }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -395,7 +403,7 @@ describe('Rendering', () => {
|
|||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('button', { ...props }),
|
setup: (props) => () => h('button', { ...props }),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -433,7 +441,7 @@ describe('Rendering', () => {
|
|||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('div', props),
|
setup: (props) => () => h('div', props),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -810,7 +818,7 @@ describe('Rendering composition', () => {
|
|||||||
|
|
||||||
// Verify items are buttons now
|
// Verify items are buttons now
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
items.forEach(item =>
|
items.forEach((item) =>
|
||||||
assertMenuItem(item, { tag: 'button', attributes: { 'data-my-custom-button': 'true' } })
|
assertMenuItem(item, { tag: 'button', attributes: { 'data-my-custom-button': 'true' } })
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -853,11 +861,11 @@ describe('Rendering composition', () => {
|
|||||||
|
|
||||||
expect.hasAssertions()
|
expect.hasAssertions()
|
||||||
|
|
||||||
document.querySelectorAll('.outer').forEach(element => {
|
document.querySelectorAll('.outer').forEach((element) => {
|
||||||
expect(element).not.toHaveAttribute('role', 'none')
|
expect(element).not.toHaveAttribute('role', 'none')
|
||||||
})
|
})
|
||||||
|
|
||||||
document.querySelectorAll('.inner').forEach(element => {
|
document.querySelectorAll('.inner').forEach((element) => {
|
||||||
expect(element).toHaveAttribute('role', 'none')
|
expect(element).toHaveAttribute('role', 'none')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1069,7 +1077,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
|
|
||||||
// Verify that the first menu item is active
|
// Verify that the first menu item is active
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
@@ -1398,7 +1406,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1704,7 +1712,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
|
|
||||||
// Try to tab
|
// Try to tab
|
||||||
@@ -1750,7 +1758,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
|
|
||||||
// Try to Shift+Tab
|
// Try to Shift+Tab
|
||||||
@@ -1798,7 +1806,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
|
|
||||||
// Verify that the first menu item is active
|
// Verify that the first menu item is active
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
@@ -1883,7 +1891,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -1926,7 +1934,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[1])
|
assertMenuLinkedWithMenuItem(items[1])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -1961,7 +1969,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[2])
|
assertMenuLinkedWithMenuItem(items[2])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -2002,7 +2010,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
|
|
||||||
// ! ALERT: The LAST item should now be active
|
// ! ALERT: The LAST item should now be active
|
||||||
assertMenuLinkedWithMenuItem(items[2])
|
assertMenuLinkedWithMenuItem(items[2])
|
||||||
@@ -2055,7 +2063,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[0])
|
assertMenuLinkedWithMenuItem(items[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2086,7 +2094,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[2])
|
assertMenuLinkedWithMenuItem(items[2])
|
||||||
|
|
||||||
// We should not be able to go up (because those are disabled)
|
// We should not be able to go up (because those are disabled)
|
||||||
@@ -2133,7 +2141,7 @@ describe('Keyboard interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
assertMenuLinkedWithMenuItem(items[2])
|
assertMenuLinkedWithMenuItem(items[2])
|
||||||
|
|
||||||
// We should be able to go down once
|
// We should be able to go down once
|
||||||
@@ -2820,7 +2828,7 @@ describe('Mouse interactions', () => {
|
|||||||
// Verify we have menu items
|
// Verify we have menu items
|
||||||
let items = getMenuItems()
|
let items = getMenuItems()
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
items.forEach(item => assertMenuItem(item))
|
items.forEach((item) => assertMenuItem(item))
|
||||||
})
|
})
|
||||||
|
|
||||||
it(
|
it(
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ export let Menu = defineComponent({
|
|||||||
{
|
{
|
||||||
resolveItems: () => items.value,
|
resolveItems: () => items.value,
|
||||||
resolveActiveIndex: () => activeItemIndex.value,
|
resolveActiveIndex: () => activeItemIndex.value,
|
||||||
resolveId: item => item.id,
|
resolveId: (item) => item.id,
|
||||||
resolveDisabled: item => item.dataRef.disabled,
|
resolveDisabled: (item) => item.dataRef.disabled,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ export let Menu = defineComponent({
|
|||||||
: items.value
|
: items.value
|
||||||
|
|
||||||
let matchingItem = reOrderedItems.find(
|
let matchingItem = reOrderedItems.find(
|
||||||
item => item.dataRef.textValue.startsWith(searchQuery.value) && !item.dataRef.disabled
|
(item) => item.dataRef.textValue.startsWith(searchQuery.value) && !item.dataRef.disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
let matchIdx = matchingItem ? items.value.indexOf(matchingItem) : -1
|
let matchIdx = matchingItem ? items.value.indexOf(matchingItem) : -1
|
||||||
@@ -144,7 +144,7 @@ export let Menu = defineComponent({
|
|||||||
let nextItems = items.value.slice()
|
let nextItems = items.value.slice()
|
||||||
let currentActiveItem =
|
let currentActiveItem =
|
||||||
activeItemIndex.value !== null ? nextItems[activeItemIndex.value] : null
|
activeItemIndex.value !== null ? nextItems[activeItemIndex.value] : null
|
||||||
let idx = nextItems.findIndex(a => a.id === id)
|
let idx = nextItems.findIndex((a) => a.id === id)
|
||||||
if (idx !== -1) nextItems.splice(idx, 1)
|
if (idx !== -1) nextItems.splice(idx, 1)
|
||||||
items.value = nextItems
|
items.value = nextItems
|
||||||
activeItemIndex.value = (() => {
|
activeItemIndex.value = (() => {
|
||||||
@@ -158,7 +158,7 @@ export let Menu = defineComponent({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
useWindowEvent('mousedown', event => {
|
useWindowEvent('mousedown', (event) => {
|
||||||
let target = event.target as HTMLElement
|
let target = event.target as HTMLElement
|
||||||
let active = document.activeElement
|
let active = document.activeElement
|
||||||
|
|
||||||
@@ -452,10 +452,7 @@ export let MenuItem = defineComponent({
|
|||||||
|
|
||||||
let dataRef = ref<MenuItemDataRef['value']>({ disabled: props.disabled, textValue: '' })
|
let dataRef = ref<MenuItemDataRef['value']>({ disabled: props.disabled, textValue: '' })
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let textValue = document
|
let textValue = document.getElementById(id)?.textContent?.toLowerCase().trim()
|
||||||
.getElementById(id)
|
|
||||||
?.textContent?.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
if (textValue !== undefined) dataRef.value.textValue = textValue
|
if (textValue !== undefined) dataRef.value.textValue = textValue
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, nextTick, ref, watch, h } from 'vue'
|
import { defineComponent, nextTick, ref, watch, h, ComponentOptionsWithoutProps } from 'vue'
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
|
|
||||||
import { Popover, PopoverGroup, PopoverButton, PopoverPanel, PopoverOverlay } from './popover'
|
import { Popover, PopoverGroup, PopoverButton, PopoverPanel, PopoverOverlay } from './popover'
|
||||||
@@ -28,7 +28,7 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = {
|
let defaultComponents = {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverGroup,
|
PopoverGroup,
|
||||||
@@ -345,9 +345,7 @@ describe('Rendering', () => {
|
|||||||
renderTemplate(
|
renderTemplate(
|
||||||
html`
|
html`
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverButton type="submit">
|
<PopoverButton type="submit"> Trigger </PopoverButton>
|
||||||
Trigger
|
|
||||||
</PopoverButton>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@@ -361,14 +359,12 @@ describe('Rendering', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverButton :as="CustomButton">
|
<PopoverButton :as="CustomButton"> Trigger </PopoverButton>
|
||||||
Trigger
|
|
||||||
</PopoverButton>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('button', { ...props }),
|
setup: (props) => () => h('button', { ...props }),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -383,9 +379,7 @@ describe('Rendering', () => {
|
|||||||
renderTemplate(
|
renderTemplate(
|
||||||
html`
|
html`
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverButton as="div">
|
<PopoverButton as="div"> Trigger </PopoverButton>
|
||||||
Trigger
|
|
||||||
</PopoverButton>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@@ -399,14 +393,12 @@ describe('Rendering', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverButton :as="CustomButton">
|
<PopoverButton :as="CustomButton"> Trigger </PopoverButton>
|
||||||
Trigger
|
|
||||||
</PopoverButton>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
`,
|
`,
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('div', props),
|
setup: (props) => () => h('div', props),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -569,9 +561,7 @@ describe('Rendering', () => {
|
|||||||
<Popover>
|
<Popover>
|
||||||
<PopoverButton>Trigger</PopoverButton>
|
<PopoverButton>Trigger</PopoverButton>
|
||||||
<PopoverPanel focus>
|
<PopoverPanel focus>
|
||||||
<a href="/" style="display:none">
|
<a href="/" style="display:none"> Link 1 </a>
|
||||||
Link 1
|
|
||||||
</a>
|
|
||||||
<a href="/">Link 2</a>
|
<a href="/">Link 2</a>
|
||||||
</PopoverPanel>
|
</PopoverPanel>
|
||||||
</Popover>
|
</Popover>
|
||||||
@@ -757,9 +747,7 @@ describe('Composition', () => {
|
|||||||
<Popover>
|
<Popover>
|
||||||
<PopoverButton>Trigger</PopoverButton>
|
<PopoverButton>Trigger</PopoverButton>
|
||||||
<OpenClosedWrite :open="true">
|
<OpenClosedWrite :open="true">
|
||||||
<PopoverPanel v-slot="data">
|
<PopoverPanel v-slot="data"> {{JSON.stringify(data)}} </PopoverPanel>
|
||||||
{{JSON.stringify(data)}}
|
|
||||||
</PopoverPanel>
|
|
||||||
</OpenClosedWrite>
|
</OpenClosedWrite>
|
||||||
</Popover>
|
</Popover>
|
||||||
`,
|
`,
|
||||||
@@ -787,9 +775,7 @@ describe('Composition', () => {
|
|||||||
<Popover>
|
<Popover>
|
||||||
<PopoverButton>Trigger</PopoverButton>
|
<PopoverButton>Trigger</PopoverButton>
|
||||||
<OpenClosedWrite :open="false">
|
<OpenClosedWrite :open="false">
|
||||||
<PopoverPanel v-slot="data">
|
<PopoverPanel v-slot="data"> {{JSON.stringify(data)}} </PopoverPanel>
|
||||||
{{JSON.stringify(data)}}
|
|
||||||
</PopoverPanel>
|
|
||||||
</OpenClosedWrite>
|
</OpenClosedWrite>
|
||||||
</Popover>
|
</Popover>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ export let PopoverPanel = defineComponent({
|
|||||||
|
|
||||||
let nextElements = elements
|
let nextElements = elements
|
||||||
.splice(buttonIdx + 1) // Elements after button
|
.splice(buttonIdx + 1) // Elements after button
|
||||||
.filter(element => !dom(api.panel)?.contains(element)) // Ignore items in panel
|
.filter((element) => !dom(api.panel)?.contains(element)) // Ignore items in panel
|
||||||
|
|
||||||
// Try to focus the next element, however it could fail if we are in a
|
// Try to focus the next element, however it could fail if we are in a
|
||||||
// Portal that happens to be the very last one in the DOM. In that
|
// Portal that happens to be the very last one in the DOM. In that
|
||||||
@@ -624,7 +624,7 @@ export let PopoverGroup = defineComponent({
|
|||||||
if (dom(groupRef)?.contains(element)) return true
|
if (dom(groupRef)?.contains(element)) return true
|
||||||
|
|
||||||
// Check if the focus is in one of the button or panel elements. This is important in case you are rendering inside a Portal.
|
// Check if the focus is in one of the button or panel elements. This is important in case you are rendering inside a Portal.
|
||||||
return popovers.value.some(bag => {
|
return popovers.value.some((bag) => {
|
||||||
return (
|
return (
|
||||||
document.getElementById(bag.buttonId)?.contains(element) ||
|
document.getElementById(bag.buttonId)?.contains(element) ||
|
||||||
document.getElementById(bag.panelId)?.contains(element)
|
document.getElementById(bag.panelId)?.contains(element)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, ref, nextTick } from 'vue'
|
import { defineComponent, ref, nextTick, ComponentOptionsWithoutProps } from 'vue'
|
||||||
|
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
import { Portal, PortalGroup } from './portal'
|
import { Portal, PortalGroup } from './portal'
|
||||||
@@ -22,7 +22,7 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { Portal, PortalGroup }
|
let defaultComponents = { Portal, PortalGroup }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -108,12 +108,8 @@ it('should cleanup the Portal root when the last Portal is unmounted', async ()
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<main id="parent">
|
<main id="parent">
|
||||||
<button id="a" @click="toggleA">
|
<button id="a" @click="toggleA">Toggle A</button>
|
||||||
Toggle A
|
<button id="b" @click="toggleB">Toggle B</button>
|
||||||
</button>
|
|
||||||
<button id="b" @click="toggleB">
|
|
||||||
Toggle B
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Portal v-if="renderA">
|
<Portal v-if="renderA">
|
||||||
<p id="content1">Contents 1 ...</p>
|
<p id="content1">Contents 1 ...</p>
|
||||||
@@ -182,19 +178,11 @@ it('should be possible to render multiple portals at the same time', async () =>
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<main id="parent">
|
<main id="parent">
|
||||||
<button id="a" @click="toggleA">
|
<button id="a" @click="toggleA">Toggle A</button>
|
||||||
Toggle A
|
<button id="b" @click="toggleB">Toggle B</button>
|
||||||
</button>
|
<button id="c" @click="toggleC">Toggle C</button>
|
||||||
<button id="b" @click="toggleB">
|
|
||||||
Toggle B
|
|
||||||
</button>
|
|
||||||
<button id="c" @click="toggleC">
|
|
||||||
Toggle C
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button id="double" @click="toggleAB">
|
<button id="double" @click="toggleAB">Toggle A & B</button>
|
||||||
Toggle A & B
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Portal v-if="renderA">
|
<Portal v-if="renderA">
|
||||||
<p id="content1">Contents 1 ...</p>
|
<p id="content1">Contents 1 ...</p>
|
||||||
@@ -269,12 +257,8 @@ it('should be possible to tamper with the modal root and restore correctly', asy
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<main id="parent">
|
<main id="parent">
|
||||||
<button id="a" @click="toggleA">
|
<button id="a" @click="toggleA">Toggle A</button>
|
||||||
Toggle A
|
<button id="b" @click="toggleB">Toggle B</button>
|
||||||
</button>
|
|
||||||
<button id="b" @click="toggleB">
|
|
||||||
Toggle B
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Portal v-if="renderA">
|
<Portal v-if="renderA">
|
||||||
<p id="content1">Contents 1 ...</p>
|
<p id="content1">Contents 1 ...</p>
|
||||||
@@ -325,9 +309,7 @@ it('should be possible to force the Portal into a specific element using PortalG
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<main>
|
<main>
|
||||||
<aside ref="container" id="group-1">
|
<aside ref="container" id="group-1">A</aside>
|
||||||
A
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<PortalGroup :target="container">
|
<PortalGroup :target="container">
|
||||||
<section id="group-2">
|
<section id="group-2">
|
||||||
@@ -346,6 +328,6 @@ it('should be possible to force the Portal into a specific element using PortalG
|
|||||||
await new Promise<void>(nextTick)
|
await new Promise<void>(nextTick)
|
||||||
|
|
||||||
expect(document.body.innerHTML).toMatchInlineSnapshot(
|
expect(document.body.innerHTML).toMatchInlineSnapshot(
|
||||||
`"<div><div><div data-v-app=\\"\\"><main><aside id=\\"group-1\\"> A <div>Next to A</div></aside><section id=\\"group-2\\"><span>B</span></section><!--teleport start--><!--teleport end--></main></div></div></div>"`
|
`"<div><div><div data-v-app=\\"\\"><main><aside id=\\"group-1\\">A<div>Next to A</div></aside><section id=\\"group-2\\"><span>B</span></section><!--teleport start--><!--teleport end--></main></div></div></div>"`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, nextTick, ref, watch, reactive } from 'vue'
|
import { defineComponent, nextTick, ref, watch, reactive, ComponentOptionsWithoutProps } from 'vue'
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
|
|
||||||
import { RadioGroup, RadioGroupOption, RadioGroupLabel, RadioGroupDescription } from './radio-group'
|
import { RadioGroup, RadioGroupOption, RadioGroupLabel, RadioGroupDescription } from './radio-group'
|
||||||
@@ -25,7 +25,7 @@ beforeAll(() => {
|
|||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function nextFrame() {
|
function nextFrame() {
|
||||||
return new Promise(resolve => {
|
return new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
resolve()
|
resolve()
|
||||||
@@ -34,7 +34,7 @@ function nextFrame() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { RadioGroup, RadioGroupOption, RadioGroupLabel, RadioGroupDescription }
|
let defaultComponents = { RadioGroup, RadioGroupOption, RadioGroupLabel, RadioGroupDescription }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -86,9 +86,7 @@ describe('Safe guards', () => {
|
|||||||
|
|
||||||
it('should be possible to render a RadioGroup without options and without crashing', () => {
|
it('should be possible to render a RadioGroup without options and without crashing', () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <RadioGroup v-model="deliveryMethod" /> `,
|
||||||
<RadioGroup v-model="deliveryMethod" />
|
|
||||||
`,
|
|
||||||
setup() {
|
setup() {
|
||||||
let deliveryMethod = ref(undefined)
|
let deliveryMethod = ref(undefined)
|
||||||
return { deliveryMethod }
|
return { deliveryMethod }
|
||||||
|
|||||||
@@ -99,19 +99,19 @@ export let RadioGroup = defineComponent({
|
|||||||
value,
|
value,
|
||||||
disabled: computed(() => props.disabled),
|
disabled: computed(() => props.disabled),
|
||||||
firstOption: computed(() =>
|
firstOption: computed(() =>
|
||||||
options.value.find(option => {
|
options.value.find((option) => {
|
||||||
if (option.propsRef.disabled) return false
|
if (option.propsRef.disabled) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
containsCheckedOption: computed(() =>
|
containsCheckedOption: computed(() =>
|
||||||
options.value.some(option => toRaw(option.propsRef.value) === toRaw(props.modelValue))
|
options.value.some((option) => toRaw(option.propsRef.value) === toRaw(props.modelValue))
|
||||||
),
|
),
|
||||||
change(nextValue: unknown) {
|
change(nextValue: unknown) {
|
||||||
if (props.disabled) return false
|
if (props.disabled) return false
|
||||||
if (value.value === nextValue) return false
|
if (value.value === nextValue) return false
|
||||||
let nextOption = options.value.find(
|
let nextOption = options.value.find(
|
||||||
option => toRaw(option.propsRef.value) === toRaw(nextValue)
|
(option) => toRaw(option.propsRef.value) === toRaw(nextValue)
|
||||||
)?.propsRef
|
)?.propsRef
|
||||||
if (nextOption?.disabled) return false
|
if (nextOption?.disabled) return false
|
||||||
emit('update:modelValue', nextValue)
|
emit('update:modelValue', nextValue)
|
||||||
@@ -129,7 +129,7 @@ export let RadioGroup = defineComponent({
|
|||||||
options.value.sort((a, z) => orderMap[a.id] - orderMap[z.id])
|
options.value.sort((a, z) => orderMap[a.id] - orderMap[z.id])
|
||||||
},
|
},
|
||||||
unregisterOption(id: Option['id']) {
|
unregisterOption(id: Option['id']) {
|
||||||
let idx = options.value.findIndex(radio => radio.id === id)
|
let idx = options.value.findIndex((radio) => radio.id === id)
|
||||||
if (idx === -1) return
|
if (idx === -1) return
|
||||||
options.value.splice(idx, 1)
|
options.value.splice(idx, 1)
|
||||||
},
|
},
|
||||||
@@ -155,8 +155,8 @@ export let RadioGroup = defineComponent({
|
|||||||
if (!radioGroupRef.value.contains(event.target as HTMLElement)) return
|
if (!radioGroupRef.value.contains(event.target as HTMLElement)) return
|
||||||
|
|
||||||
let all = options.value
|
let all = options.value
|
||||||
.filter(option => option.propsRef.disabled === false)
|
.filter((option) => option.propsRef.disabled === false)
|
||||||
.map(radio => radio.element) as HTMLElement[]
|
.map((radio) => radio.element) as HTMLElement[]
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Keys.ArrowLeft:
|
case Keys.ArrowLeft:
|
||||||
@@ -169,7 +169,7 @@ export let RadioGroup = defineComponent({
|
|||||||
|
|
||||||
if (result === FocusResult.Success) {
|
if (result === FocusResult.Success) {
|
||||||
let activeOption = options.value.find(
|
let activeOption = options.value.find(
|
||||||
option => option.element === document.activeElement
|
(option) => option.element === document.activeElement
|
||||||
)
|
)
|
||||||
if (activeOption) api.change(activeOption.propsRef.value)
|
if (activeOption) api.change(activeOption.propsRef.value)
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ export let RadioGroup = defineComponent({
|
|||||||
|
|
||||||
if (result === FocusResult.Success) {
|
if (result === FocusResult.Success) {
|
||||||
let activeOption = options.value.find(
|
let activeOption = options.value.find(
|
||||||
option => option.element === document.activeElement
|
(option) => option.element === document.activeElement
|
||||||
)
|
)
|
||||||
if (activeOption) api.change(activeOption.propsRef.value)
|
if (activeOption) api.change(activeOption.propsRef.value)
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ export let RadioGroup = defineComponent({
|
|||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
let activeOption = options.value.find(
|
let activeOption = options.value.find(
|
||||||
option => option.element === document.activeElement
|
(option) => option.element === document.activeElement
|
||||||
)
|
)
|
||||||
if (activeOption) api.change(activeOption.propsRef.value)
|
if (activeOption) api.change(activeOption.propsRef.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, ref, watch, h } from 'vue'
|
import { defineComponent, ref, watch, h, ComponentOptionsWithoutProps } from 'vue'
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
|
|
||||||
import { Switch, SwitchLabel, SwitchDescription, SwitchGroup } from './switch'
|
import { Switch, SwitchLabel, SwitchDescription, SwitchGroup } from './switch'
|
||||||
@@ -16,7 +16,7 @@ import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
|||||||
|
|
||||||
jest.mock('../../hooks/use-id')
|
jest.mock('../../hooks/use-id')
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { Switch, SwitchLabel, SwitchDescription, SwitchGroup }
|
let defaultComponents = { Switch, SwitchLabel, SwitchDescription, SwitchGroup }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -35,9 +35,7 @@ function renderTemplate(input: string | Partial<Parameters<typeof defineComponen
|
|||||||
describe('Safe guards', () => {
|
describe('Safe guards', () => {
|
||||||
it('should be possible to render a Switch without crashing', () => {
|
it('should be possible to render a Switch without crashing', () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked" /> `,
|
||||||
<Switch v-model="checked" />
|
|
||||||
`,
|
|
||||||
setup: () => ({ checked: ref(false) }),
|
setup: () => ({ checked: ref(false) }),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -72,9 +70,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
it('should be possible to render an (on) Switch using an `as` prop', () => {
|
it('should be possible to render an (on) Switch using an `as` prop', () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch as="span" v-model="checked" /> `,
|
||||||
<Switch as="span" v-model="checked" />
|
|
||||||
`,
|
|
||||||
setup: () => ({ checked: ref(true) }),
|
setup: () => ({ checked: ref(true) }),
|
||||||
})
|
})
|
||||||
assertSwitch({ state: SwitchState.On, tag: 'span' })
|
assertSwitch({ state: SwitchState.On, tag: 'span' })
|
||||||
@@ -82,9 +78,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
it('should be possible to render an (off) Switch using an `as` prop', () => {
|
it('should be possible to render an (off) Switch using an `as` prop', () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch as="span" v-model="checked" /> `,
|
||||||
<Switch as="span" v-model="checked" />
|
|
||||||
`,
|
|
||||||
setup: () => ({ checked: ref(false) }),
|
setup: () => ({ checked: ref(false) }),
|
||||||
})
|
})
|
||||||
assertSwitch({ state: SwitchState.Off, tag: 'span' })
|
assertSwitch({ state: SwitchState.Off, tag: 'span' })
|
||||||
@@ -106,11 +100,7 @@ describe('Rendering', () => {
|
|||||||
describe('`type` attribute', () => {
|
describe('`type` attribute', () => {
|
||||||
it('should set the `type` to "button" by default', async () => {
|
it('should set the `type` to "button" by default', async () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked"> Trigger </Switch> `,
|
||||||
<Switch v-model="checked">
|
|
||||||
Trigger
|
|
||||||
</Switch>
|
|
||||||
`,
|
|
||||||
setup: () => ({ checked: ref(false) }),
|
setup: () => ({ checked: ref(false) }),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -119,11 +109,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
it('should not set the `type` to "button" if it already contains a `type`', async () => {
|
it('should not set the `type` to "button" if it already contains a `type`', async () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked" type="submit"> Trigger </Switch> `,
|
||||||
<Switch v-model="checked" type="submit">
|
|
||||||
Trigger
|
|
||||||
</Switch>
|
|
||||||
`,
|
|
||||||
setup: () => ({ checked: ref(false) }),
|
setup: () => ({ checked: ref(false) }),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -134,15 +120,11 @@ describe('Rendering', () => {
|
|||||||
'should set the `type` to "button" when using the `as` prop which resolves to a "button"',
|
'should set the `type` to "button" when using the `as` prop which resolves to a "button"',
|
||||||
suppressConsoleLogs(async () => {
|
suppressConsoleLogs(async () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked" :as="CustomButton"> Trigger </Switch> `,
|
||||||
<Switch v-model="checked" :as="CustomButton">
|
|
||||||
Trigger
|
|
||||||
</Switch>
|
|
||||||
`,
|
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
checked: ref(false),
|
checked: ref(false),
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('button', { ...props }),
|
setup: (props) => () => h('button', { ...props }),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -155,11 +137,7 @@ describe('Rendering', () => {
|
|||||||
|
|
||||||
it('should not set the type if the "as" prop is not a "button"', async () => {
|
it('should not set the type if the "as" prop is not a "button"', async () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked" as="div"> Trigger </Switch> `,
|
||||||
<Switch v-model="checked" as="div">
|
|
||||||
Trigger
|
|
||||||
</Switch>
|
|
||||||
`,
|
|
||||||
setup: () => ({ checked: ref(false) }),
|
setup: () => ({ checked: ref(false) }),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -170,15 +148,11 @@ describe('Rendering', () => {
|
|||||||
'should not set the `type` to "button" when using the `as` prop which resolves to a "div"',
|
'should not set the `type` to "button" when using the `as` prop which resolves to a "div"',
|
||||||
suppressConsoleLogs(async () => {
|
suppressConsoleLogs(async () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked" :as="CustomButton"> Trigger </Switch> `,
|
||||||
<Switch v-model="checked" :as="CustomButton">
|
|
||||||
Trigger
|
|
||||||
</Switch>
|
|
||||||
`,
|
|
||||||
setup: () => ({
|
setup: () => ({
|
||||||
checked: ref(false),
|
checked: ref(false),
|
||||||
CustomButton: defineComponent({
|
CustomButton: defineComponent({
|
||||||
setup: props => () => h('div', props),
|
setup: (props) => () => h('div', props),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -213,9 +187,7 @@ describe('Render composition', () => {
|
|||||||
template: html`
|
template: html`
|
||||||
<SwitchGroup>
|
<SwitchGroup>
|
||||||
<SwitchLabel>Label B</SwitchLabel>
|
<SwitchLabel>Label B</SwitchLabel>
|
||||||
<Switch v-model="checked">
|
<Switch v-model="checked"> Label A </Switch>
|
||||||
Label A
|
|
||||||
</Switch>
|
|
||||||
</SwitchGroup>
|
</SwitchGroup>
|
||||||
`,
|
`,
|
||||||
setup: () => ({ checked: ref(false) }),
|
setup: () => ({ checked: ref(false) }),
|
||||||
@@ -234,9 +206,7 @@ describe('Render composition', () => {
|
|||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<SwitchGroup>
|
<SwitchGroup>
|
||||||
<Switch v-model="checked">
|
<Switch v-model="checked"> Label A </Switch>
|
||||||
Label A
|
|
||||||
</Switch>
|
|
||||||
<SwitchLabel>Label B</SwitchLabel>
|
<SwitchLabel>Label B</SwitchLabel>
|
||||||
</SwitchGroup>
|
</SwitchGroup>
|
||||||
`,
|
`,
|
||||||
@@ -370,9 +340,7 @@ describe('Keyboard interactions', () => {
|
|||||||
it('should be possible to toggle the Switch with Space', async () => {
|
it('should be possible to toggle the Switch with Space', async () => {
|
||||||
let handleChange = jest.fn()
|
let handleChange = jest.fn()
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked" /> `,
|
||||||
<Switch v-model="checked" />
|
|
||||||
`,
|
|
||||||
setup() {
|
setup() {
|
||||||
let checked = ref(false)
|
let checked = ref(false)
|
||||||
watch([checked], () => handleChange(checked.value))
|
watch([checked], () => handleChange(checked.value))
|
||||||
@@ -404,9 +372,7 @@ describe('Keyboard interactions', () => {
|
|||||||
it('should not be possible to use Enter to toggle the Switch', async () => {
|
it('should not be possible to use Enter to toggle the Switch', async () => {
|
||||||
let handleChange = jest.fn()
|
let handleChange = jest.fn()
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked" /> `,
|
||||||
<Switch v-model="checked" />
|
|
||||||
`,
|
|
||||||
setup() {
|
setup() {
|
||||||
let checked = ref(false)
|
let checked = ref(false)
|
||||||
watch([checked], () => handleChange(checked.value))
|
watch([checked], () => handleChange(checked.value))
|
||||||
@@ -461,9 +427,7 @@ describe('Mouse interactions', () => {
|
|||||||
it('should be possible to toggle the Switch with a click', async () => {
|
it('should be possible to toggle the Switch with a click', async () => {
|
||||||
let handleChange = jest.fn()
|
let handleChange = jest.fn()
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Switch v-model="checked" /> `,
|
||||||
<Switch v-model="checked" />
|
|
||||||
`,
|
|
||||||
setup() {
|
setup() {
|
||||||
let checked = ref(false)
|
let checked = ref(false)
|
||||||
watch([checked], () => handleChange(checked.value))
|
watch([checked], () => handleChange(checked.value))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, nextTick, ref } from 'vue'
|
import { ComponentOptionsWithoutProps, defineComponent, nextTick, ref } from 'vue'
|
||||||
import { render } from '../../test-utils/vue-testing-library'
|
import { render } from '../../test-utils/vue-testing-library'
|
||||||
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from './tabs'
|
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from './tabs'
|
||||||
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
||||||
@@ -20,7 +20,7 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { TabGroup, TabList, Tab, TabPanels, TabPanel }
|
let defaultComponents = { TabGroup, TabList, Tab, TabPanels, TabPanel }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ export let TabGroup = defineComponent({
|
|||||||
if (api.tabs.value.length <= 0) return
|
if (api.tabs.value.length <= 0) return
|
||||||
if (props.selectedIndex === null && selectedIndex.value !== null) return
|
if (props.selectedIndex === null && selectedIndex.value !== null) return
|
||||||
|
|
||||||
let tabs = api.tabs.value.map(tab => dom(tab)).filter(Boolean) as HTMLElement[]
|
let tabs = api.tabs.value.map((tab) => dom(tab)).filter(Boolean) as HTMLElement[]
|
||||||
let focusableTabs = tabs.filter(tab => !tab.hasAttribute('disabled'))
|
let focusableTabs = tabs.filter((tab) => !tab.hasAttribute('disabled'))
|
||||||
|
|
||||||
let indexToSet = props.selectedIndex ?? props.defaultIndex
|
let indexToSet = props.selectedIndex ?? props.defaultIndex
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ export let TabGroup = defineComponent({
|
|||||||
let before = tabs.slice(0, indexToSet)
|
let before = tabs.slice(0, indexToSet)
|
||||||
let after = tabs.slice(indexToSet)
|
let after = tabs.slice(indexToSet)
|
||||||
|
|
||||||
let next = [...after, ...before].find(tab => focusableTabs.includes(tab))
|
let next = [...after, ...before].find((tab) => focusableTabs.includes(tab))
|
||||||
if (!next) return
|
if (!next) return
|
||||||
|
|
||||||
selectedIndex.value = tabs.indexOf(next)
|
selectedIndex.value = tabs.indexOf(next)
|
||||||
@@ -220,7 +220,7 @@ export let Tab = defineComponent({
|
|||||||
let selected = computed(() => myIndex.value === api.selectedIndex.value)
|
let selected = computed(() => myIndex.value === api.selectedIndex.value)
|
||||||
|
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
let list = api.tabs.value.map(tab => dom(tab)).filter(Boolean) as HTMLElement[]
|
let list = api.tabs.value.map((tab) => dom(tab)).filter(Boolean) as HTMLElement[]
|
||||||
|
|
||||||
if (event.key === Keys.Space || event.key === Keys.Enter) {
|
if (event.key === Keys.Space || event.key === Keys.Enter) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, ref, onMounted } from 'vue'
|
import { defineComponent, ref, onMounted, ComponentOptionsWithoutProps } from 'vue'
|
||||||
import { render, fireEvent } from '../../test-utils/vue-testing-library'
|
import { render, fireEvent } from '../../test-utils/vue-testing-library'
|
||||||
|
|
||||||
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
||||||
@@ -11,7 +11,7 @@ jest.mock('../../hooks/use-id')
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { TransitionRoot, TransitionChild }
|
let defaultComponents = { TransitionRoot, TransitionChild }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -58,9 +58,7 @@ it('should render without crashing', () => {
|
|||||||
|
|
||||||
it('should be possible to render a Transition without children', () => {
|
it('should be possible to render a Transition without children', () => {
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <TransitionRoot :show="true" class="transition" /> `,
|
||||||
<TransitionRoot :show="true" class="transition" />
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
expect(document.getElementsByClassName('transition')).not.toBeNull()
|
expect(document.getElementsByClassName('transition')).not.toBeNull()
|
||||||
})
|
})
|
||||||
@@ -91,12 +89,10 @@ describe('Setup API', () => {
|
|||||||
describe('shallow', () => {
|
describe('shallow', () => {
|
||||||
it('should render a div and its children by default', () => {
|
it('should render a div and its children by default', () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = renderTemplate({
|
||||||
template: html`
|
template: html`<TransitionRoot :show="true">Children</TransitionRoot>`,
|
||||||
<TransitionRoot :show="true">Children</TransitionRoot>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(container.firstChild).toMatchInlineSnapshot(html`
|
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||||
<div>
|
<div>
|
||||||
Children
|
Children
|
||||||
</div>
|
</div>
|
||||||
@@ -106,9 +102,7 @@ describe('Setup API', () => {
|
|||||||
it('should passthrough all the props (that we do not use internally)', () => {
|
it('should passthrough all the props (that we do not use internally)', () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = renderTemplate({
|
||||||
template: html`
|
template: html`
|
||||||
<TransitionRoot :show="true" id="root" class="text-blue-400">
|
<TransitionRoot :show="true" id="root" class="text-blue-400"> Children </TransitionRoot>
|
||||||
Children
|
|
||||||
</TransitionRoot>
|
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -124,11 +118,7 @@ describe('Setup API', () => {
|
|||||||
|
|
||||||
it('should render another component if the `as` prop is used and its children by default', () => {
|
it('should render another component if the `as` prop is used and its children by default', () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = renderTemplate({
|
||||||
template: html`
|
template: html` <TransitionRoot :show="true" as="a"> Children </TransitionRoot> `,
|
||||||
<TransitionRoot :show="true" as="a">
|
|
||||||
Children
|
|
||||||
</TransitionRoot>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||||
@@ -159,9 +149,7 @@ describe('Setup API', () => {
|
|||||||
|
|
||||||
it('should render nothing when the show prop is false', () => {
|
it('should render nothing when the show prop is false', () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = renderTemplate({
|
||||||
template: html`
|
template: html` <TransitionRoot :show="false">Children</TransitionRoot> `,
|
||||||
<TransitionRoot :show="false">Children</TransitionRoot>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(container.firstChild).toMatchInlineSnapshot(`<!---->`)
|
expect(container.firstChild).toMatchInlineSnapshot(`<!---->`)
|
||||||
@@ -169,11 +157,7 @@ describe('Setup API', () => {
|
|||||||
|
|
||||||
it('should be possible to change the underlying DOM tag', () => {
|
it('should be possible to change the underlying DOM tag', () => {
|
||||||
let { container } = renderTemplate({
|
let { container } = renderTemplate({
|
||||||
template: html`
|
template: html` <TransitionRoot :show="true" as="a"> Children </TransitionRoot> `,
|
||||||
<TransitionRoot :show="true" as="a">
|
|
||||||
Children
|
|
||||||
</TransitionRoot>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||||
@@ -470,9 +454,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(false)
|
let show = ref(false)
|
||||||
@@ -525,9 +507,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(false)
|
let show = ref(false)
|
||||||
@@ -580,9 +560,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(false)
|
let show = ref(false)
|
||||||
@@ -630,9 +608,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(false)
|
let show = ref(false)
|
||||||
@@ -687,9 +663,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(true)
|
let show = ref(true)
|
||||||
@@ -753,9 +727,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(true)
|
let show = ref(true)
|
||||||
@@ -822,9 +794,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(false)
|
let show = ref(false)
|
||||||
@@ -918,9 +888,7 @@ describe('Transitions', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(false)
|
let show = ref(false)
|
||||||
@@ -1021,9 +989,7 @@ describe('Transitions', () => {
|
|||||||
</TransitionChild>
|
</TransitionChild>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(true)
|
let show = ref(true)
|
||||||
@@ -1113,9 +1079,7 @@ describe('Transitions', () => {
|
|||||||
</TransitionChild>
|
</TransitionChild>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(true)
|
let show = ref(true)
|
||||||
@@ -1227,9 +1191,7 @@ describe('Events', () => {
|
|||||||
<span>Hello!</span>
|
<span>Hello!</span>
|
||||||
</TransitionRoot>
|
</TransitionRoot>
|
||||||
|
|
||||||
<button data-testid="toggle" @click="show = !show">
|
<button data-testid="toggle" @click="show = !show">Toggle</button>
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
setup() {
|
setup() {
|
||||||
let show = ref(false)
|
let show = ref(false)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
type ID = ReturnType<typeof useId>
|
type ID = ReturnType<typeof useId>
|
||||||
|
|
||||||
function splitClasses(classes: string = '') {
|
function splitClasses(classes: string = '') {
|
||||||
return classes.split(' ').filter(className => className.trim().length > 1)
|
return classes.split(' ').filter((className) => className.trim().length > 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TransitionContextValues {
|
interface TransitionContextValues {
|
||||||
@@ -292,7 +292,7 @@ export let TransitionChild = defineComponent({
|
|||||||
enterFromClasses,
|
enterFromClasses,
|
||||||
enterToClasses,
|
enterToClasses,
|
||||||
enteredClasses,
|
enteredClasses,
|
||||||
reason => {
|
(reason) => {
|
||||||
isTransitioning.value = false
|
isTransitioning.value = false
|
||||||
if (reason === Reason.Finished) emit('afterEnter')
|
if (reason === Reason.Finished) emit('afterEnter')
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ export let TransitionChild = defineComponent({
|
|||||||
leaveFromClasses,
|
leaveFromClasses,
|
||||||
leaveToClasses,
|
leaveToClasses,
|
||||||
enteredClasses,
|
enteredClasses,
|
||||||
reason => {
|
(reason) => {
|
||||||
isTransitioning.value = false
|
isTransitioning.value = false
|
||||||
|
|
||||||
if (reason !== Reason.Finished) return
|
if (reason !== Reason.Finished) return
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ it('should be possible to transition', async () => {
|
|||||||
d.add(
|
d.add(
|
||||||
reportChanges(
|
reportChanges(
|
||||||
() => document.body.innerHTML,
|
() => document.body.innerHTML,
|
||||||
content => {
|
(content) => {
|
||||||
snapshots.push({
|
snapshots.push({
|
||||||
content,
|
content,
|
||||||
recordedAt: process.hrtime.bigint(),
|
recordedAt: process.hrtime.bigint(),
|
||||||
@@ -26,11 +26,11 @@ it('should be possible to transition', async () => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
await new Promise(resolve => {
|
await new Promise((resolve) => {
|
||||||
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
|
|
||||||
// Initial render:
|
// Initial render:
|
||||||
expect(snapshots[0].content).toEqual('<div></div>')
|
expect(snapshots[0].content).toEqual('<div></div>')
|
||||||
@@ -61,7 +61,7 @@ it('should wait the correct amount of time to finish a transition', async () =>
|
|||||||
d.add(
|
d.add(
|
||||||
reportChanges(
|
reportChanges(
|
||||||
() => document.body.innerHTML,
|
() => document.body.innerHTML,
|
||||||
content => {
|
(content) => {
|
||||||
snapshots.push({
|
snapshots.push({
|
||||||
content,
|
content,
|
||||||
recordedAt: process.hrtime.bigint(),
|
recordedAt: process.hrtime.bigint(),
|
||||||
@@ -70,11 +70,11 @@ it('should wait the correct amount of time to finish a transition', async () =>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let reason = await new Promise(resolve => {
|
let reason = await new Promise((resolve) => {
|
||||||
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
expect(reason).toBe(Reason.Finished)
|
expect(reason).toBe(Reason.Finished)
|
||||||
|
|
||||||
// Initial render:
|
// Initial render:
|
||||||
@@ -118,7 +118,7 @@ it('should keep the delay time into account', async () => {
|
|||||||
d.add(
|
d.add(
|
||||||
reportChanges(
|
reportChanges(
|
||||||
() => document.body.innerHTML,
|
() => document.body.innerHTML,
|
||||||
content => {
|
(content) => {
|
||||||
snapshots.push({
|
snapshots.push({
|
||||||
content,
|
content,
|
||||||
recordedAt: process.hrtime.bigint(),
|
recordedAt: process.hrtime.bigint(),
|
||||||
@@ -127,11 +127,11 @@ it('should keep the delay time into account', async () => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let reason = await new Promise(resolve => {
|
let reason = await new Promise((resolve) => {
|
||||||
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
expect(reason).toBe(Reason.Finished)
|
expect(reason).toBe(Reason.Finished)
|
||||||
|
|
||||||
let estimatedDuration = Number(
|
let estimatedDuration = Number(
|
||||||
@@ -161,7 +161,7 @@ it('should be possible to cancel a transition at any time', async () => {
|
|||||||
d.add(
|
d.add(
|
||||||
reportChanges(
|
reportChanges(
|
||||||
() => document.body.innerHTML,
|
() => document.body.innerHTML,
|
||||||
content => {
|
(content) => {
|
||||||
let recordedAt = process.hrtime.bigint()
|
let recordedAt = process.hrtime.bigint()
|
||||||
let total = snapshots.length
|
let total = snapshots.length
|
||||||
|
|
||||||
@@ -178,16 +178,16 @@ it('should be possible to cancel a transition at any time', async () => {
|
|||||||
expect.assertions(2)
|
expect.assertions(2)
|
||||||
|
|
||||||
// Setup the transition
|
// Setup the transition
|
||||||
let cancel = transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], reason => {
|
let cancel = transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], (reason) => {
|
||||||
expect(reason).toBe(Reason.Cancelled)
|
expect(reason).toBe(Reason.Cancelled)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for a bit
|
// Wait for a bit
|
||||||
await new Promise(resolve => setTimeout(resolve, 20))
|
await new Promise((resolve) => setTimeout(resolve, 20))
|
||||||
|
|
||||||
// Cancel the transition
|
// Cancel the transition
|
||||||
cancel()
|
cancel()
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
|
|
||||||
expect(snapshots.map(snapshot => snapshot.content).join('\n')).not.toContain('enterTo')
|
expect(snapshots.map((snapshot) => snapshot.content).join('\n')).not.toContain('enterTo')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ function waitForTransition(node: HTMLElement, done: (reason: Reason) => void) {
|
|||||||
// Safari returns a comma separated list of values, so let's sort them and take the highest value.
|
// Safari returns a comma separated list of values, so let's sort them and take the highest value.
|
||||||
let { transitionDuration, transitionDelay } = getComputedStyle(node)
|
let { transitionDuration, transitionDelay } = getComputedStyle(node)
|
||||||
|
|
||||||
let [durationMs, delaysMs] = [transitionDuration, transitionDelay].map(value => {
|
let [durationMs, delaysMs] = [transitionDuration, transitionDelay].map((value) => {
|
||||||
let [resolvedValue = 0] = value
|
let [resolvedValue = 0] = value
|
||||||
.split(',')
|
.split(',')
|
||||||
// Remove falseys we can't work with
|
// Remove falseys we can't work with
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
// Values are returned as `0.3s` or `75ms`
|
// Values are returned as `0.3s` or `75ms`
|
||||||
.map(v => (v.includes('ms') ? parseFloat(v) : parseFloat(v) * 1000))
|
.map((v) => (v.includes('ms') ? parseFloat(v) : parseFloat(v) * 1000))
|
||||||
.sort((a, z) => z - a)
|
.sort((a, z) => z - a)
|
||||||
|
|
||||||
return resolvedValue
|
return resolvedValue
|
||||||
@@ -72,7 +72,7 @@ export function transition(
|
|||||||
addClasses(node, ...to)
|
addClasses(node, ...to)
|
||||||
|
|
||||||
d.add(
|
d.add(
|
||||||
waitForTransition(node, reason => {
|
waitForTransition(node, (reason) => {
|
||||||
removeClasses(node, ...to, ...base)
|
removeClasses(node, ...to, ...base)
|
||||||
addClasses(node, ...entered)
|
addClasses(node, ...entered)
|
||||||
return _done(reason)
|
return _done(reason)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function useFocusTrap(
|
|||||||
onUnmounted(restore)
|
onUnmounted(restore)
|
||||||
|
|
||||||
// Handle Tab & Shift+Tab keyboard events
|
// Handle Tab & Shift+Tab keyboard events
|
||||||
useWindowEvent('keydown', event => {
|
useWindowEvent('keydown', (event) => {
|
||||||
if (!enabled.value) return
|
if (!enabled.value) return
|
||||||
if (event.key !== Keys.Tab) return
|
if (event.key !== Keys.Tab) return
|
||||||
if (!document.activeElement) return
|
if (!document.activeElement) return
|
||||||
@@ -99,7 +99,7 @@ export function useFocusTrap(
|
|||||||
// Prevent programmatically escaping
|
// Prevent programmatically escaping
|
||||||
useWindowEvent(
|
useWindowEvent(
|
||||||
'focus',
|
'focus',
|
||||||
event => {
|
(event) => {
|
||||||
if (!enabled.value) return
|
if (!enabled.value) return
|
||||||
if (containers.value.size !== 1) return
|
if (containers.value.size !== 1) return
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, ref, nextTick } from 'vue'
|
import { defineComponent, ref, nextTick, ComponentOptionsWithoutProps } from 'vue'
|
||||||
|
|
||||||
import { render } from '../test-utils/vue-testing-library'
|
import { render } from '../test-utils/vue-testing-library'
|
||||||
import { useInertOthers } from './use-inert-others'
|
import { useInertOthers } from './use-inert-others'
|
||||||
@@ -14,7 +14,7 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterAll(() => jest.restoreAllMocks())
|
afterAll(() => jest.restoreAllMocks())
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = {}
|
let defaultComponents = {}
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -35,16 +35,12 @@ function renderTemplate(input: string | Partial<Parameters<typeof defineComponen
|
|||||||
|
|
||||||
let Before = defineComponent({
|
let Before = defineComponent({
|
||||||
name: 'Before',
|
name: 'Before',
|
||||||
template: html`
|
template: html` <div>before</div> `,
|
||||||
<div>before</div>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let After = defineComponent({
|
let After = defineComponent({
|
||||||
name: 'After',
|
name: 'After',
|
||||||
template: html`
|
template: html` <div>after</div> `,
|
||||||
<div>after</div>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to inert other elements', async () => {
|
it('should be possible to inert other elements', async () => {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function useInertOthers<TElement extends HTMLElement>(
|
|||||||
container: Ref<TElement | null>,
|
container: Ref<TElement | null>,
|
||||||
enabled: Ref<boolean> = ref(true)
|
enabled: Ref<boolean> = ref(true)
|
||||||
) {
|
) {
|
||||||
watchEffect(onInvalidate => {
|
watchEffect((onInvalidate) => {
|
||||||
if (!enabled.value) return
|
if (!enabled.value) return
|
||||||
if (!container.value) return
|
if (!container.value) return
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ export function useInertOthers<TElement extends HTMLElement>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect direct children of the body
|
// Collect direct children of the body
|
||||||
document.querySelectorAll(CHILDREN_SELECTOR).forEach(child => {
|
document.querySelectorAll(CHILDREN_SELECTOR).forEach((child) => {
|
||||||
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
|
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
|
||||||
|
|
||||||
// Skip the interactables, and the parents of the interactables
|
// Skip the interactables, and the parents of the interactables
|
||||||
@@ -79,7 +79,7 @@ export function useInertOthers<TElement extends HTMLElement>(
|
|||||||
// will become inert as well.
|
// will become inert as well.
|
||||||
if (interactables.size > 0) {
|
if (interactables.size > 0) {
|
||||||
// Collect direct children of the body
|
// Collect direct children of the body
|
||||||
document.querySelectorAll(CHILDREN_SELECTOR).forEach(child => {
|
document.querySelectorAll(CHILDREN_SELECTOR).forEach((child) => {
|
||||||
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
|
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
|
||||||
|
|
||||||
// Skip already inert parents
|
// Skip already inert parents
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function useTreeWalker({
|
|||||||
if (enabled !== undefined && !enabled.value) return
|
if (enabled !== undefined && !enabled.value) return
|
||||||
|
|
||||||
let acceptNode = Object.assign((node: HTMLElement) => accept(node), { acceptNode: accept })
|
let acceptNode = Object.assign((node: HTMLElement) => accept(node), { acceptNode: accept })
|
||||||
|
// @ts-expect-error This `false` is a simple small fix for older browsers
|
||||||
let walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, acceptNode, false)
|
let walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, acceptNode, false)
|
||||||
|
|
||||||
while (walker.nextNode()) walk(walker.currentNode as HTMLElement)
|
while (walker.nextNode()) walk(walker.currentNode as HTMLElement)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function useWindowEvent<TType extends keyof WindowEventMap>(
|
|||||||
) {
|
) {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
|
|
||||||
watchEffect(onInvalidate => {
|
watchEffect((onInvalidate) => {
|
||||||
window.addEventListener(type, listener, options)
|
window.addEventListener(type, listener, options)
|
||||||
|
|
||||||
onInvalidate(() => {
|
onInvalidate(() => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function useStackContext() {
|
|||||||
export function useElemenStack(element: Ref<HTMLElement | null> | null) {
|
export function useElemenStack(element: Ref<HTMLElement | null> | null) {
|
||||||
let notify = useStackContext()
|
let notify = useStackContext()
|
||||||
|
|
||||||
watchEffect(onInvalidate => {
|
watchEffect((onInvalidate) => {
|
||||||
let domElement = element?.value
|
let domElement = element?.value
|
||||||
if (!domElement) return
|
if (!domElement) return
|
||||||
|
|
||||||
|
|||||||
@@ -1256,7 +1256,7 @@ export function assertLabelValue(element: HTMLElement | null, value: string) {
|
|||||||
|
|
||||||
if (element.hasAttribute('aria-labelledby')) {
|
if (element.hasAttribute('aria-labelledby')) {
|
||||||
let ids = element.getAttribute('aria-labelledby')!.split(' ')
|
let ids = element.getAttribute('aria-labelledby')!.split(' ')
|
||||||
expect(ids.map(id => document.getElementById(id)?.textContent).join(' ')).toEqual(value)
|
expect(ids.map((id) => document.getElementById(id)?.textContent).join(' ')).toEqual(value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1612,7 +1612,7 @@ export function assertTabs(
|
|||||||
expect(list).toHaveAttribute('aria-orientation', orientation)
|
expect(list).toHaveAttribute('aria-orientation', orientation)
|
||||||
|
|
||||||
let activeTab = Array.from(list.querySelectorAll('[id^="headlessui-tabs-tab-"]'))[active]
|
let activeTab = Array.from(list.querySelectorAll('[id^="headlessui-tabs-tab-"]'))[active]
|
||||||
let activePanel = panels.find(panel => panel.id === activeTab.getAttribute('aria-controls'))
|
let activePanel = panels.find((panel) => panel.id === activeTab.getAttribute('aria-controls'))
|
||||||
|
|
||||||
for (let tab of tabs) {
|
for (let tab of tabs) {
|
||||||
expect(tab).toHaveAttribute('id')
|
expect(tab).toHaveAttribute('id')
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function redentSnapshot(input: string) {
|
|||||||
|
|
||||||
return input
|
return input
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(line =>
|
.map((line) =>
|
||||||
line.trim() === '---' ? line : line.replace(replacer, (_, sign, rest) => `${sign} ${rest}`)
|
line.trim() === '---' ? line : line.replace(replacer, (_, sign, rest) => `${sign} ${rest}`)
|
||||||
)
|
)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
@@ -70,13 +70,13 @@ export async function executeTimeline(
|
|||||||
.reduce((total, current) => total + current, 0)
|
.reduce((total, current) => total + current, 0)
|
||||||
|
|
||||||
// Changes happen in the next frame
|
// Changes happen in the next frame
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
|
|
||||||
// We wait for the amount of the duration
|
// We wait for the amount of the duration
|
||||||
await new Promise(resolve => d.setTimeout(resolve, totalDuration))
|
await new Promise((resolve) => d.setTimeout(resolve, totalDuration))
|
||||||
|
|
||||||
// We wait an additional next frame so that we know that we are done
|
// We wait an additional next frame so that we know that we are done
|
||||||
await new Promise(resolve => d.nextFrame(resolve))
|
await new Promise((resolve) => d.nextFrame(resolve))
|
||||||
}, Promise.resolve())
|
}, Promise.resolve())
|
||||||
|
|
||||||
if (snapshots.length <= 0) {
|
if (snapshots.length <= 0) {
|
||||||
@@ -128,7 +128,7 @@ export async function executeTimeline(
|
|||||||
.replace(/Snapshot Diff:\n/g, '')
|
.replace(/Snapshot Diff:\n/g, '')
|
||||||
)
|
)
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(line => ` ${line}`)
|
.map((line) => ` ${line}`)
|
||||||
.join('\n')}`
|
.join('\n')}`
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { render } from './vue-testing-library'
|
import { render } from './vue-testing-library'
|
||||||
|
|
||||||
import { type, shift, Keys } from './interactions'
|
import { type, shift, Keys } from './interactions'
|
||||||
import { defineComponent, h } from 'vue'
|
import { ComponentOptionsWithoutProps, defineComponent, h } from 'vue'
|
||||||
|
|
||||||
type Events = 'onKeydown' | 'onKeyup' | 'onKeypress' | 'onClick' | 'onBlur' | 'onFocus'
|
type Events = 'onKeydown' | 'onKeyup' | 'onKeypress' | 'onClick' | 'onBlur' | 'onFocus'
|
||||||
let events: Events[] = ['onKeydown', 'onKeyup', 'onKeypress', 'onClick', 'onBlur', 'onFocus']
|
let events: Events[] = ['onKeydown', 'onKeyup', 'onKeypress', 'onClick', 'onBlur', 'onFocus']
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = {}
|
let defaultComponents = {}
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -164,7 +164,7 @@ describe('Keyboard', () => {
|
|||||||
let state = { readyToCapture: false }
|
let state = { readyToCapture: false }
|
||||||
|
|
||||||
function createProps(id: string) {
|
function createProps(id: string) {
|
||||||
return events.reduce(
|
return events.reduce<Record<string, string | ((event: any) => void)>>(
|
||||||
(props, name) => {
|
(props, name) => {
|
||||||
props[name] = (event: any) => {
|
props[name] = (event: any) => {
|
||||||
if (!state.readyToCapture) return
|
if (!state.readyToCapture) return
|
||||||
@@ -202,7 +202,7 @@ describe('Keyboard', () => {
|
|||||||
|
|
||||||
await type([key(input)])
|
await type([key(input)])
|
||||||
|
|
||||||
let expected = result.map(e => event(e))
|
let expected = result.map((e) => event(e))
|
||||||
|
|
||||||
expect(fired.length).toEqual(result.length)
|
expect(fired.length).toEqual(result.length)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function shift(event: Partial<KeyboardEvent>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function word(input: string): Partial<KeyboardEvent>[] {
|
export function word(input: string): Partial<KeyboardEvent>[] {
|
||||||
let result = input.split('').map(key => ({ key }))
|
let result = input.split('').map((key) => ({ key }))
|
||||||
|
|
||||||
d.enqueue(() => {
|
d.enqueue(() => {
|
||||||
let element = document.activeElement
|
let element = document.activeElement
|
||||||
@@ -152,7 +152,7 @@ export async function type(events: Partial<KeyboardEvent>[], element = document.
|
|||||||
let actions = order[event.key!] ?? order[Default as any]
|
let actions = order[event.key!] ?? order[Default as any]
|
||||||
for (let action of actions) {
|
for (let action of actions) {
|
||||||
let checks = action.name.split('And')
|
let checks = action.name.split('And')
|
||||||
if (checks.some(check => skip.has(check))) continue
|
if (checks.some((check) => skip.has(check))) continue
|
||||||
|
|
||||||
let result = action(element, {
|
let result = action(element, {
|
||||||
type: action.name,
|
type: action.name,
|
||||||
@@ -344,8 +344,8 @@ let focusableSelector = [
|
|||||||
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
||||||
// "hidden" can be the document.activeElement, because this is not possible
|
// "hidden" can be the document.activeElement, because this is not possible
|
||||||
// in real browsers.
|
// in real browsers.
|
||||||
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
||||||
: selector => `${selector}:not([tabindex='-1'])`
|
: (selector) => `${selector}:not([tabindex='-1'])`
|
||||||
)
|
)
|
||||||
.join(',')
|
.join(',')
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ type FunctionPropertyNames<T> = {
|
|||||||
|
|
||||||
export function suppressConsoleLogs<T extends unknown[]>(
|
export function suppressConsoleLogs<T extends unknown[]>(
|
||||||
cb: (...args: T) => void,
|
cb: (...args: T) => void,
|
||||||
type: FunctionPropertyNames<typeof global.console> = 'warn'
|
type: FunctionPropertyNames<typeof globalThis.console> = 'warn'
|
||||||
) {
|
) {
|
||||||
return (...args: T) => {
|
return (...args: T) => {
|
||||||
let spy = jest.spyOn(global.console, type).mockImplementation(jest.fn())
|
let spy = jest.spyOn(globalThis.console, type).mockImplementation(jest.fn())
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
Promise.resolve(cb(...args)).then(resolve, reject)
|
Promise.resolve(cb(...args)).then(resolve, reject)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function calculateActiveIndex<TItem>(
|
|||||||
let nextActiveIndex = (() => {
|
let nextActiveIndex = (() => {
|
||||||
switch (action.focus) {
|
switch (action.focus) {
|
||||||
case Focus.First:
|
case Focus.First:
|
||||||
return items.findIndex(item => !resolvers.resolveDisabled(item))
|
return items.findIndex((item) => !resolvers.resolveDisabled(item))
|
||||||
|
|
||||||
case Focus.Previous: {
|
case Focus.Previous: {
|
||||||
let idx = items
|
let idx = items
|
||||||
@@ -64,13 +64,13 @@ export function calculateActiveIndex<TItem>(
|
|||||||
let idx = items
|
let idx = items
|
||||||
.slice()
|
.slice()
|
||||||
.reverse()
|
.reverse()
|
||||||
.findIndex(item => !resolvers.resolveDisabled(item))
|
.findIndex((item) => !resolvers.resolveDisabled(item))
|
||||||
if (idx === -1) return idx
|
if (idx === -1) return idx
|
||||||
return items.length - 1 - idx
|
return items.length - 1 - idx
|
||||||
}
|
}
|
||||||
|
|
||||||
case Focus.Specific:
|
case Focus.Specific:
|
||||||
return items.findIndex(item => resolvers.resolveId(item) === action.id)
|
return items.findIndex((item) => resolvers.resolveId(item) === action.id)
|
||||||
|
|
||||||
case Focus.Nothing:
|
case Focus.Nothing:
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ let focusableSelector = [
|
|||||||
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
||||||
// "hidden" can be the document.activeElement, because this is not possible
|
// "hidden" can be the document.activeElement, because this is not possible
|
||||||
// in real browsers.
|
// in real browsers.
|
||||||
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
||||||
: selector => `${selector}:not([tabindex='-1'])`
|
: (selector) => `${selector}:not([tabindex='-1'])`
|
||||||
)
|
)
|
||||||
.join(',')
|
.join(',')
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function match<TValue extends string | number = string, TReturnValue = un
|
|||||||
`Tried to handle "${value}" but there is no handler defined. Only defined handlers are: ${Object.keys(
|
`Tried to handle "${value}" but there is no handler defined. Only defined handlers are: ${Object.keys(
|
||||||
lookup
|
lookup
|
||||||
)
|
)
|
||||||
.map(key => `"${key}"`)
|
.map((key) => `"${key}"`)
|
||||||
.join(', ')}.`
|
.join(', ')}.`
|
||||||
)
|
)
|
||||||
if (Error.captureStackTrace) Error.captureStackTrace(error, match)
|
if (Error.captureStackTrace) Error.captureStackTrace(error, match)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent } from 'vue'
|
import { defineComponent, ComponentOptionsWithoutProps } from 'vue'
|
||||||
import { render as testRender } from '../test-utils/vue-testing-library'
|
import { render as testRender } from '../test-utils/vue-testing-library'
|
||||||
|
|
||||||
import { render } from './render'
|
import { render } from './render'
|
||||||
@@ -13,7 +13,7 @@ let Dummy = defineComponent({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
|
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
|
||||||
let defaultComponents = { Dummy }
|
let defaultComponents = { Dummy }
|
||||||
|
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -34,9 +34,7 @@ describe('Validation', () => {
|
|||||||
expect.hasAssertions()
|
expect.hasAssertions()
|
||||||
|
|
||||||
renderTemplate({
|
renderTemplate({
|
||||||
template: html`
|
template: html` <Dummy as="template" class="abc">Contents</Dummy> `,
|
||||||
<Dummy as="template" class="abc">Contents</Dummy>
|
|
||||||
`,
|
|
||||||
errorCaptured(err) {
|
errorCaptured(err) {
|
||||||
expect(err as Error).toEqual(
|
expect(err as Error).toEqual(
|
||||||
new Error(
|
new Error(
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ function _render({
|
|||||||
`However we need to passthrough the following props:`,
|
`However we need to passthrough the following props:`,
|
||||||
Object.keys(passThroughProps)
|
Object.keys(passThroughProps)
|
||||||
.concat(Object.keys(attrs))
|
.concat(Object.keys(attrs))
|
||||||
.map(line => ` - ${line}`)
|
.map((line) => ` - ${line}`)
|
||||||
.join('\n'),
|
.join('\n'),
|
||||||
'',
|
'',
|
||||||
'You can apply a few solutions:',
|
'You can apply a few solutions:',
|
||||||
@@ -106,7 +106,7 @@ function _render({
|
|||||||
'Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".',
|
'Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".',
|
||||||
'Render a single element as the child so that we can forward the props onto that element.',
|
'Render a single element as the child so that we can forward the props onto that element.',
|
||||||
]
|
]
|
||||||
.map(line => ` - ${line}`)
|
.map((line) => ` - ${line}`)
|
||||||
.join('\n'),
|
.join('\n'),
|
||||||
].join('\n')
|
].join('\n')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noEmit": true,
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true
|
"isolatedModules": true
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user