@oscarpalmer/oui 0.22.1 → 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 +1 -2
- package/dist/js/index.mjs +2 -2
- package/dist/js/internal/floatable.d.mts +12 -20
- package/dist/js/internal/floatable.mjs +32 -153
- 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 -303
- 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;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
disable(): void {
|
|
31
|
-
if (this.disabled) {
|
|
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);
|
|
14
|
+
this.anchor = null as never;
|
|
15
|
+
this.content = null as never;
|
|
50
16
|
}
|
|
51
17
|
|
|
52
|
-
|
|
53
|
-
|
|
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,210 +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
|
-
}
|
|
158
|
-
|
|
159
|
-
function closeAll(): void {
|
|
160
|
-
const state = getState();
|
|
51
|
+
let floatable = instances.get(content);
|
|
161
52
|
|
|
162
|
-
|
|
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
|
-
|
|
62
|
+
function getAnchorName(anchor: HTMLElement): string {
|
|
63
|
+
let {anchorName} = anchor.style;
|
|
181
64
|
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
floatable.options.anchor.insertAdjacentElement(INSERT_AFTEREND, floatable.options.content);
|
|
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
|
-
let anchorName = anchor.style.anchorName;
|
|
210
|
-
|
|
211
|
-
if (anchorName.length === 0) {
|
|
212
|
-
anchorName = getAnchorName();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
anchor.style.anchorName = anchorName;
|
|
216
|
-
|
|
217
|
-
content.style.position = 'absolute';
|
|
218
|
-
content.style.positionAnchor = anchorName;
|
|
219
|
-
content.style.positionTry = 'normal flip-start';
|
|
220
|
-
|
|
221
|
-
const state = getState();
|
|
222
|
-
|
|
223
|
-
state?.mapped.set(anchor, floatable);
|
|
224
|
-
state?.mapped.set(content, floatable);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function isDisabled(element: HTMLElement): boolean {
|
|
228
|
-
return (
|
|
229
|
-
(element as HTMLInputElement).disabled ||
|
|
230
|
-
(element as HTMLInputElement).readOnly ||
|
|
231
|
-
element.getAttribute('aria-disabled') === 'true'
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function onClick(event: PointerEvent): void {
|
|
236
|
-
const related = findAncestor(event, SELECTOR_ALL);
|
|
237
|
-
|
|
238
|
-
if (!(related instanceof HTMLElement)) {
|
|
239
|
-
closeAll();
|
|
240
|
-
} else if (related.hasAttribute(ATTRIBUTE_CONTENT)) {
|
|
241
|
-
closeAbove(related);
|
|
242
|
-
} else if (!isDisabled(related)) {
|
|
243
|
-
toggle(related);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function onKeyDown(event: KeyboardEvent): void {
|
|
248
|
-
if (event.key !== 'Escape') {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const state = getState();
|
|
253
|
-
|
|
254
|
-
if (state == null || state.active.size === 0) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
closeNonInteractive(state);
|
|
259
|
-
|
|
260
|
-
const related = findAncestor(event, SELECTOR_CONTENT);
|
|
261
|
-
|
|
262
|
-
if (!(related instanceof HTMLElement)) {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
closeAbove(related);
|
|
267
|
-
|
|
268
|
-
const floatable = state.mapped.get(related);
|
|
269
|
-
|
|
270
|
-
if (floatable != null) {
|
|
271
|
-
floatable.toggle(false);
|
|
272
|
-
}
|
|
74
|
+
export function removeFloatable(content: HTMLElement): void {
|
|
75
|
+
instances.get(content)?.destroy();
|
|
76
|
+
instances.delete(content);
|
|
273
77
|
}
|
|
274
78
|
|
|
275
79
|
function setPosition(floatable: Floatable): void {
|
|
276
|
-
let position:
|
|
277
|
-
(floatable.
|
|
278
|
-
floatable.options.
|
|
80
|
+
let position: FloatablePosition =
|
|
81
|
+
(floatable.anchor.getAttribute(floatable.options.attribute) as FloatablePosition) ??
|
|
82
|
+
floatable.options.position;
|
|
279
83
|
|
|
280
84
|
if (AREAS[position] == null) {
|
|
281
|
-
position = floatable.options.
|
|
85
|
+
position = floatable.options.position;
|
|
282
86
|
}
|
|
283
87
|
|
|
284
88
|
const area = AREAS[position];
|
|
285
89
|
|
|
286
|
-
floatable.
|
|
287
|
-
|
|
288
|
-
floatable.options.content.setAttribute(ATTRIBUTE_POSITION, POSITIONS[area]);
|
|
90
|
+
floatable.content.style.positionArea = area;
|
|
289
91
|
|
|
290
|
-
floatable.
|
|
92
|
+
floatable.content.setAttribute(ATTRIBUTE, POSITIONS[area]);
|
|
291
93
|
}
|
|
292
94
|
|
|
293
|
-
|
|
294
|
-
if (floatable.frame != null) {
|
|
295
|
-
cancelAnimationFrame(floatable.frame);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
floatable.frame = undefined;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function toggle(anchor: HTMLElement): void {
|
|
302
|
-
const floatable = getState()?.mapped.get(anchor);
|
|
303
|
-
|
|
304
|
-
if (floatable == null) {
|
|
305
|
-
closeAll();
|
|
306
|
-
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const active = Boolean(floatable.active);
|
|
311
|
-
const content = findAncestor(floatable.options.anchor, SELECTOR_CONTENT);
|
|
312
|
-
|
|
313
|
-
if (content == null) {
|
|
314
|
-
closeAll();
|
|
315
|
-
} else {
|
|
316
|
-
closeAbove(content as HTMLElement);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
floatable.toggle(!active);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
const AREAS: Record<Position, string> = {
|
|
95
|
+
const AREAS: Record<FloatablePosition, string> = {
|
|
325
96
|
above: 'block-start',
|
|
326
97
|
'above-end': 'start span-start',
|
|
327
98
|
'above-start': 'start span-end',
|
|
@@ -336,42 +107,12 @@ const AREAS: Record<Position, string> = {
|
|
|
336
107
|
'start-top': 'span-end start',
|
|
337
108
|
};
|
|
338
109
|
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
const ATTRIBUTE_CONTENT = 'oui-floatable-content';
|
|
342
|
-
|
|
343
|
-
const ATTRIBUTE_POSITION = 'oui-position';
|
|
344
|
-
|
|
345
|
-
const INSERT_AFTEREND = 'afterend';
|
|
110
|
+
const ATTRIBUTE = 'oui-position';
|
|
346
111
|
|
|
347
112
|
const POSITIONS = Object.fromEntries(
|
|
348
113
|
Object.entries(AREAS).map(([position, area]) => [area, position]),
|
|
349
|
-
) as Record<string,
|
|
114
|
+
) as Record<string, FloatablePosition>;
|
|
350
115
|
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
116
|
+
const instances = new WeakMap<HTMLElement, Floatable>();
|
|
354
117
|
|
|
355
118
|
let index = 0;
|
|
356
|
-
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
if ((window as unknown as FloatableWindow)._oscarpalmer_oui_floatable == null) {
|
|
360
|
-
Object.defineProperty(window, '_oscarpalmer_oui_floatable', {
|
|
361
|
-
enumerable: false,
|
|
362
|
-
value: {
|
|
363
|
-
active: new Set(),
|
|
364
|
-
mapped: new Map(),
|
|
365
|
-
order: [],
|
|
366
|
-
},
|
|
367
|
-
writable: false,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
on(document, 'keydown', event => {
|
|
371
|
-
onKeyDown(event);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
on(document, 'click', event => {
|
|
375
|
-
onClick(event);
|
|
376
|
-
});
|
|
377
|
-
}
|
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);
|