@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.
Files changed (37) hide show
  1. package/dist/css/popover.css +9 -9
  2. package/dist/css/tooltip.css +20 -20
  3. package/dist/js/focus-trap/embedded.mjs +1 -1
  4. package/dist/js/focus-trap/standalone.mjs +1 -1
  5. package/dist/js/index.d.mts +47 -4
  6. package/dist/js/index.mjs +2537 -5
  7. package/dist/js/{attributable.d.mts → internal/attributable.d.mts} +1 -1
  8. package/dist/js/{attributable.mjs → internal/attributable.mjs} +1 -1
  9. package/dist/js/internal/draggable.d.mts +2 -0
  10. package/dist/js/internal/draggable.mjs +215 -0
  11. package/dist/js/{floatable.d.mts → internal/floatable.d.mts} +3 -3
  12. package/dist/js/internal/floatable.mjs +187 -0
  13. package/dist/js/movable-BKFgoaOl.d.mts +101 -0
  14. package/dist/js/movable.d.mts +2 -0
  15. package/dist/js/movable.mjs +152 -0
  16. package/dist/js/popover.mjs +2 -2
  17. package/dist/js/sortable.d.mts +2 -0
  18. package/dist/js/sortable.mjs +243 -0
  19. package/dist/js/splitter.mjs +4 -4
  20. package/dist/js/tooltip.d.mts +2 -2
  21. package/dist/js/tooltip.mjs +98 -100
  22. package/package.json +28 -18
  23. package/src/css/mixins.scss +8 -12
  24. package/src/css/popover.scss +1 -1
  25. package/src/css/tooltip.scss +22 -22
  26. package/src/js/focus-trap/standalone.ts +1 -1
  27. package/src/js/index.ts +3 -1
  28. package/src/js/internal/draggable.ts +515 -0
  29. package/src/js/internal/floatable.ts +373 -0
  30. package/src/js/movable.ts +260 -0
  31. package/src/js/popover.ts +2 -2
  32. package/src/js/sortable.ts +467 -0
  33. package/src/js/splitter.ts +1 -4
  34. package/src/js/tooltip.ts +132 -132
  35. package/dist/js/floatable.mjs +0 -284
  36. package/src/js/floatable.ts +0 -590
  37. /package/src/js/{attributable.ts → internal/attributable.ts} +0 -0
@@ -1,590 +0,0 @@
1
- import {on} from '@oscarpalmer/toretto/event';
2
- import {findAncestor} from '@oscarpalmer/toretto/find';
3
-
4
- export class Floatable {
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
- }
41
-
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
- }
62
-
63
- this.ignoreFocus = !active && (ignoreFocus ?? false);
64
-
65
- stop(this);
66
-
67
- if (active) {
68
- state.active.add(this);
69
- } else {
70
- state.active.delete(this);
71
- }
72
-
73
- if (this.options.interactive) {
74
- if (active) {
75
- state.order.push(this);
76
- } else {
77
- const index = state.order.indexOf(this);
78
-
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);
89
- }
90
- }
91
- }
92
-
93
- type FloatableWindow = Window & {
94
- _oscarpalmer_oui_floatable: State;
95
- };
96
-
97
- type Options = {
98
- anchor: HTMLElement;
99
- content: HTMLElement;
100
- defaultPosition: Position;
101
- interactive: boolean;
102
- positionAttribute: string;
103
- preferAbove: boolean;
104
- onAfter?: (active: boolean) => void;
105
- };
106
-
107
- type Position =
108
- | 'above'
109
- | 'above-end'
110
- | 'above-start'
111
- | 'below'
112
- | 'below-end'
113
- | 'below-start'
114
- | 'end'
115
- | 'end-bottom'
116
- | 'end-top'
117
- | 'horizontal'
118
- | 'horizontal-bottom'
119
- | 'horizontal-top'
120
- | 'start'
121
- | 'start-bottom'
122
- | 'start-top'
123
- | 'vertical'
124
- | 'vertical-end'
125
- | 'vertical-start';
126
-
127
- type State = {
128
- active: Set<Floatable>;
129
- mapped: Map<HTMLElement, Floatable>;
130
- order: Floatable[];
131
- };
132
-
133
- //
134
-
135
- function activate(floatable: Floatable): void {
136
- floatable.active = true;
137
-
138
- document.body.append(floatable.options.content);
139
-
140
- floatable.options.content.hidden = false;
141
-
142
- updatePosition(floatable);
143
- }
144
-
145
- function closeAbove(element: HTMLElement): void {
146
- const state = getState();
147
-
148
- if (state == null) {
149
- return;
150
- }
151
-
152
- const index = state.order.findIndex(
153
- floatable => floatable.options.anchor === element || floatable.options.content === element,
154
- );
155
-
156
- if (index === -1 || index === state.order.length - 1) {
157
- return;
158
- }
159
-
160
- while (index < state.order.length - 1) {
161
- void state.order.pop()?.toggle(false, true);
162
- }
163
- }
164
-
165
- function closeAll(): void {
166
- const state = getState();
167
-
168
- const floatables = [...(state?.active ?? [])];
169
-
170
- state?.active.clear();
171
-
172
- for (const floatable of floatables) {
173
- void floatable.toggle(false, true);
174
- }
175
- }
176
-
177
- function closeNonInteractive(state: State): void {
178
- for (const floatable of state.active) {
179
- if (!floatable.options.interactive) {
180
- void floatable.toggle(false);
181
- }
182
- }
183
- }
184
-
185
- export function deactivate(floatable: Floatable): void {
186
- floatable.active = false;
187
-
188
- floatable.options.content.hidden = true;
189
-
190
- floatable.options.anchor.insertAdjacentElement(INSERT_AFTEREND, floatable.options.content);
191
-
192
- floatable.options.onAfter?.(false);
193
- }
194
-
195
- function getAttribute(
196
- position: Position,
197
- anchor: DOMRect,
198
- top: number,
199
- left: number,
200
- ): Position | undefined {
201
- switch (position) {
202
- case 'horizontal':
203
- case 'horizontal-bottom':
204
- case 'horizontal-top':
205
- return position.replace('horizontal', left >= anchor.right ? 'right' : 'left') as Position;
206
-
207
- case 'vertical':
208
- case 'vertical-end':
209
- case 'vertical-start':
210
- return position.replace('vertical', top >= anchor.bottom ? 'bottom' : 'top') as Position;
211
-
212
- default:
213
- break;
214
- }
215
- }
216
-
217
- function getMargin(): number {
218
- try {
219
- const fontsize = parseFloat(getComputedStyle(document.documentElement).fontSize);
220
-
221
- if (Number.isNaN(fontsize)) {
222
- return 5;
223
- }
224
-
225
- const margin = fontsize / 2;
226
-
227
- return margin < 5 ? 5 : margin;
228
- } catch {
229
- return 5;
230
- }
231
- }
232
-
233
- function getState(): State {
234
- return (window as unknown as FloatableWindow)._oscarpalmer_oui_floatable;
235
- }
236
-
237
- function getX(anchor: DOMRect, content: DOMRect, position: Position, margin: number): number {
238
- if (position === 'above' || position === 'below' || position === 'vertical') {
239
- const center = anchor.left + anchor.width / 2;
240
- const width = content.width / 2;
241
-
242
- if (center + width + margin >= document.body.clientWidth) {
243
- return document.body.clientWidth - content.width - margin / 2;
244
- }
245
-
246
- if (center - width - margin <= 0) {
247
- return margin / 2;
248
- }
249
-
250
- return center - width;
251
- }
252
-
253
- if (position.startsWith('end')) {
254
- return anchor.right;
255
- }
256
-
257
- if (position.startsWith('start')) {
258
- return anchor.left - content.width;
259
- }
260
-
261
- if (position.endsWith('end')) {
262
- return anchor.right - content.width - margin <= 0 ? margin / 2 : anchor.right - content.width;
263
- }
264
-
265
- if (position.endsWith('start')) {
266
- if (anchor.left + content.width + margin >= document.body.clientWidth) {
267
- return document.body.clientWidth - content.width - margin / 2;
268
- }
269
-
270
- return anchor.left - margin <= 0 ? margin / 2 : anchor.left;
271
- }
272
-
273
- if (!position.startsWith('horizontal')) {
274
- return 0;
275
- }
276
-
277
- if (anchor.right + content.width + margin >= document.body.clientWidth) {
278
- return anchor.left - content.width;
279
- }
280
-
281
- return anchor.right;
282
- }
283
-
284
- function getY(
285
- anchor: DOMRect,
286
- content: DOMRect,
287
- position: Position,
288
- margin: number,
289
- preferAbove: boolean,
290
- ): number {
291
- if (position === 'end' || position === 'start' || position === 'horizontal') {
292
- const center = anchor.top + anchor.height / 2;
293
- const height = content.height / 2;
294
-
295
- if (center + height + margin >= document.body.clientHeight) {
296
- return document.body.clientHeight - content.height - margin / 2;
297
- }
298
-
299
- if (center - height - margin <= 0) {
300
- return margin / 2;
301
- }
302
-
303
- return center - height;
304
- }
305
-
306
- if (position.startsWith('above')) {
307
- return anchor.top - content.height;
308
- }
309
-
310
- if (position.startsWith('below')) {
311
- return anchor.bottom;
312
- }
313
-
314
- if (position.endsWith('bottom')) {
315
- return anchor.bottom - content.height - margin <= 0
316
- ? margin / 2
317
- : anchor.bottom - content.height;
318
- }
319
-
320
- if (position.endsWith('top')) {
321
- return anchor.top + content.height + margin >= document.body.clientHeight
322
- ? document.body.clientHeight - content.height - margin
323
- : anchor.top;
324
- }
325
-
326
- if (!position.startsWith('vertical')) {
327
- return 0;
328
- }
329
-
330
- if (preferAbove) {
331
- if (
332
- anchor.top - content.height - margin <= 0 &&
333
- anchor.bottom + content.height + margin <= document.body.clientHeight
334
- ) {
335
- return anchor.bottom;
336
- }
337
-
338
- return anchor.top - content.height;
339
- }
340
-
341
- if (anchor.bottom + content.height + margin >= document.body.clientHeight) {
342
- return anchor.top - content.height;
343
- }
344
-
345
- return anchor.bottom;
346
- }
347
-
348
- function initialize(floatable: Floatable): void {
349
- const {anchor, content, interactive} = floatable.options;
350
-
351
- if (interactive) {
352
- anchor.setAttribute(ATTRIBUTE_ANCHOR, '');
353
- content.setAttribute(ATTRIBUTE_CONTENT, '');
354
- }
355
-
356
- content.style.position = 'fixed';
357
- content.style.inset = '0 auto auto 0';
358
- content.style.zIndex = '10';
359
-
360
- const state = getState();
361
-
362
- state?.mapped.set(anchor, floatable);
363
- state?.mapped.set(content, floatable);
364
- }
365
-
366
- function isDisabled(element: HTMLElement): boolean {
367
- return (
368
- (element as HTMLInputElement).disabled ||
369
- (element as HTMLInputElement).readOnly ||
370
- element.getAttribute('aria-disabled') === 'true'
371
- );
372
- }
373
-
374
- function onClick(event: PointerEvent): void {
375
- const related = findAncestor(event, SELECTOR_ALL);
376
-
377
- if (!(related instanceof HTMLElement)) {
378
- closeAll();
379
- } else if (related.hasAttribute(ATTRIBUTE_CONTENT)) {
380
- closeAbove(related);
381
- } else if (!isDisabled(related)) {
382
- toggle(related);
383
- }
384
- }
385
-
386
- function onKeyDown(event: KeyboardEvent): void {
387
- if (event.key !== 'Escape') {
388
- return;
389
- }
390
-
391
- const state = getState();
392
-
393
- if (state == null || state.active.size === 0) {
394
- return;
395
- }
396
-
397
- closeNonInteractive(state);
398
-
399
- const related = findAncestor(event, SELECTOR_CONTENT);
400
-
401
- if (!(related instanceof HTMLElement)) {
402
- return;
403
- }
404
-
405
- closeAbove(related);
406
-
407
- const floatable = state.mapped.get(related);
408
-
409
- if (floatable != null) {
410
- void floatable.toggle(false);
411
- }
412
- }
413
-
414
- function setAttribute(
415
- position: Position,
416
- anchor: DOMRect,
417
- floater: HTMLElement,
418
- top: number,
419
- left: number,
420
- ): void {
421
- let actual: Position | undefined;
422
-
423
- if (/^(horizontal|vertical)/.test(position)) {
424
- actual = getAttribute(position, anchor, top, left);
425
- }
426
-
427
- floater.setAttribute('position', actual ?? position);
428
- }
429
-
430
- function stop(floatable: Floatable): void {
431
- if (floatable.frame != null) {
432
- cancelAnimationFrame(floatable.frame);
433
- }
434
-
435
- floatable.frame = undefined;
436
- }
437
-
438
- function toggle(anchor: HTMLElement): void {
439
- const floatable = getState()?.mapped.get(anchor);
440
-
441
- if (floatable == null) {
442
- closeAll();
443
-
444
- return;
445
- }
446
-
447
- const active = Boolean(floatable.active);
448
- const content = findAncestor(floatable.options.anchor, SELECTOR_CONTENT);
449
-
450
- if (content == null) {
451
- closeAll();
452
- } else {
453
- closeAbove(content as HTMLElement);
454
- }
455
-
456
- void floatable.toggle(!active);
457
- }
458
-
459
- function updatePosition(floatable: Floatable): void {
460
- let position: Position =
461
- (floatable.options.anchor
462
- .getAttribute(floatable.options.positionAttribute)
463
- ?.trim() as Position) ?? floatable.options.defaultPosition;
464
-
465
- if (!POSITIONS.has(position as Position)) {
466
- position = floatable.options.defaultPosition;
467
- }
468
-
469
- const margin = getMargin();
470
-
471
- let previousAnchor: DOMRect;
472
- let previousContent: DOMRect;
473
-
474
- let calculated = false;
475
- let runOnAfter = false;
476
-
477
- function run(): void {
478
- const anchor = floatable.options.anchor.getBoundingClientRect();
479
- const content = floatable.options.content.getBoundingClientRect();
480
-
481
- if (calculated) {
482
- if (previousAnchor != null && previousContent != null) {
483
- if (
484
- PROPERTIES.every(property => anchor[property] === previousAnchor[property]) &&
485
- PROPERTIES.every(property => content[property] === previousContent[property])
486
- ) {
487
- floatable.frame = requestAnimationFrame(run);
488
-
489
- return;
490
- }
491
- }
492
- } else {
493
- calculated = true;
494
- }
495
-
496
- update(anchor, content);
497
- }
498
-
499
- function update(anchor: DOMRect, content: DOMRect): void {
500
- previousAnchor = anchor;
501
- previousContent = content;
502
-
503
- const left = getX(anchor, content, position, margin);
504
- const top = getY(anchor, content, position, margin, floatable.options.preferAbove);
505
-
506
- setAttribute(position, anchor, floatable.options.content, top, left);
507
-
508
- if (top + content.height > window.innerHeight) {
509
- floatable.options.content.style.height = `${window.innerHeight - top}px`;
510
- } else {
511
- floatable.options.content.style.height = '';
512
- }
513
-
514
- if (left + content.width > window.innerWidth) {
515
- floatable.options.content.style.width = `${window.innerWidth - left}px`;
516
- } else {
517
- floatable.options.content.style.width = '';
518
- }
519
-
520
- // floatable.options.content.style.inset = `${top}px auto auto ${left}px`;
521
- floatable.options.content.style.transform = `translate3d(${left}px, ${top}px, 0)`;
522
-
523
- floatable.frame = requestAnimationFrame(run);
524
-
525
- if (!runOnAfter) {
526
- runOnAfter = true;
527
-
528
- floatable.options.onAfter?.(true);
529
- }
530
- }
531
-
532
- floatable.frame = requestAnimationFrame(run);
533
- }
534
-
535
- //
536
-
537
- const ATTRIBUTE_ANCHOR = 'oui-floatable-anchor';
538
-
539
- const ATTRIBUTE_CONTENT = 'oui-floatable-content';
540
-
541
- const INSERT_AFTEREND = 'afterend';
542
-
543
- const POSITIONS: Set<Position> = new Set([
544
- 'above',
545
- 'above-end',
546
- 'above-start',
547
- 'below',
548
- 'below-end',
549
- 'below-start',
550
- 'end',
551
- 'end-bottom',
552
- 'end-top',
553
- 'horizontal',
554
- 'horizontal-bottom',
555
- 'horizontal-top',
556
- 'start',
557
- 'start-bottom',
558
- 'start-top',
559
- 'vertical',
560
- 'vertical-end',
561
- 'vertical-start',
562
- ]);
563
-
564
- const PROPERTIES: (keyof DOMRect)[] = ['bottom', 'left', 'right', 'top'];
565
-
566
- const SELECTOR_ALL = `[${ATTRIBUTE_ANCHOR}], [${ATTRIBUTE_CONTENT}]`;
567
-
568
- const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
569
-
570
- //
571
-
572
- if ((window as unknown as FloatableWindow)._oscarpalmer_oui_floatable == null) {
573
- Object.defineProperty(window, '_oscarpalmer_oui_floatable', {
574
- enumerable: false,
575
- value: {
576
- active: new Set(),
577
- mapped: new Map(),
578
- order: [],
579
- },
580
- writable: false,
581
- });
582
-
583
- on(document, 'keydown', event => {
584
- onKeyDown(event);
585
- });
586
-
587
- on(document, 'click', event => {
588
- onClick(event);
589
- });
590
- }