@oscarpalmer/oui 0.22.0 → 0.23.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/popover.css +15 -19
- package/dist/css/reset.css +16 -4
- package/dist/css/tooltip.css +23 -19
- package/dist/js/focus-trap/embedded.d.mts +2 -2
- package/dist/js/focus-trap/embedded.mjs +5 -1
- package/dist/js/focus-trap/standalone.mjs +2 -6
- package/dist/js/index.d.mts +3 -47
- package/dist/js/index.mjs +7 -2537
- package/dist/js/internal/floatable.d.mts +12 -20
- package/dist/js/internal/floatable.mjs +32 -152
- package/dist/js/popover.d.mts +1 -20
- package/dist/js/popover.mjs +53 -82
- package/dist/js/tooltip.d.mts +1 -1
- package/dist/js/tooltip.mjs +101 -126
- package/package.json +6 -6
- package/src/css/mixins.scss +14 -12
- package/src/css/popover.scss +1 -9
- package/src/css/reset.scss +16 -4
- package/src/css/tooltip.scss +9 -7
- package/src/js/focus-trap/embedded.ts +6 -1
- package/src/js/focus-trap/standalone.ts +2 -7
- package/src/js/index.ts +2 -1
- package/src/js/internal/floatable.ts +44 -299
- package/src/js/popover.ts +69 -131
- package/src/js/tooltip.ts +135 -180
|
@@ -1,110 +1,31 @@
|
|
|
1
|
-
import {on} from '@oscarpalmer/toretto/event';
|
|
2
|
-
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
3
|
-
|
|
4
1
|
export class Floatable {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
ignoreFocus = false;
|
|
2
|
+
constructor(
|
|
3
|
+
public anchor: HTMLElement,
|
|
4
|
+
public content: HTMLElement,
|
|
5
|
+
public options: FloatableOptions,
|
|
6
|
+
) {
|
|
7
|
+
const anchorName = getAnchorName(anchor);
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
anchor.style.anchorName = anchorName;
|
|
10
|
+
content.style.positionAnchor = anchorName;
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
destroy(): void {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const state = getState();
|
|
21
|
-
|
|
22
|
-
state?.active.delete(this);
|
|
23
|
-
state?.mapped.delete(this.options.anchor);
|
|
24
|
-
state?.mapped.delete(this.options.content);
|
|
25
|
-
|
|
26
|
-
this.options.anchor = undefined as never;
|
|
27
|
-
this.options.content = undefined as never;
|
|
14
|
+
this.anchor = null as never;
|
|
15
|
+
this.content = null as never;
|
|
28
16
|
}
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
this.disabled = true;
|
|
36
|
-
|
|
37
|
-
stop(this);
|
|
38
|
-
|
|
39
|
-
getState()?.active.delete(this);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
enable(): void {
|
|
43
|
-
if (!this.disabled) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
this.disabled = false;
|
|
48
|
-
|
|
49
|
-
initialize(this);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
toggle(active: boolean, ignoreFocus?: boolean): void {
|
|
53
|
-
if (this.disabled || this.active === active) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const state = getState();
|
|
58
|
-
|
|
59
|
-
if (state == null) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this.ignoreFocus = !active && (ignoreFocus ?? false);
|
|
64
|
-
|
|
65
|
-
stop(this);
|
|
66
|
-
|
|
67
|
-
if (active) {
|
|
68
|
-
state.active.add(this);
|
|
69
|
-
} else {
|
|
70
|
-
state.active.delete(this);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (this.options.interactive) {
|
|
74
|
-
if (active) {
|
|
75
|
-
state.order.push(this);
|
|
76
|
-
} else {
|
|
77
|
-
const index = state.order.indexOf(this);
|
|
78
|
-
|
|
79
|
-
if (index > -1) {
|
|
80
|
-
state.order.splice(index, 1);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (active) {
|
|
86
|
-
activate(this);
|
|
87
|
-
} else {
|
|
88
|
-
deactivate(this);
|
|
89
|
-
}
|
|
18
|
+
update(): void {
|
|
19
|
+
setPosition(this);
|
|
90
20
|
}
|
|
91
21
|
}
|
|
92
22
|
|
|
93
|
-
type
|
|
94
|
-
|
|
23
|
+
export type FloatableOptions = {
|
|
24
|
+
attribute: string;
|
|
25
|
+
position: FloatablePosition;
|
|
95
26
|
};
|
|
96
27
|
|
|
97
|
-
type
|
|
98
|
-
anchor: HTMLElement;
|
|
99
|
-
content: HTMLElement;
|
|
100
|
-
defaultPosition: Position;
|
|
101
|
-
interactive: boolean;
|
|
102
|
-
positionAttribute: string;
|
|
103
|
-
reusable: boolean;
|
|
104
|
-
onAfter?: (active: boolean) => void;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
type Position =
|
|
28
|
+
export type FloatablePosition =
|
|
108
29
|
| 'above'
|
|
109
30
|
| 'above-end'
|
|
110
31
|
| 'above-start'
|
|
@@ -118,206 +39,60 @@ type Position =
|
|
|
118
39
|
| 'start-bottom'
|
|
119
40
|
| 'start-top';
|
|
120
41
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
function activate(floatable: Floatable): void {
|
|
130
|
-
floatable.active = true;
|
|
131
|
-
|
|
132
|
-
document.body.append(floatable.options.content);
|
|
133
|
-
|
|
134
|
-
floatable.options.content.hidden = false;
|
|
135
|
-
|
|
136
|
-
setPosition(floatable);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function closeAbove(element: HTMLElement): void {
|
|
140
|
-
const state = getState();
|
|
141
|
-
|
|
142
|
-
if (state == null) {
|
|
42
|
+
export function createFloatable(
|
|
43
|
+
anchor: HTMLElement | null,
|
|
44
|
+
content: HTMLElement,
|
|
45
|
+
options: FloatableOptions,
|
|
46
|
+
): Floatable | undefined {
|
|
47
|
+
if (anchor == null) {
|
|
143
48
|
return;
|
|
144
49
|
}
|
|
145
50
|
|
|
146
|
-
|
|
147
|
-
floatable => floatable.options.anchor === element || floatable.options.content === element,
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
if (index === -1 || index === state.order.length - 1) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
while (index < state.order.length - 1) {
|
|
155
|
-
state.order.pop()?.toggle(false, true);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
51
|
+
let floatable = instances.get(content);
|
|
158
52
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const floatables = [...(state?.active ?? [])];
|
|
163
|
-
|
|
164
|
-
state?.active.clear();
|
|
53
|
+
if (floatable == null) {
|
|
54
|
+
floatable = new Floatable(anchor, content, options);
|
|
165
55
|
|
|
166
|
-
|
|
167
|
-
floatable.toggle(false, true);
|
|
56
|
+
instances.set(content, floatable);
|
|
168
57
|
}
|
|
169
|
-
}
|
|
170
58
|
|
|
171
|
-
|
|
172
|
-
for (const floatable of state.active) {
|
|
173
|
-
if (!floatable.options.interactive) {
|
|
174
|
-
floatable.toggle(false);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
59
|
+
return floatable;
|
|
177
60
|
}
|
|
178
61
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (!floatable.options.reusable) {
|
|
183
|
-
floatable.options.content.hidden = true;
|
|
62
|
+
function getAnchorName(anchor: HTMLElement): string {
|
|
63
|
+
let {anchorName} = anchor.style;
|
|
184
64
|
|
|
185
|
-
|
|
65
|
+
if (anchorName.length > 0) {
|
|
66
|
+
return anchorName;
|
|
186
67
|
}
|
|
187
68
|
|
|
188
|
-
floatable.options.onAfter?.(false);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function getAnchorName(): string {
|
|
192
69
|
index += 1;
|
|
193
70
|
|
|
194
71
|
return `--oui-floatable-anchor-${index}`;
|
|
195
72
|
}
|
|
196
73
|
|
|
197
|
-
function
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
function initialize(floatable: Floatable): void {
|
|
202
|
-
const {anchor, content, interactive} = floatable.options;
|
|
203
|
-
|
|
204
|
-
if (interactive) {
|
|
205
|
-
anchor.setAttribute(ATTRIBUTE_ANCHOR, '');
|
|
206
|
-
content.setAttribute(ATTRIBUTE_CONTENT, '');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const anchorName = getAnchorName();
|
|
210
|
-
|
|
211
|
-
anchor.style.anchorName = anchorName;
|
|
212
|
-
|
|
213
|
-
content.style.position = 'absolute';
|
|
214
|
-
content.style.positionAnchor = anchorName;
|
|
215
|
-
content.style.positionTry = 'normal flip-start';
|
|
216
|
-
|
|
217
|
-
const state = getState();
|
|
218
|
-
|
|
219
|
-
state?.mapped.set(anchor, floatable);
|
|
220
|
-
state?.mapped.set(content, floatable);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function isDisabled(element: HTMLElement): boolean {
|
|
224
|
-
return (
|
|
225
|
-
(element as HTMLInputElement).disabled ||
|
|
226
|
-
(element as HTMLInputElement).readOnly ||
|
|
227
|
-
element.getAttribute('aria-disabled') === 'true'
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function onClick(event: PointerEvent): void {
|
|
232
|
-
const related = findAncestor(event, SELECTOR_ALL);
|
|
233
|
-
|
|
234
|
-
if (!(related instanceof HTMLElement)) {
|
|
235
|
-
closeAll();
|
|
236
|
-
} else if (related.hasAttribute(ATTRIBUTE_CONTENT)) {
|
|
237
|
-
closeAbove(related);
|
|
238
|
-
} else if (!isDisabled(related)) {
|
|
239
|
-
toggle(related);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function onKeyDown(event: KeyboardEvent): void {
|
|
244
|
-
if (event.key !== 'Escape') {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const state = getState();
|
|
249
|
-
|
|
250
|
-
if (state == null || state.active.size === 0) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
closeNonInteractive(state);
|
|
255
|
-
|
|
256
|
-
const related = findAncestor(event, SELECTOR_CONTENT);
|
|
257
|
-
|
|
258
|
-
if (!(related instanceof HTMLElement)) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
closeAbove(related);
|
|
263
|
-
|
|
264
|
-
const floatable = state.mapped.get(related);
|
|
265
|
-
|
|
266
|
-
if (floatable != null) {
|
|
267
|
-
floatable.toggle(false);
|
|
268
|
-
}
|
|
74
|
+
export function removeFloatable(content: HTMLElement): void {
|
|
75
|
+
instances.get(content)?.destroy();
|
|
76
|
+
instances.delete(content);
|
|
269
77
|
}
|
|
270
78
|
|
|
271
79
|
function setPosition(floatable: Floatable): void {
|
|
272
|
-
let position:
|
|
273
|
-
(floatable.
|
|
274
|
-
floatable.options.
|
|
80
|
+
let position: FloatablePosition =
|
|
81
|
+
(floatable.anchor.getAttribute(floatable.options.attribute) as FloatablePosition) ??
|
|
82
|
+
floatable.options.position;
|
|
275
83
|
|
|
276
84
|
if (AREAS[position] == null) {
|
|
277
|
-
position = floatable.options.
|
|
85
|
+
position = floatable.options.position;
|
|
278
86
|
}
|
|
279
87
|
|
|
280
88
|
const area = AREAS[position];
|
|
281
89
|
|
|
282
|
-
floatable.
|
|
90
|
+
floatable.content.style.positionArea = area;
|
|
283
91
|
|
|
284
|
-
floatable.
|
|
285
|
-
|
|
286
|
-
floatable.options.onAfter?.(true);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function stop(floatable: Floatable): void {
|
|
290
|
-
if (floatable.frame != null) {
|
|
291
|
-
cancelAnimationFrame(floatable.frame);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
floatable.frame = undefined;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function toggle(anchor: HTMLElement): void {
|
|
298
|
-
const floatable = getState()?.mapped.get(anchor);
|
|
299
|
-
|
|
300
|
-
if (floatable == null) {
|
|
301
|
-
closeAll();
|
|
302
|
-
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const active = Boolean(floatable.active);
|
|
307
|
-
const content = findAncestor(floatable.options.anchor, SELECTOR_CONTENT);
|
|
308
|
-
|
|
309
|
-
if (content == null) {
|
|
310
|
-
closeAll();
|
|
311
|
-
} else {
|
|
312
|
-
closeAbove(content as HTMLElement);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
floatable.toggle(!active);
|
|
92
|
+
floatable.content.setAttribute(ATTRIBUTE, POSITIONS[area]);
|
|
316
93
|
}
|
|
317
94
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const AREAS: Record<Position, string> = {
|
|
95
|
+
const AREAS: Record<FloatablePosition, string> = {
|
|
321
96
|
above: 'block-start',
|
|
322
97
|
'above-end': 'start span-start',
|
|
323
98
|
'above-start': 'start span-end',
|
|
@@ -332,42 +107,12 @@ const AREAS: Record<Position, string> = {
|
|
|
332
107
|
'start-top': 'span-end start',
|
|
333
108
|
};
|
|
334
109
|
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
const ATTRIBUTE_CONTENT = 'oui-floatable-content';
|
|
338
|
-
|
|
339
|
-
const ATTRIBUTE_POSITION = 'oui-position';
|
|
340
|
-
|
|
341
|
-
const INSERT_AFTEREND = 'afterend';
|
|
110
|
+
const ATTRIBUTE = 'oui-position';
|
|
342
111
|
|
|
343
112
|
const POSITIONS = Object.fromEntries(
|
|
344
113
|
Object.entries(AREAS).map(([position, area]) => [area, position]),
|
|
345
|
-
) as Record<string,
|
|
114
|
+
) as Record<string, FloatablePosition>;
|
|
346
115
|
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
116
|
+
const instances = new WeakMap<HTMLElement, Floatable>();
|
|
350
117
|
|
|
351
118
|
let index = 0;
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
if ((window as unknown as FloatableWindow)._oscarpalmer_oui_floatable == null) {
|
|
356
|
-
Object.defineProperty(window, '_oscarpalmer_oui_floatable', {
|
|
357
|
-
enumerable: false,
|
|
358
|
-
value: {
|
|
359
|
-
active: new Set(),
|
|
360
|
-
mapped: new Map(),
|
|
361
|
-
order: [],
|
|
362
|
-
},
|
|
363
|
-
writable: false,
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
on(document, 'keydown', event => {
|
|
367
|
-
onKeyDown(event);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
on(document, 'click', event => {
|
|
371
|
-
onClick(event);
|
|
372
|
-
});
|
|
373
|
-
}
|
package/src/js/popover.ts
CHANGED
|
@@ -1,158 +1,96 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!(content instanceof HTMLElement)) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
'A oui-popover must have a direct child element with the [oui-popover-content]-attribute',
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!(toggle instanceof HTMLButtonElement)) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
'A oui-popover must have a direct child element with the [oui-popover-toggle]-attribute and be a button',
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
content.hidden = true;
|
|
38
|
-
|
|
39
|
-
content.setAttribute('role', 'dialog');
|
|
40
|
-
content.setAttribute('aria-modal', 'true');
|
|
41
|
-
|
|
42
|
-
if (isNullableOrWhitespace(content.id)) {
|
|
43
|
-
content.setAttribute('id', `${ATTRIBUTE_ID_PREFIX}_${++index}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
toggle.setAttribute('aria-controls', content.id);
|
|
47
|
-
toggle.setAttribute('aria-haspopup', 'dialog');
|
|
48
|
-
|
|
49
|
-
this.#floatable = new Floatable({
|
|
50
|
-
content,
|
|
51
|
-
anchor: toggle,
|
|
52
|
-
defaultPosition: 'below-start',
|
|
53
|
-
interactive: true,
|
|
54
|
-
positionAttribute: 'position',
|
|
55
|
-
reusable: 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, {
|
|
1
|
+
import {on} from '@oscarpalmer/toretto/event';
|
|
2
|
+
import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
|
|
3
|
+
import {createFocusTrap, removeFocusTrap, type FocusTrap} from './focus-trap/embedded';
|
|
4
|
+
import {attributable} from './internal/attributable';
|
|
5
|
+
import {
|
|
6
|
+
createFloatable,
|
|
7
|
+
removeFloatable,
|
|
8
|
+
type Floatable,
|
|
9
|
+
type FloatableOptions,
|
|
10
|
+
} from './internal/floatable';
|
|
11
|
+
|
|
12
|
+
class Popover {
|
|
13
|
+
anchor: HTMLElement | null;
|
|
14
|
+
floatable?: Floatable;
|
|
15
|
+
focusTrap?: FocusTrap;
|
|
16
|
+
listener: RemovableEventListener;
|
|
17
|
+
|
|
18
|
+
constructor(public element: HTMLElement) {
|
|
19
|
+
this.anchor = element.ownerDocument.querySelector(`[popovertarget="${element.id}"]`);
|
|
20
|
+
|
|
21
|
+
this.floatable = createFloatable(this.anchor as HTMLElement, element, options);
|
|
22
|
+
|
|
23
|
+
this.focusTrap = createFocusTrap(element, {
|
|
66
24
|
noescape: true,
|
|
67
25
|
});
|
|
68
26
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
27
|
+
this.listener = on(element, EVENT_TOGGLE, event => {
|
|
28
|
+
if (event.newState === STATE_OPEN) {
|
|
29
|
+
ignoreFocus = false;
|
|
72
30
|
|
|
73
|
-
|
|
74
|
-
this.hidePopover();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
connectedCallback(): void {
|
|
78
|
-
this.#floatable.enable();
|
|
79
|
-
this.#focusTrap.connect();
|
|
80
|
-
}
|
|
81
|
-
|
|
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);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
31
|
+
this.floatable?.update();
|
|
92
32
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
33
|
+
element.focus();
|
|
34
|
+
} else if (!ignoreFocus) {
|
|
35
|
+
this.anchor?.focus();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
96
38
|
|
|
97
|
-
|
|
98
|
-
if (!this.#floatable.active && this.isConnected) {
|
|
99
|
-
this.#floatable.toggle(true);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
39
|
+
this.element.setAttribute(ATTRIBUTE_CONTENT, '');
|
|
102
40
|
|
|
103
|
-
|
|
104
|
-
this.togglePopover();
|
|
41
|
+
this.floatable?.update();
|
|
105
42
|
}
|
|
106
43
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (options === true) {
|
|
110
|
-
this.showPopover();
|
|
111
|
-
} else {
|
|
112
|
-
this.hidePopover();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return options;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (this.#floatable.active) {
|
|
119
|
-
this.hidePopover();
|
|
44
|
+
destroy(): void {
|
|
45
|
+
this.focusTrap?.destroy();
|
|
120
46
|
|
|
121
|
-
|
|
122
|
-
|
|
47
|
+
removeFloatable(this.element);
|
|
48
|
+
removeFocusTrap(this.element);
|
|
123
49
|
|
|
124
|
-
this.
|
|
50
|
+
this.element.removeAttribute(ATTRIBUTE_CONTENT);
|
|
125
51
|
|
|
126
|
-
|
|
52
|
+
this.anchor = null;
|
|
53
|
+
this.element = null as never;
|
|
127
54
|
}
|
|
128
55
|
}
|
|
129
56
|
|
|
130
|
-
|
|
57
|
+
function onAdd(element: HTMLElement): void {
|
|
58
|
+
if (!instances.has(element) && element.getAttribute(ATTRIBUTE) !== POPOVER_HINT) {
|
|
59
|
+
instances.set(element, new Popover(element));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
131
62
|
|
|
132
|
-
function
|
|
133
|
-
|
|
63
|
+
function onPointerdown(): void {
|
|
64
|
+
ignoreFocus = true;
|
|
65
|
+
}
|
|
134
66
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
67
|
+
function onRemove(element: HTMLElement): void {
|
|
68
|
+
instances.get(element)?.destroy();
|
|
138
69
|
|
|
139
|
-
|
|
140
|
-
(target as HTMLElement).focus();
|
|
141
|
-
});
|
|
70
|
+
instances.delete(element);
|
|
142
71
|
}
|
|
143
72
|
|
|
144
|
-
|
|
73
|
+
const ATTRIBUTE = 'popover';
|
|
74
|
+
|
|
75
|
+
const ATTRIBUTE_CONTENT = 'oui-popover-content';
|
|
76
|
+
|
|
77
|
+
const EVENT_TOGGLE = 'toggle';
|
|
145
78
|
|
|
146
|
-
const
|
|
79
|
+
const POPOVER_HINT = 'hint';
|
|
147
80
|
|
|
148
|
-
const
|
|
81
|
+
const STATE_OPEN = 'open';
|
|
149
82
|
|
|
150
|
-
const
|
|
83
|
+
const instances = new WeakMap<HTMLElement, Popover>();
|
|
151
84
|
|
|
152
|
-
const
|
|
85
|
+
const options: FloatableOptions = {
|
|
86
|
+
attribute: 'position',
|
|
87
|
+
position: 'below-start',
|
|
88
|
+
};
|
|
153
89
|
|
|
154
|
-
|
|
90
|
+
let ignoreFocus = false;
|
|
155
91
|
|
|
156
|
-
|
|
92
|
+
on(document, 'pointerdown', onPointerdown, {
|
|
93
|
+
capture: true,
|
|
94
|
+
});
|
|
157
95
|
|
|
158
|
-
|
|
96
|
+
attributable(ATTRIBUTE, onAdd, onRemove);
|