@oscarpalmer/oui 0.20.0 → 0.22.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 +9 -9
- package/dist/css/tooltip.css +20 -20
- package/dist/js/focus-trap/embedded.mjs +1 -1
- package/dist/js/focus-trap/standalone.mjs +1 -1
- package/dist/js/index.d.mts +47 -4
- package/dist/js/index.mjs +2537 -5
- package/dist/js/{attributable.d.mts → internal/attributable.d.mts} +1 -1
- package/dist/js/{attributable.mjs → internal/attributable.mjs} +1 -1
- package/dist/js/internal/draggable.d.mts +2 -0
- package/dist/js/internal/draggable.mjs +215 -0
- package/dist/js/{floatable.d.mts → internal/floatable.d.mts} +3 -3
- package/dist/js/internal/floatable.mjs +187 -0
- package/dist/js/movable-BKFgoaOl.d.mts +101 -0
- package/dist/js/movable.d.mts +2 -0
- package/dist/js/movable.mjs +152 -0
- package/dist/js/popover.mjs +2 -2
- package/dist/js/sortable.d.mts +2 -0
- package/dist/js/sortable.mjs +243 -0
- package/dist/js/splitter.mjs +4 -4
- package/dist/js/tooltip.d.mts +2 -2
- package/dist/js/tooltip.mjs +98 -100
- package/package.json +28 -18
- package/src/css/mixins.scss +8 -12
- package/src/css/popover.scss +1 -1
- package/src/css/tooltip.scss +22 -22
- package/src/js/focus-trap/standalone.ts +1 -1
- package/src/js/index.ts +3 -1
- package/src/js/internal/draggable.ts +515 -0
- package/src/js/internal/floatable.ts +373 -0
- package/src/js/movable.ts +260 -0
- package/src/js/popover.ts +2 -2
- package/src/js/sortable.ts +467 -0
- package/src/js/splitter.ts +1 -4
- package/src/js/tooltip.ts +132 -132
- package/dist/js/floatable.mjs +0 -284
- package/src/js/floatable.ts +0 -590
- /package/src/js/{attributable.ts → internal/attributable.ts} +0 -0
package/src/js/tooltip.ts
CHANGED
|
@@ -1,56 +1,60 @@
|
|
|
1
1
|
import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import {on} from '@oscarpalmer/toretto/event';
|
|
3
3
|
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
4
|
-
import {attributable} from './attributable';
|
|
5
|
-
import {Floatable} from './floatable';
|
|
6
|
-
|
|
7
|
-
type Attributes = {
|
|
8
|
-
ariaDescribedby: string | null;
|
|
9
|
-
ariaLabelledBy: string | null;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type Content = {
|
|
13
|
-
attributes: Attributes;
|
|
14
|
-
element: HTMLElement;
|
|
15
|
-
};
|
|
4
|
+
import {attributable} from './internal/attributable';
|
|
5
|
+
import {Floatable} from './internal/floatable';
|
|
16
6
|
|
|
17
7
|
class Tooltip {
|
|
18
|
-
|
|
8
|
+
elements: HTMLElement[];
|
|
19
9
|
|
|
20
10
|
floatable: Floatable;
|
|
21
11
|
|
|
22
|
-
|
|
12
|
+
constructor(anchor: HTMLElement, elements: HTMLElement[]) {
|
|
13
|
+
const content = getContent();
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
this.attributes = content.attributes;
|
|
15
|
+
this.elements = elements;
|
|
26
16
|
|
|
27
17
|
this.floatable = new Floatable({
|
|
28
18
|
anchor,
|
|
29
|
-
content
|
|
30
|
-
defaultPosition: '
|
|
19
|
+
content,
|
|
20
|
+
defaultPosition: 'above',
|
|
31
21
|
interactive: false,
|
|
32
|
-
preferAbove: true,
|
|
33
22
|
positionAttribute: ATTRIBUTE_POSITION,
|
|
34
|
-
|
|
23
|
+
reusable: true,
|
|
24
|
+
onAfter: (active: boolean): void => {
|
|
25
|
+
if (active) {
|
|
26
|
+
content.innerHTML = '';
|
|
27
|
+
|
|
28
|
+
content.append(...this.elements);
|
|
29
|
+
}
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
if (active || TOOLTIPS_ACTIVE.size > 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
37
34
|
|
|
38
|
-
|
|
35
|
+
content.removeAttribute(ATTRIBUTE_CONTENT_ACTIVE);
|
|
36
|
+
|
|
37
|
+
contentTimer = setTimeout(() => {
|
|
38
|
+
content.innerHTML = '';
|
|
39
|
+
|
|
40
|
+
content.remove();
|
|
41
|
+
}, delayClose);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
destroy(): void {
|
|
42
|
-
const {
|
|
47
|
+
const {floatable} = this;
|
|
43
48
|
|
|
44
49
|
TOOLTIPS_ACTIVE.delete(this);
|
|
50
|
+
TOOLTIPS_PENDING.delete(this);
|
|
51
|
+
|
|
45
52
|
TOOLTIPS_ALL.delete(floatable.options.anchor);
|
|
46
53
|
TOOLTIPS_ALL.delete(floatable.options.content);
|
|
47
54
|
|
|
48
|
-
reset(floatable.options.anchor, attributes);
|
|
49
|
-
|
|
50
|
-
floatable.options.content.remove();
|
|
51
55
|
floatable.destroy();
|
|
52
56
|
|
|
53
|
-
this.
|
|
57
|
+
this.elements = [];
|
|
54
58
|
this.floatable = undefined as never;
|
|
55
59
|
}
|
|
56
60
|
}
|
|
@@ -60,78 +64,54 @@ function addTooltip(anchor: HTMLElement): void {
|
|
|
60
64
|
return;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
const
|
|
67
|
+
const elements = getElements(anchor);
|
|
64
68
|
|
|
65
|
-
if (
|
|
66
|
-
TOOLTIPS_ALL.set(anchor, new Tooltip(anchor,
|
|
69
|
+
if (elements != null) {
|
|
70
|
+
TOOLTIPS_ALL.set(anchor, new Tooltip(anchor, elements));
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
function closeTooltips(
|
|
71
|
-
const
|
|
74
|
+
function closeTooltips(current?: Tooltip): void {
|
|
75
|
+
const pending = [...TOOLTIPS_PENDING];
|
|
72
76
|
|
|
73
|
-
for (const
|
|
74
|
-
if (
|
|
77
|
+
for (const item of pending) {
|
|
78
|
+
if (item === current) {
|
|
75
79
|
continue;
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
clearTimeout(active.timer);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
active.floatable?.options.content.removeAttribute('oui-tooltip-content-active');
|
|
83
|
-
|
|
84
|
-
requestAnimationFrame(() => {
|
|
85
|
-
active.timer = setTimeout(() => {
|
|
86
|
-
TOOLTIPS_ACTIVE.delete(active);
|
|
87
|
-
active.floatable?.toggle(false);
|
|
88
|
-
}, delay) as never;
|
|
89
|
-
});
|
|
82
|
+
TOOLTIPS_PENDING.delete(item);
|
|
90
83
|
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function getContent(anchor: HTMLElement): Content | undefined {
|
|
94
|
-
const attributes = {
|
|
95
|
-
ariaDescribedby: anchor.getAttribute('aria-describedby'),
|
|
96
|
-
ariaLabelledBy: anchor.getAttribute('aria-labelledby'),
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const wrapper = getWrapper(anchor);
|
|
100
84
|
|
|
101
|
-
const
|
|
85
|
+
const active = [...TOOLTIPS_ACTIVE];
|
|
102
86
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
for (const element of labelledElements) {
|
|
107
|
-
wrapper.append(element);
|
|
87
|
+
for (const item of active) {
|
|
88
|
+
if (item === current) {
|
|
89
|
+
continue;
|
|
108
90
|
}
|
|
109
91
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
};
|
|
92
|
+
TOOLTIPS_ACTIVE.delete(item);
|
|
93
|
+
|
|
94
|
+
item.floatable.toggle(false);
|
|
114
95
|
}
|
|
96
|
+
}
|
|
115
97
|
|
|
116
|
-
|
|
98
|
+
function getElements(anchor: HTMLElement): HTMLElement[] | undefined {
|
|
99
|
+
let elements = findElements(anchor.getAttribute(ARIA_LABELLEDBY));
|
|
117
100
|
|
|
118
|
-
if (
|
|
119
|
-
|
|
101
|
+
if (elements.length > 0) {
|
|
102
|
+
return elements.map(element => element.cloneNode(true)) as HTMLElement[];
|
|
103
|
+
}
|
|
120
104
|
|
|
121
|
-
|
|
122
|
-
wrapper.append(element);
|
|
123
|
-
}
|
|
105
|
+
elements = findElements(anchor.getAttribute(ARIA_DESCRIBEDBY));
|
|
124
106
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
element: wrapper,
|
|
128
|
-
};
|
|
107
|
+
if (elements.length > 0) {
|
|
108
|
+
return elements.map(element => element.cloneNode(true)) as HTMLElement[];
|
|
129
109
|
}
|
|
130
110
|
|
|
131
|
-
let content = anchor.getAttribute(
|
|
111
|
+
let content = anchor.getAttribute(ARIA_LABEL);
|
|
132
112
|
|
|
133
113
|
if (isNullableOrWhitespace(content)) {
|
|
134
|
-
content = anchor.getAttribute(
|
|
114
|
+
content = anchor.getAttribute(ARIA_DESCRIPTION);
|
|
135
115
|
}
|
|
136
116
|
|
|
137
117
|
if (isNullableOrWhitespace(content)) {
|
|
@@ -142,15 +122,10 @@ function getContent(anchor: HTMLElement): Content | undefined {
|
|
|
142
122
|
|
|
143
123
|
paragraph.textContent = content;
|
|
144
124
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
attributes,
|
|
149
|
-
element: wrapper,
|
|
150
|
-
};
|
|
125
|
+
return [paragraph];
|
|
151
126
|
}
|
|
152
127
|
|
|
153
|
-
function
|
|
128
|
+
function findElements(attribute: string | null): HTMLElement[] {
|
|
154
129
|
const ids = attribute?.split(EXPRESSION_WHITESPACE) ?? [];
|
|
155
130
|
const elements: HTMLElement[] = [];
|
|
156
131
|
|
|
@@ -173,24 +148,31 @@ function getElements(attribute: string | null): HTMLElement[] {
|
|
|
173
148
|
return elements;
|
|
174
149
|
}
|
|
175
150
|
|
|
176
|
-
function
|
|
177
|
-
|
|
151
|
+
function getContent(): HTMLElement {
|
|
152
|
+
if (contentElement != null) {
|
|
153
|
+
return contentElement;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
contentElement = document.createElement('div');
|
|
178
157
|
|
|
179
|
-
|
|
158
|
+
contentElement.setAttribute(ATTRIBUTE_CONTENT, '');
|
|
180
159
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
wrapper.id = `${SELECTOR}-${++index}`;
|
|
184
|
-
wrapper.role = 'tooltip';
|
|
185
|
-
wrapper.tabIndex = -1;
|
|
160
|
+
contentElement.role = 'tooltip';
|
|
161
|
+
contentElement.tabIndex = -1;
|
|
186
162
|
|
|
187
|
-
return
|
|
163
|
+
return contentElement;
|
|
188
164
|
}
|
|
189
165
|
|
|
190
166
|
function onActivate(event: Event): void {
|
|
191
167
|
onToggle(event, true);
|
|
192
168
|
}
|
|
193
169
|
|
|
170
|
+
function onChange(): void {
|
|
171
|
+
if (TOOLTIPS_ACTIVE.size === 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
194
176
|
function onClick(event: Event): void {
|
|
195
177
|
if (findAncestor(event, SELECTOR_CONTENT) != null) {
|
|
196
178
|
event.stopPropagation();
|
|
@@ -206,45 +188,54 @@ function onToggle(event: Event, activate: boolean): void {
|
|
|
206
188
|
cancelAnimationFrame(toggle);
|
|
207
189
|
}
|
|
208
190
|
|
|
191
|
+
if (findAncestor(event, SELECTOR_CONTENT) != null) {
|
|
192
|
+
if (contentTimer != null) {
|
|
193
|
+
clearTimeout(contentTimer);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
209
199
|
toggle = requestAnimationFrame(() => {
|
|
210
200
|
toggle = undefined;
|
|
211
201
|
|
|
212
|
-
const element = findAncestor(event,
|
|
202
|
+
const element = findAncestor(event, SELECTOR) as HTMLElement;
|
|
213
203
|
|
|
214
204
|
const tooltip = TOOLTIPS_ALL.get(element);
|
|
215
205
|
|
|
216
|
-
if (TOOLTIPS_ACTIVE.size > 0) {
|
|
206
|
+
if (TOOLTIPS_ACTIVE.size > 0 || TOOLTIPS_PENDING.size > 0) {
|
|
217
207
|
closeTooltips(tooltip);
|
|
218
208
|
}
|
|
219
209
|
|
|
220
|
-
if (
|
|
221
|
-
tooltip == null ||
|
|
222
|
-
tooltip.floatable.options.content.hasAttribute('oui-tooltip-content-active') === activate
|
|
223
|
-
) {
|
|
210
|
+
if (tooltip == null || tooltip.floatable.active === activate) {
|
|
224
211
|
return;
|
|
225
212
|
}
|
|
226
213
|
|
|
227
214
|
if (activate) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
215
|
+
clearTimeout(contentTimer);
|
|
216
|
+
|
|
217
|
+
TOOLTIPS_PENDING.add(tooltip);
|
|
218
|
+
|
|
219
|
+
contentTimer = setTimeout(() => {
|
|
220
|
+
TOOLTIPS_ACTIVE.add(tooltip);
|
|
221
|
+
TOOLTIPS_PENDING.delete(tooltip);
|
|
222
|
+
|
|
223
|
+
tooltip.floatable.options.content.style.positionAnchor =
|
|
224
|
+
tooltip.floatable.options.anchor.style.anchorName;
|
|
231
225
|
|
|
232
|
-
|
|
233
|
-
tooltip.floatable.toggle(true);
|
|
226
|
+
tooltip.floatable.toggle(true);
|
|
234
227
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
tooltip.floatable.options.content.setAttribute(ATTRIBUTE_CONTENT_ACTIVE, '');
|
|
230
|
+
});
|
|
231
|
+
}, delayOpen);
|
|
238
232
|
|
|
239
233
|
return;
|
|
240
234
|
}
|
|
241
235
|
|
|
242
|
-
|
|
236
|
+
TOOLTIPS_ACTIVE.delete(tooltip);
|
|
243
237
|
|
|
244
|
-
tooltip.
|
|
245
|
-
TOOLTIPS_ACTIVE.delete(tooltip);
|
|
246
|
-
tooltip.floatable.toggle(false);
|
|
247
|
-
}, delay) as never;
|
|
238
|
+
tooltip.floatable.toggle(false);
|
|
248
239
|
});
|
|
249
240
|
}
|
|
250
241
|
|
|
@@ -254,31 +245,32 @@ function removeTooltip(element: HTMLElement): void {
|
|
|
254
245
|
}
|
|
255
246
|
}
|
|
256
247
|
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
} else {
|
|
261
|
-
anchor.setAttribute('aria-describedby', attributes.ariaDescribedby);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function setDelay(value: number): void {
|
|
266
|
-
delay = typeof value === 'number' && value >= 0 ? value : delay;
|
|
248
|
+
function setTooltipDelay(open?: number, close?: number): void {
|
|
249
|
+
delayClose = typeof close === 'number' && close >= 0 ? close : delayClose;
|
|
250
|
+
delayOpen = typeof open === 'number' && open >= 0 ? open : delayOpen;
|
|
267
251
|
}
|
|
268
252
|
|
|
269
253
|
//
|
|
270
254
|
|
|
271
|
-
const
|
|
255
|
+
const ARIA_DESCRIBEDBY = 'aria-describedby';
|
|
272
256
|
|
|
273
|
-
const
|
|
257
|
+
const ARIA_DESCRIPTION = 'aria-description';
|
|
274
258
|
|
|
275
|
-
const
|
|
259
|
+
const ARIA_LABEL = 'aria-label';
|
|
276
260
|
|
|
277
|
-
const
|
|
261
|
+
const ARIA_LABELLEDBY = 'aria-labelledby';
|
|
278
262
|
|
|
279
|
-
const
|
|
263
|
+
const ATTRIBUTE = 'oui-tooltip';
|
|
264
|
+
|
|
265
|
+
const ATTRIBUTE_CONTENT = `${ATTRIBUTE}-content`;
|
|
266
|
+
|
|
267
|
+
const ATTRIBUTE_CONTENT_ACTIVE = `${ATTRIBUTE_CONTENT}-active`;
|
|
280
268
|
|
|
281
|
-
const
|
|
269
|
+
const ATTRIBUTE_POSITION = `${ATTRIBUTE}-position`;
|
|
270
|
+
|
|
271
|
+
const SELECTOR = `[${ATTRIBUTE}]`;
|
|
272
|
+
|
|
273
|
+
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
282
274
|
|
|
283
275
|
const EXPRESSION_WHITESPACE = /\s+/;
|
|
284
276
|
|
|
@@ -286,22 +278,30 @@ const TOOLTIPS_ACTIVE: Set<Tooltip> = new Set();
|
|
|
286
278
|
|
|
287
279
|
const TOOLTIPS_ALL: WeakMap<HTMLElement, Tooltip> = new WeakMap();
|
|
288
280
|
|
|
289
|
-
|
|
281
|
+
const TOOLTIPS_PENDING: Set<Tooltip> = new Set();
|
|
282
|
+
|
|
283
|
+
let contentElement: HTMLElement | undefined;
|
|
284
|
+
|
|
285
|
+
let contentTimer: number | undefined;
|
|
286
|
+
|
|
287
|
+
let delayClose = 250;
|
|
290
288
|
|
|
291
|
-
let
|
|
289
|
+
let delayOpen = 500;
|
|
292
290
|
|
|
293
291
|
let toggle: DOMHighResTimeStamp | undefined;
|
|
294
292
|
|
|
295
293
|
//
|
|
296
294
|
|
|
297
|
-
attributable(
|
|
295
|
+
attributable(ATTRIBUTE, addTooltip, removeTooltip);
|
|
298
296
|
|
|
299
297
|
on(document, 'click', onClick, {capture: true});
|
|
300
298
|
on(document, 'focusin', onActivate);
|
|
301
299
|
on(document, 'focusout', onDeactivate);
|
|
302
300
|
on(document, 'mousemove', onActivate);
|
|
301
|
+
on(window, 'resize', onChange);
|
|
302
|
+
on(document, 'scroll', onChange);
|
|
303
303
|
on(document, 'touchstart', onActivate);
|
|
304
304
|
|
|
305
305
|
//
|
|
306
306
|
|
|
307
|
-
export {
|
|
307
|
+
export {setTooltipDelay};
|
package/dist/js/floatable.mjs
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import { on } from "@oscarpalmer/toretto/event";
|
|
2
|
-
import { findAncestor } from "@oscarpalmer/toretto/find";
|
|
3
|
-
//#region src/js/floatable.ts
|
|
4
|
-
var Floatable = class {
|
|
5
|
-
active = false;
|
|
6
|
-
disabled = false;
|
|
7
|
-
frame;
|
|
8
|
-
ignoreFocus = false;
|
|
9
|
-
constructor(options) {
|
|
10
|
-
this.options = options;
|
|
11
|
-
initialize(this);
|
|
12
|
-
}
|
|
13
|
-
destroy() {
|
|
14
|
-
stop(this);
|
|
15
|
-
const state = getState();
|
|
16
|
-
state?.active.delete(this);
|
|
17
|
-
state?.mapped.delete(this.options.anchor);
|
|
18
|
-
state?.mapped.delete(this.options.content);
|
|
19
|
-
this.options.anchor = void 0;
|
|
20
|
-
this.options.content = void 0;
|
|
21
|
-
}
|
|
22
|
-
disable() {
|
|
23
|
-
if (this.disabled) return;
|
|
24
|
-
this.disabled = true;
|
|
25
|
-
stop(this);
|
|
26
|
-
getState()?.active.delete(this);
|
|
27
|
-
}
|
|
28
|
-
enable() {
|
|
29
|
-
if (!this.disabled) return;
|
|
30
|
-
this.disabled = false;
|
|
31
|
-
initialize(this);
|
|
32
|
-
}
|
|
33
|
-
toggle(active, ignoreFocus) {
|
|
34
|
-
if (this.disabled || this.active === active) return;
|
|
35
|
-
const state = getState();
|
|
36
|
-
if (state == null) return;
|
|
37
|
-
this.ignoreFocus = !active && (ignoreFocus ?? false);
|
|
38
|
-
stop(this);
|
|
39
|
-
if (active) state.active.add(this);
|
|
40
|
-
else state.active.delete(this);
|
|
41
|
-
if (this.options.interactive) if (active) state.order.push(this);
|
|
42
|
-
else {
|
|
43
|
-
const index = state.order.indexOf(this);
|
|
44
|
-
if (index > -1) state.order.splice(index, 1);
|
|
45
|
-
}
|
|
46
|
-
if (active) activate(this);
|
|
47
|
-
else deactivate(this);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
function activate(floatable) {
|
|
51
|
-
floatable.active = true;
|
|
52
|
-
document.body.append(floatable.options.content);
|
|
53
|
-
floatable.options.content.hidden = false;
|
|
54
|
-
updatePosition(floatable);
|
|
55
|
-
}
|
|
56
|
-
function closeAbove(element) {
|
|
57
|
-
const state = getState();
|
|
58
|
-
if (state == null) return;
|
|
59
|
-
const index = state.order.findIndex((floatable) => floatable.options.anchor === element || floatable.options.content === element);
|
|
60
|
-
if (index === -1 || index === state.order.length - 1) return;
|
|
61
|
-
while (index < state.order.length - 1) state.order.pop()?.toggle(false, true);
|
|
62
|
-
}
|
|
63
|
-
function closeAll() {
|
|
64
|
-
const state = getState();
|
|
65
|
-
const floatables = [...state?.active ?? []];
|
|
66
|
-
state?.active.clear();
|
|
67
|
-
for (const floatable of floatables) floatable.toggle(false, true);
|
|
68
|
-
}
|
|
69
|
-
function closeNonInteractive(state) {
|
|
70
|
-
for (const floatable of state.active) if (!floatable.options.interactive) floatable.toggle(false);
|
|
71
|
-
}
|
|
72
|
-
function deactivate(floatable) {
|
|
73
|
-
floatable.active = false;
|
|
74
|
-
floatable.options.content.hidden = true;
|
|
75
|
-
floatable.options.anchor.insertAdjacentElement(INSERT_AFTEREND, floatable.options.content);
|
|
76
|
-
floatable.options.onAfter?.(false);
|
|
77
|
-
}
|
|
78
|
-
function getAttribute(position, anchor, top, left) {
|
|
79
|
-
switch (position) {
|
|
80
|
-
case "horizontal":
|
|
81
|
-
case "horizontal-bottom":
|
|
82
|
-
case "horizontal-top": return position.replace("horizontal", left >= anchor.right ? "right" : "left");
|
|
83
|
-
case "vertical":
|
|
84
|
-
case "vertical-end":
|
|
85
|
-
case "vertical-start": return position.replace("vertical", top >= anchor.bottom ? "bottom" : "top");
|
|
86
|
-
default: break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function getMargin() {
|
|
90
|
-
try {
|
|
91
|
-
const fontsize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
92
|
-
if (Number.isNaN(fontsize)) return 5;
|
|
93
|
-
const margin = fontsize / 2;
|
|
94
|
-
return margin < 5 ? 5 : margin;
|
|
95
|
-
} catch {
|
|
96
|
-
return 5;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function getState() {
|
|
100
|
-
return window._oscarpalmer_oui_floatable;
|
|
101
|
-
}
|
|
102
|
-
function getX(anchor, content, position, margin) {
|
|
103
|
-
if (position === "above" || position === "below" || position === "vertical") {
|
|
104
|
-
const center = anchor.left + anchor.width / 2;
|
|
105
|
-
const width = content.width / 2;
|
|
106
|
-
if (center + width + margin >= document.body.clientWidth) return document.body.clientWidth - content.width - margin / 2;
|
|
107
|
-
if (center - width - margin <= 0) return margin / 2;
|
|
108
|
-
return center - width;
|
|
109
|
-
}
|
|
110
|
-
if (position.startsWith("end")) return anchor.right;
|
|
111
|
-
if (position.startsWith("start")) return anchor.left - content.width;
|
|
112
|
-
if (position.endsWith("end")) return anchor.right - content.width - margin <= 0 ? margin / 2 : anchor.right - content.width;
|
|
113
|
-
if (position.endsWith("start")) {
|
|
114
|
-
if (anchor.left + content.width + margin >= document.body.clientWidth) return document.body.clientWidth - content.width - margin / 2;
|
|
115
|
-
return anchor.left - margin <= 0 ? margin / 2 : anchor.left;
|
|
116
|
-
}
|
|
117
|
-
if (!position.startsWith("horizontal")) return 0;
|
|
118
|
-
if (anchor.right + content.width + margin >= document.body.clientWidth) return anchor.left - content.width;
|
|
119
|
-
return anchor.right;
|
|
120
|
-
}
|
|
121
|
-
function getY(anchor, content, position, margin, preferAbove) {
|
|
122
|
-
if (position === "end" || position === "start" || position === "horizontal") {
|
|
123
|
-
const center = anchor.top + anchor.height / 2;
|
|
124
|
-
const height = content.height / 2;
|
|
125
|
-
if (center + height + margin >= document.body.clientHeight) return document.body.clientHeight - content.height - margin / 2;
|
|
126
|
-
if (center - height - margin <= 0) return margin / 2;
|
|
127
|
-
return center - height;
|
|
128
|
-
}
|
|
129
|
-
if (position.startsWith("above")) return anchor.top - content.height;
|
|
130
|
-
if (position.startsWith("below")) return anchor.bottom;
|
|
131
|
-
if (position.endsWith("bottom")) return anchor.bottom - content.height - margin <= 0 ? margin / 2 : anchor.bottom - content.height;
|
|
132
|
-
if (position.endsWith("top")) return anchor.top + content.height + margin >= document.body.clientHeight ? document.body.clientHeight - content.height - margin : anchor.top;
|
|
133
|
-
if (!position.startsWith("vertical")) return 0;
|
|
134
|
-
if (preferAbove) {
|
|
135
|
-
if (anchor.top - content.height - margin <= 0 && anchor.bottom + content.height + margin <= document.body.clientHeight) return anchor.bottom;
|
|
136
|
-
return anchor.top - content.height;
|
|
137
|
-
}
|
|
138
|
-
if (anchor.bottom + content.height + margin >= document.body.clientHeight) return anchor.top - content.height;
|
|
139
|
-
return anchor.bottom;
|
|
140
|
-
}
|
|
141
|
-
function initialize(floatable) {
|
|
142
|
-
const { anchor, content, interactive } = floatable.options;
|
|
143
|
-
if (interactive) {
|
|
144
|
-
anchor.setAttribute(ATTRIBUTE_ANCHOR, "");
|
|
145
|
-
content.setAttribute(ATTRIBUTE_CONTENT, "");
|
|
146
|
-
}
|
|
147
|
-
content.style.position = "fixed";
|
|
148
|
-
content.style.inset = "0 auto auto 0";
|
|
149
|
-
content.style.zIndex = "10";
|
|
150
|
-
const state = getState();
|
|
151
|
-
state?.mapped.set(anchor, floatable);
|
|
152
|
-
state?.mapped.set(content, floatable);
|
|
153
|
-
}
|
|
154
|
-
function isDisabled(element) {
|
|
155
|
-
return element.disabled || element.readOnly || element.getAttribute("aria-disabled") === "true";
|
|
156
|
-
}
|
|
157
|
-
function onClick(event) {
|
|
158
|
-
const related = findAncestor(event, SELECTOR_ALL);
|
|
159
|
-
if (!(related instanceof HTMLElement)) closeAll();
|
|
160
|
-
else if (related.hasAttribute(ATTRIBUTE_CONTENT)) closeAbove(related);
|
|
161
|
-
else if (!isDisabled(related)) toggle(related);
|
|
162
|
-
}
|
|
163
|
-
function onKeyDown(event) {
|
|
164
|
-
if (event.key !== "Escape") return;
|
|
165
|
-
const state = getState();
|
|
166
|
-
if (state == null || state.active.size === 0) return;
|
|
167
|
-
closeNonInteractive(state);
|
|
168
|
-
const related = findAncestor(event, SELECTOR_CONTENT);
|
|
169
|
-
if (!(related instanceof HTMLElement)) return;
|
|
170
|
-
closeAbove(related);
|
|
171
|
-
const floatable = state.mapped.get(related);
|
|
172
|
-
if (floatable != null) floatable.toggle(false);
|
|
173
|
-
}
|
|
174
|
-
function setAttribute(position, anchor, floater, top, left) {
|
|
175
|
-
let actual;
|
|
176
|
-
if (/^(horizontal|vertical)/.test(position)) actual = getAttribute(position, anchor, top, left);
|
|
177
|
-
floater.setAttribute("position", actual ?? position);
|
|
178
|
-
}
|
|
179
|
-
function stop(floatable) {
|
|
180
|
-
if (floatable.frame != null) cancelAnimationFrame(floatable.frame);
|
|
181
|
-
floatable.frame = void 0;
|
|
182
|
-
}
|
|
183
|
-
function toggle(anchor) {
|
|
184
|
-
const floatable = getState()?.mapped.get(anchor);
|
|
185
|
-
if (floatable == null) {
|
|
186
|
-
closeAll();
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
const active = Boolean(floatable.active);
|
|
190
|
-
const content = findAncestor(floatable.options.anchor, SELECTOR_CONTENT);
|
|
191
|
-
if (content == null) closeAll();
|
|
192
|
-
else closeAbove(content);
|
|
193
|
-
floatable.toggle(!active);
|
|
194
|
-
}
|
|
195
|
-
function updatePosition(floatable) {
|
|
196
|
-
let position = floatable.options.anchor.getAttribute(floatable.options.positionAttribute)?.trim() ?? floatable.options.defaultPosition;
|
|
197
|
-
if (!POSITIONS.has(position)) position = floatable.options.defaultPosition;
|
|
198
|
-
const margin = getMargin();
|
|
199
|
-
let previousAnchor;
|
|
200
|
-
let previousContent;
|
|
201
|
-
let calculated = false;
|
|
202
|
-
let runOnAfter = false;
|
|
203
|
-
function run() {
|
|
204
|
-
const anchor = floatable.options.anchor.getBoundingClientRect();
|
|
205
|
-
const content = floatable.options.content.getBoundingClientRect();
|
|
206
|
-
if (calculated) {
|
|
207
|
-
if (previousAnchor != null && previousContent != null) {
|
|
208
|
-
if (PROPERTIES.every((property) => anchor[property] === previousAnchor[property]) && PROPERTIES.every((property) => content[property] === previousContent[property])) {
|
|
209
|
-
floatable.frame = requestAnimationFrame(run);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} else calculated = true;
|
|
214
|
-
update(anchor, content);
|
|
215
|
-
}
|
|
216
|
-
function update(anchor, content) {
|
|
217
|
-
previousAnchor = anchor;
|
|
218
|
-
previousContent = content;
|
|
219
|
-
const left = getX(anchor, content, position, margin);
|
|
220
|
-
const top = getY(anchor, content, position, margin, floatable.options.preferAbove);
|
|
221
|
-
setAttribute(position, anchor, floatable.options.content, top, left);
|
|
222
|
-
if (top + content.height > window.innerHeight) floatable.options.content.style.height = `${window.innerHeight - top}px`;
|
|
223
|
-
else floatable.options.content.style.height = "";
|
|
224
|
-
if (left + content.width > window.innerWidth) floatable.options.content.style.width = `${window.innerWidth - left}px`;
|
|
225
|
-
else floatable.options.content.style.width = "";
|
|
226
|
-
floatable.options.content.style.transform = `translate3d(${left}px, ${top}px, 0)`;
|
|
227
|
-
floatable.frame = requestAnimationFrame(run);
|
|
228
|
-
if (!runOnAfter) {
|
|
229
|
-
runOnAfter = true;
|
|
230
|
-
floatable.options.onAfter?.(true);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
floatable.frame = requestAnimationFrame(run);
|
|
234
|
-
}
|
|
235
|
-
const ATTRIBUTE_ANCHOR = "oui-floatable-anchor";
|
|
236
|
-
const ATTRIBUTE_CONTENT = "oui-floatable-content";
|
|
237
|
-
const INSERT_AFTEREND = "afterend";
|
|
238
|
-
const POSITIONS = new Set([
|
|
239
|
-
"above",
|
|
240
|
-
"above-end",
|
|
241
|
-
"above-start",
|
|
242
|
-
"below",
|
|
243
|
-
"below-end",
|
|
244
|
-
"below-start",
|
|
245
|
-
"end",
|
|
246
|
-
"end-bottom",
|
|
247
|
-
"end-top",
|
|
248
|
-
"horizontal",
|
|
249
|
-
"horizontal-bottom",
|
|
250
|
-
"horizontal-top",
|
|
251
|
-
"start",
|
|
252
|
-
"start-bottom",
|
|
253
|
-
"start-top",
|
|
254
|
-
"vertical",
|
|
255
|
-
"vertical-end",
|
|
256
|
-
"vertical-start"
|
|
257
|
-
]);
|
|
258
|
-
const PROPERTIES = [
|
|
259
|
-
"bottom",
|
|
260
|
-
"left",
|
|
261
|
-
"right",
|
|
262
|
-
"top"
|
|
263
|
-
];
|
|
264
|
-
const SELECTOR_ALL = `[${ATTRIBUTE_ANCHOR}], [${ATTRIBUTE_CONTENT}]`;
|
|
265
|
-
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
266
|
-
if (window._oscarpalmer_oui_floatable == null) {
|
|
267
|
-
Object.defineProperty(window, "_oscarpalmer_oui_floatable", {
|
|
268
|
-
enumerable: false,
|
|
269
|
-
value: {
|
|
270
|
-
active: /* @__PURE__ */ new Set(),
|
|
271
|
-
mapped: /* @__PURE__ */ new Map(),
|
|
272
|
-
order: []
|
|
273
|
-
},
|
|
274
|
-
writable: false
|
|
275
|
-
});
|
|
276
|
-
on(document, "keydown", (event) => {
|
|
277
|
-
onKeyDown(event);
|
|
278
|
-
});
|
|
279
|
-
on(document, "click", (event) => {
|
|
280
|
-
onClick(event);
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
//#endregion
|
|
284
|
-
export { Floatable, deactivate };
|