diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 5ca11a8b77..63bcef9381 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482), [#1717](https://github.com/tailwindlabs/headlessui/pull/1717)) +- Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482), [#1717](https://github.com/tailwindlabs/headlessui/pull/1717), [#1814](https://github.com/tailwindlabs/headlessui/pull/1814)) - Add `@headlessui/tailwindcss` plugin ([#1487](https://github.com/tailwindlabs/headlessui/pull/1487)) ### Fixed diff --git a/packages/@headlessui-react/src/components/listbox/listbox.test.tsx b/packages/@headlessui-react/src/components/listbox/listbox.test.tsx index 0b6af6ec25..1cb824903c 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.test.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.test.tsx @@ -264,6 +264,97 @@ describe('Rendering', () => { }) ) + it( + 'should be possible to use the by prop (as a string) with a null initial value', + suppressConsoleLogs(async () => { + function Example() { + let [value, setValue] = useState(null) + + return ( + + Trigger + + alice + bob + charlie + + + ) + } + + render() + + await click(getListboxButton()) + let [alice, bob, charlie] = getListboxOptions() + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'false') + expect(charlie).toHaveAttribute('aria-selected', 'false') + + await click(getListboxOptions()[2]) + await click(getListboxButton()) + ;[alice, bob, charlie] = getListboxOptions() + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'false') + expect(charlie).toHaveAttribute('aria-selected', 'true') + + await click(getListboxOptions()[1]) + await click(getListboxButton()) + ;[alice, bob, charlie] = getListboxOptions() + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'true') + expect(charlie).toHaveAttribute('aria-selected', 'false') + }) + ) + + // TODO: Does this test prove anything useful? + it( + 'should be possible to use the by prop (as a string) with a null listbox option', + suppressConsoleLogs(async () => { + function Example() { + let [value, setValue] = useState(null) + + return ( + + Trigger + + + Please select an option + + alice + bob + charlie + + + ) + } + + render() + + await click(getListboxButton()) + let [disabled, alice, bob, charlie] = getListboxOptions() + expect(disabled).toHaveAttribute('aria-selected', 'true') + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'false') + expect(charlie).toHaveAttribute('aria-selected', 'false') + + await click(getListboxOptions()[3]) + await click(getListboxButton()) + ;[disabled, alice, bob, charlie] = getListboxOptions() + expect(disabled).toHaveAttribute('aria-selected', 'false') + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'false') + expect(charlie).toHaveAttribute('aria-selected', 'true') + + await click(getListboxOptions()[2]) + await click(getListboxButton()) + ;[disabled, alice, bob, charlie] = getListboxOptions() + expect(disabled).toHaveAttribute('aria-selected', 'false') + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'true') + expect(charlie).toHaveAttribute('aria-selected', 'false') + }) + ) + it( 'should be possible to use completely new objects while rendering (single mode)', suppressConsoleLogs(async () => { diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index b8a5798c4a..2cfcf032fc 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -353,7 +353,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox< typeof by === 'string' ? (a: TActualType, z: TActualType) => { let property = by as unknown as keyof TActualType - return a[property] === z[property] + return a?.[property] === z?.[property] } : by ), diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index 42da3b2baf..132af10b95 100644 --- a/packages/@headlessui-vue/CHANGELOG.md +++ b/packages/@headlessui-vue/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482), [#1717](https://github.com/tailwindlabs/headlessui/pull/1717)) +- Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482), [#1717](https://github.com/tailwindlabs/headlessui/pull/1717), [#1814](https://github.com/tailwindlabs/headlessui/pull/1814)) - Add `@headlessui/tailwindcss` plugin ([#1487](https://github.com/tailwindlabs/headlessui/pull/1487)) ### Fixed diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx b/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx index ee6b062d2d..c09f101f52 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx +++ b/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx @@ -289,6 +289,95 @@ describe('Rendering', () => { }) ) + it( + 'should be possible to use the by prop (as a string) with a null initial value', + suppressConsoleLogs(async () => { + renderTemplate({ + template: html` + + Trigger + + alice + bob + charlie + + + `, + setup: () => { + let value = ref(null) + return { options, value } + }, + }) + + await click(getListboxButton()) + let [alice, bob, charlie] = getListboxOptions() + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'false') + expect(charlie).toHaveAttribute('aria-selected', 'false') + + await click(getListboxOptions()[2]) + await click(getListboxButton()) + ;[alice, bob, charlie] = getListboxOptions() + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'false') + expect(charlie).toHaveAttribute('aria-selected', 'true') + + await click(getListboxOptions()[1]) + await click(getListboxButton()) + ;[alice, bob, charlie] = getListboxOptions() + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'true') + expect(charlie).toHaveAttribute('aria-selected', 'false') + }) + ) + + // TODO: Does this test prove anything useful? + it( + 'should be possible to use the by prop (as a string) with a null listbox option', + suppressConsoleLogs(async () => { + renderTemplate({ + template: html` + + Trigger + + Please select an option + alice + bob + charlie + + + `, + setup: () => { + let value = ref(null) + return { options, value } + }, + }) + + await click(getListboxButton()) + let [disabled, alice, bob, charlie] = getListboxOptions() + expect(disabled).toHaveAttribute('aria-selected', 'true') + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'false') + expect(charlie).toHaveAttribute('aria-selected', 'false') + + await click(getListboxOptions()[3]) + await click(getListboxButton()) + ;[disabled, alice, bob, charlie] = getListboxOptions() + expect(disabled).toHaveAttribute('aria-selected', 'false') + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'false') + expect(charlie).toHaveAttribute('aria-selected', 'true') + + await click(getListboxOptions()[2]) + await click(getListboxButton()) + ;[disabled, alice, bob, charlie] = getListboxOptions() + expect(disabled).toHaveAttribute('aria-selected', 'false') + expect(alice).toHaveAttribute('aria-selected', 'false') + expect(bob).toHaveAttribute('aria-selected', 'true') + expect(charlie).toHaveAttribute('aria-selected', 'false') + }) + ) + it( 'should be possible to use completely new objects while rendering (single mode)', suppressConsoleLogs(async () => { diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.ts b/packages/@headlessui-vue/src/components/listbox/listbox.ts index 7a4e7908be..44cb3b1c0a 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.ts +++ b/packages/@headlessui-vue/src/components/listbox/listbox.ts @@ -180,7 +180,7 @@ export let Listbox = defineComponent({ compare(a: any, z: any) { if (typeof props.by === 'string') { let property = props.by as unknown as any - return a[property] === z[property] + return a?.[property] === z?.[property] } return props.by(a, z) },