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)
},