@oscarpalmer/oui 0.13.0 → 0.15.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 (38) hide show
  1. package/README.md +2 -0
  2. package/dist/css/popover.css +9 -0
  3. package/dist/css/tooltip.css +9 -3
  4. package/dist/css/value.css +1 -0
  5. package/dist/js/floatable.cjs +134 -0
  6. package/dist/js/floatable.js +134 -0
  7. package/dist/js/focus-trap/embedded.cjs +150 -0
  8. package/dist/js/focus-trap/embedded.js +150 -0
  9. package/dist/js/focus-trap/standalone.cjs +8 -0
  10. package/dist/js/focus-trap/standalone.js +7 -0
  11. package/dist/js/index.cjs +5 -1
  12. package/dist/js/index.js +6 -1
  13. package/dist/js/popover.cjs +160 -0
  14. package/dist/js/popover.js +160 -0
  15. package/dist/js/tooltip.cjs +83 -126
  16. package/dist/js/tooltip.js +83 -126
  17. package/package.json +7 -7
  18. package/src/css/popover.scss +9 -0
  19. package/src/css/tooltip.scss +7 -3
  20. package/src/js/floatable.ts +196 -0
  21. package/src/js/focus-trap/embedded.ts +215 -0
  22. package/src/js/focus-trap/standalone.ts +9 -0
  23. package/src/js/index.ts +3 -1
  24. package/src/js/popover.ts +221 -0
  25. package/src/js/tooltip.ts +97 -161
  26. package/types/floatable.d.cts +16 -0
  27. package/types/floatable.d.ts +12 -0
  28. package/types/focus-trap/embedded.d.cts +15 -0
  29. package/types/focus-trap/embedded.d.ts +11 -0
  30. package/types/index.d.cts +11 -0
  31. package/types/index.d.ts +3 -1
  32. package/types/popover.d.cts +14 -0
  33. package/types/popover.d.ts +15 -0
  34. package/dist/js/focus-trap.cjs +0 -104
  35. package/dist/js/focus-trap.js +0 -103
  36. package/src/js/focus-trap.ts +0 -133
  37. /package/types/{focus-trap.d.cts → focus-trap/standalone.d.cts} +0 -0
  38. /package/types/{focus-trap.d.ts → focus-trap/standalone.d.ts} +0 -0
package/src/js/tooltip.ts CHANGED
@@ -1,119 +1,102 @@
1
1
  import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
2
  import {on} from '@oscarpalmer/toretto/event';
3
3
  import {attributable} from './attributable';
4
+ import {activate, deactivate, Floatable} from './floatable';
4
5
 
5
- class Tooltip {
6
- abortController = new AbortController();
7
-
8
- activate: number | undefined;
9
-
10
- active = false;
11
-
12
- deactivate: number | undefined;
6
+ type Attributes = {
7
+ ariaDescribedby: string | null;
8
+ ariaLabelledBy: string | null;
9
+ };
13
10
 
11
+ type Content = {
12
+ attributes: Attributes;
14
13
  element: HTMLElement;
14
+ };
15
15
 
16
- frame: DOMHighResTimeStamp | undefined;
17
-
18
- constructor(public parent: HTMLElement) {
19
- this.element = getContent(parent) as HTMLElement;
16
+ class Tooltip {
17
+ abortController = new AbortController();
18
+ attributes: Attributes;
19
+ floatable: Floatable;
20
20
 
21
- if (this.element != null) {
22
- parent.insertAdjacentElement('afterend', this.element);
21
+ constructor(anchor: HTMLElement, content: Content) {
22
+ this.attributes = content.attributes;
23
+ this.floatable = new Floatable(anchor, content.element);
23
24
 
24
- initialize(this);
25
- }
25
+ initialize(this);
26
26
  }
27
27
 
28
- destroy() {
29
- deactivate(this);
28
+ destroy(): void {
29
+ deactivate(this.floatable, active);
30
+ reset(this.floatable.anchor, this.attributes);
30
31
 
31
32
  this.abortController.abort();
33
+ this.floatable.content.remove();
34
+ this.floatable.destroy();
32
35
 
33
36
  this.abortController = undefined as never;
34
- this.activate = undefined as never;
35
- this.deactivate = undefined as never;
36
- this.element = undefined as never;
37
- this.frame = undefined as never;
38
- this.parent = undefined as never;
37
+ this.attributes = undefined as never;
38
+ this.floatable = undefined as never;
39
39
  }
40
40
  }
41
41
 
42
- function activate(tooltip: Tooltip): void {
43
- stop(tooltip);
44
-
45
- if (tooltip.active) {
42
+ function addTooltip(anchor: HTMLElement): void {
43
+ if (tooltips.has(anchor)) {
46
44
  return;
47
45
  }
48
46
 
49
- tooltip.activate = +setTimeout(() => {
50
- tooltip.active = true;
47
+ const content = getContent(anchor);
51
48
 
52
- active.add(tooltip);
53
-
54
- document.body.append(tooltip.element);
55
-
56
- tooltip.element.hidden = false;
57
-
58
- update(tooltip);
59
- }, 250);
60
- }
61
-
62
- function addTooltip(element: HTMLElement): void {
63
- if (!tooltips.has(element)) {
64
- tooltips.set(element, new Tooltip(element));
49
+ if (content != null) {
50
+ tooltips.set(anchor, new Tooltip(anchor, content));
65
51
  }
66
52
  }
67
53
 
68
- function deactivate(tooltip: Tooltip, time?: number): void {
69
- stop(tooltip);
70
-
71
- if (!tooltip.active) {
72
- return;
73
- }
74
-
75
- tooltip.deactivate = +setTimeout(() => {
76
- tooltip.active = false;
77
-
78
- active.delete(tooltip);
54
+ function getContent(anchor: HTMLElement): Content | undefined {
55
+ const attributes = {
56
+ ariaDescribedby: anchor.getAttribute('aria-describedby'),
57
+ ariaLabelledBy: anchor.getAttribute('aria-labelledby'),
58
+ };
79
59
 
80
- tooltip.element.hidden = true;
60
+ const wrapper = getWrapper(anchor);
81
61
 
82
- tooltip.parent.insertAdjacentElement('afterend', tooltip.element);
83
- }, time ?? 250);
84
- }
62
+ const labelledElements = getElements(attributes.ariaLabelledBy);
85
63
 
86
- function getContent(parent: HTMLElement): HTMLElement | undefined {
87
- const wrapper = getWrapper(parent);
64
+ if (labelledElements.length > 0) {
65
+ anchor.setAttribute('aria-labelledby', wrapper.id);
88
66
 
89
- const ids = (parent.getAttribute('aria-describedby') ?? '').split(/\s+/);
67
+ for (const element of labelledElements) {
68
+ wrapper.append(element);
69
+ }
90
70
 
91
- parent.setAttribute('aria-describedby', wrapper.id);
71
+ return {
72
+ attributes,
73
+ element: wrapper,
74
+ };
75
+ }
92
76
 
93
- const elements = ids
94
- .map(id =>
95
- isNullableOrWhitespace(id) ? undefined : document.querySelector(`#${id}`),
96
- )
97
- .filter(element => element instanceof HTMLElement);
77
+ const describedElements = getElements(attributes.ariaDescribedby);
98
78
 
99
- if (elements.length > 0) {
100
- for (const element of elements) {
101
- element.hidden = false;
79
+ if (describedElements.length > 0) {
80
+ anchor.setAttribute('aria-describedby', wrapper.id);
102
81
 
82
+ for (const element of describedElements) {
103
83
  wrapper.append(element);
104
84
  }
105
85
 
106
- return wrapper;
86
+ return {
87
+ attributes,
88
+ element: wrapper,
89
+ };
107
90
  }
108
91
 
109
- let content = parent.getAttribute('aria-description');
92
+ let content = anchor.getAttribute('aria-label');
110
93
 
111
94
  if (isNullableOrWhitespace(content)) {
112
- content = parent.getAttribute('aria-label');
95
+ content = anchor.getAttribute('aria-description');
96
+ }
113
97
 
114
- if (isNullableOrWhitespace(content)) {
115
- return;
116
- }
98
+ if (isNullableOrWhitespace(content)) {
99
+ return;
117
100
  }
118
101
 
119
102
  const paragraph = document.createElement('p');
@@ -122,31 +105,41 @@ function getContent(parent: HTMLElement): HTMLElement | undefined {
122
105
 
123
106
  wrapper.append(paragraph);
124
107
 
125
- return wrapper;
108
+ return {
109
+ attributes,
110
+ element: wrapper,
111
+ };
126
112
  }
127
113
 
128
- function getTop(parent: DOMRect, content: DOMRect): number {
129
- let top = parent.top - content.height - margin;
114
+ function getElements(attribute: string | null): HTMLElement[] {
115
+ const ids = attribute?.split(/\s+/) ?? [];
116
+ const elements: HTMLElement[] = [];
130
117
 
131
- if (top - margin >= 0) {
132
- return top;
133
- }
118
+ for (const id of ids) {
119
+ if (isNullableOrWhitespace(id)) {
120
+ continue;
121
+ }
122
+
123
+ const element = document.querySelector(`#${id}`);
124
+
125
+ if (element instanceof HTMLElement) {
126
+ const cloned = element.cloneNode(true) as HTMLElement;
134
127
 
135
- top = parent.bottom + margin;
128
+ cloned.hidden = false;
136
129
 
137
- if (top + content.height + margin <= window.innerHeight) {
138
- return top;
130
+ elements.push(cloned);
131
+ }
139
132
  }
140
133
 
141
- return parent.top + (parent.height - content.height) / 2;
134
+ return elements;
142
135
  }
143
136
 
144
- function getWrapper(parent: HTMLElement): HTMLElement {
137
+ function getWrapper(anchor: HTMLElement): HTMLElement {
145
138
  const wrapper = document.createElement('div');
146
139
 
147
140
  wrapper.setAttribute(`${selector}-content`, '');
148
141
 
149
- wrapper.className = parent.getAttribute(`${selector}-class`) ?? '';
142
+ wrapper.className = anchor.getAttribute(`${selector}-class`) ?? '';
150
143
  wrapper.hidden = true;
151
144
  wrapper.id = `${selector}-${++index}`;
152
145
  wrapper.role = 'tooltip';
@@ -155,39 +148,13 @@ function getWrapper(parent: HTMLElement): HTMLElement {
155
148
  return wrapper;
156
149
  }
157
150
 
158
- function getX(
159
- parent: DOMRect,
160
- content: DOMRect,
161
- ): {left?: number; right?: number} {
162
- if (content.width + 2 * margin >= window.innerWidth) {
163
- return {
164
- left: margin,
165
- right: margin,
166
- };
167
- }
168
-
169
- const left = parent.left + (parent.width - content.width) / 2;
170
- const right = window.innerWidth - margin;
171
-
172
- if (left < margin) {
173
- return {
174
- left: margin,
175
- };
176
- }
177
-
178
- if (left + content.width > right) {
179
- return {
180
- right: margin,
181
- };
182
- }
151
+ function initialize(tooltip: Tooltip): void {
152
+ const {abortController, floatable} = tooltip;
153
+ const {anchor, content} = floatable;
183
154
 
184
- return {
185
- left,
186
- };
187
- }
155
+ anchor.insertAdjacentElement('afterend', content);
188
156
 
189
- function initialize(tooltip: Tooltip): void {
190
- const elements = [tooltip.parent, tooltip.element];
157
+ const elements = [anchor, content];
191
158
 
192
159
  for (const element of elements) {
193
160
  for (const event of activateEvents) {
@@ -195,10 +162,10 @@ function initialize(tooltip: Tooltip): void {
195
162
  element,
196
163
  event,
197
164
  () => {
198
- activate(tooltip);
165
+ activate(floatable, active, undefined, 250);
199
166
  },
200
167
  {
201
- signal: tooltip.abortController.signal,
168
+ signal: abortController.signal,
202
169
  },
203
170
  );
204
171
  }
@@ -208,10 +175,10 @@ function initialize(tooltip: Tooltip): void {
208
175
  element,
209
176
  event,
210
177
  () => {
211
- deactivate(tooltip);
178
+ deactivate(floatable, active, undefined, 250);
212
179
  },
213
180
  {
214
- signal: tooltip.abortController.signal,
181
+ signal: abortController.signal,
215
182
  },
216
183
  );
217
184
  }
@@ -223,53 +190,22 @@ function removeTooltip(element: HTMLElement): void {
223
190
  tooltips.delete(element);
224
191
  }
225
192
 
226
- function stop(tooltip: Tooltip): void {
227
- if (tooltip.frame != null) {
228
- cancelAnimationFrame(tooltip.frame);
229
-
230
- tooltip.frame = undefined;
231
- }
232
-
233
- if (tooltip.activate != null) {
234
- clearTimeout(tooltip.activate);
235
-
236
- tooltip.activate = undefined;
237
- }
238
-
239
- if (tooltip.deactivate != null) {
240
- clearTimeout(tooltip.deactivate);
241
-
242
- tooltip.deactivate = undefined;
243
- }
244
- }
245
-
246
- function update(tooltip: Tooltip): void {
247
- function run(): void {
248
- const parentRect = tooltip.parent.getBoundingClientRect();
249
- const contentRect = tooltip.element.getBoundingClientRect();
250
-
251
- const top = getTop(parentRect, contentRect);
252
- const {left, right} = getX(parentRect, contentRect);
253
-
254
- tooltip.element.style.inset = `${top}px ${right == null ? 'auto' : `${right}px`} auto ${left == null ? 'auto' : `${left}px`}`;
255
-
256
- tooltip.frame = requestAnimationFrame(run);
193
+ function reset(anchor: HTMLElement, attributes: Attributes): void {
194
+ if (attributes.ariaDescribedby == null) {
195
+ anchor.removeAttribute('aria-describedby');
196
+ } else {
197
+ anchor.setAttribute('aria-describedby', attributes.ariaDescribedby);
257
198
  }
258
-
259
- tooltip.frame = requestAnimationFrame(run);
260
199
  }
261
200
 
262
201
  //
263
202
 
264
- const active = new Set<Tooltip>();
203
+ const active = new Set<Floatable>();
265
204
 
266
205
  const activateEvents = ['focus', 'mouseenter', 'touchstart'];
267
206
 
268
207
  const deactivateEvents = ['blur', 'mouseleave'];
269
208
 
270
- const margin =
271
- Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
272
-
273
209
  const selector = 'oui-tooltip';
274
210
 
275
211
  const tooltips = new WeakMap<HTMLElement, Tooltip>();
@@ -282,8 +218,8 @@ attributable(selector, addTooltip, removeTooltip);
282
218
 
283
219
  on(document, 'keydown', event => {
284
220
  if (event.key === 'Escape') {
285
- for (const tooltip of active) {
286
- deactivate(tooltip, 0);
221
+ for (const floatable of active) {
222
+ deactivate(floatable, active);
287
223
  }
288
224
  }
289
225
  });
@@ -0,0 +1,16 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ export declare class Floatable {
4
+ anchor: HTMLElement;
5
+ content: HTMLElement;
6
+ activate: number | undefined;
7
+ active: boolean;
8
+ deactivate: number | undefined;
9
+ frame: DOMHighResTimeStamp | undefined;
10
+ constructor(anchor: HTMLElement, content: HTMLElement);
11
+ destroy(): void;
12
+ }
13
+ export declare function activate(floatable: Floatable, active: Set<Floatable>, order?: Floatable[], time?: number): void;
14
+ export declare function deactivate(floatable: Floatable, active: Set<Floatable>, order?: Floatable[], time?: number): void;
15
+
16
+ export {};
@@ -0,0 +1,12 @@
1
+ export declare class Floatable {
2
+ anchor: HTMLElement;
3
+ content: HTMLElement;
4
+ activate: number | undefined;
5
+ active: boolean;
6
+ deactivate: number | undefined;
7
+ frame: DOMHighResTimeStamp | undefined;
8
+ constructor(anchor: HTMLElement, content: HTMLElement);
9
+ destroy(): void;
10
+ }
11
+ export declare function activate(floatable: Floatable, active: Set<Floatable>, order?: Floatable[], time?: number): void;
12
+ export declare function deactivate(floatable: Floatable, active: Set<Floatable>, order?: Floatable[], time?: number): void;
@@ -0,0 +1,15 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ export declare class FocusTrap {
4
+ element: HTMLElement;
5
+ active: boolean;
6
+ constructor(element: HTMLElement);
7
+ destroy(): void;
8
+ disable(): void;
9
+ enable(): void;
10
+ }
11
+ export declare function createFocusTrap(element: HTMLElement): FocusTrap;
12
+ export declare const focusTraps: WeakMap<HTMLElement, FocusTrap>;
13
+ export declare const selector = "oui-focus-trap";
14
+
15
+ export {};
@@ -0,0 +1,11 @@
1
+ export declare class FocusTrap {
2
+ element: HTMLElement;
3
+ active: boolean;
4
+ constructor(element: HTMLElement);
5
+ destroy(): void;
6
+ disable(): void;
7
+ enable(): void;
8
+ }
9
+ export declare function createFocusTrap(element: HTMLElement): FocusTrap;
10
+ export declare const focusTraps: WeakMap<HTMLElement, FocusTrap>;
11
+ export declare const selector = "oui-focus-trap";
package/types/index.d.cts CHANGED
@@ -1,3 +1,14 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
+ export declare class OuiPopoverElement extends HTMLElement {
4
+ #private;
5
+ constructor();
6
+ close(): void;
7
+ hidePopover(): void;
8
+ open(): void;
9
+ showPopover(): void;
10
+ toggle(): void;
11
+ togglePopover(options?: boolean): boolean;
12
+ }
13
+
3
14
  export {};
package/types/index.d.ts CHANGED
@@ -1 +1,3 @@
1
- export * from './focus-trap';
1
+ export * from './focus-trap/standalone';
2
+ export * from './popover';
3
+ export * from './tooltip';
@@ -0,0 +1,14 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ export declare class OuiPopoverElement extends HTMLElement {
4
+ #private;
5
+ constructor();
6
+ close(): void;
7
+ hidePopover(): void;
8
+ open(): void;
9
+ showPopover(): void;
10
+ toggle(): void;
11
+ togglePopover(options?: boolean): boolean;
12
+ }
13
+
14
+ export {};
@@ -0,0 +1,15 @@
1
+ declare global {
2
+ interface HTMLElementTagNameMap {
3
+ 'oui-popover': OuiPopoverElement;
4
+ }
5
+ }
6
+ export declare class OuiPopoverElement extends HTMLElement {
7
+ #private;
8
+ constructor();
9
+ close(): void;
10
+ hidePopover(): void;
11
+ open(): void;
12
+ showPopover(): void;
13
+ toggle(): void;
14
+ togglePopover(options?: boolean): boolean;
15
+ }
@@ -1,104 +0,0 @@
1
- "use strict";
2
- const number = require("@oscarpalmer/atoms/number");
3
- const event = require("@oscarpalmer/toretto/event");
4
- const find = require("@oscarpalmer/toretto/find");
5
- const focusable = require("@oscarpalmer/toretto/focusable");
6
- const attributable = require("./attributable.cjs");
7
- class FocusTrap {
8
- constructor(element) {
9
- this.element = element;
10
- element.setAttribute("aria-modal", "true");
11
- element.setAttribute("tabindex", "-1");
12
- }
13
- active = true;
14
- activate() {
15
- this.element.setAttribute("aria-modal", "true");
16
- this.active = true;
17
- }
18
- destroy() {
19
- this.active = false;
20
- this.element = void 0;
21
- }
22
- inactivate() {
23
- this.element.setAttribute("aria-modal", "false");
24
- this.active = false;
25
- inactive.add(this);
26
- }
27
- }
28
- function addFocusTrap(element) {
29
- if (!focusTraps.has(element)) {
30
- focusTraps.set(element, new FocusTrap(element));
31
- }
32
- }
33
- function onEscape(event2) {
34
- if (event2.key !== "Escape") {
35
- return;
36
- }
37
- const element = find.findAncestor(
38
- document.activeElement,
39
- `[${selector}]`
40
- );
41
- focusTraps.get(element)?.inactivate();
42
- }
43
- function onFocusOut() {
44
- if (inactive.size === 0) {
45
- return;
46
- }
47
- requestAnimationFrame(() => {
48
- const element = find.findAncestor(
49
- document.activeElement,
50
- `[${selector}]`
51
- );
52
- if (!focusTraps.has(element)) {
53
- for (const focusTrap of inactive) {
54
- focusTrap.activate();
55
- }
56
- }
57
- });
58
- }
59
- function onTab(event2) {
60
- if (event2.key !== "Tab") {
61
- return;
62
- }
63
- const element = find.findAncestor(
64
- event2.target,
65
- `[${selector}]`
66
- );
67
- if (!(focusTraps.get(element)?.active ?? false)) {
68
- return;
69
- }
70
- event2.preventDefault();
71
- const elements = focusable.getFocusable(element);
72
- const { length } = elements;
73
- const index = elements.indexOf(event2.target);
74
- if (index === -1 || length === 0) {
75
- if (length === 0) {
76
- element.focus();
77
- } else {
78
- elements[0].focus();
79
- }
80
- return;
81
- }
82
- const next = number.clamp(index + (event2.shiftKey ? -1 : 1), 0, length - 1, true);
83
- if (next !== index) {
84
- elements[next].focus();
85
- }
86
- }
87
- function removeFocusTraps(element) {
88
- focusTraps.get(element)?.destroy();
89
- focusTraps.delete(element);
90
- }
91
- const focusTraps = /* @__PURE__ */ new WeakMap();
92
- const inactive = /* @__PURE__ */ new Set();
93
- const selector = "oui-focus-trap";
94
- attributable.attributable(selector, addFocusTrap, removeFocusTraps);
95
- event.on(document, "focusout", onFocusOut, {
96
- capture: true
97
- });
98
- event.on(document, "keydown", onTab, {
99
- capture: true,
100
- passive: false
101
- });
102
- event.on(document, "keydown", onEscape, {
103
- passive: false
104
- });
@@ -1,103 +0,0 @@
1
- import { clamp } from "@oscarpalmer/atoms/number";
2
- import { on } from "@oscarpalmer/toretto/event";
3
- import { findAncestor } from "@oscarpalmer/toretto/find";
4
- import { getFocusable } from "@oscarpalmer/toretto/focusable";
5
- import { attributable } from "./attributable.js";
6
- class FocusTrap {
7
- constructor(element) {
8
- this.element = element;
9
- element.setAttribute("aria-modal", "true");
10
- element.setAttribute("tabindex", "-1");
11
- }
12
- active = true;
13
- activate() {
14
- this.element.setAttribute("aria-modal", "true");
15
- this.active = true;
16
- }
17
- destroy() {
18
- this.active = false;
19
- this.element = void 0;
20
- }
21
- inactivate() {
22
- this.element.setAttribute("aria-modal", "false");
23
- this.active = false;
24
- inactive.add(this);
25
- }
26
- }
27
- function addFocusTrap(element) {
28
- if (!focusTraps.has(element)) {
29
- focusTraps.set(element, new FocusTrap(element));
30
- }
31
- }
32
- function onEscape(event) {
33
- if (event.key !== "Escape") {
34
- return;
35
- }
36
- const element = findAncestor(
37
- document.activeElement,
38
- `[${selector}]`
39
- );
40
- focusTraps.get(element)?.inactivate();
41
- }
42
- function onFocusOut() {
43
- if (inactive.size === 0) {
44
- return;
45
- }
46
- requestAnimationFrame(() => {
47
- const element = findAncestor(
48
- document.activeElement,
49
- `[${selector}]`
50
- );
51
- if (!focusTraps.has(element)) {
52
- for (const focusTrap of inactive) {
53
- focusTrap.activate();
54
- }
55
- }
56
- });
57
- }
58
- function onTab(event) {
59
- if (event.key !== "Tab") {
60
- return;
61
- }
62
- const element = findAncestor(
63
- event.target,
64
- `[${selector}]`
65
- );
66
- if (!(focusTraps.get(element)?.active ?? false)) {
67
- return;
68
- }
69
- event.preventDefault();
70
- const elements = getFocusable(element);
71
- const { length } = elements;
72
- const index = elements.indexOf(event.target);
73
- if (index === -1 || length === 0) {
74
- if (length === 0) {
75
- element.focus();
76
- } else {
77
- elements[0].focus();
78
- }
79
- return;
80
- }
81
- const next = clamp(index + (event.shiftKey ? -1 : 1), 0, length - 1, true);
82
- if (next !== index) {
83
- elements[next].focus();
84
- }
85
- }
86
- function removeFocusTraps(element) {
87
- focusTraps.get(element)?.destroy();
88
- focusTraps.delete(element);
89
- }
90
- const focusTraps = /* @__PURE__ */ new WeakMap();
91
- const inactive = /* @__PURE__ */ new Set();
92
- const selector = "oui-focus-trap";
93
- attributable(selector, addFocusTrap, removeFocusTraps);
94
- on(document, "focusout", onFocusOut, {
95
- capture: true
96
- });
97
- on(document, "keydown", onTab, {
98
- capture: true,
99
- passive: false
100
- });
101
- on(document, "keydown", onEscape, {
102
- passive: false
103
- });