@oscarpalmer/oui 0.29.1 → 0.30.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.
Files changed (45) hide show
  1. package/dist/css/combobox.css +26 -0
  2. package/dist/js/combobox.d.mts +27 -0
  3. package/dist/js/combobox.mjs +474 -0
  4. package/dist/js/dialog.d.mts +4 -3
  5. package/dist/js/dialog.mjs +1 -1
  6. package/dist/js/{draggable-fIYXd8eO.d.mts → draggable-a6w0J1OD.d.mts} +1 -2
  7. package/dist/js/floatable/floatable.embedded.d.mts +1 -1
  8. package/dist/js/floatable/floatable.embedded.mjs +2 -2
  9. package/dist/js/floatable/floatable.standalone.d.mts +1 -1
  10. package/dist/js/{floatable.embedded-DUR8bQBK.d.mts → floatable.embedded-Bct4U5Dm.d.mts} +0 -1
  11. package/dist/js/focus-trap/focus-trap.embedded.mjs +13 -8
  12. package/dist/js/index.d.mts +9 -4
  13. package/dist/js/index.mjs +2 -1
  14. package/dist/js/internal/draggable.d.mts +1 -1
  15. package/dist/js/internal/draggable.mjs +24 -15
  16. package/dist/js/internal/navigable.d.mts +0 -1
  17. package/dist/js/internal/navigable.mjs +4 -3
  18. package/dist/js/menu.mjs +1 -1
  19. package/dist/js/movable/movable.embedded.d.mts +1 -1
  20. package/dist/js/movable/movable.embedded.mjs +3 -4
  21. package/dist/js/movable/movable.standalone.d.mts +1 -1
  22. package/dist/js/popover.d.mts +1 -2
  23. package/dist/js/popover.mjs +1 -1
  24. package/dist/js/resizable/resizable.embedded.d.mts +1 -1
  25. package/dist/js/resizable/resizable.embedded.mjs +47 -19
  26. package/dist/js/resizable/resizable.standalone.d.mts +1 -1
  27. package/dist/js/{resizable.embedded-CXrUVPzR.d.mts → resizable.embedded-3qoR0Bjm.d.mts} +0 -1
  28. package/dist/js/sortable/sortable.embedded.d.mts +1 -1
  29. package/dist/js/sortable/sortable.embedded.mjs +11 -15
  30. package/dist/js/sortable/sortable.standalone.d.mts +1 -1
  31. package/dist/js/splitter.d.mts +2 -2
  32. package/dist/js/splitter.mjs +11 -11
  33. package/dist/js/tooltip.d.mts +1 -2
  34. package/dist/js/tooltip.mjs +3 -3
  35. package/package.json +6 -2
  36. package/src/css/combobox.scss +26 -0
  37. package/src/js/combobox.ts +917 -0
  38. package/src/js/focus-trap/focus-trap.embedded.ts +22 -10
  39. package/src/js/index.ts +1 -0
  40. package/src/js/internal/draggable.ts +37 -29
  41. package/src/js/internal/navigable.ts +5 -1
  42. package/src/js/movable/movable.embedded.ts +1 -3
  43. package/src/js/resizable/resizable.embedded.ts +68 -18
  44. package/src/js/sortable/sortable.embedded.ts +7 -15
  45. package/src/js/splitter.ts +37 -23
@@ -0,0 +1,26 @@
1
+ /*! https://github.com/oscarpalmer/oui */
2
+
3
+ :where([oui-combobox-content]) {
4
+ box-shadow: inset 0 1px 0 0 transparent, 0 0 0 1px rgba(0, 0, 0, 0.05), 0 1px 2px -0.5px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.05), 0 4px 8px -2px rgba(0, 0, 0, 0.05), 0 8px 16px -4px rgba(0, 0, 0, 0.05), 0 16px 32px -8px rgba(0, 0, 0, 0.05), 0 32px 64px -16px rgba(0, 0, 0, 0.05), 0 64px 128px -32px rgba(0, 0, 0, 0.05);
5
+ width: -moz-fit-content;
6
+ width: fit-content;
7
+ height: -moz-fit-content;
8
+ height: fit-content;
9
+ max-width: max(25vw, 32rem);
10
+ max-height: max(25vh, 32rem);
11
+ min-width: var(--oui-combobox-width, auto);
12
+ flex-flow: column nowrap;
13
+ }
14
+ :where([oui-combobox-content]):popover-open {
15
+ display: flex !important;
16
+ }
17
+
18
+ :where([oui-combobox-list]) {
19
+ overflow: auto;
20
+ }
21
+
22
+ :where([oui-combobox-option]) {
23
+ cursor: pointer;
24
+ }
25
+
26
+
@@ -0,0 +1,27 @@
1
+ //#region src/js/combobox.d.ts
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ [TAGNAME]: OuiComboboxElement;
5
+ }
6
+ }
7
+ declare class OuiComboboxElement extends HTMLElement {
8
+ #private;
9
+ static formAssociated: boolean;
10
+ static observedAttributes: string[];
11
+ get disabled(): boolean;
12
+ set disabled(value: boolean);
13
+ get multiple(): boolean;
14
+ get name(): string;
15
+ set name(value: string);
16
+ get required(): boolean;
17
+ set required(value: boolean);
18
+ get type(): OuiComboboxType;
19
+ get value(): string;
20
+ set value(value: unknown);
21
+ constructor();
22
+ attributeChangedCallback(name: string, _: string | null, value: string | null): void;
23
+ }
24
+ type OuiComboboxType = 'autocomplete' | 'select';
25
+ declare const TAGNAME = "oui-combobox";
26
+ //#endregion
27
+ export { OuiComboboxElement };
@@ -0,0 +1,474 @@
1
+ import { createEmbeddedFloatable } from "./floatable/floatable.embedded.mjs";
2
+ import { clamp } from "@oscarpalmer/atoms/number";
3
+ import { getString } from "@oscarpalmer/atoms/string";
4
+ import { getAria, setAria } from "@oscarpalmer/toretto/aria";
5
+ import { getAttribute, setAttribute, setAttributes } from "@oscarpalmer/toretto/attribute";
6
+ import { createElement } from "@oscarpalmer/toretto/create";
7
+ import { dispatch, on } from "@oscarpalmer/toretto/event";
8
+ import { findAncestor } from "@oscarpalmer/toretto/find";
9
+ import { setProperty } from "@oscarpalmer/toretto/property";
10
+ import { setStyle } from "@oscarpalmer/toretto/style";
11
+ //#region src/js/combobox.ts
12
+ var OuiComboboxElement = class extends HTMLElement {
13
+ static formAssociated = true;
14
+ static observedAttributes = [
15
+ "disabled",
16
+ "required",
17
+ "value"
18
+ ];
19
+ #internals;
20
+ #state;
21
+ get disabled() {
22
+ return this.#state?.disabled ?? false;
23
+ }
24
+ set disabled(value) {
25
+ if (typeof value === "boolean" && value !== this.#state.disabled) setDisabled(this, this.#state, value);
26
+ }
27
+ get multiple() {
28
+ return this.hasAttribute("multiple");
29
+ }
30
+ get name() {
31
+ return this.getAttribute("name") ?? "";
32
+ }
33
+ set name(value) {
34
+ if (typeof value === "string") this.setAttribute("name", value);
35
+ }
36
+ get required() {
37
+ return this.hasAttribute("required");
38
+ }
39
+ set required(value) {
40
+ if (typeof value === "boolean" && value !== this.#state.required) setRequired(this, this.#state, value);
41
+ }
42
+ get type() {
43
+ return this.#state.type;
44
+ }
45
+ get value() {
46
+ return this.#state.value;
47
+ }
48
+ set value(value) {
49
+ const [option, all] = getOption(this.#state, value);
50
+ if (option != null) setNext(this, this.#state, all, "set", option);
51
+ }
52
+ constructor() {
53
+ super();
54
+ this.#internals = this.attachInternals();
55
+ this.#state = getState(this, this.#internals);
56
+ comboboxes.set(this.#state.content, this);
57
+ states.set(this, this.#state);
58
+ states.set(this.#state.content, this.#state);
59
+ }
60
+ attributeChangedCallback(name, _, value) {
61
+ switch (name) {
62
+ case "disabled":
63
+ this.disabled = this.hasAttribute("disabled");
64
+ break;
65
+ case "required":
66
+ this.required = this.hasAttribute("required");
67
+ break;
68
+ case "value":
69
+ this.value = value;
70
+ break;
71
+ }
72
+ }
73
+ };
74
+ function filterOptions(state, options, clear) {
75
+ if (clear || state.autocomplete.none) state.filter = "";
76
+ const filter = state.filter.toLocaleLowerCase();
77
+ const all = options ?? getOptions(state)[0];
78
+ const { length } = all;
79
+ const visible = [];
80
+ for (let index = 0; index < length; index += 1) {
81
+ const option = all[index];
82
+ option.hidden = option.textContent?.toLocaleLowerCase().includes(filter) === false;
83
+ if (!option.hidden) visible.push(option);
84
+ }
85
+ if (visible.length > 0) return state.active != null && visible.includes(state.active) ? state.active : visible[0];
86
+ }
87
+ function getAnchor(type, id, element, label, select, autocomplete) {
88
+ const anchor = type === "autocomplete" ? getInput(id, element, label, select) : getBox(id, element, label, select);
89
+ setAria(anchor, "autocomplete", autocomplete.value);
90
+ setAttributes(anchor, {
91
+ [ATTRIBUTE_ANCHOR]: "",
92
+ "aria-controls": `${id}_content`,
93
+ "aria-expanded": false,
94
+ "aria-haspopup": "listbox",
95
+ "aria-labelledby": label.id,
96
+ role: "combobox"
97
+ });
98
+ return anchor;
99
+ }
100
+ function getAutoComplete(element, select) {
101
+ let value;
102
+ if (select) value = "list";
103
+ else {
104
+ const attribute = getAttribute(element, "autocomplete");
105
+ value = AUTOCOMPLETES.has(attribute) ? attribute : "none";
106
+ }
107
+ return {
108
+ value,
109
+ both: value === "both",
110
+ inline: value === "inline",
111
+ list: value === "list",
112
+ none: value === "none"
113
+ };
114
+ }
115
+ function getBox(id, element, label, select) {
116
+ return createElement("div", {
117
+ id,
118
+ innerHTML: select.selectedOptions[0]?.textContent ?? "",
119
+ tabIndex: 0
120
+ });
121
+ }
122
+ function getContent(type, id, element, anchor, label, select, autocomplete) {
123
+ const content = createElement("div", { id: `${id}_content` }, {
124
+ [ATTRIBUTE_CONTENT]: "",
125
+ popover: "",
126
+ role: "listbox",
127
+ tabindex: -1,
128
+ "aria-labelledby": label.id
129
+ });
130
+ const list = createElement("ul", {}, { [ATTRIBUTE_LIST]: "" });
131
+ const options = [...select.options];
132
+ const selected = [];
133
+ for (let index = 0; index < options.length; index += 1) {
134
+ const option = options[index];
135
+ const item = createElement("li", {
136
+ id: `${id}_option_${index}`,
137
+ textContent: option.textContent
138
+ }, {
139
+ [ATTRIBUTE_OPTION]: "",
140
+ [ATTRIBUTE_OPTION_CURRENT]: index === 0 ? "" : void 0,
141
+ role: "option",
142
+ value: option.value,
143
+ "aria-selected": !autocomplete.none && option.selected
144
+ });
145
+ if (option.selected) selected.push(item);
146
+ list.append(item);
147
+ }
148
+ content.append(list);
149
+ return [content, selected];
150
+ }
151
+ function getId() {
152
+ index += 1;
153
+ return `oui_combobox_${index}`;
154
+ }
155
+ function getInput(id, element, label, select) {
156
+ return createElement("input", {
157
+ id,
158
+ value: select.selectedOptions[0]?.textContent ?? ""
159
+ }, {
160
+ [ATTRIBUTE_ANCHOR]: "",
161
+ autocomplete: "off",
162
+ type: "text"
163
+ });
164
+ }
165
+ function getOption(state, value) {
166
+ const [all] = getOptions(state);
167
+ const valueAsString = getString(value);
168
+ filterOptions(state, all, true);
169
+ let match;
170
+ for (const option of all) if (option.getAttribute("value")?.toLocaleLowerCase() === valueAsString) {
171
+ match = option;
172
+ break;
173
+ }
174
+ return [match, all];
175
+ }
176
+ function getOptions(state) {
177
+ const all = [...state.content.querySelectorAll(`[${ATTRIBUTE_OPTION}]`)].filter((option) => option instanceof HTMLElement);
178
+ return [all, all.filter((option) => !option.hidden)];
179
+ }
180
+ function getSelected(update, state, element, next) {
181
+ if (state.autocomplete.none) return false;
182
+ if (update === "navigation") {
183
+ if (state.autocomplete.both) return element === next;
184
+ return getAria(element, "selected") === "true";
185
+ }
186
+ if (update === "none") return getAria(element, "selected") === "true";
187
+ if (update === "set") return element === next;
188
+ return state.autocomplete.both && element === next;
189
+ }
190
+ function getState(element, internals) {
191
+ const select = element.querySelector("select");
192
+ if (select == null) throw new Error(MESSAGE_SELECT);
193
+ const label = element.querySelector(`label[for="${select.id}"]`);
194
+ if (label == null) throw new Error(MESSAGE_LABEL);
195
+ select.hidden = true;
196
+ element.id = select.id;
197
+ element.name = select.name === "" ? select.id : select.name;
198
+ const disabled = element.disabled || select.disabled;
199
+ const multiple = element.multiple || select.multiple;
200
+ const required = element.required || select.required;
201
+ const id = getId();
202
+ const type = multiple ? "select" : getType(element);
203
+ const autocomplete = getAutoComplete(element, type === "select");
204
+ label.htmlFor = id;
205
+ label.id = `${id}_label`;
206
+ const anchor = getAnchor(type, id, element, label, select, autocomplete);
207
+ const [content, selected] = getContent(type, id, element, anchor, label, select, autocomplete);
208
+ const floatable = createEmbeddedFloatable(anchor, content, {
209
+ position: "below-start",
210
+ reusable: false
211
+ });
212
+ floatable.update();
213
+ select.insertAdjacentElement("afterend", anchor);
214
+ anchor.insertAdjacentElement("afterend", content);
215
+ select.remove();
216
+ const state = {
217
+ anchor,
218
+ autocomplete,
219
+ content,
220
+ disabled,
221
+ floatable,
222
+ id,
223
+ internals,
224
+ label,
225
+ multiple,
226
+ required,
227
+ select,
228
+ selected,
229
+ type,
230
+ filter: "",
231
+ listeners: [],
232
+ value: ""
233
+ };
234
+ state.listeners.push(on(content, "click", (event) => {
235
+ onContent(event, content);
236
+ }), on(content, "toggle", () => {
237
+ setAria(anchor, {
238
+ activedescendant: floatable.open ? state.active?.id : void 0,
239
+ expanded: floatable.open
240
+ });
241
+ if (floatable.open) setStyle(content, "--oui-combobox-width", `${anchor.getBoundingClientRect().width}px`);
242
+ }));
243
+ setDisabled(element, state, disabled);
244
+ return state;
245
+ }
246
+ function getType(element) {
247
+ const type = getAttribute(element, ATTRIBUTE_TYPE);
248
+ return TYPES.has(type) ? type : "select";
249
+ }
250
+ function onAnchor(event, handler) {
251
+ if (event.ctrlKey || event.metaKey || event.shiftKey) return;
252
+ const combobox = findAncestor(event, TAGNAME);
253
+ const state = states.get(combobox);
254
+ if (combobox == null || state == null) return;
255
+ if (findAncestor(event, SELECTOR_ANCHOR) != null) handler(event, combobox, state);
256
+ }
257
+ function onAnchorKeydown(event, combobox, state) {
258
+ let preventDefault = true;
259
+ switch (true) {
260
+ case event.key === "Enter":
261
+ onEnter(combobox, state);
262
+ break;
263
+ case event.key === "Escape":
264
+ onEscape(combobox, state);
265
+ break;
266
+ case state.type === "select" && KEYS_ABSOLUTE.has(event.key):
267
+ onNavigation(event, combobox, state);
268
+ break;
269
+ case KEYS_NAVIGATION.has(event.key):
270
+ onNavigation(event, combobox, state);
271
+ break;
272
+ default:
273
+ preventDefault = false;
274
+ if (event.key === "Tab") onTab(combobox, state);
275
+ break;
276
+ }
277
+ if (preventDefault) {
278
+ event.preventDefault();
279
+ event.stopPropagation();
280
+ }
281
+ }
282
+ function onAnchorKeyup(event, combobox, state) {
283
+ if (state.type === "select") return;
284
+ let preventDefault = true;
285
+ const typed = event.key.length === 1 && EXPRESSION_TYPED.test(event.key);
286
+ if (typed) state.filter += event.key;
287
+ if (state.anchor instanceof HTMLInputElement && (KEYS_REMOVE.has(event.key) || state.anchor.value.length < state.filter.length)) {
288
+ state.filter = state.anchor.value;
289
+ filterOptions(state);
290
+ }
291
+ switch (true) {
292
+ case typed:
293
+ onTyped(combobox, state);
294
+ break;
295
+ case KEYS_REMOVE.has(event.key):
296
+ onRemove(combobox, state);
297
+ break;
298
+ default:
299
+ preventDefault = false;
300
+ break;
301
+ }
302
+ if (preventDefault) {
303
+ event.preventDefault();
304
+ event.stopPropagation();
305
+ }
306
+ }
307
+ function onClick(event) {
308
+ const combobox = findAncestor(event, TAGNAME);
309
+ const state = states.get(combobox);
310
+ if (combobox == null || state == null) return;
311
+ const label = findAncestor(event, "label");
312
+ if (label != null) {
313
+ if (label === state.label && combobox.type === "select") state.anchor.focus();
314
+ return;
315
+ }
316
+ if (findAncestor(event, SELECTOR_ANCHOR) != null) {
317
+ state.floatable.show();
318
+ return;
319
+ }
320
+ }
321
+ function onContent(event, content) {
322
+ const combobox = comboboxes.get(content);
323
+ const state = states.get(content);
324
+ if (combobox == null || state == null) return;
325
+ const option = findAncestor(event, SELECTOR_OPTION);
326
+ if (option instanceof HTMLElement) setNext(combobox, state, getOptions(state)[0], "set", option);
327
+ state.anchor.focus();
328
+ }
329
+ function onEnter(combobox, state) {
330
+ if (state.floatable.open) setNext(combobox, state, getOptions(state)[0], "set", state.active);
331
+ else state.floatable.show();
332
+ }
333
+ function onEscape(combobox, state) {
334
+ if (!state.floatable.open) return;
335
+ if (state.type === "select") state.floatable.hide();
336
+ else setNext(combobox, state, getOptions(state)[0], "set", state.active);
337
+ }
338
+ function onKeydown(event) {
339
+ onAnchor(event, onAnchorKeydown);
340
+ }
341
+ function onKeyup(event) {
342
+ onAnchor(event, onAnchorKeyup);
343
+ }
344
+ function onNavigation(event, combobox, state) {
345
+ let skip = false;
346
+ if (!state.floatable.open) {
347
+ skip = true;
348
+ state.floatable.show();
349
+ }
350
+ const [all, visible] = getOptions(state);
351
+ if (visible.length === 0) return;
352
+ let next;
353
+ if (skip ?? state.active == null) next = state.active ?? visible[0];
354
+ else {
355
+ const current = visible.findIndex((option) => option.id === state.active?.id);
356
+ let index;
357
+ switch (event.key) {
358
+ case "ArrowDown":
359
+ case "ArrowUp":
360
+ index = clamp(current + (event.key === "ArrowDown" ? 1 : -1), 0, visible.length - 1, true);
361
+ break;
362
+ case "End":
363
+ index = visible.length - 1;
364
+ break;
365
+ case "Home":
366
+ index = 0;
367
+ break;
368
+ case "PageDown":
369
+ case "PageUp":
370
+ index = clamp(current + (event.key === "PageDown" ? 10 : -10), 0, visible.length - 1);
371
+ break;
372
+ default: break;
373
+ }
374
+ if (index != null) next = visible[index];
375
+ }
376
+ setNext(combobox, state, all, state.autocomplete.none ? "none" : "navigation", next);
377
+ }
378
+ function onRemove(combobox, state) {
379
+ const [all] = getOptions(state);
380
+ setNext(combobox, state, all, "none", filterOptions(state, all));
381
+ }
382
+ function onTab(combobox, state) {
383
+ if (combobox !== document.activeElement || !combobox.contains(document.activeElement)) state.floatable.hide();
384
+ }
385
+ function onTyped(combobox, state) {
386
+ function open() {
387
+ if (state.anchor instanceof HTMLInputElement && state.anchor.value.length > 0 && (state.autocomplete.both || state.autocomplete.list)) state.floatable.show();
388
+ }
389
+ if (!state.autocomplete.none) {
390
+ const next = filterOptions(state);
391
+ state.active = next != null && next.textContent?.toLocaleLowerCase().indexOf(state.filter.toLocaleLowerCase()) === 0 ? next : void 0;
392
+ if (next != null) open();
393
+ setNext(combobox, state, getOptions(state)[0], "typed", next);
394
+ } else open();
395
+ }
396
+ function setDisabled(combobox, state, value) {
397
+ state.disabled = value;
398
+ setAria(state.anchor, "disabled", value);
399
+ setProperty(combobox, "disabled", value);
400
+ if (state.anchor instanceof HTMLInputElement) state.anchor.readOnly = value;
401
+ }
402
+ function setNext(combobox, state, all, update, next) {
403
+ for (const option of all) setAttribute(option, ATTRIBUTE_OPTION_CURRENT, option === next ? "" : void 0);
404
+ state.active = next;
405
+ setAria(state.anchor, "activedescendant", next?.id);
406
+ next?.scrollIntoView(scrollOptions);
407
+ setValue(combobox, state, update, next);
408
+ }
409
+ function setRequired(combobox, state, value) {
410
+ state.required = value;
411
+ setAria(state.anchor, "required", value);
412
+ setProperty(combobox, "required", value);
413
+ }
414
+ function setValue(combobox, state, update, next) {
415
+ if (state.disabled) return;
416
+ const [all] = getOptions(state);
417
+ for (const option of all) setAria(option, "selected", getSelected(update, state, option, next));
418
+ state.select.value = next?.getAttribute("value") ?? "";
419
+ if (state.value !== state.select.value) {
420
+ state.value = state.select.value;
421
+ state.internals.setFormValue(state.value);
422
+ dispatch(combobox, "change");
423
+ dispatch(state.select, "change");
424
+ }
425
+ if (next == null || update === "none") return;
426
+ if (update === "set" || state.autocomplete.both || state.autocomplete.inline) {
427
+ const textContent = next.textContent ?? "";
428
+ if (state.anchor instanceof HTMLInputElement) state.anchor.value = textContent;
429
+ else state.anchor.innerHTML = textContent;
430
+ if (state.anchor instanceof HTMLInputElement) state.anchor.setSelectionRange(update === "typed" ? state.filter.length : textContent.length, textContent.length);
431
+ }
432
+ if (update === "set") state.floatable.hide();
433
+ }
434
+ const TAGNAME = "oui-combobox";
435
+ const ATTRIBUTE_ANCHOR = `${TAGNAME}-anchor`;
436
+ const ATTRIBUTE_CONTENT = `${TAGNAME}-content`;
437
+ const ATTRIBUTE_LIST = `${TAGNAME}-list`;
438
+ const ATTRIBUTE_OPTION = `${TAGNAME}-option`;
439
+ const ATTRIBUTE_OPTION_CURRENT = `${TAGNAME}-option-current`;
440
+ const ATTRIBUTE_TYPE = "type";
441
+ const AUTOCOMPLETES = /* @__PURE__ */ new Set([
442
+ "both",
443
+ "inline",
444
+ "list",
445
+ "none"
446
+ ]);
447
+ const EXPRESSION_TYPED = /\p{L}|\s/u;
448
+ const KEYS_ABSOLUTE = /* @__PURE__ */ new Set(["End", "Home"]);
449
+ const KEYS_NAVIGATION = /* @__PURE__ */ new Set([
450
+ "ArrowDown",
451
+ "ArrowUp",
452
+ "PageDown",
453
+ "PageUp"
454
+ ]);
455
+ const KEYS_REMOVE = /* @__PURE__ */ new Set(["Backspace", "Delete"]);
456
+ const MESSAGE_LABEL = `<${TAGNAME}> must contain a <label> element for the <select> element`;
457
+ const MESSAGE_SELECT = `<${TAGNAME}> must contain a <select> element`;
458
+ const SELECTOR_ANCHOR = `[${ATTRIBUTE_ANCHOR}]`;
459
+ const SELECTOR_OPTION = `[${ATTRIBUTE_OPTION}]`;
460
+ const TYPES = /* @__PURE__ */ new Set(["autocomplete", "select"]);
461
+ const comboboxes = /* @__PURE__ */ new WeakMap();
462
+ const scrollOptions = {
463
+ behavior: "smooth",
464
+ block: "nearest",
465
+ inline: "nearest"
466
+ };
467
+ const states = /* @__PURE__ */ new WeakMap();
468
+ let index = 0;
469
+ on(document, "click", onClick);
470
+ on(document, "keydown", onKeydown, { passive: false });
471
+ on(document, "keyup", onKeyup, { passive: false });
472
+ customElements.define(TAGNAME, OuiComboboxElement);
473
+ //#endregion
474
+ export { OuiComboboxElement };
@@ -1,7 +1,8 @@
1
- import { h as OuiMovable } from "./draggable-fIYXd8eO.mjs";
2
- import { n as OuiResizable } from "./resizable.embedded-CXrUVPzR.mjs";
1
+ import { h as OuiMovable } from "./draggable-a6w0J1OD.mjs";
2
+ import "./movable/movable.standalone.mjs";
3
+ import { n as OuiResizable } from "./resizable.embedded-3qoR0Bjm.mjs";
4
+ import "./resizable/resizable.standalone.mjs";
3
5
  import { RemovableEventListener } from "@oscarpalmer/toretto/models";
4
-
5
6
  //#region src/js/dialog.d.ts
6
7
  declare class OuiDialog {
7
8
  #private;
@@ -4,8 +4,8 @@ import { ATTRIBUTE_MOVABLE_HANDLE, createMovable } from "./movable/movable.embed
4
4
  import "./movable/movable.standalone.mjs";
5
5
  import { RESIZABLE_ATTRIBUTE_HANDLE, createResizable } from "./resizable/resizable.embedded.mjs";
6
6
  import { getAria, setAria } from "@oscarpalmer/toretto/aria";
7
- import { on } from "@oscarpalmer/toretto/event";
8
7
  import { getAttribute, setAttribute } from "@oscarpalmer/toretto/attribute";
8
+ import { on } from "@oscarpalmer/toretto/event";
9
9
  import { setStyles } from "@oscarpalmer/toretto/style";
10
10
  //#region src/js/dialog.ts
11
11
  var OuiDialog = class {
@@ -1,6 +1,5 @@
1
1
  import { StyleToggler } from "@oscarpalmer/toretto/style";
2
2
  import { EventPosition, PlainObject } from "@oscarpalmer/atoms/models";
3
-
4
3
  //#region src/js/movable/movable.embedded.d.ts
5
4
  declare class OuiMovable extends OuiDraggable<OuiMovableState> {
6
5
  #private;
@@ -134,9 +133,9 @@ type OuiDraggableState = {
134
133
  attribute: string;
135
134
  container?: HTMLElement;
136
135
  element: HTMLElement;
137
- event: string;
138
136
  hasHandles?: boolean;
139
137
  horizontal: boolean;
138
+ name: keyof OuiDraggableStates;
140
139
  options: OuiDraggableOptions;
141
140
  styling: StyleToggler;
142
141
  vertical: boolean;
@@ -1,2 +1,2 @@
1
- import { a as OuiFloatablePosition, c as getOnBeforeToggleListener, i as OuiFloatableOptions, l as removeFloatable, n as CreateOuiFloatableOptions, o as createEmbeddedFloatable, r as OuiFloatable, s as createFloatable, t as ATTRIBUTE_FLOATABLE } from "../floatable.embedded-DUR8bQBK.mjs";
1
+ import { a as OuiFloatablePosition, c as getOnBeforeToggleListener, i as OuiFloatableOptions, l as removeFloatable, n as CreateOuiFloatableOptions, o as createEmbeddedFloatable, r as OuiFloatable, s as createFloatable, t as ATTRIBUTE_FLOATABLE } from "../floatable.embedded-Bct4U5Dm.mjs";
2
2
  export { ATTRIBUTE_FLOATABLE, CreateOuiFloatableOptions, OuiFloatable, OuiFloatableOptions, OuiFloatablePosition, createEmbeddedFloatable, createFloatable, getOnBeforeToggleListener, removeFloatable };
@@ -1,9 +1,9 @@
1
1
  import { getAria } from "@oscarpalmer/toretto/aria";
2
+ import { getAttribute, setAttribute } from "@oscarpalmer/toretto/attribute";
2
3
  import { on } from "@oscarpalmer/toretto/event";
4
+ import { setStyle, setStyles } from "@oscarpalmer/toretto/style";
3
5
  import { isPlainObject } from "@oscarpalmer/atoms/is";
4
- import { getAttribute, setAttribute } from "@oscarpalmer/toretto/attribute";
5
6
  import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
6
- import { setStyle, setStyles } from "@oscarpalmer/toretto/style";
7
7
  //#region src/js/floatable/floatable.embedded.ts
8
8
  var OuiFloatable = class {
9
9
  #destroyed = false;
@@ -1,2 +1,2 @@
1
- import { a as OuiFloatablePosition, n as CreateOuiFloatableOptions, r as OuiFloatable, s as createFloatable } from "../floatable.embedded-DUR8bQBK.mjs";
1
+ import { a as OuiFloatablePosition, n as CreateOuiFloatableOptions, r as OuiFloatable, s as createFloatable } from "../floatable.embedded-Bct4U5Dm.mjs";
2
2
  export { type CreateOuiFloatableOptions, type OuiFloatable, type OuiFloatablePosition, createFloatable };
@@ -1,5 +1,4 @@
1
1
  import { RemovableEventListener } from "@oscarpalmer/toretto/models";
2
-
3
2
  //#region src/js/floatable/floatable.embedded.d.ts
4
3
  type CreateOuiFloatableOptions = {
5
4
  /**
@@ -1,10 +1,10 @@
1
1
  import { attributable } from "../internal/attributable.mjs";
2
+ import { clamp } from "@oscarpalmer/atoms/number";
3
+ import { setAttribute } from "@oscarpalmer/toretto/attribute";
2
4
  import { on } from "@oscarpalmer/toretto/event";
5
+ import { findAncestor } from "@oscarpalmer/toretto/find";
3
6
  import { isPlainObject } from "@oscarpalmer/atoms/is";
4
- import { setAttribute } from "@oscarpalmer/toretto/attribute";
5
7
  import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
6
- import { clamp } from "@oscarpalmer/atoms/number";
7
- import { findAncestor } from "@oscarpalmer/toretto/find";
8
8
  import { getFocusable } from "@oscarpalmer/toretto/focusable";
9
9
  //#region src/js/focus-trap/focus-trap.embedded.ts
10
10
  var OuiFocusTrap = class {
@@ -172,11 +172,16 @@ function onTab(event) {
172
172
  const element = findAncestor(event, SELECTOR);
173
173
  const instance = instances.get(element);
174
174
  if (instance == null) return;
175
- if (!instance.disabled) event.preventDefault();
176
- const elements = getFocusable(element);
177
- const index = elements.indexOf(event.target);
178
- if (index === -1 || elements.length === 0) tabToTrap(instance, elements, element);
179
- else tabToElement(instance, elements, index, event.shiftKey ? -1 : 1);
175
+ const dispatch = new CustomEvent("focus-trap:tab", { cancelable: true });
176
+ element.dispatchEvent(dispatch);
177
+ setTimeout(() => {
178
+ if (dispatch.defaultPrevented) return;
179
+ if (!instance.disabled) event.preventDefault();
180
+ const elements = getFocusable(element);
181
+ const index = elements.indexOf(event.target);
182
+ if (index === -1 || elements.length === 0) tabToTrap(instance, elements, element);
183
+ else tabToElement(instance, elements, index, event.shiftKey ? -1 : 1);
184
+ });
180
185
  }
181
186
  function removeFocusTrap(element) {
182
187
  instances.get(element)?.destroy();
@@ -1,9 +1,14 @@
1
- import { d as OuiSortable, f as createSortable, g as createMovable, h as OuiMovable, t as CreateOuiDraggableOptions } from "./draggable-fIYXd8eO.mjs";
2
- import { a as createResizable, n as OuiResizable, t as CreateOuiResizableOptions } from "./resizable.embedded-CXrUVPzR.mjs";
1
+ import { OuiComboboxElement } from "./combobox.mjs";
2
+ import { d as OuiSortable, f as createSortable, g as createMovable, h as OuiMovable, t as CreateOuiDraggableOptions } from "./draggable-a6w0J1OD.mjs";
3
+ import "./movable/movable.standalone.mjs";
4
+ import { a as createResizable, n as OuiResizable, t as CreateOuiResizableOptions } from "./resizable.embedded-3qoR0Bjm.mjs";
5
+ import "./resizable/resizable.standalone.mjs";
3
6
  import { OuiDialog, getDialog } from "./dialog.mjs";
4
- import { a as OuiFloatablePosition, n as CreateOuiFloatableOptions, r as OuiFloatable, s as createFloatable } from "./floatable.embedded-DUR8bQBK.mjs";
7
+ import { a as OuiFloatablePosition, n as CreateOuiFloatableOptions, r as OuiFloatable, s as createFloatable } from "./floatable.embedded-Bct4U5Dm.mjs";
8
+ import "./floatable/floatable.standalone.mjs";
5
9
  import { a as OuiFocusTrapOptions, i as OuiFocusTrap, o as createFocusTrap } from "./focus-trap.embedded-DR8NpZfr.mjs";
6
10
  import { OuiPopover, getPopover } from "./popover.mjs";
11
+ import "./sortable/sortable.standalone.mjs";
7
12
  import { OuiSplitterElement } from "./splitter.mjs";
8
13
  import { OuiTooltip, getTooltip, setTooltipDelay } from "./tooltip.mjs";
9
- export { type CreateOuiFloatableOptions, type CreateOuiDraggableOptions as CreateOuiMovableOptions, type CreateOuiDraggableOptions as CreateOuiSortableOptions, type CreateOuiResizableOptions, type OuiDialog, type OuiFloatable, type OuiFloatablePosition, type OuiFocusTrap, type OuiFocusTrapOptions, type OuiMovable, type OuiPopover, type OuiResizable, type OuiSortable, OuiSplitterElement, type OuiTooltip, createFloatable, createFocusTrap, createMovable, createResizable as createOuiResizable, createSortable, getDialog, getPopover, getTooltip, setTooltipDelay };
14
+ export { type CreateOuiFloatableOptions, type CreateOuiDraggableOptions as CreateOuiMovableOptions, type CreateOuiDraggableOptions as CreateOuiSortableOptions, type CreateOuiResizableOptions, OuiComboboxElement, type OuiDialog, type OuiFloatable, type OuiFloatablePosition, type OuiFocusTrap, type OuiFocusTrapOptions, type OuiMovable, type OuiPopover, type OuiResizable, type OuiSortable, OuiSplitterElement, type OuiTooltip, createFloatable, createFocusTrap, createMovable, createResizable as createOuiResizable, createSortable, getDialog, getPopover, getTooltip, setTooltipDelay };
package/dist/js/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createFloatable } from "./floatable/floatable.embedded.mjs";
2
+ import { OuiComboboxElement } from "./combobox.mjs";
2
3
  import { createMovable } from "./movable/movable.embedded.mjs";
3
4
  import "./movable/movable.standalone.mjs";
4
5
  import { createResizable } from "./resizable/resizable.embedded.mjs";
@@ -14,4 +15,4 @@ import { createSortable } from "./sortable/sortable.embedded.mjs";
14
15
  import "./sortable/sortable.standalone.mjs";
15
16
  import { OuiSplitterElement } from "./splitter.mjs";
16
17
  import { getTooltip, setTooltipDelay } from "./tooltip.mjs";
17
- export { OuiSplitterElement, createFloatable, createFocusTrap, createMovable, createResizable as createOuiResizable, createSortable, getDialog, getPopover, getTooltip, setTooltipDelay };
18
+ export { OuiComboboxElement, OuiSplitterElement, createFloatable, createFocusTrap, createMovable, createResizable as createOuiResizable, createSortable, getDialog, getPopover, getTooltip, setTooltipDelay };
@@ -1,2 +1,2 @@
1
- import { a as OuiDraggableGlobals, c as addDraggable, i as OuiDraggableBound, l as getDraggableStates, n as OuiDragMovePosition, o as OuiDraggableItem, r as OuiDraggable, s as OuiDraggableState, t as CreateOuiDraggableOptions, u as removeDraggable } from "../draggable-fIYXd8eO.mjs";
1
+ import { a as OuiDraggableGlobals, c as addDraggable, i as OuiDraggableBound, l as getDraggableStates, n as OuiDragMovePosition, o as OuiDraggableItem, r as OuiDraggable, s as OuiDraggableState, t as CreateOuiDraggableOptions, u as removeDraggable } from "../draggable-a6w0J1OD.mjs";
2
2
  export { CreateOuiDraggableOptions, OuiDragMovePosition, OuiDraggable, OuiDraggableBound, OuiDraggableGlobals, OuiDraggableItem, OuiDraggableState, addDraggable, getDraggableStates, removeDraggable };