From 0c34fe802c982a3591af89431590bc7cfde19e4d Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 22 Apr 2022 18:55:55 +0200 Subject: [PATCH] Add explicit `multiple` prop (#1355) * add explicit `multiple` prop to the `Combobox` This allows you to set the value to a **tuple** in `single-value` mode, which was not possible before the `multiple` prop was introduced, because then it resulted in `multi-value` mode instead of `single-value` mode. * add explicit `multiple` prop to the `Listbox` This allows you to set the value to a **tuple** in `single-value` mode, which was not possible before the `multiple` prop was introduced, because then it resulted in `multi-value` mode instead of `single-value` mode. * update changelog * update playground to use `multiple` prop --- CHANGELOG.md | 2 ++ .../src/components/combobox/combobox.test.tsx | 8 ++++---- .../src/components/combobox/combobox.tsx | 10 ++++++---- .../src/components/listbox/listbox.test.tsx | 8 ++++---- .../src/components/listbox/listbox.tsx | 17 +++++++++++++---- .../src/components/combobox/combobox.test.ts | 10 +++++----- .../src/components/combobox/combobox.ts | 5 +++-- .../src/components/listbox/listbox.test.tsx | 10 +++++----- .../src/components/listbox/listbox.ts | 5 +++-- .../pages/combobox/multi-select.tsx | 2 +- .../pages/listbox/multi-select.tsx | 2 +- .../src/components/combobox/multi-select.vue | 2 +- .../src/components/listbox/multi-select.vue | 2 +- 13 files changed, 49 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f693d3c72..58dbb730e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure that there is always an active option in the `Combobox` ([#1279](https://github.com/tailwindlabs/headlessui/pull/1279), [#1281](https://github.com/tailwindlabs/headlessui/pull/1281)) - Allow `Enter` for form submit in `RadioGroup`, `Switch` and `Combobox` improvements ([#1285](https://github.com/tailwindlabs/headlessui/pull/1285)) - add React 18 compatibility ([#1326](https://github.com/tailwindlabs/headlessui/pull/1326)) +- Add explicit `multiple` prop ([#1355](https://github.com/tailwindlabs/headlessui/pull/1355)) ### Added @@ -75,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Resolve `initialFocusRef` correctly ([#1276](https://github.com/tailwindlabs/headlessui/pull/1276)) - Ensure that there is always an active option in the `Combobox` ([#1279](https://github.com/tailwindlabs/headlessui/pull/1279), [#1281](https://github.com/tailwindlabs/headlessui/pull/1281)) - Allow `Enter` for form submit in `RadioGroup`, `Switch` and `Combobox` improvements ([#1285](https://github.com/tailwindlabs/headlessui/pull/1285)) +- Add explicit `multiple` prop ([#1355](https://github.com/tailwindlabs/headlessui/pull/1355)) ### Added diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx index 1143142fd4..358676fec7 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx @@ -4594,7 +4594,7 @@ describe('Multi-select', () => { let [value, setValue] = useState(['bob', 'charlie']) return ( - + {}} /> Trigger @@ -4630,7 +4630,7 @@ describe('Multi-select', () => { let [value, setValue] = useState(['bob', 'charlie']) return ( - + {}} /> Trigger @@ -4659,7 +4659,7 @@ describe('Multi-select', () => { let [value, setValue] = useState(['bob', 'charlie']) return ( - + {}} /> Trigger @@ -4692,7 +4692,7 @@ describe('Multi-select', () => { let [value, setValue] = useState(['bob', 'charlie']) return ( - + {}} /> Trigger diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index dba8197576..38db7e71f4 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -340,7 +340,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< props: Props< TTag, ComboboxRenderPropArg, - 'value' | 'onChange' | 'disabled' | 'name' | 'nullable' + 'value' | 'onChange' | 'disabled' | 'name' | 'nullable' | 'multiple' > & { value: TType onChange(value: TType): void @@ -348,6 +348,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< __demoMode?: boolean name?: string nullable?: boolean + multiple?: boolean }, ref: Ref ) { @@ -358,20 +359,21 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< disabled = false, __demoMode = false, nullable = false, + multiple = false, ...theirProps } = props let defaultToFirstOption = useRef(false) let comboboxPropsRef = useRef({ value, - mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single, + mode: multiple ? ValueMode.Multi : ValueMode.Single, onChange, nullable, __demoMode, }) comboboxPropsRef.current.value = value - comboboxPropsRef.current.mode = Array.isArray(value) ? ValueMode.Multi : ValueMode.Single + comboboxPropsRef.current.mode = multiple ? ValueMode.Multi : ValueMode.Single comboboxPropsRef.current.nullable = nullable let optionsPropsRef = useRef({ @@ -411,7 +413,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< let dataBag = useMemo, null>>( () => ({ value, - mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single, + mode: multiple ? ValueMode.Multi : ValueMode.Single, get activeOptionIndex() { if (defaultToFirstOption.current && _activeOptionIndex === null && options.length > 0) { let localActiveOptionIndex = options.findIndex( diff --git a/packages/@headlessui-react/src/components/listbox/listbox.test.tsx b/packages/@headlessui-react/src/components/listbox/listbox.test.tsx index 30a445c5d1..c20e005057 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.test.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.test.tsx @@ -3963,7 +3963,7 @@ describe('Multi-select', () => { let [value, setValue] = useState(['bob', 'charlie']) return ( - + Trigger alice @@ -3998,7 +3998,7 @@ describe('Multi-select', () => { let [value, setValue] = useState(['bob', 'charlie']) return ( - + Trigger alice @@ -4026,7 +4026,7 @@ describe('Multi-select', () => { let [value, setValue] = useState(['bob', 'charlie']) return ( - + Trigger alice @@ -4058,7 +4058,7 @@ describe('Multi-select', () => { let [value, setValue] = useState(['bob', 'charlie']) return ( - + Trigger alice diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index f832b15267..5e8876308a 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -304,24 +304,33 @@ let ListboxRoot = forwardRefWithAs(function Listbox< props: Props< TTag, ListboxRenderPropArg, - 'value' | 'onChange' | 'disabled' | 'horizontal' | 'name' + 'value' | 'onChange' | 'disabled' | 'horizontal' | 'name' | 'multiple' > & { value: TType onChange(value: TType): void disabled?: boolean horizontal?: boolean name?: string + multiple?: boolean }, ref: Ref ) { - let { value, name, onChange, disabled = false, horizontal = false, ...theirProps } = props + let { + value, + name, + onChange, + disabled = false, + horizontal = false, + multiple = false, + ...theirProps + } = props const orientation = horizontal ? 'horizontal' : 'vertical' let listboxRef = useSyncRefs(ref) let reducerBag = useReducer(stateReducer, { listboxState: ListboxStates.Closed, propsRef: { - current: { value, onChange, mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single }, + current: { value, onChange, mode: multiple ? ValueMode.Multi : ValueMode.Single }, }, labelRef: createRef(), buttonRef: createRef(), @@ -336,7 +345,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox< let [{ listboxState, propsRef, optionsRef, buttonRef }, dispatch] = reducerBag propsRef.current.value = value - propsRef.current.mode = Array.isArray(value) ? ValueMode.Multi : ValueMode.Single + propsRef.current.mode = multiple ? ValueMode.Multi : ValueMode.Single useIsoMorphicEffect(() => { propsRef.current.onChange = (value: unknown) => { diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts index 0e5368cb2e..74375525ec 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts @@ -4821,7 +4821,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger @@ -4854,7 +4854,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger @@ -4880,7 +4880,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger @@ -4910,7 +4910,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger @@ -4954,7 +4954,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.ts b/packages/@headlessui-vue/src/components/combobox/combobox.ts index 60246ef6ff..4d87c15e00 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts @@ -113,6 +113,7 @@ export let Combobox = defineComponent({ modelValue: { type: [Object, String, Number, Boolean] }, name: { type: String }, nullable: { type: Boolean, default: false }, + multiple: { type: [Boolean], default: false }, }, setup(props, { slots, attrs, emit }) { let comboboxState = ref(ComboboxStates.Closed) @@ -163,7 +164,7 @@ export let Combobox = defineComponent({ } let value = computed(() => props.modelValue) - let mode = computed(() => (Array.isArray(value.value) ? ValueMode.Multi : ValueMode.Single)) + let mode = computed(() => (props.multiple ? ValueMode.Multi : ValueMode.Single)) let nullable = computed(() => props.nullable) let api = { @@ -444,7 +445,7 @@ export let Combobox = defineComponent({ ) : []), render({ - props: omit(incomingProps, ['nullable', 'onUpdate:modelValue']), + props: omit(incomingProps, ['nullable', 'multiple', 'onUpdate:modelValue']), slot, slots, attrs, diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx b/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx index f2aeca3c45..433a078cd4 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx +++ b/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx @@ -4086,7 +4086,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger alice @@ -4118,7 +4118,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger alice @@ -4143,7 +4143,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger alice @@ -4172,7 +4172,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger alice @@ -4215,7 +4215,7 @@ describe('Multi-select', () => { suppressConsoleLogs(async () => { renderTemplate({ template: html` - + Trigger (ListboxStates.Closed) @@ -156,7 +157,7 @@ export let Listbox = defineComponent({ } let value = computed(() => props.modelValue) - let mode = computed(() => (Array.isArray(value.value) ? ValueMode.Multi : ValueMode.Single)) + let mode = computed(() => (props.multiple ? ValueMode.Multi : ValueMode.Single)) let api = { listboxState, @@ -327,7 +328,7 @@ export let Listbox = defineComponent({ ) : []), render({ - props: omit(incomingProps, ['onUpdate:modelValue', 'horizontal']), + props: omit(incomingProps, ['onUpdate:modelValue', 'horizontal', 'multiple']), slot, slots, attrs, diff --git a/packages/playground-react/pages/combobox/multi-select.tsx b/packages/playground-react/pages/combobox/multi-select.tsx index d2936f067b..bda0153afb 100644 --- a/packages/playground-react/pages/combobox/multi-select.tsx +++ b/packages/playground-react/pages/combobox/multi-select.tsx @@ -39,7 +39,7 @@ function MultiPeopleList() { console.log([...new FormData(e.currentTarget).entries()]) }} > - + Assigned to diff --git a/packages/playground-react/pages/listbox/multi-select.tsx b/packages/playground-react/pages/listbox/multi-select.tsx index 6e5c418bda..82c416c721 100644 --- a/packages/playground-react/pages/listbox/multi-select.tsx +++ b/packages/playground-react/pages/listbox/multi-select.tsx @@ -38,7 +38,7 @@ function MultiPeopleList() { console.log([...new FormData(e.currentTarget).entries()]) }} > - + Assigned to diff --git a/packages/playground-vue/src/components/combobox/multi-select.vue b/packages/playground-vue/src/components/combobox/multi-select.vue index 5a1e637fd3..683a49ddf9 100644 --- a/packages/playground-vue/src/components/combobox/multi-select.vue +++ b/packages/playground-vue/src/components/combobox/multi-select.vue @@ -3,7 +3,7 @@
- + Assigned to diff --git a/packages/playground-vue/src/components/listbox/multi-select.vue b/packages/playground-vue/src/components/listbox/multi-select.vue index 4e522a8f7a..dc668045b8 100644 --- a/packages/playground-vue/src/components/listbox/multi-select.vue +++ b/packages/playground-vue/src/components/listbox/multi-select.vue @@ -3,7 +3,7 @@
- + Assigned to