@oscarpalmer/oui 0.25.0 → 0.26.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 (40) hide show
  1. package/dist/js/embedded-CHK82Uh5.d.mts +76 -0
  2. package/dist/js/{embedded-84gxpReS.d.mts → embedded-ai6cqURc.d.mts} +21 -17
  3. package/dist/js/floatable/embedded.d.mts +2 -2
  4. package/dist/js/floatable/embedded.mjs +27 -16
  5. package/dist/js/floatable/standalone.d.mts +2 -2
  6. package/dist/js/floatable/standalone.mjs +2 -2
  7. package/dist/js/focus-trap/embedded.d.mts +2 -2
  8. package/dist/js/focus-trap/embedded.mjs +157 -79
  9. package/dist/js/focus-trap/standalone.d.mts +2 -1
  10. package/dist/js/focus-trap/standalone.mjs +3 -3
  11. package/dist/js/index.d.mts +3 -2
  12. package/dist/js/index.mjs +4 -2
  13. package/dist/js/internal/draggable.d.mts +2 -2
  14. package/dist/js/internal/draggable.mjs +2 -2
  15. package/dist/js/{movable-BKFgoaOl.d.mts → movable-dNqBctx3.d.mts} +16 -16
  16. package/dist/js/movable.d.mts +2 -2
  17. package/dist/js/movable.mjs +4 -4
  18. package/dist/js/popover.d.mts +3 -3
  19. package/dist/js/popover.mjs +9 -9
  20. package/dist/js/sortable.d.mts +2 -2
  21. package/dist/js/sortable.mjs +4 -4
  22. package/dist/js/splitter.mjs +48 -47
  23. package/dist/js/toolbar.d.mts +1 -0
  24. package/dist/js/toolbar.mjs +107 -0
  25. package/dist/js/tooltip.d.mts +1 -1
  26. package/dist/js/tooltip.mjs +7 -7
  27. package/package.json +13 -1
  28. package/src/js/floatable/embedded.ts +65 -44
  29. package/src/js/floatable/standalone.ts +1 -1
  30. package/src/js/focus-trap/embedded.ts +219 -93
  31. package/src/js/focus-trap/standalone.ts +4 -2
  32. package/src/js/index.ts +1 -0
  33. package/src/js/internal/draggable.ts +20 -20
  34. package/src/js/movable.ts +17 -17
  35. package/src/js/popover.ts +18 -18
  36. package/src/js/sortable.ts +26 -26
  37. package/src/js/splitter.ts +98 -96
  38. package/src/js/toolbar.ts +214 -0
  39. package/src/js/tooltip.ts +14 -10
  40. package/dist/js/embedded-BpwzMSEF.d.mts +0 -26
@@ -5,18 +5,40 @@ import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
5
5
 
6
6
  // #region Types
7
7
 
8
+ type CreateOuiFloatableOptions = {
9
+ /**
10
+ * Open the _OuiFloatable_ when created? _(defaults to `true`)_
11
+ */
12
+ open?: boolean;
13
+ /**
14
+ * Position of the _OuiFloatable_ _(defaults to `below`)_
15
+ */
16
+ position?: OuiFloatablePosition;
17
+ };
18
+
8
19
  export class OuiFloatable {
9
20
  #destroyed = false;
10
21
 
11
22
  readonly #state: OuiFloatableState;
12
23
 
13
24
  /**
14
- * Is the _Floatable_ open?
25
+ * Is the _OuiFloatable_ open?
15
26
  */
16
27
  get open(): boolean {
17
28
  return this.#state.content.checkVisibility();
18
29
  }
19
30
 
31
+ /**
32
+ * Open or close the _OuiFloatable_
33
+ */
34
+ set open(value: boolean) {
35
+ if (value === true) {
36
+ this.show();
37
+ } else if (value === false) {
38
+ this.hide();
39
+ }
40
+ }
41
+
20
42
  constructor(state: OuiFloatableState, standalone: boolean) {
21
43
  this.#state = state;
22
44
 
@@ -105,42 +127,10 @@ type OuiFloatableState = {
105
127
  options: OuiFloatableOptions;
106
128
  };
107
129
 
108
- type GetOuiFloatableOptions = {
109
- /**
110
- * Open the _OuiFloatable_ when created? _(defaults to `true`)_
111
- */
112
- open?: boolean;
113
- /**
114
- * Position of the _OuiFloatable_ _(defaults to `below`)_
115
- */
116
- position?: OuiFloatablePosition;
117
- };
118
-
119
130
  // #endregion
120
131
 
121
132
  // #region Functions
122
133
 
123
- export function createFloatable(
124
- anchor: HTMLElement,
125
- content: HTMLElement,
126
- options: OuiFloatableOptions,
127
- standalone: boolean,
128
- ): OuiFloatable {
129
- if (!isHTMLOrSVGElement(anchor) || !isHTMLOrSVGElement(content)) {
130
- throw new TypeError('Anchor and content must be an HTMLElement or SVGElement.');
131
- }
132
-
133
- let floatable = instances.get(content);
134
-
135
- if (floatable == null) {
136
- floatable = new OuiFloatable(getState(anchor, content, options), standalone);
137
-
138
- instances.set(content, floatable);
139
- }
140
-
141
- return floatable;
142
- }
143
-
144
134
  function getAnchorName(anchor: HTMLElement): string {
145
135
  let {anchorName} = anchor.style;
146
136
 
@@ -153,24 +143,32 @@ function getAnchorName(anchor: HTMLElement): string {
153
143
  return `--oui-floatable-anchor-${index}`;
154
144
  }
155
145
 
146
+ export function createEmbeddedFloatable(
147
+ anchor: HTMLElement,
148
+ content: HTMLElement,
149
+ options: OuiFloatableOptions,
150
+ ): OuiFloatable {
151
+ return createInstance(anchor, content, options, false);
152
+ }
153
+
156
154
  /**
157
- * Create a _Floatable_ for an anchor and content element
155
+ * Create a _OuiFloatable_ for an anchor and content element
158
156
  *
159
157
  * @param anchor Anchor element
160
158
  * @param content Content element
161
- * @param options _Floatable_ options
162
- * @returns _Floatable_ instance
159
+ * @param options _OuiFloatable_ options
160
+ * @returns _OuiFloatable_ instance
163
161
  */
164
- export function getFloatable(
165
- anchor: HTMLElement | SVGElement,
166
- content: HTMLElement | SVGElement,
167
- options?: GetOuiFloatableOptions,
162
+ export function createFloatable(
163
+ anchor: HTMLElement,
164
+ content: HTMLElement,
165
+ options?: CreateOuiFloatableOptions,
168
166
  ): OuiFloatable {
169
167
  const {open, position} = getOptions(options);
170
168
 
171
- const floatable = createFloatable(
172
- anchor as HTMLElement,
173
- content as HTMLElement,
169
+ const floatable = createInstance(
170
+ anchor,
171
+ content,
174
172
  {
175
173
  position,
176
174
  },
@@ -186,6 +184,27 @@ export function getFloatable(
186
184
  return floatable;
187
185
  }
188
186
 
187
+ function createInstance(
188
+ anchor: HTMLElement,
189
+ content: HTMLElement,
190
+ options: OuiFloatableOptions,
191
+ standalone: boolean,
192
+ ): OuiFloatable {
193
+ if (!isHTMLOrSVGElement(anchor) || !isHTMLOrSVGElement(content)) {
194
+ throw new TypeError(MESSAGE);
195
+ }
196
+
197
+ let floatable = instances.get(content);
198
+
199
+ if (floatable == null) {
200
+ floatable = new OuiFloatable(getState(anchor, content, options), standalone);
201
+
202
+ instances.set(content, floatable);
203
+ }
204
+
205
+ return floatable;
206
+ }
207
+
189
208
  export function getOnBeforeToggleListener(element: HTMLElement): RemovableEventListener {
190
209
  return on(
191
210
  element,
@@ -203,7 +222,7 @@ export function getOnBeforeToggleListener(element: HTMLElement): RemovableEventL
203
222
  );
204
223
  }
205
224
 
206
- function getOptions(input?: GetOuiFloatableOptions): Required<GetOuiFloatableOptions> {
225
+ function getOptions(input?: CreateOuiFloatableOptions): Required<CreateOuiFloatableOptions> {
207
226
  const object = isPlainObject(input) ? input : {};
208
227
 
209
228
  return {
@@ -287,6 +306,8 @@ const ATTRIBUTE_POSITION = 'oui-position';
287
306
 
288
307
  const EVENT_BEFORE = 'beforetoggle';
289
308
 
309
+ const MESSAGE = 'Anchor and content must be an HTMLElement or SVGElement';
310
+
290
311
  const POSITIONS = Object.fromEntries(
291
312
  Object.entries(AREAS).map(([position, area]) => [area, position]),
292
313
  ) as Record<string, OuiFloatablePosition>;
@@ -1 +1 @@
1
- export {getFloatable, type OuiFloatable, type OuiFloatablePosition} from './embedded';
1
+ export {createFloatable, type OuiFloatable, type OuiFloatablePosition} from './embedded';
@@ -1,118 +1,239 @@
1
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
1
2
  import {clamp} from '@oscarpalmer/atoms/number';
2
3
  import {on} from '@oscarpalmer/toretto/event';
3
4
  import {findAncestor} from '@oscarpalmer/toretto/find';
4
5
  import {getFocusable} from '@oscarpalmer/toretto/focusable';
6
+ import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
5
7
 
6
- export class FocusTrap {
7
- disabled = false;
8
+ // #region Types
8
9
 
9
- connected = true;
10
+ export class OuiFocusTrap {
11
+ #destroyed = false;
12
+ #tabIndex: number;
10
13
 
11
- element: HTMLElement;
12
-
13
- embedded: boolean;
14
+ readonly #state: OuiFocusTrapState;
14
15
 
16
+ /**
17
+ * Is focused maintained within the _FocusTrap_ when clicking outside of it?
18
+ */
15
19
  get contain(): boolean {
16
- return this.element.hasAttribute(FOCUS_TRAP_CONTAIN);
20
+ return this.#state.element.hasAttribute(ATTRIBUTE_FOCUSTRAP_CONTAIN);
17
21
  }
18
22
 
19
- get noescape(): boolean {
20
- return this.element.hasAttribute(FOCUS_TRAP_NOESCAPE);
23
+ /**
24
+ * Should focus be maintained within the _FocusTrap_ when clicking outside of it?
25
+ */
26
+ set contain(value: boolean) {
27
+ if (value === true) {
28
+ this.#state.element.setAttribute(ATTRIBUTE_FOCUSTRAP_CONTAIN, '');
29
+ } else if (value === false) {
30
+ this.#state.element.removeAttribute(ATTRIBUTE_FOCUSTRAP_CONTAIN);
31
+ }
21
32
  }
22
33
 
23
- constructor(element: HTMLElement, options?: Options) {
24
- this.element = element;
25
- this.embedded = options != null;
26
-
27
- element.setAttribute('tabindex', '-1');
34
+ /**
35
+ * Is the _FocusTrap_ disabled?
36
+ */
37
+ get disabled(): boolean {
38
+ return this.#state.disabled;
39
+ }
28
40
 
29
- if (options != null) {
30
- element.setAttribute(FOCUS_TRAP_SELECTOR, '');
41
+ /**
42
+ * Should the _FocusTrap_ be disabled?
43
+ */
44
+ set disabled(value: boolean) {
45
+ if (value === true) {
46
+ this.disable();
47
+ } else if (value === false) {
48
+ this.enable();
31
49
  }
50
+ }
51
+
52
+ /**
53
+ * Is focus allowed to escape when pressing _Escape_ within the _FocusTrap_?
54
+ */
55
+ get noescape(): boolean {
56
+ return this.#state.element.hasAttribute(ATTRIBUTE_FOCUSTRAP_NOESCAPE);
57
+ }
32
58
 
33
- if (options?.noescape) {
34
- element.setAttribute(FOCUS_TRAP_NOESCAPE, '');
59
+ /**
60
+ * Should the _FocusTrap_ allow focus to escape when pressing _Escape_?
61
+ */
62
+ set noescape(value: boolean) {
63
+ if (value === true) {
64
+ this.#state.element.setAttribute(ATTRIBUTE_FOCUSTRAP_NOESCAPE, '');
65
+ } else if (value === false) {
66
+ this.#state.element.removeAttribute(ATTRIBUTE_FOCUSTRAP_NOESCAPE);
35
67
  }
36
68
  }
37
69
 
38
- connect(): void {
39
- setConnected(this, true);
70
+ constructor(state: OuiFocusTrapState) {
71
+ this.#state = state;
72
+ this.#tabIndex = state.element.tabIndex;
73
+
74
+ state.element.tabIndex = -1;
75
+
76
+ state.element.setAttribute(ATTRIBUTE_FOCUSTRAP, '');
77
+
78
+ this.contain = state.options.contain;
79
+ this.noescape = state.options.noescape;
80
+
81
+ states.set(this, state);
40
82
  }
41
83
 
84
+ /**
85
+ * Destroys the _FocusTrap_
86
+ */
42
87
  destroy(): void {
43
- this.element = undefined as never;
88
+ if (this.#destroyed) {
89
+ return;
90
+ }
91
+
92
+ this.#destroyed = true;
93
+
94
+ disabled.delete(this);
95
+ instances.delete(this.#state.element);
96
+ states.delete(this);
97
+
98
+ this.#state.element.tabIndex = this.#tabIndex;
99
+
100
+ this.#state.element.removeAttribute(ATTRIBUTE_FOCUSTRAP);
101
+ this.#state.element.removeAttribute(ATTRIBUTE_FOCUSTRAP_CONTAIN);
102
+ this.#state.element.removeAttribute(ATTRIBUTE_FOCUSTRAP_NOESCAPE);
103
+
104
+ this.#state.element = undefined as never;
44
105
  }
45
106
 
107
+ /**
108
+ * Disables the _FocusTrap_
109
+ */
46
110
  disable(): void {
47
- if (!(this.disabled || this.noescape)) {
48
- setDisabled(this, true);
111
+ if (!this.#destroyed && !(this.disabled || this.#state.options.noescape)) {
112
+ setDisabled(this, this.#state, true);
49
113
  }
50
114
  }
51
115
 
52
- disconnect(): void {
53
- setConnected(this, false);
54
- }
55
-
116
+ /**
117
+ * Enables the _FocusTrap_
118
+ */
56
119
  enable(): void {
57
- if (this.disabled) {
58
- setDisabled(this, false);
120
+ if (!this.#destroyed && this.disabled) {
121
+ setDisabled(this, this.#state, false);
59
122
  }
60
123
  }
61
124
 
125
+ /**
126
+ * Focuses the _FocusTrap_
127
+ *
128
+ * @param last When `true`, focuses the last focusable element within the _FocusTrap_ instead of the first
129
+ */
62
130
  focus(last: boolean): void {
63
- tabToTrap(this, getFocusable(this.element), this.element, last ? -1 : 0);
131
+ if (!this.#destroyed && !this.disabled) {
132
+ tabToTrap(this, getFocusable(this.#state.element), this.#state.element, last ? -1 : 0);
133
+ }
64
134
  }
65
135
  }
66
136
 
67
- type Options = {
68
- noescape: boolean;
137
+ type OuiFocusTrapOptions = {
138
+ /**
139
+ * Should focus be maintained within the _FocusTrap_ when clicking outside of it?
140
+ */
141
+ contain?: boolean;
142
+ /**
143
+ * Should the _FocusTrap_ allow focus to escape when pressing _Escape_?
144
+ */
145
+ noescape?: boolean;
146
+ };
147
+
148
+ type OuiFocusTrapState = {
149
+ disabled: boolean;
150
+ element: HTMLElement;
151
+ options: Required<OuiFocusTrapOptions>;
69
152
  };
70
153
 
71
- //
154
+ // #endregion
155
+
156
+ // #region Functions
157
+
158
+ /**
159
+ * Creates _(or retrieves)_ a _FocusTrap_ for an element
160
+ *
161
+ * @param element Element to trap focus within
162
+ * @param options _FocusTrap_ options
163
+ * @returns _FocusTrap_ instance
164
+ */
165
+ export function createFocusTrap(element: HTMLElement, options?: OuiFocusTrapOptions): OuiFocusTrap {
166
+ if (!isHTMLOrSVGElement(element)) {
167
+ throw new TypeError(MESSAGE);
168
+ }
72
169
 
73
- export function createFocusTrap(element: HTMLElement, options?: Options): FocusTrap {
74
- let focusTrap = FOCUSTRAPS_ALL.get(element);
170
+ let instance = instances.get(element);
75
171
 
76
- if (focusTrap == null) {
77
- focusTrap = new FocusTrap(element, options);
172
+ if (instance == null) {
173
+ instance = new OuiFocusTrap(getState(element, options));
78
174
 
79
- FOCUSTRAPS_ALL.set(element, focusTrap);
175
+ instances.set(element, instance);
80
176
  }
81
177
 
82
- return focusTrap;
178
+ return instance;
179
+ }
180
+
181
+ function getOptions(
182
+ element: HTMLElement,
183
+ input?: OuiFocusTrapOptions,
184
+ ): Required<OuiFocusTrapOptions> {
185
+ const options = isPlainObject(input) ? input : {};
186
+
187
+ return {
188
+ contain:
189
+ typeof options.contain === 'boolean'
190
+ ? options.contain
191
+ : element.hasAttribute(ATTRIBUTE_FOCUSTRAP_CONTAIN),
192
+ noescape:
193
+ typeof options.noescape === 'boolean'
194
+ ? options.noescape
195
+ : element.hasAttribute(ATTRIBUTE_FOCUSTRAP_NOESCAPE),
196
+ };
197
+ }
198
+
199
+ function getState(element: HTMLElement, options?: OuiFocusTrapOptions): OuiFocusTrapState {
200
+ return {
201
+ element,
202
+ disabled: false,
203
+ options: getOptions(element, options),
204
+ };
83
205
  }
84
206
 
85
207
  function onEscape(event: KeyboardEvent): void {
86
- let element = findAncestor(event, ATTRIBUTE_SELECTOR) as HTMLElement;
208
+ let element = findAncestor(event, SELECTOR) as HTMLElement;
87
209
 
88
- let focusTrap = FOCUSTRAPS_ALL.get(element);
210
+ let instance = instances.get(element);
211
+ let state = states.get(instance!);
89
212
 
90
- if (element == null || focusTrap == null) {
213
+ if (element == null || instance == null || state == null) {
91
214
  return;
92
215
  }
93
216
 
94
- while (focusTrap.disabled) {
95
- element = findAncestor(
96
- focusTrap.element.parentElement as HTMLElement,
97
- ATTRIBUTE_SELECTOR,
98
- ) as HTMLElement;
217
+ while (state.disabled) {
218
+ element = findAncestor(state.element.parentElement as HTMLElement, SELECTOR) as HTMLElement;
99
219
 
100
- focusTrap = FOCUSTRAPS_ALL.get(element);
220
+ instance = instances.get(element);
221
+ state = states.get(instance!);
101
222
 
102
- if (element == null || focusTrap == null) {
223
+ if (element == null || instance == null || state == null) {
103
224
  return;
104
225
  }
105
226
 
106
- if (!focusTrap.disabled) {
227
+ if (!state.disabled) {
107
228
  break;
108
229
  }
109
230
  }
110
231
 
111
- focusTrap?.disable();
232
+ instance?.disable();
112
233
  }
113
234
 
114
235
  function onFocusIn(event: Event): void {
115
- const focusTrap = FOCUSTRAPS_ALL.get(findAncestor(event, ATTRIBUTE_SELECTOR) as HTMLElement);
236
+ const focusTrap = instances.get(findAncestor(event, SELECTOR) as HTMLElement);
116
237
 
117
238
  lastTarget = focusTrap == null ? undefined : (event.target as HTMLElement);
118
239
  }
@@ -133,38 +254,40 @@ function onKeydown(event: KeyboardEvent): void {
133
254
  }
134
255
 
135
256
  function onPointerdown(event: MouseEvent | TouchEvent): void {
136
- const lastFocusTrap = findAncestor(lastTarget as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement;
137
- const nextFocusTrap = findAncestor(event, ATTRIBUTE_SELECTOR) as HTMLElement;
257
+ const last = findAncestor(lastTarget as HTMLElement, SELECTOR) as HTMLElement;
258
+ const next = findAncestor(event, SELECTOR) as HTMLElement;
138
259
 
139
- if (lastFocusTrap != null && nextFocusTrap !== lastFocusTrap) {
140
- if (FOCUSTRAPS_ALL.get(lastFocusTrap)?.contain ?? false) {
260
+ if (last != null && next !== last) {
261
+ if (states.get(instances.get(last)!)?.options.contain ?? false) {
141
262
  event.preventDefault();
142
263
 
143
264
  lastTarget?.focus();
144
265
  }
145
266
  }
146
267
 
147
- if (FOCUSTRAPS_DISABLED.size === 0) {
268
+ if (disabled.size === 0) {
148
269
  return;
149
270
  }
150
271
 
151
- for (const focusTrap of FOCUSTRAPS_DISABLED) {
152
- if (focusTrap.element !== nextFocusTrap) {
272
+ for (const focusTrap of disabled) {
273
+ const state = states.get(focusTrap);
274
+
275
+ if (state != null && state.element !== next) {
153
276
  focusTrap.enable();
154
277
  }
155
278
  }
156
279
  }
157
280
 
158
281
  function onTab(event: KeyboardEvent): void {
159
- const element = findAncestor(event, ATTRIBUTE_SELECTOR) as HTMLElement;
282
+ const element = findAncestor(event, SELECTOR) as HTMLElement;
160
283
 
161
- const focusTrap = FOCUSTRAPS_ALL.get(element);
284
+ const instance = instances.get(element);
162
285
 
163
- if (focusTrap == null) {
286
+ if (instance == null) {
164
287
  return;
165
288
  }
166
289
 
167
- if (!focusTrap.disabled) {
290
+ if (!instance.disabled) {
168
291
  event.preventDefault();
169
292
  }
170
293
 
@@ -173,37 +296,30 @@ function onTab(event: KeyboardEvent): void {
173
296
  const index = elements.indexOf(event.target as HTMLElement);
174
297
 
175
298
  if (index === -1 || elements.length === 0) {
176
- tabToTrap(focusTrap, elements, element);
299
+ tabToTrap(instance, elements, element);
177
300
  } else {
178
- tabToElement(focusTrap, elements, index, event.shiftKey ? -1 : 1);
301
+ tabToElement(instance, elements, index, event.shiftKey ? -1 : 1);
179
302
  }
180
303
  }
181
304
 
182
305
  export function removeFocusTrap(element: HTMLElement): void {
183
- FOCUSTRAPS_ALL.get(element)?.destroy();
184
- FOCUSTRAPS_ALL.delete(element);
185
- }
186
-
187
- function setConnected(focusTrap: FocusTrap, value: boolean): void {
188
- if (!focusTrap.embedded || focusTrap.connected === value) {
189
- return;
190
- }
306
+ instances.get(element)?.destroy();
191
307
 
192
- focusTrap.connected = value;
308
+ instances.delete(element);
193
309
  }
194
310
 
195
- function setDisabled(focusTrap: FocusTrap, value: boolean): void {
196
- focusTrap.disabled = value;
311
+ function setDisabled(focusTrap: OuiFocusTrap, state: OuiFocusTrapState, value: boolean): void {
312
+ state.disabled = value;
197
313
 
198
314
  if (value) {
199
- FOCUSTRAPS_DISABLED.add(focusTrap);
315
+ disabled.add(focusTrap);
200
316
  } else {
201
- FOCUSTRAPS_DISABLED.delete(focusTrap);
317
+ disabled.delete(focusTrap);
202
318
  }
203
319
  }
204
320
 
205
321
  function tabToElement(
206
- focusTrap: FocusTrap,
322
+ instance: OuiFocusTrap,
207
323
  elements: Element[],
208
324
  index: number,
209
325
  modifier: number,
@@ -211,18 +327,18 @@ function tabToElement(
211
327
  const {length} = elements;
212
328
  const next = clamp(index + modifier, 0, length - 1, true);
213
329
 
214
- if (focusTrap.disabled) {
330
+ if (instance.disabled) {
215
331
  if ((index === length - 1 && next === 0) || (index === 0 && next === length - 1)) {
216
332
  lastTarget = undefined;
217
333
 
218
- focusTrap.enable();
334
+ instance.enable();
219
335
  }
220
336
 
221
337
  return;
222
338
  }
223
339
 
224
340
  const element = elements[next] as HTMLElement;
225
- const elementFocusTrap = FOCUSTRAPS_ALL.get(element);
341
+ const elementFocusTrap = instances.get(element);
226
342
 
227
343
  if (elementFocusTrap != null) {
228
344
  elementFocusTrap.focus(modifier === -1);
@@ -234,15 +350,15 @@ function tabToElement(
234
350
  }
235
351
 
236
352
  function tabToTrap(
237
- focusTrap: FocusTrap,
353
+ instance: OuiFocusTrap,
238
354
  elements: Element[],
239
355
  element: HTMLElement,
240
356
  index?: number,
241
357
  ): void {
242
- if (focusTrap.disabled) {
358
+ if (instance.disabled) {
243
359
  lastTarget = undefined;
244
360
 
245
- focusTrap.enable();
361
+ instance.enable();
246
362
  } else {
247
363
  const next = elements.length === 0 ? element : elements.at(index ?? 0);
248
364
 
@@ -252,30 +368,40 @@ function tabToTrap(
252
368
  }
253
369
  }
254
370
 
255
- //
371
+ // #endregion
256
372
 
257
- export const FOCUS_TRAP_SELECTOR = 'oui-focus-trap';
373
+ // #region Variables
258
374
 
259
- const ATTRIBUTE_SELECTOR = `[${FOCUS_TRAP_SELECTOR}]`;
375
+ export const ATTRIBUTE_FOCUSTRAP = 'oui-focus-trap';
376
+
377
+ export const ATTRIBUTE_FOCUSTRAP_CONTAIN = `${ATTRIBUTE_FOCUSTRAP}-contain`;
378
+
379
+ export const ATTRIBUTE_FOCUSTRAP_NOESCAPE = `${ATTRIBUTE_FOCUSTRAP}-noescape`;
260
380
 
261
381
  const EVENT_OPTIONS: AddEventListenerOptions = {
262
382
  capture: true,
263
383
  passive: false,
264
384
  };
265
385
 
266
- export const FOCUS_TRAP_CONTAIN = `${FOCUS_TRAP_SELECTOR}-contain`;
386
+ const MESSAGE = 'Element must be an HTMLElement or SVGElement';
387
+
388
+ const SELECTOR = `[${ATTRIBUTE_FOCUSTRAP}]`;
267
389
 
268
- export const FOCUS_TRAP_NOESCAPE = `${FOCUS_TRAP_SELECTOR}-noescape`;
390
+ const disabled = new Set<OuiFocusTrap>();
269
391
 
270
- const FOCUSTRAPS_ALL: WeakMap<HTMLElement, FocusTrap> = new WeakMap();
392
+ const instances = new WeakMap<HTMLElement, OuiFocusTrap>();
271
393
 
272
- const FOCUSTRAPS_DISABLED: Set<FocusTrap> = new Set();
394
+ const states = new WeakMap<OuiFocusTrap, OuiFocusTrapState>();
273
395
 
274
396
  let lastTarget: HTMLElement | undefined;
275
397
 
276
- //
398
+ // #endregion
399
+
400
+ // #region Initialization
277
401
 
278
402
  on(document, 'focusin', onFocusIn, EVENT_OPTIONS);
279
403
  on(document, 'keydown', onKeydown, EVENT_OPTIONS);
280
404
  on(document, 'mousedown', onPointerdown, EVENT_OPTIONS);
281
405
  on(document, 'touchstart', onPointerdown, EVENT_OPTIONS);
406
+
407
+ // #endregion
@@ -1,4 +1,6 @@
1
1
  import {attributable} from '../internal/attributable';
2
- import {createFocusTrap, FOCUS_TRAP_SELECTOR, removeFocusTrap} from './embedded';
2
+ import {ATTRIBUTE_FOCUSTRAP, createFocusTrap, removeFocusTrap, type OuiFocusTrap} from './embedded';
3
3
 
4
- attributable(FOCUS_TRAP_SELECTOR, createFocusTrap, removeFocusTrap);
4
+ attributable(ATTRIBUTE_FOCUSTRAP, createFocusTrap, removeFocusTrap);
5
+
6
+ export {createFocusTrap, type OuiFocusTrap};
package/src/js/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import './movable';
2
2
  import './sortable';
3
+ import './toolbar';
3
4
 
4
5
  export * from './dialog';
5
6
  export * from './floatable/standalone';