@oscarpalmer/oui 0.34.0 → 0.36.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 (93) hide show
  1. package/dist/css/combobox.css +1 -1
  2. package/dist/css/listbox.css +152 -0
  3. package/dist/js/combobox/constants.combobox.d.mts +2 -2
  4. package/dist/js/combobox/constants.combobox.mjs +40 -33
  5. package/dist/js/combobox/dom.combobox.d.mts +8 -91
  6. package/dist/js/combobox/dom.combobox.mjs +204 -2
  7. package/dist/js/combobox/event.combobox.d.mts +14 -8
  8. package/dist/js/combobox/event.combobox.mjs +2 -2
  9. package/dist/js/combobox/index.d.mts +1 -1
  10. package/dist/js/combobox/index.mjs +21 -18
  11. package/dist/js/combobox/misc.combobox.d.mts +2 -14
  12. package/dist/js/combobox/misc.combobox.mjs +2 -2
  13. package/dist/js/combobox/models.combobox.d.mts +44 -2
  14. package/dist/js/combobox/state.combobox.d.mts +9 -8
  15. package/dist/js/combobox/state.combobox.mjs +2 -2
  16. package/dist/js/constants.combobox-DmRAMC64.d.mts +26 -0
  17. package/dist/js/constants.listbox-DRsXUxd3.d.mts +14 -0
  18. package/dist/js/index-EfkUSals.d.mts +139 -0
  19. package/dist/js/index.d.mts +4 -2
  20. package/dist/js/index.mjs +4 -2
  21. package/dist/js/internal/attributable.mjs +4 -3
  22. package/dist/js/internal/listbox/listbox.constants.d.mts +11 -0
  23. package/dist/js/internal/listbox/listbox.constants.mjs +23 -0
  24. package/dist/js/internal/listbox/listbox.data.d.mts +20 -0
  25. package/dist/js/internal/listbox/listbox.data.mjs +2 -0
  26. package/dist/js/internal/listbox/listbox.dom.d.mts +66 -0
  27. package/dist/js/internal/listbox/listbox.dom.mjs +2 -0
  28. package/dist/js/internal/listbox/listbox.event.d.mts +5 -0
  29. package/dist/js/internal/listbox/listbox.event.mjs +63 -0
  30. package/dist/js/internal/listbox/listbox.form.d.mts +6 -0
  31. package/dist/js/internal/listbox/listbox.form.mjs +52 -0
  32. package/dist/js/internal/listbox/listbox.misc.d.mts +12 -0
  33. package/dist/js/internal/listbox/listbox.misc.mjs +2 -0
  34. package/dist/js/internal/listbox/listbox.model.d.mts +2 -0
  35. package/dist/js/internal/listbox/listbox.model.mjs +1 -0
  36. package/dist/js/internal/listbox/listbox.state.d.mts +7 -0
  37. package/dist/js/internal/listbox/listbox.state.mjs +40 -0
  38. package/dist/js/internal/misc.d.mts +6 -0
  39. package/dist/js/internal/misc.mjs +13 -0
  40. package/dist/js/l10n-Bn-7RcwR.d.mts +20 -0
  41. package/dist/js/l10n.d.mts +2 -0
  42. package/dist/js/l10n.mjs +80 -0
  43. package/dist/js/listbox/constants.listbox.d.mts +2 -0
  44. package/dist/js/listbox/constants.listbox.mjs +40 -0
  45. package/dist/js/listbox/event.listbox.d.mts +8 -0
  46. package/dist/js/listbox/event.listbox.mjs +2 -0
  47. package/dist/js/listbox/index.d.mts +116 -0
  48. package/dist/js/listbox/index.mjs +189 -0
  49. package/dist/js/listbox/misc.listbox.d.mts +2 -0
  50. package/dist/js/listbox/misc.listbox.mjs +2 -0
  51. package/dist/js/listbox/state.listbox.d.mts +11 -0
  52. package/dist/js/listbox/state.listbox.mjs +2 -0
  53. package/dist/js/listbox.data-BWd0PCy7.mjs +424 -0
  54. package/dist/js/listbox.model-CRReGOmu.d.mts +125 -0
  55. package/dist/js/misc.listbox-BI-XsTOf.mjs +197 -0
  56. package/dist/js/misc.listbox-gnoJhrTr.d.mts +20 -0
  57. package/dist/js/sortable/sortable.embedded.mjs +4 -2
  58. package/dist/js/state.combobox-4xq0FPCF.mjs +365 -0
  59. package/package.json +7 -3
  60. package/src/css/combobox.scss +1 -1
  61. package/src/css/listbox.scss +149 -0
  62. package/src/js/combobox/constants.combobox.ts +46 -25
  63. package/src/js/combobox/dom.combobox.ts +65 -452
  64. package/src/js/combobox/event.combobox.ts +129 -218
  65. package/src/js/combobox/index.ts +43 -30
  66. package/src/js/combobox/misc.combobox.ts +60 -94
  67. package/src/js/combobox/models.combobox.ts +7 -47
  68. package/src/js/combobox/state.combobox.ts +136 -125
  69. package/src/js/index.ts +1 -0
  70. package/src/js/internal/listbox/listbox.constants.ts +25 -0
  71. package/src/js/{combobox/data.combobox.ts → internal/listbox/listbox.data.ts} +94 -43
  72. package/src/js/internal/listbox/listbox.dom.ts +445 -0
  73. package/src/js/internal/listbox/listbox.event.ts +129 -0
  74. package/src/js/{combobox/form.combobox.ts → internal/listbox/listbox.form.ts} +33 -33
  75. package/src/js/internal/listbox/listbox.misc.ts +135 -0
  76. package/src/js/internal/listbox/listbox.model.ts +169 -0
  77. package/src/js/internal/listbox/listbox.state.ts +82 -0
  78. package/src/js/internal/misc.ts +27 -0
  79. package/src/js/l10n.ts +192 -0
  80. package/src/js/listbox/constants.listbox.ts +61 -0
  81. package/src/js/listbox/event.listbox.ts +106 -0
  82. package/src/js/listbox/index.ts +302 -0
  83. package/src/js/listbox/misc.listbox.ts +70 -0
  84. package/src/js/listbox/state.listbox.ts +196 -0
  85. package/dist/js/combobox/data.combobox.d.mts +0 -18
  86. package/dist/js/combobox/data.combobox.mjs +0 -2
  87. package/dist/js/combobox/form.combobox.d.mts +0 -6
  88. package/dist/js/combobox/form.combobox.mjs +0 -2
  89. package/dist/js/combobox/i18n.combobox.d.mts +0 -2
  90. package/dist/js/combobox/i18n.combobox.mjs +0 -2
  91. package/dist/js/constants.combobox-DqNUGBLS.d.mts +0 -243
  92. package/dist/js/data.combobox-8RwBv5ey.mjs +0 -1077
  93. package/src/js/combobox/i18n.combobox.ts +0 -179
@@ -0,0 +1,365 @@
1
+ import { createEmbeddedFloatable } from "./floatable/floatable.embedded.mjs";
2
+ import { getLocalizations, updateLocalizations } from "./l10n.mjs";
3
+ import { LISTBOX_KEYS, LISTBOX_TYPES } from "./internal/listbox/listbox.constants.mjs";
4
+ import { b as updateListboxValueLabel, i as filterListboxOptions, n as filterListbox, r as filterListboxOption, s as toggleListboxOption, t as applyListboxSelection, v as updateListboxMessage, x as generateListboxIds, y as updateListboxPreview } from "./listbox.data-BWd0PCy7.mjs";
5
+ import { initializeListboxState } from "./internal/listbox/listbox.state.mjs";
6
+ import { COMBOBOX_ATTRIBUTES, COMBOBOX_ATTRIBUTE_SELECTION_ITEM, COMBOBOX_AUTOCOMPLETES, COMBOBOX_MESSAGES, COMBOBOX_SELECTORS, COMBOBOX_TAGNAME } from "./combobox/constants.combobox.mjs";
7
+ import { getAttributeValue, getBooleanAriaValue } from "./internal/misc.mjs";
8
+ import { createComboboxAnchor, createComboboxContent, updateComboboxAutocomplete, updateComboboxSelection, updateComboboxSelectionItems, updateComboboxTabindices } from "./combobox/dom.combobox.mjs";
9
+ import { processListboxKeydown } from "./internal/listbox/listbox.event.mjs";
10
+ import { setAria } from "@oscarpalmer/toretto/aria";
11
+ import { on } from "@oscarpalmer/toretto/event";
12
+ import { setAttributes } from "@oscarpalmer/toretto/attribute";
13
+ import { clamp } from "@oscarpalmer/atoms/number";
14
+ import { getData } from "@oscarpalmer/toretto/data";
15
+ import { findAncestor, getData as getData$1 } from "@oscarpalmer/toretto";
16
+ import { noop } from "@oscarpalmer/atoms/function";
17
+ //#region src/js/combobox/misc.combobox.ts
18
+ function getComboboxAutoComplete(element, select) {
19
+ let value;
20
+ if (select) value = "list";
21
+ else value = getAttributeValue(element, "autocomplete", COMBOBOX_AUTOCOMPLETES, "none");
22
+ return {
23
+ value,
24
+ both: value === "both",
25
+ inline: value === "inline",
26
+ list: value === "list",
27
+ none: value === "none"
28
+ };
29
+ }
30
+ function getComboboxId() {
31
+ index += 1;
32
+ return `oui_combobox_${index}`;
33
+ }
34
+ function hasTextSelection(element) {
35
+ return element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
36
+ }
37
+ function setComboboxLocalizations(first, second) {
38
+ updateComboboxLocalizations(comboboxLocalizations, comboboxLocalizations.values, first, second);
39
+ }
40
+ function updateComboboxLocalizations(instance, destination, first, second) {
41
+ updateLocalizations(instance, typeof first === "string" ? { [first]: second } : first, destination, () => {
42
+ for (const state of comboboxStates.set) {
43
+ updateListboxMessage(state);
44
+ updateListboxValueLabel(state);
45
+ updateComboboxSelectionItems(state);
46
+ }
47
+ });
48
+ }
49
+ let index = 0;
50
+ //#endregion
51
+ //#region src/js/combobox/event.combobox.ts
52
+ function onComboboxActivation(state) {
53
+ state.floatable.show();
54
+ }
55
+ function onComboboxApply(state, option, _, added, selected) {
56
+ updateComboboxSelection(state, option, added, selected);
57
+ }
58
+ function onComboboxClickContent(event, state) {
59
+ if (state == null) return;
60
+ if (state.disabled) return;
61
+ const option = findAncestor(event, COMBOBOX_SELECTORS.option);
62
+ if (!(option instanceof HTMLElement)) state.filter.element.focus();
63
+ else if (state.readonly) onComboboxPreview("set", state, option);
64
+ else toggleListboxOption(state, option);
65
+ }
66
+ function onComboboxClickDocument(event) {
67
+ const combobox = findAncestor(event, COMBOBOX_TAGNAME);
68
+ const state = comboboxStates.map.get(combobox);
69
+ if (combobox == null || state == null) return;
70
+ if (findAncestor(event, COMBOBOX_SELECTORS.content) != null) {
71
+ onComboboxClickContent(event, state);
72
+ return;
73
+ }
74
+ const label = findAncestor(event, "label");
75
+ if (label != null) {
76
+ if (label === state.label.element && combobox.type === "select") {
77
+ state.anchor.element.focus();
78
+ if (!state.disabled) state.floatable.show();
79
+ }
80
+ return;
81
+ }
82
+ const selection = findAncestor(event, `[${COMBOBOX_ATTRIBUTE_SELECTION_ITEM}]`);
83
+ if (selection != null) {
84
+ const option = state.options.selected.find((option) => option.id === getData$1(selection, "option", false));
85
+ if (state.disabled) state.anchor.element.focus();
86
+ else if (state.readonly) updateComboboxTabindices(state, option);
87
+ else toggleListboxOption(state, option);
88
+ return;
89
+ }
90
+ if (findAncestor(event, COMBOBOX_SELECTORS.anchor) != null && !state.disabled) {
91
+ state.floatable.show();
92
+ return;
93
+ }
94
+ }
95
+ function onComboboxEnter(state) {
96
+ if (state.disabled || state.readonly) return;
97
+ if (hasTextSelection(state.anchor.element)) onComboboxSelection(state, true);
98
+ else toggleListboxOption(state, state.active);
99
+ }
100
+ function onComboboxEscape(state) {
101
+ state.floatable.hide();
102
+ }
103
+ function onComboboxFilter(preview, state, update, active, emit) {
104
+ if (state.autocomplete.none) applyListboxSelection({
105
+ state,
106
+ added: false,
107
+ emit: (emit ?? true) && update
108
+ });
109
+ else updateListboxPreview(preview, state, active);
110
+ }
111
+ function onComboboxFocusout(event) {
112
+ setTimeout(() => {
113
+ const combobox = findAncestor(event, COMBOBOX_TAGNAME);
114
+ const state = comboboxStates.map.get(combobox);
115
+ if (combobox == null || state == null || state.disabled || state.readonly || state.type === "select" || state.host === document.activeElement || state.host.contains(document.activeElement)) return;
116
+ const option = filterListboxOption(state, state.filter.element.value, true);
117
+ applyListboxSelection({
118
+ option,
119
+ state,
120
+ added: false,
121
+ selected: option == null ? [] : [option]
122
+ });
123
+ });
124
+ }
125
+ function onComboboxInput(event) {
126
+ const combobox = findAncestor(event, COMBOBOX_TAGNAME);
127
+ const state = comboboxStates.map.get(combobox);
128
+ if (combobox != null && state != null && event.target === state.filter.element) filterListbox(event.inputType, state);
129
+ }
130
+ function onComboboxKeydown(event) {
131
+ const combobox = findAncestor(event, COMBOBOX_TAGNAME);
132
+ const state = comboboxStates.map.get(combobox);
133
+ if (combobox == null || state == null) return;
134
+ const item = findAncestor(event, COMBOBOX_SELECTORS.selectionItem);
135
+ if (item instanceof HTMLElement) onComboboxKeydownSelection(event, state, item);
136
+ else processListboxKeydown(event, state);
137
+ }
138
+ function onComboboxKeydownSelection(event, state, item) {
139
+ if (LISTBOX_KEYS.activation.has(event.key)) {
140
+ event.preventDefault();
141
+ onComboboxRemoveSelection(state, item);
142
+ return;
143
+ }
144
+ if (state.disabled || state.selection == null) return;
145
+ const { length } = state.selection.items;
146
+ const index = state.selection.items.findIndex((selection) => selection.button === item);
147
+ let next;
148
+ switch (event.key) {
149
+ case "ArrowDown":
150
+ state.anchor.element.focus();
151
+ state.anchor.element.click();
152
+ return;
153
+ case "ArrowRight":
154
+ case "ArrowLeft":
155
+ next = clamp(index + (event.key === "ArrowRight" ? 1 : -1), 0, length - 1, true);
156
+ break;
157
+ case "End":
158
+ next = length - 1;
159
+ break;
160
+ case "Home": next = 0;
161
+ }
162
+ if (next == null) return;
163
+ event.preventDefault();
164
+ for (let index = 0; index < length; index += 1) {
165
+ const { button } = state.selection.items[index];
166
+ const selected = index === next;
167
+ button.tabIndex = selected ? 0 : -1;
168
+ if (selected) button.focus();
169
+ }
170
+ }
171
+ function onComboboxPreview(preview, state, active) {
172
+ updateComboboxAutocomplete(preview, state, active);
173
+ }
174
+ function onComboboxRemoveSelection(state, item) {
175
+ if (state.disabled || state.readonly) return;
176
+ const option = state.options.selected.find((option) => option.id === getData$1(item, "option", false));
177
+ if (option != null) toggleListboxOption(state, option);
178
+ }
179
+ function onComboboxNavigation(preview, state, active) {
180
+ updateListboxPreview(preview, state, active);
181
+ }
182
+ function onComboboxSelection(state, hasSelection) {
183
+ if (!(hasSelection ?? hasTextSelection(state.anchor.element))) return;
184
+ state.filter.value = state.anchor.element === state.filter.element ? state.filter.element.value : "";
185
+ toggleListboxOption(state, filterListboxOptions(state) ?? state.active);
186
+ }
187
+ function onComboboxTab(state) {
188
+ setTimeout(() => {
189
+ if (state.host === document.activeElement) return;
190
+ if (state.selection?.container.contains(document.activeElement) || !state.host.contains(document.activeElement)) state.floatable.hide();
191
+ });
192
+ }
193
+ function onComboboxToggle(state) {
194
+ if (!state.multiple) state.floatable.hide();
195
+ }
196
+ function preventComboboxActivation(state) {
197
+ return state.floatable.open;
198
+ }
199
+ function skipComboboxNavigation(state) {
200
+ if (state.floatable.open) return false;
201
+ state.floatable.show();
202
+ return true;
203
+ }
204
+ on(document, "click", onComboboxClickDocument);
205
+ on(document, "focusout", onComboboxFocusout);
206
+ on(document, "input", onComboboxInput, { passive: false });
207
+ on(document, "keydown", onComboboxKeydown, { passive: false });
208
+ //#endregion
209
+ //#region src/js/combobox/state.combobox.ts
210
+ function createComboboxState(element, internals) {
211
+ const select = element.querySelector("select");
212
+ if (select == null || select.id === "") throw new Error(COMBOBOX_MESSAGES.select);
213
+ const label = element.querySelector(`label[for="${select.id}"]`);
214
+ if (label == null) throw new Error(COMBOBOX_MESSAGES.label);
215
+ element.id = select.id;
216
+ const disabled = getBooleanAriaValue(element, "disabled") || select.disabled;
217
+ const multiple = element.multiple || select.multiple;
218
+ const readonly = getBooleanAriaValue(element, "readonly");
219
+ const required = getBooleanAriaValue(element, "required") || select.required;
220
+ const id = getComboboxId();
221
+ const type = multiple ? "select" : getAttributeValue(element, "type", LISTBOX_TYPES, "select");
222
+ const autocomplete = getComboboxAutoComplete(element, type === "select");
223
+ setAttributes(element, {
224
+ autocomplete: autocomplete.value,
225
+ name: select.name === "" ? select.id : select.name
226
+ });
227
+ const labelId = label.id;
228
+ label.htmlFor = id;
229
+ label.id = `${id}_label`;
230
+ generateListboxIds(id, select);
231
+ const parameters = {
232
+ autocomplete,
233
+ element,
234
+ id,
235
+ label,
236
+ multiple,
237
+ select,
238
+ type,
239
+ attributes: COMBOBOX_ATTRIBUTES,
240
+ listbox: false
241
+ };
242
+ const anchor = createComboboxAnchor(parameters);
243
+ const content = createComboboxContent(parameters);
244
+ const { selected } = content.list.options;
245
+ const values = selected.length === 0 ? [""] : selected.map((option) => getData(option, "value", false) ?? "");
246
+ const state = {
247
+ autocomplete,
248
+ disabled,
249
+ id,
250
+ internals,
251
+ multiple,
252
+ readonly,
253
+ required,
254
+ select,
255
+ type,
256
+ active: selected[0],
257
+ anchor: {
258
+ container: anchor.container,
259
+ element: anchor.element
260
+ },
261
+ attributes: COMBOBOX_ATTRIBUTES,
262
+ callbacks: comboboxCallbacks,
263
+ content: content.element,
264
+ elements: [
265
+ element,
266
+ anchor.element,
267
+ content.filter?.element
268
+ ],
269
+ filter: {
270
+ element: content.filter?.element ?? anchor.element,
271
+ value: ""
272
+ },
273
+ floatable: void 0,
274
+ groups: content.list.groups,
275
+ host: element,
276
+ label: {
277
+ element: label,
278
+ id: labelId
279
+ },
280
+ list: content.list.element,
281
+ listbox: false,
282
+ listeners: [],
283
+ localizations: {
284
+ component: {},
285
+ global: comboboxLocalizations
286
+ },
287
+ message: content.message,
288
+ name: element.name,
289
+ options: {
290
+ selected,
291
+ all: content.list.options.all
292
+ },
293
+ selection: anchor.selection,
294
+ values: {
295
+ current: values.slice(),
296
+ initial: values.slice(),
297
+ label: content.value
298
+ }
299
+ };
300
+ return initializeListboxState(state, comboboxStates, onAttachCombobox);
301
+ }
302
+ function onAttachCombobox(state) {
303
+ const { anchor, content, filter, select } = state;
304
+ select.hidden = true;
305
+ select.insertAdjacentElement("afterend", anchor.container ?? anchor.element);
306
+ (anchor.container ?? anchor.element).insertAdjacentElement("afterend", content);
307
+ select.remove();
308
+ state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
309
+ position: "below-start",
310
+ reusable: false
311
+ });
312
+ state.floatable.update();
313
+ state.listeners.push(on(content, "toggle", () => {
314
+ setAria(anchor.element, "expanded", state.floatable.open);
315
+ setAria(filter.element, "activedescendant", state.floatable.open ? state.active?.id : void 0);
316
+ if (state.floatable.open) filter.element.focus();
317
+ }));
318
+ }
319
+ function onDetachCombobox(state) {
320
+ const { content, floatable, label, select } = state;
321
+ floatable.destroy();
322
+ const anchor = state.anchor.container ?? state.anchor.element;
323
+ anchor.insertAdjacentElement("beforebegin", select);
324
+ anchor.remove();
325
+ content.remove();
326
+ label.element.htmlFor = select.id;
327
+ label.element.id = state.label.id;
328
+ select.hidden = false;
329
+ }
330
+ const comboboxCallbacks = {
331
+ data: {
332
+ onApply: onComboboxApply,
333
+ onFilter: onComboboxFilter,
334
+ onForm: noop,
335
+ onPreview: onComboboxPreview,
336
+ onToggle: onComboboxToggle
337
+ },
338
+ keydown: {
339
+ onActivation: onComboboxActivation,
340
+ onEnter: onComboboxEnter,
341
+ onEscape: onComboboxEscape,
342
+ onNavigation: onComboboxNavigation,
343
+ onSelection: onComboboxSelection,
344
+ onTab: onComboboxTab,
345
+ onToggle: onComboboxToggle,
346
+ preventActivation: preventComboboxActivation,
347
+ skipNavigation: skipComboboxNavigation
348
+ }
349
+ };
350
+ const comboboxLocalizations = getLocalizations({
351
+ empty: "No results found",
352
+ input: "Please enter a value",
353
+ invalid: "Please select an option from the list",
354
+ max: (max) => `Please select fewer options (maximum is ${max})`,
355
+ min: (min) => `Please select more options (minimum is ${min})`,
356
+ remove: (label) => `Remove ${label}`,
357
+ results: (amount) => `${amount} results available`,
358
+ selected: (amount) => `${amount} selected`
359
+ });
360
+ const comboboxStates = {
361
+ map: /* @__PURE__ */ new WeakMap(),
362
+ set: /* @__PURE__ */ new Set()
363
+ };
364
+ //#endregion
365
+ export { skipComboboxNavigation as C, setComboboxLocalizations as D, hasTextSelection as E, updateComboboxLocalizations as O, preventComboboxActivation as S, getComboboxId as T, onComboboxPreview as _, onDetachCombobox as a, onComboboxTab as b, onComboboxClickContent as c, onComboboxFilter as d, onComboboxFocusout as f, onComboboxNavigation as g, onComboboxKeydownSelection as h, onAttachCombobox as i, onComboboxEnter as l, onComboboxKeydown as m, comboboxStates as n, onComboboxActivation as o, onComboboxInput as p, createComboboxState as r, onComboboxApply as s, comboboxLocalizations as t, onComboboxEscape as u, onComboboxRemoveSelection as v, getComboboxAutoComplete as w, onComboboxToggle as x, onComboboxSelection as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/oui",
3
- "version": "0.34.0",
3
+ "version": "0.36.0",
4
4
  "description": "Styles, components, and utilities for building websites and applications.",
5
5
  "keywords": [
6
6
  "css",
@@ -49,6 +49,10 @@
49
49
  "types": "./dist/js/focus-trap/focus-trap.standalone.d.mts",
50
50
  "default": "./dist/js/focus-trap/focus-trap.standalone.mjs"
51
51
  },
52
+ "./listbox": {
53
+ "types": "./dist/js/listbox/index.d.mts",
54
+ "default": "./dist/js/listbox/index.mjs"
55
+ },
52
56
  "./movable": {
53
57
  "types": "./dist/js/movable/movable.standalone.d.mts",
54
58
  "default": "./dist/js/movable/movable.standalone.mjs"
@@ -95,10 +99,10 @@
95
99
  },
96
100
  "dependencies": {
97
101
  "@oscarpalmer/atoms": "^0.188.1",
98
- "@oscarpalmer/toretto": "^0.50.1"
102
+ "@oscarpalmer/toretto": "^0.51"
99
103
  },
100
104
  "devDependencies": {
101
- "@oxlint/plugins": "^1.77",
105
+ "@oxlint/plugins": "^1.78",
102
106
  "@vitest/coverage-istanbul": "^4.1",
103
107
  "autoprefixer": "^10.5",
104
108
  "concurrently": "^10",
@@ -95,7 +95,7 @@
95
95
  background-color: var(--oui-yellow-0);
96
96
  border-radius: 0.25em 0.25em 0 0;
97
97
  position: relative;
98
- z-index: 1;
98
+ z-index: 2;
99
99
  }
100
100
 
101
101
  :where([oui-combobox-filter-input]) {
@@ -0,0 +1,149 @@
1
+ /*! https://github.com/oscarpalmer/oui */
2
+
3
+ @use './mixins.scss' as mixins;
4
+
5
+ :where(oui-listbox, [oui-listbox-content]) {
6
+ display: flex;
7
+ flex-flow: column nowrap;
8
+ }
9
+
10
+ :where([oui-listbox-content]) {
11
+ flex: 1;
12
+ background-color: var(--oui-absolute);
13
+ border: 1px solid var(--oui-gray-3);
14
+ border-radius: 0.5em;
15
+ }
16
+
17
+ :where([oui-listbox-filter], [oui-listbox-header]) {
18
+ &::after {
19
+ content: '';
20
+ width: 100%;
21
+ height: 0.25em;
22
+ background-image: linear-gradient(to bottom, rgb(0 0 0 / 12.5%), transparent);
23
+ position: absolute;
24
+ bottom: -0.25em;
25
+ left: 0;
26
+ }
27
+ }
28
+
29
+ :where([oui-listbox-filter]) {
30
+ padding: 1em;
31
+ background-color: var(--oui-yellow-0);
32
+ border-radius: 0.5em 0.5em 0 0;
33
+ position: relative;
34
+ z-index: 2;
35
+ }
36
+
37
+ [oui-listbox-filter-input] {
38
+ :where(&) {
39
+ @include mixins.input;
40
+
41
+ --oui-background-value: var(--oui-absolute);
42
+
43
+ width: 100%;
44
+ padding: 0.5em 0.75em;
45
+ border-radius: 0.25em;
46
+ position: relative;
47
+ }
48
+
49
+ :where(&:not([disabled], [readonly], [aria-disabled='true' i]):focus) {
50
+ @include mixins.input-focus;
51
+ }
52
+
53
+ @media (hover: hover) {
54
+ :where(
55
+ &:not([disabled], [readonly], [aria-disabled='true' i], [aria-readonly='true' i]):hover
56
+ ) {
57
+ @include mixins.input-hover;
58
+ }
59
+ }
60
+
61
+ :where(&:is([disabled], [readonly], [aria-disabled='true' i], [aria-readonly='true' i])) {
62
+ --oui-foreground-percentage: 25%;
63
+
64
+ font-style: italic;
65
+ }
66
+
67
+ :where(&:is([disabled], [readonly], [aria-disabled='true' i], [aria-readonly='true' i]):focus) {
68
+ --oui-outline-percentage: 75%;
69
+ }
70
+
71
+ :where(&:is([disabled], [aria-disabled='true' i])) {
72
+ --oui-background-percentage: 50%;
73
+ }
74
+ }
75
+
76
+ :where([oui-listbox-header]) {
77
+ padding: 0.5em calc(0.75em / 0.75);
78
+ background-color: var(--oui-gray-0);
79
+ font-size: 0.75em;
80
+ font-weight: bold;
81
+ position: sticky;
82
+ top: 0;
83
+ z-index: 1;
84
+ }
85
+
86
+ :where([oui-listbox-hidden]) {
87
+ @include mixins.visually-hidden;
88
+
89
+ position: absolute;
90
+ }
91
+
92
+ :where([oui-listbox-list]) {
93
+ height: fit-content;
94
+ max-height: max(25vh, 32rem);
95
+ flex: 1;
96
+ overflow: auto;
97
+ border-radius: 0 0 0.5em 0.5em;
98
+ position: relative;
99
+ }
100
+
101
+ :where([oui-listbox-list-empty]) {
102
+ display: none;
103
+ }
104
+
105
+ :where([oui-listbox-message]) {
106
+ padding: 1em;
107
+ flex: 1;
108
+ align-content: center;
109
+ text-align: center;
110
+ font-style: italic;
111
+ }
112
+
113
+ [oui-listbox-option] {
114
+ :where(&) {
115
+ padding: 0.5em 0.75em;
116
+ outline: 4px solid transparent;
117
+ outline-offset: -4px;
118
+ line-height: 1.25;
119
+ cursor: pointer;
120
+ transition: 0.125s ease-in-out;
121
+ }
122
+
123
+ :where(&[aria-selected='true' i]) {
124
+ background-color: var(--oui-blue-3);
125
+ color: var(--oui-blue-9);
126
+ }
127
+
128
+ :where(oui-listbox[multiple] &[aria-selected='true' i])::before {
129
+ content: '✓';
130
+ margin: 0 0.75em 0 0;
131
+ }
132
+
133
+ :where(
134
+ oui-listbox:not([disabled], [readonly], [aria-disabled='true' i], [aria-readonly='true' i]) &
135
+ ) {
136
+ @media (hover: hover) {
137
+ :where(&:hover) {
138
+ background-color: var(--oui-blue-1);
139
+ color: var(--oui-blue-8);
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ :where(oui-listbox:focus-within [oui-listbox-option-active]) {
146
+ background-color: var(--oui-blue-1);
147
+ outline-color: var(--oui-blue-4);
148
+ color: var(--oui-blue-8);
149
+ }
@@ -1,4 +1,7 @@
1
- import type {OuiComboboxAutoCompleteType, OuiComboboxType} from './models.combobox';
1
+ import type {
2
+ OuiListboxAttributes,
3
+ OuiListboxAutoCompleteType,
4
+ } from '../internal/listbox/listbox.model';
2
5
 
3
6
  // #region Variables
4
7
 
@@ -6,57 +9,75 @@ export const COMBOBOX_TAGNAME = 'oui-combobox';
6
9
 
7
10
  export const COMBOBOX_ATTRIBUTE_ANCHOR = `${COMBOBOX_TAGNAME}-anchor`;
8
11
 
9
- export const COMBOBOX_ATTRIBUTE_CONTENT = `${COMBOBOX_TAGNAME}-content`;
12
+ const attributeContent = `${COMBOBOX_TAGNAME}-content`;
10
13
 
11
- export const COMBOBOX_ATTRIBUTE_FILTER = `${COMBOBOX_TAGNAME}-filter`;
14
+ const attributeFilter = `${COMBOBOX_TAGNAME}-filter`;
12
15
 
13
- export const COMBOBOX_ATTRIBUTE_FILTER_INPUT = `${COMBOBOX_ATTRIBUTE_FILTER}-input`;
16
+ const attributeFilterInput = `${attributeFilter}-input`;
14
17
 
15
- export const COMBOBOX_ATTRIBUTE_HEADER = `${COMBOBOX_TAGNAME}-header`;
18
+ const attributeGroup = `${COMBOBOX_TAGNAME}-group`;
16
19
 
17
- export const COMBOBOX_ATTRIBUTE_HIDDEN = `${COMBOBOX_TAGNAME}-hidden`;
20
+ const attributeHeader = `${COMBOBOX_TAGNAME}-header`;
18
21
 
19
- export const COMBOBOX_ATTRIBUTE_LIST = `${COMBOBOX_TAGNAME}-list`;
22
+ const attributeHidden = `${COMBOBOX_TAGNAME}-hidden`;
20
23
 
21
- export const COMBOBOX_ATTRIBUTE_MESSAGE = `${COMBOBOX_TAGNAME}-message`;
24
+ const attributeList = `${COMBOBOX_TAGNAME}-list`;
22
25
 
23
- export const COMBOBOX_ATTRIBUTE_OPTION = `${COMBOBOX_TAGNAME}-option`;
26
+ const attributeListEmpty = `${attributeList}-empty`;
24
27
 
25
- export const COMBOBOX_ATTRIBUTE_OPTION_ACTIVE = `${COMBOBOX_TAGNAME}-option-active`;
28
+ const attributeMessage = `${COMBOBOX_TAGNAME}-message`;
29
+
30
+ const attributeOption = `${COMBOBOX_TAGNAME}-option`;
31
+
32
+ const attributeOptionActive = `${COMBOBOX_TAGNAME}-option-active`;
26
33
 
27
34
  export const COMBOBOX_ATTRIBUTE_SELECTION = `${COMBOBOX_TAGNAME}-selection`;
28
35
 
29
36
  export const COMBOBOX_ATTRIBUTE_SELECTION_ITEM = `${COMBOBOX_TAGNAME}-selection-item`;
30
37
 
31
- export const COMBOBOX_AUTOCOMPLETES = new Set<OuiComboboxAutoCompleteType>([
38
+ export const COMBOBOX_AUTOCOMPLETES = new Set<OuiListboxAutoCompleteType>([
32
39
  'both',
33
40
  'inline',
34
41
  'list',
35
42
  'none',
36
43
  ]);
37
44
 
38
- export const COMBOBOX_KEYS_ACTIVATION = new Set([' ', 'Backspace', 'Delete', 'Enter']);
39
-
40
- export const COMBOBOX_KEYS_NAVIGATION = new Set(['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp']);
45
+ export const messageLabel = `<${COMBOBOX_TAGNAME}> must contain a <label> element for the <select> element`;
41
46
 
42
- export const COMBOBOX_KEYS_SELECTION = new Set(['ArrowRight', 'End']);
47
+ export const messageSelect = `<${COMBOBOX_TAGNAME}> must contain a valid <select> element`;
43
48
 
44
- export const COMBOBOX_MESSAGE_LABEL = `<${COMBOBOX_TAGNAME}> must contain a <label> element for the <select> element`;
49
+ export const selectorAnchor = `[${COMBOBOX_ATTRIBUTE_ANCHOR}]`;
45
50
 
46
- export const COMBOBOX_MESSAGE_SELECT = `<${COMBOBOX_TAGNAME}> must contain a valid <select> element`;
51
+ export const selectorContent = `[${attributeContent}]`;
47
52
 
48
- export const COMBOBOX_SELECTOR_ANCHOR = `[${COMBOBOX_ATTRIBUTE_ANCHOR}]`;
53
+ export const selectorOption = `[${attributeOption}]`;
49
54
 
50
- export const COMBOBOX_SELECTOR_SELECTION_ITEM = `[${COMBOBOX_ATTRIBUTE_SELECTION_ITEM}]`;
55
+ export const selectorSelectionItem = `[${COMBOBOX_ATTRIBUTE_SELECTION_ITEM}]`;
51
56
 
52
- export const COMBOBOX_SELECTOR_OPTION = `[${COMBOBOX_ATTRIBUTE_OPTION}]`;
57
+ export const COMBOBOX_ATTRIBUTES: OuiListboxAttributes = {
58
+ active: attributeOptionActive,
59
+ content: attributeContent,
60
+ empty: attributeListEmpty,
61
+ filter: attributeFilter,
62
+ group: attributeGroup,
63
+ header: attributeHeader,
64
+ hidden: attributeHidden,
65
+ input: attributeFilterInput,
66
+ list: attributeList,
67
+ message: attributeMessage,
68
+ option: attributeOption,
69
+ };
53
70
 
54
- export const COMBOBOX_TYPES = new Set<OuiComboboxType>(['autocomplete', 'select']);
71
+ export const COMBOBOX_MESSAGES = {
72
+ label: messageLabel,
73
+ select: messageSelect,
74
+ };
55
75
 
56
- export const comboboxScrollOptions: ScrollIntoViewOptions = {
57
- behavior: 'auto',
58
- block: 'nearest',
59
- inline: 'nearest',
76
+ export const COMBOBOX_SELECTORS = {
77
+ anchor: selectorAnchor,
78
+ content: selectorContent,
79
+ selectionItem: selectorSelectionItem,
80
+ option: selectorOption,
60
81
  };
61
82
 
62
83
  // #endregion