@oscarpalmer/oui 0.13.0 → 0.15.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/README.md +2 -0
- package/dist/css/popover.css +9 -0
- package/dist/css/tooltip.css +9 -3
- package/dist/css/value.css +1 -0
- package/dist/js/floatable.cjs +134 -0
- package/dist/js/floatable.js +134 -0
- package/dist/js/focus-trap/embedded.cjs +150 -0
- package/dist/js/focus-trap/embedded.js +150 -0
- package/dist/js/focus-trap/standalone.cjs +8 -0
- package/dist/js/focus-trap/standalone.js +7 -0
- package/dist/js/index.cjs +5 -1
- package/dist/js/index.js +6 -1
- package/dist/js/popover.cjs +160 -0
- package/dist/js/popover.js +160 -0
- package/dist/js/tooltip.cjs +83 -126
- package/dist/js/tooltip.js +83 -126
- package/package.json +7 -7
- package/src/css/popover.scss +9 -0
- package/src/css/tooltip.scss +7 -3
- package/src/js/floatable.ts +196 -0
- package/src/js/focus-trap/embedded.ts +215 -0
- package/src/js/focus-trap/standalone.ts +9 -0
- package/src/js/index.ts +3 -1
- package/src/js/popover.ts +221 -0
- package/src/js/tooltip.ts +97 -161
- package/types/floatable.d.cts +16 -0
- package/types/floatable.d.ts +12 -0
- package/types/focus-trap/embedded.d.cts +15 -0
- package/types/focus-trap/embedded.d.ts +11 -0
- package/types/index.d.cts +11 -0
- package/types/index.d.ts +3 -1
- package/types/popover.d.cts +14 -0
- package/types/popover.d.ts +15 -0
- package/dist/js/focus-trap.cjs +0 -104
- package/dist/js/focus-trap.js +0 -103
- package/src/js/focus-trap.ts +0 -133
- /package/types/{focus-trap.d.cts → focus-trap/standalone.d.cts} +0 -0
- /package/types/{focus-trap.d.ts → focus-trap/standalone.d.ts} +0 -0
package/dist/js/tooltip.js
CHANGED
|
@@ -1,144 +1,120 @@
|
|
|
1
1
|
import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
|
|
2
2
|
import { on } from "@oscarpalmer/toretto/event";
|
|
3
3
|
import { attributable } from "./attributable.js";
|
|
4
|
+
import { deactivate, Floatable, activate } from "./floatable.js";
|
|
4
5
|
class Tooltip {
|
|
5
|
-
constructor(parent) {
|
|
6
|
-
this.parent = parent;
|
|
7
|
-
this.element = getContent(parent);
|
|
8
|
-
if (this.element != null) {
|
|
9
|
-
parent.insertAdjacentElement("afterend", this.element);
|
|
10
|
-
initialize(this);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
6
|
abortController = new AbortController();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
attributes;
|
|
8
|
+
floatable;
|
|
9
|
+
constructor(anchor, content) {
|
|
10
|
+
this.attributes = content.attributes;
|
|
11
|
+
this.floatable = new Floatable(anchor, content.element);
|
|
12
|
+
initialize(this);
|
|
13
|
+
}
|
|
19
14
|
destroy() {
|
|
20
|
-
deactivate(this);
|
|
15
|
+
deactivate(this.floatable, active);
|
|
16
|
+
reset(this.floatable.anchor, this.attributes);
|
|
21
17
|
this.abortController.abort();
|
|
18
|
+
this.floatable.content.remove();
|
|
19
|
+
this.floatable.destroy();
|
|
22
20
|
this.abortController = void 0;
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.element = void 0;
|
|
26
|
-
this.frame = void 0;
|
|
27
|
-
this.parent = void 0;
|
|
21
|
+
this.attributes = void 0;
|
|
22
|
+
this.floatable = void 0;
|
|
28
23
|
}
|
|
29
24
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
if (tooltip.active) {
|
|
25
|
+
function addTooltip(anchor) {
|
|
26
|
+
if (tooltips.has(anchor)) {
|
|
33
27
|
return;
|
|
34
28
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
document.body.append(tooltip.element);
|
|
39
|
-
tooltip.element.hidden = false;
|
|
40
|
-
update(tooltip);
|
|
41
|
-
}, 250);
|
|
42
|
-
}
|
|
43
|
-
function addTooltip(element) {
|
|
44
|
-
if (!tooltips.has(element)) {
|
|
45
|
-
tooltips.set(element, new Tooltip(element));
|
|
29
|
+
const content = getContent(anchor);
|
|
30
|
+
if (content != null) {
|
|
31
|
+
tooltips.set(anchor, new Tooltip(anchor, content));
|
|
46
32
|
}
|
|
47
33
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
function getContent(anchor) {
|
|
35
|
+
const attributes = {
|
|
36
|
+
ariaDescribedby: anchor.getAttribute("aria-describedby"),
|
|
37
|
+
ariaLabelledBy: anchor.getAttribute("aria-labelledby")
|
|
38
|
+
};
|
|
39
|
+
const wrapper = getWrapper(anchor);
|
|
40
|
+
const labelledElements = getElements(attributes.ariaLabelledBy);
|
|
41
|
+
if (labelledElements.length > 0) {
|
|
42
|
+
anchor.setAttribute("aria-labelledby", wrapper.id);
|
|
43
|
+
for (const element of labelledElements) {
|
|
44
|
+
wrapper.append(element);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
attributes,
|
|
48
|
+
element: wrapper
|
|
49
|
+
};
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
tooltip.parent.insertAdjacentElement("afterend", tooltip.element);
|
|
58
|
-
}, time ?? 250);
|
|
59
|
-
}
|
|
60
|
-
function getContent(parent) {
|
|
61
|
-
const wrapper = getWrapper(parent);
|
|
62
|
-
const ids = (parent.getAttribute("aria-describedby") ?? "").split(/\s+/);
|
|
63
|
-
parent.setAttribute("aria-describedby", wrapper.id);
|
|
64
|
-
const elements = ids.map(
|
|
65
|
-
(id) => isNullableOrWhitespace(id) ? void 0 : document.querySelector(`#${id}`)
|
|
66
|
-
).filter((element) => element instanceof HTMLElement);
|
|
67
|
-
if (elements.length > 0) {
|
|
68
|
-
for (const element of elements) {
|
|
69
|
-
element.hidden = false;
|
|
51
|
+
const describedElements = getElements(attributes.ariaDescribedby);
|
|
52
|
+
if (describedElements.length > 0) {
|
|
53
|
+
anchor.setAttribute("aria-describedby", wrapper.id);
|
|
54
|
+
for (const element of describedElements) {
|
|
70
55
|
wrapper.append(element);
|
|
71
56
|
}
|
|
72
|
-
return
|
|
57
|
+
return {
|
|
58
|
+
attributes,
|
|
59
|
+
element: wrapper
|
|
60
|
+
};
|
|
73
61
|
}
|
|
74
|
-
let content =
|
|
62
|
+
let content = anchor.getAttribute("aria-label");
|
|
75
63
|
if (isNullableOrWhitespace(content)) {
|
|
76
|
-
content =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
64
|
+
content = anchor.getAttribute("aria-description");
|
|
65
|
+
}
|
|
66
|
+
if (isNullableOrWhitespace(content)) {
|
|
67
|
+
return;
|
|
80
68
|
}
|
|
81
69
|
const paragraph = document.createElement("p");
|
|
82
70
|
paragraph.textContent = content;
|
|
83
71
|
wrapper.append(paragraph);
|
|
84
|
-
return
|
|
72
|
+
return {
|
|
73
|
+
attributes,
|
|
74
|
+
element: wrapper
|
|
75
|
+
};
|
|
85
76
|
}
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
function getElements(attribute) {
|
|
78
|
+
const ids = attribute?.split(/\s+/) ?? [];
|
|
79
|
+
const elements = [];
|
|
80
|
+
for (const id of ids) {
|
|
81
|
+
if (isNullableOrWhitespace(id)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const element = document.querySelector(`#${id}`);
|
|
85
|
+
if (element instanceof HTMLElement) {
|
|
86
|
+
const cloned = element.cloneNode(true);
|
|
87
|
+
cloned.hidden = false;
|
|
88
|
+
elements.push(cloned);
|
|
89
|
+
}
|
|
94
90
|
}
|
|
95
|
-
return
|
|
91
|
+
return elements;
|
|
96
92
|
}
|
|
97
|
-
function getWrapper(
|
|
93
|
+
function getWrapper(anchor) {
|
|
98
94
|
const wrapper = document.createElement("div");
|
|
99
95
|
wrapper.setAttribute(`${selector}-content`, "");
|
|
100
|
-
wrapper.className =
|
|
96
|
+
wrapper.className = anchor.getAttribute(`${selector}-class`) ?? "";
|
|
101
97
|
wrapper.hidden = true;
|
|
102
98
|
wrapper.id = `${selector}-${++index}`;
|
|
103
99
|
wrapper.role = "tooltip";
|
|
104
100
|
wrapper.tabIndex = -1;
|
|
105
101
|
return wrapper;
|
|
106
102
|
}
|
|
107
|
-
function getX(parent, content) {
|
|
108
|
-
if (content.width + 2 * margin >= window.innerWidth) {
|
|
109
|
-
return {
|
|
110
|
-
left: margin,
|
|
111
|
-
right: margin
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
const left = parent.left + (parent.width - content.width) / 2;
|
|
115
|
-
const right = window.innerWidth - margin;
|
|
116
|
-
if (left < margin) {
|
|
117
|
-
return {
|
|
118
|
-
left: margin
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
if (left + content.width > right) {
|
|
122
|
-
return {
|
|
123
|
-
right: margin
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
left
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
103
|
function initialize(tooltip) {
|
|
131
|
-
const
|
|
104
|
+
const { abortController, floatable } = tooltip;
|
|
105
|
+
const { anchor, content } = floatable;
|
|
106
|
+
anchor.insertAdjacentElement("afterend", content);
|
|
107
|
+
const elements = [anchor, content];
|
|
132
108
|
for (const element of elements) {
|
|
133
109
|
for (const event of activateEvents) {
|
|
134
110
|
on(
|
|
135
111
|
element,
|
|
136
112
|
event,
|
|
137
113
|
() => {
|
|
138
|
-
activate(
|
|
114
|
+
activate(floatable, active, void 0, 250);
|
|
139
115
|
},
|
|
140
116
|
{
|
|
141
|
-
signal:
|
|
117
|
+
signal: abortController.signal
|
|
142
118
|
}
|
|
143
119
|
);
|
|
144
120
|
}
|
|
@@ -147,10 +123,10 @@ function initialize(tooltip) {
|
|
|
147
123
|
element,
|
|
148
124
|
event,
|
|
149
125
|
() => {
|
|
150
|
-
deactivate(
|
|
126
|
+
deactivate(floatable, active, void 0, 250);
|
|
151
127
|
},
|
|
152
128
|
{
|
|
153
|
-
signal:
|
|
129
|
+
signal: abortController.signal
|
|
154
130
|
}
|
|
155
131
|
);
|
|
156
132
|
}
|
|
@@ -160,43 +136,24 @@ function removeTooltip(element) {
|
|
|
160
136
|
tooltips.get(element)?.destroy();
|
|
161
137
|
tooltips.delete(element);
|
|
162
138
|
}
|
|
163
|
-
function
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (tooltip.activate != null) {
|
|
169
|
-
clearTimeout(tooltip.activate);
|
|
170
|
-
tooltip.activate = void 0;
|
|
171
|
-
}
|
|
172
|
-
if (tooltip.deactivate != null) {
|
|
173
|
-
clearTimeout(tooltip.deactivate);
|
|
174
|
-
tooltip.deactivate = void 0;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
function update(tooltip) {
|
|
178
|
-
function run() {
|
|
179
|
-
const parentRect = tooltip.parent.getBoundingClientRect();
|
|
180
|
-
const contentRect = tooltip.element.getBoundingClientRect();
|
|
181
|
-
const top = getTop(parentRect, contentRect);
|
|
182
|
-
const { left, right } = getX(parentRect, contentRect);
|
|
183
|
-
tooltip.element.style.inset = `${top}px ${right == null ? "auto" : `${right}px`} auto ${left == null ? "auto" : `${left}px`}`;
|
|
184
|
-
tooltip.frame = requestAnimationFrame(run);
|
|
139
|
+
function reset(anchor, attributes) {
|
|
140
|
+
if (attributes.ariaDescribedby == null) {
|
|
141
|
+
anchor.removeAttribute("aria-describedby");
|
|
142
|
+
} else {
|
|
143
|
+
anchor.setAttribute("aria-describedby", attributes.ariaDescribedby);
|
|
185
144
|
}
|
|
186
|
-
tooltip.frame = requestAnimationFrame(run);
|
|
187
145
|
}
|
|
188
146
|
const active = /* @__PURE__ */ new Set();
|
|
189
147
|
const activateEvents = ["focus", "mouseenter", "touchstart"];
|
|
190
148
|
const deactivateEvents = ["blur", "mouseleave"];
|
|
191
|
-
const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
|
|
192
149
|
const selector = "oui-tooltip";
|
|
193
150
|
const tooltips = /* @__PURE__ */ new WeakMap();
|
|
194
151
|
let index = 0;
|
|
195
152
|
attributable(selector, addTooltip, removeTooltip);
|
|
196
153
|
on(document, "keydown", (event) => {
|
|
197
154
|
if (event.key === "Escape") {
|
|
198
|
-
for (const
|
|
199
|
-
deactivate(
|
|
155
|
+
for (const floatable of active) {
|
|
156
|
+
deactivate(floatable, active);
|
|
200
157
|
}
|
|
201
158
|
}
|
|
202
159
|
});
|
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"postcss-input-range": "^6",
|
|
23
23
|
"postcss-scss": "^4",
|
|
24
24
|
"sass": "^1.89",
|
|
25
|
-
"stylelint": "^16.
|
|
25
|
+
"stylelint": "^16.22",
|
|
26
26
|
"tslib": "^2.8",
|
|
27
27
|
"typescript": "^5.8",
|
|
28
28
|
"vite": "^7",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"./focus-trap": {
|
|
43
43
|
"import": {
|
|
44
|
-
"types": "./types/focus-trap.d.ts",
|
|
45
|
-
"default": "./dist/js/focus-trap.js"
|
|
44
|
+
"types": "./types/focus-trap/standalone.d.ts",
|
|
45
|
+
"default": "./dist/js/focus-trap/standalone.js"
|
|
46
46
|
},
|
|
47
47
|
"require": {
|
|
48
|
-
"types": "./types/focus-trap.d.cts",
|
|
49
|
-
"default": "./dist/js/focus-trap.cjs"
|
|
48
|
+
"types": "./types/focus-trap/standalone.d.cts",
|
|
49
|
+
"default": "./dist/js/focus-trap/standalone.cjs"
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"./tooltip": {
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"build:postcss": "npx postcss ./src/css/*.scss -d ./postcss -u autoprefixer postcss-input-range --parser postcss-scss",
|
|
86
86
|
"build:js": "npx vite build && npm run types",
|
|
87
87
|
"clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
|
|
88
|
-
"rollup": "npx rollup -c",
|
|
88
|
+
"rollup": "npx rollup -c --watch",
|
|
89
89
|
"test": "npx vitest --coverage",
|
|
90
90
|
"types": "npx tsc && npx dts-bundle-generator --config ./dts.config.cts --silent",
|
|
91
91
|
"watch:css": "npx sass ./postcss:./dist/css --no-source-map --watch",
|
|
@@ -94,5 +94,5 @@
|
|
|
94
94
|
},
|
|
95
95
|
"type": "module",
|
|
96
96
|
"types": "types/index.d.cts",
|
|
97
|
-
"version": "0.
|
|
97
|
+
"version": "0.15.0"
|
|
98
98
|
}
|
package/src/css/tooltip.scss
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
@use 'mixins.scss' as mixins;
|
|
2
2
|
|
|
3
|
+
:where([oui-tooltip]:is(abbr, b, cite, dfn, code, em, i, kbd, mark, q, samp, small, span, strong, sub, sup, time, var)) {
|
|
4
|
+
text-decoration: underline dotted;
|
|
5
|
+
text-decoration-skip-ink: auto;
|
|
6
|
+
text-underline-offset: 0.25em;
|
|
7
|
+
cursor: help;
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
:where([oui-tooltip-content]) {
|
|
4
11
|
@include mixins.shadowOverlay;
|
|
5
12
|
|
|
@@ -8,7 +15,4 @@
|
|
|
8
15
|
background-color: var(--oui-absolute);
|
|
9
16
|
border-radius: 0.25rem;
|
|
10
17
|
font-size: 0.875rem;
|
|
11
|
-
position: fixed;
|
|
12
|
-
inset: 0 auto auto 0;
|
|
13
|
-
z-index: 10;
|
|
14
18
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
export class Floatable {
|
|
2
|
+
activate: number | undefined;
|
|
3
|
+
active = false;
|
|
4
|
+
deactivate: number | undefined;
|
|
5
|
+
frame: DOMHighResTimeStamp | undefined;
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
public anchor: HTMLElement,
|
|
9
|
+
public content: HTMLElement,
|
|
10
|
+
) {
|
|
11
|
+
setStyles(content);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
destroy(): void {
|
|
15
|
+
this.activate = undefined;
|
|
16
|
+
this.deactivate = undefined;
|
|
17
|
+
this.frame = undefined;
|
|
18
|
+
|
|
19
|
+
this.anchor = undefined as never;
|
|
20
|
+
this.content = undefined as never;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function activate(
|
|
25
|
+
floatable: Floatable,
|
|
26
|
+
active: Set<Floatable>,
|
|
27
|
+
order?: Floatable[],
|
|
28
|
+
time?: number,
|
|
29
|
+
): void {
|
|
30
|
+
stop(floatable);
|
|
31
|
+
|
|
32
|
+
if (floatable.active) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (time == null || time === 0) {
|
|
37
|
+
onActivate(floatable, active, order);
|
|
38
|
+
} else {
|
|
39
|
+
floatable.activate = +setTimeout(() => {
|
|
40
|
+
onActivate(floatable, active, order);
|
|
41
|
+
}, time);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function deactivate(
|
|
46
|
+
floatable: Floatable,
|
|
47
|
+
active: Set<Floatable>,
|
|
48
|
+
order?: Floatable[],
|
|
49
|
+
time?: number,
|
|
50
|
+
): void {
|
|
51
|
+
stop(floatable);
|
|
52
|
+
|
|
53
|
+
if (!floatable.active) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (time == null || time === 0) {
|
|
58
|
+
onDeactivate(floatable, active, order);
|
|
59
|
+
} else {
|
|
60
|
+
floatable.deactivate = +setTimeout(() => {
|
|
61
|
+
onDeactivate(floatable, active, order);
|
|
62
|
+
}, time);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getX(
|
|
67
|
+
anchor: DOMRect,
|
|
68
|
+
content: DOMRect,
|
|
69
|
+
): {left?: number; right?: number} {
|
|
70
|
+
if (content.width + 2 * margin >= window.innerWidth) {
|
|
71
|
+
return {
|
|
72
|
+
left: margin,
|
|
73
|
+
right: margin,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const left = anchor.left + (anchor.width - content.width) / 2;
|
|
78
|
+
const right = window.innerWidth - margin;
|
|
79
|
+
|
|
80
|
+
if (left < margin) {
|
|
81
|
+
return {
|
|
82
|
+
left: margin,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (left + content.width > right) {
|
|
87
|
+
return {
|
|
88
|
+
right: margin,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
left,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getY(anchor: DOMRect, content: DOMRect): number {
|
|
98
|
+
let top = anchor.top - content.height - margin;
|
|
99
|
+
|
|
100
|
+
if (top - margin >= 0) {
|
|
101
|
+
return top;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
top = anchor.bottom + margin;
|
|
105
|
+
|
|
106
|
+
if (top + content.height + margin <= window.innerHeight) {
|
|
107
|
+
return top;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return anchor.top + (anchor.height - content.height) / 2;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function onActivate(
|
|
114
|
+
floatable: Floatable,
|
|
115
|
+
active: Set<Floatable>,
|
|
116
|
+
order?: Floatable[],
|
|
117
|
+
): void {
|
|
118
|
+
floatable.active = true;
|
|
119
|
+
|
|
120
|
+
active.add(floatable);
|
|
121
|
+
|
|
122
|
+
if (order != null) {
|
|
123
|
+
order.push(floatable);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
document.body.append(floatable.content);
|
|
127
|
+
|
|
128
|
+
floatable.content.hidden = false;
|
|
129
|
+
|
|
130
|
+
update(floatable);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function onDeactivate(
|
|
134
|
+
floatable: Floatable,
|
|
135
|
+
active: Set<Floatable>,
|
|
136
|
+
order?: Floatable[],
|
|
137
|
+
): void {
|
|
138
|
+
floatable.active = false;
|
|
139
|
+
|
|
140
|
+
active.delete(floatable);
|
|
141
|
+
|
|
142
|
+
const index = order?.indexOf(floatable) ?? -1;
|
|
143
|
+
|
|
144
|
+
if (index > -1) {
|
|
145
|
+
order?.splice(index, 1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
floatable.content.hidden = true;
|
|
149
|
+
|
|
150
|
+
floatable.anchor.insertAdjacentElement('afterend', floatable.content);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function update(floatable: Floatable): void {
|
|
154
|
+
function run(): void {
|
|
155
|
+
const anchor = floatable.anchor.getBoundingClientRect();
|
|
156
|
+
const content = floatable.content.getBoundingClientRect();
|
|
157
|
+
|
|
158
|
+
const {left, right} = getX(anchor, content);
|
|
159
|
+
const top = getY(anchor, content);
|
|
160
|
+
|
|
161
|
+
floatable.content.style.inset = `${top}px ${right == null ? 'auto' : `${right}px`} auto ${left == null ? 'auto' : `${left}px`}`;
|
|
162
|
+
|
|
163
|
+
floatable.frame = requestAnimationFrame(run);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
floatable.frame = requestAnimationFrame(run);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function setStyles(content: HTMLElement): void {
|
|
170
|
+
content.style.position = 'fixed';
|
|
171
|
+
content.style.inset = '0 auto auto 0';
|
|
172
|
+
content.style.zIndex = '10';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function stop(floatable: Floatable): void {
|
|
176
|
+
if (floatable.frame != null) {
|
|
177
|
+
cancelAnimationFrame(floatable.frame);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (floatable.activate != null) {
|
|
181
|
+
clearTimeout(floatable.activate);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (floatable.deactivate != null) {
|
|
185
|
+
clearTimeout(floatable.deactivate);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
floatable.activate = undefined;
|
|
189
|
+
floatable.deactivate = undefined;
|
|
190
|
+
floatable.frame = undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
//
|
|
194
|
+
|
|
195
|
+
const margin =
|
|
196
|
+
Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
|