@oscarpalmer/oui 0.16.0 → 0.18.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 +3 -3
- package/dist/css/content.css +9 -11
- package/dist/css/form.css +36 -22
- package/dist/css/message.css +4 -0
- package/dist/css/popover.css +3 -0
- package/dist/css/reset.css +176 -56
- package/dist/css/splitter.css +3 -3
- package/dist/css/tooltip.css +8 -0
- package/dist/js/attributable.full.js +48 -0
- package/dist/js/attributable.js +30 -45
- package/dist/js/floatable.full.js +413 -0
- package/dist/js/floatable.js +259 -132
- package/dist/js/focus-trap/embedded.full.js +351 -0
- package/dist/js/focus-trap/embedded.js +131 -131
- package/dist/js/focus-trap/standalone.full.js +402 -0
- package/dist/js/focus-trap/standalone.js +4 -4
- package/dist/js/index.js +4 -5
- package/dist/js/oui.full.js +1596 -0
- package/dist/js/popover.full.js +822 -0
- package/dist/js/popover.js +83 -156
- package/dist/js/splitter.full.js +605 -0
- package/dist/js/splitter.js +279 -200
- package/dist/js/tooltip.full.js +679 -0
- package/dist/js/tooltip.js +159 -144
- package/package.json +31 -59
- package/src/css/content.scss +1 -2
- package/src/css/form.scss +12 -4
- package/src/css/message.scss +3 -0
- package/src/css/mixins.scss +7 -0
- package/src/css/popover.scss +4 -0
- package/src/css/reset.scss +180 -54
- package/src/css/splitter.scss +3 -3
- package/src/css/tooltip.scss +8 -0
- package/src/js/floatable.ts +458 -124
- package/src/js/focus-trap/embedded.ts +149 -64
- package/src/js/focus-trap/standalone.ts +4 -4
- package/src/js/index.ts +1 -0
- package/src/js/popover.ts +51 -114
- package/src/js/splitter.ts +346 -109
- package/src/js/tooltip.ts +157 -63
- package/types/floatable.d.ts +19 -7
- 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/js/attributable.cjs +0 -51
- package/dist/js/floatable.cjs +0 -134
- 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 -160
- package/dist/js/splitter.cjs +0 -211
- package/dist/js/tooltip.cjs +0 -160
- package/types/attributable.d.cts +0 -5
- package/types/floatable.d.cts +0 -16
- 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,128 @@ 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(
|
|
34
|
-
|
|
73
|
+
export function createFocusTrap(
|
|
74
|
+
element: HTMLElement,
|
|
75
|
+
options?: Options,
|
|
76
|
+
): FocusTrap {
|
|
77
|
+
let focusTrap = FOCUSTRAPS_ALL.get(element);
|
|
35
78
|
|
|
36
79
|
if (focusTrap == null) {
|
|
37
|
-
focusTrap = new FocusTrap(element);
|
|
80
|
+
focusTrap = new FocusTrap(element, options);
|
|
38
81
|
|
|
39
|
-
|
|
82
|
+
FOCUSTRAPS_ALL.set(element, focusTrap);
|
|
40
83
|
}
|
|
41
84
|
|
|
42
85
|
return focusTrap;
|
|
43
86
|
}
|
|
44
87
|
|
|
45
88
|
function onEscape(event: KeyboardEvent): void {
|
|
46
|
-
|
|
47
|
-
.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
89
|
+
let element = findAncestor(
|
|
90
|
+
event.target as HTMLElement,
|
|
91
|
+
ATTRIBUTE_SELECTOR,
|
|
92
|
+
) as HTMLElement;
|
|
93
|
+
|
|
94
|
+
let focusTrap = FOCUSTRAPS_ALL.get(element);
|
|
95
|
+
|
|
96
|
+
if (element == null || focusTrap == null) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
while (focusTrap.disabled) {
|
|
101
|
+
element = findAncestor(
|
|
102
|
+
focusTrap.element.parentElement as HTMLElement,
|
|
103
|
+
ATTRIBUTE_SELECTOR,
|
|
104
|
+
) as HTMLElement;
|
|
105
|
+
|
|
106
|
+
focusTrap = FOCUSTRAPS_ALL.get(element);
|
|
107
|
+
|
|
108
|
+
if (element == null || focusTrap == null) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!focusTrap.disabled) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
focusTrap?.disable();
|
|
51
118
|
}
|
|
52
119
|
|
|
53
120
|
function onFocusIn(event: Event): void {
|
|
54
|
-
|
|
55
|
-
findAncestor(
|
|
56
|
-
|
|
57
|
-
|
|
121
|
+
const focusTrap = FOCUSTRAPS_ALL.get(
|
|
122
|
+
findAncestor(
|
|
123
|
+
event.target as HTMLElement,
|
|
124
|
+
ATTRIBUTE_SELECTOR,
|
|
125
|
+
) as HTMLElement,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
lastTarget = focusTrap == null ? undefined : (event.target as HTMLElement);
|
|
58
129
|
}
|
|
59
130
|
|
|
60
131
|
function onKeydown(event: KeyboardEvent): void {
|
|
@@ -75,28 +146,29 @@ function onKeydown(event: KeyboardEvent): void {
|
|
|
75
146
|
function onPointerdown(event: MouseEvent | TouchEvent): void {
|
|
76
147
|
const lastFocusTrap = findAncestor(
|
|
77
148
|
lastTarget as HTMLElement,
|
|
78
|
-
|
|
149
|
+
ATTRIBUTE_SELECTOR,
|
|
79
150
|
) as HTMLElement;
|
|
80
151
|
|
|
81
152
|
const nextFocusTrap = findAncestor(
|
|
82
153
|
event.target as HTMLElement,
|
|
83
|
-
|
|
154
|
+
ATTRIBUTE_SELECTOR,
|
|
84
155
|
) as HTMLElement;
|
|
85
156
|
|
|
86
157
|
if (lastFocusTrap != null && nextFocusTrap !== lastFocusTrap) {
|
|
87
|
-
|
|
158
|
+
if (FOCUSTRAPS_ALL.get(lastFocusTrap)?.contain ?? false) {
|
|
159
|
+
event.preventDefault();
|
|
88
160
|
|
|
89
|
-
|
|
161
|
+
lastTarget?.focus();
|
|
162
|
+
}
|
|
90
163
|
}
|
|
91
164
|
|
|
92
|
-
if (
|
|
165
|
+
if (FOCUSTRAPS_DISABLED.size === 0) {
|
|
93
166
|
return;
|
|
94
167
|
}
|
|
95
168
|
|
|
96
|
-
for (const focusTrap of
|
|
169
|
+
for (const focusTrap of FOCUSTRAPS_DISABLED) {
|
|
97
170
|
if (focusTrap.element !== nextFocusTrap) {
|
|
98
171
|
focusTrap.enable();
|
|
99
|
-
return;
|
|
100
172
|
}
|
|
101
173
|
}
|
|
102
174
|
}
|
|
@@ -104,16 +176,16 @@ function onPointerdown(event: MouseEvent | TouchEvent): void {
|
|
|
104
176
|
function onTab(event: KeyboardEvent): void {
|
|
105
177
|
const element = findAncestor(
|
|
106
178
|
event.target as HTMLElement,
|
|
107
|
-
|
|
179
|
+
ATTRIBUTE_SELECTOR,
|
|
108
180
|
) as HTMLElement;
|
|
109
181
|
|
|
110
|
-
const focusTrap =
|
|
182
|
+
const focusTrap = FOCUSTRAPS_ALL.get(element);
|
|
111
183
|
|
|
112
184
|
if (focusTrap == null) {
|
|
113
185
|
return;
|
|
114
186
|
}
|
|
115
187
|
|
|
116
|
-
if (focusTrap.
|
|
188
|
+
if (!focusTrap.disabled) {
|
|
117
189
|
event.preventDefault();
|
|
118
190
|
}
|
|
119
191
|
|
|
@@ -124,36 +196,38 @@ function onTab(event: KeyboardEvent): void {
|
|
|
124
196
|
if (index === -1 || elements.length === 0) {
|
|
125
197
|
tabToTrap(focusTrap, elements, element);
|
|
126
198
|
} else {
|
|
127
|
-
tabToElement(
|
|
199
|
+
tabToElement(focusTrap, elements, index, event.shiftKey ? -1 : 1);
|
|
128
200
|
}
|
|
129
201
|
}
|
|
130
202
|
|
|
131
|
-
function
|
|
132
|
-
focusTrap.
|
|
203
|
+
function setConnected(focusTrap: FocusTrap, value: boolean): void {
|
|
204
|
+
if (!focusTrap.embedded || focusTrap.connected === value) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
focusTrap.connected = value;
|
|
209
|
+
}
|
|
133
210
|
|
|
134
|
-
|
|
211
|
+
function setDisabled(focusTrap: FocusTrap, value: boolean): void {
|
|
212
|
+
focusTrap.disabled = value;
|
|
135
213
|
|
|
136
214
|
if (value) {
|
|
137
|
-
|
|
215
|
+
FOCUSTRAPS_DISABLED.add(focusTrap);
|
|
138
216
|
} else {
|
|
139
|
-
|
|
217
|
+
FOCUSTRAPS_DISABLED.delete(focusTrap);
|
|
140
218
|
}
|
|
141
219
|
}
|
|
142
220
|
|
|
143
221
|
function tabToElement(
|
|
144
|
-
event: KeyboardEvent,
|
|
145
222
|
focusTrap: FocusTrap,
|
|
146
223
|
elements: Element[],
|
|
147
224
|
index: number,
|
|
225
|
+
modifier: number,
|
|
148
226
|
): void {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
0,
|
|
152
|
-
elements.length - 1,
|
|
153
|
-
true,
|
|
154
|
-
);
|
|
227
|
+
const {length} = elements;
|
|
228
|
+
const next = clamp(index + modifier, 0, length - 1, true);
|
|
155
229
|
|
|
156
|
-
if (
|
|
230
|
+
if (focusTrap.disabled) {
|
|
157
231
|
if (
|
|
158
232
|
(index === length - 1 && next === 0) ||
|
|
159
233
|
(index === 0 && next === length - 1)
|
|
@@ -166,50 +240,61 @@ function tabToElement(
|
|
|
166
240
|
return;
|
|
167
241
|
}
|
|
168
242
|
|
|
169
|
-
|
|
243
|
+
const element = elements[next] as HTMLElement;
|
|
244
|
+
const elementFocusTrap = FOCUSTRAPS_ALL.get(element);
|
|
170
245
|
|
|
171
|
-
|
|
246
|
+
if (elementFocusTrap != null) {
|
|
247
|
+
elementFocusTrap.focus(modifier === -1);
|
|
248
|
+
} else {
|
|
249
|
+
(elements[next] as HTMLElement).focus();
|
|
250
|
+
|
|
251
|
+
lastTarget = elements[next] as HTMLElement;
|
|
252
|
+
}
|
|
172
253
|
}
|
|
173
254
|
|
|
174
255
|
function tabToTrap(
|
|
175
256
|
focusTrap: FocusTrap,
|
|
176
257
|
elements: Element[],
|
|
177
258
|
element: HTMLElement,
|
|
259
|
+
index?: number,
|
|
178
260
|
): void {
|
|
179
|
-
if (focusTrap.
|
|
180
|
-
|
|
261
|
+
if (focusTrap.disabled) {
|
|
262
|
+
lastTarget = undefined;
|
|
263
|
+
|
|
264
|
+
focusTrap.enable();
|
|
265
|
+
} else {
|
|
266
|
+
const next = elements.length === 0 ? element : elements.at(index ?? 0);
|
|
181
267
|
|
|
182
268
|
(next as HTMLElement).focus();
|
|
183
269
|
|
|
184
270
|
lastTarget = next as HTMLElement;
|
|
185
|
-
} else {
|
|
186
|
-
lastTarget = undefined;
|
|
187
|
-
|
|
188
|
-
focusTrap.enable();
|
|
189
271
|
}
|
|
190
272
|
}
|
|
191
273
|
|
|
192
274
|
//
|
|
193
275
|
|
|
194
|
-
const
|
|
276
|
+
export const FOCUS_TRAP_SELECTOR = 'oui-focus-trap';
|
|
195
277
|
|
|
196
|
-
const
|
|
278
|
+
const ATTRIBUTE_SELECTOR = `[${FOCUS_TRAP_SELECTOR}]`;
|
|
279
|
+
|
|
280
|
+
const EVENT_OPTIONS: AddEventListenerOptions = {
|
|
197
281
|
capture: true,
|
|
198
282
|
passive: false,
|
|
199
283
|
};
|
|
200
284
|
|
|
201
|
-
export const
|
|
202
|
-
|
|
203
|
-
let lastTarget: HTMLElement | undefined;
|
|
285
|
+
export const FOCUS_TRAP_CONTAIN = `${FOCUS_TRAP_SELECTOR}-contain`;
|
|
204
286
|
|
|
205
|
-
export const
|
|
287
|
+
export const FOCUS_TRAP_NOESCAPE = `${FOCUS_TRAP_SELECTOR}-noescape`;
|
|
206
288
|
|
|
207
|
-
|
|
289
|
+
export const FOCUSTRAPS_ALL: WeakMap<HTMLElement, FocusTrap> = new WeakMap();
|
|
208
290
|
|
|
209
|
-
|
|
291
|
+
const FOCUSTRAPS_DISABLED: Set<FocusTrap> = new Set();
|
|
210
292
|
|
|
211
|
-
|
|
293
|
+
let lastTarget: HTMLElement | undefined;
|
|
212
294
|
|
|
213
|
-
|
|
295
|
+
//
|
|
214
296
|
|
|
215
|
-
on(document, '
|
|
297
|
+
on(document, 'focusin', onFocusIn, EVENT_OPTIONS);
|
|
298
|
+
on(document, 'keydown', onKeydown, EVENT_OPTIONS);
|
|
299
|
+
on(document, 'mousedown', onPointerdown, EVENT_OPTIONS);
|
|
300
|
+
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,33 +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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
this.#floatable = new Floatable({
|
|
50
|
+
content,
|
|
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);
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
close(): void {
|
|
61
74
|
this.hidePopover();
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
connectedCallback(): void {
|
|
78
|
+
this.#floatable.enable();
|
|
79
|
+
this.#focusTrap.connect();
|
|
80
|
+
}
|
|
68
81
|
|
|
69
|
-
|
|
82
|
+
disconnectedCallback(): void {
|
|
83
|
+
this.#floatable.disable();
|
|
84
|
+
this.#focusTrap.disconnect();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
hidePopover(): void {
|
|
88
|
+
if (this.#floatable.active && this.isConnected) {
|
|
89
|
+
this.#floatable.toggle(false);
|
|
70
90
|
}
|
|
71
91
|
}
|
|
72
92
|
|
|
@@ -75,8 +95,8 @@ export class OuiPopoverElement extends HTMLElement {
|
|
|
75
95
|
}
|
|
76
96
|
|
|
77
97
|
showPopover(): void {
|
|
78
|
-
if (!this.#floatable.active) {
|
|
79
|
-
|
|
98
|
+
if (!this.#floatable.active && this.isConnected) {
|
|
99
|
+
this.#floatable.toggle(true);
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
102
|
|
|
@@ -109,32 +129,6 @@ export class OuiPopoverElement extends HTMLElement {
|
|
|
109
129
|
|
|
110
130
|
//
|
|
111
131
|
|
|
112
|
-
function closeAll(): void {
|
|
113
|
-
for (const floatable of active) {
|
|
114
|
-
deactivate(floatable, active, order);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function closeAbove(element: HTMLElement): void {
|
|
119
|
-
const index = order.findIndex(
|
|
120
|
-
floatable => floatable.anchor === element || floatable.content === element,
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
if (index === -1 || index === order.length - 1) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
while (index < order.length - 1) {
|
|
128
|
-
const floatable = order.pop();
|
|
129
|
-
|
|
130
|
-
if (floatable != null) {
|
|
131
|
-
deactivate(floatable, active, order);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
focus(order[index].content);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
132
|
function focus(content: HTMLElement): void {
|
|
139
133
|
let target = getFocusable(content)[0];
|
|
140
134
|
|
|
@@ -147,75 +141,18 @@ function focus(content: HTMLElement): void {
|
|
|
147
141
|
});
|
|
148
142
|
}
|
|
149
143
|
|
|
150
|
-
|
|
151
|
-
const related = findAncestor(
|
|
152
|
-
event.target as never,
|
|
153
|
-
'[oui-popover-toggle], [oui-popover-content]',
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
if (!(related instanceof HTMLElement)) {
|
|
157
|
-
closeAll();
|
|
158
|
-
} else if (related.hasAttribute('oui-popover-content')) {
|
|
159
|
-
closeAbove(related);
|
|
160
|
-
} else {
|
|
161
|
-
toggle(related);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function onKeydown(event: KeyboardEvent): void {
|
|
166
|
-
if (event.key !== 'Escape' || order.length === 0) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const related = findAncestor(event.target as never, '[oui-popover-content]');
|
|
171
|
-
|
|
172
|
-
if (!(related instanceof HTMLElement)) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
closeAbove(related);
|
|
177
|
-
|
|
178
|
-
const floatable = mapped.get(related);
|
|
179
|
-
|
|
180
|
-
if (floatable != null) {
|
|
181
|
-
deactivate(floatable, active, order);
|
|
182
|
-
|
|
183
|
-
floatable.anchor.focus();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function toggle(anchor: HTMLElement): void {
|
|
188
|
-
const content = findAncestor(anchor, '[oui-popover-content]');
|
|
189
|
-
|
|
190
|
-
if (content instanceof HTMLElement) {
|
|
191
|
-
closeAbove(content);
|
|
192
|
-
} else {
|
|
193
|
-
closeAll();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const floatable = mapped.get(anchor);
|
|
144
|
+
//
|
|
197
145
|
|
|
198
|
-
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
146
|
+
const ATTRIBUTE_ID_PREFIX = 'oui_popover_content_';
|
|
201
147
|
|
|
202
|
-
|
|
203
|
-
deactivate(floatable, active, order);
|
|
204
|
-
} else {
|
|
205
|
-
activate(floatable, active, order);
|
|
206
|
-
focus(floatable.content);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
148
|
+
const POPOVERS: Map<HTMLElement, Floatable> = new Map();
|
|
209
149
|
|
|
210
|
-
|
|
150
|
+
const SELECTOR = 'oui-popover';
|
|
211
151
|
|
|
212
|
-
|
|
152
|
+
const SELECTOR_CONTENT = `:scope > [${SELECTOR}-content]`;
|
|
213
153
|
|
|
214
|
-
|
|
215
|
-
on(document, 'keydown', onKeydown);
|
|
154
|
+
const SELECTOR_TOGGLE = `:scope > [${SELECTOR}-toggle]`;
|
|
216
155
|
|
|
217
156
|
//
|
|
218
157
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const order: Floatable[] = [];
|
|
158
|
+
customElements.define(SELECTOR, OuiPopoverElement);
|