@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.
Files changed (72) hide show
  1. package/dist/css/badge.css +13 -13
  2. package/dist/css/button.css +9 -9
  3. package/dist/css/color.css +99 -99
  4. package/dist/css/content.css +10 -12
  5. package/dist/css/flex.css +39 -39
  6. package/dist/css/form.css +47 -60
  7. package/dist/css/message.css +10 -6
  8. package/dist/css/reset.css +178 -57
  9. package/dist/css/table.css +4 -4
  10. package/dist/css/tooltip.css +26 -1
  11. package/dist/js/attributable.full.js +36 -0
  12. package/dist/js/attributable.js +30 -45
  13. package/dist/js/floatable.full.js +398 -0
  14. package/dist/js/floatable.js +254 -169
  15. package/dist/js/focus-trap/embedded.full.js +408 -0
  16. package/dist/js/focus-trap/embedded.js +131 -131
  17. package/dist/js/focus-trap/standalone.full.js +447 -0
  18. package/dist/js/focus-trap/standalone.js +4 -4
  19. package/dist/js/index.js +3 -4
  20. package/dist/js/oui.full.js +1573 -0
  21. package/dist/js/popover.full.js +760 -0
  22. package/dist/js/popover.js +84 -163
  23. package/dist/js/splitter.full.js +769 -0
  24. package/dist/js/splitter.js +264 -246
  25. package/dist/js/tooltip.full.js +613 -0
  26. package/dist/js/tooltip.js +160 -151
  27. package/package.json +35 -63
  28. package/src/css/a11y.scss +1 -1
  29. package/src/css/badge.scss +23 -19
  30. package/src/css/button.scss +9 -9
  31. package/src/css/color.scss +99 -99
  32. package/src/css/content.scss +6 -9
  33. package/src/css/flex.scss +21 -20
  34. package/src/css/form.scss +47 -62
  35. package/src/css/message.scss +11 -12
  36. package/src/css/mixins.scss +16 -10
  37. package/src/css/popover.scss +2 -2
  38. package/src/css/reset.scss +181 -55
  39. package/src/css/splitter.scss +15 -13
  40. package/src/css/table.scss +11 -11
  41. package/src/css/tooltip.scss +31 -2
  42. package/src/js/attributable.ts +3 -10
  43. package/src/js/floatable.ts +370 -141
  44. package/src/js/focus-trap/embedded.ts +141 -74
  45. package/src/js/focus-trap/standalone.ts +4 -4
  46. package/src/js/index.ts +1 -0
  47. package/src/js/popover.ts +52 -122
  48. package/src/js/splitter.ts +248 -131
  49. package/src/js/tooltip.ts +143 -68
  50. package/types/floatable.d.ts +17 -10
  51. package/types/focus-trap/embedded.d.ts +18 -5
  52. package/types/index.d.ts +1 -0
  53. package/types/popover.d.ts +2 -0
  54. package/types/splitter.d.ts +13 -1
  55. package/types/tooltip.d.ts +2 -1
  56. package/dist/css/value.css +0 -1
  57. package/dist/js/attributable.cjs +0 -51
  58. package/dist/js/floatable.cjs +0 -176
  59. package/dist/js/focus-trap/embedded.cjs +0 -150
  60. package/dist/js/focus-trap/standalone.cjs +0 -8
  61. package/dist/js/index.cjs +0 -6
  62. package/dist/js/popover.cjs +0 -166
  63. package/dist/js/splitter.cjs +0 -272
  64. package/dist/js/tooltip.cjs +0 -166
  65. package/types/attributable.d.cts +0 -5
  66. package/types/floatable.d.cts +0 -20
  67. package/types/focus-trap/embedded.d.cts +0 -15
  68. package/types/focus-trap/standalone.d.cts +0 -3
  69. package/types/index.d.cts +0 -14
  70. package/types/popover.d.cts +0 -14
  71. package/types/splitter.d.cts +0 -10
  72. package/types/tooltip.d.cts +0 -3
@@ -1,6 +1,27 @@
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)) {
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.shadowOverlay;
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
  }
@@ -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
- nodes: Node[] | NodeList,
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
 
@@ -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
- activate: number | undefined;
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
- active = false;
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
- deactivate: number | undefined;
63
+ this.ignoreFocus = !active && (ignoreFocus ?? false);
7
64
 
8
- frame: DOMHighResTimeStamp | undefined;
65
+ stop(this);
9
66
 
10
- constructor(
11
- public anchor: HTMLElement,
12
- public content: HTMLElement,
13
- readonly attribute: string,
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
- destroy(): void {
21
- this.activate = undefined;
22
- this.deactivate = undefined;
23
- this.frame = undefined;
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
- this.anchor = undefined as never;
26
- this.content = undefined as never;
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
- export function activate(
53
- floatable: Floatable,
54
- active: Set<Floatable>,
55
- order?: Floatable[],
56
- time?: number,
57
- ): void {
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
- if (floatable.active) {
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
- if (time == null || time === 0) {
65
- onActivate(floatable, active, order);
66
- } else {
67
- floatable.activate = +setTimeout(() => {
68
- onActivate(floatable, active, order);
69
- }, time);
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
- export function deactivate(
74
- floatable: Floatable,
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
- if (!floatable.active) {
82
- return;
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
- if (time == null || time === 0) {
86
- onDeactivate(floatable, active, order);
87
- } else {
88
- floatable.deactivate = +setTimeout(() => {
89
- onDeactivate(floatable, active, order);
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('-end')) {
201
+ if (position.endsWith(POSITION_SUFFIX_END)) {
96
202
  const end = anchor.right - content.width;
97
203
 
98
- return end - margin < 0 ? margin : end;
204
+ return end < 0 ? 0 : end;
99
205
  }
100
206
 
101
- if (position.endsWith('-start')) {
207
+ if (position.endsWith(POSITION_SUFFIX_START)) {
102
208
  const start = anchor.left;
103
209
 
104
- return start + content.width + margin > window.innerWidth
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('end')) {
110
- return anchor.right + margin;
213
+ if (position.startsWith(POSITION_END)) {
214
+ return anchor.right;
111
215
  }
112
216
 
113
- if (position.startsWith('start')) {
114
- return anchor.left - content.width - margin;
217
+ if (position.startsWith(POSITION_START)) {
218
+ return anchor.left - content.width;
115
219
  }
116
220
 
117
- if (position.startsWith('horizontal')) {
118
- if (anchor.right + content.width + margin <= window.innerWidth) {
119
- return anchor.right + margin;
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 - margin;
226
+ return anchor.left - content.width;
123
227
  }
124
228
 
125
- return anchor.left + (anchor.width - content.width) / 2;
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
- anchor: DOMRect,
130
- content: DOMRect,
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('below')) {
139
- return anchor.bottom + margin;
239
+ if (position.startsWith(POSITION_BELOW)) {
240
+ return anchor.bottom;
140
241
  }
141
242
 
142
- if (position.endsWith('bottom')) {
243
+ if (position.endsWith(POSITION_BOTTOM)) {
143
244
  const bottom = anchor.bottom - content.height;
144
245
 
145
- return bottom + margin < 0 ? margin : bottom;
246
+ return bottom < 0 ? 0 : bottom;
146
247
  }
147
248
 
148
- if (position.endsWith('top')) {
249
+ if (position.endsWith(POSITION_TOP)) {
149
250
  const top = anchor.top;
150
251
 
151
- return top + content.height + margin > window.innerHeight
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('vertical')) {
157
- if (preferAbove && anchor.top - content.height - margin >= 0) {
158
- return anchor.top - content.height - margin;
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 + margin <= document.body.clientHeight) {
162
- return anchor.bottom + margin;
260
+ if (anchor.bottom + content.height <= document.body.clientHeight) {
261
+ return anchor.bottom;
163
262
  }
164
263
 
165
- return anchor.top - content.height - margin;
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 onActivate(
172
- floatable: Floatable,
173
- active: Set<Floatable>,
174
- order?: Floatable[],
175
- ): void {
176
- floatable.active = true;
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
- active.add(floatable);
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 (order != null) {
181
- order.push(floatable);
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
- document.body.append(floatable.content);
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
- floatable.content.hidden = false;
319
+ closeNonInteractive(state);
187
320
 
188
- update(floatable);
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 onDeactivate(
192
- floatable: Floatable,
193
- active: Set<Floatable>,
194
- order?: Floatable[],
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
- active.delete(floatable);
341
+ floatable.frame = undefined;
342
+ }
343
+
344
+ function toggle(anchor: HTMLElement): void {
345
+ const floatable = getState()?.mapped.get(anchor);
199
346
 
200
- const index = order?.indexOf(floatable) ?? -1;
347
+ if (floatable == null) {
348
+ closeAll();
201
349
 
202
- if (index > -1) {
203
- order?.splice(index, 1);
350
+ return;
204
351
  }
205
352
 
206
- floatable.content.hidden = true;
353
+ const active = Boolean(floatable.active);
354
+ const content = findAncestor(floatable.options.anchor, SELECTOR_CONTENT);
207
355
 
208
- floatable.anchor.insertAdjacentElement('afterend', floatable.content);
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 update(floatable: Floatable): void {
365
+ function updatePosition(floatable: Floatable): void {
212
366
  let position: Position =
213
- (floatable.anchor.getAttribute(floatable.attribute)?.trim() as Position) ??
214
- floatable.position;
367
+ (floatable.options.anchor
368
+ .getAttribute(floatable.options.positionAttribute)
369
+ ?.trim() as Position) ?? floatable.options.defaultPosition;
215
370
 
216
- if (!positions.has(position as Position)) {
217
- position = floatable.position;
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
- const anchor = floatable.anchor.getBoundingClientRect();
222
- const content = floatable.content.getBoundingClientRect();
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
- const left = getX(anchor, content, position);
225
- const top = getY(anchor, content, position, floatable.preferAbove);
403
+ function update(anchor: DOMRect, content: DOMRect): void {
404
+ previousAnchor = anchor;
405
+ previousContent = content;
226
406
 
227
- floatable.content.style.inset = `${top}px auto auto ${left}px`;
407
+ const left = getX(anchor, content, position);
408
+ const top = getY(anchor, content, position, floatable.options.preferAbove);
228
409
 
229
- floatable.frame = requestAnimationFrame(run);
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
- floatable.frame = requestAnimationFrame(run);
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
- function setStyles(content: HTMLElement): void {
236
- content.style.position = 'fixed';
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
- function stop(floatable: Floatable): void {
242
- if (floatable.frame != null) {
243
- cancelAnimationFrame(floatable.frame);
244
- }
425
+ floatable.frame = requestAnimationFrame(run);
245
426
 
246
- if (floatable.activate != null) {
247
- clearTimeout(floatable.activate);
248
- }
427
+ if (!runOnAfter) {
428
+ runOnAfter = true;
249
429
 
250
- if (floatable.deactivate != null) {
251
- clearTimeout(floatable.deactivate);
430
+ floatable.options.onAfter?.(true);
431
+ }
252
432
  }
253
433
 
254
- floatable.activate = undefined;
255
- floatable.deactivate = undefined;
256
- floatable.frame = undefined;
434
+ floatable.frame = requestAnimationFrame(run);
257
435
  }
258
436
 
259
437
  //
260
438
 
261
- const margin =
262
- Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 2;
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 positions = new Set<Position>([
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
+ }