@wpmoo/ui 1.0.0-rc.1 → 1.0.0-rc.10

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.
Files changed (99) hide show
  1. package/ASSET_LICENSE.md +4 -2
  2. package/LICENSE +28 -0
  3. package/README.md +105 -100
  4. package/certification.json +25 -3
  5. package/dist/assets/css/moo-ui.css +14005 -12457
  6. package/dist/assets/css/moo-ui.min.css +10 -28
  7. package/dist/assets/css/moo.css +2606 -902
  8. package/dist/assets/css/moo.min.css +10 -29
  9. package/dist/js/chart.js +15312 -0
  10. package/dist/js/chart.min.js +27 -0
  11. package/dist/js/combobox.js +129 -85
  12. package/dist/js/context-menu.js +35 -66
  13. package/dist/js/datatable.js +390 -188
  14. package/dist/js/datepicker.js +1197 -0
  15. package/dist/js/datepicker.min.js +7 -0
  16. package/dist/js/moo-ui.js +3947 -0
  17. package/dist/js/moo-ui.min.js +7 -0
  18. package/dist/js/sheet.js +46 -0
  19. package/dist/js/sidebar.js +190 -172
  20. package/dist/js/slider.js +289 -0
  21. package/dist/js/state.js +76 -0
  22. package/dist/release-manifest.json +24 -0
  23. package/package.json +39 -9
  24. package/scss/_components.scss +70 -0
  25. package/scss/_config.scss +18 -0
  26. package/scss/_settings.scss +5 -0
  27. package/scss/components/_accordion.scss +54 -0
  28. package/scss/components/_alert.scss +82 -0
  29. package/scss/components/_avatar.scss +139 -0
  30. package/scss/components/_badge.scss +34 -0
  31. package/scss/components/_breadcrumb.scss +119 -0
  32. package/scss/components/_button.scss +200 -0
  33. package/scss/components/_button_group.scss +57 -0
  34. package/scss/components/_card.scss +114 -0
  35. package/scss/components/_checkbox.scss +78 -0
  36. package/scss/components/_close_button.scss +56 -0
  37. package/scss/components/_collapsible.scss +44 -0
  38. package/scss/components/_combobox.scss +253 -0
  39. package/scss/components/_context-menu.scss +18 -0
  40. package/scss/components/_datatable.scss +1067 -0
  41. package/scss/components/_datepicker.scss +237 -0
  42. package/scss/components/_dialog.scss +69 -0
  43. package/scss/components/_dropdown.scss +201 -0
  44. package/scss/components/_field.scss +94 -0
  45. package/scss/components/_input.scss +31 -0
  46. package/scss/components/_input_group.scss +151 -0
  47. package/scss/components/_kbd.scss +11 -0
  48. package/scss/components/_menubar.scss +17 -0
  49. package/scss/components/_navigation.scss +36 -0
  50. package/scss/components/_pagination.scss +58 -0
  51. package/scss/components/_popover.scss +26 -0
  52. package/scss/components/_progress.scss +5 -0
  53. package/scss/components/_radio_group.scss +108 -0
  54. package/scss/components/_search_trigger.scss +61 -0
  55. package/scss/components/_select.scss +8 -0
  56. package/scss/components/_separator.scss +10 -0
  57. package/scss/components/_sheet.scss +16 -0
  58. package/scss/components/_sidebar.scss +5 -0
  59. package/scss/components/_skeleton.scss +10 -0
  60. package/scss/components/_slider.scss +213 -0
  61. package/scss/components/_spinner.scss +11 -0
  62. package/scss/components/_switch.scss +30 -0
  63. package/scss/components/_table.scss +12 -0
  64. package/scss/components/_tabs.scss +116 -0
  65. package/scss/components/_textarea.scss +8 -0
  66. package/scss/components/_toast.scss +116 -0
  67. package/scss/components/_toggle_group.scss +17 -0
  68. package/scss/components/_tooltip.scss +64 -0
  69. package/scss/components/sidebar/_base.scss +86 -0
  70. package/scss/components/sidebar/_collapsed.scss +112 -0
  71. package/scss/components/sidebar/_identity.scss +71 -0
  72. package/scss/components/sidebar/_menus.scss +219 -0
  73. package/scss/foundations/_backdrop.scss +102 -0
  74. package/scss/foundations/_focus.scss +83 -0
  75. package/scss/foundations/_globals.scss +5 -0
  76. package/scss/foundations/_scope.scss +26 -0
  77. package/scss/layouts/_app.scss +75 -0
  78. package/scss/layouts/_page_grid.scss +27 -0
  79. package/scss/layouts/app/sidebar/_base.scss +84 -0
  80. package/scss/layouts/app/sidebar/_default.scss +20 -0
  81. package/scss/layouts/app/sidebar/_floating.scss +36 -0
  82. package/scss/layouts/app/sidebar/_inset.scss +32 -0
  83. package/scss/mixins/_banner.scss +9 -0
  84. package/scss/moo-core.scss +16 -0
  85. package/scss/moo-ui.scss +54 -0
  86. package/scss/settings/_bootstrap_overrides.scss +7 -0
  87. package/scss/settings/_component_variables.scss +244 -0
  88. package/scss/settings/_forms.scss +28 -0
  89. package/scss/settings/_options.scss +2 -0
  90. package/scss/settings/_palette.scss +90 -0
  91. package/scss/themes/_forms.scss +15 -0
  92. package/scss/themes/_root.scss +20 -0
  93. package/scss/themes/_standalone.scss +3 -0
  94. package/scss/themes/_theme.scss +244 -0
  95. package/scss/utilities/_background_color.scss +18 -0
  96. package/scss/utilities/_scroll_fade.scss +193 -0
  97. package/scss/utilities/_scroll_fade_primitives.scss +43 -0
  98. package/scss/_facade-settings.scss +0 -51
  99. package/scss/settings/_facade_public.scss +0 -23
@@ -1,15 +1,18 @@
1
- const instances = new WeakMap();
2
- const normalize = (value) => value.trim().toLowerCase();
1
+ /*!
2
+ * Moo UI combobox.js v1.0.0-rc.10 (https://wpmoo.org/)
3
+ * Copyright 2026 WPMoo Authors
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
3
6
 
4
- export default class Combobox {
7
+ const instances = /* @__PURE__ */ new WeakMap();
8
+ const normalize = (value) => value.trim().toLowerCase();
9
+ class Combobox {
5
10
  static getInstance(element) {
6
11
  return element?.nodeType === 1 ? instances.get(element) || null : null;
7
12
  }
8
-
9
13
  static getOrCreateInstance(element, config = {}) {
10
14
  return Combobox.getInstance(element) || new Combobox(element, config);
11
15
  }
12
-
13
16
  constructor(element, config = {}) {
14
17
  if (element?.nodeType !== 1 || !element.matches(".combobox")) {
15
18
  throw new TypeError("Combobox requires a .combobox root element.");
@@ -18,7 +21,6 @@ export default class Combobox {
18
21
  if (existing) {
19
22
  return existing;
20
23
  }
21
-
22
24
  this._element = element;
23
25
  this._document = element.ownerDocument;
24
26
  this._window = this._document.defaultView;
@@ -29,46 +31,41 @@ export default class Combobox {
29
31
  if (!this._input || !this._menu || this._options.length === 0) {
30
32
  throw new TypeError("Combobox requires an input, menu, and at least one option.");
31
33
  }
32
-
33
- this._isMultiple = element.dataset.mooComboboxMultiple === "true";
34
+ this._isMultiple = element.dataset.comboboxMultiple === "true";
34
35
  this._hidden = this._isMultiple ? null : element.querySelector('input[type="hidden"]');
35
36
  this._chipValue = element.querySelector(".combobox-value");
36
- this._valueStore = element.querySelector("[data-moo-combobox-value-store]");
37
- this._chipIconTemplate = element.querySelector("[data-moo-combobox-chip-icon]");
38
- this._clearTrigger = element.querySelector("[data-moo-combobox-clear]");
37
+ this._valueStore = element.querySelector("[data-combobox-value-store]");
38
+ this._chipIconTemplate = element.querySelector("[data-combobox-chip-icon]");
39
+ this._clearTrigger = element.querySelector("[data-combobox-clear]");
39
40
  this._indicator = element.querySelector(".combobox-indicator");
40
- this._groups = Array.from(element.querySelectorAll("[data-moo-combobox-group]"));
41
- this._separators = Array.from(element.querySelectorAll("[data-moo-combobox-separator]"));
41
+ this._groups = Array.from(element.querySelectorAll("[data-combobox-group]"));
42
+ this._separators = Array.from(element.querySelectorAll("[data-combobox-separator]"));
42
43
  this._listeners = [];
43
44
  this._generated = [];
44
45
  this._startsOpen = this._menu.classList.contains("show");
45
-
46
- this._empty = this._menu.querySelector("[data-moo-combobox-empty]");
46
+ this._empty = this._menu.querySelector("[data-combobox-empty]");
47
47
  if (!this._empty) {
48
48
  this._empty = this._document.createElement("li");
49
49
  this._empty.className = "combobox-empty";
50
50
  this._empty.hidden = true;
51
- this._empty.dataset.mooComboboxEmpty = "true";
51
+ this._empty.dataset.comboboxEmpty = "true";
52
52
  this._empty.textContent = this._config.noResultsText;
53
53
  this._menu.appendChild(this._empty);
54
54
  this._generated.push(this._empty);
55
55
  }
56
-
57
- this._liveRegion = element.querySelector("[data-moo-combobox-live]");
56
+ this._liveRegion = element.querySelector("[data-combobox-live]");
58
57
  if (!this._liveRegion) {
59
58
  this._liveRegion = this._document.createElement("span");
60
59
  this._liveRegion.className = "visually-hidden";
61
60
  this._liveRegion.setAttribute("aria-live", "polite");
62
- this._liveRegion.dataset.mooComboboxLive = "true";
61
+ this._liveRegion.dataset.comboboxLive = "true";
63
62
  element.appendChild(this._liveRegion);
64
63
  this._generated.push(this._liveRegion);
65
64
  }
66
-
67
65
  instances.set(element, this);
68
66
  this._bindEvents();
69
67
  this._toggleClear(
70
- this._input.dataset.mooComboboxSelected === "true" ||
71
- element.dataset.mooComboboxSelected === "true"
68
+ this._input.dataset.comboboxSelected === "true" || element.dataset.comboboxSelected === "true"
72
69
  );
73
70
  this._syncMultipleValue();
74
71
  this._filterOptions({ open: this._startsOpen, activate: this._startsOpen });
@@ -76,7 +73,6 @@ export default class Combobox {
76
73
  this._closeMenu(false);
77
74
  }
78
75
  }
79
-
80
76
  dispose() {
81
77
  this._listeners.forEach(({ target, type, handler, options }) => {
82
78
  target.removeEventListener(type, handler, options);
@@ -85,38 +81,33 @@ export default class Combobox {
85
81
  this._closeMenu(false);
86
82
  this._generated.forEach((element) => element.remove());
87
83
  this._generated = [];
88
- this._chipValue?.querySelectorAll("[data-moo-combobox-generated]").forEach((item) => item.remove());
89
- this._valueStore?.querySelectorAll("[data-moo-combobox-generated]").forEach((item) => item.remove());
84
+ this._chipValue?.querySelectorAll("[data-combobox-generated]").forEach((item) => item.remove());
85
+ this._valueStore?.querySelectorAll("[data-combobox-generated]").forEach((item) => item.remove());
90
86
  instances.delete(this._element);
91
87
  }
92
-
93
88
  _listen(target, type, handler, options) {
94
89
  target?.addEventListener(type, handler, options);
95
90
  if (target) {
96
91
  this._listeners.push({ target, type, handler, options });
97
92
  }
98
93
  }
99
-
100
94
  _trigger(name, detail = {}) {
101
95
  return this._element.dispatchEvent(
102
96
  new this._window.CustomEvent(`${name}.moo.combobox`, {
103
97
  bubbles: true,
104
98
  cancelable: name === "show" || name === "hide",
105
- detail,
99
+ detail
106
100
  })
107
101
  );
108
102
  }
109
-
110
103
  _optionGroupIsHidden(option) {
111
- return option.closest("[data-moo-combobox-group]")?.hidden || false;
104
+ return option.closest("[data-combobox-group]")?.hidden || false;
112
105
  }
113
-
114
106
  _visibleOptions() {
115
107
  return this._options.filter(
116
108
  (option) => !option.hidden && !option.disabled && !this._optionGroupIsHidden(option)
117
109
  );
118
110
  }
119
-
120
111
  _syncGroupVisibility() {
121
112
  if (this._groups.length === 0) {
122
113
  return;
@@ -133,7 +124,6 @@ export default class Combobox {
133
124
  separator.hidden = visibleGroupCount < 2;
134
125
  });
135
126
  }
136
-
137
127
  _openMenu(emit = true) {
138
128
  if (this._menu.classList.contains("show")) {
139
129
  this._input.setAttribute("aria-expanded", "true");
@@ -148,7 +138,6 @@ export default class Combobox {
148
138
  this._trigger("shown");
149
139
  }
150
140
  }
151
-
152
141
  _closeMenu(emit = true) {
153
142
  const wasOpen = this._menu.classList.contains("show");
154
143
  if (wasOpen && emit && !this._trigger("hide")) {
@@ -157,24 +146,24 @@ export default class Combobox {
157
146
  this._menu.classList.remove("show");
158
147
  this._input.setAttribute("aria-expanded", "false");
159
148
  this._input.removeAttribute("aria-activedescendant");
160
- this._options.forEach((option) => option.removeAttribute("aria-current"));
149
+ this._options.forEach((option) => {
150
+ option.removeAttribute("aria-current");
151
+ option.classList.remove("active");
152
+ });
161
153
  if (wasOpen && emit) {
162
154
  this._trigger("hidden");
163
155
  }
164
156
  }
165
-
166
157
  _optionLabel(option) {
167
158
  return option?.querySelector(".combobox-option__label")?.textContent?.trim() || "";
168
159
  }
169
-
170
160
  _selectedOptions() {
171
161
  return this._options.filter((option) => option.getAttribute("aria-selected") === "true");
172
162
  }
173
-
174
163
  _toggleClear(selected) {
175
164
  const value = selected ? "true" : "false";
176
- this._element.dataset.mooComboboxSelected = value;
177
- this._input.dataset.mooComboboxSelected = value;
165
+ this._element.dataset.comboboxSelected = value;
166
+ this._input.dataset.comboboxSelected = value;
178
167
  if (this._clearTrigger) {
179
168
  this._clearTrigger.hidden = !selected;
180
169
  if (this._indicator) {
@@ -182,14 +171,13 @@ export default class Combobox {
182
171
  }
183
172
  }
184
173
  }
185
-
186
174
  _createChip(option) {
187
175
  const value = option.dataset.value || "";
188
176
  const label = this._optionLabel(option);
189
177
  const chip = this._document.createElement("span");
190
178
  chip.className = "badge text-bg-secondary combobox-chip";
191
179
  chip.dataset.value = value;
192
- chip.dataset.mooComboboxGenerated = "true";
180
+ chip.dataset.comboboxGenerated = "true";
193
181
  const labelElement = this._document.createElement("span");
194
182
  labelElement.className = "combobox-chip__label";
195
183
  labelElement.textContent = label;
@@ -198,13 +186,12 @@ export default class Combobox {
198
186
  remove.className = "combobox-chip__remove";
199
187
  remove.type = "button";
200
188
  remove.dataset.value = value;
201
- remove.dataset.mooComboboxChipRemove = "true";
189
+ remove.dataset.comboboxChipRemove = "true";
202
190
  remove.setAttribute("aria-label", `Remove ${label}`);
203
191
  remove.append(this._chipIconTemplate?.content.cloneNode(true) || "x");
204
192
  chip.appendChild(remove);
205
193
  return chip;
206
194
  }
207
-
208
195
  _syncMultipleValue() {
209
196
  if (!this._isMultiple) {
210
197
  return;
@@ -215,26 +202,24 @@ export default class Combobox {
215
202
  }
216
203
  if (this._valueStore) {
217
204
  this._valueStore.replaceChildren();
218
- const name = this._valueStore.dataset.mooComboboxName || "";
205
+ const name = this._valueStore.dataset.comboboxName || "";
219
206
  selected.forEach((option) => {
220
207
  const field = this._document.createElement("input");
221
208
  field.type = "hidden";
222
209
  field.name = name;
223
210
  field.value = option.dataset.value || "";
224
- field.dataset.mooComboboxGenerated = "true";
211
+ field.dataset.comboboxGenerated = "true";
225
212
  this._valueStore.appendChild(field);
226
213
  });
227
214
  }
228
215
  this._toggleClear(selected.length > 0);
229
216
  }
230
-
231
217
  _emitChange() {
232
218
  const selected = this._selectedOptions();
233
219
  this._trigger("change", {
234
- values: selected.map((option) => option.dataset.value || ""),
220
+ values: selected.map((option) => option.dataset.value || "")
235
221
  });
236
222
  }
237
-
238
223
  _removeChip(value) {
239
224
  const option = this._options.find((candidate) => candidate.dataset.value === value);
240
225
  if (!option) {
@@ -244,7 +229,6 @@ export default class Combobox {
244
229
  this._syncMultipleValue();
245
230
  this._emitChange();
246
231
  }
247
-
248
232
  _clearSelection(emit = true) {
249
233
  if (this._hidden) {
250
234
  this._hidden.value = "";
@@ -259,7 +243,6 @@ export default class Combobox {
259
243
  this._emitChange();
260
244
  }
261
245
  }
262
-
263
246
  _clearStaleSelection() {
264
247
  if (this._isMultiple) {
265
248
  this._input.value = "";
@@ -272,29 +255,75 @@ export default class Combobox {
272
255
  }
273
256
  const selected = this._selectedOptions()[0] || null;
274
257
  const label = this._optionLabel(selected);
275
- if (!this._input.value || (label && normalize(this._input.value) === normalize(label))) {
258
+ if (!this._input.value || label && normalize(this._input.value) === normalize(label)) {
276
259
  return;
277
260
  }
278
261
  this._clearSelection();
279
262
  this._input.value = "";
280
263
  }
281
-
282
- _setActiveOption(option) {
283
- this._options.forEach((candidate) => candidate.toggleAttribute("aria-current", candidate === option));
264
+ _activeOption() {
265
+ const activeId = this._input.getAttribute("aria-activedescendant");
266
+ if (!activeId) {
267
+ return null;
268
+ }
269
+ return this._visibleOptions().find((option) => option.id === activeId) || null;
270
+ }
271
+ _selectedOption() {
272
+ if (this._isMultiple) {
273
+ return null;
274
+ }
275
+ return this._visibleOptions().find(
276
+ (option) => option.getAttribute("aria-selected") === "true"
277
+ ) || null;
278
+ }
279
+ _preferredActiveOption() {
280
+ return this._selectedOption() || this._visibleOptions()[0] || null;
281
+ }
282
+ _activatePreferredOptionIfNeeded() {
283
+ if (!this._activeOption()) {
284
+ this._setActiveOption(this._preferredActiveOption());
285
+ }
286
+ }
287
+ _setActiveOption(option, { scroll = true } = {}) {
288
+ this._options.forEach((candidate) => {
289
+ const active = candidate === option;
290
+ if (active) {
291
+ candidate.setAttribute("aria-current", "true");
292
+ } else {
293
+ candidate.removeAttribute("aria-current");
294
+ }
295
+ candidate.classList.toggle("active", active);
296
+ });
284
297
  if (!option) {
285
298
  this._input.removeAttribute("aria-activedescendant");
286
299
  return;
287
300
  }
288
301
  this._input.setAttribute("aria-activedescendant", option.id);
289
- const optionRect = option.getBoundingClientRect();
290
- const menuRect = this._menu.getBoundingClientRect();
291
- if (optionRect.top < menuRect.top) {
292
- this._menu.scrollTop -= menuRect.top - optionRect.top;
293
- } else if (optionRect.bottom > menuRect.bottom) {
294
- this._menu.scrollTop += optionRect.bottom - menuRect.bottom;
302
+ if (scroll) {
303
+ const optionRect = option.getBoundingClientRect();
304
+ const menuRect = this._menu.getBoundingClientRect();
305
+ if (optionRect.top < menuRect.top) {
306
+ this._menu.scrollTop -= menuRect.top - optionRect.top;
307
+ } else if (optionRect.bottom > menuRect.bottom) {
308
+ this._menu.scrollTop += optionRect.bottom - menuRect.bottom;
309
+ }
310
+ }
311
+ }
312
+ _activateOptionFromPointer(option, event) {
313
+ if (event.pointerType === "touch" || option.disabled || option.getAttribute("aria-disabled") === "true" || option === this._activeOption()) {
314
+ return;
315
+ }
316
+ this._setActiveOption(option, { scroll: false });
317
+ }
318
+ _restoreSelectedActiveOption() {
319
+ if (!this._menu.classList.contains("show")) {
320
+ return;
321
+ }
322
+ const selected = this._selectedOption();
323
+ if (selected) {
324
+ this._setActiveOption(selected, { scroll: false });
295
325
  }
296
326
  }
297
-
298
327
  _chooseOption(option) {
299
328
  if (!option || option.disabled) {
300
329
  return;
@@ -322,8 +351,7 @@ export default class Combobox {
322
351
  this._setActiveOption(option);
323
352
  this._emitChange();
324
353
  }
325
-
326
- _filterOptions({ open = true, activate = true } = {}) {
354
+ _filterOptions({ open = true, activate = true, resetActive = false } = {}) {
327
355
  if (open) {
328
356
  this._openMenu();
329
357
  }
@@ -337,29 +365,44 @@ export default class Combobox {
337
365
  this._syncGroupVisibility();
338
366
  this._empty.hidden = count !== 0;
339
367
  this._liveRegion.textContent = count === 0 ? "No results" : `${count} result${count === 1 ? "" : "s"}`;
368
+ if (resetActive) {
369
+ this._setActiveOption(null, { scroll: false });
370
+ }
340
371
  if (activate) {
341
- this._setActiveOption(this._visibleOptions()[0] || null);
372
+ this._activatePreferredOptionIfNeeded();
342
373
  }
343
374
  }
344
-
345
375
  _bindEvents() {
346
376
  this._listen(this._input, "focus", () => {
347
377
  this._openMenu();
348
- if (!this._isMultiple && this._input.dataset.mooComboboxSelected === "true") {
378
+ if (!this._isMultiple && this._input.dataset.comboboxSelected === "true") {
349
379
  this._input.select();
350
380
  }
351
- this._setActiveOption(this._visibleOptions()[0] || null);
381
+ this._activatePreferredOptionIfNeeded();
352
382
  });
353
383
  this._listen(this._input, "input", () => {
354
384
  if (!this._isMultiple) {
355
385
  this._clearSelection();
356
386
  }
357
- this._filterOptions();
387
+ this._filterOptions({ resetActive: true });
388
+ });
389
+ this._listen(this._input, "blur", (event) => {
390
+ const nextTarget = event.relatedTarget;
391
+ if (nextTarget instanceof this._window.Node && this._element.contains(nextTarget)) {
392
+ return;
393
+ }
394
+ this._clearStaleSelection();
395
+ });
396
+ this._listen(this._input, "click", () => {
397
+ this._openMenu();
398
+ this._setActiveOption(this._preferredActiveOption());
358
399
  });
359
- this._listen(this._input, "blur", () => this._clearStaleSelection());
360
- this._listen(this._input, "click", () => this._openMenu());
361
400
  this._listen(this._input, "keydown", (event) => this._handleKeydown(event));
362
401
  this._options.forEach((option) => {
402
+ this._listen(option, "keydown", (event) => this._handleKeydown(event));
403
+ this._listen(option, "pointerenter", (event) => {
404
+ this._activateOptionFromPointer(option, event);
405
+ });
363
406
  this._listen(option, "click", () => {
364
407
  this._chooseOption(option);
365
408
  if (this._isMultiple) {
@@ -374,8 +417,9 @@ export default class Combobox {
374
417
  this._clearSelection();
375
418
  this._input.value = "";
376
419
  this._input.focus();
377
- this._filterOptions();
420
+ this._filterOptions({ resetActive: true });
378
421
  });
422
+ this._listen(this._menu, "pointerleave", () => this._restoreSelectedActiveOption());
379
423
  this._listen(this._element, "click", (event) => this._handleRootClick(event));
380
424
  this._listen(this._document, "click", (event) => {
381
425
  if (event.target instanceof this._window.Node && !this._element.contains(event.target)) {
@@ -383,30 +427,24 @@ export default class Combobox {
383
427
  }
384
428
  });
385
429
  }
386
-
387
430
  _handleRootClick(event) {
388
431
  const target = event.target;
389
- const trigger = target instanceof this._window.Element
390
- ? target.closest("[data-moo-combobox-chip-remove]")
391
- : null;
432
+ const trigger = target instanceof this._window.Element ? target.closest("[data-combobox-chip-remove]") : null;
392
433
  if (trigger) {
393
434
  event.preventDefault();
394
435
  this._removeChip(trigger.dataset.value || "");
395
436
  this._input.focus();
396
- } else if (
397
- this._isMultiple &&
398
- target instanceof this._window.Element &&
399
- target.closest(".combobox-chips")
400
- ) {
437
+ } else if (this._isMultiple && target instanceof this._window.Element && target.closest(".combobox-chips")) {
401
438
  this._input.focus();
402
439
  }
403
440
  }
404
-
405
441
  _handleKeydown(event) {
406
442
  const available = this._visibleOptions();
407
443
  const current = available.findIndex(
408
444
  (option) => option.id === this._input.getAttribute("aria-activedescendant")
409
445
  );
446
+ const isNextKey = event.key === "ArrowDown" || event.key === "Down";
447
+ const isPreviousKey = event.key === "ArrowUp" || event.key === "Up";
410
448
  if (this._isMultiple && event.key === "Backspace" && this._input.value === "") {
411
449
  const selected = this._selectedOptions();
412
450
  const last = selected[selected.length - 1];
@@ -414,12 +452,15 @@ export default class Combobox {
414
452
  event.preventDefault();
415
453
  this._removeChip(last.dataset.value || "");
416
454
  }
417
- } else if ((event.key === "ArrowDown" || event.key === "ArrowUp") && available.length) {
455
+ } else if ((isNextKey || isPreviousKey) && available.length) {
418
456
  event.preventDefault();
419
457
  this._openMenu();
420
- const offset = event.key === "ArrowDown" ? 1 : -1;
458
+ const offset = isNextKey ? 1 : -1;
421
459
  const next = current === -1 ? 0 : (current + offset + available.length) % available.length;
422
460
  this._setActiveOption(available[next]);
461
+ if (event.target !== this._input) {
462
+ this._input.focus({ preventScroll: true });
463
+ }
423
464
  } else if (event.key === "Enter") {
424
465
  const option = available[current];
425
466
  if (option) {
@@ -431,9 +472,12 @@ export default class Combobox {
431
472
  }
432
473
  } else if (event.key === "Escape") {
433
474
  this._closeMenu();
434
- this._input.blur();
475
+ this._input.focus();
435
476
  } else if (event.key === "Tab") {
436
477
  this._closeMenu();
437
478
  }
438
479
  }
439
480
  }
481
+ export {
482
+ Combobox as default
483
+ };