@zag-js/combobox 1.34.1 → 1.35.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.
@@ -0,0 +1,1050 @@
1
+ // src/combobox.machine.ts
2
+ import { setup } from "@zag-js/core";
3
+ import { trackDismissableElement } from "@zag-js/dismissable";
4
+ import { clickIfLink, nextTick, observeAttributes, raf, scrollIntoView, setCaretToEnd } from "@zag-js/dom-query";
5
+ import { getInteractionModality, setInteractionModality, trackFocusVisible } from "@zag-js/focus-visible";
6
+ import { getPlacement } from "@zag-js/popper";
7
+ import { addOrRemove, isBoolean, isEqual, match, remove } from "@zag-js/utils";
8
+ import { collection } from "./combobox.collection.mjs";
9
+ import * as dom from "./combobox.dom.mjs";
10
+ var { guards, createMachine, choose } = setup();
11
+ var { and, not } = guards;
12
+ var machine = createMachine({
13
+ props({ props }) {
14
+ return {
15
+ loopFocus: true,
16
+ openOnClick: false,
17
+ defaultValue: [],
18
+ defaultInputValue: "",
19
+ closeOnSelect: !props.multiple,
20
+ allowCustomValue: false,
21
+ alwaysSubmitOnEnter: false,
22
+ inputBehavior: "none",
23
+ selectionBehavior: props.multiple ? "clear" : "replace",
24
+ openOnKeyPress: true,
25
+ openOnChange: true,
26
+ composite: true,
27
+ navigate({ node }) {
28
+ clickIfLink(node);
29
+ },
30
+ collection: collection.empty(),
31
+ ...props,
32
+ positioning: {
33
+ placement: "bottom",
34
+ sameWidth: true,
35
+ ...props.positioning
36
+ },
37
+ translations: {
38
+ triggerLabel: "Toggle suggestions",
39
+ clearTriggerLabel: "Clear value",
40
+ ...props.translations
41
+ }
42
+ };
43
+ },
44
+ initialState({ prop }) {
45
+ const open = prop("open") || prop("defaultOpen");
46
+ return open ? "suggesting" : "idle";
47
+ },
48
+ context({ prop, bindable, getContext, getEvent }) {
49
+ return {
50
+ currentPlacement: bindable(() => ({
51
+ defaultValue: void 0
52
+ })),
53
+ value: bindable(() => ({
54
+ defaultValue: prop("defaultValue"),
55
+ value: prop("value"),
56
+ isEqual,
57
+ hash(value) {
58
+ return value.join(",");
59
+ },
60
+ onChange(value) {
61
+ const context = getContext();
62
+ const prevSelectedItems = context.get("selectedItems");
63
+ const collection2 = prop("collection");
64
+ const findItems = (vals) => vals.map((v) => prevSelectedItems.find((item) => collection2.getItemValue(item) === v) || collection2.find(v));
65
+ const nextItems = findItems(value);
66
+ const effectiveValue = prop("value") || value;
67
+ context.set("selectedItems", findItems(effectiveValue));
68
+ prop("onValueChange")?.({ value, items: nextItems });
69
+ }
70
+ })),
71
+ highlightedValue: bindable(() => ({
72
+ defaultValue: prop("defaultHighlightedValue") || null,
73
+ value: prop("highlightedValue"),
74
+ onChange(value) {
75
+ const item = prop("collection").find(value);
76
+ prop("onHighlightChange")?.({ highlightedValue: value, highlightedItem: item });
77
+ }
78
+ })),
79
+ inputValue: bindable(() => {
80
+ let inputValue = prop("inputValue") || prop("defaultInputValue");
81
+ const value = prop("value") || prop("defaultValue");
82
+ if (!inputValue.trim() && !prop("multiple")) {
83
+ const valueAsString = prop("collection").stringifyMany(value);
84
+ inputValue = match(prop("selectionBehavior"), {
85
+ preserve: inputValue || valueAsString,
86
+ replace: valueAsString,
87
+ clear: ""
88
+ });
89
+ }
90
+ return {
91
+ defaultValue: inputValue,
92
+ value: prop("inputValue"),
93
+ onChange(value2) {
94
+ const event = getEvent();
95
+ const reason = (event.previousEvent || event).src;
96
+ prop("onInputValueChange")?.({ inputValue: value2, reason });
97
+ }
98
+ };
99
+ }),
100
+ highlightedItem: bindable(() => {
101
+ const highlightedValue = prop("highlightedValue");
102
+ const highlightedItem = prop("collection").find(highlightedValue);
103
+ return { defaultValue: highlightedItem };
104
+ }),
105
+ selectedItems: bindable(() => {
106
+ const value = prop("value") || prop("defaultValue") || [];
107
+ const selectedItems = prop("collection").findMany(value);
108
+ return { defaultValue: selectedItems };
109
+ })
110
+ };
111
+ },
112
+ computed: {
113
+ isInputValueEmpty: ({ context }) => context.get("inputValue").length === 0,
114
+ isInteractive: ({ prop }) => !(prop("readOnly") || prop("disabled")),
115
+ autoComplete: ({ prop }) => prop("inputBehavior") === "autocomplete",
116
+ autoHighlight: ({ prop }) => prop("inputBehavior") === "autohighlight",
117
+ hasSelectedItems: ({ context }) => context.get("value").length > 0,
118
+ valueAsString: ({ context, prop }) => prop("collection").stringifyItems(context.get("selectedItems")),
119
+ isCustomValue: ({ context, computed }) => context.get("inputValue") !== computed("valueAsString")
120
+ },
121
+ watch({ context, prop, track, action, send }) {
122
+ track([() => context.hash("value")], () => {
123
+ action(["syncSelectedItems"]);
124
+ });
125
+ track([() => context.get("inputValue")], () => {
126
+ action(["syncInputValue"]);
127
+ });
128
+ track([() => context.get("highlightedValue")], () => {
129
+ action(["syncHighlightedItem", "autofillInputValue"]);
130
+ });
131
+ track([() => prop("open")], () => {
132
+ action(["toggleVisibility"]);
133
+ });
134
+ track([() => prop("collection").toString()], () => {
135
+ send({ type: "CHILDREN_CHANGE" });
136
+ });
137
+ },
138
+ on: {
139
+ "SELECTED_ITEMS.SYNC": {
140
+ actions: ["syncSelectedItems"]
141
+ },
142
+ "HIGHLIGHTED_VALUE.SET": {
143
+ actions: ["setHighlightedValue"]
144
+ },
145
+ "HIGHLIGHTED_VALUE.CLEAR": {
146
+ actions: ["clearHighlightedValue"]
147
+ },
148
+ "ITEM.SELECT": {
149
+ actions: ["selectItem"]
150
+ },
151
+ "ITEM.CLEAR": {
152
+ actions: ["clearItem"]
153
+ },
154
+ "VALUE.SET": {
155
+ actions: ["setValue"]
156
+ },
157
+ "INPUT_VALUE.SET": {
158
+ actions: ["setInputValue"]
159
+ },
160
+ "POSITIONING.SET": {
161
+ actions: ["reposition"]
162
+ }
163
+ },
164
+ entry: choose([
165
+ {
166
+ guard: "autoFocus",
167
+ actions: ["setInitialFocus"]
168
+ }
169
+ ]),
170
+ states: {
171
+ idle: {
172
+ tags: ["idle", "closed"],
173
+ entry: ["scrollContentToTop", "clearHighlightedValue"],
174
+ on: {
175
+ "CONTROLLED.OPEN": {
176
+ target: "interacting"
177
+ },
178
+ "TRIGGER.CLICK": [
179
+ {
180
+ guard: "isOpenControlled",
181
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"]
182
+ },
183
+ {
184
+ target: "interacting",
185
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"]
186
+ }
187
+ ],
188
+ "INPUT.CLICK": [
189
+ {
190
+ guard: "isOpenControlled",
191
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
192
+ },
193
+ {
194
+ target: "interacting",
195
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
196
+ }
197
+ ],
198
+ "INPUT.FOCUS": {
199
+ target: "focused"
200
+ },
201
+ OPEN: [
202
+ {
203
+ guard: "isOpenControlled",
204
+ actions: ["invokeOnOpen"]
205
+ },
206
+ {
207
+ target: "interacting",
208
+ actions: ["invokeOnOpen"]
209
+ }
210
+ ],
211
+ "VALUE.CLEAR": {
212
+ target: "focused",
213
+ actions: ["clearInputValue", "clearSelectedItems", "setInitialFocus"]
214
+ }
215
+ }
216
+ },
217
+ focused: {
218
+ tags: ["focused", "closed"],
219
+ entry: ["scrollContentToTop", "clearHighlightedValue"],
220
+ on: {
221
+ "CONTROLLED.OPEN": [
222
+ {
223
+ guard: "isChangeEvent",
224
+ target: "suggesting"
225
+ },
226
+ {
227
+ target: "interacting"
228
+ }
229
+ ],
230
+ "INPUT.CHANGE": [
231
+ {
232
+ guard: and("isOpenControlled", "openOnChange"),
233
+ actions: ["setInputValue", "invokeOnOpen", "highlightFirstItemIfNeeded"]
234
+ },
235
+ {
236
+ guard: "openOnChange",
237
+ target: "suggesting",
238
+ actions: ["setInputValue", "invokeOnOpen", "highlightFirstItemIfNeeded"]
239
+ },
240
+ {
241
+ actions: ["setInputValue"]
242
+ }
243
+ ],
244
+ "LAYER.INTERACT_OUTSIDE": {
245
+ target: "idle"
246
+ },
247
+ "INPUT.ESCAPE": {
248
+ guard: and("isCustomValue", not("allowCustomValue")),
249
+ actions: ["revertInputValue"]
250
+ },
251
+ "INPUT.BLUR": {
252
+ target: "idle"
253
+ },
254
+ "INPUT.CLICK": [
255
+ {
256
+ guard: "isOpenControlled",
257
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
258
+ },
259
+ {
260
+ target: "interacting",
261
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
262
+ }
263
+ ],
264
+ "TRIGGER.CLICK": [
265
+ {
266
+ guard: "isOpenControlled",
267
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"]
268
+ },
269
+ {
270
+ target: "interacting",
271
+ actions: ["setInitialFocus", "highlightFirstSelectedItem", "invokeOnOpen"]
272
+ }
273
+ ],
274
+ "INPUT.ARROW_DOWN": [
275
+ // == group 1 ==
276
+ {
277
+ guard: and("isOpenControlled", "autoComplete"),
278
+ actions: ["invokeOnOpen"]
279
+ },
280
+ {
281
+ guard: "autoComplete",
282
+ target: "interacting",
283
+ actions: ["invokeOnOpen"]
284
+ },
285
+ // == group 2 ==
286
+ {
287
+ guard: "isOpenControlled",
288
+ actions: ["highlightFirstOrSelectedItem", "invokeOnOpen"]
289
+ },
290
+ {
291
+ target: "interacting",
292
+ actions: ["highlightFirstOrSelectedItem", "invokeOnOpen"]
293
+ }
294
+ ],
295
+ "INPUT.ARROW_UP": [
296
+ // == group 1 ==
297
+ {
298
+ guard: and("isOpenControlled", "autoComplete"),
299
+ actions: ["invokeOnOpen"]
300
+ },
301
+ {
302
+ guard: "autoComplete",
303
+ target: "interacting",
304
+ actions: ["invokeOnOpen"]
305
+ },
306
+ // == group 2 ==
307
+ {
308
+ guard: "isOpenControlled",
309
+ actions: ["highlightLastOrSelectedItem", "invokeOnOpen"]
310
+ },
311
+ {
312
+ target: "interacting",
313
+ actions: ["highlightLastOrSelectedItem", "invokeOnOpen"]
314
+ }
315
+ ],
316
+ OPEN: [
317
+ {
318
+ guard: "isOpenControlled",
319
+ actions: ["invokeOnOpen"]
320
+ },
321
+ {
322
+ target: "interacting",
323
+ actions: ["invokeOnOpen"]
324
+ }
325
+ ],
326
+ "VALUE.CLEAR": {
327
+ actions: ["clearInputValue", "clearSelectedItems"]
328
+ }
329
+ }
330
+ },
331
+ interacting: {
332
+ tags: ["open", "focused"],
333
+ entry: ["setInitialFocus"],
334
+ effects: ["trackFocusVisible", "scrollToHighlightedItem", "trackDismissableLayer", "trackPlacement"],
335
+ on: {
336
+ "CONTROLLED.CLOSE": [
337
+ {
338
+ guard: "restoreFocus",
339
+ target: "focused",
340
+ actions: ["setFinalFocus"]
341
+ },
342
+ {
343
+ target: "idle"
344
+ }
345
+ ],
346
+ CHILDREN_CHANGE: [
347
+ {
348
+ guard: "isHighlightedItemRemoved",
349
+ actions: ["clearHighlightedValue"]
350
+ },
351
+ {
352
+ actions: ["scrollToHighlightedItem"]
353
+ }
354
+ ],
355
+ "INPUT.HOME": {
356
+ actions: ["highlightFirstItem"]
357
+ },
358
+ "INPUT.END": {
359
+ actions: ["highlightLastItem"]
360
+ },
361
+ "INPUT.ARROW_DOWN": [
362
+ {
363
+ guard: and("autoComplete", "isLastItemHighlighted"),
364
+ actions: ["clearHighlightedValue", "scrollContentToTop"]
365
+ },
366
+ {
367
+ actions: ["highlightNextItem"]
368
+ }
369
+ ],
370
+ "INPUT.ARROW_UP": [
371
+ {
372
+ guard: and("autoComplete", "isFirstItemHighlighted"),
373
+ actions: ["clearHighlightedValue"]
374
+ },
375
+ {
376
+ actions: ["highlightPrevItem"]
377
+ }
378
+ ],
379
+ "INPUT.ENTER": [
380
+ // == group 1 ==
381
+ {
382
+ guard: and("isOpenControlled", "isCustomValue", not("hasHighlightedItem"), not("allowCustomValue")),
383
+ actions: ["revertInputValue", "invokeOnClose"]
384
+ },
385
+ {
386
+ guard: and("isCustomValue", not("hasHighlightedItem"), not("allowCustomValue")),
387
+ target: "focused",
388
+ actions: ["revertInputValue", "invokeOnClose"]
389
+ },
390
+ // == group 2 ==
391
+ {
392
+ guard: and("isOpenControlled", "closeOnSelect"),
393
+ actions: ["selectHighlightedItem", "invokeOnClose"]
394
+ },
395
+ {
396
+ guard: "closeOnSelect",
397
+ target: "focused",
398
+ actions: ["selectHighlightedItem", "invokeOnClose", "setFinalFocus"]
399
+ },
400
+ {
401
+ actions: ["selectHighlightedItem"]
402
+ }
403
+ ],
404
+ "INPUT.CHANGE": [
405
+ {
406
+ guard: "autoComplete",
407
+ target: "suggesting",
408
+ actions: ["setInputValue"]
409
+ },
410
+ {
411
+ target: "suggesting",
412
+ actions: ["clearHighlightedValue", "setInputValue"]
413
+ }
414
+ ],
415
+ "ITEM.POINTER_MOVE": {
416
+ actions: ["setHighlightedValue"]
417
+ },
418
+ "ITEM.POINTER_LEAVE": {
419
+ actions: ["clearHighlightedValue"]
420
+ },
421
+ "ITEM.CLICK": [
422
+ {
423
+ guard: and("isOpenControlled", "closeOnSelect"),
424
+ actions: ["selectItem", "invokeOnClose"]
425
+ },
426
+ {
427
+ guard: "closeOnSelect",
428
+ target: "focused",
429
+ actions: ["selectItem", "invokeOnClose", "setFinalFocus"]
430
+ },
431
+ {
432
+ actions: ["selectItem"]
433
+ }
434
+ ],
435
+ "LAYER.ESCAPE": [
436
+ {
437
+ guard: and("isOpenControlled", "autoComplete"),
438
+ actions: ["syncInputValue", "invokeOnClose"]
439
+ },
440
+ {
441
+ guard: "autoComplete",
442
+ target: "focused",
443
+ actions: ["syncInputValue", "invokeOnClose"]
444
+ },
445
+ {
446
+ guard: "isOpenControlled",
447
+ actions: ["invokeOnClose"]
448
+ },
449
+ {
450
+ target: "focused",
451
+ actions: ["invokeOnClose", "setFinalFocus"]
452
+ }
453
+ ],
454
+ "TRIGGER.CLICK": [
455
+ {
456
+ guard: "isOpenControlled",
457
+ actions: ["invokeOnClose"]
458
+ },
459
+ {
460
+ target: "focused",
461
+ actions: ["invokeOnClose"]
462
+ }
463
+ ],
464
+ "LAYER.INTERACT_OUTSIDE": [
465
+ // == group 1 ==
466
+ {
467
+ guard: and("isOpenControlled", "isCustomValue", not("allowCustomValue")),
468
+ actions: ["revertInputValue", "invokeOnClose"]
469
+ },
470
+ {
471
+ guard: and("isCustomValue", not("allowCustomValue")),
472
+ target: "idle",
473
+ actions: ["revertInputValue", "invokeOnClose"]
474
+ },
475
+ // == group 2 ==
476
+ {
477
+ guard: "isOpenControlled",
478
+ actions: ["invokeOnClose"]
479
+ },
480
+ {
481
+ target: "idle",
482
+ actions: ["invokeOnClose"]
483
+ }
484
+ ],
485
+ CLOSE: [
486
+ {
487
+ guard: "isOpenControlled",
488
+ actions: ["invokeOnClose"]
489
+ },
490
+ {
491
+ target: "focused",
492
+ actions: ["invokeOnClose", "setFinalFocus"]
493
+ }
494
+ ],
495
+ "VALUE.CLEAR": [
496
+ {
497
+ guard: "isOpenControlled",
498
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"]
499
+ },
500
+ {
501
+ target: "focused",
502
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose", "setFinalFocus"]
503
+ }
504
+ ]
505
+ }
506
+ },
507
+ suggesting: {
508
+ tags: ["open", "focused"],
509
+ effects: ["trackFocusVisible", "trackDismissableLayer", "scrollToHighlightedItem", "trackPlacement"],
510
+ entry: ["setInitialFocus"],
511
+ on: {
512
+ "CONTROLLED.CLOSE": [
513
+ {
514
+ guard: "restoreFocus",
515
+ target: "focused",
516
+ actions: ["setFinalFocus"]
517
+ },
518
+ {
519
+ target: "idle"
520
+ }
521
+ ],
522
+ CHILDREN_CHANGE: [
523
+ {
524
+ guard: and("isHighlightedItemRemoved", "hasCollectionItems", "autoHighlight"),
525
+ actions: ["clearHighlightedValue", "highlightFirstItem"]
526
+ },
527
+ {
528
+ guard: "isHighlightedItemRemoved",
529
+ actions: ["clearHighlightedValue"]
530
+ },
531
+ {
532
+ guard: "autoHighlight",
533
+ actions: ["highlightFirstItem"]
534
+ }
535
+ ],
536
+ "INPUT.ARROW_DOWN": {
537
+ target: "interacting",
538
+ actions: ["highlightNextItem"]
539
+ },
540
+ "INPUT.ARROW_UP": {
541
+ target: "interacting",
542
+ actions: ["highlightPrevItem"]
543
+ },
544
+ "INPUT.HOME": {
545
+ target: "interacting",
546
+ actions: ["highlightFirstItem"]
547
+ },
548
+ "INPUT.END": {
549
+ target: "interacting",
550
+ actions: ["highlightLastItem"]
551
+ },
552
+ "INPUT.ENTER": [
553
+ // == group 1 ==
554
+ {
555
+ guard: and("isOpenControlled", "isCustomValue", not("hasHighlightedItem"), not("allowCustomValue")),
556
+ actions: ["revertInputValue", "invokeOnClose"]
557
+ },
558
+ {
559
+ guard: and("isCustomValue", not("hasHighlightedItem"), not("allowCustomValue")),
560
+ target: "focused",
561
+ actions: ["revertInputValue", "invokeOnClose"]
562
+ },
563
+ // == group 2 ==
564
+ {
565
+ guard: and("isOpenControlled", "closeOnSelect"),
566
+ actions: ["selectHighlightedItem", "invokeOnClose"]
567
+ },
568
+ {
569
+ guard: "closeOnSelect",
570
+ target: "focused",
571
+ actions: ["selectHighlightedItem", "invokeOnClose", "setFinalFocus"]
572
+ },
573
+ {
574
+ actions: ["selectHighlightedItem"]
575
+ }
576
+ ],
577
+ "INPUT.CHANGE": {
578
+ actions: ["setInputValue"]
579
+ },
580
+ "LAYER.ESCAPE": [
581
+ {
582
+ guard: "isOpenControlled",
583
+ actions: ["invokeOnClose"]
584
+ },
585
+ {
586
+ target: "focused",
587
+ actions: ["invokeOnClose"]
588
+ }
589
+ ],
590
+ "ITEM.POINTER_MOVE": {
591
+ target: "interacting",
592
+ actions: ["setHighlightedValue"]
593
+ },
594
+ "ITEM.POINTER_LEAVE": {
595
+ actions: ["clearHighlightedValue"]
596
+ },
597
+ "LAYER.INTERACT_OUTSIDE": [
598
+ // == group 1 ==
599
+ {
600
+ guard: and("isOpenControlled", "isCustomValue", not("allowCustomValue")),
601
+ actions: ["revertInputValue", "invokeOnClose"]
602
+ },
603
+ {
604
+ guard: and("isCustomValue", not("allowCustomValue")),
605
+ target: "idle",
606
+ actions: ["revertInputValue", "invokeOnClose"]
607
+ },
608
+ // == group 2 ==
609
+ {
610
+ guard: "isOpenControlled",
611
+ actions: ["invokeOnClose"]
612
+ },
613
+ {
614
+ target: "idle",
615
+ actions: ["invokeOnClose"]
616
+ }
617
+ ],
618
+ "TRIGGER.CLICK": [
619
+ {
620
+ guard: "isOpenControlled",
621
+ actions: ["invokeOnClose"]
622
+ },
623
+ {
624
+ target: "focused",
625
+ actions: ["invokeOnClose"]
626
+ }
627
+ ],
628
+ "ITEM.CLICK": [
629
+ {
630
+ guard: and("isOpenControlled", "closeOnSelect"),
631
+ actions: ["selectItem", "invokeOnClose"]
632
+ },
633
+ {
634
+ guard: "closeOnSelect",
635
+ target: "focused",
636
+ actions: ["selectItem", "invokeOnClose", "setFinalFocus"]
637
+ },
638
+ {
639
+ actions: ["selectItem"]
640
+ }
641
+ ],
642
+ CLOSE: [
643
+ {
644
+ guard: "isOpenControlled",
645
+ actions: ["invokeOnClose"]
646
+ },
647
+ {
648
+ target: "focused",
649
+ actions: ["invokeOnClose", "setFinalFocus"]
650
+ }
651
+ ],
652
+ "VALUE.CLEAR": [
653
+ {
654
+ guard: "isOpenControlled",
655
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"]
656
+ },
657
+ {
658
+ target: "focused",
659
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose", "setFinalFocus"]
660
+ }
661
+ ]
662
+ }
663
+ }
664
+ },
665
+ implementations: {
666
+ guards: {
667
+ isInputValueEmpty: ({ computed }) => computed("isInputValueEmpty"),
668
+ autoComplete: ({ computed, prop }) => computed("autoComplete") && !prop("multiple"),
669
+ autoHighlight: ({ computed }) => computed("autoHighlight"),
670
+ isFirstItemHighlighted: ({ prop, context }) => prop("collection").firstValue === context.get("highlightedValue"),
671
+ isLastItemHighlighted: ({ prop, context }) => prop("collection").lastValue === context.get("highlightedValue"),
672
+ isCustomValue: ({ computed }) => computed("isCustomValue"),
673
+ allowCustomValue: ({ prop }) => !!prop("allowCustomValue"),
674
+ hasHighlightedItem: ({ context }) => context.get("highlightedValue") != null,
675
+ closeOnSelect: ({ prop }) => !!prop("closeOnSelect"),
676
+ isOpenControlled: ({ prop }) => prop("open") != null,
677
+ openOnChange: ({ prop, context }) => {
678
+ const openOnChange = prop("openOnChange");
679
+ if (isBoolean(openOnChange)) return openOnChange;
680
+ return !!openOnChange?.({ inputValue: context.get("inputValue") });
681
+ },
682
+ restoreFocus: ({ event }) => {
683
+ const restoreFocus = event.restoreFocus ?? event.previousEvent?.restoreFocus;
684
+ return restoreFocus == null ? true : !!restoreFocus;
685
+ },
686
+ isChangeEvent: ({ event }) => event.previousEvent?.type === "INPUT.CHANGE",
687
+ autoFocus: ({ prop }) => !!prop("autoFocus"),
688
+ isHighlightedItemRemoved: ({ prop, context }) => !prop("collection").has(context.get("highlightedValue")),
689
+ hasCollectionItems: ({ prop }) => prop("collection").size > 0
690
+ },
691
+ effects: {
692
+ trackFocusVisible({ scope }) {
693
+ return trackFocusVisible({ root: scope.getRootNode?.() });
694
+ },
695
+ trackDismissableLayer({ send, prop, scope }) {
696
+ if (prop("disableLayer")) return;
697
+ const contentEl = () => dom.getContentEl(scope);
698
+ return trackDismissableElement(contentEl, {
699
+ type: "listbox",
700
+ defer: true,
701
+ exclude: () => [dom.getInputEl(scope), dom.getTriggerEl(scope), dom.getClearTriggerEl(scope)],
702
+ onFocusOutside: prop("onFocusOutside"),
703
+ onPointerDownOutside: prop("onPointerDownOutside"),
704
+ onInteractOutside: prop("onInteractOutside"),
705
+ onEscapeKeyDown(event) {
706
+ event.preventDefault();
707
+ event.stopPropagation();
708
+ send({ type: "LAYER.ESCAPE", src: "escape-key" });
709
+ },
710
+ onDismiss() {
711
+ send({ type: "LAYER.INTERACT_OUTSIDE", src: "interact-outside", restoreFocus: false });
712
+ }
713
+ });
714
+ },
715
+ trackPlacement({ context, prop, scope }) {
716
+ const anchorEl = () => dom.getControlEl(scope) || dom.getTriggerEl(scope);
717
+ const positionerEl = () => dom.getPositionerEl(scope);
718
+ context.set("currentPlacement", prop("positioning").placement);
719
+ return getPlacement(anchorEl, positionerEl, {
720
+ ...prop("positioning"),
721
+ defer: true,
722
+ onComplete(data) {
723
+ context.set("currentPlacement", data.placement);
724
+ }
725
+ });
726
+ },
727
+ scrollToHighlightedItem({ context, prop, scope }) {
728
+ const inputEl = dom.getInputEl(scope);
729
+ let cleanups = [];
730
+ const exec = (immediate) => {
731
+ const modality = getInteractionModality();
732
+ if (modality === "pointer") return;
733
+ const highlightedValue = context.get("highlightedValue");
734
+ if (!highlightedValue) return;
735
+ const contentEl = dom.getContentEl(scope);
736
+ const scrollToIndexFn = prop("scrollToIndexFn");
737
+ if (scrollToIndexFn) {
738
+ const highlightedIndex = prop("collection").indexOf(highlightedValue);
739
+ scrollToIndexFn({
740
+ index: highlightedIndex,
741
+ immediate,
742
+ getElement: () => dom.getItemEl(scope, highlightedValue)
743
+ });
744
+ return;
745
+ }
746
+ const itemEl = dom.getItemEl(scope, highlightedValue);
747
+ const raf_cleanup = raf(() => {
748
+ scrollIntoView(itemEl, { rootEl: contentEl, block: "nearest" });
749
+ });
750
+ cleanups.push(raf_cleanup);
751
+ };
752
+ const rafCleanup = raf(() => {
753
+ setInteractionModality("virtual");
754
+ exec(true);
755
+ });
756
+ cleanups.push(rafCleanup);
757
+ const observerCleanup = observeAttributes(inputEl, {
758
+ attributes: ["aria-activedescendant"],
759
+ callback: () => exec(false)
760
+ });
761
+ cleanups.push(observerCleanup);
762
+ return () => {
763
+ cleanups.forEach((cleanup) => cleanup());
764
+ };
765
+ }
766
+ },
767
+ actions: {
768
+ reposition({ context, prop, scope, event }) {
769
+ const controlEl = () => dom.getControlEl(scope);
770
+ const positionerEl = () => dom.getPositionerEl(scope);
771
+ getPlacement(controlEl, positionerEl, {
772
+ ...prop("positioning"),
773
+ ...event.options,
774
+ defer: true,
775
+ listeners: false,
776
+ onComplete(data) {
777
+ context.set("currentPlacement", data.placement);
778
+ }
779
+ });
780
+ },
781
+ setHighlightedValue({ context, event }) {
782
+ if (event.value == null) return;
783
+ context.set("highlightedValue", event.value);
784
+ },
785
+ clearHighlightedValue({ context }) {
786
+ context.set("highlightedValue", null);
787
+ },
788
+ selectHighlightedItem(params) {
789
+ const { context, prop } = params;
790
+ const collection2 = prop("collection");
791
+ const highlightedValue = context.get("highlightedValue");
792
+ if (!highlightedValue || !collection2.has(highlightedValue)) return;
793
+ const nextValue = prop("multiple") ? addOrRemove(context.get("value"), highlightedValue) : [highlightedValue];
794
+ prop("onSelect")?.({ value: nextValue, itemValue: highlightedValue });
795
+ context.set("value", nextValue);
796
+ const inputValue = match(prop("selectionBehavior"), {
797
+ preserve: context.get("inputValue"),
798
+ replace: collection2.stringifyMany(nextValue),
799
+ clear: ""
800
+ });
801
+ context.set("inputValue", inputValue);
802
+ },
803
+ scrollToHighlightedItem({ context, prop, scope }) {
804
+ nextTick(() => {
805
+ const highlightedValue = context.get("highlightedValue");
806
+ if (highlightedValue == null) return;
807
+ const itemEl = dom.getItemEl(scope, highlightedValue);
808
+ const contentEl = dom.getContentEl(scope);
809
+ const scrollToIndexFn = prop("scrollToIndexFn");
810
+ if (scrollToIndexFn) {
811
+ const highlightedIndex = prop("collection").indexOf(highlightedValue);
812
+ scrollToIndexFn({
813
+ index: highlightedIndex,
814
+ immediate: true,
815
+ getElement: () => dom.getItemEl(scope, highlightedValue)
816
+ });
817
+ return;
818
+ }
819
+ scrollIntoView(itemEl, { rootEl: contentEl, block: "nearest" });
820
+ });
821
+ },
822
+ selectItem(params) {
823
+ const { context, event, flush, prop } = params;
824
+ if (event.value == null) return;
825
+ flush(() => {
826
+ const nextValue = prop("multiple") ? addOrRemove(context.get("value"), event.value) : [event.value];
827
+ prop("onSelect")?.({ value: nextValue, itemValue: event.value });
828
+ context.set("value", nextValue);
829
+ const inputValue = match(prop("selectionBehavior"), {
830
+ preserve: context.get("inputValue"),
831
+ replace: prop("collection").stringifyMany(nextValue),
832
+ clear: ""
833
+ });
834
+ context.set("inputValue", inputValue);
835
+ });
836
+ },
837
+ clearItem(params) {
838
+ const { context, event, flush, prop } = params;
839
+ if (event.value == null) return;
840
+ flush(() => {
841
+ const nextValue = remove(context.get("value"), event.value);
842
+ context.set("value", nextValue);
843
+ const inputValue = match(prop("selectionBehavior"), {
844
+ preserve: context.get("inputValue"),
845
+ replace: prop("collection").stringifyMany(nextValue),
846
+ clear: ""
847
+ });
848
+ context.set("inputValue", inputValue);
849
+ });
850
+ },
851
+ setInitialFocus({ scope }) {
852
+ raf(() => {
853
+ dom.focusInputEl(scope);
854
+ });
855
+ },
856
+ setFinalFocus({ scope }) {
857
+ raf(() => {
858
+ const triggerEl = dom.getTriggerEl(scope);
859
+ if (triggerEl?.dataset.focusable == null) {
860
+ dom.focusInputEl(scope);
861
+ } else {
862
+ dom.focusTriggerEl(scope);
863
+ }
864
+ });
865
+ },
866
+ syncInputValue({ context, scope, event }) {
867
+ const inputEl = dom.getInputEl(scope);
868
+ if (!inputEl) return;
869
+ inputEl.value = context.get("inputValue");
870
+ queueMicrotask(() => {
871
+ if (event.current().type === "INPUT.CHANGE") return;
872
+ setCaretToEnd(inputEl);
873
+ });
874
+ },
875
+ setInputValue({ context, event }) {
876
+ context.set("inputValue", event.value);
877
+ },
878
+ clearInputValue({ context }) {
879
+ context.set("inputValue", "");
880
+ },
881
+ revertInputValue({ context, prop, computed }) {
882
+ const selectionBehavior = prop("selectionBehavior");
883
+ const inputValue = match(selectionBehavior, {
884
+ replace: computed("hasSelectedItems") ? computed("valueAsString") : "",
885
+ preserve: context.get("inputValue"),
886
+ clear: ""
887
+ });
888
+ context.set("inputValue", inputValue);
889
+ },
890
+ setValue(params) {
891
+ const { context, flush, event, prop } = params;
892
+ flush(() => {
893
+ context.set("value", event.value);
894
+ const inputValue = match(prop("selectionBehavior"), {
895
+ preserve: context.get("inputValue"),
896
+ replace: prop("collection").stringifyMany(event.value),
897
+ clear: ""
898
+ });
899
+ context.set("inputValue", inputValue);
900
+ });
901
+ },
902
+ clearSelectedItems(params) {
903
+ const { context, flush, prop } = params;
904
+ flush(() => {
905
+ context.set("value", []);
906
+ const inputValue = match(prop("selectionBehavior"), {
907
+ preserve: context.get("inputValue"),
908
+ replace: prop("collection").stringifyMany([]),
909
+ clear: ""
910
+ });
911
+ context.set("inputValue", inputValue);
912
+ });
913
+ },
914
+ scrollContentToTop({ prop, scope }) {
915
+ const scrollToIndexFn = prop("scrollToIndexFn");
916
+ if (scrollToIndexFn) {
917
+ const firstValue = prop("collection").firstValue;
918
+ scrollToIndexFn({
919
+ index: 0,
920
+ immediate: true,
921
+ getElement: () => dom.getItemEl(scope, firstValue)
922
+ });
923
+ } else {
924
+ const contentEl = dom.getContentEl(scope);
925
+ if (!contentEl) return;
926
+ contentEl.scrollTop = 0;
927
+ }
928
+ },
929
+ invokeOnOpen({ prop, event, context }) {
930
+ const reason = getOpenChangeReason(event);
931
+ prop("onOpenChange")?.({ open: true, reason, value: context.get("value") });
932
+ },
933
+ invokeOnClose({ prop, event, context }) {
934
+ const reason = getOpenChangeReason(event);
935
+ prop("onOpenChange")?.({ open: false, reason, value: context.get("value") });
936
+ },
937
+ highlightFirstItem({ context, prop, scope }) {
938
+ const exec = dom.getContentEl(scope) ? queueMicrotask : raf;
939
+ exec(() => {
940
+ const value = prop("collection").firstValue;
941
+ if (value) context.set("highlightedValue", value);
942
+ });
943
+ },
944
+ highlightFirstItemIfNeeded({ computed, action }) {
945
+ if (!computed("autoHighlight")) return;
946
+ action(["highlightFirstItem"]);
947
+ },
948
+ highlightLastItem({ context, prop, scope }) {
949
+ const exec = dom.getContentEl(scope) ? queueMicrotask : raf;
950
+ exec(() => {
951
+ const value = prop("collection").lastValue;
952
+ if (value) context.set("highlightedValue", value);
953
+ });
954
+ },
955
+ highlightNextItem({ context, prop }) {
956
+ let value = null;
957
+ const highlightedValue = context.get("highlightedValue");
958
+ const collection2 = prop("collection");
959
+ if (highlightedValue) {
960
+ value = collection2.getNextValue(highlightedValue);
961
+ if (!value && prop("loopFocus")) value = collection2.firstValue;
962
+ } else {
963
+ value = collection2.firstValue;
964
+ }
965
+ if (value) context.set("highlightedValue", value);
966
+ },
967
+ highlightPrevItem({ context, prop }) {
968
+ let value = null;
969
+ const highlightedValue = context.get("highlightedValue");
970
+ const collection2 = prop("collection");
971
+ if (highlightedValue) {
972
+ value = collection2.getPreviousValue(highlightedValue);
973
+ if (!value && prop("loopFocus")) value = collection2.lastValue;
974
+ } else {
975
+ value = collection2.lastValue;
976
+ }
977
+ if (value) context.set("highlightedValue", value);
978
+ },
979
+ highlightFirstSelectedItem({ context, prop }) {
980
+ raf(() => {
981
+ const [value] = prop("collection").sort(context.get("value"));
982
+ if (value) context.set("highlightedValue", value);
983
+ });
984
+ },
985
+ highlightFirstOrSelectedItem({ context, prop, computed }) {
986
+ raf(() => {
987
+ let value = null;
988
+ if (computed("hasSelectedItems")) {
989
+ value = prop("collection").sort(context.get("value"))[0];
990
+ } else {
991
+ value = prop("collection").firstValue;
992
+ }
993
+ if (value) context.set("highlightedValue", value);
994
+ });
995
+ },
996
+ highlightLastOrSelectedItem({ context, prop, computed }) {
997
+ raf(() => {
998
+ const collection2 = prop("collection");
999
+ let value = null;
1000
+ if (computed("hasSelectedItems")) {
1001
+ value = collection2.sort(context.get("value"))[0];
1002
+ } else {
1003
+ value = collection2.lastValue;
1004
+ }
1005
+ if (value) context.set("highlightedValue", value);
1006
+ });
1007
+ },
1008
+ autofillInputValue({ context, computed, prop, event, scope }) {
1009
+ const inputEl = dom.getInputEl(scope);
1010
+ const collection2 = prop("collection");
1011
+ if (!computed("autoComplete") || !inputEl || !event.keypress) return;
1012
+ const valueText = collection2.stringify(context.get("highlightedValue"));
1013
+ raf(() => {
1014
+ inputEl.value = valueText || context.get("inputValue");
1015
+ });
1016
+ },
1017
+ syncSelectedItems(params) {
1018
+ queueMicrotask(() => {
1019
+ const { context, prop } = params;
1020
+ const collection2 = prop("collection");
1021
+ const value = context.get("value");
1022
+ const selectedItems = value.map((v) => {
1023
+ const item = context.get("selectedItems").find((item2) => collection2.getItemValue(item2) === v);
1024
+ return item || collection2.find(v);
1025
+ });
1026
+ context.set("selectedItems", selectedItems);
1027
+ const inputValue = match(prop("selectionBehavior"), {
1028
+ preserve: context.get("inputValue"),
1029
+ replace: collection2.stringifyMany(value),
1030
+ clear: ""
1031
+ });
1032
+ context.set("inputValue", inputValue);
1033
+ });
1034
+ },
1035
+ syncHighlightedItem({ context, prop }) {
1036
+ const item = prop("collection").find(context.get("highlightedValue"));
1037
+ context.set("highlightedItem", item);
1038
+ },
1039
+ toggleVisibility({ event, send, prop }) {
1040
+ send({ type: prop("open") ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: event });
1041
+ }
1042
+ }
1043
+ }
1044
+ });
1045
+ function getOpenChangeReason(event) {
1046
+ return (event.previousEvent || event).src;
1047
+ }
1048
+ export {
1049
+ machine
1050
+ };