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