@zag-js/combobox 0.49.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.
package/dist/index.mjs CHANGED
@@ -35,18 +35,17 @@ import {
35
35
  isContextMenuEvent,
36
36
  isLeftClick
37
37
  } from "@zag-js/dom-event";
38
- import { ariaAttr, dataAttr, isDownloadingEvent, isOpeningInNewTab, raf } from "@zag-js/dom-query";
38
+ import { ariaAttr, dataAttr, isDownloadingEvent, isOpeningInNewTab } from "@zag-js/dom-query";
39
39
  import { getPlacementStyles } from "@zag-js/popper";
40
40
 
41
41
  // src/combobox.dom.ts
42
- import { createScope } from "@zag-js/dom-query";
42
+ import { createScope, query } from "@zag-js/dom-query";
43
43
  var dom = createScope({
44
44
  getRootId: (ctx) => ctx.ids?.root ?? `combobox:${ctx.id}`,
45
45
  getLabelId: (ctx) => ctx.ids?.label ?? `combobox:${ctx.id}:label`,
46
46
  getControlId: (ctx) => ctx.ids?.control ?? `combobox:${ctx.id}:control`,
47
47
  getInputId: (ctx) => ctx.ids?.input ?? `combobox:${ctx.id}:input`,
48
48
  getContentId: (ctx) => ctx.ids?.content ?? `combobox:${ctx.id}:content`,
49
- getListId: (ctx) => `combobox:${ctx.id}:listbox`,
50
49
  getPositionerId: (ctx) => ctx.ids?.positioner ?? `combobox:${ctx.id}:popper`,
51
50
  getTriggerId: (ctx) => ctx.ids?.trigger ?? `combobox:${ctx.id}:toggle-btn`,
52
51
  getClearTriggerId: (ctx) => ctx.ids?.clearTrigger ?? `combobox:${ctx.id}:clear-btn`,
@@ -54,18 +53,28 @@ var dom = createScope({
54
53
  getItemGroupLabelId: (ctx, id) => ctx.ids?.itemGroupLabel?.(id) ?? `combobox:${ctx.id}:optgroup-label:${id}`,
55
54
  getItemId: (ctx, id) => `combobox:${ctx.id}:option:${id}`,
56
55
  getContentEl: (ctx) => dom.getById(ctx, dom.getContentId(ctx)),
57
- getListEl: (ctx) => dom.getById(ctx, dom.getListId(ctx)),
58
56
  getInputEl: (ctx) => dom.getById(ctx, dom.getInputId(ctx)),
59
57
  getPositionerEl: (ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
60
58
  getControlEl: (ctx) => dom.getById(ctx, dom.getControlId(ctx)),
61
59
  getTriggerEl: (ctx) => dom.getById(ctx, dom.getTriggerId(ctx)),
62
60
  getClearTriggerEl: (ctx) => dom.getById(ctx, dom.getClearTriggerId(ctx)),
63
- isInputFocused: (ctx) => dom.getDoc(ctx).activeElement === dom.getInputEl(ctx),
64
61
  getHighlightedItemEl: (ctx) => {
65
62
  const value = ctx.highlightedValue;
66
63
  if (value == null)
67
64
  return;
68
- return dom.getContentEl(ctx)?.querySelector(`[role=option][data-value="${CSS.escape(value)}"`);
65
+ return query(dom.getContentEl(ctx), `[role=option][data-value="${CSS.escape(value)}"`);
66
+ },
67
+ focusInputEl: (ctx) => {
68
+ const inputEl = dom.getInputEl(ctx);
69
+ if (dom.getActiveElement(ctx) === inputEl)
70
+ return;
71
+ inputEl?.focus({ preventScroll: true });
72
+ },
73
+ focusTriggerEl: (ctx) => {
74
+ const triggerEl = dom.getTriggerEl(ctx);
75
+ if (dom.getActiveElement(ctx) === triggerEl)
76
+ return;
77
+ triggerEl?.focus({ preventScroll: true });
69
78
  }
70
79
  });
71
80
 
@@ -79,7 +88,8 @@ function connect(state, send, normalize) {
79
88
  const readOnly = state.context.readOnly;
80
89
  const open = state.hasTag("open");
81
90
  const focused = state.hasTag("focused");
82
- const isDialogPopup = state.context.popup === "dialog";
91
+ const composite = state.context.composite;
92
+ const highlightedValue = state.context.highlightedValue;
83
93
  const popperStyles = getPlacementStyles({
84
94
  ...state.context.positioning,
85
95
  placement: state.context.currentPlacement
@@ -91,7 +101,7 @@ function connect(state, send, normalize) {
91
101
  return {
92
102
  value,
93
103
  disabled: Boolean(disabled2 || disabled2),
94
- highlighted: state.context.highlightedValue === value,
104
+ highlighted: highlightedValue === value,
95
105
  selected: state.context.value.includes(value)
96
106
  };
97
107
  }
@@ -99,8 +109,7 @@ function connect(state, send, normalize) {
99
109
  focused,
100
110
  open,
101
111
  inputValue: state.context.inputValue,
102
- inputEmpty: state.context.isInputValueEmpty,
103
- highlightedValue: state.context.highlightedValue,
112
+ highlightedValue,
104
113
  highlightedItem: state.context.highlightedItem,
105
114
  value: state.context.value,
106
115
  valueAsString: state.context.valueAsString,
@@ -113,7 +122,7 @@ function connect(state, send, normalize) {
113
122
  setCollection(collection3) {
114
123
  send({ type: "COLLECTION.SET", value: collection3 });
115
124
  },
116
- highlightValue(value) {
125
+ setHighlightValue(value) {
117
126
  send({ type: "HIGHLIGHTED_VALUE.SET", value });
118
127
  },
119
128
  selectValue(value) {
@@ -135,10 +144,10 @@ function connect(state, send, normalize) {
135
144
  focus() {
136
145
  dom.getInputEl(state.context)?.focus();
137
146
  },
138
- setOpen(_open) {
139
- if (_open === open)
147
+ setOpen(nextOpen) {
148
+ if (nextOpen === open)
140
149
  return;
141
- send(_open ? "OPEN" : "CLOSE");
150
+ send(nextOpen ? "OPEN" : "CLOSE");
142
151
  },
143
152
  rootProps: normalize.element({
144
153
  ...parts.root.attrs,
@@ -157,7 +166,7 @@ function connect(state, send, normalize) {
157
166
  "data-invalid": dataAttr(invalid),
158
167
  "data-focus": dataAttr(focused),
159
168
  onClick(event) {
160
- if (!isDialogPopup)
169
+ if (composite)
161
170
  return;
162
171
  event.preventDefault();
163
172
  dom.getTriggerEl(state.context)?.focus({ preventScroll: true });
@@ -198,11 +207,13 @@ function connect(state, send, normalize) {
198
207
  role: "combobox",
199
208
  defaultValue: state.context.inputValue,
200
209
  "aria-autocomplete": state.context.autoComplete ? "both" : "list",
201
- "aria-controls": isDialogPopup ? dom.getListId(state.context) : dom.getContentId(state.context),
210
+ "aria-controls": dom.getContentId(state.context),
202
211
  "aria-expanded": open,
203
212
  "data-state": open ? "open" : "closed",
204
- "aria-activedescendant": state.context.highlightedValue ? dom.getItemId(state.context, state.context.highlightedValue) : void 0,
205
- onClick() {
213
+ "aria-activedescendant": highlightedValue ? dom.getItemId(state.context, highlightedValue) : void 0,
214
+ onClick(event) {
215
+ if (event.defaultPrevented)
216
+ return;
206
217
  if (!state.context.openOnClick)
207
218
  return;
208
219
  if (!interactive)
@@ -282,86 +293,87 @@ function connect(state, send, normalize) {
282
293
  exec?.(event);
283
294
  }
284
295
  }),
285
- triggerProps: normalize.button({
286
- ...parts.trigger.attrs,
287
- dir: state.context.dir,
288
- id: dom.getTriggerId(state.context),
289
- "aria-haspopup": isDialogPopup ? "dialog" : "listbox",
290
- type: "button",
291
- tabIndex: isDialogPopup ? 0 : -1,
292
- "aria-label": translations.triggerLabel,
293
- "aria-expanded": open,
294
- "data-state": open ? "open" : "closed",
295
- "aria-controls": open ? dom.getContentId(state.context) : void 0,
296
- disabled,
297
- "data-readonly": dataAttr(readOnly),
298
- "data-disabled": dataAttr(disabled),
299
- onClick(event) {
300
- const evt = getNativeEvent(event);
301
- if (!interactive)
302
- return;
303
- if (!isLeftClick(evt))
304
- return;
305
- send("TRIGGER.CLICK");
306
- },
307
- onPointerDown(event) {
308
- if (!interactive)
309
- return;
310
- if (event.pointerType === "touch")
311
- return;
312
- event.preventDefault();
313
- queueMicrotask(() => {
314
- dom.getInputEl(state.context)?.focus({ preventScroll: true });
315
- });
316
- },
317
- onKeyDown(event) {
318
- if (event.defaultPrevented)
319
- return;
320
- if (!isDialogPopup)
321
- return;
322
- const keyMap = {
323
- ArrowDown() {
324
- send("INPUT.FOCUS");
325
- send("INPUT.ARROW_DOWN");
326
- raf(() => {
327
- dom.getInputEl(state.context)?.focus({ preventScroll: true });
328
- });
329
- },
330
- ArrowUp() {
331
- send("INPUT.FOCUS");
332
- send("INPUT.ARROW_UP");
333
- raf(() => {
334
- dom.getInputEl(state.context)?.focus({ preventScroll: true });
335
- });
336
- }
337
- };
338
- const key = getEventKey(event, state.context);
339
- const exec = keyMap[key];
340
- if (exec) {
341
- exec(event);
296
+ getTriggerProps(props = {}) {
297
+ return normalize.button({
298
+ ...parts.trigger.attrs,
299
+ dir: state.context.dir,
300
+ id: dom.getTriggerId(state.context),
301
+ "aria-haspopup": composite ? "listbox" : "dialog",
302
+ type: "button",
303
+ tabIndex: props.focusable ? void 0 : -1,
304
+ "aria-label": translations.triggerLabel,
305
+ "aria-expanded": open,
306
+ "data-state": open ? "open" : "closed",
307
+ "aria-controls": open ? dom.getContentId(state.context) : void 0,
308
+ disabled,
309
+ "data-focusable": dataAttr(props.focusable),
310
+ "data-readonly": dataAttr(readOnly),
311
+ "data-disabled": dataAttr(disabled),
312
+ onFocus() {
313
+ if (!props.focusable)
314
+ return;
315
+ send({ type: "INPUT.FOCUS", src: "trigger" });
316
+ },
317
+ onClick(event) {
318
+ if (event.defaultPrevented)
319
+ return;
320
+ const evt = getNativeEvent(event);
321
+ if (!interactive)
322
+ return;
323
+ if (!isLeftClick(evt))
324
+ return;
325
+ send("TRIGGER.CLICK");
326
+ },
327
+ onPointerDown(event) {
328
+ if (!interactive)
329
+ return;
330
+ if (event.pointerType === "touch")
331
+ return;
342
332
  event.preventDefault();
333
+ queueMicrotask(() => {
334
+ dom.getInputEl(state.context)?.focus({ preventScroll: true });
335
+ });
336
+ },
337
+ onKeyDown(event) {
338
+ if (event.defaultPrevented)
339
+ return;
340
+ if (composite)
341
+ return;
342
+ const keyMap = {
343
+ ArrowDown() {
344
+ send({ type: "INPUT.ARROW_DOWN", src: "trigger" });
345
+ },
346
+ ArrowUp() {
347
+ send({ type: "INPUT.ARROW_UP", src: "trigger" });
348
+ }
349
+ };
350
+ const key = getEventKey(event, state.context);
351
+ const exec = keyMap[key];
352
+ if (exec) {
353
+ exec(event);
354
+ event.preventDefault();
355
+ }
343
356
  }
344
- }
345
- }),
357
+ });
358
+ },
346
359
  contentProps: normalize.element({
347
360
  ...parts.content.attrs,
348
361
  dir: state.context.dir,
349
362
  id: dom.getContentId(state.context),
350
- role: isDialogPopup ? "dialog" : "listbox",
363
+ role: !composite ? "dialog" : "listbox",
351
364
  tabIndex: -1,
352
365
  hidden: !open,
353
366
  "data-state": open ? "open" : "closed",
354
367
  "aria-labelledby": dom.getLabelId(state.context),
355
- "aria-multiselectable": state.context.multiple && !isDialogPopup ? true : void 0,
368
+ "aria-multiselectable": state.context.multiple && composite ? true : void 0,
356
369
  onPointerDown(event) {
357
370
  event.preventDefault();
358
371
  }
359
372
  }),
360
- // only used when triggerOnly: true
361
373
  listProps: normalize.element({
362
- id: dom.getListId(state.context),
363
- role: isDialogPopup ? "listbox" : void 0,
364
- "aria-multiselectable": isDialogPopup && state.context.multiple ? true : void 0
374
+ role: !composite ? "listbox" : void 0,
375
+ "aria-labelledby": dom.getLabelId(state.context),
376
+ "aria-multiselectable": state.context.multiple && !composite ? true : void 0
365
377
  }),
366
378
  clearTriggerProps: normalize.button({
367
379
  ...parts.clearTrigger.attrs,
@@ -373,7 +385,12 @@ function connect(state, send, normalize) {
373
385
  "aria-label": translations.clearTriggerLabel,
374
386
  "aria-controls": dom.getInputId(state.context),
375
387
  hidden: !state.context.value.length,
376
- onClick() {
388
+ onPointerDown(event) {
389
+ event.preventDefault();
390
+ },
391
+ onClick(event) {
392
+ if (event.defaultPrevented)
393
+ return;
377
394
  if (!interactive)
378
395
  return;
379
396
  send({ type: "VALUE.CLEAR", src: "clear-trigger" });
@@ -398,6 +415,8 @@ function connect(state, send, normalize) {
398
415
  onPointerMove() {
399
416
  if (itemState.disabled)
400
417
  return;
418
+ if (itemState.highlighted)
419
+ return;
401
420
  send({ type: "ITEM.POINTER_MOVE", value });
402
421
  },
403
422
  onPointerLeave() {
@@ -405,7 +424,7 @@ function connect(state, send, normalize) {
405
424
  return;
406
425
  if (itemState.disabled)
407
426
  return;
408
- const mouseMoved = state.previousEvent.type === "ITEM.POINTER_MOVE";
427
+ const mouseMoved = state.previousEvent.type.includes("POINTER");
409
428
  if (!mouseMoved)
410
429
  return;
411
430
  send({ type: "ITEM.POINTER_LEAVE", value });
@@ -471,9 +490,9 @@ function connect(state, send, normalize) {
471
490
  import { ariaHidden } from "@zag-js/aria-hidden";
472
491
  import { createMachine, guards } from "@zag-js/core";
473
492
  import { trackDismissableElement } from "@zag-js/dismissable";
474
- import { observeAttributes, observeChildren, raf as raf2, scrollIntoView } from "@zag-js/dom-query";
493
+ import { observeAttributes, observeChildren, raf, scrollIntoView } from "@zag-js/dom-query";
475
494
  import { getPlacement } from "@zag-js/popper";
476
- import { addOrRemove, compact, isBoolean, isEqual, match } from "@zag-js/utils";
495
+ import { addOrRemove, compact, isArray, isBoolean, isEqual, match } from "@zag-js/utils";
477
496
  var { and, not } = guards;
478
497
  function machine(userContext) {
479
498
  const ctx = compact(userContext);
@@ -493,8 +512,7 @@ function machine(userContext) {
493
512
  selectionBehavior: "replace",
494
513
  openOnKeyPress: true,
495
514
  openOnChange: true,
496
- dismissable: true,
497
- popup: "listbox",
515
+ composite: true,
498
516
  ...ctx,
499
517
  highlightedItem: null,
500
518
  selectedItems: [],
@@ -523,7 +541,7 @@ function machine(userContext) {
523
541
  watch: {
524
542
  value: ["syncSelectedItems"],
525
543
  inputValue: ["syncInputValue"],
526
- highlightedValue: ["autofillInputValue"],
544
+ highlightedValue: ["syncHighlightedItem", "autofillInputValue"],
527
545
  multiple: ["syncSelectionBehavior"],
528
546
  open: ["toggleVisibility"]
529
547
  },
@@ -561,17 +579,17 @@ function machine(userContext) {
561
579
  "TRIGGER.CLICK": [
562
580
  {
563
581
  guard: "isOpenControlled",
564
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
582
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"]
565
583
  },
566
584
  {
567
585
  target: "interacting",
568
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
586
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"]
569
587
  }
570
588
  ],
571
589
  "INPUT.CLICK": [
572
590
  {
573
591
  guard: "isOpenControlled",
574
- actions: ["invokeOnOpen"]
592
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
575
593
  },
576
594
  {
577
595
  target: "interacting",
@@ -593,13 +611,13 @@ function machine(userContext) {
593
611
  ],
594
612
  "VALUE.CLEAR": {
595
613
  target: "focused",
596
- actions: ["clearInputValue", "clearSelectedItems"]
614
+ actions: ["clearInputValue", "clearSelectedItems", "setInitialFocus"]
597
615
  }
598
616
  }
599
617
  },
600
618
  focused: {
601
619
  tags: ["focused", "closed"],
602
- entry: ["focusInputOrTrigger", "scrollContentToTop", "clearHighlightedItem"],
620
+ entry: ["scrollContentToTop", "clearHighlightedItem"],
603
621
  on: {
604
622
  "CONTROLLED.OPEN": [
605
623
  {
@@ -613,12 +631,12 @@ function machine(userContext) {
613
631
  "INPUT.CHANGE": [
614
632
  {
615
633
  guard: and("isOpenControlled", "openOnChange"),
616
- actions: ["setInputValue", "invokeOnOpen"]
634
+ actions: ["setInputValue", "invokeOnOpen", "highlightFirstItemIfNeeded"]
617
635
  },
618
636
  {
619
637
  guard: "openOnChange",
620
638
  target: "suggesting",
621
- actions: ["setInputValue", "invokeOnOpen"]
639
+ actions: ["setInputValue", "invokeOnOpen", "highlightFirstItemIfNeeded"]
622
640
  },
623
641
  {
624
642
  actions: "setInputValue"
@@ -647,11 +665,11 @@ function machine(userContext) {
647
665
  "TRIGGER.CLICK": [
648
666
  {
649
667
  guard: "isOpenControlled",
650
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
668
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"]
651
669
  },
652
670
  {
653
671
  target: "interacting",
654
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
672
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"]
655
673
  }
656
674
  ],
657
675
  "INPUT.ARROW_DOWN": [
@@ -714,18 +732,14 @@ function machine(userContext) {
714
732
  },
715
733
  interacting: {
716
734
  tags: ["open", "focused"],
717
- activities: [
718
- "scrollIntoView",
719
- "trackDismissableLayer",
720
- "computePlacement",
721
- "hideOtherElements",
722
- "trackContentHeight"
723
- ],
735
+ entry: ["setInitialFocus"],
736
+ activities: ["scrollToHighlightedItem", "trackDismissableLayer", "computePlacement", "hideOtherElements"],
724
737
  on: {
725
738
  "CONTROLLED.CLOSE": [
726
739
  {
727
740
  guard: "restoreFocus",
728
- target: "focused"
741
+ target: "focused",
742
+ actions: ["setFinalFocus"]
729
743
  },
730
744
  {
731
745
  target: "idle"
@@ -763,7 +777,7 @@ function machine(userContext) {
763
777
  {
764
778
  guard: "closeOnSelect",
765
779
  target: "focused",
766
- actions: ["selectHighlightedItem", "invokeOnClose"]
780
+ actions: ["selectHighlightedItem", "invokeOnClose", "setFinalFocus"]
767
781
  },
768
782
  {
769
783
  actions: ["selectHighlightedItem"]
@@ -794,7 +808,7 @@ function machine(userContext) {
794
808
  {
795
809
  guard: "closeOnSelect",
796
810
  target: "focused",
797
- actions: ["selectItem", "invokeOnClose"]
811
+ actions: ["selectItem", "invokeOnClose", "setFinalFocus"]
798
812
  },
799
813
  {
800
814
  actions: ["selectItem"]
@@ -816,7 +830,7 @@ function machine(userContext) {
816
830
  },
817
831
  {
818
832
  target: "focused",
819
- actions: ["invokeOnClose"]
833
+ actions: ["invokeOnClose", "setFinalFocus"]
820
834
  }
821
835
  ],
822
836
  "TRIGGER.CLICK": [
@@ -853,11 +867,11 @@ function machine(userContext) {
853
867
  CLOSE: [
854
868
  {
855
869
  guard: "isOpenControlled",
856
- actions: "invokeOnClose"
870
+ actions: ["invokeOnClose"]
857
871
  },
858
872
  {
859
873
  target: "focused",
860
- actions: "invokeOnClose"
874
+ actions: ["invokeOnClose", "setFinalFocus"]
861
875
  }
862
876
  ],
863
877
  "VALUE.CLEAR": [
@@ -867,7 +881,7 @@ function machine(userContext) {
867
881
  },
868
882
  {
869
883
  target: "focused",
870
- actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"]
884
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose", "setFinalFocus"]
871
885
  }
872
886
  ]
873
887
  }
@@ -876,18 +890,18 @@ function machine(userContext) {
876
890
  tags: ["open", "focused"],
877
891
  activities: [
878
892
  "trackDismissableLayer",
879
- "scrollIntoView",
893
+ "scrollToHighlightedItem",
880
894
  "computePlacement",
881
895
  "trackChildNodes",
882
- "hideOtherElements",
883
- "trackContentHeight"
896
+ "hideOtherElements"
884
897
  ],
885
- entry: ["focusInput"],
898
+ entry: ["setInitialFocus"],
886
899
  on: {
887
900
  "CONTROLLED.CLOSE": [
888
901
  {
889
902
  guard: "restoreFocus",
890
- target: "focused"
903
+ target: "focused",
904
+ actions: ["setFinalFocus"]
891
905
  },
892
906
  {
893
907
  target: "idle"
@@ -898,11 +912,11 @@ function machine(userContext) {
898
912
  },
899
913
  "INPUT.ARROW_DOWN": {
900
914
  target: "interacting",
901
- actions: "highlightNextItem"
915
+ actions: ["highlightNextItem"]
902
916
  },
903
917
  "INPUT.ARROW_UP": {
904
918
  target: "interacting",
905
- actions: "highlightPrevItem"
919
+ actions: ["highlightPrevItem"]
906
920
  },
907
921
  "INPUT.HOME": {
908
922
  target: "interacting",
@@ -920,7 +934,7 @@ function machine(userContext) {
920
934
  {
921
935
  guard: "closeOnSelect",
922
936
  target: "focused",
923
- actions: ["selectHighlightedItem", "invokeOnClose"]
937
+ actions: ["selectHighlightedItem", "invokeOnClose", "setFinalFocus"]
924
938
  },
925
939
  {
926
940
  actions: ["selectHighlightedItem"]
@@ -938,19 +952,19 @@ function machine(userContext) {
938
952
  "LAYER.ESCAPE": [
939
953
  {
940
954
  guard: "isOpenControlled",
941
- actions: "invokeOnClose"
955
+ actions: ["invokeOnClose"]
942
956
  },
943
957
  {
944
958
  target: "focused",
945
- actions: "invokeOnClose"
959
+ actions: ["invokeOnClose"]
946
960
  }
947
961
  ],
948
962
  "ITEM.POINTER_MOVE": {
949
963
  target: "interacting",
950
- actions: "setHighlightedItem"
964
+ actions: ["setHighlightedItem"]
951
965
  },
952
966
  "ITEM.POINTER_LEAVE": {
953
- actions: "clearHighlightedItem"
967
+ actions: ["clearHighlightedItem"]
954
968
  },
955
969
  "LAYER.INTERACT_OUTSIDE": [
956
970
  // == group 1 ==
@@ -966,21 +980,21 @@ function machine(userContext) {
966
980
  // == group 2 ==
967
981
  {
968
982
  guard: "isOpenControlled",
969
- actions: "invokeOnClose"
983
+ actions: ["invokeOnClose"]
970
984
  },
971
985
  {
972
986
  target: "idle",
973
- actions: "invokeOnClose"
987
+ actions: ["invokeOnClose"]
974
988
  }
975
989
  ],
976
990
  "TRIGGER.CLICK": [
977
991
  {
978
992
  guard: "isOpenControlled",
979
- actions: "invokeOnClose"
993
+ actions: ["invokeOnClose"]
980
994
  },
981
995
  {
982
996
  target: "focused",
983
- actions: "invokeOnClose"
997
+ actions: ["invokeOnClose"]
984
998
  }
985
999
  ],
986
1000
  "ITEM.CLICK": [
@@ -991,7 +1005,7 @@ function machine(userContext) {
991
1005
  {
992
1006
  guard: "closeOnSelect",
993
1007
  target: "focused",
994
- actions: ["selectItem", "invokeOnClose"]
1008
+ actions: ["selectItem", "invokeOnClose", "setFinalFocus"]
995
1009
  },
996
1010
  {
997
1011
  actions: ["selectItem"]
@@ -1000,11 +1014,11 @@ function machine(userContext) {
1000
1014
  CLOSE: [
1001
1015
  {
1002
1016
  guard: "isOpenControlled",
1003
- actions: "invokeOnClose"
1017
+ actions: ["invokeOnClose"]
1004
1018
  },
1005
1019
  {
1006
1020
  target: "focused",
1007
- actions: "invokeOnClose"
1021
+ actions: ["invokeOnClose", "setFinalFocus"]
1008
1022
  }
1009
1023
  ],
1010
1024
  "VALUE.CLEAR": [
@@ -1014,7 +1028,7 @@ function machine(userContext) {
1014
1028
  },
1015
1029
  {
1016
1030
  target: "focused",
1017
- actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"]
1031
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose", "setFinalFocus"]
1018
1032
  }
1019
1033
  ]
1020
1034
  }
@@ -1043,7 +1057,7 @@ function machine(userContext) {
1043
1057
  },
1044
1058
  activities: {
1045
1059
  trackDismissableLayer(ctx2, _evt, { send }) {
1046
- if (!ctx2.dismissable)
1060
+ if (ctx2.disableLayer)
1047
1061
  return;
1048
1062
  const contentEl = () => dom.getContentEl(ctx2);
1049
1063
  return trackDismissableElement(contentEl, {
@@ -1082,56 +1096,42 @@ function machine(userContext) {
1082
1096
  if (!ctx2.autoHighlight)
1083
1097
  return;
1084
1098
  const exec = () => send("CHILDREN_CHANGE");
1085
- raf2(() => exec());
1086
1099
  const contentEl = () => dom.getContentEl(ctx2);
1087
1100
  return observeChildren(contentEl, {
1088
1101
  callback: exec,
1089
1102
  defer: true
1090
1103
  });
1091
1104
  },
1092
- scrollIntoView(ctx2, _evt, { getState }) {
1105
+ scrollToHighlightedItem(ctx2, _evt, { getState }) {
1093
1106
  const inputEl = dom.getInputEl(ctx2);
1107
+ let cleanups = [];
1094
1108
  const exec = (immediate) => {
1095
1109
  const state = getState();
1096
- const pointer = state.event.type.startsWith("ITEM.POINTER");
1110
+ const pointer = state.event.type.includes("POINTER");
1097
1111
  if (pointer || !ctx2.highlightedValue)
1098
1112
  return;
1099
- const optionEl = dom.getHighlightedItemEl(ctx2);
1113
+ const itemEl = dom.getHighlightedItemEl(ctx2);
1100
1114
  const contentEl = dom.getContentEl(ctx2);
1101
1115
  if (ctx2.scrollToIndexFn) {
1102
1116
  const highlightedIndex = ctx2.collection.indexOf(ctx2.highlightedValue);
1103
1117
  ctx2.scrollToIndexFn({ index: highlightedIndex, immediate });
1104
1118
  return;
1105
1119
  }
1106
- scrollIntoView(optionEl, { rootEl: contentEl, block: "nearest" });
1120
+ const rafCleanup2 = raf(() => {
1121
+ scrollIntoView(itemEl, { rootEl: contentEl, block: "nearest" });
1122
+ });
1123
+ cleanups.push(rafCleanup2);
1107
1124
  };
1108
- raf2(() => exec(true));
1109
- return observeAttributes(inputEl, {
1125
+ const rafCleanup = raf(() => exec(true));
1126
+ cleanups.push(rafCleanup);
1127
+ const observerCleanup = observeAttributes(inputEl, {
1110
1128
  attributes: ["aria-activedescendant"],
1111
1129
  callback: () => exec(false)
1112
1130
  });
1113
- },
1114
- trackContentHeight(ctx2) {
1115
- let cleanup;
1116
- raf2(() => {
1117
- const contentEl = dom.getContentEl(ctx2);
1118
- const listboxEl = dom.getListEl(ctx2);
1119
- if (!contentEl || !listboxEl)
1120
- return;
1121
- const win = dom.getWin(ctx2);
1122
- let rafId;
1123
- const observer = new win.ResizeObserver(() => {
1124
- rafId = requestAnimationFrame(() => {
1125
- contentEl.style.setProperty(`--height`, `${listboxEl.offsetHeight}px`);
1126
- });
1127
- });
1128
- observer.observe(contentEl);
1129
- cleanup = () => {
1130
- cancelAnimationFrame(rafId);
1131
- observer.unobserve(contentEl);
1132
- };
1133
- });
1134
- return () => cleanup?.();
1131
+ cleanups.push(observerCleanup);
1132
+ return () => {
1133
+ cleanups.forEach((cleanup) => cleanup());
1134
+ };
1135
1135
  }
1136
1136
  },
1137
1137
  actions: {
@@ -1149,45 +1149,48 @@ function machine(userContext) {
1149
1149
  });
1150
1150
  },
1151
1151
  setHighlightedItem(ctx2, evt) {
1152
- set.highlightedItem(ctx2, evt.value);
1152
+ if (evt.value == null)
1153
+ return;
1154
+ set.highlightedValue(ctx2, evt.value);
1153
1155
  },
1154
1156
  clearHighlightedItem(ctx2) {
1155
- set.highlightedItem(ctx2, null, true);
1157
+ set.highlightedValue(ctx2, null, true);
1156
1158
  },
1157
1159
  selectHighlightedItem(ctx2) {
1158
- set.selectedItem(ctx2, ctx2.highlightedValue);
1160
+ set.value(ctx2, ctx2.highlightedValue);
1159
1161
  },
1160
1162
  selectItem(ctx2, evt) {
1161
- set.selectedItem(ctx2, evt.value);
1163
+ if (evt.value == null)
1164
+ return;
1165
+ set.value(ctx2, evt.value);
1162
1166
  },
1163
1167
  clearItem(ctx2, evt) {
1168
+ if (evt.value == null)
1169
+ return;
1164
1170
  const value = ctx2.value.filter((v) => v !== evt.value);
1165
- set.selectedItems(ctx2, value);
1171
+ set.value(ctx2, value);
1166
1172
  },
1167
- focusInput(ctx2) {
1168
- raf2(() => {
1169
- if (dom.isInputFocused(ctx2))
1170
- return;
1171
- dom.getInputEl(ctx2)?.focus({ preventScroll: true });
1173
+ setInitialFocus(ctx2) {
1174
+ raf(() => {
1175
+ dom.focusInputEl(ctx2);
1172
1176
  });
1173
1177
  },
1174
- focusInputOrTrigger(ctx2) {
1175
- queueMicrotask(() => {
1176
- if (ctx2.popup === "dialog") {
1177
- dom.getTriggerEl(ctx2)?.focus({ preventScroll: true });
1178
+ setFinalFocus(ctx2) {
1179
+ raf(() => {
1180
+ const triggerEl = dom.getTriggerEl(ctx2);
1181
+ if (triggerEl?.dataset.focusable == null) {
1182
+ dom.focusInputEl(ctx2);
1178
1183
  } else {
1179
- dom.getInputEl(ctx2)?.focus({ preventScroll: true });
1184
+ dom.focusTriggerEl(ctx2);
1180
1185
  }
1181
1186
  });
1182
1187
  },
1183
- syncInputValue(ctx2, evt) {
1188
+ syncInputValue(ctx2) {
1184
1189
  const inputEl = dom.getInputEl(ctx2);
1185
1190
  if (!inputEl)
1186
1191
  return;
1187
1192
  inputEl.value = ctx2.inputValue;
1188
- raf2(() => {
1189
- if (!evt.keypress)
1190
- return;
1193
+ queueMicrotask(() => {
1191
1194
  const { selectionStart, selectionEnd } = inputEl;
1192
1195
  if (Math.abs((selectionEnd ?? 0) - (selectionStart ?? 0)) !== 0)
1193
1196
  return;
@@ -1228,10 +1231,12 @@ function machine(userContext) {
1228
1231
  }
1229
1232
  },
1230
1233
  setSelectedItems(ctx2, evt) {
1231
- set.selectedItems(ctx2, evt.value);
1234
+ if (!isArray(evt.value))
1235
+ return;
1236
+ set.value(ctx2, evt.value);
1232
1237
  },
1233
1238
  clearSelectedItems(ctx2) {
1234
- set.selectedItems(ctx2, []);
1239
+ set.value(ctx2, []);
1235
1240
  },
1236
1241
  scrollContentToTop(ctx2) {
1237
1242
  if (ctx2.scrollToIndexFn) {
@@ -1250,15 +1255,23 @@ function machine(userContext) {
1250
1255
  ctx2.onOpenChange?.({ open: false });
1251
1256
  },
1252
1257
  highlightFirstItem(ctx2) {
1253
- raf2(() => {
1258
+ raf(() => {
1254
1259
  const value = ctx2.collection.first();
1255
- set.highlightedItem(ctx2, value);
1260
+ set.highlightedValue(ctx2, value);
1261
+ });
1262
+ },
1263
+ highlightFirstItemIfNeeded(ctx2) {
1264
+ if (!ctx2.autoHighlight)
1265
+ return;
1266
+ raf(() => {
1267
+ const value = ctx2.collection.first();
1268
+ set.highlightedValue(ctx2, value);
1256
1269
  });
1257
1270
  },
1258
1271
  highlightLastItem(ctx2) {
1259
- raf2(() => {
1272
+ raf(() => {
1260
1273
  const value = ctx2.collection.last();
1261
- set.highlightedItem(ctx2, value);
1274
+ set.highlightedValue(ctx2, value);
1262
1275
  });
1263
1276
  },
1264
1277
  highlightNextItem(ctx2) {
@@ -1270,7 +1283,7 @@ function machine(userContext) {
1270
1283
  } else {
1271
1284
  value = ctx2.collection.first();
1272
1285
  }
1273
- set.highlightedItem(ctx2, value);
1286
+ set.highlightedValue(ctx2, value);
1274
1287
  },
1275
1288
  highlightPrevItem(ctx2) {
1276
1289
  let value = null;
@@ -1281,34 +1294,34 @@ function machine(userContext) {
1281
1294
  } else {
1282
1295
  value = ctx2.collection.last();
1283
1296
  }
1284
- set.highlightedItem(ctx2, value);
1297
+ set.highlightedValue(ctx2, value);
1285
1298
  },
1286
1299
  highlightFirstSelectedItem(ctx2) {
1287
- raf2(() => {
1300
+ raf(() => {
1288
1301
  const [value] = ctx2.collection.sort(ctx2.value);
1289
- set.highlightedItem(ctx2, value);
1302
+ set.highlightedValue(ctx2, value);
1290
1303
  });
1291
1304
  },
1292
1305
  highlightFirstOrSelectedItem(ctx2) {
1293
- raf2(() => {
1306
+ raf(() => {
1294
1307
  let value = null;
1295
1308
  if (ctx2.hasSelectedItems) {
1296
1309
  value = ctx2.collection.sort(ctx2.value)[0];
1297
1310
  } else {
1298
1311
  value = ctx2.collection.first();
1299
1312
  }
1300
- set.highlightedItem(ctx2, value);
1313
+ set.highlightedValue(ctx2, value);
1301
1314
  });
1302
1315
  },
1303
1316
  highlightLastOrSelectedItem(ctx2) {
1304
- raf2(() => {
1317
+ raf(() => {
1305
1318
  let value = null;
1306
1319
  if (ctx2.hasSelectedItems) {
1307
1320
  value = ctx2.collection.sort(ctx2.value)[0];
1308
1321
  } else {
1309
1322
  value = ctx2.collection.last();
1310
1323
  }
1311
- set.highlightedItem(ctx2, value);
1324
+ set.highlightedValue(ctx2, value);
1312
1325
  });
1313
1326
  },
1314
1327
  autofillInputValue(ctx2, evt) {
@@ -1316,7 +1329,7 @@ function machine(userContext) {
1316
1329
  if (!ctx2.autoComplete || !inputEl || !evt.keypress)
1317
1330
  return;
1318
1331
  const valueText = ctx2.collection.valueToString(ctx2.highlightedValue);
1319
- raf2(() => {
1332
+ raf(() => {
1320
1333
  inputEl.value = valueText || ctx2.inputValue;
1321
1334
  });
1322
1335
  },
@@ -1324,13 +1337,10 @@ function machine(userContext) {
1324
1337
  ctx2.collection = evt.value;
1325
1338
  },
1326
1339
  syncSelectedItems(ctx2) {
1327
- const prevSelectedItems = ctx2.selectedItems;
1328
- ctx2.selectedItems = ctx2.value.map((v) => {
1329
- const foundItem = prevSelectedItems.find((item) => ctx2.collection.itemToValue(item) === v);
1330
- if (foundItem)
1331
- return foundItem;
1332
- return ctx2.collection.item(v);
1333
- });
1340
+ sync.valueChange(ctx2);
1341
+ },
1342
+ syncHighlightedItem(ctx2) {
1343
+ sync.highlightChange(ctx2);
1334
1344
  },
1335
1345
  toggleVisibility(ctx2, evt, { send }) {
1336
1346
  send({ type: ctx2.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt });
@@ -1339,12 +1349,8 @@ function machine(userContext) {
1339
1349
  }
1340
1350
  );
1341
1351
  }
1342
- var invoke = {
1352
+ var sync = {
1343
1353
  valueChange: (ctx) => {
1344
- ctx.onValueChange?.({
1345
- value: Array.from(ctx.value),
1346
- items: ctx.selectedItems
1347
- });
1348
1354
  const prevSelectedItems = ctx.selectedItems;
1349
1355
  ctx.selectedItems = ctx.value.map((v) => {
1350
1356
  const foundItem = prevSelectedItems.find((item) => ctx.collection.itemToValue(item) === v);
@@ -1354,36 +1360,47 @@ var invoke = {
1354
1360
  });
1355
1361
  const valueAsString = ctx.collection.itemsToString(ctx.selectedItems);
1356
1362
  ctx.valueAsString = valueAsString;
1357
- let nextInputValue;
1363
+ let inputValue;
1358
1364
  if (ctx.getSelectionValue) {
1359
- nextInputValue = ctx.getSelectionValue({
1365
+ inputValue = ctx.getSelectionValue({
1360
1366
  inputValue: ctx.inputValue,
1361
1367
  selectedItems: Array.from(ctx.selectedItems),
1362
1368
  valueAsString
1363
1369
  });
1364
1370
  } else {
1365
- nextInputValue = match(ctx.selectionBehavior, {
1371
+ inputValue = match(ctx.selectionBehavior, {
1366
1372
  replace: ctx.valueAsString,
1367
1373
  preserve: ctx.inputValue,
1368
1374
  clear: ""
1369
1375
  });
1370
1376
  }
1371
- ctx.inputValue = nextInputValue;
1372
- invoke.inputChange(ctx);
1377
+ set.inputValue(ctx, inputValue);
1378
+ },
1379
+ highlightChange: (ctx) => {
1380
+ ctx.highlightedItem = ctx.collection.item(ctx.highlightedValue);
1381
+ }
1382
+ };
1383
+ var invoke = {
1384
+ valueChange: (ctx) => {
1385
+ sync.valueChange(ctx);
1386
+ ctx.onValueChange?.({
1387
+ value: Array.from(ctx.value),
1388
+ items: Array.from(ctx.selectedItems)
1389
+ });
1373
1390
  },
1374
1391
  highlightChange: (ctx) => {
1392
+ sync.highlightChange(ctx);
1375
1393
  ctx.onHighlightChange?.({
1376
1394
  highlightedValue: ctx.highlightedValue,
1377
- highligtedItem: ctx.highlightedItem
1395
+ highlightedItem: ctx.highlightedItem
1378
1396
  });
1379
- ctx.highlightedItem = ctx.collection.item(ctx.highlightedValue);
1380
1397
  },
1381
1398
  inputChange: (ctx) => {
1382
1399
  ctx.onInputValueChange?.({ inputValue: ctx.inputValue });
1383
1400
  }
1384
1401
  };
1385
1402
  var set = {
1386
- selectedItem: (ctx, value, force = false) => {
1403
+ value: (ctx, value, force = false) => {
1387
1404
  if (isEqual(ctx.value, value))
1388
1405
  return;
1389
1406
  if (value == null && !force)
@@ -1393,16 +1410,14 @@ var set = {
1393
1410
  invoke.valueChange(ctx);
1394
1411
  return;
1395
1412
  }
1396
- ctx.value = ctx.multiple ? addOrRemove(ctx.value, value) : [value];
1397
- invoke.valueChange(ctx);
1398
- },
1399
- selectedItems: (ctx, value) => {
1400
- if (isEqual(ctx.value, value))
1401
- return;
1402
- ctx.value = value;
1413
+ if (isArray(value)) {
1414
+ ctx.value = value;
1415
+ } else if (value != null) {
1416
+ ctx.value = ctx.multiple ? addOrRemove(ctx.value, value) : [value];
1417
+ }
1403
1418
  invoke.valueChange(ctx);
1404
1419
  },
1405
- highlightedItem: (ctx, value, force = false) => {
1420
+ highlightedValue: (ctx, value, force = false) => {
1406
1421
  if (isEqual(ctx.highlightedValue, value))
1407
1422
  return;
1408
1423
  if (!value && !force)