@zag-js/combobox 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import { createMachine, guards } from "@zag-js/core"
3
3
  import { trackDismissableElement } from "@zag-js/dismissable"
4
4
  import { observeAttributes, observeChildren, raf, scrollIntoView } from "@zag-js/dom-query"
5
5
  import { getPlacement } from "@zag-js/popper"
6
- import { addOrRemove, compact, isBoolean, isEqual, match } from "@zag-js/utils"
6
+ import { addOrRemove, compact, isArray, isBoolean, isEqual, match } from "@zag-js/utils"
7
7
  import { collection } from "./combobox.collection"
8
8
  import { dom } from "./combobox.dom"
9
9
  import type { CollectionItem, MachineContext, MachineState, UserDefinedContext } from "./combobox.types"
@@ -28,8 +28,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
28
28
  selectionBehavior: "replace",
29
29
  openOnKeyPress: true,
30
30
  openOnChange: true,
31
- dismissable: true,
32
- popup: "listbox",
31
+ composite: true,
33
32
  ...ctx,
34
33
  highlightedItem: null,
35
34
  selectedItems: [],
@@ -61,7 +60,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
61
60
  watch: {
62
61
  value: ["syncSelectedItems"],
63
62
  inputValue: ["syncInputValue"],
64
- highlightedValue: ["autofillInputValue"],
63
+ highlightedValue: ["syncHighlightedItem", "autofillInputValue"],
65
64
  multiple: ["syncSelectionBehavior"],
66
65
  open: ["toggleVisibility"],
67
66
  },
@@ -101,17 +100,17 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
101
100
  "TRIGGER.CLICK": [
102
101
  {
103
102
  guard: "isOpenControlled",
104
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"],
103
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"],
105
104
  },
106
105
  {
107
106
  target: "interacting",
108
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"],
107
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"],
109
108
  },
110
109
  ],
111
110
  "INPUT.CLICK": [
112
111
  {
113
112
  guard: "isOpenControlled",
114
- actions: ["invokeOnOpen"],
113
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"],
115
114
  },
116
115
  {
117
116
  target: "interacting",
@@ -133,14 +132,14 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
133
132
  ],
134
133
  "VALUE.CLEAR": {
135
134
  target: "focused",
136
- actions: ["clearInputValue", "clearSelectedItems"],
135
+ actions: ["clearInputValue", "clearSelectedItems", "setInitialFocus"],
137
136
  },
138
137
  },
139
138
  },
140
139
 
141
140
  focused: {
142
141
  tags: ["focused", "closed"],
143
- entry: ["focusInputOrTrigger", "scrollContentToTop", "clearHighlightedItem"],
142
+ entry: ["scrollContentToTop", "clearHighlightedItem"],
144
143
  on: {
145
144
  "CONTROLLED.OPEN": [
146
145
  {
@@ -154,12 +153,12 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
154
153
  "INPUT.CHANGE": [
155
154
  {
156
155
  guard: and("isOpenControlled", "openOnChange"),
157
- actions: ["setInputValue", "invokeOnOpen"],
156
+ actions: ["setInputValue", "invokeOnOpen", "highlightFirstItemIfNeeded"],
158
157
  },
159
158
  {
160
159
  guard: "openOnChange",
161
160
  target: "suggesting",
162
- actions: ["setInputValue", "invokeOnOpen"],
161
+ actions: ["setInputValue", "invokeOnOpen", "highlightFirstItemIfNeeded"],
163
162
  },
164
163
  {
165
164
  actions: "setInputValue",
@@ -188,11 +187,11 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
188
187
  "TRIGGER.CLICK": [
189
188
  {
190
189
  guard: "isOpenControlled",
191
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"],
190
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"],
192
191
  },
193
192
  {
194
193
  target: "interacting",
195
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"],
194
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"],
196
195
  },
197
196
  ],
198
197
  "INPUT.ARROW_DOWN": [
@@ -256,18 +255,14 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
256
255
 
257
256
  interacting: {
258
257
  tags: ["open", "focused"],
259
- activities: [
260
- "scrollIntoView",
261
- "trackDismissableLayer",
262
- "computePlacement",
263
- "hideOtherElements",
264
- "trackContentHeight",
265
- ],
258
+ entry: ["setInitialFocus"],
259
+ activities: ["scrollToHighlightedItem", "trackDismissableLayer", "computePlacement", "hideOtherElements"],
266
260
  on: {
267
261
  "CONTROLLED.CLOSE": [
268
262
  {
269
263
  guard: "restoreFocus",
270
264
  target: "focused",
265
+ actions: ["setFinalFocus"],
271
266
  },
272
267
  {
273
268
  target: "idle",
@@ -305,7 +300,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
305
300
  {
306
301
  guard: "closeOnSelect",
307
302
  target: "focused",
308
- actions: ["selectHighlightedItem", "invokeOnClose"],
303
+ actions: ["selectHighlightedItem", "invokeOnClose", "setFinalFocus"],
309
304
  },
310
305
  {
311
306
  actions: ["selectHighlightedItem"],
@@ -336,7 +331,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
336
331
  {
337
332
  guard: "closeOnSelect",
338
333
  target: "focused",
339
- actions: ["selectItem", "invokeOnClose"],
334
+ actions: ["selectItem", "invokeOnClose", "setFinalFocus"],
340
335
  },
341
336
  {
342
337
  actions: ["selectItem"],
@@ -358,7 +353,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
358
353
  },
359
354
  {
360
355
  target: "focused",
361
- actions: ["invokeOnClose"],
356
+ actions: ["invokeOnClose", "setFinalFocus"],
362
357
  },
363
358
  ],
364
359
  "TRIGGER.CLICK": [
@@ -395,11 +390,11 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
395
390
  CLOSE: [
396
391
  {
397
392
  guard: "isOpenControlled",
398
- actions: "invokeOnClose",
393
+ actions: ["invokeOnClose"],
399
394
  },
400
395
  {
401
396
  target: "focused",
402
- actions: "invokeOnClose",
397
+ actions: ["invokeOnClose", "setFinalFocus"],
403
398
  },
404
399
  ],
405
400
  "VALUE.CLEAR": [
@@ -409,7 +404,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
409
404
  },
410
405
  {
411
406
  target: "focused",
412
- actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"],
407
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose", "setFinalFocus"],
413
408
  },
414
409
  ],
415
410
  },
@@ -419,18 +414,18 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
419
414
  tags: ["open", "focused"],
420
415
  activities: [
421
416
  "trackDismissableLayer",
422
- "scrollIntoView",
417
+ "scrollToHighlightedItem",
423
418
  "computePlacement",
424
419
  "trackChildNodes",
425
420
  "hideOtherElements",
426
- "trackContentHeight",
427
421
  ],
428
- entry: ["focusInput"],
422
+ entry: ["setInitialFocus"],
429
423
  on: {
430
424
  "CONTROLLED.CLOSE": [
431
425
  {
432
426
  guard: "restoreFocus",
433
427
  target: "focused",
428
+ actions: ["setFinalFocus"],
434
429
  },
435
430
  {
436
431
  target: "idle",
@@ -441,11 +436,11 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
441
436
  },
442
437
  "INPUT.ARROW_DOWN": {
443
438
  target: "interacting",
444
- actions: "highlightNextItem",
439
+ actions: ["highlightNextItem"],
445
440
  },
446
441
  "INPUT.ARROW_UP": {
447
442
  target: "interacting",
448
- actions: "highlightPrevItem",
443
+ actions: ["highlightPrevItem"],
449
444
  },
450
445
  "INPUT.HOME": {
451
446
  target: "interacting",
@@ -463,7 +458,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
463
458
  {
464
459
  guard: "closeOnSelect",
465
460
  target: "focused",
466
- actions: ["selectHighlightedItem", "invokeOnClose"],
461
+ actions: ["selectHighlightedItem", "invokeOnClose", "setFinalFocus"],
467
462
  },
468
463
  {
469
464
  actions: ["selectHighlightedItem"],
@@ -481,19 +476,19 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
481
476
  "LAYER.ESCAPE": [
482
477
  {
483
478
  guard: "isOpenControlled",
484
- actions: "invokeOnClose",
479
+ actions: ["invokeOnClose"],
485
480
  },
486
481
  {
487
482
  target: "focused",
488
- actions: "invokeOnClose",
483
+ actions: ["invokeOnClose"],
489
484
  },
490
485
  ],
491
486
  "ITEM.POINTER_MOVE": {
492
487
  target: "interacting",
493
- actions: "setHighlightedItem",
488
+ actions: ["setHighlightedItem"],
494
489
  },
495
490
  "ITEM.POINTER_LEAVE": {
496
- actions: "clearHighlightedItem",
491
+ actions: ["clearHighlightedItem"],
497
492
  },
498
493
  "LAYER.INTERACT_OUTSIDE": [
499
494
  // == group 1 ==
@@ -509,21 +504,21 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
509
504
  // == group 2 ==
510
505
  {
511
506
  guard: "isOpenControlled",
512
- actions: "invokeOnClose",
507
+ actions: ["invokeOnClose"],
513
508
  },
514
509
  {
515
510
  target: "idle",
516
- actions: "invokeOnClose",
511
+ actions: ["invokeOnClose"],
517
512
  },
518
513
  ],
519
514
  "TRIGGER.CLICK": [
520
515
  {
521
516
  guard: "isOpenControlled",
522
- actions: "invokeOnClose",
517
+ actions: ["invokeOnClose"],
523
518
  },
524
519
  {
525
520
  target: "focused",
526
- actions: "invokeOnClose",
521
+ actions: ["invokeOnClose"],
527
522
  },
528
523
  ],
529
524
  "ITEM.CLICK": [
@@ -534,7 +529,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
534
529
  {
535
530
  guard: "closeOnSelect",
536
531
  target: "focused",
537
- actions: ["selectItem", "invokeOnClose"],
532
+ actions: ["selectItem", "invokeOnClose", "setFinalFocus"],
538
533
  },
539
534
  {
540
535
  actions: ["selectItem"],
@@ -543,11 +538,11 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
543
538
  CLOSE: [
544
539
  {
545
540
  guard: "isOpenControlled",
546
- actions: "invokeOnClose",
541
+ actions: ["invokeOnClose"],
547
542
  },
548
543
  {
549
544
  target: "focused",
550
- actions: "invokeOnClose",
545
+ actions: ["invokeOnClose", "setFinalFocus"],
551
546
  },
552
547
  ],
553
548
  "VALUE.CLEAR": [
@@ -557,7 +552,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
557
552
  },
558
553
  {
559
554
  target: "focused",
560
- actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"],
555
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose", "setFinalFocus"],
561
556
  },
562
557
  ],
563
558
  },
@@ -587,7 +582,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
587
582
 
588
583
  activities: {
589
584
  trackDismissableLayer(ctx, _evt, { send }) {
590
- if (!ctx.dismissable) return
585
+ if (ctx.disableLayer) return
591
586
  const contentEl = () => dom.getContentEl(ctx)
592
587
  return trackDismissableElement(contentEl, {
593
588
  defer: true,
@@ -624,23 +619,24 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
624
619
  trackChildNodes(ctx, _evt, { send }) {
625
620
  if (!ctx.autoHighlight) return
626
621
  const exec = () => send("CHILDREN_CHANGE")
627
- raf(() => exec())
628
622
  const contentEl = () => dom.getContentEl(ctx)
629
623
  return observeChildren(contentEl, {
630
624
  callback: exec,
631
625
  defer: true,
632
626
  })
633
627
  },
634
- scrollIntoView(ctx, _evt, { getState }) {
628
+ scrollToHighlightedItem(ctx, _evt, { getState }) {
635
629
  const inputEl = dom.getInputEl(ctx)
636
630
 
631
+ let cleanups: VoidFunction[] = []
632
+
637
633
  const exec = (immediate: boolean) => {
638
634
  const state = getState()
639
635
 
640
- const pointer = state.event.type.startsWith("ITEM.POINTER")
636
+ const pointer = state.event.type.includes("POINTER")
641
637
  if (pointer || !ctx.highlightedValue) return
642
638
 
643
- const optionEl = dom.getHighlightedItemEl(ctx)
639
+ const itemEl = dom.getHighlightedItemEl(ctx)
644
640
  const contentEl = dom.getContentEl(ctx)
645
641
 
646
642
  if (ctx.scrollToIndexFn) {
@@ -649,39 +645,24 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
649
645
  return
650
646
  }
651
647
 
652
- scrollIntoView(optionEl, { rootEl: contentEl, block: "nearest" })
648
+ const rafCleanup = raf(() => {
649
+ scrollIntoView(itemEl, { rootEl: contentEl, block: "nearest" })
650
+ })
651
+ cleanups.push(rafCleanup)
653
652
  }
654
653
 
655
- raf(() => exec(true))
656
- return observeAttributes(inputEl, {
654
+ const rafCleanup = raf(() => exec(true))
655
+ cleanups.push(rafCleanup)
656
+
657
+ const observerCleanup = observeAttributes(inputEl, {
657
658
  attributes: ["aria-activedescendant"],
658
659
  callback: () => exec(false),
659
660
  })
660
- },
661
- trackContentHeight(ctx) {
662
- let cleanup: VoidFunction
663
-
664
- raf(() => {
665
- const contentEl = dom.getContentEl(ctx)
666
- const listboxEl = dom.getListEl(ctx)
667
-
668
- if (!contentEl || !listboxEl) return
669
- const win = dom.getWin(ctx)
670
-
671
- let rafId: number
672
- const observer = new win.ResizeObserver(() => {
673
- rafId = requestAnimationFrame(() => {
674
- contentEl.style.setProperty(`--height`, `${listboxEl.offsetHeight}px`)
675
- })
676
- })
677
- observer.observe(contentEl)
678
- cleanup = () => {
679
- cancelAnimationFrame(rafId)
680
- observer.unobserve(contentEl)
681
- }
682
- })
661
+ cleanups.push(observerCleanup)
683
662
 
684
- return () => cleanup?.()
663
+ return () => {
664
+ cleanups.forEach((cleanup) => cleanup())
665
+ }
685
666
  },
686
667
  },
687
668
 
@@ -700,45 +681,46 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
700
681
  })
701
682
  },
702
683
  setHighlightedItem(ctx, evt) {
703
- set.highlightedItem(ctx, evt.value)
684
+ if (evt.value == null) return
685
+ set.highlightedValue(ctx, evt.value)
704
686
  },
705
687
  clearHighlightedItem(ctx) {
706
- set.highlightedItem(ctx, null, true)
688
+ set.highlightedValue(ctx, null, true)
707
689
  },
708
690
  selectHighlightedItem(ctx) {
709
- set.selectedItem(ctx, ctx.highlightedValue)
691
+ set.value(ctx, ctx.highlightedValue)
710
692
  },
711
693
  selectItem(ctx, evt) {
712
- set.selectedItem(ctx, evt.value)
694
+ if (evt.value == null) return
695
+ set.value(ctx, evt.value)
713
696
  },
714
697
  clearItem(ctx, evt) {
698
+ if (evt.value == null) return
715
699
  const value = ctx.value.filter((v) => v !== evt.value)
716
- set.selectedItems(ctx, value)
700
+ set.value(ctx, value)
717
701
  },
718
- focusInput(ctx) {
719
- // use raf since the input might be rendered in the content
702
+ setInitialFocus(ctx) {
720
703
  raf(() => {
721
- if (dom.isInputFocused(ctx)) return
722
- dom.getInputEl(ctx)?.focus({ preventScroll: true })
704
+ dom.focusInputEl(ctx)
723
705
  })
724
706
  },
725
- focusInputOrTrigger(ctx) {
726
- queueMicrotask(() => {
727
- if (ctx.popup === "dialog") {
728
- dom.getTriggerEl(ctx)?.focus({ preventScroll: true })
707
+ setFinalFocus(ctx) {
708
+ raf(() => {
709
+ const triggerEl = dom.getTriggerEl(ctx)
710
+ if (triggerEl?.dataset.focusable == null) {
711
+ dom.focusInputEl(ctx)
729
712
  } else {
730
- dom.getInputEl(ctx)?.focus({ preventScroll: true })
713
+ dom.focusTriggerEl(ctx)
731
714
  }
732
715
  })
733
716
  },
734
- syncInputValue(ctx, evt) {
717
+ syncInputValue(ctx) {
735
718
  const inputEl = dom.getInputEl(ctx)
736
719
  if (!inputEl) return
737
720
 
738
721
  inputEl.value = ctx.inputValue
739
722
 
740
- raf(() => {
741
- if (!evt.keypress) return
723
+ queueMicrotask(() => {
742
724
  const { selectionStart, selectionEnd } = inputEl
743
725
 
744
726
  if (Math.abs((selectionEnd ?? 0) - (selectionStart ?? 0)) !== 0) return
@@ -782,10 +764,11 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
782
764
  }
783
765
  },
784
766
  setSelectedItems(ctx, evt) {
785
- set.selectedItems(ctx, evt.value)
767
+ if (!isArray(evt.value)) return
768
+ set.value(ctx, evt.value)
786
769
  },
787
770
  clearSelectedItems(ctx) {
788
- set.selectedItems(ctx, [])
771
+ set.value(ctx, [])
789
772
  },
790
773
  scrollContentToTop(ctx) {
791
774
  if (ctx.scrollToIndexFn) {
@@ -805,13 +788,20 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
805
788
  highlightFirstItem(ctx) {
806
789
  raf(() => {
807
790
  const value = ctx.collection.first()
808
- set.highlightedItem(ctx, value)
791
+ set.highlightedValue(ctx, value)
792
+ })
793
+ },
794
+ highlightFirstItemIfNeeded(ctx) {
795
+ if (!ctx.autoHighlight) return
796
+ raf(() => {
797
+ const value = ctx.collection.first()
798
+ set.highlightedValue(ctx, value)
809
799
  })
810
800
  },
811
801
  highlightLastItem(ctx) {
812
802
  raf(() => {
813
803
  const value = ctx.collection.last()
814
- set.highlightedItem(ctx, value)
804
+ set.highlightedValue(ctx, value)
815
805
  })
816
806
  },
817
807
  highlightNextItem(ctx) {
@@ -822,7 +812,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
822
812
  } else {
823
813
  value = ctx.collection.first()
824
814
  }
825
- set.highlightedItem(ctx, value)
815
+ set.highlightedValue(ctx, value)
826
816
  },
827
817
  highlightPrevItem(ctx) {
828
818
  let value: string | null = null
@@ -832,12 +822,12 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
832
822
  } else {
833
823
  value = ctx.collection.last()
834
824
  }
835
- set.highlightedItem(ctx, value)
825
+ set.highlightedValue(ctx, value)
836
826
  },
837
827
  highlightFirstSelectedItem(ctx) {
838
828
  raf(() => {
839
829
  const [value] = ctx.collection.sort(ctx.value)
840
- set.highlightedItem(ctx, value)
830
+ set.highlightedValue(ctx, value)
841
831
  })
842
832
  },
843
833
  highlightFirstOrSelectedItem(ctx) {
@@ -848,7 +838,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
848
838
  } else {
849
839
  value = ctx.collection.first()
850
840
  }
851
- set.highlightedItem(ctx, value)
841
+ set.highlightedValue(ctx, value)
852
842
  })
853
843
  },
854
844
  highlightLastOrSelectedItem(ctx) {
@@ -859,7 +849,7 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
859
849
  } else {
860
850
  value = ctx.collection.last()
861
851
  }
862
- set.highlightedItem(ctx, value)
852
+ set.highlightedValue(ctx, value)
863
853
  })
864
854
  },
865
855
  autofillInputValue(ctx, evt) {
@@ -874,12 +864,10 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
874
864
  ctx.collection = evt.value
875
865
  },
876
866
  syncSelectedItems(ctx) {
877
- const prevSelectedItems = ctx.selectedItems
878
- ctx.selectedItems = ctx.value.map((v) => {
879
- const foundItem = prevSelectedItems.find((item) => ctx.collection.itemToValue(item) === v)
880
- if (foundItem) return foundItem
881
- return ctx.collection.item(v)
882
- })
867
+ sync.valueChange(ctx)
868
+ },
869
+ syncHighlightedItem(ctx) {
870
+ sync.highlightChange(ctx)
883
871
  },
884
872
  toggleVisibility(ctx, evt, { send }) {
885
873
  send({ type: ctx.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt })
@@ -889,31 +877,25 @@ export function machine<T extends CollectionItem>(userContext: UserDefinedContex
889
877
  )
890
878
  }
891
879
 
892
- const invoke = {
880
+ const sync = {
893
881
  valueChange: (ctx: MachineContext) => {
894
- ctx.onValueChange?.({
895
- value: Array.from(ctx.value),
896
- items: ctx.selectedItems,
897
- })
898
-
899
- const prevSelectedItems = ctx.selectedItems
900
-
901
882
  // side effect
883
+ const prevSelectedItems = ctx.selectedItems
902
884
  ctx.selectedItems = ctx.value.map((v) => {
903
885
  const foundItem = prevSelectedItems.find((item) => ctx.collection.itemToValue(item) === v)
904
886
  if (foundItem) return foundItem
905
887
  return ctx.collection.item(v)
906
888
  })
907
889
 
890
+ // set valueAsString
908
891
  const valueAsString = ctx.collection.itemsToString(ctx.selectedItems)
909
-
910
892
  ctx.valueAsString = valueAsString
911
893
 
912
- let nextInputValue: string | undefined
913
-
894
+ // set inputValue
895
+ let inputValue: string | undefined
914
896
  if (ctx.getSelectionValue) {
915
897
  //
916
- nextInputValue = ctx.getSelectionValue({
898
+ inputValue = ctx.getSelectionValue({
917
899
  inputValue: ctx.inputValue,
918
900
  selectedItems: Array.from(ctx.selectedItems),
919
901
  valueAsString,
@@ -921,25 +903,34 @@ const invoke = {
921
903
  //
922
904
  } else {
923
905
  //
924
- nextInputValue = match(ctx.selectionBehavior, {
906
+ inputValue = match(ctx.selectionBehavior, {
925
907
  replace: ctx.valueAsString,
926
908
  preserve: ctx.inputValue,
927
909
  clear: "",
928
910
  })
929
911
  }
930
912
 
931
- ctx.inputValue = nextInputValue
913
+ set.inputValue(ctx, inputValue)
914
+ },
915
+ highlightChange: (ctx: MachineContext) => {
916
+ ctx.highlightedItem = ctx.collection.item(ctx.highlightedValue)
917
+ },
918
+ }
932
919
 
933
- invoke.inputChange(ctx)
920
+ const invoke = {
921
+ valueChange: (ctx: MachineContext) => {
922
+ sync.valueChange(ctx)
923
+ ctx.onValueChange?.({
924
+ value: Array.from(ctx.value),
925
+ items: Array.from(ctx.selectedItems),
926
+ })
934
927
  },
935
928
  highlightChange: (ctx: MachineContext) => {
929
+ sync.highlightChange(ctx)
936
930
  ctx.onHighlightChange?.({
937
931
  highlightedValue: ctx.highlightedValue,
938
- highligtedItem: ctx.highlightedItem,
932
+ highlightedItem: ctx.highlightedItem,
939
933
  })
940
-
941
- // side effect
942
- ctx.highlightedItem = ctx.collection.item(ctx.highlightedValue)
943
934
  },
944
935
  inputChange: (ctx: MachineContext) => {
945
936
  ctx.onInputValueChange?.({ inputValue: ctx.inputValue })
@@ -947,25 +938,22 @@ const invoke = {
947
938
  }
948
939
 
949
940
  const set = {
950
- selectedItem: (ctx: MachineContext, value: string | null | undefined, force = false) => {
941
+ value: (ctx: MachineContext, value: string | string[] | null | undefined, force = false) => {
951
942
  if (isEqual(ctx.value, value)) return
952
-
953
943
  if (value == null && !force) return
954
-
955
944
  if (value == null && force) {
956
945
  ctx.value = []
957
946
  invoke.valueChange(ctx)
958
947
  return
959
948
  }
960
- ctx.value = ctx.multiple ? addOrRemove(ctx.value, value!) : [value!]
961
- invoke.valueChange(ctx)
962
- },
963
- selectedItems: (ctx: MachineContext, value: string[]) => {
964
- if (isEqual(ctx.value, value)) return
965
- ctx.value = value
949
+ if (isArray(value)) {
950
+ ctx.value = value
951
+ } else if (value != null) {
952
+ ctx.value = ctx.multiple ? addOrRemove(ctx.value, value) : [value]
953
+ }
966
954
  invoke.valueChange(ctx)
967
955
  },
968
- highlightedItem: (ctx: MachineContext, value: string | null | undefined, force = false) => {
956
+ highlightedValue: (ctx: MachineContext, value: string | null | undefined, force = false) => {
969
957
  if (isEqual(ctx.highlightedValue, value)) return
970
958
  if (!value && !force) return
971
959
  ctx.highlightedValue = value || null
@@ -9,7 +9,7 @@ export const props = createProps<UserDefinedContext>()([
9
9
  "collection",
10
10
  "dir",
11
11
  "disabled",
12
- "dismissable",
12
+ "disableLayer",
13
13
  "form",
14
14
  "getRootNode",
15
15
  "getSelectionValue",
@@ -41,7 +41,7 @@ export const props = createProps<UserDefinedContext>()([
41
41
  "scrollToIndexFn",
42
42
  "selectionBehavior",
43
43
  "translations",
44
- "popup",
44
+ "composite",
45
45
  "value",
46
46
  ])
47
47
  export const splitProps = createSplitProps<Partial<UserDefinedContext>>(props)