@oscarpalmer/oui 0.35.0 → 0.37.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 (128) hide show
  1. package/dist/css/badge.css +18 -15
  2. package/dist/css/button.css +35 -31
  3. package/dist/css/color.css +20 -20
  4. package/dist/css/combobox.css +134 -35
  5. package/dist/css/content.css +29 -10
  6. package/dist/css/floatable.css +10 -10
  7. package/dist/css/form.css +393 -129
  8. package/dist/css/listbox.css +176 -0
  9. package/dist/css/message.css +13 -10
  10. package/dist/css/reset.css +29 -88
  11. package/dist/css/splitter.css +2 -2
  12. package/dist/css/tabs.css +103 -0
  13. package/dist/js/combobox/constants.combobox.d.mts +2 -2
  14. package/dist/js/combobox/constants.combobox.mjs +40 -33
  15. package/dist/js/combobox/dom.combobox.d.mts +8 -91
  16. package/dist/js/combobox/dom.combobox.mjs +204 -2
  17. package/dist/js/combobox/event.combobox.d.mts +14 -8
  18. package/dist/js/combobox/event.combobox.mjs +2 -2
  19. package/dist/js/combobox/index.d.mts +1 -1
  20. package/dist/js/combobox/index.mjs +20 -18
  21. package/dist/js/combobox/misc.combobox.d.mts +2 -2
  22. package/dist/js/combobox/misc.combobox.mjs +2 -2
  23. package/dist/js/combobox/models.combobox.d.mts +44 -2
  24. package/dist/js/combobox/state.combobox.d.mts +9 -10
  25. package/dist/js/combobox/state.combobox.mjs +2 -2
  26. package/dist/js/constants.combobox-DmRAMC64.d.mts +26 -0
  27. package/dist/js/constants.listbox-DRsXUxd3.d.mts +14 -0
  28. package/dist/js/focus-trap/focus-trap.embedded.mjs +6 -10
  29. package/dist/js/index-EfkUSals.d.mts +139 -0
  30. package/dist/js/index.d.mts +5 -2
  31. package/dist/js/index.mjs +5 -2
  32. package/dist/js/internal/attributable.mjs +4 -3
  33. package/dist/js/internal/draggable.mjs +16 -22
  34. package/dist/js/internal/listbox/listbox.constants.d.mts +11 -0
  35. package/dist/js/internal/listbox/listbox.constants.mjs +23 -0
  36. package/dist/js/internal/listbox/listbox.data.d.mts +20 -0
  37. package/dist/js/internal/listbox/listbox.data.mjs +2 -0
  38. package/dist/js/internal/listbox/listbox.dom.d.mts +66 -0
  39. package/dist/js/internal/listbox/listbox.dom.mjs +2 -0
  40. package/dist/js/internal/listbox/listbox.event.d.mts +5 -0
  41. package/dist/js/internal/listbox/listbox.event.mjs +63 -0
  42. package/dist/js/internal/listbox/listbox.form.d.mts +6 -0
  43. package/dist/js/internal/listbox/listbox.form.mjs +52 -0
  44. package/dist/js/internal/listbox/listbox.misc.d.mts +12 -0
  45. package/dist/js/internal/listbox/listbox.misc.mjs +2 -0
  46. package/dist/js/internal/listbox/listbox.model.d.mts +2 -0
  47. package/dist/js/internal/listbox/listbox.model.mjs +1 -0
  48. package/dist/js/internal/listbox/listbox.state.d.mts +7 -0
  49. package/dist/js/internal/listbox/listbox.state.mjs +40 -0
  50. package/dist/js/internal/misc.d.mts +6 -0
  51. package/dist/js/internal/misc.mjs +13 -0
  52. package/dist/js/internal/navigable.d.mts +11 -5
  53. package/dist/js/internal/navigable.mjs +44 -18
  54. package/dist/js/l10n-Bn-7RcwR.d.mts +20 -0
  55. package/dist/js/l10n.d.mts +2 -20
  56. package/dist/js/listbox/constants.listbox.d.mts +2 -0
  57. package/dist/js/listbox/constants.listbox.mjs +40 -0
  58. package/dist/js/listbox/event.listbox.d.mts +8 -0
  59. package/dist/js/listbox/event.listbox.mjs +2 -0
  60. package/dist/js/listbox/index.d.mts +116 -0
  61. package/dist/js/listbox/index.mjs +189 -0
  62. package/dist/js/listbox/misc.listbox.d.mts +2 -0
  63. package/dist/js/listbox/misc.listbox.mjs +2 -0
  64. package/dist/js/listbox/state.listbox.d.mts +11 -0
  65. package/dist/js/listbox/state.listbox.mjs +2 -0
  66. package/dist/js/listbox.data-BWd0PCy7.mjs +424 -0
  67. package/dist/js/listbox.model-CRReGOmu.d.mts +125 -0
  68. package/dist/js/misc.listbox-BI-XsTOf.mjs +197 -0
  69. package/dist/js/misc.listbox-gnoJhrTr.d.mts +20 -0
  70. package/dist/js/movable/movable.embedded.mjs +3 -6
  71. package/dist/js/resizable/resizable.embedded.mjs +16 -26
  72. package/dist/js/sortable/sortable.embedded.mjs +4 -2
  73. package/dist/js/splitter.mjs +15 -12
  74. package/dist/js/state.combobox-4xq0FPCF.mjs +365 -0
  75. package/dist/js/tabs.d.mts +25 -0
  76. package/dist/js/tabs.mjs +240 -0
  77. package/dist/js/tooltip.mjs +13 -12
  78. package/package.json +7 -3
  79. package/src/css/badge.scss +6 -18
  80. package/src/css/button.scss +6 -45
  81. package/src/css/color.scss +20 -20
  82. package/src/css/combobox.scss +21 -52
  83. package/src/css/content.scss +30 -3
  84. package/src/css/floatable.scss +17 -17
  85. package/src/css/form.scss +84 -72
  86. package/src/css/listbox.scss +120 -0
  87. package/src/css/message.scss +13 -28
  88. package/src/css/mixins.scss +253 -28
  89. package/src/css/reset.scss +37 -86
  90. package/src/css/splitter.scss +2 -2
  91. package/src/css/tabs.scss +85 -0
  92. package/src/js/combobox/constants.combobox.ts +46 -25
  93. package/src/js/combobox/dom.combobox.ts +36 -454
  94. package/src/js/combobox/event.combobox.ts +129 -218
  95. package/src/js/combobox/index.ts +37 -30
  96. package/src/js/combobox/misc.combobox.ts +30 -114
  97. package/src/js/combobox/models.combobox.ts +7 -47
  98. package/src/js/combobox/state.combobox.ts +126 -127
  99. package/src/js/focus-trap/focus-trap.embedded.ts +11 -19
  100. package/src/js/index.ts +2 -0
  101. package/src/js/internal/draggable.ts +29 -33
  102. package/src/js/internal/listbox/listbox.constants.ts +25 -0
  103. package/src/js/{combobox/data.combobox.ts → internal/listbox/listbox.data.ts} +94 -43
  104. package/src/js/internal/listbox/listbox.dom.ts +445 -0
  105. package/src/js/internal/listbox/listbox.event.ts +129 -0
  106. package/src/js/{combobox/form.combobox.ts → internal/listbox/listbox.form.ts} +13 -14
  107. package/src/js/internal/listbox/listbox.misc.ts +135 -0
  108. package/src/js/internal/listbox/listbox.model.ts +169 -0
  109. package/src/js/internal/listbox/listbox.state.ts +82 -0
  110. package/src/js/internal/misc.ts +27 -0
  111. package/src/js/internal/navigable.ts +80 -23
  112. package/src/js/l10n.ts +8 -8
  113. package/src/js/listbox/constants.listbox.ts +61 -0
  114. package/src/js/listbox/event.listbox.ts +106 -0
  115. package/src/js/listbox/index.ts +302 -0
  116. package/src/js/listbox/misc.listbox.ts +70 -0
  117. package/src/js/listbox/state.listbox.ts +196 -0
  118. package/src/js/movable/movable.embedded.ts +11 -13
  119. package/src/js/resizable/resizable.embedded.ts +25 -39
  120. package/src/js/splitter.ts +21 -18
  121. package/src/js/tabs.ts +447 -0
  122. package/src/js/tooltip.ts +20 -17
  123. package/dist/js/combobox/data.combobox.d.mts +0 -18
  124. package/dist/js/combobox/data.combobox.mjs +0 -2
  125. package/dist/js/combobox/form.combobox.d.mts +0 -6
  126. package/dist/js/combobox/form.combobox.mjs +0 -2
  127. package/dist/js/constants.combobox-h_kyDegw.d.mts +0 -248
  128. package/dist/js/data.combobox-B8HMkDOh.mjs +0 -1030
@@ -1,70 +1,28 @@
1
- import {getAria} from '@oscarpalmer/toretto/aria';
2
- import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
3
- import type {AnyAriaBooleanAttribute} from '@oscarpalmer/toretto/models';
4
- import type {OuiComboboxElement} from '.';
5
- import {updateLocalizations} from '../l10n';
6
- import {COMBOBOX_AUTOCOMPLETES} from './constants.combobox';
7
- import {
8
- updateComboboxAnchor,
9
- updateComboboxMessage,
10
- updateComboboxSelectionItems,
11
- } from './dom.combobox';
12
- import {onComboboxFilter} from './event.combobox';
13
- import {updateComboboxValidity} from './form.combobox';
1
+ import {updateListboxMessage, updateListboxValueLabel} from '../internal/listbox/listbox.dom';
14
2
  import type {
15
- OuiComboboxAutoComplete,
16
- OuiComboboxAutoCompleteType,
17
- OuiComboboxLocalizations,
18
- OuiComboboxState,
19
- } from './models.combobox';
3
+ OuiListboxAutoComplete,
4
+ OuiListboxAutoCompleteType,
5
+ OuiListboxLocalizations,
6
+ } from '../internal/listbox/listbox.model';
7
+ import {getAttributeValue} from '../internal/misc';
8
+ import {OuiLocalizations, updateLocalizations} from '../l10n';
9
+ import {COMBOBOX_AUTOCOMPLETES} from './constants.combobox';
10
+ import {updateComboboxSelectionItems} from './dom.combobox';
11
+ import type {OuiComboboxElement} from './index';
20
12
  import {comboboxLocalizations, comboboxStates} from './state.combobox';
21
13
 
22
14
  // #region Functions
23
15
 
24
- export function generateComboboxIds(prefix: string, select: HTMLSelectElement): void {
25
- const elements = [...select.children].flatMap(child =>
26
- child instanceof HTMLOptGroupElement
27
- ? [
28
- child,
29
- ...[...child.children].filter(groupChild => groupChild instanceof HTMLOptionElement),
30
- ]
31
- : child instanceof HTMLOptionElement
32
- ? [child]
33
- : [],
34
- );
35
-
36
- const {length} = elements;
37
-
38
- for (let index = 0; index < length; index += 1) {
39
- const element = elements[index];
40
-
41
- if (element.id === '') {
42
- element.id = `${prefix}${element instanceof HTMLOptGroupElement ? '_group_' : '_option_'}${index}`;
43
- }
44
- }
45
- }
46
-
47
- export function getComboboxAttributeValue<Value>(
48
- element: HTMLElement,
49
- attribute: string,
50
- possible: Set<Value>,
51
- defaultValue: Value,
52
- ): Value {
53
- const value = getAttribute(element, attribute);
54
-
55
- return possible.has(value as Value) ? (value as Value) : defaultValue;
56
- }
57
-
58
16
  export function getComboboxAutoComplete(
59
17
  element: OuiComboboxElement,
60
18
  select: boolean,
61
- ): OuiComboboxAutoComplete {
62
- let value: OuiComboboxAutoCompleteType;
19
+ ): OuiListboxAutoComplete {
20
+ let value: OuiListboxAutoCompleteType;
63
21
 
64
22
  if (select) {
65
23
  value = 'list';
66
24
  } else {
67
- value = getComboboxAttributeValue(element, 'autocomplete', COMBOBOX_AUTOCOMPLETES, 'none');
25
+ value = getAttributeValue(element, 'autocomplete', COMBOBOX_AUTOCOMPLETES, 'none');
68
26
  }
69
27
 
70
28
  return {
@@ -76,67 +34,16 @@ export function getComboboxAutoComplete(
76
34
  };
77
35
  }
78
36
 
79
- export function getComboboxBooleanAria(
80
- element: HTMLElement,
81
- attribute: AnyAriaBooleanAttribute,
82
- ): boolean {
83
- const value = getAria(element, attribute);
84
-
85
- return value === true || value === 'true';
86
- }
87
-
88
37
  export function getComboboxId(): string {
89
38
  index += 1;
90
39
 
91
40
  return `oui_combobox_${index}`;
92
41
  }
93
42
 
94
- export function getComboboxVisibleOptions(state: OuiComboboxState): HTMLElement[] {
95
- return state.options.all.filter(option => !option.hidden);
96
- }
97
-
98
43
  export function hasTextSelection(element: HTMLElement): element is HTMLInputElement {
99
44
  return element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
100
45
  }
101
46
 
102
- export function setComboboxFilter(state: OuiComboboxState, value: unknown): void {
103
- if (typeof value !== 'string') {
104
- return;
105
- }
106
-
107
- const type: InputEvent['inputType'] =
108
- value.length < state.filter.element.value.length ? 'deleteContentBackward' : 'insertText';
109
-
110
- state.filter.element.value = value;
111
-
112
- onComboboxFilter(type, state);
113
- }
114
-
115
- export function setComboboxLimit(
116
- type: 'max' | 'min',
117
- state: OuiComboboxState,
118
- value?: unknown,
119
- ): void {
120
- let actual = value;
121
-
122
- if (typeof actual === 'string') {
123
- actual = Number.parseInt(actual, 10);
124
- }
125
-
126
- const updateLimit =
127
- typeof actual === 'number' && !Number.isNaN(actual) && actual >= 0
128
- ? actual !== state[type]
129
- : actual == null;
130
-
131
- if (updateLimit) {
132
- state[type] = actual as number | undefined;
133
-
134
- setAttribute(state.host, type, actual);
135
-
136
- updateComboboxValidity(state);
137
- }
138
- }
139
-
140
47
  /**
141
48
  * Set combobox localizations for a specific language
142
49
  *
@@ -145,7 +52,7 @@ export function setComboboxLimit(
145
52
  */
146
53
  export function setComboboxLocalizations(
147
54
  language: string,
148
- localizations: Partial<OuiComboboxLocalizations>,
55
+ localizations: Partial<OuiListboxLocalizations>,
149
56
  ): void;
150
57
 
151
58
  /**
@@ -154,25 +61,34 @@ export function setComboboxLocalizations(
154
61
  * @param localizations Localizations for one or more languages
155
62
  */
156
63
  export function setComboboxLocalizations(
157
- localizations: Record<string, Partial<OuiComboboxLocalizations>>,
64
+ localizations: Record<string, Partial<OuiListboxLocalizations>>,
158
65
  ): void;
159
66
 
160
67
  export function setComboboxLocalizations(
161
- first: string | Record<string, Partial<OuiComboboxLocalizations>>,
162
- second?: Partial<OuiComboboxLocalizations>,
68
+ first: string | Record<string, Partial<OuiListboxLocalizations>>,
69
+ second?: Partial<OuiListboxLocalizations>,
70
+ ): void {
71
+ updateComboboxLocalizations(comboboxLocalizations, comboboxLocalizations.values, first, second);
72
+ }
73
+
74
+ export function updateComboboxLocalizations(
75
+ instance: OuiLocalizations<OuiListboxLocalizations>,
76
+ destination: Record<string, Partial<OuiListboxLocalizations>>,
77
+ first: string | Record<string, Partial<OuiListboxLocalizations>>,
78
+ second?: Partial<OuiListboxLocalizations>,
163
79
  ): void {
164
80
  updateLocalizations(
165
- comboboxLocalizations,
81
+ instance,
166
82
  typeof first === 'string'
167
83
  ? {
168
84
  [first]: second!,
169
85
  }
170
86
  : first,
171
- comboboxLocalizations.values,
87
+ destination,
172
88
  () => {
173
89
  for (const state of comboboxStates.set) {
174
- updateComboboxAnchor(state);
175
- updateComboboxMessage(state, getComboboxVisibleOptions(state).length);
90
+ updateListboxMessage(state);
91
+ updateListboxValueLabel(state);
176
92
  updateComboboxSelectionItems(state);
177
93
  }
178
94
  },
@@ -1,18 +1,13 @@
1
- import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
2
1
  import type {OuiFloatable} from '../floatable/floatable.embedded';
3
- import type {OuiComboboxElement} from './index';
2
+ import type {OuiListboxState, OuiListboxType} from '../internal/listbox/listbox.model';
3
+
4
+ // #region Types
4
5
 
5
6
  export type OuiComboboxAnchor = {
6
7
  container?: HTMLElement;
7
8
  element: HTMLElement;
8
9
  };
9
10
 
10
- export type OuiComboboxAutoComplete = Record<OuiComboboxAutoCompleteType, boolean> & {
11
- value: OuiComboboxAutoCompleteType;
12
- };
13
-
14
- export type OuiComboboxAutoCompleteType = 'both' | 'inline' | 'list' | 'none';
15
-
16
11
  export type OuiComboboxFilter = {
17
12
  element: HTMLInputElement;
18
13
  value: string;
@@ -29,24 +24,11 @@ export type OuiComboboxLabel = {
29
24
  id: string;
30
25
  };
31
26
 
32
- export type OuiComboboxLocalizations = {
33
- empty: string | (() => string);
34
- input: string | (() => string);
35
- invalid: string | (() => string);
36
- max: string | ((max: number) => string);
37
- min: string | ((min: number) => string);
38
- remove: string | ((label: string, value: string) => string);
39
- results: string | ((amount: number) => string);
40
- selected: string | ((amount: number) => string);
41
- };
42
-
43
27
  export type OuiComboboxOptions = {
44
28
  all: HTMLElement[];
45
29
  selected: HTMLElement[];
46
30
  };
47
31
 
48
- export type OuiComboboxPreview = 'none' | 'set' | 'typed';
49
-
50
32
  export type OuiComboboxSelection = {
51
33
  container: HTMLElement;
52
34
  items: OuiComboboxSelectionItem[];
@@ -58,37 +40,15 @@ export type OuiComboboxSelectionItem = {
58
40
  };
59
41
 
60
42
  export type OuiComboboxState = {
61
- active?: HTMLElement;
62
43
  anchor: OuiComboboxAnchor;
63
- autocomplete: OuiComboboxAutoComplete;
64
- content: HTMLElement;
65
- disabled: boolean;
66
- filter: OuiComboboxFilter;
67
44
  floatable: OuiFloatable;
68
- groups?: OuiComboboxGroup[];
69
- host: OuiComboboxElement;
70
- id: string;
71
- internals: ElementInternals;
72
- label: OuiComboboxLabel;
73
- listeners: RemovableEventListener[];
74
- localizations: Record<string, Partial<OuiComboboxLocalizations>>;
75
- max?: number;
76
- min?: number;
77
- message: HTMLElement;
78
- name: string;
79
- multiple: boolean;
80
- options: OuiComboboxOptions;
81
- readonly: boolean;
82
- required: boolean;
83
- select: HTMLSelectElement;
84
45
  selection?: OuiComboboxSelection;
85
- type: OuiComboboxType;
86
- values: OuiComboboxValues;
87
- };
88
-
89
- export type OuiComboboxType = 'autocomplete' | 'select';
46
+ type: OuiListboxType;
47
+ } & OuiListboxState;
90
48
 
91
49
  export type OuiComboboxValues = {
92
50
  current: string[];
93
51
  initial: string[];
94
52
  };
53
+
54
+ // #endregion
@@ -1,90 +1,41 @@
1
+ import {noop} from '@oscarpalmer/atoms/function';
1
2
  import {setAria} from '@oscarpalmer/toretto/aria';
2
- import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
3
+ import {setAttributes} from '@oscarpalmer/toretto/attribute';
3
4
  import {getData} from '@oscarpalmer/toretto/data';
4
5
  import {on} from '@oscarpalmer/toretto/event';
5
6
  import {createEmbeddedFloatable} from '../floatable/floatable.embedded';
7
+ import {LISTBOX_TYPES} from '../internal/listbox/listbox.constants';
8
+ import {generateListboxIds} from '../internal/listbox/listbox.misc';
9
+ import type {
10
+ OuiListboxCallbacks,
11
+ OuiListboxLocalizations,
12
+ OuiListboxStates,
13
+ } from '../internal/listbox/listbox.model';
14
+ import {initializeListboxState} from '../internal/listbox/listbox.state';
15
+ import {getAttributeValue, getBooleanAriaValue} from '../internal/misc';
6
16
  import {getLocalizations} from '../l10n';
17
+ import {COMBOBOX_ATTRIBUTES, COMBOBOX_MESSAGES} from './constants.combobox';
18
+ import {createComboboxAnchor, createComboboxContent} from './dom.combobox';
7
19
  import {
8
- COMBOBOX_MESSAGE_LABEL,
9
- COMBOBOX_MESSAGE_SELECT,
10
- COMBOBOX_TYPES,
11
- } from './constants.combobox';
12
- import {filterComboboxOptions} from './data.combobox';
13
- import {
14
- createComboboxAnchor,
15
- createComboboxContent,
16
- setComboboxDisabled,
17
- setComboboxReadonly,
18
- setComboboxRequired,
19
- updateComboboxMessage,
20
- updateComboboxPreview,
21
- updateComboboxSelectionItems,
22
- } from './dom.combobox';
23
- import {onComboboxClickContent, onComboboxFocusout} from './event.combobox';
24
- import {updateComboboxForm} from './form.combobox';
20
+ onComboboxActivation,
21
+ onComboboxApply,
22
+ onComboboxEnter,
23
+ onComboboxEscape,
24
+ onComboboxFilter,
25
+ onComboboxNavigation,
26
+ onComboboxPreview,
27
+ onComboboxSelection,
28
+ onComboboxTab,
29
+ onComboboxToggle,
30
+ preventComboboxActivation,
31
+ skipComboboxNavigation,
32
+ } from './event.combobox';
25
33
  import type {OuiComboboxElement} from './index';
26
- import {
27
- generateComboboxIds,
28
- getComboboxAttributeValue,
29
- getComboboxAutoComplete,
30
- getComboboxBooleanAria,
31
- getComboboxId,
32
- getComboboxVisibleOptions,
33
- setComboboxLimit,
34
- } from './misc.combobox';
35
- import type {OuiComboboxLocalizations, OuiComboboxState} from './models.combobox';
34
+ import {getComboboxAutoComplete, getComboboxId} from './misc.combobox';
35
+ import type {OuiComboboxState} from './models.combobox';
36
36
 
37
37
  // #region Functions
38
38
 
39
- export function attachCombobox(state: OuiComboboxState): void {
40
- const {anchor, content, filter, label, listeners, select} = state;
41
-
42
- label.element.htmlFor = filter.element.id;
43
- label.element.id = `${state.id}_label`;
44
-
45
- select.hidden = true;
46
-
47
- select.insertAdjacentElement('afterend', anchor.container ?? anchor.element);
48
- (anchor.container ?? anchor.element).insertAdjacentElement('afterend', content);
49
-
50
- select.remove();
51
-
52
- state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
53
- position: 'below-start',
54
- reusable: false,
55
- });
56
-
57
- state.floatable.update();
58
-
59
- listeners.push(
60
- on(state.host, 'focusout', () => {
61
- setTimeout(() => {
62
- onComboboxFocusout(state);
63
- });
64
- }),
65
- on(content, 'click', event => {
66
- onComboboxClickContent(event, content);
67
- }),
68
- on(content, 'toggle', () => {
69
- setAria(anchor.element, 'expanded', state.floatable.open);
70
-
71
- setAria(
72
- filter.element,
73
- 'activedescendant',
74
- state.floatable.open ? state.active?.id : undefined,
75
- );
76
-
77
- if (state.floatable.open) {
78
- filter.element.focus();
79
- }
80
- }),
81
- );
82
-
83
- comboboxStates.map.set(state.host, state);
84
- comboboxStates.map.set(state.content, state);
85
- comboboxStates.set.add(state);
86
- }
87
-
88
39
  export function createComboboxState(
89
40
  element: OuiComboboxElement,
90
41
  internals: ElementInternals,
@@ -92,45 +43,58 @@ export function createComboboxState(
92
43
  const select = element.querySelector('select');
93
44
 
94
45
  if (select == null || select.id === '') {
95
- throw new Error(COMBOBOX_MESSAGE_SELECT);
46
+ throw new Error(COMBOBOX_MESSAGES.select);
96
47
  }
97
48
 
98
49
  const label = element.querySelector(`label[for="${select.id}"]`) as HTMLLabelElement;
99
50
 
100
51
  if (label == null) {
101
- throw new Error(COMBOBOX_MESSAGE_LABEL);
52
+ throw new Error(COMBOBOX_MESSAGES.label);
102
53
  }
103
54
 
104
55
  element.id = select.id;
105
56
 
106
- setAttribute(element, 'name', select.name === '' ? select.id : select.name);
107
-
108
- const disabled = getComboboxBooleanAria(element, 'disabled') || select.disabled;
57
+ const disabled = getBooleanAriaValue(element, 'disabled') || select.disabled;
109
58
  const multiple = element.multiple || select.multiple;
110
- const readonly = getComboboxBooleanAria(element, 'readonly');
111
- const required = getComboboxBooleanAria(element, 'required') || select.required;
59
+ const readonly = getBooleanAriaValue(element, 'readonly');
60
+ const required = getBooleanAriaValue(element, 'required') || select.required;
112
61
 
113
62
  const id = getComboboxId();
114
- const type = multiple
115
- ? 'select'
116
- : getComboboxAttributeValue(element, 'type', COMBOBOX_TYPES, 'select');
63
+ const type = multiple ? 'select' : getAttributeValue(element, 'type', LISTBOX_TYPES, 'select');
117
64
 
118
65
  const autocomplete = getComboboxAutoComplete(element, type === 'select');
119
66
 
67
+ setAttributes(element, {
68
+ autocomplete: autocomplete.value,
69
+ name: select.name === '' ? select.id : select.name,
70
+ });
71
+
120
72
  const labelId = label.id;
121
73
 
122
74
  label.htmlFor = id;
123
75
  label.id = `${id}_label`;
124
76
 
125
- generateComboboxIds(id, select);
77
+ generateListboxIds(id, select);
126
78
 
127
- const anchor = createComboboxAnchor({autocomplete, id, label, multiple, select, type});
128
- const content = createComboboxContent({autocomplete, id, label, multiple, select, type});
79
+ const parameters = {
80
+ autocomplete,
81
+ element,
82
+ id,
83
+ label,
84
+ multiple,
85
+ select,
86
+ type,
87
+ attributes: COMBOBOX_ATTRIBUTES,
88
+ listbox: false,
89
+ };
90
+
91
+ const anchor = createComboboxAnchor(parameters);
92
+ const content = createComboboxContent(parameters);
93
+
94
+ const {selected} = content.list.options;
129
95
 
130
96
  const values =
131
- content.list.selected.length === 0
132
- ? ['']
133
- : content.list.selected.map(option => getData(option, 'value', false) ?? '');
97
+ selected.length === 0 ? [''] : selected.map(option => getData(option, 'value', false) ?? '');
134
98
 
135
99
  const state: OuiComboboxState = {
136
100
  autocomplete,
@@ -142,12 +106,15 @@ export function createComboboxState(
142
106
  required,
143
107
  select,
144
108
  type,
109
+ active: selected[0],
145
110
  anchor: {
146
111
  container: anchor.container,
147
112
  element: anchor.element,
148
113
  },
149
- active: content.list.selected[0],
114
+ attributes: COMBOBOX_ATTRIBUTES,
115
+ callbacks: comboboxCallbacks as OuiComboboxState['callbacks'],
150
116
  content: content.element,
117
+ elements: [element, anchor.element, content.filter?.element],
151
118
  filter: {
152
119
  element: content.filter?.element ?? (anchor.element as HTMLInputElement),
153
120
  value: '',
@@ -159,51 +126,66 @@ export function createComboboxState(
159
126
  element: label,
160
127
  id: labelId,
161
128
  },
129
+ list: content.list.element,
130
+ listbox: false,
162
131
  listeners: [],
163
- localizations: {},
132
+ localizations: {
133
+ component: {},
134
+ global: comboboxLocalizations,
135
+ },
164
136
  message: content.message,
165
137
  name: element.name,
166
138
  options: {
167
- all: content.list.options,
168
- selected: content.list.selected,
139
+ selected,
140
+ all: content.list.options.all,
169
141
  },
170
142
  selection: anchor.selection,
171
143
  values: {
172
144
  current: values.slice(),
173
145
  initial: values.slice(),
146
+ label: content.value,
174
147
  },
175
148
  };
176
149
 
177
- attachCombobox(state);
150
+ return initializeListboxState(state, comboboxStates, onAttachCombobox);
151
+ }
178
152
 
179
- setComboboxDisabled(state, disabled);
180
- setComboboxLimit('max', state, getAttribute(element, 'max'));
181
- setComboboxLimit('min', state, getAttribute(element, 'min'));
182
- setComboboxReadonly(state, readonly);
183
- setComboboxRequired(state, required);
153
+ export function onAttachCombobox(state: OuiComboboxState): void {
154
+ const {anchor, content, filter, select} = state;
184
155
 
185
- filterComboboxOptions(state);
156
+ select.hidden = true;
186
157
 
187
- if (content.list.selected.length > 0) {
188
- updateComboboxPreview('set', state, content.list.selected[0]);
189
- }
158
+ select.insertAdjacentElement('afterend', anchor.container ?? anchor.element);
159
+ (anchor.container ?? anchor.element).insertAdjacentElement('afterend', content);
190
160
 
191
- updateComboboxForm(state, state.values.current, false);
192
- updateComboboxMessage(state, getComboboxVisibleOptions(state).length);
193
- updateComboboxSelectionItems(state);
161
+ select.remove();
194
162
 
195
- return state;
196
- }
163
+ state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
164
+ position: 'below-start',
165
+ reusable: false,
166
+ });
197
167
 
198
- export function detachCombobox(state: OuiComboboxState): void {
199
- const {content, floatable, host, label, select} = state;
168
+ state.floatable.update();
200
169
 
201
- const listeners = state.listeners.splice(0);
202
- const {length} = listeners;
170
+ state.listeners.push(
171
+ on(content, 'toggle', () => {
172
+ setAria(anchor.element, 'expanded', state.floatable.open);
203
173
 
204
- for (let index = 0; index < length; index += 1) {
205
- listeners[index]();
206
- }
174
+ setAria(
175
+ filter.element,
176
+ 'activedescendant',
177
+ state.floatable.open ? state.active?.id : undefined,
178
+ );
179
+
180
+ if (state.floatable.open) {
181
+ filter.element.focus();
182
+ }
183
+ }),
184
+ );
185
+ }
186
+
187
+ export function onDetachCombobox(state: OuiComboboxState): void {
188
+ const {content, floatable, label, select} = state;
207
189
 
208
190
  floatable.destroy();
209
191
 
@@ -218,17 +200,34 @@ export function detachCombobox(state: OuiComboboxState): void {
218
200
  label.element.id = state.label.id;
219
201
 
220
202
  select.hidden = false;
221
-
222
- comboboxStates.map.delete(host);
223
- comboboxStates.map.delete(content);
224
- comboboxStates.set.delete(state);
225
203
  }
226
204
 
227
205
  // #endregion
228
206
 
229
207
  // #region Variables
230
208
 
231
- export const comboboxLocalizations = getLocalizations<OuiComboboxLocalizations>({
209
+ const comboboxCallbacks: OuiListboxCallbacks<OuiComboboxState> = {
210
+ data: {
211
+ onApply: onComboboxApply,
212
+ onFilter: onComboboxFilter,
213
+ onForm: noop,
214
+ onPreview: onComboboxPreview,
215
+ onToggle: onComboboxToggle,
216
+ },
217
+ keydown: {
218
+ onActivation: onComboboxActivation,
219
+ onEnter: onComboboxEnter,
220
+ onEscape: onComboboxEscape,
221
+ onNavigation: onComboboxNavigation,
222
+ onSelection: onComboboxSelection,
223
+ onTab: onComboboxTab,
224
+ onToggle: onComboboxToggle,
225
+ preventActivation: preventComboboxActivation,
226
+ skipNavigation: skipComboboxNavigation,
227
+ },
228
+ };
229
+
230
+ export const comboboxLocalizations = getLocalizations<OuiListboxLocalizations>({
232
231
  empty: 'No results found',
233
232
  input: 'Please enter a value',
234
233
  invalid: 'Please select an option from the list',
@@ -239,9 +238,9 @@ export const comboboxLocalizations = getLocalizations<OuiComboboxLocalizations>(
239
238
  selected: amount => `${amount} selected`,
240
239
  });
241
240
 
242
- export const comboboxStates = {
243
- map: new WeakMap<HTMLElement, OuiComboboxState>(),
244
- set: new Set<OuiComboboxState>(),
241
+ export const comboboxStates: OuiListboxStates<OuiComboboxState> = {
242
+ map: new WeakMap(),
243
+ set: new Set(),
245
244
  };
246
245
 
247
246
  // #endregion
@@ -285,31 +285,23 @@ function onTab(event: KeyboardEvent): void {
285
285
 
286
286
  const instance = instances.get(element);
287
287
 
288
- if (instance == null) {
288
+ if (instance == null || dispatch(element, 'oui:focus-trap').defaultPrevented) {
289
289
  return;
290
290
  }
291
291
 
292
- const dispatched = dispatch(element, 'focus-trap');
293
-
294
- setTimeout(() => {
295
- if (dispatched.defaultPrevented) {
296
- return;
297
- }
298
-
299
- if (!instance.disabled) {
300
- event.preventDefault();
301
- }
292
+ if (!instance.disabled) {
293
+ event.preventDefault();
294
+ }
302
295
 
303
- const elements = getFocusable(element);
296
+ const elements = getFocusable(element);
304
297
 
305
- const index = elements.indexOf(event.target as HTMLElement);
298
+ const index = elements.indexOf(event.target as HTMLElement);
306
299
 
307
- if (index === -1 || elements.length === 0) {
308
- tabToTrap(instance, elements, element);
309
- } else {
310
- tabToElement(instance, elements, index, event.shiftKey ? -1 : 1);
311
- }
312
- });
300
+ if (index === -1 || elements.length === 0) {
301
+ tabToTrap(instance, elements, element);
302
+ } else {
303
+ tabToElement(instance, elements, index, event.shiftKey ? -1 : 1);
304
+ }
313
305
  }
314
306
 
315
307
  export function removeFocusTrap(element: HTMLElement): void {