@zag-js/combobox 0.46.0 → 0.48.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
@@ -28,8 +28,14 @@ collection.empty = () => {
28
28
  };
29
29
 
30
30
  // src/combobox.connect.ts
31
- import { getEventKey, getNativeEvent, isContextMenuEvent, isLeftClick } from "@zag-js/dom-event";
32
- import { ariaAttr, dataAttr, raf } from "@zag-js/dom-query";
31
+ import {
32
+ clickIfLink,
33
+ getEventKey,
34
+ getNativeEvent,
35
+ isContextMenuEvent,
36
+ isLeftClick
37
+ } from "@zag-js/dom-event";
38
+ import { ariaAttr, dataAttr, isDownloadingEvent, isOpeningInNewTab, raf } from "@zag-js/dom-query";
33
39
  import { getPlacementStyles } from "@zag-js/popper";
34
40
 
35
41
  // src/combobox.dom.ts
@@ -40,6 +46,7 @@ var dom = createScope({
40
46
  getControlId: (ctx) => ctx.ids?.control ?? `combobox:${ctx.id}:control`,
41
47
  getInputId: (ctx) => ctx.ids?.input ?? `combobox:${ctx.id}:input`,
42
48
  getContentId: (ctx) => ctx.ids?.content ?? `combobox:${ctx.id}:content`,
49
+ getListId: (ctx) => `combobox:${ctx.id}:listbox`,
43
50
  getPositionerId: (ctx) => ctx.ids?.positioner ?? `combobox:${ctx.id}:popper`,
44
51
  getTriggerId: (ctx) => ctx.ids?.trigger ?? `combobox:${ctx.id}:toggle-btn`,
45
52
  getClearTriggerId: (ctx) => ctx.ids?.clearTrigger ?? `combobox:${ctx.id}:clear-btn`,
@@ -47,6 +54,7 @@ var dom = createScope({
47
54
  getItemGroupLabelId: (ctx, id) => ctx.ids?.itemGroupLabel?.(id) ?? `combobox:${ctx.id}:optgroup-label:${id}`,
48
55
  getItemId: (ctx, id) => `combobox:${ctx.id}:option:${id}`,
49
56
  getContentEl: (ctx) => dom.getById(ctx, dom.getContentId(ctx)),
57
+ getListEl: (ctx) => dom.getById(ctx, dom.getListId(ctx)),
50
58
  getInputEl: (ctx) => dom.getById(ctx, dom.getInputId(ctx)),
51
59
  getPositionerEl: (ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
52
60
  getControlEl: (ctx) => dom.getById(ctx, dom.getControlId(ctx)),
@@ -65,32 +73,33 @@ var dom = createScope({
65
73
  function connect(state, send, normalize) {
66
74
  const translations = state.context.translations;
67
75
  const collection2 = state.context.collection;
68
- const isDisabled = state.context.disabled;
69
- const isInteractive = state.context.isInteractive;
70
- const isInvalid = state.context.invalid;
71
- const isReadOnly = state.context.readOnly;
72
- const isOpen = state.hasTag("open");
73
- const isFocused = state.hasTag("focused");
76
+ const disabled = state.context.disabled;
77
+ const interactive = state.context.isInteractive;
78
+ const invalid = state.context.invalid;
79
+ const readOnly = state.context.readOnly;
80
+ const open = state.hasTag("open");
81
+ const focused = state.hasTag("focused");
82
+ const isDialogPopup = state.context.popup === "dialog";
74
83
  const popperStyles = getPlacementStyles({
75
84
  ...state.context.positioning,
76
85
  placement: state.context.currentPlacement
77
86
  });
78
87
  function getItemState(props) {
79
88
  const { item } = props;
80
- const disabled = collection2.isItemDisabled(item);
89
+ const disabled2 = collection2.isItemDisabled(item);
81
90
  const value = collection2.itemToValue(item);
82
91
  return {
83
92
  value,
84
- isDisabled: Boolean(disabled || isDisabled),
85
- isHighlighted: state.context.highlightedValue === value,
86
- isSelected: state.context.value.includes(value)
93
+ disabled: Boolean(disabled2 || disabled2),
94
+ highlighted: state.context.highlightedValue === value,
95
+ selected: state.context.value.includes(value)
87
96
  };
88
97
  }
89
98
  return {
90
- isFocused,
91
- isOpen,
99
+ focused,
100
+ open,
92
101
  inputValue: state.context.inputValue,
93
- isInputValueEmpty: state.context.isInputValueEmpty,
102
+ inputEmpty: state.context.isInputValueEmpty,
94
103
  highlightedValue: state.context.highlightedValue,
95
104
  highlightedItem: state.context.highlightedItem,
96
105
  value: state.context.value,
@@ -126,37 +135,42 @@ function connect(state, send, normalize) {
126
135
  focus() {
127
136
  dom.getInputEl(state.context)?.focus();
128
137
  },
129
- open() {
130
- send("OPEN");
131
- },
132
- close() {
133
- send("CLOSE");
138
+ setOpen(_open) {
139
+ if (_open === open)
140
+ return;
141
+ send(_open ? "OPEN" : "CLOSE");
134
142
  },
135
143
  rootProps: normalize.element({
136
144
  ...parts.root.attrs,
137
145
  dir: state.context.dir,
138
146
  id: dom.getRootId(state.context),
139
- "data-invalid": dataAttr(isInvalid),
140
- "data-readonly": dataAttr(isReadOnly)
147
+ "data-invalid": dataAttr(invalid),
148
+ "data-readonly": dataAttr(readOnly)
141
149
  }),
142
150
  labelProps: normalize.label({
143
151
  ...parts.label.attrs,
144
152
  dir: state.context.dir,
145
153
  htmlFor: dom.getInputId(state.context),
146
154
  id: dom.getLabelId(state.context),
147
- "data-readonly": dataAttr(isReadOnly),
148
- "data-disabled": dataAttr(isDisabled),
149
- "data-invalid": dataAttr(isInvalid),
150
- "data-focus": dataAttr(isFocused)
155
+ "data-readonly": dataAttr(readOnly),
156
+ "data-disabled": dataAttr(disabled),
157
+ "data-invalid": dataAttr(invalid),
158
+ "data-focus": dataAttr(focused),
159
+ onClick(event) {
160
+ if (!isDialogPopup)
161
+ return;
162
+ event.preventDefault();
163
+ dom.getTriggerEl(state.context)?.focus({ preventScroll: true });
164
+ }
151
165
  }),
152
166
  controlProps: normalize.element({
153
167
  ...parts.control.attrs,
154
168
  dir: state.context.dir,
155
169
  id: dom.getControlId(state.context),
156
- "data-state": isOpen ? "open" : "closed",
157
- "data-focus": dataAttr(isFocused),
158
- "data-disabled": dataAttr(isDisabled),
159
- "data-invalid": dataAttr(isInvalid)
170
+ "data-state": open ? "open" : "closed",
171
+ "data-focus": dataAttr(focused),
172
+ "data-disabled": dataAttr(disabled),
173
+ "data-invalid": dataAttr(invalid)
160
174
  }),
161
175
  positionerProps: normalize.element({
162
176
  ...parts.positioner.attrs,
@@ -167,47 +181,41 @@ function connect(state, send, normalize) {
167
181
  inputProps: normalize.input({
168
182
  ...parts.input.attrs,
169
183
  dir: state.context.dir,
170
- "aria-invalid": ariaAttr(isInvalid),
171
- "data-invalid": dataAttr(isInvalid),
184
+ "aria-invalid": ariaAttr(invalid),
185
+ "data-invalid": dataAttr(invalid),
172
186
  name: state.context.name,
173
187
  form: state.context.form,
174
- disabled: isDisabled,
188
+ disabled,
175
189
  autoFocus: state.context.autoFocus,
176
190
  autoComplete: "off",
177
191
  autoCorrect: "off",
178
192
  autoCapitalize: "none",
179
193
  spellCheck: "false",
180
- readOnly: isReadOnly,
194
+ readOnly,
181
195
  placeholder: state.context.placeholder,
182
196
  id: dom.getInputId(state.context),
183
197
  type: "text",
184
198
  role: "combobox",
185
199
  defaultValue: state.context.inputValue,
186
200
  "aria-autocomplete": state.context.autoComplete ? "both" : "list",
187
- "aria-controls": isOpen ? dom.getContentId(state.context) : void 0,
188
- "aria-expanded": isOpen,
189
- "data-state": isOpen ? "open" : "closed",
201
+ "aria-controls": isDialogPopup ? dom.getListId(state.context) : dom.getContentId(state.context),
202
+ "aria-expanded": open,
203
+ "data-state": open ? "open" : "closed",
190
204
  "aria-activedescendant": state.context.highlightedValue ? dom.getItemId(state.context, state.context.highlightedValue) : void 0,
191
- onCompositionStart() {
192
- send("INPUT.COMPOSITION_START");
193
- },
194
- onCompositionEnd() {
195
- raf(() => {
196
- send("INPUT.COMPOSITION_END");
197
- });
198
- },
199
205
  onClick() {
200
- if (!isInteractive)
206
+ if (!state.context.openOnClick)
207
+ return;
208
+ if (!interactive)
201
209
  return;
202
210
  send("INPUT.CLICK");
203
211
  },
204
212
  onFocus() {
205
- if (isDisabled)
213
+ if (disabled)
206
214
  return;
207
215
  send("INPUT.FOCUS");
208
216
  },
209
217
  onBlur() {
210
- if (isDisabled)
218
+ if (disabled)
211
219
  return;
212
220
  send("INPUT.BLUR");
213
221
  },
@@ -215,51 +223,57 @@ function connect(state, send, normalize) {
215
223
  send({ type: "INPUT.CHANGE", value: event.currentTarget.value });
216
224
  },
217
225
  onKeyDown(event) {
218
- if (!isInteractive)
226
+ if (event.defaultPrevented)
227
+ return;
228
+ if (!interactive)
219
229
  return;
220
230
  const evt = getNativeEvent(event);
221
231
  if (evt.ctrlKey || evt.shiftKey || evt.isComposing)
222
232
  return;
233
+ const openOnKeyPress = state.context.openOnKeyPress;
234
+ const isModifierKey = event.ctrlKey || event.metaKey || event.shiftKey;
235
+ const keypress = true;
223
236
  const keymap = {
224
237
  ArrowDown(event2) {
225
- send({ type: event2.altKey ? "INPUT.ARROW_DOWN+ALT" : "INPUT.ARROW_DOWN" });
238
+ if (!openOnKeyPress && !open)
239
+ return;
240
+ send({ type: event2.altKey ? "OPEN" : "INPUT.ARROW_DOWN", keypress });
226
241
  event2.preventDefault();
227
- event2.stopPropagation();
228
242
  },
229
243
  ArrowUp() {
230
- send(event.altKey ? "INPUT.ARROW_UP+ALT" : "INPUT.ARROW_UP");
244
+ if (!openOnKeyPress && !open)
245
+ return;
246
+ send({ type: event.altKey ? "CLOSE" : "INPUT.ARROW_UP", keypress });
231
247
  event.preventDefault();
232
- event.stopPropagation();
233
248
  },
234
249
  Home(event2) {
235
- const isModified = event2.ctrlKey || event2.metaKey || event2.shiftKey;
236
- if (isModified)
250
+ if (isModifierKey)
237
251
  return;
238
- send("INPUT.HOME");
239
- if (isOpen) {
252
+ send({ type: "INPUT.HOME", keypress });
253
+ if (open) {
240
254
  event2.preventDefault();
241
- event2.stopPropagation();
242
255
  }
243
256
  },
244
257
  End(event2) {
245
- const isModified = event2.ctrlKey || event2.metaKey || event2.shiftKey;
246
- if (isModified)
258
+ if (isModifierKey)
247
259
  return;
248
- send("INPUT.END");
249
- if (isOpen) {
260
+ send({ type: "INPUT.END", keypress });
261
+ if (open) {
250
262
  event2.preventDefault();
251
- event2.stopPropagation();
252
263
  }
253
264
  },
254
- Enter() {
255
- if (state.context.composing)
265
+ Enter(event2) {
266
+ if (evt.isComposing)
256
267
  return;
257
- send("INPUT.ENTER");
258
- event.preventDefault();
259
- event.stopPropagation();
268
+ send({ type: "INPUT.ENTER", keypress });
269
+ if (open) {
270
+ event2.preventDefault();
271
+ }
272
+ const itemEl = dom.getHighlightedItemEl(state.context);
273
+ clickIfLink(itemEl);
260
274
  },
261
275
  Escape() {
262
- send("INPUT.ESCAPE");
276
+ send({ type: "INPUT.ESCAPE", keypress });
263
277
  event.preventDefault();
264
278
  }
265
279
  };
@@ -272,59 +286,97 @@ function connect(state, send, normalize) {
272
286
  ...parts.trigger.attrs,
273
287
  dir: state.context.dir,
274
288
  id: dom.getTriggerId(state.context),
275
- "aria-haspopup": "listbox",
289
+ "aria-haspopup": isDialogPopup ? "dialog" : "listbox",
276
290
  type: "button",
277
- tabIndex: -1,
291
+ tabIndex: isDialogPopup ? 0 : -1,
278
292
  "aria-label": translations.triggerLabel,
279
- "aria-expanded": isOpen,
280
- "data-state": isOpen ? "open" : "closed",
281
- "aria-controls": isOpen ? dom.getContentId(state.context) : void 0,
282
- disabled: isDisabled,
283
- "data-readonly": dataAttr(isReadOnly),
284
- "data-disabled": dataAttr(isDisabled),
285
- onPointerDown(event) {
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) {
286
300
  const evt = getNativeEvent(event);
287
- if (!isInteractive || !isLeftClick(evt) || evt.pointerType === "touch")
301
+ if (!interactive)
302
+ return;
303
+ if (!isLeftClick(evt))
288
304
  return;
289
305
  send("TRIGGER.CLICK");
290
- event.preventDefault();
291
306
  },
292
- onPointerUp(event) {
293
- if (event.pointerType !== "touch")
307
+ onPointerDown(event) {
308
+ if (!interactive)
294
309
  return;
295
- send("TRIGGER.CLICK");
310
+ if (event.pointerType === "touch")
311
+ return;
312
+ event.preventDefault();
313
+ queueMicrotask(() => {
314
+ dom.getInputEl(state.context)?.focus({ preventScroll: true });
315
+ });
296
316
  },
297
- style: { outline: 0 }
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);
342
+ event.preventDefault();
343
+ }
344
+ }
298
345
  }),
299
346
  contentProps: normalize.element({
300
347
  ...parts.content.attrs,
301
348
  dir: state.context.dir,
302
349
  id: dom.getContentId(state.context),
303
- role: "listbox",
350
+ role: isDialogPopup ? "dialog" : "listbox",
304
351
  tabIndex: -1,
305
- hidden: !isOpen,
306
- "data-state": isOpen ? "open" : "closed",
352
+ hidden: !open,
353
+ "data-state": open ? "open" : "closed",
307
354
  "aria-labelledby": dom.getLabelId(state.context),
308
- "aria-multiselectable": state.context.multiple ? true : void 0,
355
+ "aria-multiselectable": state.context.multiple && !isDialogPopup ? true : void 0,
309
356
  onPointerDown(event) {
310
357
  event.preventDefault();
311
358
  }
312
359
  }),
360
+ // only used when triggerOnly: true
361
+ 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
365
+ }),
313
366
  clearTriggerProps: normalize.button({
314
367
  ...parts.clearTrigger.attrs,
315
368
  dir: state.context.dir,
316
369
  id: dom.getClearTriggerId(state.context),
317
370
  type: "button",
318
371
  tabIndex: -1,
319
- disabled: isDisabled,
372
+ disabled,
320
373
  "aria-label": translations.clearTriggerLabel,
374
+ "aria-controls": dom.getInputId(state.context),
321
375
  hidden: !state.context.value.length,
322
- onPointerDown(event) {
323
- const evt = getNativeEvent(event);
324
- if (!isInteractive || !isLeftClick(evt))
376
+ onClick() {
377
+ if (!interactive)
325
378
  return;
326
379
  send({ type: "VALUE.CLEAR", src: "clear-trigger" });
327
- event.preventDefault();
328
380
  }
329
381
  }),
330
382
  getItemState,
@@ -337,24 +389,35 @@ function connect(state, send, normalize) {
337
389
  id: dom.getItemId(state.context, value),
338
390
  role: "option",
339
391
  tabIndex: -1,
340
- "data-highlighted": dataAttr(itemState.isHighlighted),
341
- "data-state": itemState.isSelected ? "checked" : "unchecked",
342
- "aria-selected": itemState.isHighlighted,
343
- "aria-disabled": itemState.isDisabled,
344
- "data-disabled": dataAttr(itemState.isDisabled),
392
+ "data-highlighted": dataAttr(itemState.highlighted),
393
+ "data-state": itemState.selected ? "checked" : "unchecked",
394
+ "aria-selected": itemState.highlighted,
395
+ "aria-disabled": itemState.disabled,
396
+ "data-disabled": dataAttr(itemState.disabled),
345
397
  "data-value": itemState.value,
346
398
  onPointerMove() {
347
- if (itemState.isDisabled)
399
+ if (itemState.disabled)
348
400
  return;
349
- send({ type: "ITEM.POINTER_OVER", value });
401
+ send({ type: "ITEM.POINTER_MOVE", value });
350
402
  },
351
403
  onPointerLeave() {
352
- if (itemState.isDisabled)
404
+ if (props.persistFocus)
405
+ return;
406
+ if (itemState.disabled)
407
+ return;
408
+ const mouseMoved = state.previousEvent.type === "ITEM.POINTER_MOVE";
409
+ if (!mouseMoved)
353
410
  return;
354
411
  send({ type: "ITEM.POINTER_LEAVE", value });
355
412
  },
356
413
  onPointerUp(event) {
357
- if (itemState.isDisabled || isContextMenuEvent(event))
414
+ if (isDownloadingEvent(event))
415
+ return;
416
+ if (isOpeningInNewTab(event))
417
+ return;
418
+ if (isContextMenuEvent(event))
419
+ return;
420
+ if (itemState.disabled)
358
421
  return;
359
422
  send({ type: "ITEM.CLICK", src: "pointerup", value });
360
423
  },
@@ -369,8 +432,8 @@ function connect(state, send, normalize) {
369
432
  return normalize.element({
370
433
  ...parts.itemText.attrs,
371
434
  dir: state.context.dir,
372
- "data-disabled": dataAttr(itemState.isDisabled),
373
- "data-highlighted": dataAttr(itemState.isHighlighted)
435
+ "data-disabled": dataAttr(itemState.disabled),
436
+ "data-highlighted": dataAttr(itemState.highlighted)
374
437
  });
375
438
  },
376
439
  getItemIndicatorProps(props) {
@@ -379,8 +442,8 @@ function connect(state, send, normalize) {
379
442
  "aria-hidden": true,
380
443
  ...parts.itemIndicator.attrs,
381
444
  dir: state.context.dir,
382
- "data-state": itemState.isSelected ? "checked" : "unchecked",
383
- hidden: !itemState.isSelected
445
+ "data-state": itemState.selected ? "checked" : "unchecked",
446
+ hidden: !itemState.selected
384
447
  });
385
448
  },
386
449
  getItemGroupProps(props) {
@@ -408,31 +471,34 @@ function connect(state, send, normalize) {
408
471
  import { ariaHidden } from "@zag-js/aria-hidden";
409
472
  import { createMachine, guards } from "@zag-js/core";
410
473
  import { trackDismissableElement } from "@zag-js/dismissable";
411
- import { raf as raf2, scrollIntoView } from "@zag-js/dom-query";
412
- import { observeAttributes, observeChildren } from "@zag-js/mutation-observer";
474
+ import { observeAttributes, observeChildren, raf as raf2, scrollIntoView } from "@zag-js/dom-query";
413
475
  import { getPlacement } from "@zag-js/popper";
414
- import { addOrRemove, compact, isEqual, match } from "@zag-js/utils";
476
+ import { addOrRemove, compact, isBoolean, isEqual, match } from "@zag-js/utils";
415
477
  var { and, not } = guards;
416
- var KEYDOWN_EVENT_REGEX = /(ARROW_UP|ARROW_DOWN|HOME|END|ENTER|ESCAPE)/;
417
478
  function machine(userContext) {
418
479
  const ctx = compact(userContext);
419
480
  return createMachine(
420
481
  {
421
482
  id: "combobox",
422
- initial: ctx.autoFocus ? "focused" : "idle",
483
+ initial: ctx.open ? "suggesting" : "idle",
423
484
  context: {
424
- loop: true,
485
+ loopFocus: true,
425
486
  openOnClick: false,
426
- composing: false,
427
487
  value: [],
428
488
  highlightedValue: null,
429
489
  inputValue: "",
430
- selectOnBlur: true,
431
490
  allowCustomValue: false,
432
- closeOnSelect: true,
491
+ closeOnSelect: !ctx.multiple,
433
492
  inputBehavior: "none",
434
493
  selectionBehavior: "replace",
494
+ openOnKeyPress: true,
495
+ openOnChange: true,
496
+ dismissable: true,
497
+ popup: "listbox",
435
498
  ...ctx,
499
+ highlightedItem: null,
500
+ selectedItems: [],
501
+ valueAsString: "",
436
502
  collection: ctx.collection ?? collection.empty(),
437
503
  positioning: {
438
504
  placement: "bottom",
@@ -446,20 +512,20 @@ function machine(userContext) {
446
512
  ...ctx.translations
447
513
  }
448
514
  },
449
- created: ["initialize"],
515
+ created: ["syncInitialValues", "syncSelectionBehavior"],
450
516
  computed: {
451
517
  isInputValueEmpty: (ctx2) => ctx2.inputValue.length === 0,
452
518
  isInteractive: (ctx2) => !(ctx2.readOnly || ctx2.disabled),
453
519
  autoComplete: (ctx2) => ctx2.inputBehavior === "autocomplete",
454
520
  autoHighlight: (ctx2) => ctx2.inputBehavior === "autohighlight",
455
- selectedItems: (ctx2) => ctx2.collection.items(ctx2.value),
456
- highlightedItem: (ctx2) => ctx2.collection.item(ctx2.highlightedValue),
457
- valueAsString: (ctx2) => ctx2.collection.itemsToString(ctx2.selectedItems),
458
521
  hasSelectedItems: (ctx2) => ctx2.value.length > 0
459
522
  },
460
523
  watch: {
524
+ value: ["syncSelectedItems"],
461
525
  inputValue: ["syncInputValue"],
462
- highlightedValue: ["autofillInputValue"]
526
+ highlightedValue: ["autofillInputValue"],
527
+ multiple: ["syncSelectionBehavior"],
528
+ open: ["toggleVisibility"]
463
529
  },
464
530
  on: {
465
531
  "HIGHLIGHTED_VALUE.SET": {
@@ -477,16 +543,6 @@ function machine(userContext) {
477
543
  "INPUT_VALUE.SET": {
478
544
  actions: "setInputValue"
479
545
  },
480
- "VALUE.CLEAR": {
481
- target: "focused",
482
- actions: ["clearInputValue", "clearSelectedItems"]
483
- },
484
- "INPUT.COMPOSITION_START": {
485
- actions: ["setIsComposing"]
486
- },
487
- "INPUT.COMPOSITION_END": {
488
- actions: ["clearIsComposing"]
489
- },
490
546
  "COLLECTION.SET": {
491
547
  actions: ["setCollection"]
492
548
  },
@@ -499,32 +555,75 @@ function machine(userContext) {
499
555
  tags: ["idle", "closed"],
500
556
  entry: ["scrollContentToTop", "clearHighlightedItem"],
501
557
  on: {
502
- "TRIGGER.CLICK": {
503
- target: "interacting",
504
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
505
- },
506
- "INPUT.CLICK": {
507
- guard: "openOnClick",
508
- target: "interacting",
509
- actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
558
+ "CONTROLLED.OPEN": {
559
+ target: "interacting"
510
560
  },
561
+ "TRIGGER.CLICK": [
562
+ {
563
+ guard: "isOpenControlled",
564
+ actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
565
+ },
566
+ {
567
+ target: "interacting",
568
+ actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
569
+ }
570
+ ],
571
+ "INPUT.CLICK": [
572
+ {
573
+ guard: "isOpenControlled",
574
+ actions: ["invokeOnOpen"]
575
+ },
576
+ {
577
+ target: "interacting",
578
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
579
+ }
580
+ ],
511
581
  "INPUT.FOCUS": {
512
582
  target: "focused"
513
583
  },
514
- OPEN: {
515
- target: "interacting",
516
- actions: ["invokeOnOpen"]
584
+ OPEN: [
585
+ {
586
+ guard: "isOpenControlled",
587
+ actions: ["invokeOnOpen"]
588
+ },
589
+ {
590
+ target: "interacting",
591
+ actions: ["invokeOnOpen"]
592
+ }
593
+ ],
594
+ "VALUE.CLEAR": {
595
+ target: "focused",
596
+ actions: ["clearInputValue", "clearSelectedItems"]
517
597
  }
518
598
  }
519
599
  },
520
600
  focused: {
521
601
  tags: ["focused", "closed"],
522
- entry: ["focusInput", "scrollContentToTop", "clearHighlightedItem"],
602
+ entry: ["focusInputOrTrigger", "scrollContentToTop", "clearHighlightedItem"],
523
603
  on: {
524
- "INPUT.CHANGE": {
525
- target: "suggesting",
526
- actions: "setInputValue"
527
- },
604
+ "CONTROLLED.OPEN": [
605
+ {
606
+ guard: "isChangeEvent",
607
+ target: "suggesting"
608
+ },
609
+ {
610
+ target: "interacting"
611
+ }
612
+ ],
613
+ "INPUT.CHANGE": [
614
+ {
615
+ guard: and("isOpenControlled", "openOnChange"),
616
+ actions: ["setInputValue", "invokeOnOpen"]
617
+ },
618
+ {
619
+ guard: "openOnChange",
620
+ target: "suggesting",
621
+ actions: ["setInputValue", "invokeOnOpen"]
622
+ },
623
+ {
624
+ actions: "setInputValue"
625
+ }
626
+ ],
528
627
  "LAYER.INTERACT_OUTSIDE": {
529
628
  target: "idle"
530
629
  },
@@ -535,61 +634,103 @@ function machine(userContext) {
535
634
  "INPUT.BLUR": {
536
635
  target: "idle"
537
636
  },
538
- "INPUT.CLICK": {
539
- guard: "openOnClick",
540
- target: "interacting",
541
- actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
542
- },
543
- "TRIGGER.CLICK": {
544
- target: "interacting",
545
- actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
546
- },
637
+ "INPUT.CLICK": [
638
+ {
639
+ guard: "isOpenControlled",
640
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
641
+ },
642
+ {
643
+ target: "interacting",
644
+ actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
645
+ }
646
+ ],
647
+ "TRIGGER.CLICK": [
648
+ {
649
+ guard: "isOpenControlled",
650
+ actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
651
+ },
652
+ {
653
+ target: "interacting",
654
+ actions: ["focusInput", "highlightFirstSelectedItem", "invokeOnOpen"]
655
+ }
656
+ ],
547
657
  "INPUT.ARROW_DOWN": [
658
+ // == group 1 ==
659
+ {
660
+ guard: and("isOpenControlled", "autoComplete"),
661
+ actions: ["invokeOnOpen"]
662
+ },
548
663
  {
549
664
  guard: "autoComplete",
550
665
  target: "interacting",
551
666
  actions: ["invokeOnOpen"]
552
667
  },
668
+ // == group 2 ==
553
669
  {
554
- guard: "hasSelectedItems",
555
- target: "interacting",
556
- actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
670
+ guard: "isOpenControlled",
671
+ actions: ["highlightFirstOrSelectedItem", "invokeOnOpen"]
557
672
  },
558
673
  {
559
674
  target: "interacting",
560
- actions: ["highlightFirstItem", "invokeOnOpen"]
675
+ actions: ["highlightFirstOrSelectedItem", "invokeOnOpen"]
561
676
  }
562
677
  ],
563
- "INPUT.ARROW_DOWN+ALT": {
564
- target: "interacting",
565
- actions: "invokeOnOpen"
566
- },
567
678
  "INPUT.ARROW_UP": [
679
+ // == group 1 ==
568
680
  {
569
681
  guard: "autoComplete",
570
682
  target: "interacting",
571
683
  actions: "invokeOnOpen"
572
684
  },
573
685
  {
574
- guard: "hasSelectedItems",
686
+ guard: "autoComplete",
575
687
  target: "interacting",
576
- actions: ["highlightFirstSelectedItem", "invokeOnOpen"]
688
+ actions: "invokeOnOpen"
577
689
  },
690
+ // == group 2 ==
578
691
  {
579
692
  target: "interacting",
580
- actions: ["highlightLastItem", "invokeOnOpen"]
693
+ actions: ["highlightLastOrSelectedItem", "invokeOnOpen"]
694
+ },
695
+ {
696
+ target: "interacting",
697
+ actions: ["highlightLastOrSelectedItem", "invokeOnOpen"]
581
698
  }
582
699
  ],
583
- OPEN: {
584
- target: "interacting",
585
- actions: ["invokeOnOpen"]
700
+ OPEN: [
701
+ {
702
+ guard: "isOpenControlled",
703
+ actions: ["invokeOnOpen"]
704
+ },
705
+ {
706
+ target: "interacting",
707
+ actions: ["invokeOnOpen"]
708
+ }
709
+ ],
710
+ "VALUE.CLEAR": {
711
+ actions: ["clearInputValue", "clearSelectedItems"]
586
712
  }
587
713
  }
588
714
  },
589
715
  interacting: {
590
716
  tags: ["open", "focused"],
591
- activities: ["scrollIntoView", "trackDismissableLayer", "computePlacement", "hideOtherElements"],
717
+ activities: [
718
+ "scrollIntoView",
719
+ "trackDismissableLayer",
720
+ "computePlacement",
721
+ "hideOtherElements",
722
+ "trackContentHeight"
723
+ ],
592
724
  on: {
725
+ "CONTROLLED.CLOSE": [
726
+ {
727
+ guard: "restoreFocus",
728
+ target: "focused"
729
+ },
730
+ {
731
+ target: "idle"
732
+ }
733
+ ],
593
734
  "INPUT.HOME": {
594
735
  actions: ["highlightFirstItem"]
595
736
  },
@@ -614,31 +755,32 @@ function machine(userContext) {
614
755
  actions: "highlightPrevItem"
615
756
  }
616
757
  ],
617
- "INPUT.ARROW_UP+ALT": {
618
- target: "focused"
619
- },
620
758
  "INPUT.ENTER": [
621
759
  {
622
- guard: not("closeOnSelect"),
623
- actions: ["selectHighlightedItem"]
760
+ guard: and("isOpenControlled", "closeOnSelect"),
761
+ actions: ["selectHighlightedItem", "invokeOnClose"]
624
762
  },
625
763
  {
764
+ guard: "closeOnSelect",
626
765
  target: "focused",
627
766
  actions: ["selectHighlightedItem", "invokeOnClose"]
767
+ },
768
+ {
769
+ actions: ["selectHighlightedItem"]
628
770
  }
629
771
  ],
630
772
  "INPUT.CHANGE": [
631
773
  {
632
774
  guard: "autoComplete",
633
775
  target: "suggesting",
634
- actions: ["setInputValue"]
776
+ actions: ["setInputValue", "invokeOnOpen"]
635
777
  },
636
778
  {
637
779
  target: "suggesting",
638
- actions: ["clearHighlightedItem", "setInputValue"]
780
+ actions: ["clearHighlightedItem", "setInputValue", "invokeOnOpen"]
639
781
  }
640
782
  ],
641
- "ITEM.POINTER_OVER": {
783
+ "ITEM.POINTER_MOVE": {
642
784
  actions: ["setHighlightedItem"]
643
785
  },
644
786
  "ITEM.POINTER_LEAVE": {
@@ -646,49 +788,88 @@ function machine(userContext) {
646
788
  },
647
789
  "ITEM.CLICK": [
648
790
  {
649
- guard: not("closeOnSelect"),
650
- actions: ["selectItem"]
791
+ guard: and("isOpenControlled", "closeOnSelect"),
792
+ actions: ["selectItem", "invokeOnClose"]
651
793
  },
652
794
  {
795
+ guard: "closeOnSelect",
653
796
  target: "focused",
654
797
  actions: ["selectItem", "invokeOnClose"]
798
+ },
799
+ {
800
+ actions: ["selectItem"]
655
801
  }
656
802
  ],
657
803
  "LAYER.ESCAPE": [
804
+ {
805
+ guard: and("isOpenControlled", "autoComplete"),
806
+ actions: ["syncInputValue", "invokeOnClose"]
807
+ },
658
808
  {
659
809
  guard: "autoComplete",
660
810
  target: "focused",
661
811
  actions: ["syncInputValue", "invokeOnClose"]
662
812
  },
813
+ {
814
+ guard: "isOpenControlled",
815
+ actions: "invokeOnClose"
816
+ },
663
817
  {
664
818
  target: "focused",
665
819
  actions: ["invokeOnClose"]
666
820
  }
667
821
  ],
668
- "TRIGGER.CLICK": {
669
- target: "focused",
670
- actions: "invokeOnClose"
671
- },
822
+ "TRIGGER.CLICK": [
823
+ {
824
+ guard: "isOpenControlled",
825
+ actions: "invokeOnClose"
826
+ },
827
+ {
828
+ target: "focused",
829
+ actions: "invokeOnClose"
830
+ }
831
+ ],
672
832
  "LAYER.INTERACT_OUTSIDE": [
833
+ // == group 1 ==
673
834
  {
674
- guard: and("selectOnBlur", "hasHighlightedItem"),
675
- target: "idle",
676
- actions: ["selectHighlightedItem", "invokeOnClose"]
835
+ guard: and("isOpenControlled", "isCustomValue", not("allowCustomValue")),
836
+ actions: ["revertInputValue", "invokeOnClose"]
677
837
  },
678
838
  {
679
839
  guard: and("isCustomValue", not("allowCustomValue")),
680
840
  target: "idle",
681
841
  actions: ["revertInputValue", "invokeOnClose"]
682
842
  },
843
+ // == group 2 ==
844
+ {
845
+ guard: "isOpenControlled",
846
+ actions: "invokeOnClose"
847
+ },
683
848
  {
684
849
  target: "idle",
685
850
  actions: "invokeOnClose"
686
851
  }
687
852
  ],
688
- CLOSE: {
689
- target: "focused",
690
- actions: "invokeOnClose"
691
- }
853
+ CLOSE: [
854
+ {
855
+ guard: "isOpenControlled",
856
+ actions: "invokeOnClose"
857
+ },
858
+ {
859
+ target: "focused",
860
+ actions: "invokeOnClose"
861
+ }
862
+ ],
863
+ "VALUE.CLEAR": [
864
+ {
865
+ guard: "isOpenControlled",
866
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"]
867
+ },
868
+ {
869
+ target: "focused",
870
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"]
871
+ }
872
+ ]
692
873
  }
693
874
  },
694
875
  suggesting: {
@@ -698,12 +879,21 @@ function machine(userContext) {
698
879
  "scrollIntoView",
699
880
  "computePlacement",
700
881
  "trackChildNodes",
701
- "hideOtherElements"
882
+ "hideOtherElements",
883
+ "trackContentHeight"
702
884
  ],
703
- entry: ["focusInput", "invokeOnOpen"],
885
+ entry: ["focusInput"],
704
886
  on: {
887
+ "CONTROLLED.CLOSE": [
888
+ {
889
+ guard: "restoreFocus",
890
+ target: "focused"
891
+ },
892
+ {
893
+ target: "idle"
894
+ }
895
+ ],
705
896
  CHILDREN_CHANGE: {
706
- guard: not("isHighlightedItemVisible"),
707
897
  actions: ["highlightFirstItem"]
708
898
  },
709
899
  "INPUT.ARROW_DOWN": {
@@ -714,9 +904,6 @@ function machine(userContext) {
714
904
  target: "interacting",
715
905
  actions: "highlightPrevItem"
716
906
  },
717
- "INPUT.ARROW_UP+ALT": {
718
- target: "focused"
719
- },
720
907
  "INPUT.HOME": {
721
908
  target: "interacting",
722
909
  actions: ["highlightFirstItem"]
@@ -727,28 +914,38 @@ function machine(userContext) {
727
914
  },
728
915
  "INPUT.ENTER": [
729
916
  {
730
- guard: not("closeOnSelect"),
731
- actions: ["selectHighlightedItem"]
917
+ guard: and("isOpenControlled", "closeOnSelect"),
918
+ actions: ["selectHighlightedItem", "invokeOnClose"]
732
919
  },
733
920
  {
921
+ guard: "closeOnSelect",
734
922
  target: "focused",
735
923
  actions: ["selectHighlightedItem", "invokeOnClose"]
924
+ },
925
+ {
926
+ actions: ["selectHighlightedItem"]
736
927
  }
737
928
  ],
738
929
  "INPUT.CHANGE": [
739
930
  {
740
931
  guard: "autoHighlight",
741
- actions: ["setInputValue", "highlightFirstItem"]
932
+ actions: ["setInputValue"]
742
933
  },
743
934
  {
744
- actions: ["clearHighlightedItem", "setInputValue"]
935
+ actions: ["setInputValue"]
745
936
  }
746
937
  ],
747
- "LAYER.ESCAPE": {
748
- target: "focused",
749
- actions: "invokeOnClose"
750
- },
751
- "ITEM.POINTER_OVER": {
938
+ "LAYER.ESCAPE": [
939
+ {
940
+ guard: "isOpenControlled",
941
+ actions: "invokeOnClose"
942
+ },
943
+ {
944
+ target: "focused",
945
+ actions: "invokeOnClose"
946
+ }
947
+ ],
948
+ "ITEM.POINTER_MOVE": {
752
949
  target: "interacting",
753
950
  actions: "setHighlightedItem"
754
951
  },
@@ -756,41 +953,76 @@ function machine(userContext) {
756
953
  actions: "clearHighlightedItem"
757
954
  },
758
955
  "LAYER.INTERACT_OUTSIDE": [
956
+ // == group 1 ==
957
+ {
958
+ guard: and("isOpenControlled", "isCustomValue", not("allowCustomValue")),
959
+ actions: ["revertInputValue", "invokeOnClose"]
960
+ },
759
961
  {
760
962
  guard: and("isCustomValue", not("allowCustomValue")),
761
963
  target: "idle",
762
964
  actions: ["revertInputValue", "invokeOnClose"]
763
965
  },
966
+ // == group 2 ==
967
+ {
968
+ guard: "isOpenControlled",
969
+ actions: "invokeOnClose"
970
+ },
764
971
  {
765
972
  target: "idle",
766
973
  actions: "invokeOnClose"
767
974
  }
768
975
  ],
769
- "TRIGGER.CLICK": {
770
- target: "focused",
771
- actions: "invokeOnClose"
772
- },
976
+ "TRIGGER.CLICK": [
977
+ {
978
+ guard: "isOpenControlled",
979
+ actions: "invokeOnClose"
980
+ },
981
+ {
982
+ target: "focused",
983
+ actions: "invokeOnClose"
984
+ }
985
+ ],
773
986
  "ITEM.CLICK": [
774
987
  {
775
- guard: not("closeOnSelect"),
776
- actions: ["selectItem"]
988
+ guard: and("isOpenControlled", "closeOnSelect"),
989
+ actions: ["selectItem", "invokeOnClose"]
777
990
  },
778
991
  {
992
+ guard: "closeOnSelect",
779
993
  target: "focused",
780
994
  actions: ["selectItem", "invokeOnClose"]
995
+ },
996
+ {
997
+ actions: ["selectItem"]
781
998
  }
782
999
  ],
783
- CLOSE: {
784
- target: "focused",
785
- actions: "invokeOnClose"
786
- }
1000
+ CLOSE: [
1001
+ {
1002
+ guard: "isOpenControlled",
1003
+ actions: "invokeOnClose"
1004
+ },
1005
+ {
1006
+ target: "focused",
1007
+ actions: "invokeOnClose"
1008
+ }
1009
+ ],
1010
+ "VALUE.CLEAR": [
1011
+ {
1012
+ guard: "isOpenControlled",
1013
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"]
1014
+ },
1015
+ {
1016
+ target: "focused",
1017
+ actions: ["clearInputValue", "clearSelectedItems", "invokeOnClose"]
1018
+ }
1019
+ ]
787
1020
  }
788
1021
  }
789
1022
  }
790
1023
  },
791
1024
  {
792
1025
  guards: {
793
- openOnClick: (ctx2) => !!ctx2.openOnClick,
794
1026
  isInputValueEmpty: (ctx2) => ctx2.isInputValueEmpty,
795
1027
  autoComplete: (ctx2) => ctx2.autoComplete && !ctx2.multiple,
796
1028
  autoHighlight: (ctx2) => ctx2.autoHighlight,
@@ -799,22 +1031,24 @@ function machine(userContext) {
799
1031
  isCustomValue: (ctx2) => ctx2.inputValue !== ctx2.valueAsString,
800
1032
  allowCustomValue: (ctx2) => !!ctx2.allowCustomValue,
801
1033
  hasHighlightedItem: (ctx2) => ctx2.highlightedValue != null,
802
- hasSelectedItems: (ctx2) => ctx2.hasSelectedItems,
803
- selectOnBlur: (ctx2) => !!ctx2.selectOnBlur,
804
- closeOnSelect: (ctx2) => ctx2.multiple ? false : !!ctx2.closeOnSelect,
805
- isHighlightedItemVisible: (ctx2) => ctx2.collection.has(ctx2.highlightedValue)
1034
+ closeOnSelect: (ctx2) => !!ctx2.closeOnSelect,
1035
+ isOpenControlled: (ctx2) => !!ctx2["open.controlled"],
1036
+ openOnChange: (ctx2, evt) => {
1037
+ if (isBoolean(ctx2.openOnChange))
1038
+ return ctx2.openOnChange;
1039
+ return !!ctx2.openOnChange?.({ inputValue: evt.value });
1040
+ },
1041
+ restoreFocus: (_ctx, evt) => evt.restoreFocus == null ? true : !!evt.restoreFocus,
1042
+ isChangeEvent: (_ctx, evt) => evt.previousEvent?.type === "INPUT.CHANGE"
806
1043
  },
807
1044
  activities: {
808
1045
  trackDismissableLayer(ctx2, _evt, { send }) {
1046
+ if (!ctx2.dismissable)
1047
+ return;
809
1048
  const contentEl = () => dom.getContentEl(ctx2);
810
1049
  return trackDismissableElement(contentEl, {
811
1050
  defer: true,
812
- exclude: () => [
813
- dom.getInputEl(ctx2),
814
- dom.getContentEl(ctx2),
815
- dom.getTriggerEl(ctx2),
816
- dom.getClearTriggerEl(ctx2)
817
- ],
1051
+ exclude: () => [dom.getInputEl(ctx2), dom.getTriggerEl(ctx2), dom.getClearTriggerEl(ctx2)],
818
1052
  onFocusOutside: ctx2.onFocusOutside,
819
1053
  onPointerDownOutside: ctx2.onPointerDownOutside,
820
1054
  onInteractOutside: ctx2.onInteractOutside,
@@ -824,7 +1058,7 @@ function machine(userContext) {
824
1058
  send("LAYER.ESCAPE");
825
1059
  },
826
1060
  onDismiss() {
827
- send("LAYER.INTERACT_OUTSIDE");
1061
+ send({ type: "LAYER.INTERACT_OUTSIDE", restoreFocus: false });
828
1062
  }
829
1063
  });
830
1064
  },
@@ -848,22 +1082,56 @@ function machine(userContext) {
848
1082
  if (!ctx2.autoHighlight)
849
1083
  return;
850
1084
  const exec = () => send("CHILDREN_CHANGE");
851
- exec();
852
- return observeChildren(dom.getContentEl(ctx2), exec);
1085
+ raf2(() => exec());
1086
+ const contentEl = () => dom.getContentEl(ctx2);
1087
+ return observeChildren(contentEl, {
1088
+ callback: exec,
1089
+ defer: true
1090
+ });
853
1091
  },
854
1092
  scrollIntoView(ctx2, _evt, { getState }) {
855
1093
  const inputEl = dom.getInputEl(ctx2);
856
- const exec = () => {
1094
+ const exec = (immediate) => {
857
1095
  const state = getState();
858
- const isPointer = state.event.type.startsWith("ITEM.POINTER");
859
- if (isPointer || !ctx2.highlightedValue)
1096
+ const pointer = state.event.type.startsWith("ITEM.POINTER");
1097
+ if (pointer || !ctx2.highlightedValue)
860
1098
  return;
861
1099
  const optionEl = dom.getHighlightedItemEl(ctx2);
862
1100
  const contentEl = dom.getContentEl(ctx2);
1101
+ if (ctx2.scrollToIndexFn) {
1102
+ const highlightedIndex = ctx2.collection.indexOf(ctx2.highlightedValue);
1103
+ ctx2.scrollToIndexFn({ index: highlightedIndex, immediate });
1104
+ return;
1105
+ }
863
1106
  scrollIntoView(optionEl, { rootEl: contentEl, block: "nearest" });
864
1107
  };
865
- raf2(() => exec());
866
- return observeAttributes(inputEl, ["aria-activedescendant"], exec);
1108
+ raf2(() => exec(true));
1109
+ return observeAttributes(inputEl, {
1110
+ attributes: ["aria-activedescendant"],
1111
+ callback: () => exec(false)
1112
+ });
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?.();
867
1135
  }
868
1136
  },
869
1137
  actions: {
@@ -880,12 +1148,6 @@ function machine(userContext) {
880
1148
  }
881
1149
  });
882
1150
  },
883
- setIsComposing(ctx2) {
884
- ctx2.composing = true;
885
- },
886
- clearIsComposing(ctx2) {
887
- ctx2.composing = false;
888
- },
889
1151
  setHighlightedItem(ctx2, evt) {
890
1152
  set.highlightedItem(ctx2, evt.value);
891
1153
  },
@@ -903,18 +1165,28 @@ function machine(userContext) {
903
1165
  set.selectedItems(ctx2, value);
904
1166
  },
905
1167
  focusInput(ctx2) {
906
- if (dom.isInputFocused(ctx2))
907
- return;
908
- dom.getInputEl(ctx2)?.focus({ preventScroll: true });
1168
+ raf2(() => {
1169
+ if (dom.isInputFocused(ctx2))
1170
+ return;
1171
+ dom.getInputEl(ctx2)?.focus({ preventScroll: true });
1172
+ });
1173
+ },
1174
+ focusInputOrTrigger(ctx2) {
1175
+ queueMicrotask(() => {
1176
+ if (ctx2.popup === "dialog") {
1177
+ dom.getTriggerEl(ctx2)?.focus({ preventScroll: true });
1178
+ } else {
1179
+ dom.getInputEl(ctx2)?.focus({ preventScroll: true });
1180
+ }
1181
+ });
909
1182
  },
910
1183
  syncInputValue(ctx2, evt) {
911
- const isTyping = !KEYDOWN_EVENT_REGEX.test(evt.type);
912
1184
  const inputEl = dom.getInputEl(ctx2);
913
1185
  if (!inputEl)
914
1186
  return;
915
1187
  inputEl.value = ctx2.inputValue;
916
1188
  raf2(() => {
917
- if (isTyping)
1189
+ if (!evt.keypress)
918
1190
  return;
919
1191
  const { selectionStart, selectionEnd } = inputEl;
920
1192
  if (Math.abs((selectionEnd ?? 0) - (selectionStart ?? 0)) !== 0)
@@ -931,24 +1203,30 @@ function machine(userContext) {
931
1203
  set.inputValue(ctx2, "");
932
1204
  },
933
1205
  revertInputValue(ctx2) {
934
- set.inputValue(
935
- ctx2,
936
- match(ctx2.selectionBehavior, {
937
- replace: ctx2.hasSelectedItems ? ctx2.valueAsString : "",
938
- clear: "",
939
- preserve: ctx2.inputValue
940
- })
941
- );
1206
+ const inputValue = match(ctx2.selectionBehavior, {
1207
+ replace: ctx2.hasSelectedItems ? ctx2.valueAsString : "",
1208
+ preserve: ctx2.inputValue,
1209
+ clear: ""
1210
+ });
1211
+ set.inputValue(ctx2, inputValue);
942
1212
  },
943
- initialize(ctx2) {
944
- const items = ctx2.collection.items(ctx2.value);
945
- const valueAsString = ctx2.collection.itemsToString(items);
1213
+ syncInitialValues(ctx2) {
1214
+ const selectedItems = ctx2.collection.items(ctx2.value);
1215
+ const valueAsString = ctx2.collection.itemsToString(selectedItems);
1216
+ ctx2.highlightedItem = ctx2.collection.item(ctx2.highlightedValue);
1217
+ ctx2.selectedItems = selectedItems;
1218
+ ctx2.valueAsString = valueAsString;
946
1219
  ctx2.inputValue = match(ctx2.selectionBehavior, {
947
- preserve: valueAsString,
1220
+ preserve: ctx2.inputValue || valueAsString,
948
1221
  replace: valueAsString,
949
1222
  clear: ""
950
1223
  });
951
1224
  },
1225
+ syncSelectionBehavior(ctx2) {
1226
+ if (ctx2.multiple) {
1227
+ ctx2.selectionBehavior = "clear";
1228
+ }
1229
+ },
952
1230
  setSelectedItems(ctx2, evt) {
953
1231
  set.selectedItems(ctx2, evt.value);
954
1232
  },
@@ -956,10 +1234,14 @@ function machine(userContext) {
956
1234
  set.selectedItems(ctx2, []);
957
1235
  },
958
1236
  scrollContentToTop(ctx2) {
959
- const contentEl = dom.getContentEl(ctx2);
960
- if (!contentEl)
961
- return;
962
- contentEl.scrollTop = 0;
1237
+ if (ctx2.scrollToIndexFn) {
1238
+ ctx2.scrollToIndexFn({ index: 0, immediate: true });
1239
+ } else {
1240
+ const contentEl = dom.getContentEl(ctx2);
1241
+ if (!contentEl)
1242
+ return;
1243
+ contentEl.scrollTop = 0;
1244
+ }
963
1245
  },
964
1246
  invokeOnOpen(ctx2) {
965
1247
  ctx2.onOpenChange?.({ open: true });
@@ -968,28 +1250,70 @@ function machine(userContext) {
968
1250
  ctx2.onOpenChange?.({ open: false });
969
1251
  },
970
1252
  highlightFirstItem(ctx2) {
971
- const value = ctx2.collection.first();
972
- set.highlightedItem(ctx2, value);
1253
+ raf2(() => {
1254
+ const value = ctx2.collection.first();
1255
+ set.highlightedItem(ctx2, value);
1256
+ });
973
1257
  },
974
1258
  highlightLastItem(ctx2) {
975
- const value = ctx2.collection.last();
976
- set.highlightedItem(ctx2, value);
1259
+ raf2(() => {
1260
+ const value = ctx2.collection.last();
1261
+ set.highlightedItem(ctx2, value);
1262
+ });
977
1263
  },
978
1264
  highlightNextItem(ctx2) {
979
- const value = ctx2.collection.next(ctx2.highlightedValue) ?? (ctx2.loop ? ctx2.collection.first() : null);
1265
+ let value = null;
1266
+ if (ctx2.highlightedValue) {
1267
+ value = ctx2.collection.next(ctx2.highlightedValue);
1268
+ if (!value && ctx2.loopFocus)
1269
+ value = ctx2.collection.first();
1270
+ } else {
1271
+ value = ctx2.collection.first();
1272
+ }
980
1273
  set.highlightedItem(ctx2, value);
981
1274
  },
982
1275
  highlightPrevItem(ctx2) {
983
- const value = ctx2.collection.prev(ctx2.highlightedValue) ?? (ctx2.loop ? ctx2.collection.last() : null);
1276
+ let value = null;
1277
+ if (ctx2.highlightedValue) {
1278
+ value = ctx2.collection.prev(ctx2.highlightedValue);
1279
+ if (!value && ctx2.loopFocus)
1280
+ value = ctx2.collection.last();
1281
+ } else {
1282
+ value = ctx2.collection.last();
1283
+ }
984
1284
  set.highlightedItem(ctx2, value);
985
1285
  },
986
1286
  highlightFirstSelectedItem(ctx2) {
987
- const [value] = ctx2.collection.sort(ctx2.value);
988
- set.highlightedItem(ctx2, value);
1287
+ raf2(() => {
1288
+ const [value] = ctx2.collection.sort(ctx2.value);
1289
+ set.highlightedItem(ctx2, value);
1290
+ });
1291
+ },
1292
+ highlightFirstOrSelectedItem(ctx2) {
1293
+ raf2(() => {
1294
+ let value = null;
1295
+ if (ctx2.hasSelectedItems) {
1296
+ value = ctx2.collection.sort(ctx2.value)[0];
1297
+ } else {
1298
+ value = ctx2.collection.first();
1299
+ }
1300
+ set.highlightedItem(ctx2, value);
1301
+ });
1302
+ },
1303
+ highlightLastOrSelectedItem(ctx2) {
1304
+ raf2(() => {
1305
+ let value = null;
1306
+ if (ctx2.hasSelectedItems) {
1307
+ value = ctx2.collection.sort(ctx2.value)[0];
1308
+ } else {
1309
+ value = ctx2.collection.last();
1310
+ }
1311
+ set.highlightedItem(ctx2, value);
1312
+ });
989
1313
  },
990
1314
  autofillInputValue(ctx2, evt) {
991
1315
  const inputEl = dom.getInputEl(ctx2);
992
- if (!ctx2.autoComplete || !inputEl || !KEYDOWN_EVENT_REGEX.test(evt.type))
1316
+ if (!ctx2.autoComplete || !inputEl || !evt.keypress)
993
1317
  return;
994
1318
  const valueText = ctx2.collection.valueToString(ctx2.highlightedValue);
995
1319
  raf2(() => {
@@ -998,31 +1322,64 @@ function machine(userContext) {
998
1322
  },
999
1323
  setCollection(ctx2, evt) {
1000
1324
  ctx2.collection = evt.value;
1325
+ },
1326
+ 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
+ });
1334
+ },
1335
+ toggleVisibility(ctx2, evt, { send }) {
1336
+ send({ type: ctx2.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt });
1001
1337
  }
1002
1338
  }
1003
1339
  }
1004
1340
  );
1005
1341
  }
1006
1342
  var invoke = {
1007
- selectionChange: (ctx) => {
1343
+ valueChange: (ctx) => {
1008
1344
  ctx.onValueChange?.({
1009
1345
  value: Array.from(ctx.value),
1010
1346
  items: ctx.selectedItems
1011
1347
  });
1012
- ctx.inputValue = match(ctx.selectionBehavior, {
1013
- replace: ctx.valueAsString,
1014
- clear: "",
1015
- preserve: ctx.inputValue
1348
+ const prevSelectedItems = ctx.selectedItems;
1349
+ ctx.selectedItems = ctx.value.map((v) => {
1350
+ const foundItem = prevSelectedItems.find((item) => ctx.collection.itemToValue(item) === v);
1351
+ if (foundItem)
1352
+ return foundItem;
1353
+ return ctx.collection.item(v);
1016
1354
  });
1355
+ const valueAsString = ctx.collection.itemsToString(ctx.selectedItems);
1356
+ ctx.valueAsString = valueAsString;
1357
+ let nextInputValue;
1358
+ if (ctx.getSelectionValue) {
1359
+ nextInputValue = ctx.getSelectionValue({
1360
+ inputValue: ctx.inputValue,
1361
+ selectedItems: Array.from(ctx.selectedItems),
1362
+ valueAsString
1363
+ });
1364
+ } else {
1365
+ nextInputValue = match(ctx.selectionBehavior, {
1366
+ replace: ctx.valueAsString,
1367
+ preserve: ctx.inputValue,
1368
+ clear: ""
1369
+ });
1370
+ }
1371
+ ctx.inputValue = nextInputValue;
1372
+ invoke.inputChange(ctx);
1017
1373
  },
1018
1374
  highlightChange: (ctx) => {
1019
1375
  ctx.onHighlightChange?.({
1020
1376
  highlightedValue: ctx.highlightedValue,
1021
1377
  highligtedItem: ctx.highlightedItem
1022
1378
  });
1379
+ ctx.highlightedItem = ctx.collection.item(ctx.highlightedValue);
1023
1380
  },
1024
1381
  inputChange: (ctx) => {
1025
- ctx.onInputValueChange?.({ value: ctx.inputValue });
1382
+ ctx.onInputValueChange?.({ inputValue: ctx.inputValue });
1026
1383
  }
1027
1384
  };
1028
1385
  var set = {
@@ -1033,17 +1390,17 @@ var set = {
1033
1390
  return;
1034
1391
  if (value == null && force) {
1035
1392
  ctx.value = [];
1036
- invoke.selectionChange(ctx);
1393
+ invoke.valueChange(ctx);
1037
1394
  return;
1038
1395
  }
1039
1396
  ctx.value = ctx.multiple ? addOrRemove(ctx.value, value) : [value];
1040
- invoke.selectionChange(ctx);
1397
+ invoke.valueChange(ctx);
1041
1398
  },
1042
1399
  selectedItems: (ctx, value) => {
1043
1400
  if (isEqual(ctx.value, value))
1044
1401
  return;
1045
1402
  ctx.value = value;
1046
- invoke.selectionChange(ctx);
1403
+ invoke.valueChange(ctx);
1047
1404
  },
1048
1405
  highlightedItem: (ctx, value, force = false) => {
1049
1406
  if (isEqual(ctx.highlightedValue, value))