Skip to content

Commit 0c34fe8

Browse files
authored
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
1 parent 591b328 commit 0c34fe8

File tree

13 files changed

+49
-34
lines changed

13 files changed

+49
-34
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3636
- 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))
3737
- Allow `Enter` for form submit in `RadioGroup`, `Switch` and `Combobox` improvements ([#1285](https://github.com/tailwindlabs/headlessui/pull/1285))
3838
- add React 18 compatibility ([#1326](https://github.com/tailwindlabs/headlessui/pull/1326))
39+
- Add explicit `multiple` prop ([#1355](https://github.com/tailwindlabs/headlessui/pull/1355))
3940

4041
### Added
4142

@@ -75,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7576
- Resolve `initialFocusRef` correctly ([#1276](https://github.com/tailwindlabs/headlessui/pull/1276))
7677
- 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))
7778
- Allow `Enter` for form submit in `RadioGroup`, `Switch` and `Combobox` improvements ([#1285](https://github.com/tailwindlabs/headlessui/pull/1285))
79+
- Add explicit `multiple` prop ([#1355](https://github.com/tailwindlabs/headlessui/pull/1355))
7880

7981
### Added
8082

packages/@headlessui-react/src/components/combobox/combobox.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4594,7 +4594,7 @@ describe('Multi-select', () => {
45944594
let [value, setValue] = useState<string[]>(['bob', 'charlie'])
45954595

45964596
return (
4597-
<Combobox value={value} onChange={setValue}>
4597+
<Combobox value={value} onChange={setValue} multiple>
45984598
<Combobox.Input onChange={() => {}} />
45994599
<Combobox.Button>Trigger</Combobox.Button>
46004600
<Combobox.Options>
@@ -4630,7 +4630,7 @@ describe('Multi-select', () => {
46304630
let [value, setValue] = useState<string[]>(['bob', 'charlie'])
46314631

46324632
return (
4633-
<Combobox value={value} onChange={setValue}>
4633+
<Combobox value={value} onChange={setValue} multiple>
46344634
<Combobox.Input onChange={() => {}} />
46354635
<Combobox.Button>Trigger</Combobox.Button>
46364636
<Combobox.Options>
@@ -4659,7 +4659,7 @@ describe('Multi-select', () => {
46594659
let [value, setValue] = useState<string[]>(['bob', 'charlie'])
46604660

46614661
return (
4662-
<Combobox value={value} onChange={setValue}>
4662+
<Combobox value={value} onChange={setValue} multiple>
46634663
<Combobox.Input onChange={() => {}} />
46644664
<Combobox.Button>Trigger</Combobox.Button>
46654665
<Combobox.Options>
@@ -4692,7 +4692,7 @@ describe('Multi-select', () => {
46924692
let [value, setValue] = useState<string[]>(['bob', 'charlie'])
46934693

46944694
return (
4695-
<Combobox value={value} onChange={setValue}>
4695+
<Combobox value={value} onChange={setValue} multiple>
46964696
<Combobox.Input onChange={() => {}} />
46974697
<Combobox.Button>Trigger</Combobox.Button>
46984698
<Combobox.Options>

packages/@headlessui-react/src/components/combobox/combobox.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,14 +340,15 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
340340
props: Props<
341341
TTag,
342342
ComboboxRenderPropArg<TType>,
343-
'value' | 'onChange' | 'disabled' | 'name' | 'nullable'
343+
'value' | 'onChange' | 'disabled' | 'name' | 'nullable' | 'multiple'
344344
> & {
345345
value: TType
346346
onChange(value: TType): void
347347
disabled?: boolean
348348
__demoMode?: boolean
349349
name?: string
350350
nullable?: boolean
351+
multiple?: boolean
351352
},
352353
ref: Ref<TTag>
353354
) {
@@ -358,20 +359,21 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
358359
disabled = false,
359360
__demoMode = false,
360361
nullable = false,
362+
multiple = false,
361363
...theirProps
362364
} = props
363365
let defaultToFirstOption = useRef(false)
364366

365367
let comboboxPropsRef = useRef<StateDefinition['comboboxPropsRef']['current']>({
366368
value,
367-
mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single,
369+
mode: multiple ? ValueMode.Multi : ValueMode.Single,
368370
onChange,
369371
nullable,
370372
__demoMode,
371373
})
372374

373375
comboboxPropsRef.current.value = value
374-
comboboxPropsRef.current.mode = Array.isArray(value) ? ValueMode.Multi : ValueMode.Single
376+
comboboxPropsRef.current.mode = multiple ? ValueMode.Multi : ValueMode.Single
375377
comboboxPropsRef.current.nullable = nullable
376378

377379
let optionsPropsRef = useRef<StateDefinition['optionsPropsRef']['current']>({
@@ -411,7 +413,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
411413
let dataBag = useMemo<Exclude<ContextType<typeof ComboboxData>, null>>(
412414
() => ({
413415
value,
414-
mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single,
416+
mode: multiple ? ValueMode.Multi : ValueMode.Single,
415417
get activeOptionIndex() {
416418
if (defaultToFirstOption.current && _activeOptionIndex === null && options.length > 0) {
417419
let localActiveOptionIndex = options.findIndex(

packages/@headlessui-react/src/components/listbox/listbox.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3963,7 +3963,7 @@ describe('Multi-select', () => {
39633963
let [value, setValue] = useState<string[]>(['bob', 'charlie'])
39643964

39653965
return (
3966-
<Listbox value={value} onChange={setValue}>
3966+
<Listbox value={value} onChange={setValue} multiple>
39673967
<Listbox.Button>Trigger</Listbox.Button>
39683968
<Listbox.Options>
39693969
<Listbox.Option value="alice">alice</Listbox.Option>
@@ -3998,7 +3998,7 @@ describe('Multi-select', () => {
39983998
let [value, setValue] = useState<string[]>(['bob', 'charlie'])
39993999

40004000
return (
4001-
<Listbox value={value} onChange={setValue}>
4001+
<Listbox value={value} onChange={setValue} multiple>
40024002
<Listbox.Button>Trigger</Listbox.Button>
40034003
<Listbox.Options>
40044004
<Listbox.Option value="alice">alice</Listbox.Option>
@@ -4026,7 +4026,7 @@ describe('Multi-select', () => {
40264026
let [value, setValue] = useState<string[]>(['bob', 'charlie'])
40274027

40284028
return (
4029-
<Listbox value={value} onChange={setValue}>
4029+
<Listbox value={value} onChange={setValue} multiple>
40304030
<Listbox.Button>Trigger</Listbox.Button>
40314031
<Listbox.Options>
40324032
<Listbox.Option value="alice">alice</Listbox.Option>
@@ -4058,7 +4058,7 @@ describe('Multi-select', () => {
40584058
let [value, setValue] = useState<string[]>(['bob', 'charlie'])
40594059

40604060
return (
4061-
<Listbox value={value} onChange={setValue}>
4061+
<Listbox value={value} onChange={setValue} multiple>
40624062
<Listbox.Button>Trigger</Listbox.Button>
40634063
<Listbox.Options>
40644064
<Listbox.Option value="alice">alice</Listbox.Option>

packages/@headlessui-react/src/components/listbox/listbox.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,24 +304,33 @@ let ListboxRoot = forwardRefWithAs(function Listbox<
304304
props: Props<
305305
TTag,
306306
ListboxRenderPropArg,
307-
'value' | 'onChange' | 'disabled' | 'horizontal' | 'name'
307+
'value' | 'onChange' | 'disabled' | 'horizontal' | 'name' | 'multiple'
308308
> & {
309309
value: TType
310310
onChange(value: TType): void
311311
disabled?: boolean
312312
horizontal?: boolean
313313
name?: string
314+
multiple?: boolean
314315
},
315316
ref: Ref<TTag>
316317
) {
317-
let { value, name, onChange, disabled = false, horizontal = false, ...theirProps } = props
318+
let {
319+
value,
320+
name,
321+
onChange,
322+
disabled = false,
323+
horizontal = false,
324+
multiple = false,
325+
...theirProps
326+
} = props
318327
const orientation = horizontal ? 'horizontal' : 'vertical'
319328
let listboxRef = useSyncRefs(ref)
320329

321330
let reducerBag = useReducer(stateReducer, {
322331
listboxState: ListboxStates.Closed,
323332
propsRef: {
324-
current: { value, onChange, mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single },
333+
current: { value, onChange, mode: multiple ? ValueMode.Multi : ValueMode.Single },
325334
},
326335
labelRef: createRef(),
327336
buttonRef: createRef(),
@@ -336,7 +345,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox<
336345
let [{ listboxState, propsRef, optionsRef, buttonRef }, dispatch] = reducerBag
337346

338347
propsRef.current.value = value
339-
propsRef.current.mode = Array.isArray(value) ? ValueMode.Multi : ValueMode.Single
348+
propsRef.current.mode = multiple ? ValueMode.Multi : ValueMode.Single
340349

341350
useIsoMorphicEffect(() => {
342351
propsRef.current.onChange = (value: unknown) => {

packages/@headlessui-vue/src/components/combobox/combobox.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4821,7 +4821,7 @@ describe('Multi-select', () => {
48214821
suppressConsoleLogs(async () => {
48224822
renderTemplate({
48234823
template: html`
4824-
<Combobox v-model="value">
4824+
<Combobox v-model="value" multiple>
48254825
<ComboboxInput />
48264826
<ComboboxButton>Trigger</ComboboxButton>
48274827
<ComboboxOptions>
@@ -4854,7 +4854,7 @@ describe('Multi-select', () => {
48544854
suppressConsoleLogs(async () => {
48554855
renderTemplate({
48564856
template: html`
4857-
<Combobox v-model="value">
4857+
<Combobox v-model="value" multiple>
48584858
<ComboboxInput />
48594859
<ComboboxButton>Trigger</ComboboxButton>
48604860
<ComboboxOptions>
@@ -4880,7 +4880,7 @@ describe('Multi-select', () => {
48804880
suppressConsoleLogs(async () => {
48814881
renderTemplate({
48824882
template: html`
4883-
<Combobox v-model="value">
4883+
<Combobox v-model="value" multiple>
48844884
<ComboboxInput />
48854885
<ComboboxButton>Trigger</ComboboxButton>
48864886
<ComboboxOptions>
@@ -4910,7 +4910,7 @@ describe('Multi-select', () => {
49104910
suppressConsoleLogs(async () => {
49114911
renderTemplate({
49124912
template: html`
4913-
<Combobox v-model="value">
4913+
<Combobox v-model="value" multiple>
49144914
<ComboboxInput />
49154915
<ComboboxButton>Trigger</ComboboxButton>
49164916
<ComboboxOptions>
@@ -4954,7 +4954,7 @@ describe('Multi-select', () => {
49544954
suppressConsoleLogs(async () => {
49554955
renderTemplate({
49564956
template: html`
4957-
<Combobox v-model="value">
4957+
<Combobox v-model="value" multiple>
49584958
<ComboboxInput />
49594959
<ComboboxButton>Trigger</ComboboxButton>
49604960
<ComboboxOptions>

packages/@headlessui-vue/src/components/combobox/combobox.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export let Combobox = defineComponent({
113113
modelValue: { type: [Object, String, Number, Boolean] },
114114
name: { type: String },
115115
nullable: { type: Boolean, default: false },
116+
multiple: { type: [Boolean], default: false },
116117
},
117118
setup(props, { slots, attrs, emit }) {
118119
let comboboxState = ref<StateDefinition['comboboxState']['value']>(ComboboxStates.Closed)
@@ -163,7 +164,7 @@ export let Combobox = defineComponent({
163164
}
164165

165166
let value = computed(() => props.modelValue)
166-
let mode = computed(() => (Array.isArray(value.value) ? ValueMode.Multi : ValueMode.Single))
167+
let mode = computed(() => (props.multiple ? ValueMode.Multi : ValueMode.Single))
167168
let nullable = computed(() => props.nullable)
168169

169170
let api = {
@@ -444,7 +445,7 @@ export let Combobox = defineComponent({
444445
)
445446
: []),
446447
render({
447-
props: omit(incomingProps, ['nullable', 'onUpdate:modelValue']),
448+
props: omit(incomingProps, ['nullable', 'multiple', 'onUpdate:modelValue']),
448449
slot,
449450
slots,
450451
attrs,

packages/@headlessui-vue/src/components/listbox/listbox.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4086,7 +4086,7 @@ describe('Multi-select', () => {
40864086
suppressConsoleLogs(async () => {
40874087
renderTemplate({
40884088
template: html`
4089-
<Listbox v-model="value">
4089+
<Listbox v-model="value" multiple>
40904090
<ListboxButton>Trigger</ListboxButton>
40914091
<ListboxOptions>
40924092
<ListboxOption value="alice">alice</ListboxOption>
@@ -4118,7 +4118,7 @@ describe('Multi-select', () => {
41184118
suppressConsoleLogs(async () => {
41194119
renderTemplate({
41204120
template: html`
4121-
<Listbox v-model="value">
4121+
<Listbox v-model="value" multiple>
41224122
<ListboxButton>Trigger</ListboxButton>
41234123
<ListboxOptions>
41244124
<ListboxOption value="alice">alice</ListboxOption>
@@ -4143,7 +4143,7 @@ describe('Multi-select', () => {
41434143
suppressConsoleLogs(async () => {
41444144
renderTemplate({
41454145
template: html`
4146-
<Listbox v-model="value">
4146+
<Listbox v-model="value" multiple>
41474147
<ListboxButton>Trigger</ListboxButton>
41484148
<ListboxOptions>
41494149
<ListboxOption value="alice">alice</ListboxOption>
@@ -4172,7 +4172,7 @@ describe('Multi-select', () => {
41724172
suppressConsoleLogs(async () => {
41734173
renderTemplate({
41744174
template: html`
4175-
<Listbox v-model="value">
4175+
<Listbox v-model="value" multiple>
41764176
<ListboxButton>Trigger</ListboxButton>
41774177
<ListboxOptions>
41784178
<ListboxOption value="alice">alice</ListboxOption>
@@ -4215,7 +4215,7 @@ describe('Multi-select', () => {
42154215
suppressConsoleLogs(async () => {
42164216
renderTemplate({
42174217
template: html`
4218-
<Listbox v-model="value">
4218+
<Listbox v-model="value" multiple>
42194219
<ListboxButton>Trigger</ListboxButton>
42204220
<ListboxOptions>
42214221
<ListboxOption v-for="person in people" :value="person"

packages/@headlessui-vue/src/components/listbox/listbox.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export let Listbox = defineComponent({
113113
horizontal: { type: [Boolean], default: false },
114114
modelValue: { type: [Object, String, Number, Boolean] },
115115
name: { type: String, optional: true },
116+
multiple: { type: [Boolean], default: false },
116117
},
117118
setup(props, { slots, attrs, emit }) {
118119
let listboxState = ref<StateDefinition['listboxState']['value']>(ListboxStates.Closed)
@@ -156,7 +157,7 @@ export let Listbox = defineComponent({
156157
}
157158

158159
let value = computed(() => props.modelValue)
159-
let mode = computed(() => (Array.isArray(value.value) ? ValueMode.Multi : ValueMode.Single))
160+
let mode = computed(() => (props.multiple ? ValueMode.Multi : ValueMode.Single))
160161

161162
let api = {
162163
listboxState,
@@ -327,7 +328,7 @@ export let Listbox = defineComponent({
327328
)
328329
: []),
329330
render({
330-
props: omit(incomingProps, ['onUpdate:modelValue', 'horizontal']),
331+
props: omit(incomingProps, ['onUpdate:modelValue', 'horizontal', 'multiple']),
331332
slot,
332333
slots,
333334
attrs,

packages/playground-react/pages/combobox/multi-select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function MultiPeopleList() {
3939
console.log([...new FormData(e.currentTarget).entries()])
4040
}}
4141
>
42-
<Combobox value={activePersons} onChange={setActivePersons} name="people">
42+
<Combobox value={activePersons} onChange={setActivePersons} name="people" multiple>
4343
<Combobox.Label className="block text-sm font-medium leading-5 text-gray-700">
4444
Assigned to
4545
</Combobox.Label>

0 commit comments

Comments
 (0)