@oscarpalmer/oui 0.17.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/reset.css +176 -56
- 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 +254 -169
- 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 -162
- package/dist/js/splitter.full.js +605 -0
- package/dist/js/splitter.js +263 -245
- package/dist/js/tooltip.full.js +679 -0
- package/dist/js/tooltip.js +159 -150
- 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/reset.scss +180 -54
- package/src/css/tooltip.scss +8 -0
- package/src/js/floatable.ts +380 -133
- 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 +50 -120
- package/src/js/splitter.ts +250 -103
- package/src/js/tooltip.ts +155 -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/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
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
function getString(value) {
|
|
2
|
+
if (typeof value === "string") return value;
|
|
3
|
+
if (value == null) return "";
|
|
4
|
+
if (typeof value !== "object") return String(value);
|
|
5
|
+
const asString = String(value.valueOf?.() ?? value);
|
|
6
|
+
return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isNullableOrWhitespace(value) {
|
|
10
|
+
return value == null || EXPRESSION_WHITESPACE$1.test(getString(value));
|
|
11
|
+
}
|
|
12
|
+
var EXPRESSION_WHITESPACE$1 = /^\s*$/;
|
|
13
|
+
|
|
14
|
+
function isEventTarget(value) {
|
|
15
|
+
return typeof value === "object" && value != null && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function" && typeof value.dispatchEvent === "function";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getBoolean(value, defaultValue) {
|
|
19
|
+
return typeof value === "boolean" ? value : defaultValue ?? false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function calculate() {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const values = [];
|
|
25
|
+
let last;
|
|
26
|
+
function step(now) {
|
|
27
|
+
if (last != null) values.push(now - last);
|
|
28
|
+
last = now;
|
|
29
|
+
if (values.length >= CALCULATION_TOTAL) {
|
|
30
|
+
const median = values.sort().slice(2, -2).reduce((first, second) => first + second, 0) / (values.length - CALCULATION_TRIM);
|
|
31
|
+
resolve(median);
|
|
32
|
+
} else requestAnimationFrame(step);
|
|
33
|
+
}
|
|
34
|
+
requestAnimationFrame(step);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function noop() {}
|
|
38
|
+
var CALCULATION_TOTAL = 10;
|
|
39
|
+
var CALCULATION_TRIM = 4;
|
|
40
|
+
calculate().then((value) => {
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function createEventOptions(options) {
|
|
44
|
+
return {
|
|
45
|
+
capture: getBoolean(options?.capture),
|
|
46
|
+
once: getBoolean(options?.once),
|
|
47
|
+
passive: getBoolean(options?.passive, true),
|
|
48
|
+
signal: options?.signal
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function on(target, type, listener, options) {
|
|
52
|
+
if (!isEventTarget(target) || typeof type !== "string" || typeof listener !== "function") return noop;
|
|
53
|
+
const extended = createEventOptions(options);
|
|
54
|
+
target.addEventListener(type, listener, extended);
|
|
55
|
+
return () => {
|
|
56
|
+
target.removeEventListener(type, listener, extended);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function findAncestor(origin, selector) {
|
|
61
|
+
if (!(origin instanceof Element) || selector == null) return null;
|
|
62
|
+
if (typeof selector === "string") {
|
|
63
|
+
if (origin.matches?.(selector)) return origin;
|
|
64
|
+
return origin.closest(selector);
|
|
65
|
+
}
|
|
66
|
+
if (typeof selector !== "function") return null;
|
|
67
|
+
if (selector(origin)) return origin;
|
|
68
|
+
let parent = origin.parentElement;
|
|
69
|
+
while (parent != null && !selector(parent)) {
|
|
70
|
+
if (parent === document.body) return null;
|
|
71
|
+
parent = parent.parentElement;
|
|
72
|
+
}
|
|
73
|
+
return parent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function attributable(attribute, add, remove) {
|
|
77
|
+
const observer = new MutationObserver(entries => {
|
|
78
|
+
const { length } = entries;
|
|
79
|
+
for (let index = 0; index < length; index += 1) {
|
|
80
|
+
const entry = entries[index];
|
|
81
|
+
if (entry.type === 'attributes' && entry.target instanceof HTMLElement) {
|
|
82
|
+
if (entry.target.hasAttribute(attribute)) {
|
|
83
|
+
handleAdded(attribute, [entry.target], add);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
handleRemoved([entry.target], remove);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (entry.type === 'childList') {
|
|
90
|
+
handleAdded(attribute, entry.addedNodes, add);
|
|
91
|
+
handleRemoved(entry.removedNodes, remove);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
observer.observe(document, {
|
|
96
|
+
attributeFilter: [attribute],
|
|
97
|
+
attributes: true,
|
|
98
|
+
childList: true,
|
|
99
|
+
subtree: true,
|
|
100
|
+
});
|
|
101
|
+
requestAnimationFrame(() => {
|
|
102
|
+
handleAdded(attribute, document.querySelectorAll(`[${attribute}]`), add);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function handleAdded(attribute, nodes, add) {
|
|
106
|
+
const elements = [...nodes].filter(node => node instanceof HTMLElement);
|
|
107
|
+
const { length } = elements;
|
|
108
|
+
for (let index = 0; index < length; index += 1) {
|
|
109
|
+
const element = elements[index];
|
|
110
|
+
if (element.hasAttribute(attribute)) {
|
|
111
|
+
add(element);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function handleRemoved(nodes, remove) {
|
|
116
|
+
const elements = [...nodes].filter(node => node instanceof HTMLElement);
|
|
117
|
+
const { length } = elements;
|
|
118
|
+
for (let index = 0; index < length; index += 1) {
|
|
119
|
+
remove(elements[index]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class Floatable {
|
|
124
|
+
options;
|
|
125
|
+
active = false;
|
|
126
|
+
disabled = false;
|
|
127
|
+
frame;
|
|
128
|
+
ignoreFocus = false;
|
|
129
|
+
constructor(options) {
|
|
130
|
+
this.options = options;
|
|
131
|
+
initialize(this);
|
|
132
|
+
}
|
|
133
|
+
destroy() {
|
|
134
|
+
stop(this);
|
|
135
|
+
const state = getState();
|
|
136
|
+
state?.active.delete(this);
|
|
137
|
+
state?.mapped.delete(this.options.anchor);
|
|
138
|
+
state?.mapped.delete(this.options.content);
|
|
139
|
+
this.options.anchor = undefined;
|
|
140
|
+
this.options.content = undefined;
|
|
141
|
+
}
|
|
142
|
+
disable() {
|
|
143
|
+
if (this.disabled) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.disabled = true;
|
|
147
|
+
stop(this);
|
|
148
|
+
getState()?.active.delete(this);
|
|
149
|
+
}
|
|
150
|
+
enable() {
|
|
151
|
+
if (!this.disabled) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.disabled = false;
|
|
155
|
+
initialize(this);
|
|
156
|
+
}
|
|
157
|
+
toggle(active, ignoreFocus) {
|
|
158
|
+
if (this.disabled || this.active === active) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const state = getState();
|
|
162
|
+
if (state == null) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.ignoreFocus = !active && (ignoreFocus ?? false);
|
|
166
|
+
stop(this);
|
|
167
|
+
if (active) {
|
|
168
|
+
state.active.add(this);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
state.active.delete(this);
|
|
172
|
+
}
|
|
173
|
+
if (this.options.interactive) {
|
|
174
|
+
if (active) {
|
|
175
|
+
state.order.push(this);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const index = state.order.indexOf(this);
|
|
179
|
+
if (index > -1) {
|
|
180
|
+
state.order.splice(index, 1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (active) {
|
|
185
|
+
activate(this);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
deactivate(this);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//
|
|
193
|
+
function activate(floatable) {
|
|
194
|
+
floatable.active = true;
|
|
195
|
+
document.body.append(floatable.options.content);
|
|
196
|
+
floatable.options.content.hidden = false;
|
|
197
|
+
updatePosition(floatable);
|
|
198
|
+
}
|
|
199
|
+
function closeAbove(element) {
|
|
200
|
+
const state = getState();
|
|
201
|
+
if (state == null) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const index = state.order.findIndex(floatable => floatable.options.anchor === element ||
|
|
205
|
+
floatable.options.content === element);
|
|
206
|
+
if (index === -1 || index === state.order.length - 1) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
while (index < state.order.length - 1) {
|
|
210
|
+
void state.order.pop()?.toggle(false, true);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function closeAll() {
|
|
214
|
+
const state = getState();
|
|
215
|
+
const floatables = [...(state?.active ?? [])];
|
|
216
|
+
state?.active.clear();
|
|
217
|
+
for (const floatable of floatables) {
|
|
218
|
+
void floatable.toggle(false, true);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function closeNonInteractive(state) {
|
|
222
|
+
for (const floatable of state.active) {
|
|
223
|
+
if (!floatable.options.interactive) {
|
|
224
|
+
void floatable.toggle(false);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function deactivate(floatable) {
|
|
229
|
+
floatable.active = false;
|
|
230
|
+
floatable.options.content.hidden = true;
|
|
231
|
+
floatable.options.anchor.insertAdjacentElement(INSERT_AFTEREND, floatable.options.content);
|
|
232
|
+
floatable.options.onAfter?.(false);
|
|
233
|
+
}
|
|
234
|
+
function getState() {
|
|
235
|
+
return window._oscarpalmer_oui_floatable;
|
|
236
|
+
}
|
|
237
|
+
function getX(anchor, content, position) {
|
|
238
|
+
if (position.endsWith(POSITION_SUFFIX_END)) {
|
|
239
|
+
const end = anchor.right - content.width;
|
|
240
|
+
return end < 0 ? 0 : end;
|
|
241
|
+
}
|
|
242
|
+
if (position.endsWith(POSITION_SUFFIX_START)) {
|
|
243
|
+
const start = anchor.left;
|
|
244
|
+
return start + content.width > window.innerWidth
|
|
245
|
+
? window.innerWidth - content.width
|
|
246
|
+
: start;
|
|
247
|
+
}
|
|
248
|
+
if (position.startsWith(POSITION_END)) {
|
|
249
|
+
return anchor.right;
|
|
250
|
+
}
|
|
251
|
+
if (position.startsWith(POSITION_START)) {
|
|
252
|
+
return anchor.left - content.width;
|
|
253
|
+
}
|
|
254
|
+
if (position.startsWith(POSITION_HORIZONTAL)) {
|
|
255
|
+
if (anchor.right + content.width <= window.innerWidth) {
|
|
256
|
+
return anchor.right;
|
|
257
|
+
}
|
|
258
|
+
return anchor.left - content.width;
|
|
259
|
+
}
|
|
260
|
+
const left = anchor.left + (anchor.width - content.width) / 2;
|
|
261
|
+
return left < 0 ? 0 : left;
|
|
262
|
+
}
|
|
263
|
+
function getY(anchor, content, position, preferAbove) {
|
|
264
|
+
if (position.startsWith(POSITION_ABOVE)) {
|
|
265
|
+
return anchor.top - content.height;
|
|
266
|
+
}
|
|
267
|
+
if (position.startsWith(POSITION_BELOW)) {
|
|
268
|
+
return anchor.bottom;
|
|
269
|
+
}
|
|
270
|
+
if (position.endsWith(POSITION_BOTTOM)) {
|
|
271
|
+
const bottom = anchor.bottom - content.height;
|
|
272
|
+
return bottom < 0 ? 0 : bottom;
|
|
273
|
+
}
|
|
274
|
+
if (position.endsWith(POSITION_TOP)) {
|
|
275
|
+
const top = anchor.top;
|
|
276
|
+
return top + content.height > window.innerHeight
|
|
277
|
+
? window.innerHeight - content.height
|
|
278
|
+
: top;
|
|
279
|
+
}
|
|
280
|
+
if (position.startsWith(POSITION_VERTICAL)) {
|
|
281
|
+
if (preferAbove && anchor.top - content.height >= 0) {
|
|
282
|
+
return anchor.top - content.height;
|
|
283
|
+
}
|
|
284
|
+
if (anchor.bottom + content.height <= document.body.clientHeight) {
|
|
285
|
+
return anchor.bottom;
|
|
286
|
+
}
|
|
287
|
+
return anchor.top - content.height;
|
|
288
|
+
}
|
|
289
|
+
return anchor.top + (anchor.height - content.height) / 2;
|
|
290
|
+
}
|
|
291
|
+
function initialize(floatable) {
|
|
292
|
+
const { anchor, content, interactive } = floatable.options;
|
|
293
|
+
if (interactive) {
|
|
294
|
+
anchor.setAttribute(ATTRIBUTE_ANCHOR, '');
|
|
295
|
+
content.setAttribute(ATTRIBUTE_CONTENT$1, '');
|
|
296
|
+
}
|
|
297
|
+
content.style.position = 'fixed';
|
|
298
|
+
content.style.inset = '0 auto auto 0';
|
|
299
|
+
content.style.zIndex = '10';
|
|
300
|
+
const state = getState();
|
|
301
|
+
state?.mapped.set(anchor, floatable);
|
|
302
|
+
state?.mapped.set(content, floatable);
|
|
303
|
+
}
|
|
304
|
+
function isDisabled(element) {
|
|
305
|
+
return (element.disabled ||
|
|
306
|
+
element.readOnly ||
|
|
307
|
+
element.getAttribute('aria-disabled') === 'true');
|
|
308
|
+
}
|
|
309
|
+
function onClick$1(event) {
|
|
310
|
+
const related = findAncestor(event.target, SELECTOR_ALL);
|
|
311
|
+
if (!(related instanceof HTMLElement)) {
|
|
312
|
+
closeAll();
|
|
313
|
+
}
|
|
314
|
+
else if (related.hasAttribute(ATTRIBUTE_CONTENT$1)) {
|
|
315
|
+
closeAbove(related);
|
|
316
|
+
}
|
|
317
|
+
else if (!isDisabled(related)) {
|
|
318
|
+
toggle$1(related);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function onKeyDown(event) {
|
|
322
|
+
if (event.key !== 'Escape') {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const state = getState();
|
|
326
|
+
if (state == null || state.active.size === 0) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
closeNonInteractive(state);
|
|
330
|
+
const related = findAncestor(event.target, SELECTOR_CONTENT$1);
|
|
331
|
+
if (!(related instanceof HTMLElement)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
closeAbove(related);
|
|
335
|
+
const floatable = state.mapped.get(related);
|
|
336
|
+
if (floatable != null) {
|
|
337
|
+
void floatable.toggle(false);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function stop(floatable) {
|
|
341
|
+
if (floatable.frame != null) {
|
|
342
|
+
cancelAnimationFrame(floatable.frame);
|
|
343
|
+
}
|
|
344
|
+
floatable.frame = undefined;
|
|
345
|
+
}
|
|
346
|
+
function toggle$1(anchor) {
|
|
347
|
+
const floatable = getState()?.mapped.get(anchor);
|
|
348
|
+
if (floatable == null) {
|
|
349
|
+
closeAll();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const active = Boolean(floatable.active);
|
|
353
|
+
const content = findAncestor(floatable.options.anchor, SELECTOR_CONTENT$1);
|
|
354
|
+
if (content == null) {
|
|
355
|
+
closeAll();
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
closeAbove(content);
|
|
359
|
+
}
|
|
360
|
+
void floatable.toggle(!active);
|
|
361
|
+
}
|
|
362
|
+
function updatePosition(floatable) {
|
|
363
|
+
let position = floatable.options.anchor
|
|
364
|
+
.getAttribute(floatable.options.positionAttribute)
|
|
365
|
+
?.trim() ?? floatable.options.defaultPosition;
|
|
366
|
+
if (!POSITIONS.has(position)) {
|
|
367
|
+
position = floatable.options.defaultPosition;
|
|
368
|
+
}
|
|
369
|
+
let previousAnchor;
|
|
370
|
+
let previousContent;
|
|
371
|
+
let calculated = false;
|
|
372
|
+
let runOnAfter = false;
|
|
373
|
+
function run() {
|
|
374
|
+
const anchor = floatable.options.anchor.getBoundingClientRect();
|
|
375
|
+
const content = floatable.options.content.getBoundingClientRect();
|
|
376
|
+
if (calculated) {
|
|
377
|
+
if (previousAnchor != null && previousContent != null) {
|
|
378
|
+
if (PROPERTIES.every(property => anchor[property] === previousAnchor[property]) &&
|
|
379
|
+
PROPERTIES.every(property => content[property] === previousContent[property])) {
|
|
380
|
+
floatable.frame = requestAnimationFrame(run);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
calculated = true;
|
|
387
|
+
}
|
|
388
|
+
update(anchor, content);
|
|
389
|
+
}
|
|
390
|
+
function update(anchor, content) {
|
|
391
|
+
previousAnchor = anchor;
|
|
392
|
+
previousContent = content;
|
|
393
|
+
const left = getX(anchor, content, position);
|
|
394
|
+
const top = getY(anchor, content, position, floatable.options.preferAbove);
|
|
395
|
+
if (top + content.height > window.innerHeight) {
|
|
396
|
+
floatable.options.content.style.height = `${window.innerHeight - top}px`;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
floatable.options.content.style.height = '';
|
|
400
|
+
}
|
|
401
|
+
if (left + content.width > window.innerWidth) {
|
|
402
|
+
floatable.options.content.style.width = `${window.innerWidth - left}px`;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
floatable.options.content.style.width = '';
|
|
406
|
+
}
|
|
407
|
+
// floatable.options.content.style.inset = `${top}px auto auto ${left}px`;
|
|
408
|
+
floatable.options.content.style.transform = `translate3d(${left}px, ${top}px, 0)`;
|
|
409
|
+
floatable.frame = requestAnimationFrame(run);
|
|
410
|
+
if (!runOnAfter) {
|
|
411
|
+
runOnAfter = true;
|
|
412
|
+
floatable.options.onAfter?.(true);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
floatable.frame = requestAnimationFrame(run);
|
|
416
|
+
}
|
|
417
|
+
//
|
|
418
|
+
const ATTRIBUTE_ANCHOR = 'oui-floatable-anchor';
|
|
419
|
+
const ATTRIBUTE_CONTENT$1 = 'oui-floatable-content';
|
|
420
|
+
const INSERT_AFTEREND = 'afterend';
|
|
421
|
+
const POSITION_ABOVE = 'above';
|
|
422
|
+
const POSITION_BELOW = 'below';
|
|
423
|
+
const POSITION_BOTTOM = 'bottom';
|
|
424
|
+
const POSITION_END = 'end';
|
|
425
|
+
const POSITION_HORIZONTAL = 'horizontal';
|
|
426
|
+
const POSITION_START = 'start';
|
|
427
|
+
const POSITION_SUFFIX_END = '-end';
|
|
428
|
+
const POSITION_SUFFIX_START = '-start';
|
|
429
|
+
const POSITION_TOP = 'top';
|
|
430
|
+
const POSITION_VERTICAL = 'vertical';
|
|
431
|
+
const POSITIONS = new Set([
|
|
432
|
+
'above',
|
|
433
|
+
'above-end',
|
|
434
|
+
'above-start',
|
|
435
|
+
'below',
|
|
436
|
+
'below-end',
|
|
437
|
+
'below-start',
|
|
438
|
+
'end',
|
|
439
|
+
'end-bottom',
|
|
440
|
+
'end-top',
|
|
441
|
+
'horizontal',
|
|
442
|
+
'horizontal-bottom',
|
|
443
|
+
'horizontal-top',
|
|
444
|
+
'start',
|
|
445
|
+
'start-bottom',
|
|
446
|
+
'start-top',
|
|
447
|
+
'vertical',
|
|
448
|
+
'vertical-end',
|
|
449
|
+
'vertical-start',
|
|
450
|
+
]);
|
|
451
|
+
const PROPERTIES = ['bottom', 'left', 'right', 'top'];
|
|
452
|
+
const SELECTOR_ALL = `[${ATTRIBUTE_ANCHOR}], [${ATTRIBUTE_CONTENT$1}]`;
|
|
453
|
+
const SELECTOR_CONTENT$1 = `[${ATTRIBUTE_CONTENT$1}]`;
|
|
454
|
+
//
|
|
455
|
+
if (window._oscarpalmer_oui_floatable == null) {
|
|
456
|
+
Object.defineProperty(window, '_oscarpalmer_oui_floatable', {
|
|
457
|
+
enumerable: false,
|
|
458
|
+
value: {
|
|
459
|
+
active: new Set(),
|
|
460
|
+
mapped: new Map(),
|
|
461
|
+
order: [],
|
|
462
|
+
},
|
|
463
|
+
writable: false,
|
|
464
|
+
});
|
|
465
|
+
on(document, 'keydown', event => {
|
|
466
|
+
onKeyDown(event);
|
|
467
|
+
});
|
|
468
|
+
on(document, 'click', event => {
|
|
469
|
+
onClick$1(event);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
class Tooltip {
|
|
474
|
+
attributes;
|
|
475
|
+
floatable;
|
|
476
|
+
timer;
|
|
477
|
+
constructor(anchor, content) {
|
|
478
|
+
this.attributes = content.attributes;
|
|
479
|
+
this.floatable = new Floatable({
|
|
480
|
+
anchor,
|
|
481
|
+
content: content.element,
|
|
482
|
+
defaultPosition: 'vertical',
|
|
483
|
+
interactive: false,
|
|
484
|
+
preferAbove: true,
|
|
485
|
+
positionAttribute: ATTRIBUTE_POSITION,
|
|
486
|
+
});
|
|
487
|
+
anchor.insertAdjacentElement('afterend', content.element);
|
|
488
|
+
TOOLTIPS_ALL.set(content.element, this);
|
|
489
|
+
}
|
|
490
|
+
destroy() {
|
|
491
|
+
const { attributes, floatable } = this;
|
|
492
|
+
TOOLTIPS_ACTIVE.delete(this);
|
|
493
|
+
TOOLTIPS_ALL.delete(floatable.options.anchor);
|
|
494
|
+
TOOLTIPS_ALL.delete(floatable.options.content);
|
|
495
|
+
reset(floatable.options.anchor, attributes);
|
|
496
|
+
floatable.options.content.remove();
|
|
497
|
+
floatable.destroy();
|
|
498
|
+
this.attributes = undefined;
|
|
499
|
+
this.floatable = undefined;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function addTooltip(anchor) {
|
|
503
|
+
if (TOOLTIPS_ALL.has(anchor)) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const content = getContent(anchor);
|
|
507
|
+
if (content != null) {
|
|
508
|
+
TOOLTIPS_ALL.set(anchor, new Tooltip(anchor, content));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function closeTooltips(next) {
|
|
512
|
+
const tooltips = [...TOOLTIPS_ACTIVE];
|
|
513
|
+
for (const active of tooltips) {
|
|
514
|
+
if (active === next) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (active.timer != null) {
|
|
518
|
+
clearTimeout(active.timer);
|
|
519
|
+
}
|
|
520
|
+
active.floatable.options.content.removeAttribute('oui-tooltip-content-active');
|
|
521
|
+
requestAnimationFrame(() => {
|
|
522
|
+
active.timer = setTimeout(() => {
|
|
523
|
+
TOOLTIPS_ACTIVE.delete(active);
|
|
524
|
+
active.floatable.toggle(false);
|
|
525
|
+
}, delay);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function getContent(anchor) {
|
|
530
|
+
const attributes = {
|
|
531
|
+
ariaDescribedby: anchor.getAttribute('aria-describedby'),
|
|
532
|
+
ariaLabelledBy: anchor.getAttribute('aria-labelledby'),
|
|
533
|
+
};
|
|
534
|
+
const wrapper = getWrapper(anchor);
|
|
535
|
+
const labelledElements = getElements(attributes.ariaLabelledBy);
|
|
536
|
+
if (labelledElements.length > 0) {
|
|
537
|
+
anchor.setAttribute('aria-labelledby', wrapper.id);
|
|
538
|
+
for (const element of labelledElements) {
|
|
539
|
+
wrapper.append(element);
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
attributes,
|
|
543
|
+
element: wrapper,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const describedElements = getElements(attributes.ariaDescribedby);
|
|
547
|
+
if (describedElements.length > 0) {
|
|
548
|
+
anchor.setAttribute('aria-describedby', wrapper.id);
|
|
549
|
+
for (const element of describedElements) {
|
|
550
|
+
wrapper.append(element);
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
attributes,
|
|
554
|
+
element: wrapper,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
let content = anchor.getAttribute('aria-label');
|
|
558
|
+
if (isNullableOrWhitespace(content)) {
|
|
559
|
+
content = anchor.getAttribute('aria-description');
|
|
560
|
+
}
|
|
561
|
+
if (isNullableOrWhitespace(content)) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const paragraph = document.createElement('p');
|
|
565
|
+
paragraph.textContent = content;
|
|
566
|
+
wrapper.append(paragraph);
|
|
567
|
+
return {
|
|
568
|
+
attributes,
|
|
569
|
+
element: wrapper,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function getElements(attribute) {
|
|
573
|
+
const ids = attribute?.split(EXPRESSION_WHITESPACE) ?? [];
|
|
574
|
+
const elements = [];
|
|
575
|
+
for (const id of ids) {
|
|
576
|
+
if (isNullableOrWhitespace(id)) {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const element = document.querySelector(`#${id}`);
|
|
580
|
+
if (element instanceof HTMLElement) {
|
|
581
|
+
const cloned = element.cloneNode(true);
|
|
582
|
+
cloned.hidden = false;
|
|
583
|
+
elements.push(cloned);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return elements;
|
|
587
|
+
}
|
|
588
|
+
function getWrapper(anchor) {
|
|
589
|
+
const wrapper = document.createElement('div');
|
|
590
|
+
wrapper.setAttribute(ATTRIBUTE_CONTENT, '');
|
|
591
|
+
wrapper.className = anchor.getAttribute(ATTRIBUTE_CLASS) ?? '';
|
|
592
|
+
wrapper.hidden = true;
|
|
593
|
+
wrapper.id = `${SELECTOR}-${++index}`;
|
|
594
|
+
wrapper.role = 'tooltip';
|
|
595
|
+
wrapper.tabIndex = -1;
|
|
596
|
+
return wrapper;
|
|
597
|
+
}
|
|
598
|
+
function onActivate(event) {
|
|
599
|
+
onToggle(event, true);
|
|
600
|
+
}
|
|
601
|
+
function onClick(event) {
|
|
602
|
+
if (findAncestor(event.target, SELECTOR_CONTENT) != null) {
|
|
603
|
+
event.stopPropagation();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function onDeactivate(event) {
|
|
607
|
+
onToggle(event, false);
|
|
608
|
+
}
|
|
609
|
+
function onToggle(event, activate) {
|
|
610
|
+
if (toggle != null) {
|
|
611
|
+
cancelAnimationFrame(toggle);
|
|
612
|
+
}
|
|
613
|
+
toggle = requestAnimationFrame(() => {
|
|
614
|
+
toggle = undefined;
|
|
615
|
+
const element = findAncestor(event.target, SELECTOR_FULL);
|
|
616
|
+
const tooltip = TOOLTIPS_ALL.get(element);
|
|
617
|
+
if (TOOLTIPS_ACTIVE.size > 0) {
|
|
618
|
+
closeTooltips(tooltip);
|
|
619
|
+
}
|
|
620
|
+
if (tooltip == null ||
|
|
621
|
+
tooltip.floatable.options.content.hasAttribute('oui-tooltip-content-active') === activate) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (activate) {
|
|
625
|
+
if (tooltip.timer != null) {
|
|
626
|
+
clearTimeout(tooltip.timer);
|
|
627
|
+
}
|
|
628
|
+
TOOLTIPS_ACTIVE.add(tooltip);
|
|
629
|
+
tooltip.floatable.toggle(true);
|
|
630
|
+
requestAnimationFrame(() => {
|
|
631
|
+
tooltip.floatable.options.content.setAttribute('oui-tooltip-content-active', '');
|
|
632
|
+
});
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
tooltip.floatable.options.content.removeAttribute('oui-tooltip-content-active');
|
|
636
|
+
tooltip.timer ??= setTimeout(() => {
|
|
637
|
+
TOOLTIPS_ACTIVE.delete(tooltip);
|
|
638
|
+
tooltip.floatable.toggle(false);
|
|
639
|
+
}, delay);
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
function removeTooltip(element) {
|
|
643
|
+
if (!element.hasAttribute(ATTRIBUTE_CONTENT)) {
|
|
644
|
+
TOOLTIPS_ALL.get(element)?.destroy();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function reset(anchor, attributes) {
|
|
648
|
+
if (attributes.ariaDescribedby == null) {
|
|
649
|
+
anchor.removeAttribute('aria-describedby');
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
anchor.setAttribute('aria-describedby', attributes.ariaDescribedby);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function setDelay(value) {
|
|
656
|
+
delay = typeof value === 'number' && value >= 0 ? value : delay;
|
|
657
|
+
}
|
|
658
|
+
//
|
|
659
|
+
const SELECTOR = 'oui-tooltip';
|
|
660
|
+
const ATTRIBUTE_CLASS = `${SELECTOR}-class`;
|
|
661
|
+
const ATTRIBUTE_CONTENT = `${SELECTOR}-content`;
|
|
662
|
+
const ATTRIBUTE_POSITION = `${SELECTOR}-position`;
|
|
663
|
+
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
664
|
+
const SELECTOR_FULL = `[${SELECTOR}], [${ATTRIBUTE_CONTENT}]`;
|
|
665
|
+
const EXPRESSION_WHITESPACE = /\s+/;
|
|
666
|
+
const TOOLTIPS_ACTIVE = new Set();
|
|
667
|
+
const TOOLTIPS_ALL = new WeakMap();
|
|
668
|
+
let delay = 250;
|
|
669
|
+
let index = 0;
|
|
670
|
+
let toggle;
|
|
671
|
+
//
|
|
672
|
+
attributable(SELECTOR, addTooltip, removeTooltip);
|
|
673
|
+
on(document, 'click', onClick, { capture: true });
|
|
674
|
+
on(document, 'focusin', onActivate);
|
|
675
|
+
on(document, 'focusout', onDeactivate);
|
|
676
|
+
on(document, 'mousemove', onActivate);
|
|
677
|
+
on(document, 'touchstart', onActivate);
|
|
678
|
+
|
|
679
|
+
export { setDelay };
|