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:
Robin Malfait
2022-01-27 17:07:38 +01:00
committed by GitHub
parent ea26870480
commit fdd2629795
166 changed files with 5137 additions and 5561 deletions
-1
View File
@@ -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.
-1
View File
@@ -12,4 +12,3 @@ contact_links:
- name: Documentation Issue
url: https://github.com/tailwindlabs/headlessui/issues/new?title=%5BDOCS%5D:%20
about: 'For documentation issues, suggest changes on our documentation repository.'
+1 -2
View File
@@ -44,7 +44,7 @@ jobs:
id: vars
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
- name: Publish
@@ -52,4 +52,3 @@ jobs:
env:
CI: true
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+4
View File
@@ -0,0 +1,4 @@
dist/
node_modules/
coverage/
.next/
+11
View File
@@ -0,0 +1,11 @@
{
"minify": false,
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": false,
"dynamicImport": false
}
}
}
+2
View File
@@ -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))
- 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 overal codebase, use modern tech like `esbuild` and TypeScript 4! ([#1055](https://github.com/tailwindlabs/headlessui/pull/1055))
### 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))
- 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
-1
View File
@@ -49,4 +49,3 @@ For casual chit-chat with others using the library:
## 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**.
+2 -9
View File
@@ -1,17 +1,10 @@
const { createJestConfig: create } = require('tsdx/dist/createJestConfig')
module.exports = function createJestConfig(root, options) {
return Object.assign(
{},
create(undefined, root),
{
rootDir: root,
setupFilesAfterEnv: ['<rootDir>../../jest/custom-matchers.ts'],
globals: {
'ts-jest': {
isolatedModules: true,
tsConfig: '<rootDir>/tsconfig.tsdx.json',
},
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
},
},
options
+18 -6
View File
@@ -14,10 +14,13 @@
"react-playground": "yarn workspace playground-react dev",
"playground-react": "yarn workspace playground-react dev",
"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",
"test": "./scripts/test.sh",
"lint": "./scripts/lint.sh"
"lint": "./scripts/lint.sh",
"lint-check": "CI=true ./scripts/lint.sh"
},
"husky": {
"hooks": {
@@ -25,7 +28,7 @@
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": "tsdx lint"
"*": "yarn lint-check"
},
"prettier": {
"printWidth": 100,
@@ -34,12 +37,21 @@
"trailingComma": "es5"
},
"devDependencies": {
"@swc/core": "^1.2.131",
"@swc/jest": "^0.2.17",
"@testing-library/jest-dom": "^5.11.9",
"@types/node": "^14.14.22",
"esbuild": "^0.14.11",
"husky": "^4.3.8",
"jest": "26",
"lint-staged": "^12.2.1",
"tsdx": "^0.14.1",
"tslib": "^2.1.0",
"typescript": "^3.9.7"
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1",
"rimraf": "^3.0.2",
"tslib": "^2.3.1",
"typescript": "^4.5.4"
},
"dependencies": {
"prettier-plugin-tailwindcss": "^0.1.4"
}
}
-1
View File
@@ -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:
[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')
}
+17 -5
View File
@@ -4,12 +4,21 @@
"description": "A set of completely unstyled, fully accessible UI components for React, designed to integrate beautifully with Tailwind CSS.",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"module": "dist/index.esm.js",
"module": "dist/headlessui.esm.js",
"license": "MIT",
"files": [
"README.md",
"dist"
],
"exports": {
".": {
"import": {
"default": "./dist/headlessui.esm.js"
},
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"sideEffects": false,
"engines": {
"node": ">=10"
@@ -24,10 +33,12 @@
},
"scripts": {
"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",
"build": "../../scripts/build.sh",
"watch": "../../scripts/watch.sh",
"lint": "../../scripts/lint.sh"
"lint": "../../scripts/lint.sh",
"playground": "yarn workspace playground-react dev",
"clean": "rimraf ./dist"
},
"peerDependencies": {
"react": "^16 || ^17 || ^18",
@@ -39,6 +50,7 @@
"@types/react-dom": "^16.9.10",
"react": "^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.Button>Trigger</Combobox.Button>
<Combobox.Options>
{data => (
{(data) => (
<>
<Combobox.Option value="a">{JSON.stringify(data)}</Combobox.Option>
</>
@@ -639,10 +639,10 @@ describe('Rendering composition', () => {
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="a" className={bag => JSON.stringify(bag)}>
<Combobox.Option value="a" className={(bag) => JSON.stringify(bag)}>
Option A
</Combobox.Option>
<Combobox.Option value="b" disabled className={bag => JSON.stringify(bag)}>
<Combobox.Option value="b" disabled className={(bag) => JSON.stringify(bag)}>
Option B
</Combobox.Option>
<Combobox.Option value="c" className="no-special-treatment">
@@ -738,7 +738,7 @@ describe('Rendering composition', () => {
await click(getComboboxButton())
// 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} />
<Combobox.Options>
<Combobox.Option value="a">
{data => (
{(data) => (
<>
{JSON.stringify(data)}
<Debug name="Combobox.Option" fn={orderFn} />
@@ -855,7 +855,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option, { selected: false }))
options.forEach((option) => assertComboboxOption(option, { selected: false }))
assertNoActiveComboboxOption()
assertNoSelectedComboboxOption()
@@ -1026,7 +1026,7 @@ describe('Keyboard interactions', () => {
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
{myOptions.map(myOption => (
{myOptions.map((myOption) => (
<Combobox.Option key={myOption.id} value={myOption}>
{myOption.name}
</Combobox.Option>
@@ -1142,7 +1142,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
})
)
@@ -1383,7 +1383,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// Verify that the first combobox option is active
assertNoActiveComboboxOption()
@@ -1539,7 +1539,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// Verify that the first combobox option is active
assertNoActiveComboboxOption()
@@ -1695,7 +1695,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveComboboxOption(options[2])
@@ -1843,7 +1843,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[0])
})
)
@@ -1890,7 +1890,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveComboboxOption(options[2])
@@ -2039,7 +2039,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[0])
})
)
@@ -2059,7 +2059,7 @@ describe('Keyboard interactions', () => {
return (
<Combobox
value={value}
onChange={value => {
onChange={(value) => {
setValue(value)
handleChange(value)
}}
@@ -2305,7 +2305,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// Verify that the first combobox option is active
assertNoActiveComboboxOption()
@@ -2446,7 +2446,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// We should be able to go down once
@@ -2496,7 +2496,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// We should be able to go down once
@@ -2536,7 +2536,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// Open combobox
@@ -2587,7 +2587,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// Verify that the first combobox option is active
assertNoActiveComboboxOption()
@@ -2729,7 +2729,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// We should be able to go down once
@@ -2779,7 +2779,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// We should be able to go down once
@@ -2819,7 +2819,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// Open combobox
@@ -2869,7 +2869,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveComboboxOption(options[2])
@@ -3017,7 +3017,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[0])
})
)
@@ -3053,7 +3053,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// Going up or down should select the single available option
@@ -3108,7 +3108,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[2])
// We should be able to go down once
@@ -3167,7 +3167,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveComboboxOption(options[2])
@@ -3316,7 +3316,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[0])
})
)
@@ -3894,14 +3894,16 @@ describe('Keyboard interactions', () => {
let filteredPeople =
query === ''
? props.people
: props.people.filter(person => person.name.toLowerCase().includes(query.toLowerCase()))
: props.people.filter((person) =>
person.name.toLowerCase().includes(query.toLowerCase())
)
return (
<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.Options>
{filteredPeople.map(person => (
{filteredPeople.map((person) => (
<Combobox.Option key={person.value} value={person.value} disabled={person.disabled}>
{person.name}
</Combobox.Option>
@@ -4207,7 +4209,7 @@ describe('Mouse interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
})
)
@@ -4752,7 +4754,7 @@ describe('Mouse interactions', () => {
return (
<Combobox
value={value}
onChange={value => {
onChange={(value) => {
setValue(value)
handleChange(value)
}}
@@ -4804,7 +4806,7 @@ describe('Mouse interactions', () => {
return (
<Combobox
value={value}
onChange={value => {
onChange={(value) => {
setValue(value)
handleChange(value)
}}
@@ -123,8 +123,8 @@ let reducers: {
let activeOptionIndex = calculateActiveIndex(action, {
resolveItems: () => state.options,
resolveActiveIndex: () => state.activeOptionIndex,
resolveId: item => item.id,
resolveDisabled: item => item.dataRef.current.disabled,
resolveId: (item) => item.id,
resolveDisabled: (item) => item.dataRef.current.disabled,
})
if (state.activeOptionIndex === activeOptionIndex) return state
@@ -163,7 +163,7 @@ let reducers: {
let currentActiveOption =
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)
@@ -284,12 +284,13 @@ export function Combobox<TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG,
}, [onChange, propsRef])
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetDisabled, disabled }), [disabled])
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetOrientation, orientation }), [
orientation,
])
useIsoMorphicEffect(
() => dispatch({ type: ActionTypes.SetOrientation, orientation }),
[orientation]
)
// Handle outside click
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
if (comboboxState !== ComboboxStates.Open) return
@@ -333,7 +334,7 @@ export function Combobox<TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG,
let selectOption = useCallback(
(id: string) => {
let option = options.find(item => item.id === id)
let option = options.find((item) => item.id === id)
if (!option) return
let { dataRef } = option
@@ -418,7 +419,7 @@ let Input = forwardRefWithAs(function Input<
ref: Ref<HTMLInputElement>
) {
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 inputRef = useSyncRefs(state.inputRef, ref)
@@ -579,7 +580,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
ref: Ref<HTMLButtonElement>
) {
let [state, dispatch] = useComboboxContext([Combobox.name, Button.name].join('.'))
let [state, dispatch] = useComboboxContext('Combobox.Button')
let actions = useComboboxActions()
let buttonRef = useSyncRefs(state.buttonRef, ref)
@@ -693,12 +694,13 @@ type LabelPropsWeControl = 'id' | 'ref' | 'onClick'
function Label<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
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 handleClick = useCallback(() => state.inputRef.current?.focus({ preventScroll: true }), [
state.inputRef,
])
let handleClick = useCallback(
() => state.inputRef.current?.focus({ preventScroll: true }),
[state.inputRef]
)
let slot = useMemo<LabelRenderPropArg>(
() => ({ open: state.comboboxState === ComboboxStates.Open, disabled: state.disabled }),
@@ -737,7 +739,7 @@ let Options = forwardRefWithAs(function Options<
PropsForFeatures<typeof OptionsRenderFeatures>,
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 id = `headlessui-combobox-options-${useId()}`
@@ -751,10 +753,10 @@ let Options = forwardRefWithAs(function Options<
return state.comboboxState === ComboboxStates.Open
})()
let labelledby = useComputed(() => state.labelRef.current?.id ?? state.buttonRef.current?.id, [
state.labelRef.current,
state.buttonRef.current,
])
let labelledby = useComputed(
() => state.labelRef.current?.id ?? state.buttonRef.current?.id,
[state.labelRef.current, state.buttonRef.current]
)
let handleLeave = useCallback(() => {
if (state.comboboxState !== ComboboxStates.Open) return
@@ -820,7 +822,7 @@ function Option<
}
) {
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 id = `headlessui-combobox-option-${useId()}`
let active =
@@ -886,11 +888,10 @@ function Option<
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Nothing })
}, [disabled, active, dispatch])
let slot = useMemo<OptionRenderPropArg>(() => ({ active, selected, disabled }), [
active,
selected,
disabled,
])
let slot = useMemo<OptionRenderPropArg>(
() => ({ active, selected, disabled }),
[active, selected, disabled]
)
let propsWeControl = {
id,
@@ -57,10 +57,10 @@ export function useDescriptions(): [
useMemo(() => {
return function DescriptionProvider(props: DescriptionProviderProps) {
let register = useCallback((value: string) => {
setDescriptionIds(existing => [...existing, value])
setDescriptionIds((existing) => [...existing, value])
return () =>
setDescriptionIds(existing => {
setDescriptionIds((existing) => {
let clone = existing.slice()
let idx = clone.indexOf(value)
if (idx !== -1) clone.splice(idx, 1)
@@ -143,7 +143,7 @@ describe('Rendering', () => {
Trigger
</button>
<Dialog open={isOpen} onClose={setIsOpen}>
{data => (
{(data) => (
<>
<pre>{JSON.stringify(data)}</pre>
<TabSentinel />
@@ -204,7 +204,7 @@ describe('Rendering', () => {
return (
<>
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
Trigger
</button>
<Dialog open={isOpen} onClose={setIsOpen} unmount={false}>
@@ -239,7 +239,7 @@ describe('Rendering', () => {
return (
<>
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
Trigger
</button>
@@ -277,7 +277,7 @@ describe('Rendering', () => {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
Trigger
</button>
<Dialog open={isOpen} onClose={setIsOpen}>
@@ -400,7 +400,7 @@ describe('Keyboard interactions', () => {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
Trigger
</button>
<Dialog open={isOpen} onClose={setIsOpen}>
@@ -438,7 +438,7 @@ describe('Keyboard interactions', () => {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
Trigger
</button>
<Dialog open={isOpen} onClose={setIsOpen}>
@@ -477,14 +477,14 @@ describe('Keyboard interactions', () => {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
Trigger
</button>
<Dialog open={isOpen} onClose={setIsOpen}>
Contents
<input
id="name"
onKeyDown={event => {
onKeyDown={(event) => {
event.preventDefault()
event.stopPropagation()
}}
@@ -525,7 +525,7 @@ describe('Mouse interactions', () => {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
Trigger
</button>
<Dialog open={isOpen} onClose={setIsOpen}>
@@ -559,7 +559,7 @@ describe('Mouse interactions', () => {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button id="trigger" onClick={() => setIsOpen(v => !v)}>
<button id="trigger" onClick={() => setIsOpen((v) => !v)}>
Trigger
</button>
<Dialog open={isOpen} onClose={setIsOpen}>
@@ -595,7 +595,7 @@ describe('Mouse interactions', () => {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button onClick={() => setIsOpen(v => !v)}>Trigger</button>
<button onClick={() => setIsOpen((v) => !v)}>Trigger</button>
<Dialog open={isOpen} onClose={setIsOpen}>
Contents
<TabSentinel />
@@ -630,7 +630,7 @@ describe('Mouse interactions', () => {
return (
<>
<button>Hello</button>
<button onClick={() => setIsOpen(v => !v)}>Trigger</button>
<button onClick={() => setIsOpen((v) => !v)}>Trigger</button>
<Dialog open={isOpen} onClose={setIsOpen}>
Contents
<TabSentinel />
@@ -206,7 +206,7 @@ let DialogRoot = forwardRefWithAs(function Dialog<
useInertOthers(internalDialogRef, hasNestedDialogs ? enabled : false)
// Handle outside click
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
if (dialogState !== DialogStates.Open) return
@@ -217,7 +217,7 @@ let DialogRoot = forwardRefWithAs(function Dialog<
})
// Handle `Escape` to close
useWindowEvent('keydown', event => {
useWindowEvent('keydown', (event) => {
if (event.key !== Keys.Escape) return
if (dialogState !== DialogStates.Open) return
if (hasNestedDialogs) return
@@ -250,7 +250,7 @@ let DialogRoot = forwardRefWithAs(function Dialog<
if (dialogState !== DialogStates.Open) return
if (!internalDialogRef.current) return
let observer = new IntersectionObserver(entries => {
let observer = new IntersectionObserver((entries) => {
for (let entry of entries) {
if (
entry.boundingClientRect.x === 0 &&
@@ -277,9 +277,10 @@ let DialogRoot = forwardRefWithAs(function Dialog<
[dialogState, state, close, setTitleId]
)
let slot = useMemo<DialogRenderPropArg>(() => ({ open: dialogState === DialogStates.Open }), [
dialogState,
])
let slot = useMemo<DialogRenderPropArg>(
() => ({ open: dialogState === DialogStates.Open }),
[dialogState]
)
let propsWeControl = {
ref: dialogRef,
@@ -304,11 +305,11 @@ let DialogRoot = forwardRefWithAs(function Dialog<
match(message, {
[StackMessage.Add]() {
containers.current.add(element)
setNestedDialogCount(count => count + 1)
setNestedDialogCount((count) => count + 1)
},
[StackMessage.Remove]() {
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<
TTag extends ElementType = typeof DEFAULT_OVERLAY_TAG
>(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 id = `headlessui-dialog-overlay-${useId()}`
@@ -364,9 +365,10 @@ let Overlay = forwardRefWithAs(function Overlay<
[close]
)
let slot = useMemo<OverlayRenderPropArg>(() => ({ open: dialogState === DialogStates.Open }), [
dialogState,
])
let slot = useMemo<OverlayRenderPropArg>(
() => ({ open: dialogState === DialogStates.Open }),
[dialogState]
)
let propsWeControl = {
ref: overlayRef,
id,
@@ -394,7 +396,7 @@ type TitlePropsWeControl = 'id'
function Title<TTag extends ElementType = typeof DEFAULT_TITLE_TAG>(
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()}`
@@ -403,9 +405,10 @@ function Title<TTag extends ElementType = typeof DEFAULT_TITLE_TAG>(
return () => setTitleId(null)
}, [id, setTitleId])
let slot = useMemo<TitleRenderPropArg>(() => ({ open: dialogState === DialogStates.Open }), [
dialogState,
])
let slot = useMemo<TitleRenderPropArg>(
() => ({ open: dialogState === DialogStates.Open }),
[dialogState]
)
let propsWeControl = { id }
let passthroughProps = props
@@ -20,7 +20,7 @@ jest.mock('../../hooks/use-id')
afterAll(() => jest.restoreAllMocks())
function nextFrame() {
return new Promise<void>(resolve => {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
resolve()
@@ -68,14 +68,14 @@ let reducers: {
action: Extract<Actions, { type: P }>
) => StateDefinition
} = {
[ActionTypes.ToggleDisclosure]: state => ({
[ActionTypes.ToggleDisclosure]: (state) => ({
...state,
disclosureState: match(state.disclosureState, {
[DisclosureStates.Open]: DisclosureStates.Closed,
[DisclosureStates.Closed]: DisclosureStates.Open,
}),
}),
[ActionTypes.CloseDisclosure]: state => {
[ActionTypes.CloseDisclosure]: (state) => {
if (state.disclosureState === DisclosureStates.Closed) return state
return { ...state, disclosureState: DisclosureStates.Closed }
},
@@ -227,7 +227,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
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 buttonRef = useSyncRefs(internalButtonRef, ref)
@@ -334,8 +334,8 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
PropsForFeatures<typeof PanelRenderFeatures>,
ref: Ref<HTMLDivElement>
) {
let [state, dispatch] = useDisclosureContext([Disclosure.name, Panel.name].join('.'))
let { close } = useDisclosureAPIContext([Disclosure.name, Panel.name].join('.'))
let [state, dispatch] = useDisclosureContext('Disclosure.Panel')
let { close } = useDisclosureAPIContext('Disclosure.Panel')
let panelRef = useSyncRefs(ref, () => {
if (state.linkedPanel) return
@@ -52,10 +52,10 @@ export function useLabels(): [string | undefined, (props: LabelProviderProps) =>
useMemo(() => {
return function LabelProvider(props: LabelProviderProps) {
let register = useCallback((value: string) => {
setLabelIds(existing => [...existing, value])
setLabelIds((existing) => [...existing, value])
return () =>
setLabelIds(existing => {
setLabelIds((existing) => {
let clone = existing.slice()
let idx = clone.indexOf(value)
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 />',
suppressConsoleLogs((name, Component) => {
// @ts-expect-error This is fine
expect(() => render(createElement(Component))).toThrowError(
`<${name} /> is missing a parent <Listbox /> component.`
)
@@ -396,7 +397,7 @@ describe('Rendering', () => {
<Listbox value={undefined} onChange={console.log}>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
{data => (
{(data) => (
<>
<Listbox.Option value="a">{JSON.stringify(data)}</Listbox.Option>
</>
@@ -547,10 +548,10 @@ describe('Rendering composition', () => {
<Listbox value={undefined} onChange={console.log}>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
<Listbox.Option value="a" className={bag => JSON.stringify(bag)}>
<Listbox.Option value="a" className={(bag) => JSON.stringify(bag)}>
Option A
</Listbox.Option>
<Listbox.Option value="b" disabled className={bag => JSON.stringify(bag)}>
<Listbox.Option value="b" disabled className={(bag) => JSON.stringify(bag)}>
Option B
</Listbox.Option>
<Listbox.Option value="c" className="no-special-treatment">
@@ -645,7 +646,7 @@ describe('Rendering composition', () => {
await click(getListboxButton())
// 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} />
<Listbox.Options>
<Listbox.Option value="a">
{data => (
{(data) => (
<>
{JSON.stringify(data)}
<Debug name="Listbox.Option" fn={orderFn} />
@@ -756,7 +757,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
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
assertActiveListboxOption(options[0])
@@ -918,7 +919,7 @@ describe('Keyboard interactions', () => {
<Listbox value={selectedOption} onChange={console.log}>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
{myOptions.map(myOption => (
{myOptions.map((myOption) => (
<Listbox.Option key={myOption.id} value={myOption}>
{myOption.name}
</Listbox.Option>
@@ -1139,7 +1140,7 @@ describe('Keyboard interactions', () => {
return (
<Listbox
value={value}
onChange={value => {
onChange={(value) => {
setValue(value)
handleChange(value)
}}
@@ -1234,7 +1235,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
})
)
@@ -1462,7 +1463,7 @@ describe('Keyboard interactions', () => {
return (
<Listbox
value={value}
onChange={value => {
onChange={(value) => {
setValue(value)
handleChange(value)
}}
@@ -1600,7 +1601,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
// Try to tab
@@ -1651,7 +1652,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
// Try to Shift+Tab
@@ -1704,7 +1705,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
// Verify that the first listbox option is active
assertActiveListboxOption(options[0])
@@ -1844,7 +1845,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
// We should be able to go down once
@@ -1892,7 +1893,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[1])
// We should be able to go down once
@@ -1934,7 +1935,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[2])
})
)
@@ -1970,7 +1971,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
// We should be able to go right once
@@ -2027,7 +2028,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveListboxOption(options[2])
@@ -2171,7 +2172,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
})
)
@@ -2209,7 +2210,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[2])
// We should not be able to go up (because those are disabled)
@@ -2260,7 +2261,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[2])
// We should be able to go down once
@@ -2318,7 +2319,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[2])
// We should be able to go left once
@@ -3198,7 +3199,7 @@ describe('Mouse interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
})
)
@@ -3726,7 +3727,7 @@ describe('Mouse interactions', () => {
return (
<Listbox
value={value}
onChange={value => {
onChange={(value) => {
setValue(value)
handleChange(value)
}}
@@ -3777,7 +3778,7 @@ describe('Mouse interactions', () => {
return (
<Listbox
value={value}
onChange={value => {
onChange={(value) => {
setValue(value)
handleChange(value)
}}
@@ -119,8 +119,8 @@ let reducers: {
let activeOptionIndex = calculateActiveIndex(action, {
resolveItems: () => state.options,
resolveActiveIndex: () => state.activeOptionIndex,
resolveId: item => item.id,
resolveDisabled: item => item.dataRef.current.disabled,
resolveId: (item) => item.id,
resolveDisabled: (item) => item.dataRef.current.disabled,
})
if (state.searchQuery === '' && state.activeOptionIndex === activeOptionIndex) return state
@@ -140,7 +140,7 @@ let reducers: {
: state.options
let matchingOption = reOrderedOptions.find(
option =>
(option) =>
!option.dataRef.current.disabled &&
option.dataRef.current.textValue?.startsWith(searchQuery)
)
@@ -175,7 +175,7 @@ let reducers: {
let currentActiveOption =
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)
@@ -251,12 +251,13 @@ export function Listbox<TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG, T
propsRef.current.onChange = onChange
}, [onChange, propsRef])
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetDisabled, disabled }), [disabled])
useIsoMorphicEffect(() => dispatch({ type: ActionTypes.SetOrientation, orientation }), [
orientation,
])
useIsoMorphicEffect(
() => dispatch({ type: ActionTypes.SetOrientation, orientation }),
[orientation]
)
// Handle outside click
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
if (listboxState !== ListboxStates.Open) return
@@ -318,7 +319,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
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 id = `headlessui-listbox-button-${useId()}`
@@ -422,12 +423,13 @@ type LabelPropsWeControl = 'id' | 'ref' | 'onClick'
function Label<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
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 handleClick = useCallback(() => state.buttonRef.current?.focus({ preventScroll: true }), [
state.buttonRef,
])
let handleClick = useCallback(
() => state.buttonRef.current?.focus({ preventScroll: true }),
[state.buttonRef]
)
let slot = useMemo<LabelRenderPropArg>(
() => ({ open: state.listboxState === ListboxStates.Open, disabled: state.disabled }),
@@ -466,7 +468,7 @@ let Options = forwardRefWithAs(function Options<
PropsForFeatures<typeof OptionsRenderFeatures>,
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 id = `headlessui-listbox-options-${useId()}`
@@ -561,10 +563,10 @@ let Options = forwardRefWithAs(function Options<
[d, dispatch, searchDisposables, state]
)
let labelledby = useComputed(() => state.labelRef.current?.id ?? state.buttonRef.current?.id, [
state.labelRef.current,
state.buttonRef.current,
])
let labelledby = useComputed(
() => state.labelRef.current?.id ?? state.buttonRef.current?.id,
[state.labelRef.current, state.buttonRef.current]
)
let slot = useMemo<OptionsRenderPropArg>(
() => ({ open: state.listboxState === ListboxStates.Open }),
@@ -625,7 +627,7 @@ function Option<
}
) {
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 active =
state.activeOptionIndex !== null ? state.options[state.activeOptionIndex].id === id : false
@@ -692,11 +694,10 @@ function Option<
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Nothing })
}, [disabled, active, dispatch])
let slot = useMemo<OptionRenderPropArg>(() => ({ active, selected, disabled }), [
active,
selected,
disabled,
])
let slot = useMemo<OptionRenderPropArg>(
() => ({ active, selected, disabled }),
[active, selected, disabled]
)
let propsWeControl = {
id,
role: 'option',
@@ -253,7 +253,7 @@ describe('Rendering', () => {
<Menu>
<Menu.Button>Trigger</Menu.Button>
<Menu.Items>
{data => (
{(data) => (
<>
<Menu.Item as="a">{JSON.stringify(data)}</Menu.Item>
</>
@@ -403,10 +403,10 @@ describe('Rendering composition', () => {
<Menu>
<Menu.Button>Trigger</Menu.Button>
<Menu.Items>
<Menu.Item as="a" className={bag => JSON.stringify(bag)}>
<Menu.Item as="a" className={(bag) => JSON.stringify(bag)}>
Item A
</Menu.Item>
<Menu.Item as="a" disabled className={bag => JSON.stringify(bag)}>
<Menu.Item as="a" disabled className={(bag) => JSON.stringify(bag)}>
Item B
</Menu.Item>
<Menu.Item as="a" className="no-special-treatment">
@@ -484,7 +484,7 @@ describe('Rendering composition', () => {
// Verify items are buttons now
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>
<div className="outer">
<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 B</Menu.Item>
</div>
<div className="py-1 inner">
<div className="inner py-1">
<Menu.Item as="button">Item C</Menu.Item>
<Menu.Item>
<div>
@@ -508,7 +508,7 @@ describe('Rendering composition', () => {
</div>
</Menu.Item>
</div>
<div className="py-1 inner">
<div className="inner py-1">
<form className="inner">
<Menu.Item as="button">Item E</Menu.Item>
</form>
@@ -523,11 +523,11 @@ describe('Rendering composition', () => {
expect.hasAssertions()
document.querySelectorAll('.outer').forEach(element => {
document.querySelectorAll('.outer').forEach((element) => {
expect(element).not.toHaveAttribute('role', 'none')
})
document.querySelectorAll('.inner').forEach(element => {
document.querySelectorAll('.inner').forEach((element) => {
expect(element).toHaveAttribute('role', 'none')
})
})
@@ -557,7 +557,7 @@ describe('Composition', () => {
<Debug name="Transition" fn={orderFn} />
<Menu.Items>
<Menu.Item as="a">
{data => (
{(data) => (
<>
{JSON.stringify(data)}
<Debug name="Menu.Item" fn={orderFn} />
@@ -611,7 +611,7 @@ describe('Composition', () => {
<Debug name="Transition" fn={orderFn} />
<Menu.Items>
<Menu.Item as="a">
{data => (
{(data) => (
<>
{JSON.stringify(data)}
<Debug name="Menu.Item" fn={orderFn} />
@@ -693,7 +693,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
// Verify that the first menu item is active
assertMenuLinkedWithMenuItem(items[0])
@@ -1057,7 +1057,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
})
)
@@ -1395,7 +1395,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// Try to tab
@@ -1444,7 +1444,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// Try to Shift+Tab
@@ -1495,7 +1495,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
// Verify that the first menu item is active
assertMenuLinkedWithMenuItem(items[0])
@@ -1589,7 +1589,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go down once
@@ -1637,7 +1637,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go down once
@@ -1679,7 +1679,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
})
)
@@ -1723,7 +1723,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
// ! ALERT: The LAST item should now be active
assertMenuLinkedWithMenuItem(items[2])
@@ -1821,7 +1821,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
})
)
@@ -1859,7 +1859,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
// We should not be able to go up (because those are disabled)
@@ -1909,7 +1909,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
// We should be able to go down once
@@ -2736,7 +2736,7 @@ describe('Mouse interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
})
)
@@ -91,8 +91,8 @@ let reducers: {
let activeItemIndex = calculateActiveIndex(action, {
resolveItems: () => state.items,
resolveActiveIndex: () => state.activeItemIndex,
resolveId: item => item.id,
resolveDisabled: item => item.dataRef.current.disabled,
resolveId: (item) => item.id,
resolveDisabled: (item) => item.dataRef.current.disabled,
})
if (state.searchQuery === '' && state.activeItemIndex === activeItemIndex) return state
@@ -109,7 +109,7 @@ let reducers: {
: state.items
let matchingItem = reOrderedItems.find(
item =>
(item) =>
item.dataRef.current.textValue?.startsWith(searchQuery) && !item.dataRef.current.disabled
)
@@ -139,7 +139,7 @@ let reducers: {
let nextItems = state.items.slice()
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)
@@ -196,7 +196,7 @@ export function Menu<TTag extends ElementType = typeof DEFAULT_MENU_TAG>(
let [{ menuState, itemsRef, buttonRef }, dispatch] = reducerBag
// Handle outside click
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
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 }), [
menuState,
])
let slot = useMemo<MenuRenderPropArg>(
() => ({ open: menuState === MenuStates.Open }),
[menuState]
)
return (
<MenuContext.Provider value={reducerBag}>
@@ -249,7 +250,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
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 id = `headlessui-menu-button-${useId()}`
@@ -307,9 +308,10 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
[dispatch, d, state, props.disabled]
)
let slot = useMemo<ButtonRenderPropArg>(() => ({ open: state.menuState === MenuStates.Open }), [
state,
])
let slot = useMemo<ButtonRenderPropArg>(
() => ({ open: state.menuState === MenuStates.Open }),
[state]
)
let passthroughProps = props
let propsWeControl = {
ref: buttonRef,
@@ -352,7 +354,7 @@ let Items = forwardRefWithAs(function Items<TTag extends ElementType = typeof DE
PropsForFeatures<typeof ItemsRenderFeatures>,
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 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 }), [
state,
])
let slot = useMemo<ItemsRenderPropArg>(
() => ({ open: state.menuState === MenuStates.Open }),
[state]
)
let propsWeControl = {
'aria-activedescendant':
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 [state, dispatch] = useMenuContext([Menu.name, Item.name].join('.'))
let [state, dispatch] = useMenuContext('Menu.Item')
let id = `headlessui-menu-item-${useId()}`
let active = state.activeItemIndex !== null ? state.items[state.activeItemIndex].id === id : false
@@ -23,7 +23,7 @@ jest.mock('../../hooks/use-id')
afterAll(() => jest.restoreAllMocks())
function nextFrame() {
return new Promise<void>(resolve => {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
resolve()
@@ -75,7 +75,7 @@ let reducers: {
action: Extract<Actions, { type: P }>
) => StateDefinition
} = {
[ActionTypes.TogglePopover]: state => ({
[ActionTypes.TogglePopover]: (state) => ({
...state,
popoverState: match(state.popoverState, {
[PopoverStates.Open]: PopoverStates.Closed,
@@ -217,7 +217,7 @@ export function Popover<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(
)
// Handle outside click
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
if (popoverState !== PopoverStates.Open) return
@@ -296,7 +296,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
props: Props<TTag, ButtonRenderPropArg, ButtonPropsWeControl>,
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 groupContext = usePopoverGroupContext()
@@ -308,7 +308,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
let buttonRef = useSyncRefs(
internalButtonRef,
ref,
isWithinPanel ? null : button => dispatch({ type: ActionTypes.SetButton, button })
isWithinPanel ? null : (button) => dispatch({ type: ActionTypes.SetButton, button })
)
let withinPanelButtonRef = useSyncRefs(internalButtonRef, ref)
@@ -517,7 +517,7 @@ let Overlay = forwardRefWithAs(function Overlay<
PropsForFeatures<typeof OverlayRenderFeatures>,
ref: Ref<HTMLDivElement>
) {
let [{ popoverState }, dispatch] = usePopoverContext([Popover.name, Overlay.name].join('.'))
let [{ popoverState }, dispatch] = usePopoverContext('Popover.Overlay')
let overlayRef = useSyncRefs(ref)
let id = `headlessui-popover-overlay-${useId()}`
@@ -539,9 +539,10 @@ let Overlay = forwardRefWithAs(function Overlay<
[dispatch]
)
let slot = useMemo<OverlayRenderPropArg>(() => ({ open: popoverState === PopoverStates.Open }), [
popoverState,
])
let slot = useMemo<OverlayRenderPropArg>(
() => ({ open: popoverState === PopoverStates.Open }),
[popoverState]
)
let propsWeControl = {
ref: overlayRef,
id,
@@ -580,11 +581,11 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
) {
let { focus = false, ...passthroughProps } = props
let [state, dispatch] = usePopoverContext([Popover.name, Panel.name].join('.'))
let { close } = usePopoverAPIContext([Popover.name, Panel.name].join('.'))
let [state, dispatch] = usePopoverContext('Popover.Panel')
let { close } = usePopoverAPIContext('Popover.Panel')
let internalPanelRef = useRef<HTMLDivElement | null>(null)
let panelRef = useSyncRefs(internalPanelRef, ref, panel => {
let panelRef = useSyncRefs(internalPanelRef, ref, (panel) => {
dispatch({ type: ActionTypes.SetPanel, panel })
})
@@ -639,7 +640,7 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
}, [focus, internalPanelRef, state.popoverState])
// Handle Tab / Shift+Tab focus positioning
useWindowEvent('keydown', event => {
useWindowEvent('keydown', (event) => {
if (state.popoverState !== PopoverStates.Open) return
if (!internalPanelRef.current) return
if (event.key !== Keys.Tab) return
@@ -665,7 +666,7 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
let nextElements = elements
.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
// 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(
(registerbag: PopoverRegisterBag) => {
setPopovers(existing => {
setPopovers((existing) => {
let idx = existing.indexOf(registerbag)
if (idx !== -1) {
let clone = existing.slice()
@@ -745,7 +746,7 @@ function Group<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
let registerPopover = useCallback(
(registerbag: PopoverRegisterBag) => {
setPopovers(existing => [...existing, registerbag])
setPopovers((existing) => [...existing, registerbag])
return () => unregisterPopover(registerbag)
},
[setPopovers, unregisterPopover]
@@ -757,7 +758,7 @@ function Group<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
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.
return popovers.some(bag => {
return popovers.some((bag) => {
return (
document.getElementById(bag.buttonId)?.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 (
<main id="parent">
<button id="a" onClick={() => setRenderA(v => !v)}>
<button id="a" onClick={() => setRenderA((v) => !v)}>
Toggle A
</button>
<button id="b" onClick={() => setRenderB(v => !v)}>
<button id="b" onClick={() => setRenderB((v) => !v)}>
Toggle B
</button>
@@ -151,21 +151,21 @@ it('should be possible to render multiple portals at the same time', async () =>
return (
<main id="parent">
<button id="a" onClick={() => setRenderA(v => !v)}>
<button id="a" onClick={() => setRenderA((v) => !v)}>
Toggle A
</button>
<button id="b" onClick={() => setRenderB(v => !v)}>
<button id="b" onClick={() => setRenderB((v) => !v)}>
Toggle B
</button>
<button id="c" onClick={() => setRenderC(v => !v)}>
<button id="c" onClick={() => setRenderC((v) => !v)}>
Toggle C
</button>
<button
id="double"
onClick={() => {
setRenderA(v => !v)
setRenderB(v => !v)
setRenderA((v) => !v)
setRenderB((v) => !v)
}}
>
Toggle A & B{' '}
@@ -231,10 +231,10 @@ it('should be possible to tamper with the modal root and restore correctly', asy
return (
<main id="parent">
<button id="a" onClick={() => setRenderA(v => !v)}>
<button id="a" onClick={() => setRenderA((v) => !v)}>
Toggle A
</button>
<button id="b" onClick={() => setRenderB(v => !v)}>
<button id="b" onClick={() => setRenderB((v) => !v)}>
Toggle B
</button>
@@ -113,7 +113,7 @@ describe('Rendering', () => {
return (
<>
<button onClick={() => setShowFirst(v => !v)}>Toggle</button>
<button onClick={() => setShowFirst((v) => !v)}>Toggle</button>
<RadioGroup value={active} onChange={setActive}>
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
{showFirst && <RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>}
@@ -145,7 +145,7 @@ describe('Rendering', () => {
let [disabled, setDisabled] = useState(true)
return (
<>
<button onClick={() => setDisabled(v => !v)}>Toggle</button>
<button onClick={() => setDisabled((v) => !v)}>Toggle</button>
<RadioGroup value={undefined} onChange={changeFn} disabled={disabled}>
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
<RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>
@@ -208,7 +208,7 @@ describe('Rendering', () => {
let [disabled, setDisabled] = useState(true)
return (
<>
<button onClick={() => setDisabled(v => !v)}>Toggle</button>
<button onClick={() => setDisabled((v) => !v)}>Toggle</button>
<RadioGroup value={undefined} onChange={changeFn}>
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
<RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>
@@ -745,7 +745,7 @@ describe('Keyboard interactions', () => {
<button>Before</button>
<RadioGroup
value={value}
onChange={v => {
onChange={(v) => {
setValue(v)
changeFn(v)
}}
@@ -815,7 +815,7 @@ describe('Mouse interactions', () => {
<button>Before</button>
<RadioGroup
value={value}
onChange={v => {
onChange={(v) => {
setValue(v)
changeFn(v)
}}
@@ -61,7 +61,7 @@ let reducers: {
},
[ActionTypes.UnregisterOption](state, action) {
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
options.splice(idx, 1)
return { ...state, options }
@@ -123,23 +123,23 @@ export function RadioGroup<
let firstOption = useMemo(
() =>
options.find(option => {
options.find((option) => {
if (option.propsRef.current.disabled) return false
return true
}),
[options]
)
let containsCheckedOption = useMemo(
() => options.some(option => option.propsRef.current.value === value),
() => options.some((option) => option.propsRef.current.value === value),
[options, value]
)
let triggerChange = useCallback(
nextValue => {
(nextValue) => {
if (disabled) return false
if (nextValue === value) return false
let nextOption = options.find(option => option.propsRef.current.value === nextValue)?.propsRef
.current
let nextOption = options.find((option) => option.propsRef.current.value === nextValue)
?.propsRef.current
if (nextOption?.disabled) return false
onChange(nextValue)
@@ -166,8 +166,8 @@ export function RadioGroup<
if (!container) return
let all = options
.filter(option => option.propsRef.current.disabled === false)
.map(radio => radio.element.current) as HTMLElement[]
.filter((option) => option.propsRef.current.disabled === false)
.map((radio) => radio.element.current) as HTMLElement[]
switch (event.key) {
case Keys.ArrowLeft:
@@ -180,7 +180,7 @@ export function RadioGroup<
if (result === FocusResult.Success) {
let activeOption = options.find(
option => option.element.current === document.activeElement
(option) => option.element.current === document.activeElement
)
if (activeOption) triggerChange(activeOption.propsRef.current.value)
}
@@ -197,7 +197,7 @@ export function RadioGroup<
if (result === FocusResult.Success) {
let activeOption = options.find(
option => option.element.current === document.activeElement
(option) => option.element.current === document.activeElement
)
if (activeOption) triggerChange(activeOption.propsRef.current.value)
}
@@ -210,7 +210,7 @@ export function RadioGroup<
event.stopPropagation()
let activeOption = options.find(
option => option.element.current === document.activeElement
(option) => option.element.current === document.activeElement
)
if (activeOption) triggerChange(activeOption.propsRef.current.value)
}
@@ -322,14 +322,12 @@ function Option<
firstOption,
containsCheckedOption,
value: radioGroupValue,
} = useRadioGroupContext([RadioGroup.name, Option.name].join('.'))
} = useRadioGroupContext('RadioGroup.Option')
useIsoMorphicEffect(() => registerOption({ id, element: optionRef, propsRef }), [
id,
registerOption,
optionRef,
props,
])
useIsoMorphicEffect(
() => registerOption({ id, element: optionRef, propsRef }),
[id, registerOption, optionRef, props]
)
let handleClick = useCallback(() => {
if (!change(value)) return
@@ -214,7 +214,7 @@ describe('Keyboard interactions', () => {
return (
<Switch
checked={state}
onChange={value => {
onChange={(value) => {
setState(value)
handleChange(value)
}}
@@ -297,7 +297,7 @@ describe('Mouse interactions', () => {
return (
<Switch
checked={state}
onChange={value => {
onChange={(value) => {
setState(value)
handleChange(value)
}}
@@ -331,7 +331,7 @@ describe('Mouse interactions', () => {
<Switch.Group>
<Switch
checked={state}
onChange={value => {
onChange={(value) => {
setState(value)
handleChange(value)
}}
@@ -373,7 +373,7 @@ describe('Mouse interactions', () => {
<Switch.Group>
<Switch
checked={state}
onChange={value => {
onChange={(value) => {
setState(value)
handleChange(value)
}}
@@ -81,7 +81,7 @@ describe('Rendering', () => {
return (
<>
<button onClick={() => setHide(v => !v)}>toggle</button>
<button onClick={() => setHide((v) => !v)}>toggle</button>
<Tab.Group>
<Tab.List>
<Tab>Tab 1</Tab>
@@ -118,7 +118,7 @@ describe('Rendering', () => {
it('should expose the `selectedIndex` on the `Tab.Group` component', async () => {
render(
<Tab.Group>
{data => (
{(data) => (
<>
<pre id="exposed">{JSON.stringify(data)}</pre>
@@ -153,7 +153,7 @@ describe('Rendering', () => {
render(
<Tab.Group>
<Tab.List>
{data => (
{(data) => (
<>
<pre id="exposed">{JSON.stringify(data)}</pre>
<Tab>Tab 1</Tab>
@@ -192,7 +192,7 @@ describe('Rendering', () => {
</Tab.List>
<Tab.Panels>
{data => (
{(data) => (
<>
<pre id="exposed">{JSON.stringify(data)}</pre>
<Tab.Panel>Content 1</Tab.Panel>
@@ -220,7 +220,7 @@ describe('Rendering', () => {
<Tab.Group>
<Tab.List>
<Tab>
{data => (
{(data) => (
<>
<pre data-tab={0}>{JSON.stringify(data)}</pre>
<span>Tab 1</span>
@@ -228,7 +228,7 @@ describe('Rendering', () => {
)}
</Tab>
<Tab>
{data => (
{(data) => (
<>
<pre data-tab={1}>{JSON.stringify(data)}</pre>
<span>Tab 2</span>
@@ -236,7 +236,7 @@ describe('Rendering', () => {
)}
</Tab>
<Tab>
{data => (
{(data) => (
<>
<pre data-tab={2}>{JSON.stringify(data)}</pre>
<span>Tab 3</span>
@@ -287,7 +287,7 @@ describe('Rendering', () => {
<Tab.Panels>
<Tab.Panel unmount={false}>
{data => (
{(data) => (
<>
<pre data-panel={0}>{JSON.stringify(data)}</pre>
<span>Content 1</span>
@@ -295,7 +295,7 @@ describe('Rendering', () => {
)}
</Tab.Panel>
<Tab.Panel unmount={false}>
{data => (
{(data) => (
<>
<pre data-panel={1}>{JSON.stringify(data)}</pre>
<span>Content 2</span>
@@ -303,7 +303,7 @@ describe('Rendering', () => {
)}
</Tab.Panel>
<Tab.Panel unmount={false}>
{data => (
{(data) => (
<>
<pre data-panel={2}>{JSON.stringify(data)}</pre>
<span>Content 3</span>
@@ -514,7 +514,7 @@ describe('Rendering', () => {
<>
<Tab.Group
selectedIndex={selectedIndex}
onChange={value => {
onChange={(value) => {
setSelectedIndex(value)
handleChange(value)
}}
@@ -533,7 +533,7 @@ describe('Rendering', () => {
</Tab.Group>
<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] }
},
[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) {
if (state.panels.includes(action.panel)) return state
return { ...state, panels: [...state.panels, action.panel] }
},
[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) {
return { ...state }
@@ -171,8 +171,8 @@ function Tabs<TTag extends ElementType = typeof DEFAULT_TABS_TAG>(
if (state.tabs.length <= 0) return
if (selectedIndex === null && state.selectedIndex !== null) return
let tabs = state.tabs.map(tab => tab.current).filter(Boolean) as HTMLElement[]
let focusableTabs = tabs.filter(tab => !tab.hasAttribute('disabled'))
let tabs = state.tabs.map((tab) => tab.current).filter(Boolean) as HTMLElement[]
let focusableTabs = tabs.filter((tab) => !tab.hasAttribute('disabled'))
let indexToSet = selectedIndex ?? defaultIndex
@@ -194,7 +194,7 @@ function Tabs<TTag extends ElementType = typeof DEFAULT_TABS_TAG>(
let before = tabs.slice(0, 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
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>(
props: Props<TTag, ListRenderPropArg, ListPropsWeControl> & {}
) {
let [{ selectedIndex, orientation }] = useTabsContext([Tab.name, List.name].join('.'))
let [{ selectedIndex, orientation }] = useTabsContext('Tab.List')
let slot = { selectedIndex }
let propsWeControl = {
@@ -275,13 +275,11 @@ export function Tab<TTag extends ElementType = typeof DEFAULT_TAB_TAG>(
) {
let id = `headlessui-tabs-tab-${useId()}`
let [
{ selectedIndex, tabs, panels, orientation, activation },
{ dispatch, change },
] = useTabsContext(Tab.name)
let [{ selectedIndex, tabs, panels, orientation, activation }, { dispatch, change }] =
useTabsContext(Tab.name)
let internalTabRef = useRef<HTMLElement>(null)
let tabRef = useSyncRefs(internalTabRef, element => {
let tabRef = useSyncRefs(internalTabRef, (element) => {
if (!element) return
dispatch({ type: ActionTypes.ForceRerender })
})
@@ -296,7 +294,7 @@ export function Tab<TTag extends ElementType = typeof DEFAULT_TAB_TAG>(
let handleKeyDown = useCallback(
(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) {
event.preventDefault()
@@ -380,7 +378,7 @@ interface PanelsRenderPropArg {
function Panels<TTag extends ElementType = typeof DEFAULT_PANELS_TAG>(
props: Props<TTag, PanelsRenderPropArg>
) {
let [{ selectedIndex }] = useTabsContext([Tab.name, Panels.name].join('.'))
let [{ selectedIndex }] = useTabsContext('Tab.Panels')
let slot = useMemo(() => ({ selectedIndex }), [selectedIndex])
@@ -405,13 +403,11 @@ function Panel<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
props: Props<TTag, PanelRenderPropArg, PanelPropsWeControl> &
PropsForFeatures<typeof PanelRenderFeatures>
) {
let [{ selectedIndex, tabs, panels }, { dispatch }] = useTabsContext(
[Tab.name, Panel.name].join('.')
)
let [{ selectedIndex, tabs, panels }, { dispatch }] = useTabsContext('Tab.Panel')
let id = `headlessui-tabs-panel-${useId()}`
let internalPanelRef = useRef<HTMLElement>(null)
let panelRef = useSyncRefs(internalPanelRef, element => {
let panelRef = useSyncRefs(internalPanelRef, (element) => {
if (!element) return
dispatch({ type: ActionTypes.ForceRerender })
})
@@ -26,8 +26,6 @@ it(
expect(() => {
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>
<div className="hello">Children</div>
</Transition>
@@ -445,7 +443,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -488,14 +486,15 @@ describe('Transitions', () => {
return (
<>
<style>{`.enter { transition-duration: ${enterDuration /
1000}s; } .from { opacity: 0%; } .to { opacity: 100%; }`}</style>
<style>{`.enter { transition-duration: ${
enterDuration / 1000
}s; } .from { opacity: 0%; } .to { opacity: 100%; }`}</style>
<Transition show={show} enter="enter" enterFrom="from" enterTo="to">
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -538,14 +537,15 @@ describe('Transitions', () => {
return (
<>
<style>{`.enter { transition-duration: ${enterDuration /
1000}s; } .from { opacity: 0%; } .to { opacity: 100%; }`}</style>
<style>{`.enter { transition-duration: ${
enterDuration / 1000
}s; } .from { opacity: 0%; } .to { opacity: 100%; }`}</style>
<Transition show={show} unmount={false} enter="enter" enterFrom="from" enterTo="to">
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -591,7 +591,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -642,7 +642,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -696,7 +696,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -757,7 +757,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -843,7 +843,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -943,7 +943,7 @@ describe('Transitions', () => {
</Transition.Child>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -1027,7 +1027,7 @@ describe('Transitions', () => {
</Transition.Child>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -1138,7 +1138,7 @@ describe('Events', () => {
<span>Hello!</span>
</Transition>
<button data-testid="toggle" onClick={() => setShow(v => !v)}>
<button data-testid="toggle" onClick={() => setShow((v) => !v)}>
Toggle
</button>
</>
@@ -28,9 +28,10 @@ import { useServerHandoffComplete } from '../../hooks/use-server-handoff-complet
type ID = ReturnType<typeof useId>
function useSplitClasses(classes: string = '') {
return useMemo(() => classes.split(' ').filter(className => className.trim().length > 1), [
classes,
])
return useMemo(
() => classes.split(' ').filter((className) => className.trim().length > 1),
[classes]
)
}
interface TransitionContextValues {
@@ -287,23 +288,37 @@ function TransitionChild<TTag extends ElementType = typeof DEFAULT_TRANSITION_CH
if (!show) events.current.beforeLeave()
return show
? transition(node, enterClasses, enterFromClasses, enterToClasses, enteredClasses, reason => {
isTransitioning.current = false
if (reason === Reason.Finished) events.current.afterEnter()
})
: 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()
? transition(
node,
enterClasses,
enterFromClasses,
enterToClasses,
enteredClasses,
(reason) => {
isTransitioning.current = false
if (reason === Reason.Finished) events.current.afterEnter()
}
})
)
: 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,
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.')
}
@@ -17,7 +17,7 @@ it('should be possible to transition', async () => {
d.add(
reportChanges(
() => document.body.innerHTML,
content => {
(content) => {
snapshots.push({
content,
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)
})
await new Promise(resolve => d.nextFrame(resolve))
await new Promise((resolve) => d.nextFrame(resolve))
// Initial render:
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(
reportChanges(
() => document.body.innerHTML,
content => {
(content) => {
snapshots.push({
content,
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)
})
await new Promise(resolve => d.nextFrame(resolve))
await new Promise((resolve) => d.nextFrame(resolve))
expect(reason).toBe(Reason.Finished)
// Initial render:
@@ -118,7 +118,7 @@ it('should keep the delay time into account', async () => {
d.add(
reportChanges(
() => document.body.innerHTML,
content => {
(content) => {
snapshots.push({
content,
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)
})
await new Promise(resolve => d.nextFrame(resolve))
await new Promise((resolve) => d.nextFrame(resolve))
expect(reason).toBe(Reason.Finished)
let estimatedDuration = Number(
@@ -161,7 +161,7 @@ it('should be possible to cancel a transition at any time', async () => {
d.add(
reportChanges(
() => document.body.innerHTML,
content => {
(content) => {
let recordedAt = process.hrtime.bigint()
let total = snapshots.length
@@ -178,16 +178,16 @@ it('should be possible to cancel a transition at any time', async () => {
expect.assertions(2)
// 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)
})
// Wait for a bit
await new Promise(resolve => setTimeout(resolve, 20))
await new Promise((resolve) => setTimeout(resolve, 20))
// Cancel the transition
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.
let { transitionDuration, transitionDelay } = getComputedStyle(node)
let [durationMs, delaysMs] = [transitionDuration, transitionDelay].map(value => {
let [durationMs, delaysMs] = [transitionDuration, transitionDelay].map((value) => {
let [resolvedValue = 0] = value
.split(',')
// Remove falsy we can't work with
.filter(Boolean)
// 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)
return resolvedValue
@@ -74,7 +74,7 @@ export function transition(
addClasses(node, ...to)
d.add(
waitForTransition(node, reason => {
waitForTransition(node, (reason) => {
removeClasses(node, ...to, ...base)
addClasses(node, ...entered)
return _done(reason)
@@ -3,10 +3,10 @@ import { useState, useCallback } from 'react'
export function useFlags(initialFlags = 0) {
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 removeFlag = useCallback((flag: number) => setFlags(flags => flags & ~flag), [setFlags])
let toggleFlag = 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])
return { addFlag, hasFlag, removeFlag, toggleFlag }
}
@@ -97,7 +97,7 @@ export function useFocusTrap(
}, [container, initialFocus, featuresInitialFocus])
// Handle `Tab` & `Shift+Tab` keyboard events
useWindowEvent('keydown', event => {
useWindowEvent('keydown', (event) => {
if (!(features & Features.TabLock)) return
if (!container.current) return
@@ -118,7 +118,7 @@ export function useFocusTrap(
// Prevent programmatically escaping the container
useWindowEvent(
'focus',
event => {
(event) => {
if (!(features & Features.FocusLock)) return
let allContainers = new Set(containers?.current)
@@ -17,7 +17,7 @@ it('should be possible to inert other elements', async () => {
return (
<div ref={ref} id="main">
<button onClick={() => setEnabled(v => !v)}>toggle</button>
<button onClick={() => setEnabled((v) => !v)}>toggle</button>
</div>
)
}
@@ -61,7 +61,7 @@ it('should restore inert elements, when all useInertOthers calls are disabled',
return (
<div ref={ref} id={id}>
<button onClick={() => setEnabled(v => !v)}>{toggle}</button>
<button onClick={() => setEnabled((v) => !v)}>{toggle}</button>
</div>
)
}
@@ -136,7 +136,7 @@ it('should restore inert elements, when all useInertOthers calls are disabled (i
return (
<div id={`parent-${id}`}>
<div ref={ref} id={id}>
<button onClick={() => setEnabled(v => !v)}>{toggle}</button>
<button onClick={() => setEnabled((v) => !v)}>{toggle}</button>
</div>
</div>
)
@@ -221,7 +221,7 @@ it('should handle inert others correctly when 2 useInertOthers are used in a sha
return (
<div ref={ref} id={id}>
<button onClick={() => setEnabled(v => !v)}>{toggle}</button>
<button onClick={() => setEnabled((v) => !v)}>{toggle}</button>
</div>
)
}
@@ -42,7 +42,7 @@ export function useInertOthers<TElement extends HTMLElement>(
}
// Collect direct children of the body
document.querySelectorAll('body > *').forEach(child => {
document.querySelectorAll('body > *').forEach((child) => {
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
// Skip the interactables, and the parents of the interactables
@@ -71,7 +71,7 @@ export function useInertOthers<TElement extends HTMLElement>(
// will become inert as well.
if (interactables.size > 0) {
// Collect direct children of the body
document.querySelectorAll('body > *').forEach(child => {
document.querySelectorAll('body > *').forEach((child) => {
if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements
// Skip already inert parents
@@ -35,6 +35,7 @@ export function useTreeWalker({
let walk = walkRef.current
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)
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')) {
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
}
@@ -1612,7 +1612,7 @@ export function assertTabs(
expect(list).toHaveAttribute('aria-orientation', orientation)
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) {
expect(tab).toHaveAttribute('id')
@@ -17,7 +17,7 @@ function redentSnapshot(input: string) {
return input
.split('\n')
.map(line =>
.map((line) =>
line.trim() === '---' ? line : line.replace(replacer, (_, sign, rest) => `${sign} ${rest}`)
)
.join('\n')
@@ -69,13 +69,13 @@ export async function executeTimeline(
.reduce((total, current) => total + current, 0)
// 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
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
await new Promise(resolve => d.nextFrame(resolve))
await new Promise((resolve) => d.nextFrame(resolve))
}, Promise.resolve())
if (snapshots.length <= 0) {
@@ -127,7 +127,7 @@ export async function executeTimeline(
.replace(/Snapshot Diff:\n/g, '')
)
.split('\n')
.map(line => ` ${line}`)
.map((line) => ` ${line}`)
.join('\n')}`
})
.filter(Boolean)
@@ -175,7 +175,7 @@ describe('Keyboard', () => {
await type([key(input)])
let expected = result.map(e => event(e))
let expected = result.map((e) => event(e))
expect(fired.length).toEqual(result.length)
@@ -36,7 +36,7 @@ export function shift(event: 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(() => {
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]
for (let action of actions) {
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, {
type: action.name,
@@ -344,8 +344,8 @@ let focusableSelector = [
? // TODO: Remove this once JSDOM fixes the issue where an element that is
// "hidden" can be the document.activeElement, because this is not possible
// in real browsers.
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: selector => `${selector}:not([tabindex='-1'])`
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: (selector) => `${selector}:not([tabindex='-1'])`
)
.join(',')
@@ -5,10 +5,10 @@ type FunctionPropertyNames<T> = {
export function suppressConsoleLogs<T extends unknown[]>(
cb: (...args: T) => unknown,
type: FunctionPropertyNames<typeof global.console> = 'error'
type: FunctionPropertyNames<typeof globalThis.console> = 'error'
) {
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) => {
Promise.resolve(cb(...args)).then(resolve, reject)
@@ -40,7 +40,7 @@ export function calculateActiveIndex<TItem>(
let nextActiveIndex = (() => {
switch (action.focus) {
case Focus.First:
return items.findIndex(item => !resolvers.resolveDisabled(item))
return items.findIndex((item) => !resolvers.resolveDisabled(item))
case Focus.Previous: {
let idx = items
@@ -64,13 +64,13 @@ export function calculateActiveIndex<TItem>(
let idx = items
.slice()
.reverse()
.findIndex(item => !resolvers.resolveDisabled(item))
.findIndex((item) => !resolvers.resolveDisabled(item))
if (idx === -1) return idx
return items.length - 1 - idx
}
case Focus.Specific:
return items.findIndex(item => resolvers.resolveId(item) === action.id)
return items.findIndex((item) => resolvers.resolveId(item) === action.id)
case Focus.Nothing:
return null
@@ -18,8 +18,8 @@ let focusableSelector = [
? // TODO: Remove this once JSDOM fixes the issue where an element that is
// "hidden" can be the document.activeElement, because this is not possible
// in real browsers.
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: selector => `${selector}:not([tabindex='-1'])`
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: (selector) => `${selector}:not([tabindex='-1'])`
)
.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(
lookup
)
.map(key => `"${key}"`)
.map((key) => `"${key}"`)
.join(', ')}.`
)
if (Error.captureStackTrace) Error.captureStackTrace(error, match)
@@ -45,7 +45,7 @@ describe('Default functionality', () => {
testRender(
<Dummy>
{data => {
{(data) => {
expect(data).toBe(slot)
return <span>Contents</span>
@@ -102,10 +102,12 @@ function _render<TTag extends ElementType, TSlot>(
tag: ElementType,
name: string
) {
let { as: Component = tag, children, refName = 'ref', ...passThroughProps } = omit(props, [
'unmount',
'static',
])
let {
as: Component = tag,
children,
refName = 'ref',
...passThroughProps
} = omit(props, ['unmount', 'static'])
// This allows us to use `<HeadlessUIComponent as={MyComponent} refName="innerRef" />`
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".`,
`However we need to passthrough the following props:`,
Object.keys(passThroughProps)
.map(line => ` - ${line}`)
.map((line) => ` - ${line}`)
.join('\n'),
'',
'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".',
'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')
)
@@ -211,7 +213,7 @@ function mergeEventFunctions(
export function forwardRefWithAs<T extends { name: string; displayName?: string }>(
component: T
): 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,
})
}
+2 -3
View File
@@ -21,13 +21,12 @@
},
"jsx": "preserve",
"esModuleInterop": true,
"target": "es5",
"target": "ESNext",
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"exclude": ["node_modules"]
"exclude": ["node_modules", "**/*.test.tsx?"]
}
@@ -1,7 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react"
}
}
-10
View File
@@ -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
View File
@@ -0,0 +1,9 @@
export {}
declare global {
namespace jest {
interface Matchers<R> {
toBeWithinRenderFrame(actual: number): R
}
}
}
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
rules: {
'react-hooks/rules-of-hooks': 'off',
'react-hooks/exhaustive-deps': 'off',
},
}
+7
View File
@@ -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')
}
+16 -5
View File
@@ -4,12 +4,21 @@
"description": "A set of completely unstyled, fully accessible UI components for Vue 3, designed to integrate beautifully with Tailwind CSS.",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"module": "dist/index.esm.js",
"module": "dist/headlessui.esm.js",
"license": "MIT",
"files": [
"README.md",
"dist"
],
"exports": {
".": {
"import": {
"default": "./dist/headlessui.esm.js"
},
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"sideEffects": false,
"engines": {
"node": ">=10"
@@ -27,14 +36,16 @@
"build": "../../scripts/build.sh",
"watch": "../../scripts/watch.sh",
"test": "../../scripts/test.sh",
"lint": "../../scripts/lint.sh"
"lint": "../../scripts/lint.sh",
"playground": "yarn workspace playground-vue dev",
"clean": "rimraf ./dist"
},
"peerDependencies": {
"vue": "^3.0.0"
},
"devDependencies": {
"@testing-library/vue": "^5.1.0",
"@vue/test-utils": "^2.0.0-beta.7",
"vue": "3.0.7"
"@testing-library/vue": "^5.8.2",
"@vue/test-utils": "^2.0.0-rc.18",
"vue": "^3.2.27"
}
}
@@ -1 +0,0 @@
module.exports = require('../../postcss.config.js')
@@ -66,7 +66,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function nextFrame() {
return new Promise<void>(resolve => {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
resolve()
@@ -95,9 +95,9 @@ function renderTemplate(input: string | Partial<DefineComponent>) {
return render(
defineComponent(
(Object.assign({}, input, {
Object.assign({}, input, {
components: { ...defaultComponents, ...input.components },
}) as unknown) as DefineComponent
}) as Parameters<typeof defineComponent>[0]
)
)
}
@@ -491,9 +491,7 @@ describe('Rendering', () => {
template: html`
<Combobox v-model="value">
<ComboboxInput />
<ComboboxButton type="submit">
Trigger
</ComboboxButton>
<ComboboxButton type="submit"> Trigger </ComboboxButton>
</Combobox>
`,
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"',
suppressConsoleLogs(async () => {
let CustomButton = defineComponent({
setup: props => () => h('button', { ...props }),
setup: (props) => () => h('button', { ...props }),
})
renderTemplate({
template: html`
<Combobox v-model="value">
<ComboboxInput />
<ComboboxButton :as="CustomButton">
Trigger
</ComboboxButton>
<ComboboxButton :as="CustomButton"> Trigger </ComboboxButton>
</Combobox>
`,
setup: () => ({
@@ -535,9 +531,7 @@ describe('Rendering', () => {
template: html`
<Combobox v-model="value">
<ComboboxInput />
<ComboboxButton as="div">
Trigger
</ComboboxButton>
<ComboboxButton as="div"> Trigger </ComboboxButton>
</Combobox>
`,
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"',
suppressConsoleLogs(async () => {
let CustomButton = defineComponent({
setup: props => () => h('div', props),
setup: (props) => () => h('div', props),
})
renderTemplate({
template: html`
<Combobox v-model="value">
<ComboboxInput />
<ComboboxButton :as="CustomButton">
Trigger
</ComboboxButton>
<ComboboxButton :as="CustomButton"> Trigger </ComboboxButton>
</Combobox>
`,
setup: () => ({
@@ -765,15 +757,9 @@ describe('Rendering composition', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption as="button" value="a">
Option A
</ComboboxOption>
<ComboboxOption as="button" value="b">
Option B
</ComboboxOption>
<ComboboxOption as="button" value="c">
Option C
</ComboboxOption>
<ComboboxOption as="button" value="a"> Option A </ComboboxOption>
<ComboboxOption as="button" value="b"> Option B </ComboboxOption>
<ComboboxOption as="button" value="c"> Option C </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -790,7 +776,7 @@ describe('Rendering composition', () => {
await click(getComboboxButton())
// 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 />
<ComboboxButton>Trigger</ComboboxButton>
<OpenClosedWrite :open="true">
<ComboboxOptions v-slot="data">
{{JSON.stringify(data)}}
</ComboboxOptions>
<ComboboxOptions v-slot="data"> {{JSON.stringify(data)}} </ComboboxOptions>
</OpenClosedWrite>
</Combobox>
`,
@@ -854,9 +838,7 @@ describe('Composition', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<OpenClosedWrite :open="false">
<ComboboxOptions v-slot="data">
{{JSON.stringify(data)}}
</ComboboxOptions>
<ComboboxOptions v-slot="data"> {{JSON.stringify(data)}} </ComboboxOptions>
</OpenClosedWrite>
</Combobox>
`,
@@ -966,7 +948,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option, { selected: false }))
options.forEach((option) => assertComboboxOption(option, { selected: false }))
assertNoActiveComboboxOption()
assertNoSelectedComboboxOption()
@@ -1274,7 +1256,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
})
)
@@ -1408,15 +1390,9 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -1533,7 +1509,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// Verify that the first combobox option is active
assertNoActiveComboboxOption()
@@ -1701,7 +1677,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// Verify that the first combobox option is active
assertNoActiveComboboxOption()
@@ -1869,7 +1845,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveComboboxOption(options[2])
@@ -2002,12 +1978,8 @@ describe('Keyboard interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="a">Option A</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -2029,7 +2001,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[0])
})
)
@@ -2079,7 +2051,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveComboboxOption(options[2])
@@ -2213,12 +2185,8 @@ describe('Keyboard interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="a">Option A</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -2240,7 +2208,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[0])
})
)
@@ -2496,7 +2464,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// Verify that the first combobox option is active
assertNoActiveComboboxOption()
@@ -2649,7 +2617,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// We should be able to go down once
@@ -2679,9 +2647,7 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption value="b">Option B</ComboboxOption>
<ComboboxOption value="c">Option C</ComboboxOption>
</ComboboxOptions>
@@ -2702,7 +2668,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// We should be able to go down once
@@ -2720,12 +2686,8 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption value="c">Option C</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -2745,7 +2707,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// Open combobox
@@ -2799,7 +2761,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// Verify that the first combobox option is active
assertNoActiveComboboxOption()
@@ -2968,7 +2930,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// We should be able to go down once
@@ -2998,9 +2960,7 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption value="b">Option B</ComboboxOption>
<ComboboxOption value="c">Option C</ComboboxOption>
</ComboboxOptions>
@@ -3021,7 +2981,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// We should be able to go down once
@@ -3039,12 +2999,8 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption value="c">Option C</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -3064,7 +3020,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// Open combobox
@@ -3117,7 +3073,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveComboboxOption(options[2])
@@ -3250,12 +3206,8 @@ describe('Keyboard interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="a">Option A</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -3277,7 +3229,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[0])
})
)
@@ -3291,12 +3243,8 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption value="c">Option C</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -3316,7 +3264,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertNoActiveComboboxOption()
// Going up or down should select the single available option
@@ -3374,7 +3322,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[2])
// We should be able to go down once
@@ -3436,7 +3384,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveComboboxOption(options[2])
@@ -3570,12 +3518,8 @@ describe('Keyboard interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="a">Option A</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -3597,7 +3541,7 @@ describe('Keyboard interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
assertActiveComboboxOption(options[0])
})
)
@@ -3647,12 +3591,8 @@ describe('Keyboard interactions', () => {
<ComboboxOptions>
<ComboboxOption value="a">Option A</ComboboxOption>
<ComboboxOption value="b">Option B</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="d">
Option D
</ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -3683,15 +3623,9 @@ describe('Keyboard interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="a">Option A</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="d">
Option D
</ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -3721,18 +3655,10 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="d">
Option D
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -3797,12 +3723,8 @@ describe('Keyboard interactions', () => {
<ComboboxOptions>
<ComboboxOption value="a">Option A</ComboboxOption>
<ComboboxOption value="b">Option B</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="d">
Option D
</ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -3836,15 +3758,9 @@ describe('Keyboard interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="a">Option A</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="d">
Option D
</ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -3874,18 +3790,10 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="d">
Option D
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -3951,12 +3859,8 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption value="c">Option C</ComboboxOption>
<ComboboxOption value="d">Option D</ComboboxOption>
</ComboboxOptions>
@@ -3990,15 +3894,9 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption value="d">Option D</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -4029,18 +3927,10 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="d">
Option D
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -4106,12 +3996,8 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption value="c">Option C</ComboboxOption>
<ComboboxOption value="d">Option D</ComboboxOption>
</ComboboxOptions>
@@ -4145,15 +4031,9 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption value="d">Option D</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -4184,18 +4064,10 @@ describe('Keyboard interactions', () => {
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption disabled value="a">
Option A
</ComboboxOption>
<ComboboxOption disabled value="b">
Option B
</ComboboxOption>
<ComboboxOption disabled value="c">
Option C
</ComboboxOption>
<ComboboxOption disabled value="d">
Option D
</ComboboxOption>
<ComboboxOption disabled value="a"> Option A </ComboboxOption>
<ComboboxOption disabled value="b"> Option B </ComboboxOption>
<ComboboxOption disabled value="c"> Option C </ComboboxOption>
<ComboboxOption disabled value="d"> Option D </ComboboxOption>
</ComboboxOptions>
</Combobox>
`,
@@ -4250,7 +4122,7 @@ describe('Keyboard interactions', () => {
let filteredPeople = computed(() => {
return query.value === ''
? props.people
: props.people.filter(person =>
: props.people.filter((person) =>
person.name.toLowerCase().includes(query.value.toLowerCase())
)
})
@@ -4586,7 +4458,7 @@ describe('Mouse interactions', () => {
// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertComboboxOption(option))
options.forEach((option) => assertComboboxOption(option))
})
)
@@ -5036,9 +4908,7 @@ describe('Mouse interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="alice">alice</ComboboxOption>
<ComboboxOption disabled value="bob">
bob
</ComboboxOption>
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
<ComboboxOption value="charlie">charlie</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -5066,9 +4936,7 @@ describe('Mouse interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="alice">alice</ComboboxOption>
<ComboboxOption disabled value="bob">
bob
</ComboboxOption>
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
<ComboboxOption value="charlie">charlie</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -5145,9 +5013,7 @@ describe('Mouse interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="alice">alice</ComboboxOption>
<ComboboxOption disabled value="bob">
bob
</ComboboxOption>
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
<ComboboxOption value="charlie">charlie</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -5227,9 +5093,7 @@ describe('Mouse interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="alice">alice</ComboboxOption>
<ComboboxOption disabled value="bob">
bob
</ComboboxOption>
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
<ComboboxOption value="charlie">charlie</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -5309,9 +5173,7 @@ describe('Mouse interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="alice">alice</ComboboxOption>
<ComboboxOption disabled value="bob">
bob
</ComboboxOption>
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
<ComboboxOption value="charlie">charlie</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -131,8 +131,8 @@ export let Combobox = defineComponent({
{
resolveItems: () => options.value,
resolveActiveIndex: () => activeOptionIndex.value,
resolveId: option => option.id,
resolveDisabled: option => option.dataRef.disabled,
resolveId: (option) => option.id,
resolveDisabled: (option) => option.dataRef.disabled,
}
)
@@ -152,7 +152,7 @@ export let Combobox = defineComponent({
}
},
selectOption(id: string) {
let option = options.value.find(item => item.id === id)
let option = options.value.find((item) => item.id === id)
if (!option) return
let { dataRef } = option
@@ -193,7 +193,7 @@ export let Combobox = defineComponent({
let nextOptions = options.value.slice()
let currentActiveOption =
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)
options.value = nextOptions
activeOptionIndex.value = (() => {
@@ -207,7 +207,7 @@ export let Combobox = defineComponent({
},
}
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
let active = document.activeElement
@@ -8,7 +8,8 @@ import { html } from '../../test-utils/html'
import { click } from '../../test-utils/interactions'
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()
return prettier.format(contents, { parser: 'babel' })
}
@@ -22,59 +23,47 @@ beforeAll(() => {
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 () => {
let { container } = renderTemplate({
render() {
return h('div', [h('div', { 'aria-describedby': this.describedby }, ['No description'])])
},
setup() {
let describedby = useDescriptions()
return { describedby }
},
})
let { container } = render(
defineComponent({
components: { Description },
render() {
return h('div', [h('div', { 'aria-describedby': this.describedby }, ['No description'])])
},
setup() {
let describedby = useDescriptions()
return { describedby }
},
})
)
expect(format(container.firstElementChild)).toEqual(
format(html`
<div>
<div>
No description
</div>
<div>No description</div>
</div>
`)
)
})
it('should be possible to use useDescriptions and a single Description, and have them linked', async () => {
let { container } = renderTemplate({
render() {
return h('div', [
h('div', { 'aria-describedby': this.describedby }, [
h(Description, () => 'I am a description'),
h('span', 'Contents'),
]),
])
},
setup() {
let describedby = useDescriptions()
return { describedby }
},
})
let { container } = render(
defineComponent({
components: { Description },
render() {
return h('div', [
h('div', { 'aria-describedby': this.describedby }, [
h(Description, () => 'I am a description'),
h('span', 'Contents'),
]),
])
},
setup() {
let describedby = useDescriptions()
return { describedby }
},
})
)
await new Promise<void>(nextTick)
@@ -82,12 +71,8 @@ it('should be possible to use useDescriptions and a single Description, and have
format(html`
<div>
<div aria-describedby="headlessui-description-1">
<p id="headlessui-description-1">
I am a description
</p>
<span>
Contents
</span>
<p id="headlessui-description-1">I am a description</p>
<span>Contents</span>
</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 () => {
let { container } = renderTemplate({
render() {
return h('div', [
h('div', { 'aria-describedby': this.describedby }, [
h(Description, () => 'I am a description'),
h('span', 'Contents'),
h(Description, () => 'I am also a description'),
]),
])
},
setup() {
let describedby = useDescriptions()
return { describedby }
},
})
let { container } = render(
defineComponent({
components: { Description },
render() {
return h('div', [
h('div', { 'aria-describedby': this.describedby }, [
h(Description, () => 'I am a description'),
h('span', 'Contents'),
h(Description, () => 'I am also a description'),
]),
])
},
setup() {
let describedby = useDescriptions()
return { describedby }
},
})
)
await new Promise<void>(nextTick)
@@ -117,15 +105,9 @@ it('should be possible to use useDescriptions and multiple Description component
format(html`
<div>
<div aria-describedby="headlessui-description-1 headlessui-description-2">
<p id="headlessui-description-1">
I am a description
</p>
<span>
Contents
</span>
<p id="headlessui-description-2">
I am also a description
</p>
<p id="headlessui-description-1">I am a description</p>
<span>Contents</span>
<p id="headlessui-description-2">I am also a description</p>
</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 () => {
let { container } = renderTemplate({
render() {
return h('div', [
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 } })
return { count, describedby }
},
})
let { container } = render(
defineComponent({
components: { Description },
render() {
return h('div', [
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 } })
return { count, describedby }
},
})
)
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`
<div>
<div aria-describedby="headlessui-description-1">
<p data-count="0" id="headlessui-description-1">
I am a description
</p>
<p data-count="0" id="headlessui-description-1">I am a description</p>
<button>+1</button>
</div>
</div>
@@ -170,9 +153,7 @@ it('should be possible to update a prop from the parent and it should reflect in
format(html`
<div>
<div aria-describedby="headlessui-description-1">
<p data-count="1" id="headlessui-description-1">
I am a description
</p>
<p data-count="1" id="headlessui-description-1">I am a description</p>
<button>+1</button>
</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 { Dialog, DialogOverlay, DialogTitle, DialogDescription } from './dialog'
@@ -30,9 +30,7 @@ afterAll(() => jest.restoreAllMocks())
let TabSentinel = defineComponent({
name: 'TabSentinel',
template: html`
<div :tabindex="0"></div>
`,
template: html` <div :tabindex="0"></div> `,
})
jest.mock('../../hooks/use-id')
@@ -44,7 +42,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
let defaultComponents = { Dialog, DialogOverlay, DialogTitle, DialogDescription, TabSentinel }
if (typeof input === 'string') {
@@ -193,7 +193,7 @@ export let Dialog = defineComponent({
provide(DialogContext, api)
// Handle outside click
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
if (dialogState.value !== DialogStates.Open) return
@@ -205,7 +205,7 @@ export let Dialog = defineComponent({
})
// Handle `Escape` to close
useWindowEvent('keydown', event => {
useWindowEvent('keydown', (event) => {
if (event.key !== Keys.Escape) return
if (dialogState.value !== DialogStates.Open) return
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
watchEffect(onInvalidate => {
watchEffect((onInvalidate) => {
if (dialogState.value !== DialogStates.Open) return
let overflow = document.documentElement.style.overflow
@@ -233,12 +233,12 @@ export let Dialog = defineComponent({
})
// Trigger close when the FocusTrap gets hidden
watchEffect(onInvalidate => {
watchEffect((onInvalidate) => {
if (dialogState.value !== DialogStates.Open) return
let container = dom(internalDialogRef)
if (!container) return
let observer = new IntersectionObserver(entries => {
let observer = new IntersectionObserver((entries) => {
for (let entry of entries) {
if (
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 { Disclosure, DisclosureButton, DisclosurePanel } from './disclosure'
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
@@ -19,7 +19,7 @@ jest.mock('../../hooks/use-id')
afterAll(() => jest.restoreAllMocks())
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
let defaultComponents = { Disclosure, DisclosureButton, DisclosurePanel }
if (typeof input === 'string') {
@@ -297,9 +297,7 @@ describe('Rendering', () => {
renderTemplate(
html`
<Disclosure>
<DisclosureButton>
Trigger
</DisclosureButton>
<DisclosureButton> Trigger </DisclosureButton>
</Disclosure>
`
)
@@ -311,9 +309,7 @@ describe('Rendering', () => {
renderTemplate(
html`
<Disclosure>
<DisclosureButton type="submit">
Trigger
</DisclosureButton>
<DisclosureButton type="submit"> Trigger </DisclosureButton>
</Disclosure>
`
)
@@ -327,14 +323,12 @@ describe('Rendering', () => {
renderTemplate({
template: html`
<Disclosure>
<DisclosureButton :as="CustomButton">
Trigger
</DisclosureButton>
<DisclosureButton :as="CustomButton"> Trigger </DisclosureButton>
</Disclosure>
`,
setup: () => ({
CustomButton: defineComponent({
setup: props => () => h('button', { ...props }),
setup: (props) => () => h('button', { ...props }),
}),
}),
})
@@ -349,9 +343,7 @@ describe('Rendering', () => {
renderTemplate(
html`
<Disclosure>
<DisclosureButton as="div">
Trigger
</DisclosureButton>
<DisclosureButton as="div"> Trigger </DisclosureButton>
</Disclosure>
`
)
@@ -365,14 +357,12 @@ describe('Rendering', () => {
renderTemplate({
template: html`
<Disclosure>
<DisclosureButton :as="CustomButton">
Trigger
</DisclosureButton>
<DisclosureButton :as="CustomButton"> Trigger </DisclosureButton>
</Disclosure>
`,
setup: () => ({
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 { assertActiveElement, getByText } from '../../test-utils/accessibility-assertions'
@@ -16,7 +16,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
let defaultComponents = { FocusTrap }
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'))
})
@@ -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'))
})
@@ -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'))
})
@@ -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'))
})
@@ -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 />')
})
@@ -143,7 +143,7 @@ it(
`,
})
await new Promise(nextTick)
await new Promise<void>(nextTick)
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`
<div>
<input id="item-1" ref="autoFocusRef" />
<button id="item-2" @click="visible = true">
Open modal
</button>
<button id="item-2" @click="visible = true">Open modal</button>
<FocusTrap v-if="visible">
<button id="item-3" @click="visible = false">
Close
</button>
<button id="item-3" @click="visible = false">Close</button>
</FocusTrap>
</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
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
assertActiveElement(document.getElementById('item-a'))
@@ -302,12 +298,8 @@ it('should skip the initial "hidden" elements within the focus trap', async () =
<div>
<button id="before">Before</button>
<FocusTrap>
<button id="item-a" style="display:none">
Item A
</button>
<button id="item-b" style="display:none">
Item B
</button>
<button id="item-a" style="display:none">Item A</button>
<button id="item-b" style="display:none">Item B</button>
<button id="item-c">Item C</button>
<button id="item-d">Item D</button>
</FocusTrap>
@@ -328,9 +320,7 @@ it('should be possible skip "hidden" elements within the focus trap', async () =
<FocusTrap>
<button id="item-a">Item A</button>
<button id="item-b">Item B</button>
<button id="item-c" style="display:none">
Item C
</button>
<button id="item-c" style="display:none">Item C</button>
<button id="item-d">Item D</button>
</FocusTrap>
<button>After</button>
@@ -364,9 +354,7 @@ it('should be possible skip disabled elements within the focus trap', async () =
<FocusTrap>
<button id="item-a">Item A</button>
<button id="item-b">Item B</button>
<button id="item-c" disabled>
Item C
</button>
<button id="item-c" disabled>Item C</button>
<button id="item-d">Item D</button>
</FocusTrap>
<button>After</button>
@@ -8,7 +8,8 @@ import { html } from '../../test-utils/html'
import { click } from '../../test-utils/interactions'
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()
return prettier.format(contents, { parser: 'babel' })
}
@@ -22,59 +23,47 @@ beforeAll(() => {
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 () => {
let { container } = renderTemplate({
render() {
return h('div', [h('div', { 'aria-labelledby': this.labelledby }, ['No label'])])
},
setup() {
let labelledby = useLabels()
return { labelledby }
},
})
let { container } = render(
defineComponent({
components: { Label },
render() {
return h('div', [h('div', { 'aria-labelledby': this.labelledby }, ['No label'])])
},
setup() {
let labelledby = useLabels()
return { labelledby }
},
})
)
expect(format(container.firstElementChild)).toEqual(
format(html`
<div>
<div>
No label
</div>
<div>No label</div>
</div>
`)
)
})
it('should be possible to use useLabels and a single Label, and have them linked', async () => {
let { container } = renderTemplate({
render() {
return h('div', [
h('div', { 'aria-labelledby': this.labelledby }, [
h(Label, () => 'I am a label'),
h('span', 'Contents'),
]),
])
},
setup() {
let labelledby = useLabels()
return { labelledby }
},
})
let { container } = render(
defineComponent({
components: { Label },
render() {
return h('div', [
h('div', { 'aria-labelledby': this.labelledby }, [
h(Label, () => 'I am a label'),
h('span', 'Contents'),
]),
])
},
setup() {
let labelledby = useLabels()
return { labelledby }
},
})
)
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`
<div>
<div aria-labelledby="headlessui-label-1">
<label id="headlessui-label-1">
I am a label
</label>
<span>
Contents
</span>
<label id="headlessui-label-1">I am a label</label>
<span>Contents</span>
</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 () => {
let { container } = renderTemplate({
render() {
return h('div', [
h('div', { 'aria-labelledby': this.labelledby }, [
h(Label, () => 'I am a label'),
h('span', 'Contents'),
h(Label, () => 'I am also a label'),
]),
])
},
setup() {
let labelledby = useLabels()
return { labelledby }
},
})
let { container } = render(
defineComponent({
components: { Label },
render() {
return h('div', [
h('div', { 'aria-labelledby': this.labelledby }, [
h(Label, () => 'I am a label'),
h('span', 'Contents'),
h(Label, () => 'I am also a label'),
]),
])
},
setup() {
let labelledby = useLabels()
return { labelledby }
},
})
)
await new Promise<void>(nextTick)
@@ -117,15 +105,9 @@ it('should be possible to use useLabels and multiple Label components, and have
format(html`
<div>
<div aria-labelledby="headlessui-label-1 headlessui-label-2">
<label id="headlessui-label-1">
I am a label
</label>
<span>
Contents
</span>
<label id="headlessui-label-2">
I am also a label
</label>
<label id="headlessui-label-1">I am a label</label>
<span>Contents</span>
<label id="headlessui-label-2">I am also a label</label>
</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 () => {
let { container } = renderTemplate({
render() {
return h('div', [
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 } })
return { count, labelledby }
},
})
let { container } = render(
defineComponent({
components: { Label },
render() {
return h('div', [
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 } })
return { count, labelledby }
},
})
)
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`
<div>
<div aria-labelledby="headlessui-label-1">
<label data-count="0" id="headlessui-label-1">
I am a label
</label>
<label data-count="0" id="headlessui-label-1">I am a label</label>
<button>+1</button>
</div>
</div>
@@ -170,9 +153,7 @@ it('should be possible to update a prop from the parent and it should reflect in
format(html`
<div>
<div aria-labelledby="headlessui-label-1">
<label data-count="1" id="headlessui-label-1">
I am a label
</label>
<label data-count="1" id="headlessui-label-1">I am a label</label>
<button>+1</button>
</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 { Listbox, ListboxLabel, ListboxButton, ListboxOptions, ListboxOption } from './listbox'
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
@@ -48,7 +56,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function nextFrame() {
return new Promise(resolve => {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
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 }
if (typeof input === 'string') {
@@ -388,9 +396,7 @@ describe('Rendering', () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<ListboxButton type="submit">
Trigger
</ListboxButton>
<ListboxButton type="submit"> Trigger </ListboxButton>
</Listbox>
`,
setup: () => ({ value: ref(null) }),
@@ -405,15 +411,13 @@ describe('Rendering', () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<ListboxButton :as="CustomButton">
Trigger
</ListboxButton>
<ListboxButton :as="CustomButton"> Trigger </ListboxButton>
</Listbox>
`,
setup: () => ({
value: ref(null),
CustomButton: defineComponent({
setup: props => () => h('button', { ...props }),
setup: (props) => () => h('button', { ...props }),
}),
}),
})
@@ -428,9 +432,7 @@ describe('Rendering', () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<ListboxButton as="div">
Trigger
</ListboxButton>
<ListboxButton as="div"> Trigger </ListboxButton>
</Listbox>
`,
setup: () => ({ value: ref(null) }),
@@ -445,15 +447,13 @@ describe('Rendering', () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<ListboxButton :as="CustomButton">
Trigger
</ListboxButton>
<ListboxButton :as="CustomButton"> Trigger </ListboxButton>
</Listbox>
`,
setup: () => ({
value: ref(null),
CustomButton: defineComponent({
setup: props => () => h('div', props),
setup: (props) => () => h('div', props),
}),
}),
})
@@ -647,15 +647,9 @@ describe('Rendering composition', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption as="button" value="a">
Option A
</ListboxOption>
<ListboxOption as="button" value="b">
Option B
</ListboxOption>
<ListboxOption as="button" value="c">
Option C
</ListboxOption>
<ListboxOption as="button" value="a"> Option A </ListboxOption>
<ListboxOption as="button" value="b"> Option B </ListboxOption>
<ListboxOption as="button" value="c"> Option C </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -672,7 +666,7 @@ describe('Rendering composition', () => {
await click(getListboxButton())
// 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>
<ListboxButton>Trigger</ListboxButton>
<OpenClosedWrite :open="true">
<ListboxOptions v-slot="data">
{{JSON.stringify(data)}}
</ListboxOptions>
<ListboxOptions v-slot="data"> {{JSON.stringify(data)}} </ListboxOptions>
</OpenClosedWrite>
</Listbox>
`,
@@ -734,9 +726,7 @@ describe('Composition', () => {
<Listbox>
<ListboxButton>Trigger</ListboxButton>
<OpenClosedWrite :open="false">
<ListboxOptions v-slot="data">
{{JSON.stringify(data)}}
</ListboxOptions>
<ListboxOptions v-slot="data"> {{JSON.stringify(data)}} </ListboxOptions>
</OpenClosedWrite>
</Listbox>
`,
@@ -840,7 +830,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
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
assertActiveListboxOption(options[0])
@@ -1092,9 +1082,7 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption value="b">Option B</ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
</ListboxOptions>
@@ -1130,12 +1118,8 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -1170,15 +1154,9 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -1345,7 +1323,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
})
)
@@ -1471,9 +1449,7 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption value="b">Option B</ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
</ListboxOptions>
@@ -1509,12 +1485,8 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -1549,15 +1521,9 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -1729,7 +1695,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
// Try to tab
@@ -1783,7 +1749,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
// Try to Shift+Tab
@@ -1839,7 +1805,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
// Verify that the first listbox option is active
assertActiveListboxOption(options[0])
@@ -1991,7 +1957,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
// We should be able to go down once
@@ -2016,9 +1982,7 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption value="b">Option B</ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
</ListboxOptions>
@@ -2042,7 +2006,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[1])
// We should be able to go down once
@@ -2059,12 +2023,8 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -2087,7 +2047,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[2])
})
)
@@ -2126,7 +2086,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
// We should be able to go right once
@@ -2186,7 +2146,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
// ! ALERT: The LAST option should now be active
assertActiveListboxOption(options[2])
@@ -2315,12 +2275,8 @@ describe('Keyboard interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="a">Option A</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -2342,7 +2298,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[0])
})
)
@@ -2355,12 +2311,8 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -2383,7 +2335,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[2])
// We should not be able to go up (because those are disabled)
@@ -2437,7 +2389,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[2])
// We should be able to go down once
@@ -2498,7 +2450,7 @@ describe('Keyboard interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
assertActiveListboxOption(options[2])
// We should be able to go left once
@@ -2561,12 +2513,8 @@ describe('Keyboard interactions', () => {
<ListboxOptions>
<ListboxOption value="a">Option A</ListboxOption>
<ListboxOption value="b">Option B</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="d">
Option D
</ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption disabled value="d"> Option D </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -2599,15 +2547,9 @@ describe('Keyboard interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="a">Option A</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="d">
Option D
</ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption disabled value="d"> Option D </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -2636,18 +2578,10 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="d">
Option D
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption disabled value="d"> Option D </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -2713,12 +2647,8 @@ describe('Keyboard interactions', () => {
<ListboxOptions>
<ListboxOption value="a">Option A</ListboxOption>
<ListboxOption value="b">Option B</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="d">
Option D
</ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption disabled value="d"> Option D </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -2751,15 +2681,9 @@ describe('Keyboard interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="a">Option A</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="d">
Option D
</ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption disabled value="d"> Option D </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -2788,18 +2712,10 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="d">
Option D
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption disabled value="d"> Option D </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -2863,12 +2779,8 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
<ListboxOption value="d">Option D</ListboxOption>
</ListboxOptions>
@@ -2901,15 +2813,9 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption value="d">Option D</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -2939,18 +2845,10 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="d">
Option D
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption disabled value="d"> Option D </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -3014,12 +2912,8 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption value="c">Option C</ListboxOption>
<ListboxOption value="d">Option D</ListboxOption>
</ListboxOptions>
@@ -3052,15 +2946,9 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption value="d">Option D</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -3090,18 +2978,10 @@ describe('Keyboard interactions', () => {
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption disabled value="a">
Option A
</ListboxOption>
<ListboxOption disabled value="b">
Option B
</ListboxOption>
<ListboxOption disabled value="c">
Option C
</ListboxOption>
<ListboxOption disabled value="d">
Option D
</ListboxOption>
<ListboxOption disabled value="a"> Option A </ListboxOption>
<ListboxOption disabled value="b"> Option B </ListboxOption>
<ListboxOption disabled value="c"> Option C </ListboxOption>
<ListboxOption disabled value="d"> Option D </ListboxOption>
</ListboxOptions>
</Listbox>
`,
@@ -3252,9 +3132,7 @@ describe('Keyboard interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
<ListboxOption disabled value="bob">
bob
</ListboxOption>
<ListboxOption disabled value="bob"> bob </ListboxOption>
<ListboxOption value="charlie">charlie</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -3453,7 +3331,7 @@ describe('Mouse interactions', () => {
// Verify we have listbox options
let options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach(option => assertListboxOption(option))
options.forEach((option) => assertListboxOption(option))
})
)
@@ -3845,9 +3723,7 @@ describe('Mouse interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
<ListboxOption disabled value="bob">
bob
</ListboxOption>
<ListboxOption disabled value="bob"> bob </ListboxOption>
<ListboxOption value="charlie">charlie</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -3874,9 +3750,7 @@ describe('Mouse interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
<ListboxOption disabled value="bob">
bob
</ListboxOption>
<ListboxOption disabled value="bob"> bob </ListboxOption>
<ListboxOption value="charlie">charlie</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -3951,9 +3825,7 @@ describe('Mouse interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
<ListboxOption disabled value="bob">
bob
</ListboxOption>
<ListboxOption disabled value="bob"> bob </ListboxOption>
<ListboxOption value="charlie">charlie</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -4031,9 +3903,7 @@ describe('Mouse interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
<ListboxOption disabled value="bob">
bob
</ListboxOption>
<ListboxOption disabled value="bob"> bob </ListboxOption>
<ListboxOption value="charlie">charlie</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -4111,9 +3981,7 @@ describe('Mouse interactions', () => {
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
<ListboxOption disabled value="bob">
bob
</ListboxOption>
<ListboxOption disabled value="bob"> bob </ListboxOption>
<ListboxOption value="charlie">charlie</ListboxOption>
</ListboxOptions>
</Listbox>
@@ -130,8 +130,8 @@ export let Listbox = defineComponent({
{
resolveItems: () => options.value,
resolveActiveIndex: () => activeOptionIndex.value,
resolveId: option => option.id,
resolveDisabled: option => option.dataRef.disabled,
resolveId: (option) => option.id,
resolveDisabled: (option) => option.dataRef.disabled,
}
)
@@ -153,7 +153,7 @@ export let Listbox = defineComponent({
: options.value
let matchingOption = reOrderedOptions.find(
option =>
(option) =>
option.dataRef.textValue.startsWith(searchQuery.value) && !option.dataRef.disabled
)
@@ -186,7 +186,7 @@ export let Listbox = defineComponent({
let nextOptions = options.value.slice()
let currentActiveOption =
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)
options.value = nextOptions
activeOptionIndex.value = (() => {
@@ -204,7 +204,7 @@ export let Listbox = defineComponent({
},
}
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
let active = document.activeElement
@@ -529,10 +529,7 @@ export let ListboxOption = defineComponent({
textValue: '',
})
onMounted(() => {
let textValue = document
.getElementById(id)
?.textContent?.toLowerCase()
.trim()
let textValue = document.getElementById(id)?.textContent?.toLowerCase().trim()
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 { Menu, MenuButton, MenuItems, MenuItem } from './menu'
import { TransitionChild } from '../transitions/transition'
@@ -44,7 +52,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function nextFrame() {
return new Promise(resolve => {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
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 }
if (typeof input === 'string') {
@@ -395,7 +403,7 @@ describe('Rendering', () => {
`,
setup: () => ({
CustomButton: defineComponent({
setup: props => () => h('button', { ...props }),
setup: (props) => () => h('button', { ...props }),
}),
}),
})
@@ -433,7 +441,7 @@ describe('Rendering', () => {
`,
setup: () => ({
CustomButton: defineComponent({
setup: props => () => h('div', props),
setup: (props) => () => h('div', props),
}),
}),
})
@@ -810,7 +818,7 @@ describe('Rendering composition', () => {
// Verify items are buttons now
let items = getMenuItems()
items.forEach(item =>
items.forEach((item) =>
assertMenuItem(item, { tag: 'button', attributes: { 'data-my-custom-button': 'true' } })
)
})
@@ -853,11 +861,11 @@ describe('Rendering composition', () => {
expect.hasAssertions()
document.querySelectorAll('.outer').forEach(element => {
document.querySelectorAll('.outer').forEach((element) => {
expect(element).not.toHaveAttribute('role', 'none')
})
document.querySelectorAll('.inner').forEach(element => {
document.querySelectorAll('.inner').forEach((element) => {
expect(element).toHaveAttribute('role', 'none')
})
})
@@ -1069,7 +1077,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
// Verify that the first menu item is active
assertMenuLinkedWithMenuItem(items[0])
@@ -1398,7 +1406,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
})
@@ -1704,7 +1712,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// Try to tab
@@ -1750,7 +1758,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// Try to Shift+Tab
@@ -1798,7 +1806,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
// Verify that the first menu item is active
assertMenuLinkedWithMenuItem(items[0])
@@ -1883,7 +1891,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go down once
@@ -1926,7 +1934,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go down once
@@ -1961,7 +1969,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
})
})
@@ -2002,7 +2010,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
// ! ALERT: The LAST item should now be active
assertMenuLinkedWithMenuItem(items[2])
@@ -2055,7 +2063,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
})
@@ -2086,7 +2094,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
// We should not be able to go up (because those are disabled)
@@ -2133,7 +2141,7 @@ describe('Keyboard interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
// We should be able to go down once
@@ -2820,7 +2828,7 @@ describe('Mouse interactions', () => {
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach(item => assertMenuItem(item))
items.forEach((item) => assertMenuItem(item))
})
it(
@@ -96,8 +96,8 @@ export let Menu = defineComponent({
{
resolveItems: () => items.value,
resolveActiveIndex: () => activeItemIndex.value,
resolveId: item => item.id,
resolveDisabled: item => item.dataRef.disabled,
resolveId: (item) => item.id,
resolveDisabled: (item) => item.dataRef.disabled,
}
)
@@ -116,7 +116,7 @@ export let Menu = defineComponent({
: items.value
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
@@ -144,7 +144,7 @@ export let Menu = defineComponent({
let nextItems = items.value.slice()
let currentActiveItem =
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)
items.value = nextItems
activeItemIndex.value = (() => {
@@ -158,7 +158,7 @@ export let Menu = defineComponent({
},
}
useWindowEvent('mousedown', event => {
useWindowEvent('mousedown', (event) => {
let target = event.target as HTMLElement
let active = document.activeElement
@@ -452,10 +452,7 @@ export let MenuItem = defineComponent({
let dataRef = ref<MenuItemDataRef['value']>({ disabled: props.disabled, textValue: '' })
onMounted(() => {
let textValue = document
.getElementById(id)
?.textContent?.toLowerCase()
.trim()
let textValue = document.getElementById(id)?.textContent?.toLowerCase().trim()
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 { Popover, PopoverGroup, PopoverButton, PopoverPanel, PopoverOverlay } from './popover'
@@ -28,7 +28,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
let defaultComponents = {
Popover,
PopoverGroup,
@@ -345,9 +345,7 @@ describe('Rendering', () => {
renderTemplate(
html`
<Popover>
<PopoverButton type="submit">
Trigger
</PopoverButton>
<PopoverButton type="submit"> Trigger </PopoverButton>
</Popover>
`
)
@@ -361,14 +359,12 @@ describe('Rendering', () => {
renderTemplate({
template: html`
<Popover>
<PopoverButton :as="CustomButton">
Trigger
</PopoverButton>
<PopoverButton :as="CustomButton"> Trigger </PopoverButton>
</Popover>
`,
setup: () => ({
CustomButton: defineComponent({
setup: props => () => h('button', { ...props }),
setup: (props) => () => h('button', { ...props }),
}),
}),
})
@@ -383,9 +379,7 @@ describe('Rendering', () => {
renderTemplate(
html`
<Popover>
<PopoverButton as="div">
Trigger
</PopoverButton>
<PopoverButton as="div"> Trigger </PopoverButton>
</Popover>
`
)
@@ -399,14 +393,12 @@ describe('Rendering', () => {
renderTemplate({
template: html`
<Popover>
<PopoverButton :as="CustomButton">
Trigger
</PopoverButton>
<PopoverButton :as="CustomButton"> Trigger </PopoverButton>
</Popover>
`,
setup: () => ({
CustomButton: defineComponent({
setup: props => () => h('div', props),
setup: (props) => () => h('div', props),
}),
}),
})
@@ -569,9 +561,7 @@ describe('Rendering', () => {
<Popover>
<PopoverButton>Trigger</PopoverButton>
<PopoverPanel focus>
<a href="/" style="display:none">
Link 1
</a>
<a href="/" style="display:none"> Link 1 </a>
<a href="/">Link 2</a>
</PopoverPanel>
</Popover>
@@ -757,9 +747,7 @@ describe('Composition', () => {
<Popover>
<PopoverButton>Trigger</PopoverButton>
<OpenClosedWrite :open="true">
<PopoverPanel v-slot="data">
{{JSON.stringify(data)}}
</PopoverPanel>
<PopoverPanel v-slot="data"> {{JSON.stringify(data)}} </PopoverPanel>
</OpenClosedWrite>
</Popover>
`,
@@ -787,9 +775,7 @@ describe('Composition', () => {
<Popover>
<PopoverButton>Trigger</PopoverButton>
<OpenClosedWrite :open="false">
<PopoverPanel v-slot="data">
{{JSON.stringify(data)}}
</PopoverPanel>
<PopoverPanel v-slot="data"> {{JSON.stringify(data)}} </PopoverPanel>
</OpenClosedWrite>
</Popover>
`,
@@ -528,7 +528,7 @@ export let PopoverPanel = defineComponent({
let nextElements = elements
.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
// 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
// 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 (
document.getElementById(bag.buttonId)?.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 { Portal, PortalGroup } from './portal'
@@ -22,7 +22,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
let defaultComponents = { Portal, PortalGroup }
if (typeof input === 'string') {
@@ -108,12 +108,8 @@ it('should cleanup the Portal root when the last Portal is unmounted', async ()
renderTemplate({
template: html`
<main id="parent">
<button id="a" @click="toggleA">
Toggle A
</button>
<button id="b" @click="toggleB">
Toggle B
</button>
<button id="a" @click="toggleA">Toggle A</button>
<button id="b" @click="toggleB">Toggle B</button>
<Portal v-if="renderA">
<p id="content1">Contents 1 ...</p>
@@ -182,19 +178,11 @@ it('should be possible to render multiple portals at the same time', async () =>
renderTemplate({
template: html`
<main id="parent">
<button id="a" @click="toggleA">
Toggle A
</button>
<button id="b" @click="toggleB">
Toggle B
</button>
<button id="c" @click="toggleC">
Toggle C
</button>
<button id="a" @click="toggleA">Toggle A</button>
<button id="b" @click="toggleB">Toggle B</button>
<button id="c" @click="toggleC">Toggle C</button>
<button id="double" @click="toggleAB">
Toggle A & B
</button>
<button id="double" @click="toggleAB">Toggle A & B</button>
<Portal v-if="renderA">
<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({
template: html`
<main id="parent">
<button id="a" @click="toggleA">
Toggle A
</button>
<button id="b" @click="toggleB">
Toggle B
</button>
<button id="a" @click="toggleA">Toggle A</button>
<button id="b" @click="toggleB">Toggle B</button>
<Portal v-if="renderA">
<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({
template: html`
<main>
<aside ref="container" id="group-1">
A
</aside>
<aside ref="container" id="group-1">A</aside>
<PortalGroup :target="container">
<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)
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 { RadioGroup, RadioGroupOption, RadioGroupLabel, RadioGroupDescription } from './radio-group'
@@ -25,7 +25,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function nextFrame() {
return new Promise(resolve => {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
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 }
if (typeof input === 'string') {
@@ -86,9 +86,7 @@ describe('Safe guards', () => {
it('should be possible to render a RadioGroup without options and without crashing', () => {
renderTemplate({
template: html`
<RadioGroup v-model="deliveryMethod" />
`,
template: html` <RadioGroup v-model="deliveryMethod" /> `,
setup() {
let deliveryMethod = ref(undefined)
return { deliveryMethod }
@@ -99,19 +99,19 @@ export let RadioGroup = defineComponent({
value,
disabled: computed(() => props.disabled),
firstOption: computed(() =>
options.value.find(option => {
options.value.find((option) => {
if (option.propsRef.disabled) return false
return true
})
),
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) {
if (props.disabled) return false
if (value.value === nextValue) return false
let nextOption = options.value.find(
option => toRaw(option.propsRef.value) === toRaw(nextValue)
(option) => toRaw(option.propsRef.value) === toRaw(nextValue)
)?.propsRef
if (nextOption?.disabled) return false
emit('update:modelValue', nextValue)
@@ -129,7 +129,7 @@ export let RadioGroup = defineComponent({
options.value.sort((a, z) => orderMap[a.id] - orderMap[z.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
options.value.splice(idx, 1)
},
@@ -155,8 +155,8 @@ export let RadioGroup = defineComponent({
if (!radioGroupRef.value.contains(event.target as HTMLElement)) return
let all = options.value
.filter(option => option.propsRef.disabled === false)
.map(radio => radio.element) as HTMLElement[]
.filter((option) => option.propsRef.disabled === false)
.map((radio) => radio.element) as HTMLElement[]
switch (event.key) {
case Keys.ArrowLeft:
@@ -169,7 +169,7 @@ export let RadioGroup = defineComponent({
if (result === FocusResult.Success) {
let activeOption = options.value.find(
option => option.element === document.activeElement
(option) => option.element === document.activeElement
)
if (activeOption) api.change(activeOption.propsRef.value)
}
@@ -186,7 +186,7 @@ export let RadioGroup = defineComponent({
if (result === FocusResult.Success) {
let activeOption = options.value.find(
option => option.element === document.activeElement
(option) => option.element === document.activeElement
)
if (activeOption) api.change(activeOption.propsRef.value)
}
@@ -199,7 +199,7 @@ export let RadioGroup = defineComponent({
event.stopPropagation()
let activeOption = options.value.find(
option => option.element === document.activeElement
(option) => option.element === document.activeElement
)
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 { Switch, SwitchLabel, SwitchDescription, SwitchGroup } from './switch'
@@ -16,7 +16,7 @@ import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
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 }
if (typeof input === 'string') {
@@ -35,9 +35,7 @@ function renderTemplate(input: string | Partial<Parameters<typeof defineComponen
describe('Safe guards', () => {
it('should be possible to render a Switch without crashing', () => {
renderTemplate({
template: html`
<Switch v-model="checked" />
`,
template: html` <Switch v-model="checked" /> `,
setup: () => ({ checked: ref(false) }),
})
})
@@ -72,9 +70,7 @@ describe('Rendering', () => {
it('should be possible to render an (on) Switch using an `as` prop', () => {
renderTemplate({
template: html`
<Switch as="span" v-model="checked" />
`,
template: html` <Switch as="span" v-model="checked" /> `,
setup: () => ({ checked: ref(true) }),
})
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', () => {
renderTemplate({
template: html`
<Switch as="span" v-model="checked" />
`,
template: html` <Switch as="span" v-model="checked" /> `,
setup: () => ({ checked: ref(false) }),
})
assertSwitch({ state: SwitchState.Off, tag: 'span' })
@@ -106,11 +100,7 @@ describe('Rendering', () => {
describe('`type` attribute', () => {
it('should set the `type` to "button" by default', async () => {
renderTemplate({
template: html`
<Switch v-model="checked">
Trigger
</Switch>
`,
template: html` <Switch v-model="checked"> Trigger </Switch> `,
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 () => {
renderTemplate({
template: html`
<Switch v-model="checked" type="submit">
Trigger
</Switch>
`,
template: html` <Switch v-model="checked" type="submit"> Trigger </Switch> `,
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"',
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Switch v-model="checked" :as="CustomButton">
Trigger
</Switch>
`,
template: html` <Switch v-model="checked" :as="CustomButton"> Trigger </Switch> `,
setup: () => ({
checked: ref(false),
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 () => {
renderTemplate({
template: html`
<Switch v-model="checked" as="div">
Trigger
</Switch>
`,
template: html` <Switch v-model="checked" as="div"> Trigger </Switch> `,
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"',
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Switch v-model="checked" :as="CustomButton">
Trigger
</Switch>
`,
template: html` <Switch v-model="checked" :as="CustomButton"> Trigger </Switch> `,
setup: () => ({
checked: ref(false),
CustomButton: defineComponent({
setup: props => () => h('div', props),
setup: (props) => () => h('div', props),
}),
}),
})
@@ -213,9 +187,7 @@ describe('Render composition', () => {
template: html`
<SwitchGroup>
<SwitchLabel>Label B</SwitchLabel>
<Switch v-model="checked">
Label A
</Switch>
<Switch v-model="checked"> Label A </Switch>
</SwitchGroup>
`,
setup: () => ({ checked: ref(false) }),
@@ -234,9 +206,7 @@ describe('Render composition', () => {
renderTemplate({
template: html`
<SwitchGroup>
<Switch v-model="checked">
Label A
</Switch>
<Switch v-model="checked"> Label A </Switch>
<SwitchLabel>Label B</SwitchLabel>
</SwitchGroup>
`,
@@ -370,9 +340,7 @@ describe('Keyboard interactions', () => {
it('should be possible to toggle the Switch with Space', async () => {
let handleChange = jest.fn()
renderTemplate({
template: html`
<Switch v-model="checked" />
`,
template: html` <Switch v-model="checked" /> `,
setup() {
let checked = ref(false)
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 () => {
let handleChange = jest.fn()
renderTemplate({
template: html`
<Switch v-model="checked" />
`,
template: html` <Switch v-model="checked" /> `,
setup() {
let checked = ref(false)
watch([checked], () => handleChange(checked.value))
@@ -461,9 +427,7 @@ describe('Mouse interactions', () => {
it('should be possible to toggle the Switch with a click', async () => {
let handleChange = jest.fn()
renderTemplate({
template: html`
<Switch v-model="checked" />
`,
template: html` <Switch v-model="checked" /> `,
setup() {
let checked = ref(false)
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 { TabGroup, TabList, Tab, TabPanels, TabPanel } from './tabs'
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
@@ -20,7 +20,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
let defaultComponents = { TabGroup, TabList, Tab, TabPanels, TabPanel }
if (typeof input === 'string') {
@@ -102,8 +102,8 @@ export let TabGroup = defineComponent({
if (api.tabs.value.length <= 0) return
if (props.selectedIndex === null && selectedIndex.value !== null) return
let tabs = api.tabs.value.map(tab => dom(tab)).filter(Boolean) as HTMLElement[]
let focusableTabs = tabs.filter(tab => !tab.hasAttribute('disabled'))
let tabs = api.tabs.value.map((tab) => dom(tab)).filter(Boolean) as HTMLElement[]
let focusableTabs = tabs.filter((tab) => !tab.hasAttribute('disabled'))
let indexToSet = props.selectedIndex ?? props.defaultIndex
@@ -122,7 +122,7 @@ export let TabGroup = defineComponent({
let before = tabs.slice(0, 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
selectedIndex.value = tabs.indexOf(next)
@@ -220,7 +220,7 @@ export let Tab = defineComponent({
let selected = computed(() => myIndex.value === api.selectedIndex.value)
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) {
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 { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
@@ -11,7 +11,7 @@ jest.mock('../../hooks/use-id')
afterAll(() => jest.restoreAllMocks())
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
let defaultComponents = { TransitionRoot, TransitionChild }
if (typeof input === 'string') {
@@ -58,9 +58,7 @@ it('should render without crashing', () => {
it('should be possible to render a Transition without children', () => {
renderTemplate({
template: html`
<TransitionRoot :show="true" class="transition" />
`,
template: html` <TransitionRoot :show="true" class="transition" /> `,
})
expect(document.getElementsByClassName('transition')).not.toBeNull()
})
@@ -91,12 +89,10 @@ describe('Setup API', () => {
describe('shallow', () => {
it('should render a div and its children by default', () => {
let { container } = renderTemplate({
template: html`
<TransitionRoot :show="true">Children</TransitionRoot>
`,
template: html`<TransitionRoot :show="true">Children</TransitionRoot>`,
})
expect(container.firstChild).toMatchInlineSnapshot(html`
expect(container.firstChild).toMatchInlineSnapshot(`
<div>
Children
</div>
@@ -106,9 +102,7 @@ describe('Setup API', () => {
it('should passthrough all the props (that we do not use internally)', () => {
let { container } = renderTemplate({
template: html`
<TransitionRoot :show="true" id="root" class="text-blue-400">
Children
</TransitionRoot>
<TransitionRoot :show="true" id="root" class="text-blue-400"> 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', () => {
let { container } = renderTemplate({
template: html`
<TransitionRoot :show="true" as="a">
Children
</TransitionRoot>
`,
template: html` <TransitionRoot :show="true" as="a"> Children </TransitionRoot> `,
})
expect(container.firstChild).toMatchInlineSnapshot(`
@@ -159,9 +149,7 @@ describe('Setup API', () => {
it('should render nothing when the show prop is false', () => {
let { container } = renderTemplate({
template: html`
<TransitionRoot :show="false">Children</TransitionRoot>
`,
template: html` <TransitionRoot :show="false">Children</TransitionRoot> `,
})
expect(container.firstChild).toMatchInlineSnapshot(`<!---->`)
@@ -169,11 +157,7 @@ describe('Setup API', () => {
it('should be possible to change the underlying DOM tag', () => {
let { container } = renderTemplate({
template: html`
<TransitionRoot :show="true" as="a">
Children
</TransitionRoot>
`,
template: html` <TransitionRoot :show="true" as="a"> Children </TransitionRoot> `,
})
expect(container.firstChild).toMatchInlineSnapshot(`
@@ -470,9 +454,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(false)
@@ -525,9 +507,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(false)
@@ -580,9 +560,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(false)
@@ -630,9 +608,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(false)
@@ -687,9 +663,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(true)
@@ -753,9 +727,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(true)
@@ -822,9 +794,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(false)
@@ -918,9 +888,7 @@ describe('Transitions', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(false)
@@ -1021,9 +989,7 @@ describe('Transitions', () => {
</TransitionChild>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(true)
@@ -1113,9 +1079,7 @@ describe('Transitions', () => {
</TransitionChild>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(true)
@@ -1227,9 +1191,7 @@ describe('Events', () => {
<span>Hello!</span>
</TransitionRoot>
<button data-testid="toggle" @click="show = !show">
Toggle
</button>
<button data-testid="toggle" @click="show = !show">Toggle</button>
`,
setup() {
let show = ref(false)
@@ -31,7 +31,7 @@ import {
type ID = ReturnType<typeof useId>
function splitClasses(classes: string = '') {
return classes.split(' ').filter(className => className.trim().length > 1)
return classes.split(' ').filter((className) => className.trim().length > 1)
}
interface TransitionContextValues {
@@ -292,7 +292,7 @@ export let TransitionChild = defineComponent({
enterFromClasses,
enterToClasses,
enteredClasses,
reason => {
(reason) => {
isTransitioning.value = false
if (reason === Reason.Finished) emit('afterEnter')
}
@@ -303,7 +303,7 @@ export let TransitionChild = defineComponent({
leaveFromClasses,
leaveToClasses,
enteredClasses,
reason => {
(reason) => {
isTransitioning.value = false
if (reason !== Reason.Finished) return
@@ -17,7 +17,7 @@ it('should be possible to transition', async () => {
d.add(
reportChanges(
() => document.body.innerHTML,
content => {
(content) => {
snapshots.push({
content,
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)
})
await new Promise(resolve => d.nextFrame(resolve))
await new Promise((resolve) => d.nextFrame(resolve))
// Initial render:
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(
reportChanges(
() => document.body.innerHTML,
content => {
(content) => {
snapshots.push({
content,
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)
})
await new Promise(resolve => d.nextFrame(resolve))
await new Promise((resolve) => d.nextFrame(resolve))
expect(reason).toBe(Reason.Finished)
// Initial render:
@@ -118,7 +118,7 @@ it('should keep the delay time into account', async () => {
d.add(
reportChanges(
() => document.body.innerHTML,
content => {
(content) => {
snapshots.push({
content,
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)
})
await new Promise(resolve => d.nextFrame(resolve))
await new Promise((resolve) => d.nextFrame(resolve))
expect(reason).toBe(Reason.Finished)
let estimatedDuration = Number(
@@ -161,7 +161,7 @@ it('should be possible to cancel a transition at any time', async () => {
d.add(
reportChanges(
() => document.body.innerHTML,
content => {
(content) => {
let recordedAt = process.hrtime.bigint()
let total = snapshots.length
@@ -178,16 +178,16 @@ it('should be possible to cancel a transition at any time', async () => {
expect.assertions(2)
// 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)
})
// Wait for a bit
await new Promise(resolve => setTimeout(resolve, 20))
await new Promise((resolve) => setTimeout(resolve, 20))
// Cancel the transition
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.
let { transitionDuration, transitionDelay } = getComputedStyle(node)
let [durationMs, delaysMs] = [transitionDuration, transitionDelay].map(value => {
let [durationMs, delaysMs] = [transitionDuration, transitionDelay].map((value) => {
let [resolvedValue = 0] = value
.split(',')
// Remove falseys we can't work with
.filter(Boolean)
// 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)
return resolvedValue
@@ -72,7 +72,7 @@ export function transition(
addClasses(node, ...to)
d.add(
waitForTransition(node, reason => {
waitForTransition(node, (reason) => {
removeClasses(node, ...to, ...base)
addClasses(node, ...entered)
return _done(reason)
@@ -75,7 +75,7 @@ export function useFocusTrap(
onUnmounted(restore)
// Handle Tab & Shift+Tab keyboard events
useWindowEvent('keydown', event => {
useWindowEvent('keydown', (event) => {
if (!enabled.value) return
if (event.key !== Keys.Tab) return
if (!document.activeElement) return
@@ -99,7 +99,7 @@ export function useFocusTrap(
// Prevent programmatically escaping
useWindowEvent(
'focus',
event => {
(event) => {
if (!enabled.value) 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 { useInertOthers } from './use-inert-others'
@@ -14,7 +14,7 @@ beforeAll(() => {
afterAll(() => jest.restoreAllMocks())
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
function renderTemplate(input: string | ComponentOptionsWithoutProps) {
let defaultComponents = {}
if (typeof input === 'string') {
@@ -35,16 +35,12 @@ function renderTemplate(input: string | Partial<Parameters<typeof defineComponen
let Before = defineComponent({
name: 'Before',
template: html`
<div>before</div>
`,
template: html` <div>before</div> `,
})
let After = defineComponent({
name: 'After',
template: html`
<div>after</div>
`,
template: html` <div>after</div> `,
})
it('should be possible to inert other elements', async () => {
@@ -32,7 +32,7 @@ export function useInertOthers<TElement extends HTMLElement>(
container: Ref<TElement | null>,
enabled: Ref<boolean> = ref(true)
) {
watchEffect(onInvalidate => {
watchEffect((onInvalidate) => {
if (!enabled.value) return
if (!container.value) return
@@ -50,7 +50,7 @@ export function useInertOthers<TElement extends HTMLElement>(
}
// 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
// Skip the interactables, and the parents of the interactables
@@ -79,7 +79,7 @@ export function useInertOthers<TElement extends HTMLElement>(
// will become inert as well.
if (interactables.size > 0) {
// 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
// Skip already inert parents
@@ -24,6 +24,7 @@ export function useTreeWalker({
if (enabled !== undefined && !enabled.value) return
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)
while (walker.nextNode()) walk(walker.currentNode as HTMLElement)
@@ -7,7 +7,7 @@ export function useWindowEvent<TType extends keyof WindowEventMap>(
) {
if (typeof window === 'undefined') return
watchEffect(onInvalidate => {
watchEffect((onInvalidate) => {
window.addEventListener(type, listener, options)
onInvalidate(() => {
@@ -24,7 +24,7 @@ export function useStackContext() {
export function useElemenStack(element: Ref<HTMLElement | null> | null) {
let notify = useStackContext()
watchEffect(onInvalidate => {
watchEffect((onInvalidate) => {
let domElement = element?.value
if (!domElement) return
@@ -1256,7 +1256,7 @@ export function assertLabelValue(element: HTMLElement | null, value: string) {
if (element.hasAttribute('aria-labelledby')) {
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
}
@@ -1612,7 +1612,7 @@ export function assertTabs(
expect(list).toHaveAttribute('aria-orientation', orientation)
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) {
expect(tab).toHaveAttribute('id')
@@ -18,7 +18,7 @@ function redentSnapshot(input: string) {
return input
.split('\n')
.map(line =>
.map((line) =>
line.trim() === '---' ? line : line.replace(replacer, (_, sign, rest) => `${sign} ${rest}`)
)
.join('\n')
@@ -70,13 +70,13 @@ export async function executeTimeline(
.reduce((total, current) => total + current, 0)
// 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
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
await new Promise(resolve => d.nextFrame(resolve))
await new Promise((resolve) => d.nextFrame(resolve))
}, Promise.resolve())
if (snapshots.length <= 0) {
@@ -128,7 +128,7 @@ export async function executeTimeline(
.replace(/Snapshot Diff:\n/g, '')
)
.split('\n')
.map(line => ` ${line}`)
.map((line) => ` ${line}`)
.join('\n')}`
})
.filter(Boolean)
@@ -1,12 +1,12 @@
import { render } from './vue-testing-library'
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'
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 = {}
if (typeof input === 'string') {
@@ -164,7 +164,7 @@ describe('Keyboard', () => {
let state = { readyToCapture: false }
function createProps(id: string) {
return events.reduce(
return events.reduce<Record<string, string | ((event: any) => void)>>(
(props, name) => {
props[name] = (event: any) => {
if (!state.readyToCapture) return
@@ -202,7 +202,7 @@ describe('Keyboard', () => {
await type([key(input)])
let expected = result.map(e => event(e))
let expected = result.map((e) => event(e))
expect(fired.length).toEqual(result.length)
@@ -36,7 +36,7 @@ export function shift(event: 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(() => {
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]
for (let action of actions) {
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, {
type: action.name,
@@ -344,8 +344,8 @@ let focusableSelector = [
? // TODO: Remove this once JSDOM fixes the issue where an element that is
// "hidden" can be the document.activeElement, because this is not possible
// in real browsers.
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: selector => `${selector}:not([tabindex='-1'])`
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: (selector) => `${selector}:not([tabindex='-1'])`
)
.join(',')
@@ -5,10 +5,10 @@ type FunctionPropertyNames<T> = {
export function suppressConsoleLogs<T extends unknown[]>(
cb: (...args: T) => void,
type: FunctionPropertyNames<typeof global.console> = 'warn'
type: FunctionPropertyNames<typeof globalThis.console> = 'warn'
) {
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) => {
Promise.resolve(cb(...args)).then(resolve, reject)
@@ -40,7 +40,7 @@ export function calculateActiveIndex<TItem>(
let nextActiveIndex = (() => {
switch (action.focus) {
case Focus.First:
return items.findIndex(item => !resolvers.resolveDisabled(item))
return items.findIndex((item) => !resolvers.resolveDisabled(item))
case Focus.Previous: {
let idx = items
@@ -64,13 +64,13 @@ export function calculateActiveIndex<TItem>(
let idx = items
.slice()
.reverse()
.findIndex(item => !resolvers.resolveDisabled(item))
.findIndex((item) => !resolvers.resolveDisabled(item))
if (idx === -1) return idx
return items.length - 1 - idx
}
case Focus.Specific:
return items.findIndex(item => resolvers.resolveId(item) === action.id)
return items.findIndex((item) => resolvers.resolveId(item) === action.id)
case Focus.Nothing:
return null
@@ -18,8 +18,8 @@ let focusableSelector = [
? // TODO: Remove this once JSDOM fixes the issue where an element that is
// "hidden" can be the document.activeElement, because this is not possible
// in real browsers.
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: selector => `${selector}:not([tabindex='-1'])`
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: (selector) => `${selector}:not([tabindex='-1'])`
)
.join(',')
+1 -1
View File
@@ -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(
lookup
)
.map(key => `"${key}"`)
.map((key) => `"${key}"`)
.join(', ')}.`
)
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 } 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 }
if (typeof input === 'string') {
@@ -34,9 +34,7 @@ describe('Validation', () => {
expect.hasAssertions()
renderTemplate({
template: html`
<Dummy as="template" class="abc">Contents</Dummy>
`,
template: html` <Dummy as="template" class="abc">Contents</Dummy> `,
errorCaptured(err) {
expect(err as Error).toEqual(
new Error(
+2 -2
View File
@@ -98,7 +98,7 @@ function _render({
`However we need to passthrough the following props:`,
Object.keys(passThroughProps)
.concat(Object.keys(attrs))
.map(line => ` - ${line}`)
.map((line) => ` - ${line}`)
.join('\n'),
'',
'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".',
'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')
)
-1
View File
@@ -24,7 +24,6 @@
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true
},

Some files were not shown because too many files have changed in this diff Show More