@oscarpalmer/oui 0.17.0 → 0.19.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 +13 -13
- package/dist/css/button.css +9 -9
- package/dist/css/color.css +99 -99
- package/dist/css/content.css +10 -12
- package/dist/css/flex.css +39 -39
- package/dist/css/form.css +47 -60
- package/dist/css/message.css +10 -6
- package/dist/css/reset.css +178 -57
- package/dist/css/table.css +4 -4
- package/dist/css/tooltip.css +26 -1
- package/dist/js/attributable.full.js +36 -0
- package/dist/js/attributable.js +30 -45
- package/dist/js/floatable.full.js +398 -0
- package/dist/js/floatable.js +254 -169
- package/dist/js/focus-trap/embedded.full.js +408 -0
- package/dist/js/focus-trap/embedded.js +131 -131
- package/dist/js/focus-trap/standalone.full.js +447 -0
- package/dist/js/focus-trap/standalone.js +4 -4
- package/dist/js/index.js +3 -4
- package/dist/js/oui.full.js +1573 -0
- package/dist/js/popover.full.js +760 -0
- package/dist/js/popover.js +84 -163
- package/dist/js/splitter.full.js +769 -0
- package/dist/js/splitter.js +264 -246
- package/dist/js/tooltip.full.js +613 -0
- package/dist/js/tooltip.js +160 -151
- package/package.json +35 -63
- package/src/css/a11y.scss +1 -1
- package/src/css/badge.scss +23 -19
- package/src/css/button.scss +9 -9
- package/src/css/color.scss +99 -99
- package/src/css/content.scss +6 -9
- package/src/css/flex.scss +21 -20
- package/src/css/form.scss +47 -62
- package/src/css/message.scss +11 -12
- package/src/css/mixins.scss +16 -10
- package/src/css/popover.scss +2 -2
- package/src/css/reset.scss +181 -55
- package/src/css/splitter.scss +15 -13
- package/src/css/table.scss +11 -11
- package/src/css/tooltip.scss +31 -2
- package/src/js/attributable.ts +3 -10
- package/src/js/floatable.ts +370 -141
- package/src/js/focus-trap/embedded.ts +141 -74
- package/src/js/focus-trap/standalone.ts +4 -4
- package/src/js/index.ts +1 -0
- package/src/js/popover.ts +52 -122
- package/src/js/splitter.ts +248 -131
- package/src/js/tooltip.ts +143 -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/css/value.css +0 -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
package/src/css/tooltip.scss
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
@use 'mixins.scss' as mixins;
|
|
2
2
|
|
|
3
|
-
:where(
|
|
3
|
+
:where(
|
|
4
|
+
[oui-tooltip]:is(
|
|
5
|
+
abbr,
|
|
6
|
+
b,
|
|
7
|
+
cite,
|
|
8
|
+
dfn,
|
|
9
|
+
code,
|
|
10
|
+
em,
|
|
11
|
+
i,
|
|
12
|
+
kbd,
|
|
13
|
+
mark,
|
|
14
|
+
q,
|
|
15
|
+
samp,
|
|
16
|
+
small,
|
|
17
|
+
span,
|
|
18
|
+
strong,
|
|
19
|
+
sub,
|
|
20
|
+
sup,
|
|
21
|
+
time,
|
|
22
|
+
var
|
|
23
|
+
)
|
|
24
|
+
) {
|
|
4
25
|
text-decoration: underline dotted;
|
|
5
26
|
text-decoration-skip-ink: auto;
|
|
6
27
|
text-underline-offset: 0.25em;
|
|
@@ -8,11 +29,19 @@
|
|
|
8
29
|
}
|
|
9
30
|
|
|
10
31
|
:where([oui-tooltip-content]) {
|
|
11
|
-
@include mixins.
|
|
32
|
+
@include mixins.shadow-overlay;
|
|
12
33
|
|
|
13
34
|
max-width: 48ch;
|
|
14
35
|
padding: 0.75em 1em;
|
|
36
|
+
opacity: 0;
|
|
15
37
|
background-color: var(--oui-absolute);
|
|
16
38
|
border-radius: 0.25rem;
|
|
17
39
|
font-size: 0.875rem;
|
|
40
|
+
transition: opacity 0.125s ease-in-out;
|
|
41
|
+
transition-delay: 0s;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
:where([oui-tooltip-content][oui-tooltip-content-active]) {
|
|
45
|
+
opacity: 1;
|
|
46
|
+
transition-delay: 0.25s;
|
|
18
47
|
}
|
package/src/js/attributable.ts
CHANGED
|
@@ -39,9 +39,7 @@ function handleAdded(
|
|
|
39
39
|
nodes: Node[] | NodeList,
|
|
40
40
|
add: (element: HTMLElement) => void,
|
|
41
41
|
): void {
|
|
42
|
-
const elements = [...nodes].filter(
|
|
43
|
-
node => node instanceof HTMLElement,
|
|
44
|
-
) as HTMLElement[];
|
|
42
|
+
const elements = [...nodes].filter(node => node instanceof HTMLElement) as HTMLElement[];
|
|
45
43
|
|
|
46
44
|
const {length} = elements;
|
|
47
45
|
|
|
@@ -54,13 +52,8 @@ function handleAdded(
|
|
|
54
52
|
}
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
function handleRemoved(
|
|
58
|
-
|
|
59
|
-
remove: (element: HTMLElement) => void,
|
|
60
|
-
): void {
|
|
61
|
-
const elements = [...nodes].filter(
|
|
62
|
-
node => node instanceof HTMLElement,
|
|
63
|
-
) as HTMLElement[];
|
|
55
|
+
function handleRemoved(nodes: Node[] | NodeList, remove: (element: HTMLElement) => void): void {
|
|
56
|
+
const elements = [...nodes].filter(node => node instanceof HTMLElement) as HTMLElement[];
|
|
64
57
|
|
|
65
58
|
const {length} = elements;
|
|
66
59
|
|
package/src/js/floatable.ts
CHANGED
|
@@ -1,31 +1,109 @@
|
|
|
1
|
+
import {on} from '@oscarpalmer/toretto/event';
|
|
2
|
+
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
3
|
+
|
|
1
4
|
export class Floatable {
|
|
2
|
-
|
|
5
|
+
active = false;
|
|
6
|
+
|
|
7
|
+
disabled = false;
|
|
8
|
+
|
|
9
|
+
frame: DOMHighResTimeStamp | undefined;
|
|
10
|
+
|
|
11
|
+
ignoreFocus = false;
|
|
12
|
+
|
|
13
|
+
constructor(readonly options: Options) {
|
|
14
|
+
initialize(this);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
destroy(): void {
|
|
18
|
+
stop(this);
|
|
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
|
+
}
|
|
3
41
|
|
|
4
|
-
|
|
42
|
+
enable(): void {
|
|
43
|
+
if (!this.disabled) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.disabled = false;
|
|
48
|
+
|
|
49
|
+
initialize(this);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toggle(active: boolean, ignoreFocus?: boolean): void {
|
|
53
|
+
if (this.disabled || this.active === active) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const state = getState();
|
|
58
|
+
|
|
59
|
+
if (state == null) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
5
62
|
|
|
6
|
-
|
|
63
|
+
this.ignoreFocus = !active && (ignoreFocus ?? false);
|
|
7
64
|
|
|
8
|
-
|
|
65
|
+
stop(this);
|
|
9
66
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
readonly position: Position,
|
|
15
|
-
readonly preferAbove: boolean,
|
|
16
|
-
) {
|
|
17
|
-
setStyles(content);
|
|
67
|
+
if (active) {
|
|
68
|
+
state.active.add(this);
|
|
69
|
+
} else {
|
|
70
|
+
state.active.delete(this);
|
|
18
71
|
}
|
|
19
72
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
73
|
+
if (this.options.interactive) {
|
|
74
|
+
if (active) {
|
|
75
|
+
state.order.push(this);
|
|
76
|
+
} else {
|
|
77
|
+
const index = state.order.indexOf(this);
|
|
24
78
|
|
|
25
|
-
|
|
26
|
-
|
|
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);
|
|
27
89
|
}
|
|
28
90
|
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type FloatableWindow = Window & {
|
|
94
|
+
// biome-ignore lint/style/useNamingConvention: Non-standard name to avoid conflicts
|
|
95
|
+
_oscarpalmer_oui_floatable: State;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
type Options = {
|
|
99
|
+
anchor: HTMLElement;
|
|
100
|
+
content: HTMLElement;
|
|
101
|
+
defaultPosition: Position;
|
|
102
|
+
interactive: boolean;
|
|
103
|
+
positionAttribute: string;
|
|
104
|
+
preferAbove: boolean;
|
|
105
|
+
onAfter?: (active: boolean) => void;
|
|
106
|
+
};
|
|
29
107
|
|
|
30
108
|
type Position =
|
|
31
109
|
| 'above'
|
|
@@ -47,221 +125,344 @@ type Position =
|
|
|
47
125
|
| 'vertical-end'
|
|
48
126
|
| 'vertical-start';
|
|
49
127
|
|
|
128
|
+
type State = {
|
|
129
|
+
active: Set<Floatable>;
|
|
130
|
+
mapped: Map<HTMLElement, Floatable>;
|
|
131
|
+
order: Floatable[];
|
|
132
|
+
};
|
|
133
|
+
|
|
50
134
|
//
|
|
51
135
|
|
|
52
|
-
|
|
53
|
-
floatable
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
stop(floatable);
|
|
136
|
+
function activate(floatable: Floatable): void {
|
|
137
|
+
floatable.active = true;
|
|
138
|
+
|
|
139
|
+
document.body.append(floatable.options.content);
|
|
140
|
+
|
|
141
|
+
floatable.options.content.hidden = false;
|
|
59
142
|
|
|
60
|
-
|
|
143
|
+
updatePosition(floatable);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function closeAbove(element: HTMLElement): void {
|
|
147
|
+
const state = getState();
|
|
148
|
+
|
|
149
|
+
if (state == null) {
|
|
61
150
|
return;
|
|
62
151
|
}
|
|
63
152
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
153
|
+
const index = state.order.findIndex(
|
|
154
|
+
floatable => floatable.options.anchor === element || floatable.options.content === element,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (index === -1 || index === state.order.length - 1) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
while (index < state.order.length - 1) {
|
|
162
|
+
void state.order.pop()?.toggle(false, true);
|
|
70
163
|
}
|
|
71
164
|
}
|
|
72
165
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
active: Set<Floatable>,
|
|
76
|
-
order?: Floatable[],
|
|
77
|
-
time?: number,
|
|
78
|
-
): void {
|
|
79
|
-
stop(floatable);
|
|
166
|
+
function closeAll(): void {
|
|
167
|
+
const state = getState();
|
|
80
168
|
|
|
81
|
-
|
|
82
|
-
|
|
169
|
+
const floatables = [...(state?.active ?? [])];
|
|
170
|
+
|
|
171
|
+
state?.active.clear();
|
|
172
|
+
|
|
173
|
+
for (const floatable of floatables) {
|
|
174
|
+
void floatable.toggle(false, true);
|
|
83
175
|
}
|
|
176
|
+
}
|
|
84
177
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}, time);
|
|
178
|
+
function closeNonInteractive(state: State): void {
|
|
179
|
+
for (const floatable of state.active) {
|
|
180
|
+
if (!floatable.options.interactive) {
|
|
181
|
+
void floatable.toggle(false);
|
|
182
|
+
}
|
|
91
183
|
}
|
|
92
184
|
}
|
|
93
185
|
|
|
186
|
+
export function deactivate(floatable: Floatable): void {
|
|
187
|
+
floatable.active = false;
|
|
188
|
+
|
|
189
|
+
floatable.options.content.hidden = true;
|
|
190
|
+
|
|
191
|
+
floatable.options.anchor.insertAdjacentElement(INSERT_AFTEREND, floatable.options.content);
|
|
192
|
+
|
|
193
|
+
floatable.options.onAfter?.(false);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getState(): State {
|
|
197
|
+
return (window as unknown as FloatableWindow)._oscarpalmer_oui_floatable;
|
|
198
|
+
}
|
|
199
|
+
|
|
94
200
|
function getX(anchor: DOMRect, content: DOMRect, position: Position): number {
|
|
95
|
-
if (position.endsWith(
|
|
201
|
+
if (position.endsWith(POSITION_SUFFIX_END)) {
|
|
96
202
|
const end = anchor.right - content.width;
|
|
97
203
|
|
|
98
|
-
return end
|
|
204
|
+
return end < 0 ? 0 : end;
|
|
99
205
|
}
|
|
100
206
|
|
|
101
|
-
if (position.endsWith(
|
|
207
|
+
if (position.endsWith(POSITION_SUFFIX_START)) {
|
|
102
208
|
const start = anchor.left;
|
|
103
209
|
|
|
104
|
-
return start + content.width
|
|
105
|
-
? window.innerWidth - content.width - margin
|
|
106
|
-
: start;
|
|
210
|
+
return start + content.width > window.innerWidth ? window.innerWidth - content.width : start;
|
|
107
211
|
}
|
|
108
212
|
|
|
109
|
-
if (position.startsWith(
|
|
110
|
-
return anchor.right
|
|
213
|
+
if (position.startsWith(POSITION_END)) {
|
|
214
|
+
return anchor.right;
|
|
111
215
|
}
|
|
112
216
|
|
|
113
|
-
if (position.startsWith(
|
|
114
|
-
return anchor.left - content.width
|
|
217
|
+
if (position.startsWith(POSITION_START)) {
|
|
218
|
+
return anchor.left - content.width;
|
|
115
219
|
}
|
|
116
220
|
|
|
117
|
-
if (position.startsWith(
|
|
118
|
-
if (anchor.right + content.width
|
|
119
|
-
return anchor.right
|
|
221
|
+
if (position.startsWith(POSITION_HORIZONTAL)) {
|
|
222
|
+
if (anchor.right + content.width <= window.innerWidth) {
|
|
223
|
+
return anchor.right;
|
|
120
224
|
}
|
|
121
225
|
|
|
122
|
-
return anchor.left - content.width
|
|
226
|
+
return anchor.left - content.width;
|
|
123
227
|
}
|
|
124
228
|
|
|
125
|
-
|
|
229
|
+
const left = anchor.left + (anchor.width - content.width) / 2;
|
|
230
|
+
|
|
231
|
+
return left < 0 ? 0 : left;
|
|
126
232
|
}
|
|
127
233
|
|
|
128
|
-
function getY(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
position: Position,
|
|
132
|
-
preferAbove: boolean,
|
|
133
|
-
): number {
|
|
134
|
-
if (position.startsWith('above')) {
|
|
135
|
-
return anchor.top - content.height - margin;
|
|
234
|
+
function getY(anchor: DOMRect, content: DOMRect, position: Position, preferAbove: boolean): number {
|
|
235
|
+
if (position.startsWith(POSITION_ABOVE)) {
|
|
236
|
+
return anchor.top - content.height;
|
|
136
237
|
}
|
|
137
238
|
|
|
138
|
-
if (position.startsWith(
|
|
139
|
-
return anchor.bottom
|
|
239
|
+
if (position.startsWith(POSITION_BELOW)) {
|
|
240
|
+
return anchor.bottom;
|
|
140
241
|
}
|
|
141
242
|
|
|
142
|
-
if (position.endsWith(
|
|
243
|
+
if (position.endsWith(POSITION_BOTTOM)) {
|
|
143
244
|
const bottom = anchor.bottom - content.height;
|
|
144
245
|
|
|
145
|
-
return bottom
|
|
246
|
+
return bottom < 0 ? 0 : bottom;
|
|
146
247
|
}
|
|
147
248
|
|
|
148
|
-
if (position.endsWith(
|
|
249
|
+
if (position.endsWith(POSITION_TOP)) {
|
|
149
250
|
const top = anchor.top;
|
|
150
251
|
|
|
151
|
-
return top + content.height
|
|
152
|
-
? window.innerHeight - content.height - margin
|
|
153
|
-
: top;
|
|
252
|
+
return top + content.height > window.innerHeight ? window.innerHeight - content.height : top;
|
|
154
253
|
}
|
|
155
254
|
|
|
156
|
-
if (position.startsWith(
|
|
157
|
-
if (preferAbove && anchor.top - content.height
|
|
158
|
-
return anchor.top - content.height
|
|
255
|
+
if (position.startsWith(POSITION_VERTICAL)) {
|
|
256
|
+
if (preferAbove && anchor.top - content.height >= 0) {
|
|
257
|
+
return anchor.top - content.height;
|
|
159
258
|
}
|
|
160
259
|
|
|
161
|
-
if (anchor.bottom + content.height
|
|
162
|
-
return anchor.bottom
|
|
260
|
+
if (anchor.bottom + content.height <= document.body.clientHeight) {
|
|
261
|
+
return anchor.bottom;
|
|
163
262
|
}
|
|
164
263
|
|
|
165
|
-
return anchor.top - content.height
|
|
264
|
+
return anchor.top - content.height;
|
|
166
265
|
}
|
|
167
266
|
|
|
168
267
|
return anchor.top + (anchor.height - content.height) / 2;
|
|
169
268
|
}
|
|
170
269
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
|
|
270
|
+
function initialize(floatable: Floatable): void {
|
|
271
|
+
const {anchor, content, interactive} = floatable.options;
|
|
272
|
+
|
|
273
|
+
if (interactive) {
|
|
274
|
+
anchor.setAttribute(ATTRIBUTE_ANCHOR, '');
|
|
275
|
+
content.setAttribute(ATTRIBUTE_CONTENT, '');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
content.style.position = 'fixed';
|
|
279
|
+
content.style.inset = '0 auto auto 0';
|
|
280
|
+
content.style.zIndex = '10';
|
|
281
|
+
|
|
282
|
+
const state = getState();
|
|
283
|
+
|
|
284
|
+
state?.mapped.set(anchor, floatable);
|
|
285
|
+
state?.mapped.set(content, floatable);
|
|
286
|
+
}
|
|
177
287
|
|
|
178
|
-
|
|
288
|
+
function isDisabled(element: HTMLElement): boolean {
|
|
289
|
+
return (
|
|
290
|
+
(element as HTMLInputElement).disabled ||
|
|
291
|
+
(element as HTMLInputElement).readOnly ||
|
|
292
|
+
element.getAttribute('aria-disabled') === 'true'
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function onClick(event: PointerEvent): void {
|
|
297
|
+
const related = findAncestor(event.target as never, SELECTOR_ALL);
|
|
179
298
|
|
|
180
|
-
if (
|
|
181
|
-
|
|
299
|
+
if (!(related instanceof HTMLElement)) {
|
|
300
|
+
closeAll();
|
|
301
|
+
} else if (related.hasAttribute(ATTRIBUTE_CONTENT)) {
|
|
302
|
+
closeAbove(related);
|
|
303
|
+
} else if (!isDisabled(related)) {
|
|
304
|
+
toggle(related);
|
|
182
305
|
}
|
|
306
|
+
}
|
|
183
307
|
|
|
184
|
-
|
|
308
|
+
function onKeyDown(event: KeyboardEvent): void {
|
|
309
|
+
if (event.key !== 'Escape') {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const state = getState();
|
|
314
|
+
|
|
315
|
+
if (state == null || state.active.size === 0) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
185
318
|
|
|
186
|
-
|
|
319
|
+
closeNonInteractive(state);
|
|
187
320
|
|
|
188
|
-
|
|
321
|
+
const related = findAncestor(event.target as never, SELECTOR_CONTENT);
|
|
322
|
+
|
|
323
|
+
if (!(related instanceof HTMLElement)) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
closeAbove(related);
|
|
328
|
+
|
|
329
|
+
const floatable = state.mapped.get(related);
|
|
330
|
+
|
|
331
|
+
if (floatable != null) {
|
|
332
|
+
void floatable.toggle(false);
|
|
333
|
+
}
|
|
189
334
|
}
|
|
190
335
|
|
|
191
|
-
function
|
|
192
|
-
floatable
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
): void {
|
|
196
|
-
floatable.active = false;
|
|
336
|
+
function stop(floatable: Floatable): void {
|
|
337
|
+
if (floatable.frame != null) {
|
|
338
|
+
cancelAnimationFrame(floatable.frame);
|
|
339
|
+
}
|
|
197
340
|
|
|
198
|
-
|
|
341
|
+
floatable.frame = undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function toggle(anchor: HTMLElement): void {
|
|
345
|
+
const floatable = getState()?.mapped.get(anchor);
|
|
199
346
|
|
|
200
|
-
|
|
347
|
+
if (floatable == null) {
|
|
348
|
+
closeAll();
|
|
201
349
|
|
|
202
|
-
|
|
203
|
-
order?.splice(index, 1);
|
|
350
|
+
return;
|
|
204
351
|
}
|
|
205
352
|
|
|
206
|
-
|
|
353
|
+
const active = Boolean(floatable.active);
|
|
354
|
+
const content = findAncestor(floatable.options.anchor, SELECTOR_CONTENT);
|
|
207
355
|
|
|
208
|
-
|
|
356
|
+
if (content == null) {
|
|
357
|
+
closeAll();
|
|
358
|
+
} else {
|
|
359
|
+
closeAbove(content as HTMLElement);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
void floatable.toggle(!active);
|
|
209
363
|
}
|
|
210
364
|
|
|
211
|
-
function
|
|
365
|
+
function updatePosition(floatable: Floatable): void {
|
|
212
366
|
let position: Position =
|
|
213
|
-
(floatable.anchor
|
|
214
|
-
|
|
367
|
+
(floatable.options.anchor
|
|
368
|
+
.getAttribute(floatable.options.positionAttribute)
|
|
369
|
+
?.trim() as Position) ?? floatable.options.defaultPosition;
|
|
215
370
|
|
|
216
|
-
if (!
|
|
217
|
-
position = floatable.
|
|
371
|
+
if (!POSITIONS.has(position as Position)) {
|
|
372
|
+
position = floatable.options.defaultPosition;
|
|
218
373
|
}
|
|
219
374
|
|
|
375
|
+
let previousAnchor: DOMRect;
|
|
376
|
+
let previousContent: DOMRect;
|
|
377
|
+
|
|
378
|
+
let calculated = false;
|
|
379
|
+
let runOnAfter = false;
|
|
380
|
+
|
|
220
381
|
function run(): void {
|
|
221
|
-
|
|
222
|
-
|
|
382
|
+
const anchor = floatable.options.anchor.getBoundingClientRect();
|
|
383
|
+
const content = floatable.options.content.getBoundingClientRect();
|
|
384
|
+
|
|
385
|
+
if (calculated) {
|
|
386
|
+
if (previousAnchor != null && previousContent != null) {
|
|
387
|
+
if (
|
|
388
|
+
PROPERTIES.every(property => anchor[property] === previousAnchor[property]) &&
|
|
389
|
+
PROPERTIES.every(property => content[property] === previousContent[property])
|
|
390
|
+
) {
|
|
391
|
+
floatable.frame = requestAnimationFrame(run);
|
|
392
|
+
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
calculated = true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
update(anchor, content);
|
|
401
|
+
}
|
|
223
402
|
|
|
224
|
-
|
|
225
|
-
|
|
403
|
+
function update(anchor: DOMRect, content: DOMRect): void {
|
|
404
|
+
previousAnchor = anchor;
|
|
405
|
+
previousContent = content;
|
|
226
406
|
|
|
227
|
-
|
|
407
|
+
const left = getX(anchor, content, position);
|
|
408
|
+
const top = getY(anchor, content, position, floatable.options.preferAbove);
|
|
228
409
|
|
|
229
|
-
|
|
410
|
+
if (top + content.height > window.innerHeight) {
|
|
411
|
+
floatable.options.content.style.height = `${window.innerHeight - top}px`;
|
|
412
|
+
} else {
|
|
413
|
+
floatable.options.content.style.height = '';
|
|
230
414
|
}
|
|
231
415
|
|
|
232
|
-
|
|
233
|
-
}
|
|
416
|
+
if (left + content.width > window.innerWidth) {
|
|
417
|
+
floatable.options.content.style.width = `${window.innerWidth - left}px`;
|
|
418
|
+
} else {
|
|
419
|
+
floatable.options.content.style.width = '';
|
|
420
|
+
}
|
|
234
421
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
content.style.inset = '0 auto auto 0';
|
|
238
|
-
content.style.zIndex = '10';
|
|
239
|
-
}
|
|
422
|
+
// floatable.options.content.style.inset = `${top}px auto auto ${left}px`;
|
|
423
|
+
floatable.options.content.style.transform = `translate3d(${left}px, ${top}px, 0)`;
|
|
240
424
|
|
|
241
|
-
|
|
242
|
-
if (floatable.frame != null) {
|
|
243
|
-
cancelAnimationFrame(floatable.frame);
|
|
244
|
-
}
|
|
425
|
+
floatable.frame = requestAnimationFrame(run);
|
|
245
426
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
427
|
+
if (!runOnAfter) {
|
|
428
|
+
runOnAfter = true;
|
|
249
429
|
|
|
250
|
-
|
|
251
|
-
|
|
430
|
+
floatable.options.onAfter?.(true);
|
|
431
|
+
}
|
|
252
432
|
}
|
|
253
433
|
|
|
254
|
-
floatable.
|
|
255
|
-
floatable.deactivate = undefined;
|
|
256
|
-
floatable.frame = undefined;
|
|
434
|
+
floatable.frame = requestAnimationFrame(run);
|
|
257
435
|
}
|
|
258
436
|
|
|
259
437
|
//
|
|
260
438
|
|
|
261
|
-
const
|
|
262
|
-
|
|
439
|
+
const ATTRIBUTE_ANCHOR = 'oui-floatable-anchor';
|
|
440
|
+
|
|
441
|
+
const ATTRIBUTE_CONTENT = 'oui-floatable-content';
|
|
442
|
+
|
|
443
|
+
const INSERT_AFTEREND = 'afterend';
|
|
444
|
+
|
|
445
|
+
const POSITION_ABOVE = 'above';
|
|
263
446
|
|
|
264
|
-
const
|
|
447
|
+
const POSITION_BELOW = 'below';
|
|
448
|
+
|
|
449
|
+
const POSITION_BOTTOM = 'bottom';
|
|
450
|
+
|
|
451
|
+
const POSITION_END = 'end';
|
|
452
|
+
|
|
453
|
+
const POSITION_HORIZONTAL = 'horizontal';
|
|
454
|
+
|
|
455
|
+
const POSITION_START = 'start';
|
|
456
|
+
|
|
457
|
+
const POSITION_SUFFIX_END = '-end';
|
|
458
|
+
|
|
459
|
+
const POSITION_SUFFIX_START = '-start';
|
|
460
|
+
|
|
461
|
+
const POSITION_TOP = 'top';
|
|
462
|
+
|
|
463
|
+
const POSITION_VERTICAL = 'vertical';
|
|
464
|
+
|
|
465
|
+
const POSITIONS: Set<Position> = new Set([
|
|
265
466
|
'above',
|
|
266
467
|
'above-end',
|
|
267
468
|
'above-start',
|
|
@@ -281,3 +482,31 @@ const positions = new Set<Position>([
|
|
|
281
482
|
'vertical-end',
|
|
282
483
|
'vertical-start',
|
|
283
484
|
]);
|
|
485
|
+
|
|
486
|
+
const PROPERTIES: (keyof DOMRect)[] = ['bottom', 'left', 'right', 'top'];
|
|
487
|
+
|
|
488
|
+
const SELECTOR_ALL = `[${ATTRIBUTE_ANCHOR}], [${ATTRIBUTE_CONTENT}]`;
|
|
489
|
+
|
|
490
|
+
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
491
|
+
|
|
492
|
+
//
|
|
493
|
+
|
|
494
|
+
if ((window as unknown as FloatableWindow)._oscarpalmer_oui_floatable == null) {
|
|
495
|
+
Object.defineProperty(window, '_oscarpalmer_oui_floatable', {
|
|
496
|
+
enumerable: false,
|
|
497
|
+
value: {
|
|
498
|
+
active: new Set(),
|
|
499
|
+
mapped: new Map(),
|
|
500
|
+
order: [],
|
|
501
|
+
},
|
|
502
|
+
writable: false,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
on(document, 'keydown', event => {
|
|
506
|
+
onKeyDown(event);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
on(document, 'click', event => {
|
|
510
|
+
onClick(event);
|
|
511
|
+
});
|
|
512
|
+
}
|