@oscarpalmer/oui 0.17.0 → 0.19.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/dist/css/badge.css +13 -13
- package/dist/css/button.css +9 -9
- package/dist/css/color.css +99 -99
- package/dist/css/content.css +10 -12
- package/dist/css/flex.css +39 -39
- package/dist/css/form.css +47 -60
- package/dist/css/message.css +10 -6
- package/dist/css/reset.css +178 -57
- package/dist/css/table.css +4 -4
- package/dist/css/tooltip.css +26 -1
- package/dist/js/attributable.full.js +36 -0
- package/dist/js/attributable.js +30 -45
- package/dist/js/floatable.full.js +398 -0
- package/dist/js/floatable.js +254 -169
- package/dist/js/focus-trap/embedded.full.js +408 -0
- package/dist/js/focus-trap/embedded.js +131 -131
- package/dist/js/focus-trap/standalone.full.js +447 -0
- package/dist/js/focus-trap/standalone.js +4 -4
- package/dist/js/index.js +3 -4
- package/dist/js/oui.full.js +1573 -0
- package/dist/js/popover.full.js +760 -0
- package/dist/js/popover.js +84 -163
- package/dist/js/splitter.full.js +769 -0
- package/dist/js/splitter.js +264 -246
- package/dist/js/tooltip.full.js +613 -0
- package/dist/js/tooltip.js +160 -151
- package/package.json +35 -63
- package/src/css/a11y.scss +1 -1
- package/src/css/badge.scss +23 -19
- package/src/css/button.scss +9 -9
- package/src/css/color.scss +99 -99
- package/src/css/content.scss +6 -9
- package/src/css/flex.scss +21 -20
- package/src/css/form.scss +47 -62
- package/src/css/message.scss +11 -12
- package/src/css/mixins.scss +16 -10
- package/src/css/popover.scss +2 -2
- package/src/css/reset.scss +181 -55
- package/src/css/splitter.scss +15 -13
- package/src/css/table.scss +11 -11
- package/src/css/tooltip.scss +31 -2
- package/src/js/attributable.ts +3 -10
- package/src/js/floatable.ts +370 -141
- package/src/js/focus-trap/embedded.ts +141 -74
- package/src/js/focus-trap/standalone.ts +4 -4
- package/src/js/index.ts +1 -0
- package/src/js/popover.ts +52 -122
- package/src/js/splitter.ts +248 -131
- package/src/js/tooltip.ts +143 -68
- package/types/floatable.d.ts +17 -10
- package/types/focus-trap/embedded.d.ts +18 -5
- package/types/index.d.ts +1 -0
- package/types/popover.d.ts +2 -0
- package/types/splitter.d.ts +13 -1
- package/types/tooltip.d.ts +2 -1
- package/dist/css/value.css +0 -1
- package/dist/js/attributable.cjs +0 -51
- package/dist/js/floatable.cjs +0 -176
- package/dist/js/focus-trap/embedded.cjs +0 -150
- package/dist/js/focus-trap/standalone.cjs +0 -8
- package/dist/js/index.cjs +0 -6
- package/dist/js/popover.cjs +0 -166
- package/dist/js/splitter.cjs +0 -272
- package/dist/js/tooltip.cjs +0 -166
- package/types/attributable.d.cts +0 -5
- package/types/floatable.d.cts +0 -20
- package/types/focus-trap/embedded.d.cts +0 -15
- package/types/focus-trap/standalone.d.cts +0 -3
- package/types/index.d.cts +0 -14
- package/types/popover.d.cts +0 -14
- package/types/splitter.d.cts +0 -10
- package/types/tooltip.d.cts +0 -3
|
@@ -4,57 +4,119 @@ import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
|
4
4
|
import {getFocusable} from '@oscarpalmer/toretto/focusable';
|
|
5
5
|
|
|
6
6
|
export class FocusTrap {
|
|
7
|
-
|
|
7
|
+
disabled = false;
|
|
8
|
+
|
|
9
|
+
connected = true;
|
|
10
|
+
|
|
11
|
+
element: HTMLElement;
|
|
12
|
+
|
|
13
|
+
embedded: boolean;
|
|
14
|
+
|
|
15
|
+
get contain(): boolean {
|
|
16
|
+
return this.element.hasAttribute(FOCUS_TRAP_CONTAIN);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get noescape(): boolean {
|
|
20
|
+
return this.element.hasAttribute(FOCUS_TRAP_NOESCAPE);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor(element: HTMLElement, options?: Options) {
|
|
24
|
+
this.element = element;
|
|
25
|
+
this.embedded = options != null;
|
|
8
26
|
|
|
9
|
-
constructor(public element: HTMLElement) {
|
|
10
|
-
element.setAttribute('aria-modal', 'true');
|
|
11
27
|
element.setAttribute('tabindex', '-1');
|
|
28
|
+
|
|
29
|
+
if (options != null) {
|
|
30
|
+
element.setAttribute(FOCUS_TRAP_SELECTOR, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (options?.noescape) {
|
|
34
|
+
element.setAttribute(FOCUS_TRAP_NOESCAPE, '');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
connect(): void {
|
|
39
|
+
setConnected(this, true);
|
|
12
40
|
}
|
|
13
41
|
|
|
14
|
-
destroy() {
|
|
42
|
+
destroy(): void {
|
|
15
43
|
this.element = undefined as never;
|
|
16
44
|
}
|
|
17
45
|
|
|
18
|
-
disable() {
|
|
19
|
-
if (this.
|
|
46
|
+
disable(): void {
|
|
47
|
+
if (!(this.disabled || this.noescape)) {
|
|
20
48
|
setDisabled(this, true);
|
|
21
49
|
}
|
|
22
50
|
}
|
|
23
51
|
|
|
24
|
-
|
|
25
|
-
|
|
52
|
+
disconnect(): void {
|
|
53
|
+
setConnected(this, false);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
enable(): void {
|
|
57
|
+
if (this.disabled) {
|
|
26
58
|
setDisabled(this, false);
|
|
27
59
|
}
|
|
28
60
|
}
|
|
61
|
+
|
|
62
|
+
focus(last: boolean): void {
|
|
63
|
+
tabToTrap(this, getFocusable(this.element), this.element, last ? -1 : 0);
|
|
64
|
+
}
|
|
29
65
|
}
|
|
30
66
|
|
|
67
|
+
type Options = {
|
|
68
|
+
noescape: boolean;
|
|
69
|
+
};
|
|
70
|
+
|
|
31
71
|
//
|
|
32
72
|
|
|
33
|
-
export function createFocusTrap(element: HTMLElement): FocusTrap {
|
|
34
|
-
let focusTrap =
|
|
73
|
+
export function createFocusTrap(element: HTMLElement, options?: Options): FocusTrap {
|
|
74
|
+
let focusTrap = FOCUSTRAPS_ALL.get(element);
|
|
35
75
|
|
|
36
76
|
if (focusTrap == null) {
|
|
37
|
-
focusTrap = new FocusTrap(element);
|
|
77
|
+
focusTrap = new FocusTrap(element, options);
|
|
38
78
|
|
|
39
|
-
|
|
79
|
+
FOCUSTRAPS_ALL.set(element, focusTrap);
|
|
40
80
|
}
|
|
41
81
|
|
|
42
82
|
return focusTrap;
|
|
43
83
|
}
|
|
44
84
|
|
|
45
85
|
function onEscape(event: KeyboardEvent): void {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
86
|
+
let element = findAncestor(event.target as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement;
|
|
87
|
+
|
|
88
|
+
let focusTrap = FOCUSTRAPS_ALL.get(element);
|
|
89
|
+
|
|
90
|
+
if (element == null || focusTrap == null) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
while (focusTrap.disabled) {
|
|
95
|
+
element = findAncestor(
|
|
96
|
+
focusTrap.element.parentElement as HTMLElement,
|
|
97
|
+
ATTRIBUTE_SELECTOR,
|
|
98
|
+
) as HTMLElement;
|
|
99
|
+
|
|
100
|
+
focusTrap = FOCUSTRAPS_ALL.get(element);
|
|
101
|
+
|
|
102
|
+
if (element == null || focusTrap == null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!focusTrap.disabled) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
focusTrap?.disable();
|
|
51
112
|
}
|
|
52
113
|
|
|
53
114
|
function onFocusIn(event: Event): void {
|
|
54
|
-
|
|
55
|
-
findAncestor(event.target as HTMLElement,
|
|
56
|
-
|
|
57
|
-
|
|
115
|
+
const focusTrap = FOCUSTRAPS_ALL.get(
|
|
116
|
+
findAncestor(event.target as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
lastTarget = focusTrap == null ? undefined : (event.target as HTMLElement);
|
|
58
120
|
}
|
|
59
121
|
|
|
60
122
|
function onKeydown(event: KeyboardEvent): void {
|
|
@@ -73,47 +135,42 @@ function onKeydown(event: KeyboardEvent): void {
|
|
|
73
135
|
}
|
|
74
136
|
|
|
75
137
|
function onPointerdown(event: MouseEvent | TouchEvent): void {
|
|
76
|
-
const lastFocusTrap = findAncestor(
|
|
77
|
-
lastTarget as HTMLElement,
|
|
78
|
-
`[${selector}]`,
|
|
79
|
-
) as HTMLElement;
|
|
138
|
+
const lastFocusTrap = findAncestor(lastTarget as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement;
|
|
80
139
|
|
|
81
140
|
const nextFocusTrap = findAncestor(
|
|
82
141
|
event.target as HTMLElement,
|
|
83
|
-
|
|
142
|
+
ATTRIBUTE_SELECTOR,
|
|
84
143
|
) as HTMLElement;
|
|
85
144
|
|
|
86
145
|
if (lastFocusTrap != null && nextFocusTrap !== lastFocusTrap) {
|
|
87
|
-
|
|
146
|
+
if (FOCUSTRAPS_ALL.get(lastFocusTrap)?.contain ?? false) {
|
|
147
|
+
event.preventDefault();
|
|
88
148
|
|
|
89
|
-
|
|
149
|
+
lastTarget?.focus();
|
|
150
|
+
}
|
|
90
151
|
}
|
|
91
152
|
|
|
92
|
-
if (
|
|
153
|
+
if (FOCUSTRAPS_DISABLED.size === 0) {
|
|
93
154
|
return;
|
|
94
155
|
}
|
|
95
156
|
|
|
96
|
-
for (const focusTrap of
|
|
157
|
+
for (const focusTrap of FOCUSTRAPS_DISABLED) {
|
|
97
158
|
if (focusTrap.element !== nextFocusTrap) {
|
|
98
159
|
focusTrap.enable();
|
|
99
|
-
return;
|
|
100
160
|
}
|
|
101
161
|
}
|
|
102
162
|
}
|
|
103
163
|
|
|
104
164
|
function onTab(event: KeyboardEvent): void {
|
|
105
|
-
const element = findAncestor(
|
|
106
|
-
event.target as HTMLElement,
|
|
107
|
-
`[${selector}]`,
|
|
108
|
-
) as HTMLElement;
|
|
165
|
+
const element = findAncestor(event.target as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement;
|
|
109
166
|
|
|
110
|
-
const focusTrap =
|
|
167
|
+
const focusTrap = FOCUSTRAPS_ALL.get(element);
|
|
111
168
|
|
|
112
169
|
if (focusTrap == null) {
|
|
113
170
|
return;
|
|
114
171
|
}
|
|
115
172
|
|
|
116
|
-
if (focusTrap.
|
|
173
|
+
if (!focusTrap.disabled) {
|
|
117
174
|
event.preventDefault();
|
|
118
175
|
}
|
|
119
176
|
|
|
@@ -124,40 +181,39 @@ function onTab(event: KeyboardEvent): void {
|
|
|
124
181
|
if (index === -1 || elements.length === 0) {
|
|
125
182
|
tabToTrap(focusTrap, elements, element);
|
|
126
183
|
} else {
|
|
127
|
-
tabToElement(
|
|
184
|
+
tabToElement(focusTrap, elements, index, event.shiftKey ? -1 : 1);
|
|
128
185
|
}
|
|
129
186
|
}
|
|
130
187
|
|
|
131
|
-
function
|
|
132
|
-
focusTrap.
|
|
188
|
+
function setConnected(focusTrap: FocusTrap, value: boolean): void {
|
|
189
|
+
if (!focusTrap.embedded || focusTrap.connected === value) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
focusTrap.connected = value;
|
|
194
|
+
}
|
|
133
195
|
|
|
134
|
-
|
|
196
|
+
function setDisabled(focusTrap: FocusTrap, value: boolean): void {
|
|
197
|
+
focusTrap.disabled = value;
|
|
135
198
|
|
|
136
199
|
if (value) {
|
|
137
|
-
|
|
200
|
+
FOCUSTRAPS_DISABLED.add(focusTrap);
|
|
138
201
|
} else {
|
|
139
|
-
|
|
202
|
+
FOCUSTRAPS_DISABLED.delete(focusTrap);
|
|
140
203
|
}
|
|
141
204
|
}
|
|
142
205
|
|
|
143
206
|
function tabToElement(
|
|
144
|
-
event: KeyboardEvent,
|
|
145
207
|
focusTrap: FocusTrap,
|
|
146
208
|
elements: Element[],
|
|
147
209
|
index: number,
|
|
210
|
+
modifier: number,
|
|
148
211
|
): void {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
0,
|
|
152
|
-
elements.length - 1,
|
|
153
|
-
true,
|
|
154
|
-
);
|
|
212
|
+
const {length} = elements;
|
|
213
|
+
const next = clamp(index + modifier, 0, length - 1, true);
|
|
155
214
|
|
|
156
|
-
if (
|
|
157
|
-
if (
|
|
158
|
-
(index === length - 1 && next === 0) ||
|
|
159
|
-
(index === 0 && next === length - 1)
|
|
160
|
-
) {
|
|
215
|
+
if (focusTrap.disabled) {
|
|
216
|
+
if ((index === length - 1 && next === 0) || (index === 0 && next === length - 1)) {
|
|
161
217
|
lastTarget = undefined;
|
|
162
218
|
|
|
163
219
|
focusTrap.enable();
|
|
@@ -166,50 +222,61 @@ function tabToElement(
|
|
|
166
222
|
return;
|
|
167
223
|
}
|
|
168
224
|
|
|
169
|
-
|
|
225
|
+
const element = elements[next] as HTMLElement;
|
|
226
|
+
const elementFocusTrap = FOCUSTRAPS_ALL.get(element);
|
|
170
227
|
|
|
171
|
-
|
|
228
|
+
if (elementFocusTrap != null) {
|
|
229
|
+
elementFocusTrap.focus(modifier === -1);
|
|
230
|
+
} else {
|
|
231
|
+
(elements[next] as HTMLElement).focus();
|
|
232
|
+
|
|
233
|
+
lastTarget = elements[next] as HTMLElement;
|
|
234
|
+
}
|
|
172
235
|
}
|
|
173
236
|
|
|
174
237
|
function tabToTrap(
|
|
175
238
|
focusTrap: FocusTrap,
|
|
176
239
|
elements: Element[],
|
|
177
240
|
element: HTMLElement,
|
|
241
|
+
index?: number,
|
|
178
242
|
): void {
|
|
179
|
-
if (focusTrap.
|
|
180
|
-
|
|
243
|
+
if (focusTrap.disabled) {
|
|
244
|
+
lastTarget = undefined;
|
|
245
|
+
|
|
246
|
+
focusTrap.enable();
|
|
247
|
+
} else {
|
|
248
|
+
const next = elements.length === 0 ? element : elements.at(index ?? 0);
|
|
181
249
|
|
|
182
250
|
(next as HTMLElement).focus();
|
|
183
251
|
|
|
184
252
|
lastTarget = next as HTMLElement;
|
|
185
|
-
} else {
|
|
186
|
-
lastTarget = undefined;
|
|
187
|
-
|
|
188
|
-
focusTrap.enable();
|
|
189
253
|
}
|
|
190
254
|
}
|
|
191
255
|
|
|
192
256
|
//
|
|
193
257
|
|
|
194
|
-
const
|
|
258
|
+
export const FOCUS_TRAP_SELECTOR = 'oui-focus-trap';
|
|
195
259
|
|
|
196
|
-
const
|
|
260
|
+
const ATTRIBUTE_SELECTOR = `[${FOCUS_TRAP_SELECTOR}]`;
|
|
261
|
+
|
|
262
|
+
const EVENT_OPTIONS: AddEventListenerOptions = {
|
|
197
263
|
capture: true,
|
|
198
264
|
passive: false,
|
|
199
265
|
};
|
|
200
266
|
|
|
201
|
-
export const
|
|
202
|
-
|
|
203
|
-
let lastTarget: HTMLElement | undefined;
|
|
267
|
+
export const FOCUS_TRAP_CONTAIN = `${FOCUS_TRAP_SELECTOR}-contain`;
|
|
204
268
|
|
|
205
|
-
export const
|
|
269
|
+
export const FOCUS_TRAP_NOESCAPE = `${FOCUS_TRAP_SELECTOR}-noescape`;
|
|
206
270
|
|
|
207
|
-
|
|
271
|
+
export const FOCUSTRAPS_ALL: WeakMap<HTMLElement, FocusTrap> = new WeakMap();
|
|
208
272
|
|
|
209
|
-
|
|
273
|
+
const FOCUSTRAPS_DISABLED: Set<FocusTrap> = new Set();
|
|
210
274
|
|
|
211
|
-
|
|
275
|
+
let lastTarget: HTMLElement | undefined;
|
|
212
276
|
|
|
213
|
-
|
|
277
|
+
//
|
|
214
278
|
|
|
215
|
-
on(document, '
|
|
279
|
+
on(document, 'focusin', onFocusIn, EVENT_OPTIONS);
|
|
280
|
+
on(document, 'keydown', onKeydown, EVENT_OPTIONS);
|
|
281
|
+
on(document, 'mousedown', onPointerdown, EVENT_OPTIONS);
|
|
282
|
+
on(document, 'touchstart', onPointerdown, EVENT_OPTIONS);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {attributable} from '../attributable';
|
|
2
|
-
import {createFocusTrap,
|
|
2
|
+
import {createFocusTrap, FOCUS_TRAP_SELECTOR, FOCUSTRAPS_ALL} from './embedded';
|
|
3
3
|
|
|
4
4
|
function removeFocusTraps(element: HTMLElement): void {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
FOCUSTRAPS_ALL.get(element)?.destroy();
|
|
6
|
+
FOCUSTRAPS_ALL.delete(element);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
attributable(
|
|
9
|
+
attributable(FOCUS_TRAP_SELECTOR, createFocusTrap, removeFocusTraps);
|
package/src/js/index.ts
CHANGED
package/src/js/popover.ts
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
|
|
2
|
-
import {on} from '@oscarpalmer/toretto/event';
|
|
3
|
-
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
4
2
|
import {getFocusable} from '@oscarpalmer/toretto/focusable';
|
|
5
|
-
import {
|
|
3
|
+
import {Floatable} from './floatable';
|
|
6
4
|
import {createFocusTrap, type FocusTrap} from './focus-trap/embedded';
|
|
7
5
|
|
|
8
6
|
declare global {
|
|
7
|
+
// biome-ignore lint/nursery/useConsistentTypeDefinitions: Extending builtins to allow custom element type help
|
|
9
8
|
interface HTMLElementTagNameMap {
|
|
10
9
|
'oui-popover': OuiPopoverElement;
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const mapped = new Map<HTMLElement, Floatable>();
|
|
15
|
-
|
|
16
13
|
let index = 0;
|
|
17
14
|
|
|
18
15
|
export class OuiPopoverElement extends HTMLElement {
|
|
19
|
-
#floatable: Floatable;
|
|
20
|
-
#focusTrap: FocusTrap;
|
|
16
|
+
readonly #floatable: Floatable;
|
|
17
|
+
readonly #focusTrap: FocusTrap;
|
|
21
18
|
|
|
22
19
|
constructor() {
|
|
23
20
|
super();
|
|
24
21
|
|
|
25
|
-
const content = this.querySelector(
|
|
26
|
-
const toggle = this.querySelector(
|
|
22
|
+
const content = this.querySelector(SELECTOR_CONTENT);
|
|
23
|
+
const toggle = this.querySelector(SELECTOR_TOGGLE);
|
|
27
24
|
|
|
28
25
|
if (!(content instanceof HTMLElement)) {
|
|
29
26
|
throw new Error(
|
|
@@ -40,40 +37,56 @@ export class OuiPopoverElement extends HTMLElement {
|
|
|
40
37
|
content.hidden = true;
|
|
41
38
|
|
|
42
39
|
content.setAttribute('role', 'dialog');
|
|
43
|
-
content.setAttribute('
|
|
44
|
-
content.setAttribute('oui-focus-trap-noescape', '');
|
|
40
|
+
content.setAttribute('aria-modal', 'true');
|
|
45
41
|
|
|
46
42
|
if (isNullableOrWhitespace(content.id)) {
|
|
47
|
-
content.setAttribute('id',
|
|
43
|
+
content.setAttribute('id', `${ATTRIBUTE_ID_PREFIX}_${++index}`);
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
toggle.setAttribute('aria-controls', content.id);
|
|
51
47
|
toggle.setAttribute('aria-haspopup', 'dialog');
|
|
52
48
|
|
|
53
|
-
this.#floatable = new Floatable(
|
|
54
|
-
toggle,
|
|
49
|
+
this.#floatable = new Floatable({
|
|
55
50
|
content,
|
|
56
|
-
|
|
57
|
-
'below-start',
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
51
|
+
anchor: toggle,
|
|
52
|
+
defaultPosition: 'below-start',
|
|
53
|
+
interactive: true,
|
|
54
|
+
positionAttribute: 'position',
|
|
55
|
+
preferAbove: false,
|
|
56
|
+
onAfter: (active: boolean): void => {
|
|
57
|
+
if (active) {
|
|
58
|
+
focus(content);
|
|
59
|
+
} else if (!this.#floatable.ignoreFocus) {
|
|
60
|
+
toggle.focus();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.#focusTrap = createFocusTrap(content, {
|
|
66
|
+
noescape: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
POPOVERS.set(toggle, this.#floatable);
|
|
70
|
+
POPOVERS.set(content, this.#floatable);
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
close(): void {
|
|
68
74
|
this.hidePopover();
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
connectedCallback(): void {
|
|
78
|
+
this.#floatable.enable();
|
|
79
|
+
this.#focusTrap.connect();
|
|
80
|
+
}
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
disconnectedCallback(): void {
|
|
83
|
+
this.#floatable.disable();
|
|
84
|
+
this.#focusTrap.disconnect();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override hidePopover(): void {
|
|
88
|
+
if (this.#floatable.active && this.isConnected) {
|
|
89
|
+
this.#floatable.toggle(false);
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
92
|
|
|
@@ -81,9 +94,9 @@ export class OuiPopoverElement extends HTMLElement {
|
|
|
81
94
|
this.showPopover();
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
showPopover(): void {
|
|
85
|
-
if (!this.#floatable.active) {
|
|
86
|
-
|
|
97
|
+
override showPopover(): void {
|
|
98
|
+
if (!this.#floatable.active && this.isConnected) {
|
|
99
|
+
this.#floatable.toggle(true);
|
|
87
100
|
}
|
|
88
101
|
}
|
|
89
102
|
|
|
@@ -91,7 +104,7 @@ export class OuiPopoverElement extends HTMLElement {
|
|
|
91
104
|
this.togglePopover();
|
|
92
105
|
}
|
|
93
106
|
|
|
94
|
-
togglePopover(options?: boolean): boolean {
|
|
107
|
+
override togglePopover(options?: boolean): boolean {
|
|
95
108
|
if (typeof options === 'boolean') {
|
|
96
109
|
if (options === true) {
|
|
97
110
|
this.showPopover();
|
|
@@ -116,32 +129,6 @@ export class OuiPopoverElement extends HTMLElement {
|
|
|
116
129
|
|
|
117
130
|
//
|
|
118
131
|
|
|
119
|
-
function closeAll(): void {
|
|
120
|
-
for (const floatable of active) {
|
|
121
|
-
deactivate(floatable, active, order);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function closeAbove(element: HTMLElement): void {
|
|
126
|
-
const index = order.findIndex(
|
|
127
|
-
floatable => floatable.anchor === element || floatable.content === element,
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
if (index === -1 || index === order.length - 1) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
while (index < order.length - 1) {
|
|
135
|
-
const floatable = order.pop();
|
|
136
|
-
|
|
137
|
-
if (floatable != null) {
|
|
138
|
-
deactivate(floatable, active, order);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
focus(order[index].content);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
132
|
function focus(content: HTMLElement): void {
|
|
146
133
|
let target = getFocusable(content)[0];
|
|
147
134
|
|
|
@@ -154,75 +141,18 @@ function focus(content: HTMLElement): void {
|
|
|
154
141
|
});
|
|
155
142
|
}
|
|
156
143
|
|
|
157
|
-
|
|
158
|
-
const related = findAncestor(
|
|
159
|
-
event.target as never,
|
|
160
|
-
'[oui-popover-toggle], [oui-popover-content]',
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
if (!(related instanceof HTMLElement)) {
|
|
164
|
-
closeAll();
|
|
165
|
-
} else if (related.hasAttribute('oui-popover-content')) {
|
|
166
|
-
closeAbove(related);
|
|
167
|
-
} else {
|
|
168
|
-
toggle(related);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function onKeydown(event: KeyboardEvent): void {
|
|
173
|
-
if (event.key !== 'Escape' || order.length === 0) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const related = findAncestor(event.target as never, '[oui-popover-content]');
|
|
178
|
-
|
|
179
|
-
if (!(related instanceof HTMLElement)) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
closeAbove(related);
|
|
184
|
-
|
|
185
|
-
const floatable = mapped.get(related);
|
|
186
|
-
|
|
187
|
-
if (floatable != null) {
|
|
188
|
-
deactivate(floatable, active, order);
|
|
189
|
-
|
|
190
|
-
floatable.anchor.focus();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function toggle(anchor: HTMLElement): void {
|
|
195
|
-
const content = findAncestor(anchor, '[oui-popover-content]');
|
|
196
|
-
|
|
197
|
-
if (content instanceof HTMLElement) {
|
|
198
|
-
closeAbove(content);
|
|
199
|
-
} else {
|
|
200
|
-
closeAll();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const floatable = mapped.get(anchor);
|
|
144
|
+
//
|
|
204
145
|
|
|
205
|
-
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
146
|
+
const ATTRIBUTE_ID_PREFIX = 'oui_popover_content_';
|
|
208
147
|
|
|
209
|
-
|
|
210
|
-
deactivate(floatable, active, order);
|
|
211
|
-
} else {
|
|
212
|
-
activate(floatable, active, order);
|
|
213
|
-
focus(floatable.content);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
148
|
+
const POPOVERS: Map<HTMLElement, Floatable> = new Map();
|
|
216
149
|
|
|
217
|
-
|
|
150
|
+
const SELECTOR = 'oui-popover';
|
|
218
151
|
|
|
219
|
-
|
|
152
|
+
const SELECTOR_CONTENT = `:scope > [${SELECTOR}-content]`;
|
|
220
153
|
|
|
221
|
-
|
|
222
|
-
on(document, 'keydown', onKeydown);
|
|
154
|
+
const SELECTOR_TOGGLE = `:scope > [${SELECTOR}-toggle]`;
|
|
223
155
|
|
|
224
156
|
//
|
|
225
157
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const order: Floatable[] = [];
|
|
158
|
+
customElements.define(SELECTOR, OuiPopoverElement);
|