@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.
- package/README.md +2 -0
- package/dist/css/popover.css +9 -0
- package/dist/css/tooltip.css +9 -3
- package/dist/css/value.css +1 -0
- package/dist/js/floatable.cjs +134 -0
- package/dist/js/floatable.js +134 -0
- package/dist/js/focus-trap/embedded.cjs +150 -0
- package/dist/js/focus-trap/embedded.js +150 -0
- package/dist/js/focus-trap/standalone.cjs +8 -0
- package/dist/js/focus-trap/standalone.js +7 -0
- package/dist/js/index.cjs +5 -1
- package/dist/js/index.js +6 -1
- package/dist/js/popover.cjs +160 -0
- package/dist/js/popover.js +160 -0
- package/dist/js/tooltip.cjs +83 -126
- package/dist/js/tooltip.js +83 -126
- package/package.json +7 -7
- package/src/css/popover.scss +9 -0
- package/src/css/tooltip.scss +7 -3
- package/src/js/floatable.ts +196 -0
- package/src/js/focus-trap/embedded.ts +215 -0
- package/src/js/focus-trap/standalone.ts +9 -0
- package/src/js/index.ts +3 -1
- package/src/js/popover.ts +221 -0
- package/src/js/tooltip.ts +97 -161
- package/types/floatable.d.cts +16 -0
- package/types/floatable.d.ts +12 -0
- package/types/focus-trap/embedded.d.cts +15 -0
- package/types/focus-trap/embedded.d.ts +11 -0
- package/types/index.d.cts +11 -0
- package/types/index.d.ts +3 -1
- package/types/popover.d.cts +14 -0
- package/types/popover.d.ts +15 -0
- package/dist/js/focus-trap.cjs +0 -104
- package/dist/js/focus-trap.js +0 -103
- package/src/js/focus-trap.ts +0 -133
- /package/types/{focus-trap.d.cts → focus-trap/standalone.d.cts} +0 -0
- /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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
class Tooltip {
|
|
17
|
+
abortController = new AbortController();
|
|
18
|
+
attributes: Attributes;
|
|
19
|
+
floatable: Floatable;
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
constructor(anchor: HTMLElement, content: Content) {
|
|
22
|
+
this.attributes = content.attributes;
|
|
23
|
+
this.floatable = new Floatable(anchor, content.element);
|
|
23
24
|
|
|
24
|
-
|
|
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.
|
|
35
|
-
this.
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (tooltip.active) {
|
|
42
|
+
function addTooltip(anchor: HTMLElement): void {
|
|
43
|
+
if (tooltips.has(anchor)) {
|
|
46
44
|
return;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
tooltip.active = true;
|
|
47
|
+
const content = getContent(anchor);
|
|
51
48
|
|
|
52
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
60
|
+
const wrapper = getWrapper(anchor);
|
|
81
61
|
|
|
82
|
-
|
|
83
|
-
}, time ?? 250);
|
|
84
|
-
}
|
|
62
|
+
const labelledElements = getElements(attributes.ariaLabelledBy);
|
|
85
63
|
|
|
86
|
-
|
|
87
|
-
|
|
64
|
+
if (labelledElements.length > 0) {
|
|
65
|
+
anchor.setAttribute('aria-labelledby', wrapper.id);
|
|
88
66
|
|
|
89
|
-
|
|
67
|
+
for (const element of labelledElements) {
|
|
68
|
+
wrapper.append(element);
|
|
69
|
+
}
|
|
90
70
|
|
|
91
|
-
|
|
71
|
+
return {
|
|
72
|
+
attributes,
|
|
73
|
+
element: wrapper,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
92
76
|
|
|
93
|
-
const
|
|
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 (
|
|
100
|
-
|
|
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
|
|
86
|
+
return {
|
|
87
|
+
attributes,
|
|
88
|
+
element: wrapper,
|
|
89
|
+
};
|
|
107
90
|
}
|
|
108
91
|
|
|
109
|
-
let content =
|
|
92
|
+
let content = anchor.getAttribute('aria-label');
|
|
110
93
|
|
|
111
94
|
if (isNullableOrWhitespace(content)) {
|
|
112
|
-
content =
|
|
95
|
+
content = anchor.getAttribute('aria-description');
|
|
96
|
+
}
|
|
113
97
|
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
108
|
+
return {
|
|
109
|
+
attributes,
|
|
110
|
+
element: wrapper,
|
|
111
|
+
};
|
|
126
112
|
}
|
|
127
113
|
|
|
128
|
-
function
|
|
129
|
-
|
|
114
|
+
function getElements(attribute: string | null): HTMLElement[] {
|
|
115
|
+
const ids = attribute?.split(/\s+/) ?? [];
|
|
116
|
+
const elements: HTMLElement[] = [];
|
|
130
117
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
128
|
+
cloned.hidden = false;
|
|
136
129
|
|
|
137
|
-
|
|
138
|
-
|
|
130
|
+
elements.push(cloned);
|
|
131
|
+
}
|
|
139
132
|
}
|
|
140
133
|
|
|
141
|
-
return
|
|
134
|
+
return elements;
|
|
142
135
|
}
|
|
143
136
|
|
|
144
|
-
function getWrapper(
|
|
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 =
|
|
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
|
|
159
|
-
|
|
160
|
-
content
|
|
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
|
-
|
|
185
|
-
left,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
155
|
+
anchor.insertAdjacentElement('afterend', content);
|
|
188
156
|
|
|
189
|
-
|
|
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(
|
|
165
|
+
activate(floatable, active, undefined, 250);
|
|
199
166
|
},
|
|
200
167
|
{
|
|
201
|
-
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(
|
|
178
|
+
deactivate(floatable, active, undefined, 250);
|
|
212
179
|
},
|
|
213
180
|
{
|
|
214
|
-
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
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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<
|
|
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
|
|
286
|
-
deactivate(
|
|
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
|
@@ -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
|
+
}
|
package/dist/js/focus-trap.cjs
DELETED
|
@@ -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
|
-
});
|
package/dist/js/focus-trap.js
DELETED
|
@@ -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
|
-
});
|