@oscarpalmer/oui 0.32.0 → 0.34.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 (59) hide show
  1. package/dist/css/combobox.css +93 -42
  2. package/dist/js/combobox/constants.combobox.d.mts +2 -0
  3. package/dist/js/combobox/constants.combobox.mjs +46 -0
  4. package/dist/js/combobox/data.combobox.d.mts +18 -0
  5. package/dist/js/combobox/data.combobox.mjs +2 -0
  6. package/dist/js/combobox/dom.combobox.d.mts +110 -0
  7. package/dist/js/combobox/dom.combobox.mjs +2 -0
  8. package/dist/js/combobox/event.combobox.d.mts +17 -0
  9. package/dist/js/combobox/event.combobox.mjs +2 -0
  10. package/dist/js/combobox/form.combobox.d.mts +6 -0
  11. package/dist/js/combobox/form.combobox.mjs +2 -0
  12. package/dist/js/combobox/i18n.combobox.d.mts +2 -0
  13. package/dist/js/combobox/i18n.combobox.mjs +2 -0
  14. package/dist/js/combobox/index.d.mts +2 -0
  15. package/dist/js/combobox/index.mjs +194 -0
  16. package/dist/js/combobox/misc.combobox.d.mts +14 -0
  17. package/dist/js/combobox/misc.combobox.mjs +2 -0
  18. package/dist/js/combobox/models.combobox.d.mts +2 -0
  19. package/dist/js/combobox/models.combobox.mjs +1 -0
  20. package/dist/js/combobox/state.combobox.d.mts +11 -0
  21. package/dist/js/combobox/state.combobox.mjs +2 -0
  22. package/dist/js/constants.combobox-DqNUGBLS.d.mts +243 -0
  23. package/dist/js/data.combobox-8RwBv5ey.mjs +1077 -0
  24. package/dist/js/dialog.mjs +3 -2
  25. package/dist/js/floatable/floatable.embedded.mjs +4 -4
  26. package/dist/js/focus-trap/focus-trap.embedded.mjs +3 -3
  27. package/dist/js/index.d.mts +3 -3
  28. package/dist/js/index.mjs +3 -2
  29. package/dist/js/internal/draggable.mjs +6 -6
  30. package/dist/js/internal/navigable.mjs +3 -3
  31. package/dist/js/menu.mjs +3 -2
  32. package/dist/js/movable/movable.embedded.mjs +3 -3
  33. package/dist/js/popover.mjs +1 -1
  34. package/dist/js/resizable/resizable.embedded.mjs +7 -7
  35. package/dist/js/sortable/sortable.embedded.mjs +7 -6
  36. package/dist/js/splitter.mjs +7 -13
  37. package/dist/js/tooltip.mjs +3 -3
  38. package/package.json +6 -6
  39. package/src/css/combobox.scss +98 -31
  40. package/src/js/combobox/constants.combobox.ts +62 -0
  41. package/src/js/combobox/data.combobox.ts +236 -0
  42. package/src/js/combobox/dom.combobox.ts +794 -0
  43. package/src/js/combobox/event.combobox.ts +454 -0
  44. package/src/js/combobox/form.combobox.ts +115 -0
  45. package/src/js/combobox/i18n.combobox.ts +179 -0
  46. package/src/js/combobox/index.ts +307 -0
  47. package/src/js/combobox/misc.combobox.ts +138 -0
  48. package/src/js/combobox/models.combobox.ts +94 -0
  49. package/src/js/combobox/state.combobox.ts +235 -0
  50. package/src/js/index.ts +1 -1
  51. package/src/js/internal/draggable.ts +3 -3
  52. package/src/js/internal/navigable.ts +10 -3
  53. package/src/js/movable/movable.embedded.ts +2 -2
  54. package/src/js/resizable/resizable.embedded.ts +3 -3
  55. package/src/js/sortable/sortable.embedded.ts +2 -2
  56. package/src/js/splitter.ts +2 -2
  57. package/dist/js/combobox.d.mts +0 -91
  58. package/dist/js/combobox.mjs +0 -855
  59. package/src/js/combobox.ts +0 -1634
@@ -0,0 +1,235 @@
1
+ import {setAria} from '@oscarpalmer/toretto/aria';
2
+ import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
3
+ import {getData} from '@oscarpalmer/toretto/data';
4
+ import {on} from '@oscarpalmer/toretto/event';
5
+ import {createEmbeddedFloatable} from '../floatable/floatable.embedded';
6
+ import {
7
+ COMBOBOX_MESSAGE_LABEL,
8
+ COMBOBOX_MESSAGE_SELECT,
9
+ COMBOBOX_TYPES,
10
+ } from './constants.combobox';
11
+ import {filterComboboxOptions} from './data.combobox';
12
+ import {
13
+ createComboboxAnchor,
14
+ createComboboxContent,
15
+ setComboboxDisabled,
16
+ setComboboxReadonly,
17
+ setComboboxRequired,
18
+ updateComboboxMessage,
19
+ updateComboboxPreview,
20
+ updateComboboxSelectionItems,
21
+ } from './dom.combobox';
22
+ import {onComboboxClickContent, onComboboxFocusout} from './event.combobox';
23
+ import {updateComboboxForm} from './form.combobox';
24
+ import type {OuiComboboxElement} from './index';
25
+ import {
26
+ generateComboboxIds,
27
+ getComboboxAttributeValue,
28
+ getComboboxAutoComplete,
29
+ getComboboxBooleanAria,
30
+ getComboboxId,
31
+ getComboboxVisibleOptions,
32
+ setComboboxLimit,
33
+ } from './misc.combobox';
34
+ import type {OuiComboboxState} from './models.combobox';
35
+
36
+ // #region Functions
37
+
38
+ export function attachCombobox(state: OuiComboboxState): void {
39
+ const {anchor, content, filter, label, listeners, select} = state;
40
+
41
+ label.element.htmlFor = filter.element.id;
42
+ label.element.id = `${state.id}_label`;
43
+
44
+ select.hidden = true;
45
+
46
+ select.insertAdjacentElement('afterend', anchor.container ?? anchor.element);
47
+ (anchor.container ?? anchor.element).insertAdjacentElement('afterend', content);
48
+
49
+ select.remove();
50
+
51
+ state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
52
+ position: 'below-start',
53
+ reusable: false,
54
+ });
55
+
56
+ state.floatable.update();
57
+
58
+ listeners.push(
59
+ on(state.host, 'focusout', () => {
60
+ setTimeout(() => {
61
+ onComboboxFocusout(state);
62
+ });
63
+ }),
64
+ on(content, 'click', event => {
65
+ onComboboxClickContent(event, content);
66
+ }),
67
+ on(content, 'toggle', () => {
68
+ setAria(anchor.element, 'expanded', state.floatable.open);
69
+
70
+ setAria(
71
+ filter.element,
72
+ 'activedescendant',
73
+ state.floatable.open ? state.active?.id : undefined,
74
+ );
75
+
76
+ if (state.floatable.open) {
77
+ filter.element.focus();
78
+ }
79
+ }),
80
+ );
81
+
82
+ comboboxStates.map.set(state.host, state);
83
+ comboboxStates.map.set(state.content, state);
84
+ comboboxStates.set.add(state);
85
+ }
86
+
87
+ export function createComboboxState(
88
+ element: OuiComboboxElement,
89
+ internals: ElementInternals,
90
+ ): OuiComboboxState {
91
+ const select = element.querySelector('select');
92
+
93
+ if (select == null || select.id === '') {
94
+ throw new Error(COMBOBOX_MESSAGE_SELECT);
95
+ }
96
+
97
+ const label = element.querySelector(`label[for="${select.id}"]`) as HTMLLabelElement;
98
+
99
+ if (label == null) {
100
+ throw new Error(COMBOBOX_MESSAGE_LABEL);
101
+ }
102
+
103
+ element.id = select.id;
104
+
105
+ setAttribute(element, 'name', select.name === '' ? select.id : select.name);
106
+
107
+ const disabled = getComboboxBooleanAria(element, 'disabled') || select.disabled;
108
+ const multiple = element.multiple || select.multiple;
109
+ const readonly = getComboboxBooleanAria(element, 'readonly');
110
+ const required = getComboboxBooleanAria(element, 'required') || select.required;
111
+
112
+ const id = getComboboxId();
113
+ const type = multiple
114
+ ? 'select'
115
+ : getComboboxAttributeValue(element, 'type', COMBOBOX_TYPES, 'select');
116
+
117
+ const autocomplete = getComboboxAutoComplete(element, type === 'select');
118
+
119
+ const labelId = label.id;
120
+
121
+ label.htmlFor = id;
122
+ label.id = `${id}_label`;
123
+
124
+ generateComboboxIds(id, select);
125
+
126
+ const anchor = createComboboxAnchor({autocomplete, id, label, multiple, select, type});
127
+ const content = createComboboxContent({autocomplete, id, label, multiple, select, type});
128
+
129
+ const values =
130
+ content.list.selected.length === 0
131
+ ? ['']
132
+ : content.list.selected.map(option => getData(option, 'value', false) ?? '');
133
+
134
+ const state: OuiComboboxState = {
135
+ autocomplete,
136
+ disabled,
137
+ id,
138
+ internals,
139
+ multiple,
140
+ readonly,
141
+ required,
142
+ select,
143
+ type,
144
+ anchor: {
145
+ container: anchor.container,
146
+ element: anchor.element,
147
+ },
148
+ active: content.list.selected[0],
149
+ content: content.element,
150
+ filter: {
151
+ element: content.filter?.element ?? (anchor.element as HTMLInputElement),
152
+ value: '',
153
+ },
154
+ floatable: undefined as never,
155
+ groups: content.list.groups,
156
+ host: element,
157
+ label: {
158
+ element: label,
159
+ id: labelId,
160
+ },
161
+ listeners: [],
162
+ localizations: {},
163
+ message: content.message,
164
+ name: element.name,
165
+ options: {
166
+ all: content.list.options,
167
+ selected: content.list.selected,
168
+ },
169
+ selection: anchor.selection,
170
+ values: {
171
+ current: values.slice(),
172
+ initial: values.slice(),
173
+ },
174
+ };
175
+
176
+ attachCombobox(state);
177
+
178
+ setComboboxDisabled(state, disabled);
179
+ setComboboxLimit('max', state, getAttribute(element, 'max'));
180
+ setComboboxLimit('min', state, getAttribute(element, 'min'));
181
+ setComboboxReadonly(state, readonly);
182
+ setComboboxRequired(state, required);
183
+
184
+ filterComboboxOptions(state);
185
+
186
+ if (content.list.selected.length > 0) {
187
+ updateComboboxPreview('set', state, content.list.selected[0]);
188
+ }
189
+
190
+ updateComboboxForm(state, state.values.current, false);
191
+ updateComboboxMessage(state, getComboboxVisibleOptions(state).length);
192
+ updateComboboxSelectionItems(state);
193
+
194
+ return state;
195
+ }
196
+
197
+ export function detachCombobox(state: OuiComboboxState): void {
198
+ const {content, floatable, host, label, select} = state;
199
+
200
+ const listeners = state.listeners.splice(0);
201
+ const {length} = listeners;
202
+
203
+ for (let index = 0; index < length; index += 1) {
204
+ listeners[index]();
205
+ }
206
+
207
+ floatable.destroy();
208
+
209
+ const anchor = state.anchor.container ?? state.anchor.element;
210
+
211
+ anchor.insertAdjacentElement('beforebegin', select);
212
+
213
+ anchor.remove();
214
+ content.remove();
215
+
216
+ label.element.htmlFor = select.id;
217
+ label.element.id = state.label.id;
218
+
219
+ select.hidden = false;
220
+
221
+ comboboxStates.map.delete(host);
222
+ comboboxStates.map.delete(content);
223
+ comboboxStates.set.delete(state);
224
+ }
225
+
226
+ // #endregion
227
+
228
+ // #region Variables
229
+
230
+ export const comboboxStates = {
231
+ map: new WeakMap<HTMLElement, OuiComboboxState>(),
232
+ set: new Set<OuiComboboxState>(),
233
+ };
234
+
235
+ // #endregion
package/src/js/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import './menu';
2
2
  import './toolbar';
3
3
 
4
- export * from './combobox';
4
+ export * from './combobox/index';
5
5
  export * from './dialog';
6
6
  export * from './floatable/floatable.standalone';
7
7
  export * from './focus-trap/focus-trap.standalone';
@@ -1,7 +1,7 @@
1
1
  import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import {clamp} from '@oscarpalmer/atoms/number';
3
3
  import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
4
- import {dispatch, getPosition, on} from '@oscarpalmer/toretto/event';
4
+ import {dispatch, getEventPosition, on} from '@oscarpalmer/toretto/event';
5
5
  import {findAncestor} from '@oscarpalmer/toretto/find';
6
6
  import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
7
7
  import {setStyle, setStyles, toggleStyles, type StyleToggler} from '@oscarpalmer/toretto/style';
@@ -392,7 +392,7 @@ function onPointerdown(event: MouseEvent | TouchEvent): void {
392
392
  const dragged = item.state.options.getDragged();
393
393
  const rectangle = dragged.getBoundingClientRect();
394
394
 
395
- const position = getPosition(event) ?? {x: 0, y: 0};
395
+ const position = getEventPosition(event) ?? {x: 0, y: 0};
396
396
 
397
397
  current = item;
398
398
 
@@ -442,7 +442,7 @@ function onPointermove(event: MouseEvent | TouchEvent): void {
442
442
  return;
443
443
  }
444
444
 
445
- const position = getPosition(event);
445
+ const position = getEventPosition(event);
446
446
 
447
447
  if (position == null) {
448
448
  return;
@@ -28,9 +28,16 @@ export class OuiNavigable {
28
28
  on(element, 'focusin', event => {
29
29
  onFocusin(this, event);
30
30
  }),
31
- on(element, 'keydown', event => {
32
- onKeydown(this, event);
33
- }),
31
+ on(
32
+ element,
33
+ 'keydown',
34
+ event => {
35
+ onKeydown(this, event);
36
+ },
37
+ {
38
+ passive: false,
39
+ },
40
+ ),
34
41
  ];
35
42
 
36
43
  this.orientation = getOrientation(element, options);
@@ -1,6 +1,6 @@
1
1
  import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import {setAttribute} from '@oscarpalmer/toretto/attribute';
3
- import {dispatch, getPosition, on} from '@oscarpalmer/toretto/event';
3
+ import {dispatch, getEventPosition, on} from '@oscarpalmer/toretto/event';
4
4
  import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
5
5
  import {getStyles, setStyle, setStyles} from '@oscarpalmer/toretto/style';
6
6
  import {attributable} from '../internal/attributable';
@@ -202,7 +202,7 @@ function onEnd(
202
202
  const dispatched = dispatch(state.element, 'movable:end', {
203
203
  detail: {
204
204
  from: {...globals.original},
205
- to: getPosition(event),
205
+ to: getEventPosition(event),
206
206
  },
207
207
  });
208
208
 
@@ -2,7 +2,7 @@ import {isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import type {EventPosition} from '@oscarpalmer/atoms/models';
3
3
  import {getNumber} from '@oscarpalmer/atoms/number';
4
4
  import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
5
- import {dispatch, getPosition, on} from '@oscarpalmer/toretto/event';
5
+ import {dispatch, getEventPosition, on} from '@oscarpalmer/toretto/event';
6
6
  import {findAncestor} from '@oscarpalmer/toretto/find';
7
7
  import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
8
8
  import {setStyles, toggleStyles, type StyleToggler} from '@oscarpalmer/toretto/style';
@@ -279,7 +279,7 @@ function onPointerdown(event: MouseEvent | TouchEvent): void {
279
279
  const handle = findAncestor(event, SELECTOR_HANDLE);
280
280
  const element = handle && findAncestor(handle, SELECTOR);
281
281
  const stored = element && items.get(element as HTMLElement);
282
- const position = getPosition(event);
282
+ const position = getEventPosition(event);
283
283
 
284
284
  if (stored == null || position == null) {
285
285
  return;
@@ -309,7 +309,7 @@ function onPointermove(event: MouseEvent | TouchEvent): void {
309
309
  return;
310
310
  }
311
311
 
312
- const {x, y} = getPosition(event) ?? {};
312
+ const {x, y} = getEventPosition(event) ?? {};
313
313
 
314
314
  if (x == null || y == null) {
315
315
  return;
@@ -1,7 +1,7 @@
1
1
  import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import {getAttribute, setAttribute, setAttributes} from '@oscarpalmer/toretto/attribute';
3
3
  import {createElement} from '@oscarpalmer/toretto/create';
4
- import {dispatch, getPosition} from '@oscarpalmer/toretto/event';
4
+ import {dispatch, getEventPosition} from '@oscarpalmer/toretto/event';
5
5
  import {findAncestor, getElementFromPosition} from '@oscarpalmer/toretto/find';
6
6
  import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
7
7
  import {setStyle} from '@oscarpalmer/toretto/style';
@@ -214,7 +214,7 @@ function onEnd(
214
214
  ): void {
215
215
  const {origin} = item.state;
216
216
 
217
- const position = getPosition(event) ?? {x: 0, y: 0};
217
+ const position = getEventPosition(event) ?? {x: 0, y: 0};
218
218
 
219
219
  const target = getElementFromPosition(position)[0];
220
220
 
@@ -3,7 +3,7 @@ import {round} from '@oscarpalmer/atoms/math';
3
3
  import {clamp} from '@oscarpalmer/atoms/number';
4
4
  import {setAria} from '@oscarpalmer/toretto/aria';
5
5
  import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
6
- import {getPosition, on} from '@oscarpalmer/toretto/event';
6
+ import {getEventPosition, on} from '@oscarpalmer/toretto/event';
7
7
  import {findAncestor} from '@oscarpalmer/toretto/find';
8
8
  import {setStyle, toggleStyles} from '@oscarpalmer/toretto/style';
9
9
  import supportsTouch from '@oscarpalmer/toretto/touch';
@@ -464,7 +464,7 @@ function onPointermove(event: MouseEvent): void {
464
464
  return;
465
465
  }
466
466
 
467
- const position = getPosition(event);
467
+ const position = getEventPosition(event);
468
468
 
469
469
  if (position != null) {
470
470
  splitter.values.now.current = getSize(
@@ -1,91 +0,0 @@
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
- /**
12
- * Is the combobox disabled?
13
- */
14
- get disabled(): boolean;
15
- /**
16
- * Should the combobox be disabled?
17
- */
18
- set disabled(value: boolean);
19
- /**
20
- * Maximum number of selected options
21
- */
22
- get max(): number | undefined;
23
- /**
24
- * Set the maximum number of selected options
25
- */
26
- set max(value: number);
27
- /**
28
- * Minimum number of selected options
29
- */
30
- get min(): number | undefined;
31
- /**
32
- * Set the minimum number of selected options
33
- */
34
- set min(value: number);
35
- /**
36
- * Does the combobox allow multiple values?
37
- */
38
- get multiple(): boolean;
39
- /**
40
- * Name of the combobox
41
- */
42
- get name(): string;
43
- /**
44
- * Is the combobox readonly?
45
- */
46
- get readonly(): boolean;
47
- /**
48
- * Should the combobox be readonly?
49
- */
50
- set readonly(value: boolean);
51
- /**
52
- * Is the combobox required?
53
- */
54
- get required(): boolean;
55
- /**
56
- * Should the combobox be required?
57
- */
58
- set required(value: boolean);
59
- /**
60
- * Selected options
61
- */
62
- get selected(): HTMLElement[];
63
- /**
64
- * Type of combobox
65
- */
66
- get type(): OuiComboboxType;
67
- /**
68
- * Current value
69
- */
70
- get value(): string;
71
- /**
72
- * Set the value
73
- */
74
- set value(value: unknown);
75
- /**
76
- * Current values
77
- */
78
- get values(): string[];
79
- /**
80
- * Set the values
81
- */
82
- set values(value: unknown[]);
83
- constructor();
84
- attributeChangedCallback(name: string, _: string | null, value: string | null): void;
85
- clear(): void;
86
- formResetCallback(): void;
87
- }
88
- type OuiComboboxType = 'autocomplete' | 'select';
89
- declare const TAGNAME = "oui-combobox";
90
- //#endregion
91
- export { OuiComboboxElement };