@adia-ai/web-components 0.8.19 → 0.8.21

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 (128) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/components/card/card.a2ui.json +6 -3
  3. package/components/card/card.css +15 -1
  4. package/components/card/card.yaml +5 -3
  5. package/components/nav-group/nav-group.a2ui.json +5 -0
  6. package/components/nav-group/nav-group.class.js +14 -6
  7. package/components/nav-group/nav-group.css +5 -2
  8. package/components/nav-group/nav-group.d.ts +2 -0
  9. package/components/nav-group/nav-group.yaml +4 -0
  10. package/components/select/select.a2ui.json +3 -2
  11. package/components/select/select.class.js +14 -1
  12. package/components/select/select.css +13 -1
  13. package/components/select/select.yaml +5 -4
  14. package/components/table/table.class.js +7 -3
  15. package/components/table/table.css +7 -5
  16. package/dist/theme-provider.min.js +1 -1
  17. package/dist/web-components.min.css +1 -1
  18. package/dist/web-components.min.js +19 -19
  19. package/dist/web-components.sheet.js +1 -1
  20. package/package.json +2 -1
  21. package/components/adia-mark/adia-mark.test.js +0 -69
  22. package/components/adia-wordmark/adia-wordmark.test.js +0 -69
  23. package/components/agent-artifact/agent-artifact.test.js +0 -61
  24. package/components/alert/alert.test.js +0 -364
  25. package/components/color-picker/color-picker.test.js +0 -96
  26. package/components/combobox/combobox.test.js +0 -256
  27. package/components/command/command.test.js +0 -99
  28. package/components/date-range-picker/date-range-picker.test.js +0 -387
  29. package/components/datetime-picker/datetime-picker.test.js +0 -454
  30. package/components/demo-toggle/demo-toggle.test.js +0 -102
  31. package/components/description-list/description-list.test.js +0 -72
  32. package/components/display-field/display-field.test.js +0 -115
  33. package/components/empty-state/empty-state.test.js +0 -77
  34. package/components/field/field.test.js +0 -260
  35. package/components/fields/fields.test.js +0 -88
  36. package/components/inline-message/inline-message.test.js +0 -203
  37. package/components/input/input.test.js +0 -335
  38. package/components/integration-card/integration-card.test.js +0 -318
  39. package/components/link/link.test.js +0 -143
  40. package/components/list/list.test.js +0 -106
  41. package/components/list-window/list-window.test.js +0 -303
  42. package/components/loading-overlay/loading-overlay.test.js +0 -257
  43. package/components/menu/menu.test.js +0 -176
  44. package/components/nav/nav.test.js +0 -428
  45. package/components/nav-group/nav-group.test.js +0 -67
  46. package/components/option-card/option-card.test.js +0 -32
  47. package/components/pagination/pagination.test.js +0 -195
  48. package/components/select/select.test.js +0 -481
  49. package/components/slider/slider.test.js +0 -202
  50. package/components/spinner/spinner.test.js +0 -272
  51. package/components/stat/stat.test.js +0 -108
  52. package/components/step-progress/step-progress.test.js +0 -129
  53. package/components/swatch/swatch.test.js +0 -563
  54. package/components/table/table.test.js +0 -479
  55. package/components/table-toolbar/table-toolbar.test.js +0 -152
  56. package/components/tag/tag.test.js +0 -137
  57. package/components/tags-input/tags-input.test.js +0 -477
  58. package/components/text/text.test.js +0 -202
  59. package/components/theme-provider/theme-provider.test.js +0 -94
  60. package/components/time-picker/time-picker.test.js +0 -287
  61. package/components/timeline/timeline.test.js +0 -31
  62. package/components/toggle-scheme/toggle-scheme.test.js +0 -110
  63. package/components/tree/tree.test.js +0 -312
  64. package/core/element.test.js +0 -389
  65. package/core/icons.test.js +0 -187
  66. package/core/logical-children.test.js +0 -54
  67. package/core/markdown.test.js +0 -47
  68. package/core/responsive.test.js +0 -121
  69. package/core/template.test.js +0 -611
  70. package/traits/active-state/active-state.test.js +0 -28
  71. package/traits/anchor-positioning/anchor-positioning.test.js +0 -122
  72. package/traits/announcer/announcer.test.js +0 -268
  73. package/traits/arrow-grid-nav/arrow-grid-nav.test.js +0 -375
  74. package/traits/attention-pulse/attention-pulse.test.js +0 -26
  75. package/traits/confetti/confetti.test.js +0 -46
  76. package/traits/confetti-burst/confetti-burst.test.js +0 -46
  77. package/traits/count-up/count-up.test.js +0 -54
  78. package/traits/declarative.test.js +0 -138
  79. package/traits/dirty-state/dirty-state.test.js +0 -45
  80. package/traits/drag-ghost/drag-ghost.test.js +0 -19
  81. package/traits/draggable/draggable.test.js +0 -60
  82. package/traits/draggable-list-item/draggable-list-item.test.js +0 -51
  83. package/traits/drop-target/drop-target.test.js +0 -241
  84. package/traits/droppable/droppable.test.js +0 -54
  85. package/traits/droppable-collection/droppable-collection.test.js +0 -99
  86. package/traits/error-shake/error-shake.test.js +0 -114
  87. package/traits/fade-presence/fade-presence.test.js +0 -20
  88. package/traits/focus-restore/focus-restore.test.js +0 -202
  89. package/traits/focus-trap/focus-trap.test.js +0 -42
  90. package/traits/focusable/focusable.test.js +0 -53
  91. package/traits/glow-focus/glow-focus.test.js +0 -31
  92. package/traits/gradient-shift/gradient-shift.test.js +0 -22
  93. package/traits/haptic-feedback/haptic-feedback.test.js +0 -52
  94. package/traits/hotkey/hotkey.test.js +0 -61
  95. package/traits/hoverable/hoverable.test.js +0 -24
  96. package/traits/inertia-drag/inertia-drag.test.js +0 -33
  97. package/traits/input-mask/input-mask.test.js +0 -151
  98. package/traits/intersection-observer/intersection-observer.test.js +0 -38
  99. package/traits/keyboard-nav/keyboard-nav.test.js +0 -41
  100. package/traits/keyboard-reorderable/keyboard-reorderable.test.js +0 -45
  101. package/traits/layout-animation/layout-animation.test.js +0 -114
  102. package/traits/long-press/long-press.test.js +0 -244
  103. package/traits/magnetic-hover/magnetic-hover.test.js +0 -30
  104. package/traits/noise-texture/noise-texture.test.js +0 -20
  105. package/traits/parallax/parallax.test.js +0 -26
  106. package/traits/portal/portal.test.js +0 -30
  107. package/traits/pressable/pressable.test.js +0 -73
  108. package/traits/resettable/resettable.test.js +0 -100
  109. package/traits/resizable/resizable.test.js +0 -39
  110. package/traits/resize-observer/resize-observer.test.js +0 -38
  111. package/traits/ripple/ripple.test.js +0 -32
  112. package/traits/roving-tabindex/roving-tabindex.test.js +0 -28
  113. package/traits/scale-press/scale-press.test.js +0 -39
  114. package/traits/scroll-lock/scroll-lock.test.js +0 -45
  115. package/traits/scroll-progress/scroll-progress.test.js +0 -182
  116. package/traits/shimmer-loading/shimmer-loading.test.js +0 -43
  117. package/traits/smoke.test.js +0 -111
  118. package/traits/snap-to-grid/snap-to-grid.test.js +0 -40
  119. package/traits/sound-feedback/sound-feedback.test.js +0 -26
  120. package/traits/spring-animate/spring-animate.test.js +0 -28
  121. package/traits/success-checkmark/success-checkmark.test.js +0 -120
  122. package/traits/tilt-hover/tilt-hover.test.js +0 -32
  123. package/traits/tossable/tossable.test.js +0 -31
  124. package/traits/traits-host.test.js +0 -73
  125. package/traits/typeahead/typeahead.test.js +0 -38
  126. package/traits/typewriter/typewriter.test.js +0 -47
  127. package/traits/validation/validation.test.js +0 -93
  128. package/traits/view-transition/view-transition.test.js +0 -268
@@ -1,20 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { noiseTexture } from './noise-texture.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('noise-texture', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('connect sets data-noise-texture-active and appends an overlay', () => {
9
- const host = mountHost();
10
- connectTrait(noiseTexture, host);
11
- expect(host.hasAttribute('data-noise-texture-active')).toBe(true);
12
- });
13
-
14
- it('disconnect clears active + removes overlay', () => {
15
- const host = mountHost();
16
- const inst = connectTrait(noiseTexture, host);
17
- inst.disconnect(host);
18
- expect(host.hasAttribute('data-noise-texture-active')).toBe(false);
19
- });
20
- });
@@ -1,26 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { parallax } from './parallax.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('parallax', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('connect sets data-parallax-active', () => {
9
- const host = mountHost();
10
- connectTrait(parallax, host);
11
- expect(host.hasAttribute('data-parallax-active')).toBe(true);
12
- });
13
-
14
- it('disconnect clears active + translate', () => {
15
- const host = mountHost();
16
- const inst = connectTrait(parallax, host);
17
- inst.disconnect(host);
18
- expect(host.hasAttribute('data-parallax-active')).toBe(false);
19
- expect(host.style.translate).toBe('');
20
- });
21
-
22
- it('respects data-parallax-strength (default 0.5)', () => {
23
- const host = mountHost('div', { 'data-parallax-strength': '0.2' });
24
- expect(() => connectTrait(parallax, host)).not.toThrow();
25
- });
26
- });
@@ -1,30 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { portal } from './portal.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('portal', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('connect sets data-portal-active', () => {
9
- const host = mountHost();
10
- connectTrait(portal, host);
11
- expect(host.hasAttribute('data-portal-active')).toBe(true);
12
- });
13
-
14
- it('disconnect clears the active attribute', () => {
15
- const host = mountHost();
16
- const inst = connectTrait(portal, host);
17
- inst.disconnect(host);
18
- expect(host.hasAttribute('data-portal-active')).toBe(false);
19
- });
20
-
21
- it('with data-portal-target, host is moved to the target element', () => {
22
- const target = document.createElement('div');
23
- target.id = 'portal-out';
24
- document.body.appendChild(target);
25
- const host = mountHost('div', { 'data-portal-target': '#portal-out' });
26
- connectTrait(portal, host);
27
- // The trait moves the host into the target on connect.
28
- expect(target.contains(host)).toBe(true);
29
- });
30
- });
@@ -1,73 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { pressable } from './pressable.js';
3
- import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
4
-
5
- describe('pressable', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('sets data-pressable-pressed on pointerdown, removes on pointerup', () => {
9
- const host = mountHost();
10
- connectTrait(pressable, host);
11
- host.dispatchEvent(new PointerEvent('pointerdown'));
12
- expect(host.hasAttribute('data-pressable-pressed')).toBe(true);
13
- host.dispatchEvent(new PointerEvent('pointerup'));
14
- expect(host.hasAttribute('data-pressable-pressed')).toBe(false);
15
- });
16
-
17
- it('dispatches "press" event on pointerup', () => {
18
- const host = mountHost();
19
- connectTrait(pressable, host);
20
- const spy = spyEvent(host, 'press');
21
- host.dispatchEvent(new PointerEvent('pointerdown'));
22
- host.dispatchEvent(new PointerEvent('pointerup'));
23
- expect(spy.count).toBe(1);
24
- });
25
-
26
- it('dispatches "press" on Enter/Space keyup', () => {
27
- const host = mountHost();
28
- connectTrait(pressable, host);
29
- const spy = spyEvent(host, 'press');
30
- host.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
31
- host.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
32
- expect(spy.count).toBe(1);
33
- host.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }));
34
- host.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' }));
35
- expect(spy.count).toBe(2);
36
- });
37
-
38
- it('respects [disabled] — no event, no attribute toggle', () => {
39
- const host = mountHost('button', { disabled: '' });
40
- connectTrait(pressable, host);
41
- const spy = spyEvent(host, 'press');
42
- host.dispatchEvent(new PointerEvent('pointerdown'));
43
- expect(host.hasAttribute('data-pressable-pressed')).toBe(false);
44
- host.dispatchEvent(new PointerEvent('pointerup'));
45
- expect(spy.count).toBe(0);
46
- });
47
-
48
- it('clears pressed state on pointerleave (canceled press)', () => {
49
- const host = mountHost();
50
- connectTrait(pressable, host);
51
- const spy = spyEvent(host, 'press');
52
- host.dispatchEvent(new PointerEvent('pointerdown'));
53
- expect(host.hasAttribute('data-pressable-pressed')).toBe(true);
54
- host.dispatchEvent(new PointerEvent('pointerleave'));
55
- expect(host.hasAttribute('data-pressable-pressed')).toBe(false);
56
- host.dispatchEvent(new PointerEvent('pointerup'));
57
- expect(spy.count).toBe(0);
58
- });
59
-
60
- it('disconnect removes all listeners and clears attribute', () => {
61
- const host = mountHost();
62
- const inst = connectTrait(pressable, host);
63
- host.dispatchEvent(new PointerEvent('pointerdown'));
64
- expect(host.hasAttribute('data-pressable-pressed')).toBe(true);
65
- inst.disconnect(host);
66
- expect(host.hasAttribute('data-pressable-pressed')).toBe(false);
67
- // Re-firing events post-disconnect is a no-op
68
- const spy = spyEvent(host, 'press');
69
- host.dispatchEvent(new PointerEvent('pointerdown'));
70
- host.dispatchEvent(new PointerEvent('pointerup'));
71
- expect(spy.count).toBe(0);
72
- });
73
- });
@@ -1,100 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { resettable } from './resettable.js';
3
- import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
4
-
5
- function mountInForm(initialValue = 'initial') {
6
- const form = document.createElement('form');
7
- document.body.appendChild(form);
8
- const host = document.createElement('input');
9
- host.value = initialValue;
10
- form.appendChild(host);
11
- return { form, host };
12
- }
13
-
14
- describe('resettable', () => {
15
- beforeEach(resetDOM);
16
-
17
- it('captures initial value on connect; restores it on form reset', () => {
18
- const { form, host } = mountInForm('original');
19
- connectTrait(resettable, host);
20
- host.value = 'modified';
21
- form.dispatchEvent(new Event('reset', { bubbles: true }));
22
- expect(host.value).toBe('original');
23
- });
24
-
25
- it('dispatches reset-applied with the initial value in detail', () => {
26
- const { form, host } = mountInForm('hello');
27
- connectTrait(resettable, host);
28
- const spy = spyEvent(host, 'reset-applied');
29
- host.value = 'changed';
30
- form.dispatchEvent(new Event('reset', { bubbles: true }));
31
- expect(spy.count).toBe(1);
32
- expect(spy.last.initialValue).toBe('hello');
33
- });
34
-
35
- it('outside a form: no-op (no throw, no listener)', () => {
36
- const host = mountHost('input', { value: 'x' });
37
- expect(() => connectTrait(resettable, host)).not.toThrow();
38
- expect(host.hasAttribute('data-resettable-active')).toBe(true);
39
- });
40
-
41
- it('disconnect removes the form listener', () => {
42
- const { form, host } = mountInForm('a');
43
- const inst = resettable();
44
- inst.connect(host);
45
- inst.disconnect(host);
46
- host.value = 'b';
47
- form.dispatchEvent(new Event('reset', { bubbles: true }));
48
- // After disconnect, the trait should NOT have reset the value.
49
- expect(host.value).toBe('b');
50
- expect(host.hasAttribute('data-resettable-active')).toBe(false);
51
- });
52
-
53
- it('multiple controls in the same form each reset independently', () => {
54
- const form = document.createElement('form');
55
- document.body.appendChild(form);
56
- const a = document.createElement('input'); a.value = 'A';
57
- const b = document.createElement('input'); b.value = 'B';
58
- form.appendChild(a); form.appendChild(b);
59
- resettable().connect(a);
60
- resettable().connect(b);
61
- a.value = 'changed-a';
62
- b.value = 'changed-b';
63
- form.dispatchEvent(new Event('reset', { bubbles: true }));
64
- expect(a.value).toBe('A');
65
- expect(b.value).toBe('B');
66
- });
67
-
68
- it('boolean controls (host.checked) restore their initial checked state', () => {
69
- // Synthesize a switch-like host: the only state is a boolean `checked`
70
- // reactive property — no `value`. Mirrors switch-ui's API.
71
- const form = document.createElement('form');
72
- document.body.appendChild(form);
73
- const host = document.createElement('div');
74
- host.checked = true;
75
- form.appendChild(host);
76
-
77
- connectTrait(resettable, host);
78
- const spy = spyEvent(host, 'reset-applied');
79
-
80
- host.checked = false; // user toggles it off
81
- form.dispatchEvent(new Event('reset', { bubbles: true }));
82
-
83
- expect(host.checked).toBe(true);
84
- expect(spy.count).toBe(1);
85
- expect(spy.last.initialValue).toBe(true);
86
- });
87
-
88
- it('boolean controls starting unchecked stay unchecked after reset', () => {
89
- const form = document.createElement('form');
90
- document.body.appendChild(form);
91
- const host = document.createElement('div');
92
- host.checked = false;
93
- form.appendChild(host);
94
-
95
- connectTrait(resettable, host);
96
- host.checked = true;
97
- form.dispatchEvent(new Event('reset', { bubbles: true }));
98
- expect(host.checked).toBe(false);
99
- });
100
- });
@@ -1,39 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { resizable } from './resizable.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('resizable', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('connect attaches without throwing', () => {
9
- const host = mountHost();
10
- expect(() => connectTrait(resizable, host)).not.toThrow();
11
- });
12
-
13
- it('disconnect clears resizing state attributes', () => {
14
- const host = mountHost();
15
- const inst = connectTrait(resizable, host);
16
- expect(() => inst.disconnect(host)).not.toThrow();
17
- expect(host.hasAttribute('data-resizable-resizing')).toBe(false);
18
- expect(host.hasAttribute('data-resizable-edge')).toBe(false);
19
- });
20
-
21
- it('creates 8 hit-zones — 4 edges + 4 corners (resize in x, y, and x/y)', () => {
22
- const host = mountHost();
23
- connectTrait(resizable, host);
24
- const names = [...host.querySelectorAll('[data-resizable-handle]')]
25
- .map((el) => el.dataset.resizableHandle);
26
- expect(names).toHaveLength(8);
27
- expect(names).toEqual(expect.arrayContaining([
28
- 'top', 'right', 'bottom', 'left',
29
- 'top-left', 'top-right', 'bottom-right', 'bottom-left',
30
- ]));
31
- });
32
-
33
- it('disconnect removes all hit-zones', () => {
34
- const host = mountHost();
35
- const inst = connectTrait(resizable, host);
36
- inst.disconnect(host);
37
- expect(host.querySelectorAll('[data-resizable-handle]')).toHaveLength(0);
38
- });
39
- });
@@ -1,38 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
- import { resizeObserver } from './resize-observer.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('resize-observer', () => {
6
- let originalRO;
7
-
8
- beforeEach(() => {
9
- resetDOM();
10
- originalRO = globalThis.ResizeObserver;
11
- });
12
-
13
- afterEach(() => {
14
- if (originalRO) globalThis.ResizeObserver = originalRO;
15
- else delete globalThis.ResizeObserver;
16
- });
17
-
18
- it('without ResizeObserver: gracefully no-ops on connect', () => {
19
- delete globalThis.ResizeObserver;
20
- const host = mountHost();
21
- expect(() => connectTrait(resizeObserver, host)).not.toThrow();
22
- });
23
-
24
- it('with mocked ResizeObserver: observe() called + disconnect cleans up', () => {
25
- const observe = vi.fn();
26
- const roDisconnect = vi.fn();
27
- globalThis.ResizeObserver = function MockRO() {
28
- this.observe = observe;
29
- this.disconnect = roDisconnect;
30
- this.unobserve = vi.fn();
31
- };
32
- const host = mountHost();
33
- const inst = connectTrait(resizeObserver, host);
34
- expect(observe).toHaveBeenCalledWith(host);
35
- inst.disconnect(host);
36
- expect(roDisconnect).toHaveBeenCalled();
37
- });
38
- });
@@ -1,32 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { ripple } from './ripple.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('ripple', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('connect sets data-ripple-active', () => {
9
- const host = mountHost();
10
- connectTrait(ripple, host);
11
- expect(host.hasAttribute('data-ripple-active')).toBe(true);
12
- });
13
-
14
- it('disconnect clears active + restores overflow', () => {
15
- const host = mountHost();
16
- const inst = connectTrait(ripple, host);
17
- inst.disconnect(host);
18
- expect(host.hasAttribute('data-ripple-active')).toBe(false);
19
- expect(host.style.overflow).toBe('');
20
- });
21
-
22
- it('click appends a ripple span (in non-reduced-motion environment)', () => {
23
- const host = mountHost();
24
- connectTrait(ripple, host);
25
- const beforeChildren = host.children.length;
26
- host.dispatchEvent(new MouseEvent('click', { clientX: 10, clientY: 10, bubbles: true }));
27
- // In happy-dom + reduced-motion preference unknown, the ripple may or may
28
- // not append a span. The contract is "connect doesn't throw, no DOM leak
29
- // on disconnect".
30
- expect(host.children.length).toBeGreaterThanOrEqual(beforeChildren);
31
- });
32
- });
@@ -1,28 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { rovingTabindex } from './roving-tabindex.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- function tabbableChild(host) {
6
- const btn = document.createElement('button');
7
- host.appendChild(btn);
8
- return btn;
9
- }
10
-
11
- describe('roving-tabindex', () => {
12
- beforeEach(resetDOM);
13
-
14
- it('connect attaches without throwing', () => {
15
- const host = mountHost();
16
- tabbableChild(host);
17
- tabbableChild(host);
18
- tabbableChild(host);
19
- expect(() => connectTrait(rovingTabindex, host)).not.toThrow();
20
- });
21
-
22
- it('disconnect cleans up', () => {
23
- const host = mountHost();
24
- tabbableChild(host);
25
- const inst = connectTrait(rovingTabindex, host);
26
- expect(() => inst.disconnect(host)).not.toThrow();
27
- });
28
- });
@@ -1,39 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { scalePress } from './scale-press.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('scale-press', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('pointerdown applies scale(0.95) + active attribute', () => {
9
- const host = mountHost();
10
- connectTrait(scalePress, host);
11
- host.dispatchEvent(new PointerEvent('pointerdown'));
12
- expect(host.hasAttribute('data-scale-press-active')).toBe(true);
13
- expect(host.style.transform).toContain('0.95');
14
- });
15
-
16
- it('pointerup releases scale and clears attribute', () => {
17
- const host = mountHost();
18
- connectTrait(scalePress, host);
19
- host.dispatchEvent(new PointerEvent('pointerdown'));
20
- host.dispatchEvent(new PointerEvent('pointerup'));
21
- expect(host.hasAttribute('data-scale-press-active')).toBe(false);
22
- });
23
-
24
- it('pointerleave during press releases (canceled press)', () => {
25
- const host = mountHost();
26
- connectTrait(scalePress, host);
27
- host.dispatchEvent(new PointerEvent('pointerdown'));
28
- host.dispatchEvent(new PointerEvent('pointerleave'));
29
- expect(host.hasAttribute('data-scale-press-active')).toBe(false);
30
- });
31
-
32
- it('respects [disabled] — no transform, no attribute', () => {
33
- const host = mountHost('button', { disabled: '' });
34
- connectTrait(scalePress, host);
35
- host.dispatchEvent(new PointerEvent('pointerdown'));
36
- expect(host.hasAttribute('data-scale-press-active')).toBe(false);
37
- expect(host.style.transform).toBe('');
38
- });
39
- });
@@ -1,45 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { scrollLock } from './scroll-lock.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('scroll-lock', () => {
6
- beforeEach(() => {
7
- resetDOM();
8
- document.body.style.overflow = '';
9
- });
10
-
11
- it('connect sets body overflow:hidden + active attribute', () => {
12
- const host = mountHost();
13
- const inst = connectTrait(scrollLock, host);
14
- expect(document.body.style.overflow).toBe('hidden');
15
- expect(host.hasAttribute('data-scroll-lock-active')).toBe(true);
16
- // Balanced disconnect to keep the module-level lockCount honest for sibling tests.
17
- inst.disconnect(host);
18
- });
19
-
20
- it('disconnect restores body overflow + clears attribute', () => {
21
- document.body.style.overflow = 'auto';
22
- const host = mountHost();
23
- const inst = connectTrait(scrollLock, host);
24
- inst.disconnect(host);
25
- expect(document.body.style.overflow).toBe('auto');
26
- expect(host.hasAttribute('data-scroll-lock-active')).toBe(false);
27
- });
28
-
29
- it('refcounted — disconnect order does not unlock until last consumer leaves', () => {
30
- // The trait is module-scoped (lockCount + savedOverflow live in module state).
31
- // Snapshot the current state so this assertion is independent of test order.
32
- const startOverflow = document.body.style.overflow;
33
- const a = mountHost();
34
- const b = mountHost();
35
- const instA = connectTrait(scrollLock, a);
36
- const instB = connectTrait(scrollLock, b);
37
- expect(document.body.style.overflow).toBe('hidden');
38
- instA.disconnect(a);
39
- // Still locked because b is still active
40
- expect(document.body.style.overflow).toBe('hidden');
41
- instB.disconnect(b);
42
- // Restored to whatever it was when the *first* lock acquired it
43
- expect(document.body.style.overflow).toBe(startOverflow);
44
- });
45
- });
@@ -1,182 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
- import { scrollProgress } from './scroll-progress.js';
3
- import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
4
-
5
- describe('scroll-progress', () => {
6
- let originalRAF;
7
- let originalCAF;
8
-
9
- beforeEach(() => {
10
- resetDOM();
11
- // Synchronous rAF — the trait's scheduler calls back immediately so
12
- // we can assert post-update side-effects without awaiting frames.
13
- originalRAF = globalThis.requestAnimationFrame;
14
- originalCAF = globalThis.cancelAnimationFrame;
15
- globalThis.requestAnimationFrame = (fn) => { fn(performance.now?.() ?? 0); return 1; };
16
- globalThis.cancelAnimationFrame = vi.fn();
17
- });
18
-
19
- afterEach(() => {
20
- globalThis.requestAnimationFrame = originalRAF;
21
- globalThis.cancelAnimationFrame = originalCAF;
22
- });
23
-
24
- it('connect sets data-scroll-progress-active', () => {
25
- const host = mountHost();
26
- connectTrait(scrollProgress, host);
27
- expect(host.hasAttribute('data-scroll-progress-active')).toBe(true);
28
- });
29
-
30
- it('disconnect clears active + progress + CSS variable', () => {
31
- const host = mountHost();
32
- const inst = connectTrait(scrollProgress, host);
33
- inst.disconnect(host);
34
- expect(host.hasAttribute('data-scroll-progress-active')).toBe(false);
35
- expect(host.hasAttribute('data-scroll-progress')).toBe(false);
36
- expect(host.style.getPropertyValue('--scroll-progress')).toBe('');
37
- });
38
-
39
- it('writes data-scroll-progress as a 0..1 string after connect', () => {
40
- const host = mountHost();
41
- connectTrait(scrollProgress, host);
42
- // Synchronous rAF means the seed update has already run.
43
- const v = host.getAttribute('data-scroll-progress');
44
- expect(v).not.toBeNull();
45
- const n = parseFloat(v);
46
- expect(n).toBeGreaterThanOrEqual(0);
47
- expect(n).toBeLessThanOrEqual(1);
48
- });
49
-
50
- it('writes the --scroll-progress CSS custom property', () => {
51
- const host = mountHost();
52
- connectTrait(scrollProgress, host);
53
- const css = host.style.getPropertyValue('--scroll-progress');
54
- expect(css).not.toBe('');
55
- const n = parseFloat(css);
56
- expect(n).toBeGreaterThanOrEqual(0);
57
- expect(n).toBeLessThanOrEqual(1);
58
- });
59
-
60
- it('dispatches a scroll-progress event with { progress } detail', () => {
61
- const host = mountHost();
62
- const spy = spyEvent(host, 'scroll-progress');
63
- connectTrait(scrollProgress, host);
64
- // Initial seed fires once via synchronous rAF.
65
- expect(spy.count).toBeGreaterThanOrEqual(1);
66
- expect(typeof spy.last.progress).toBe('number');
67
- expect(spy.last.progress).toBeGreaterThanOrEqual(0);
68
- expect(spy.last.progress).toBeLessThanOrEqual(1);
69
- });
70
-
71
- it('mode="page" falls back to 0 when scroll range is zero', () => {
72
- const host = mountHost('div', { 'data-scroll-progress-mode': 'page' });
73
- connectTrait(scrollProgress, host);
74
- // happy-dom reports zero scroll geometry by default.
75
- expect(host.getAttribute('data-scroll-progress')).toBe('0.000');
76
- });
77
-
78
- it('mode="scrolled" reports 0..1 from host scrollTop / max', () => {
79
- const host = mountHost('div', { 'data-scroll-progress-mode': 'scrolled' });
80
- // Stub the geometry — happy-dom returns zeros for scroll metrics.
81
- Object.defineProperty(host, 'scrollHeight', { configurable: true, value: 1000 });
82
- Object.defineProperty(host, 'clientHeight', { configurable: true, value: 200 });
83
- Object.defineProperty(host, 'scrollTop', { configurable: true, value: 200, writable: true });
84
- connectTrait(scrollProgress, host);
85
- const v = parseFloat(host.getAttribute('data-scroll-progress'));
86
- // 200 / (1000 - 200) = 0.25
87
- expect(v).toBeCloseTo(0.25, 2);
88
- });
89
-
90
- it('mode="in-view" clamps to [0,1] for offscreen rects', () => {
91
- const host = mountHost();
92
- // Far below the viewport.
93
- host.getBoundingClientRect = () => ({ top: 99999, bottom: 100099, left: 0, right: 0, width: 0, height: 100, x: 0, y: 99999, toJSON() { return {}; } });
94
- connectTrait(scrollProgress, host);
95
- expect(parseFloat(host.getAttribute('data-scroll-progress'))).toBe(0);
96
- });
97
-
98
- it('mode="in-view" reports 1 for rects fully scrolled past', () => {
99
- const host = mountHost();
100
- // Far above the viewport.
101
- host.getBoundingClientRect = () => ({ top: -2000, bottom: -1900, left: 0, right: 0, width: 0, height: 100, x: 0, y: -2000, toJSON() { return {}; } });
102
- // Make sure window.innerHeight is sane.
103
- Object.defineProperty(window, 'innerHeight', { configurable: true, value: 800 });
104
- connectTrait(scrollProgress, host);
105
- expect(parseFloat(host.getAttribute('data-scroll-progress'))).toBe(1);
106
- });
107
-
108
- it('data-scroll-container selector resolves an explicit ancestor', () => {
109
- document.body.innerHTML = '<div id="custom-scroller" style="overflow:auto;"><div id="inner"></div></div>';
110
- const host = document.getElementById('inner');
111
- host.setAttribute('data-scroll-container', '#custom-scroller');
112
- const scroller = document.getElementById('custom-scroller');
113
- const addSpy = vi.spyOn(scroller, 'addEventListener');
114
- connectTrait(scrollProgress, host);
115
- // The trait should attach a scroll listener to the resolved container.
116
- const calls = addSpy.mock.calls.filter(([type]) => type === 'scroll');
117
- expect(calls.length).toBeGreaterThanOrEqual(1);
118
- });
119
-
120
- it('invalid data-scroll-container selector falls through to ancestor walk', () => {
121
- document.body.innerHTML = '<div id="parent" style="overflow:auto;"><div id="child"></div></div>';
122
- const host = document.getElementById('child');
123
- host.setAttribute('data-scroll-container', '###not-a-valid-selector');
124
- expect(() => connectTrait(scrollProgress, host)).not.toThrow();
125
- });
126
-
127
- it('schedules through rAF — second scroll within the same frame coalesces', () => {
128
- let queued = 0;
129
- let pending = null;
130
- globalThis.requestAnimationFrame = (fn) => {
131
- queued++;
132
- pending = fn;
133
- return queued;
134
- };
135
- globalThis.cancelAnimationFrame = vi.fn();
136
- const host = mountHost();
137
- const inst = connectTrait(scrollProgress, host);
138
- const baseline = queued;
139
- // Fire two scroll events back-to-back without flushing the rAF.
140
- window.dispatchEvent(new Event('scroll'));
141
- window.dispatchEvent(new Event('scroll'));
142
- // Only one rAF should be queued in addition to the seed.
143
- expect(queued - baseline).toBeLessThanOrEqual(1);
144
- // Flush manually so disconnect has nothing pending.
145
- if (pending) pending(0);
146
- inst.disconnect(host);
147
- });
148
-
149
- it('disconnect cancels the pending rAF', () => {
150
- let pendingId = null;
151
- globalThis.requestAnimationFrame = () => { pendingId = 42; return 42; };
152
- const cancelSpy = vi.fn();
153
- globalThis.cancelAnimationFrame = cancelSpy;
154
- const host = mountHost();
155
- const inst = connectTrait(scrollProgress, host);
156
- inst.disconnect(host);
157
- // The trait queued one rAF on connect; disconnect should cancel it.
158
- expect(cancelSpy).toHaveBeenCalled();
159
- });
160
-
161
- it('disconnect removes the scroll listener from the resolved container', () => {
162
- document.body.innerHTML = '<div id="scr" style="overflow:auto;"><div id="h"></div></div>';
163
- const host = document.getElementById('h');
164
- host.setAttribute('data-scroll-container', '#scr');
165
- const scroller = document.getElementById('scr');
166
- const removeSpy = vi.spyOn(scroller, 'removeEventListener');
167
- const inst = connectTrait(scrollProgress, host);
168
- inst.disconnect(host);
169
- const calls = removeSpy.mock.calls.filter(([type]) => type === 'scroll');
170
- expect(calls.length).toBeGreaterThanOrEqual(1);
171
- });
172
-
173
- it('reconnect after disconnect does not throw and restores the active flag', () => {
174
- const host = mountHost();
175
- const inst1 = connectTrait(scrollProgress, host);
176
- inst1.disconnect(host);
177
- const inst2 = connectTrait(scrollProgress, host);
178
- expect(host.hasAttribute('data-scroll-progress-active')).toBe(true);
179
- inst2.disconnect(host);
180
- expect(host.hasAttribute('data-scroll-progress-active')).toBe(false);
181
- });
182
- });