@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.
- package/CHANGELOG.md +26 -0
- package/components/card/card.a2ui.json +6 -3
- package/components/card/card.css +15 -1
- package/components/card/card.yaml +5 -3
- package/components/nav-group/nav-group.a2ui.json +5 -0
- package/components/nav-group/nav-group.class.js +14 -6
- package/components/nav-group/nav-group.css +5 -2
- package/components/nav-group/nav-group.d.ts +2 -0
- package/components/nav-group/nav-group.yaml +4 -0
- package/components/select/select.a2ui.json +3 -2
- package/components/select/select.class.js +14 -1
- package/components/select/select.css +13 -1
- package/components/select/select.yaml +5 -4
- package/components/table/table.class.js +7 -3
- package/components/table/table.css +7 -5
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +19 -19
- package/dist/web-components.sheet.js +1 -1
- package/package.json +2 -1
- package/components/adia-mark/adia-mark.test.js +0 -69
- package/components/adia-wordmark/adia-wordmark.test.js +0 -69
- package/components/agent-artifact/agent-artifact.test.js +0 -61
- package/components/alert/alert.test.js +0 -364
- package/components/color-picker/color-picker.test.js +0 -96
- package/components/combobox/combobox.test.js +0 -256
- package/components/command/command.test.js +0 -99
- package/components/date-range-picker/date-range-picker.test.js +0 -387
- package/components/datetime-picker/datetime-picker.test.js +0 -454
- package/components/demo-toggle/demo-toggle.test.js +0 -102
- package/components/description-list/description-list.test.js +0 -72
- package/components/display-field/display-field.test.js +0 -115
- package/components/empty-state/empty-state.test.js +0 -77
- package/components/field/field.test.js +0 -260
- package/components/fields/fields.test.js +0 -88
- package/components/inline-message/inline-message.test.js +0 -203
- package/components/input/input.test.js +0 -335
- package/components/integration-card/integration-card.test.js +0 -318
- package/components/link/link.test.js +0 -143
- package/components/list/list.test.js +0 -106
- package/components/list-window/list-window.test.js +0 -303
- package/components/loading-overlay/loading-overlay.test.js +0 -257
- package/components/menu/menu.test.js +0 -176
- package/components/nav/nav.test.js +0 -428
- package/components/nav-group/nav-group.test.js +0 -67
- package/components/option-card/option-card.test.js +0 -32
- package/components/pagination/pagination.test.js +0 -195
- package/components/select/select.test.js +0 -481
- package/components/slider/slider.test.js +0 -202
- package/components/spinner/spinner.test.js +0 -272
- package/components/stat/stat.test.js +0 -108
- package/components/step-progress/step-progress.test.js +0 -129
- package/components/swatch/swatch.test.js +0 -563
- package/components/table/table.test.js +0 -479
- package/components/table-toolbar/table-toolbar.test.js +0 -152
- package/components/tag/tag.test.js +0 -137
- package/components/tags-input/tags-input.test.js +0 -477
- package/components/text/text.test.js +0 -202
- package/components/theme-provider/theme-provider.test.js +0 -94
- package/components/time-picker/time-picker.test.js +0 -287
- package/components/timeline/timeline.test.js +0 -31
- package/components/toggle-scheme/toggle-scheme.test.js +0 -110
- package/components/tree/tree.test.js +0 -312
- package/core/element.test.js +0 -389
- package/core/icons.test.js +0 -187
- package/core/logical-children.test.js +0 -54
- package/core/markdown.test.js +0 -47
- package/core/responsive.test.js +0 -121
- package/core/template.test.js +0 -611
- package/traits/active-state/active-state.test.js +0 -28
- package/traits/anchor-positioning/anchor-positioning.test.js +0 -122
- package/traits/announcer/announcer.test.js +0 -268
- package/traits/arrow-grid-nav/arrow-grid-nav.test.js +0 -375
- package/traits/attention-pulse/attention-pulse.test.js +0 -26
- package/traits/confetti/confetti.test.js +0 -46
- package/traits/confetti-burst/confetti-burst.test.js +0 -46
- package/traits/count-up/count-up.test.js +0 -54
- package/traits/declarative.test.js +0 -138
- package/traits/dirty-state/dirty-state.test.js +0 -45
- package/traits/drag-ghost/drag-ghost.test.js +0 -19
- package/traits/draggable/draggable.test.js +0 -60
- package/traits/draggable-list-item/draggable-list-item.test.js +0 -51
- package/traits/drop-target/drop-target.test.js +0 -241
- package/traits/droppable/droppable.test.js +0 -54
- package/traits/droppable-collection/droppable-collection.test.js +0 -99
- package/traits/error-shake/error-shake.test.js +0 -114
- package/traits/fade-presence/fade-presence.test.js +0 -20
- package/traits/focus-restore/focus-restore.test.js +0 -202
- package/traits/focus-trap/focus-trap.test.js +0 -42
- package/traits/focusable/focusable.test.js +0 -53
- package/traits/glow-focus/glow-focus.test.js +0 -31
- package/traits/gradient-shift/gradient-shift.test.js +0 -22
- package/traits/haptic-feedback/haptic-feedback.test.js +0 -52
- package/traits/hotkey/hotkey.test.js +0 -61
- package/traits/hoverable/hoverable.test.js +0 -24
- package/traits/inertia-drag/inertia-drag.test.js +0 -33
- package/traits/input-mask/input-mask.test.js +0 -151
- package/traits/intersection-observer/intersection-observer.test.js +0 -38
- package/traits/keyboard-nav/keyboard-nav.test.js +0 -41
- package/traits/keyboard-reorderable/keyboard-reorderable.test.js +0 -45
- package/traits/layout-animation/layout-animation.test.js +0 -114
- package/traits/long-press/long-press.test.js +0 -244
- package/traits/magnetic-hover/magnetic-hover.test.js +0 -30
- package/traits/noise-texture/noise-texture.test.js +0 -20
- package/traits/parallax/parallax.test.js +0 -26
- package/traits/portal/portal.test.js +0 -30
- package/traits/pressable/pressable.test.js +0 -73
- package/traits/resettable/resettable.test.js +0 -100
- package/traits/resizable/resizable.test.js +0 -39
- package/traits/resize-observer/resize-observer.test.js +0 -38
- package/traits/ripple/ripple.test.js +0 -32
- package/traits/roving-tabindex/roving-tabindex.test.js +0 -28
- package/traits/scale-press/scale-press.test.js +0 -39
- package/traits/scroll-lock/scroll-lock.test.js +0 -45
- package/traits/scroll-progress/scroll-progress.test.js +0 -182
- package/traits/shimmer-loading/shimmer-loading.test.js +0 -43
- package/traits/smoke.test.js +0 -111
- package/traits/snap-to-grid/snap-to-grid.test.js +0 -40
- package/traits/sound-feedback/sound-feedback.test.js +0 -26
- package/traits/spring-animate/spring-animate.test.js +0 -28
- package/traits/success-checkmark/success-checkmark.test.js +0 -120
- package/traits/tilt-hover/tilt-hover.test.js +0 -32
- package/traits/tossable/tossable.test.js +0 -31
- package/traits/traits-host.test.js +0 -73
- package/traits/typeahead/typeahead.test.js +0 -38
- package/traits/typewriter/typewriter.test.js +0 -47
- package/traits/validation/validation.test.js +0 -93
- package/traits/view-transition/view-transition.test.js +0 -268
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { inputMask } from './input-mask.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Drive an `input` event with a new value and a caret-position guess.
|
|
7
|
-
* Mirrors what a real browser dispatches when the user types.
|
|
8
|
-
*/
|
|
9
|
-
function type(host, value, caret) {
|
|
10
|
-
host.value = value;
|
|
11
|
-
if (caret != null) {
|
|
12
|
-
try { host.setSelectionRange(caret, caret); } catch { /* number-typed inputs throw */ }
|
|
13
|
-
}
|
|
14
|
-
host.dispatchEvent(new Event('input'));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Microtask-flush helper — caret writes are queued via queueMicrotask
|
|
19
|
-
* so the DOM write doesn't fight the browser's own caret reposition.
|
|
20
|
-
*/
|
|
21
|
-
const flush = () => new Promise(r => queueMicrotask(r));
|
|
22
|
-
|
|
23
|
-
describe('input-mask', () => {
|
|
24
|
-
beforeEach(resetDOM);
|
|
25
|
-
|
|
26
|
-
it('schema declares forms-data category + the documented surface', () => {
|
|
27
|
-
expect(inputMask.schema.category).toBe('forms-data');
|
|
28
|
-
expect(inputMask.schema.attributes).toContain('data-input-mask-active');
|
|
29
|
-
expect(inputMask.schema.attributes).toContain('data-input-mask-complete');
|
|
30
|
-
expect(inputMask.schema.events).toContain('mask-commit');
|
|
31
|
-
expect(inputMask.schema.config).toContain('data-mask-pattern');
|
|
32
|
-
expect(inputMask.schema.config).toContain('data-mask-strip-on-commit');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('phone-us named pattern: 4155551234 → (415) 555-1234', () => {
|
|
36
|
-
const host = mountHost('input', { 'data-mask-pattern': 'phone-us' });
|
|
37
|
-
connectTrait(inputMask, host);
|
|
38
|
-
type(host, '4155551234');
|
|
39
|
-
expect(host.value).toBe('(415) 555-1234');
|
|
40
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('credit-card pattern: 16 digits → 4-4-4-4 grouping', () => {
|
|
44
|
-
const host = mountHost('input', { 'data-mask-pattern': 'credit-card' });
|
|
45
|
-
connectTrait(inputMask, host);
|
|
46
|
-
type(host, '4111111111111111');
|
|
47
|
-
expect(host.value).toBe('4111 1111 1111 1111');
|
|
48
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('partial input does not set complete; full input does', () => {
|
|
52
|
-
const host = mountHost('input', { 'data-mask-pattern': 'date-iso' });
|
|
53
|
-
connectTrait(inputMask, host);
|
|
54
|
-
type(host, '2026');
|
|
55
|
-
expect(host.value).toBe('2026');
|
|
56
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(false);
|
|
57
|
-
type(host, '20260504');
|
|
58
|
-
expect(host.value).toBe('2026-05-04');
|
|
59
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('user-supplied pattern overrides any naming collision', () => {
|
|
63
|
-
// Pure literal pattern — should be taken verbatim, not matched as a name.
|
|
64
|
-
const host = mountHost('input', { 'data-mask-pattern': 'AAA-####' });
|
|
65
|
-
connectTrait(inputMask, host);
|
|
66
|
-
type(host, 'ABC1234');
|
|
67
|
-
expect(host.value).toBe('ABC-1234');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('rejects placeholder-mismatching chars (digit-only mask drops letters)', () => {
|
|
71
|
-
const host = mountHost('input', { 'data-mask-pattern': 'cvv' });
|
|
72
|
-
connectTrait(inputMask, host);
|
|
73
|
-
type(host, '12a3b4');
|
|
74
|
-
// letters skipped, only the 4 digits land in the 4 # slots
|
|
75
|
-
expect(host.value).toBe('1234');
|
|
76
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('caret preservation: typing in the middle keeps the cursor anchored', async () => {
|
|
80
|
-
const host = mountHost('input', { 'data-mask-pattern': 'phone-us' });
|
|
81
|
-
connectTrait(inputMask, host);
|
|
82
|
-
// Seed with 4 digits → "(415) " has 4 fillable slots reached with
|
|
83
|
-
// 3 area-code digits + 1 exchange digit: "(415) 5".
|
|
84
|
-
type(host, '4155');
|
|
85
|
-
expect(host.value).toBe('(415) 5');
|
|
86
|
-
// Append a 5th digit. The trait should reformat to "(415) 55" and
|
|
87
|
-
// place the caret at the position after 5 real chars (index 8).
|
|
88
|
-
type(host, '41555', 8);
|
|
89
|
-
await flush();
|
|
90
|
-
expect(host.value).toBe('(415) 55');
|
|
91
|
-
expect(host.selectionStart).toBe(8);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('mask-commit on blur fires with raw + formatted detail', () => {
|
|
95
|
-
const host = mountHost('input', { 'data-mask-pattern': 'phone-us' });
|
|
96
|
-
connectTrait(inputMask, host);
|
|
97
|
-
const spy = spyEvent(host, 'mask-commit');
|
|
98
|
-
type(host, '4155551234');
|
|
99
|
-
host.dispatchEvent(new Event('blur'));
|
|
100
|
-
expect(spy.count).toBe(1);
|
|
101
|
-
expect(spy.last.formatted).toBe('(415) 555-1234');
|
|
102
|
-
expect(spy.last.raw).toBe('4155551234');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('strip-on-commit replaces the value with the raw form on blur', () => {
|
|
106
|
-
const host = mountHost('input', {
|
|
107
|
-
'data-mask-pattern': 'phone-us',
|
|
108
|
-
'data-mask-strip-on-commit': '',
|
|
109
|
-
});
|
|
110
|
-
connectTrait(inputMask, host);
|
|
111
|
-
type(host, '4155551234');
|
|
112
|
-
expect(host.value).toBe('(415) 555-1234');
|
|
113
|
-
host.dispatchEvent(new Event('blur'));
|
|
114
|
-
expect(host.value).toBe('4155551234');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('disconnect clears managed attributes', () => {
|
|
118
|
-
const host = mountHost('input', { 'data-mask-pattern': 'phone-us' });
|
|
119
|
-
const inst = connectTrait(inputMask, host);
|
|
120
|
-
type(host, '4155551234');
|
|
121
|
-
expect(host.hasAttribute('data-input-mask-active')).toBe(true);
|
|
122
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
|
|
123
|
-
inst.disconnect(host);
|
|
124
|
-
expect(host.hasAttribute('data-input-mask-active')).toBe(false);
|
|
125
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(false);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('missing pattern is a no-op (value untouched, no active flag)', () => {
|
|
129
|
-
const host = mountHost('input');
|
|
130
|
-
connectTrait(inputMask, host);
|
|
131
|
-
type(host, 'whatever-the-user-types');
|
|
132
|
-
expect(host.value).toBe('whatever-the-user-types');
|
|
133
|
-
expect(host.hasAttribute('data-input-mask-active')).toBe(false);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('AAA letter slot only accepts letters (digits skipped)', () => {
|
|
137
|
-
const host = mountHost('input', { 'data-mask-pattern': 'AAA' });
|
|
138
|
-
connectTrait(inputMask, host);
|
|
139
|
-
type(host, 'A1B2C3');
|
|
140
|
-
expect(host.value).toBe('ABC');
|
|
141
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('star slot accepts any char', () => {
|
|
145
|
-
const host = mountHost('input', { 'data-mask-pattern': '***-***' });
|
|
146
|
-
connectTrait(inputMask, host);
|
|
147
|
-
type(host, 'aB1xY9');
|
|
148
|
-
expect(host.value).toBe('aB1-xY9');
|
|
149
|
-
expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
|
-
import { intersectionObserver } from './intersection-observer.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('intersection-observer', () => {
|
|
6
|
-
let originalIO;
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
resetDOM();
|
|
10
|
-
originalIO = globalThis.IntersectionObserver;
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (originalIO) globalThis.IntersectionObserver = originalIO;
|
|
15
|
-
else delete globalThis.IntersectionObserver;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('without IntersectionObserver: gracefully no-ops on connect', () => {
|
|
19
|
-
delete globalThis.IntersectionObserver;
|
|
20
|
-
const host = mountHost();
|
|
21
|
-
expect(() => connectTrait(intersectionObserver, host)).not.toThrow();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('with mocked IntersectionObserver: observe() is called on connect', () => {
|
|
25
|
-
const observe = vi.fn();
|
|
26
|
-
const ioDisconnect = vi.fn();
|
|
27
|
-
globalThis.IntersectionObserver = function MockIO() {
|
|
28
|
-
this.observe = observe;
|
|
29
|
-
this.disconnect = ioDisconnect;
|
|
30
|
-
this.unobserve = vi.fn();
|
|
31
|
-
};
|
|
32
|
-
const host = mountHost('div', { 'data-intersection-threshold': '0.5' });
|
|
33
|
-
const inst = connectTrait(intersectionObserver, host);
|
|
34
|
-
expect(observe).toHaveBeenCalledWith(host);
|
|
35
|
-
inst.disconnect(host);
|
|
36
|
-
expect(ioDisconnect).toHaveBeenCalled();
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { keyboardNav } from './keyboard-nav.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('keyboard-nav', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it.each([
|
|
9
|
-
['ArrowUp', 'nav-up'],
|
|
10
|
-
['ArrowDown', 'nav-down'],
|
|
11
|
-
['ArrowLeft', 'nav-left'],
|
|
12
|
-
['ArrowRight', 'nav-right'],
|
|
13
|
-
['Enter', 'nav-enter'],
|
|
14
|
-
['Escape', 'nav-escape'],
|
|
15
|
-
['Home', 'nav-home'],
|
|
16
|
-
['End', 'nav-end'],
|
|
17
|
-
])('%s keydown fires %s event', (key, eventName) => {
|
|
18
|
-
const host = mountHost();
|
|
19
|
-
connectTrait(keyboardNav, host);
|
|
20
|
-
const spy = spyEvent(host, eventName);
|
|
21
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key }));
|
|
22
|
-
expect(spy.count).toBe(1);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('unrelated key does not fire any nav event', () => {
|
|
26
|
-
const host = mountHost();
|
|
27
|
-
connectTrait(keyboardNav, host);
|
|
28
|
-
const spy = spyEvent(host, 'nav-up');
|
|
29
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
|
30
|
-
expect(spy.count).toBe(0);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('disconnect removes the keydown listener', () => {
|
|
34
|
-
const host = mountHost();
|
|
35
|
-
const inst = connectTrait(keyboardNav, host);
|
|
36
|
-
const spy = spyEvent(host, 'nav-up');
|
|
37
|
-
inst.disconnect(host);
|
|
38
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
|
|
39
|
-
expect(spy.count).toBe(0);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { keyboardReorderable } from './keyboard-reorderable.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
// Minimal smoke coverage. Keyboard-reorderable is the WCAG 2.5.7 +
|
|
6
|
-
// ARIA 1.1 alternative to draggable-list-item for the
|
|
7
|
-
// apps/tasks/ DnD model. Cases below verify
|
|
8
|
-
// only the trait's own surface (schema, attribute lifecycle, id
|
|
9
|
-
// assignment, tabindex injection). Multi-container keyboard flow
|
|
10
|
-
// belongs in the playground integration suite.
|
|
11
|
-
|
|
12
|
-
describe('keyboard-reorderable', () => {
|
|
13
|
-
beforeEach(resetDOM);
|
|
14
|
-
|
|
15
|
-
it('schema shape is defined', () => {
|
|
16
|
-
expect(keyboardReorderable.schema.name).toBe('keyboard-reorderable');
|
|
17
|
-
expect(keyboardReorderable.schema.category).toBe('keyboard-navigation');
|
|
18
|
-
expect(keyboardReorderable.schema.description.length).toBeGreaterThanOrEqual(11);
|
|
19
|
-
expect(keyboardReorderable.schema.events).toContain('dnd-lift');
|
|
20
|
-
expect(keyboardReorderable.schema.events).toContain('dnd-drop');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('connect assigns an id + ensures tabindex', () => {
|
|
24
|
-
const host = mountHost('div');
|
|
25
|
-
connectTrait(keyboardReorderable, host);
|
|
26
|
-
expect(host.getAttribute('data-keyboard-reorderable-id')).toBeTruthy();
|
|
27
|
-
expect(host.getAttribute('tabindex')).toBe('0');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('connect respects pre-existing tabindex', () => {
|
|
31
|
-
const host = mountHost('div', { tabindex: '-1' });
|
|
32
|
-
connectTrait(keyboardReorderable, host);
|
|
33
|
-
expect(host.getAttribute('tabindex')).toBe('-1');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('disconnect clears its attributes', () => {
|
|
37
|
-
const host = mountHost('div');
|
|
38
|
-
const inst = keyboardReorderable();
|
|
39
|
-
inst.connect(host);
|
|
40
|
-
host.setAttribute('data-keyboard-reorderable-lifting', '');
|
|
41
|
-
inst.disconnect(host);
|
|
42
|
-
expect(host.hasAttribute('data-keyboard-reorderable-lifting')).toBe(false);
|
|
43
|
-
expect(host.hasAttribute('data-keyboard-reorderable-id')).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { layoutAnimation } from './layout-animation.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM, raf } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('layout-animation', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('connect does not throw and registers no leftover attribute', () => {
|
|
9
|
-
const host = mountHost();
|
|
10
|
-
const inst = connectTrait(layoutAnimation, host);
|
|
11
|
-
expect(host.hasAttribute('data-layout-animation-active')).toBe(false);
|
|
12
|
-
inst.disconnect(host);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('disconnect cleans up the active attribute and is idempotent', () => {
|
|
16
|
-
const host = mountHost();
|
|
17
|
-
const inst = connectTrait(layoutAnimation, host);
|
|
18
|
-
host.setAttribute('data-layout-animation-active', '');
|
|
19
|
-
inst.disconnect(host);
|
|
20
|
-
expect(host.hasAttribute('data-layout-animation-active')).toBe(false);
|
|
21
|
-
// A second disconnect must not throw.
|
|
22
|
-
expect(() => inst.disconnect(host)).not.toThrow();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('reads data-layout-animate-duration + data-layout-animate-easing without throwing', () => {
|
|
26
|
-
const host = mountHost('div', {
|
|
27
|
-
'data-layout-animate-duration': '500',
|
|
28
|
-
'data-layout-animate-easing': 'ease-in-out',
|
|
29
|
-
});
|
|
30
|
-
expect(() => connectTrait(layoutAnimation, host)).not.toThrow();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('handles a missing duration / easing — defaults apply silently', () => {
|
|
34
|
-
const host = mountHost();
|
|
35
|
-
expect(() => connectTrait(layoutAnimation, host)).not.toThrow();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('exposes the expected schema surface', () => {
|
|
39
|
-
expect(layoutAnimation.schema.name).toBe('layout-animation');
|
|
40
|
-
expect(layoutAnimation.schema.category).toBe('motion-positioning');
|
|
41
|
-
expect(layoutAnimation.schema.attributes).toContain('data-layout-animation-active');
|
|
42
|
-
expect(layoutAnimation.schema.events).toContain('layout-animation-done');
|
|
43
|
-
expect(layoutAnimation.schema.config).toEqual([
|
|
44
|
-
'data-layout-animate-duration',
|
|
45
|
-
'data-layout-animate-easing',
|
|
46
|
-
'data-layout-animate-trigger',
|
|
47
|
-
]);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('parent-childList mutation invokes the FLIP path without throwing', async () => {
|
|
51
|
-
// Build a parent + sibling so we can mutate child order.
|
|
52
|
-
const parent = document.createElement('div');
|
|
53
|
-
document.body.appendChild(parent);
|
|
54
|
-
const sib = document.createElement('div');
|
|
55
|
-
const host = document.createElement('div');
|
|
56
|
-
parent.appendChild(sib);
|
|
57
|
-
parent.appendChild(host);
|
|
58
|
-
|
|
59
|
-
const inst = layoutAnimation();
|
|
60
|
-
inst.connect(host);
|
|
61
|
-
|
|
62
|
-
// Reorder siblings — should drive the MutationObserver callback.
|
|
63
|
-
expect(() => parent.insertBefore(host, sib)).not.toThrow();
|
|
64
|
-
|
|
65
|
-
// Allow the MutationObserver microtask + rAF capture to settle.
|
|
66
|
-
await Promise.resolve();
|
|
67
|
-
await raf();
|
|
68
|
-
|
|
69
|
-
inst.disconnect(host);
|
|
70
|
-
expect(host.hasAttribute('data-layout-animation-active')).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('attribute-trigger flip is wired (toggling data-layout-animate-trigger does not throw)', async () => {
|
|
74
|
-
const host = mountHost();
|
|
75
|
-
const inst = connectTrait(layoutAnimation, host);
|
|
76
|
-
|
|
77
|
-
// Set the trigger — observer should fire a flip(); no DOM rect change in
|
|
78
|
-
// the test env means it short-circuits to "no meaningful delta", which we
|
|
79
|
-
// assert by absence of throws + no leftover active attribute after settle.
|
|
80
|
-
host.setAttribute('data-layout-animate-trigger', '1');
|
|
81
|
-
|
|
82
|
-
await Promise.resolve();
|
|
83
|
-
await raf();
|
|
84
|
-
|
|
85
|
-
inst.disconnect(host);
|
|
86
|
-
expect(host.hasAttribute('data-layout-animation-active')).toBe(false);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('layout-animation-done event listener attaches without error', () => {
|
|
90
|
-
const host = mountHost();
|
|
91
|
-
const spy = spyEvent(host, 'layout-animation-done');
|
|
92
|
-
connectTrait(layoutAnimation, host);
|
|
93
|
-
// We don't assert spy.count > 0 here — the test environment (happy-dom)
|
|
94
|
-
// returns zero rects, so the FLIP path short-circuits at the no-delta
|
|
95
|
-
// gate. The contract we care about is that subscribing doesn't throw.
|
|
96
|
-
expect(spy.count).toBe(0);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('two parallel instances on different hosts do not interfere', () => {
|
|
100
|
-
const a = mountHost();
|
|
101
|
-
const b = mountHost();
|
|
102
|
-
const ia = connectTrait(layoutAnimation, a);
|
|
103
|
-
const ib = connectTrait(layoutAnimation, b);
|
|
104
|
-
expect(() => ia.disconnect(a)).not.toThrow();
|
|
105
|
-
expect(() => ib.disconnect(b)).not.toThrow();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('disconnect cancels any scheduled snapshot capture', () => {
|
|
109
|
-
const host = mountHost();
|
|
110
|
-
const inst = connectTrait(layoutAnimation, host);
|
|
111
|
-
// Disconnect synchronously after connect — exercises the rAF-cancel branch.
|
|
112
|
-
expect(() => inst.disconnect(host)).not.toThrow();
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { longPress } from './long-press.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM, wait } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
function pointerDown(host, x = 0, y = 0) {
|
|
6
|
-
host.dispatchEvent(new PointerEvent('pointerdown', { clientX: x, clientY: y, bubbles: true }));
|
|
7
|
-
}
|
|
8
|
-
function pointerUp(host, x = 0, y = 0) {
|
|
9
|
-
host.dispatchEvent(new PointerEvent('pointerup', { clientX: x, clientY: y, bubbles: true }));
|
|
10
|
-
}
|
|
11
|
-
function pointerMove(host, x, y) {
|
|
12
|
-
host.dispatchEvent(new PointerEvent('pointermove', { clientX: x, clientY: y, bubbles: true }));
|
|
13
|
-
}
|
|
14
|
-
function pointerLeave(host) {
|
|
15
|
-
host.dispatchEvent(new PointerEvent('pointerleave', { bubbles: true }));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe('long-press', () => {
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
resetDOM();
|
|
21
|
-
vi.useFakeTimers();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('sets data-long-press-active on pointerdown', () => {
|
|
25
|
-
const host = mountHost('button', {
|
|
26
|
-
'data-long-press-duration': '100',
|
|
27
|
-
'data-long-press-progress-interval': '20',
|
|
28
|
-
});
|
|
29
|
-
connectTrait(longPress, host);
|
|
30
|
-
pointerDown(host);
|
|
31
|
-
expect(host.hasAttribute('data-long-press-active')).toBe(true);
|
|
32
|
-
expect(host.getAttribute('data-long-press-progress')).toBe('0');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('fires "long-press" after the configured duration elapses', () => {
|
|
36
|
-
const host = mountHost('button', {
|
|
37
|
-
'data-long-press-duration': '100',
|
|
38
|
-
'data-long-press-progress-interval': '20',
|
|
39
|
-
});
|
|
40
|
-
connectTrait(longPress, host);
|
|
41
|
-
const spy = spyEvent(host, 'long-press');
|
|
42
|
-
pointerDown(host);
|
|
43
|
-
vi.advanceTimersByTime(100);
|
|
44
|
-
expect(spy.count).toBe(1);
|
|
45
|
-
expect(host.hasAttribute('data-long-press-fired')).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('emits "long-press-progress" at intervals while held', () => {
|
|
49
|
-
const host = mountHost('button', {
|
|
50
|
-
'data-long-press-duration': '100',
|
|
51
|
-
'data-long-press-progress-interval': '20',
|
|
52
|
-
});
|
|
53
|
-
connectTrait(longPress, host);
|
|
54
|
-
const spy = spyEvent(host, 'long-press-progress');
|
|
55
|
-
pointerDown(host);
|
|
56
|
-
vi.advanceTimersByTime(60); // 3 ticks at 20ms
|
|
57
|
-
expect(spy.count).toBeGreaterThanOrEqual(2);
|
|
58
|
-
// Each tick should report progress in [0, 1].
|
|
59
|
-
for (const detail of spy.captures) {
|
|
60
|
-
expect(detail.progress).toBeGreaterThanOrEqual(0);
|
|
61
|
-
expect(detail.progress).toBeLessThanOrEqual(1);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('fires "long-press-cancelled" when pointerup arrives early', () => {
|
|
66
|
-
const host = mountHost('button', {
|
|
67
|
-
'data-long-press-duration': '200',
|
|
68
|
-
'data-long-press-progress-interval': '50',
|
|
69
|
-
});
|
|
70
|
-
connectTrait(longPress, host);
|
|
71
|
-
const fireSpy = spyEvent(host, 'long-press');
|
|
72
|
-
const cancelSpy = spyEvent(host, 'long-press-cancelled');
|
|
73
|
-
pointerDown(host);
|
|
74
|
-
vi.advanceTimersByTime(80); // well short of 200
|
|
75
|
-
pointerUp(host);
|
|
76
|
-
expect(fireSpy.count).toBe(0);
|
|
77
|
-
expect(cancelSpy.count).toBe(1);
|
|
78
|
-
expect(cancelSpy.last.reason).toBe('release');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('fires "long-press-cancelled" on pointerleave', () => {
|
|
82
|
-
const host = mountHost('button', {
|
|
83
|
-
'data-long-press-duration': '200',
|
|
84
|
-
});
|
|
85
|
-
connectTrait(longPress, host);
|
|
86
|
-
const cancelSpy = spyEvent(host, 'long-press-cancelled');
|
|
87
|
-
pointerDown(host);
|
|
88
|
-
vi.advanceTimersByTime(40);
|
|
89
|
-
pointerLeave(host);
|
|
90
|
-
expect(cancelSpy.count).toBe(1);
|
|
91
|
-
expect(cancelSpy.last.reason).toBe('leave');
|
|
92
|
-
expect(host.hasAttribute('data-long-press-active')).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('cancels when pointermove exceeds the tolerance', () => {
|
|
96
|
-
const host = mountHost('button', {
|
|
97
|
-
'data-long-press-duration': '300',
|
|
98
|
-
'data-long-press-tolerance': '8',
|
|
99
|
-
});
|
|
100
|
-
connectTrait(longPress, host);
|
|
101
|
-
const cancelSpy = spyEvent(host, 'long-press-cancelled');
|
|
102
|
-
pointerDown(host, 0, 0);
|
|
103
|
-
pointerMove(host, 20, 0); // > 8px
|
|
104
|
-
expect(cancelSpy.count).toBe(1);
|
|
105
|
-
expect(cancelSpy.last.reason).toBe('move');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('does NOT cancel when pointermove stays within tolerance', () => {
|
|
109
|
-
const host = mountHost('button', {
|
|
110
|
-
'data-long-press-duration': '100',
|
|
111
|
-
'data-long-press-tolerance': '8',
|
|
112
|
-
});
|
|
113
|
-
connectTrait(longPress, host);
|
|
114
|
-
const cancelSpy = spyEvent(host, 'long-press-cancelled');
|
|
115
|
-
const fireSpy = spyEvent(host, 'long-press');
|
|
116
|
-
pointerDown(host, 0, 0);
|
|
117
|
-
pointerMove(host, 5, 5); // hypot ~7.07, under 8
|
|
118
|
-
vi.advanceTimersByTime(100);
|
|
119
|
-
expect(cancelSpy.count).toBe(0);
|
|
120
|
-
expect(fireSpy.count).toBe(1);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('respects [disabled] — no listeners fire, no attributes set', () => {
|
|
124
|
-
const host = mountHost('button', {
|
|
125
|
-
disabled: '',
|
|
126
|
-
'data-long-press-duration': '50',
|
|
127
|
-
});
|
|
128
|
-
connectTrait(longPress, host);
|
|
129
|
-
const fireSpy = spyEvent(host, 'long-press');
|
|
130
|
-
pointerDown(host);
|
|
131
|
-
expect(host.hasAttribute('data-long-press-active')).toBe(false);
|
|
132
|
-
vi.advanceTimersByTime(100);
|
|
133
|
-
expect(fireSpy.count).toBe(0);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('suppresses the synthetic click that follows a committed long-press', () => {
|
|
137
|
-
const host = mountHost('button', {
|
|
138
|
-
'data-long-press-duration': '100',
|
|
139
|
-
});
|
|
140
|
-
connectTrait(longPress, host);
|
|
141
|
-
let clickHits = 0;
|
|
142
|
-
host.addEventListener('click', () => clickHits++);
|
|
143
|
-
pointerDown(host);
|
|
144
|
-
vi.advanceTimersByTime(100);
|
|
145
|
-
pointerUp(host);
|
|
146
|
-
host.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
147
|
-
expect(clickHits).toBe(0);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('does NOT suppress click after a cancelled press', () => {
|
|
151
|
-
const host = mountHost('button', {
|
|
152
|
-
'data-long-press-duration': '300',
|
|
153
|
-
});
|
|
154
|
-
connectTrait(longPress, host);
|
|
155
|
-
let clickHits = 0;
|
|
156
|
-
host.addEventListener('click', () => clickHits++);
|
|
157
|
-
pointerDown(host);
|
|
158
|
-
vi.advanceTimersByTime(60);
|
|
159
|
-
pointerUp(host); // early release → cancelled
|
|
160
|
-
host.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
161
|
-
expect(clickHits).toBe(1);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('uses default duration (600ms) when attribute missing', () => {
|
|
165
|
-
const host = mountHost('button');
|
|
166
|
-
connectTrait(longPress, host);
|
|
167
|
-
const spy = spyEvent(host, 'long-press');
|
|
168
|
-
pointerDown(host);
|
|
169
|
-
vi.advanceTimersByTime(599);
|
|
170
|
-
expect(spy.count).toBe(0);
|
|
171
|
-
vi.advanceTimersByTime(2);
|
|
172
|
-
expect(spy.count).toBe(1);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('uses default tolerance (8px) when attribute missing', () => {
|
|
176
|
-
const host = mountHost('button', { 'data-long-press-duration': '300' });
|
|
177
|
-
connectTrait(longPress, host);
|
|
178
|
-
const cancelSpy = spyEvent(host, 'long-press-cancelled');
|
|
179
|
-
pointerDown(host, 0, 0);
|
|
180
|
-
pointerMove(host, 7, 0); // under default 8
|
|
181
|
-
expect(cancelSpy.count).toBe(0);
|
|
182
|
-
pointerMove(host, 9, 0); // over default 8
|
|
183
|
-
expect(cancelSpy.count).toBe(1);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('disconnect clears active timers + attributes mid-press', () => {
|
|
187
|
-
const host = mountHost('button', {
|
|
188
|
-
'data-long-press-duration': '200',
|
|
189
|
-
'data-long-press-progress-interval': '50',
|
|
190
|
-
});
|
|
191
|
-
const inst = connectTrait(longPress, host);
|
|
192
|
-
pointerDown(host);
|
|
193
|
-
expect(host.hasAttribute('data-long-press-active')).toBe(true);
|
|
194
|
-
inst.disconnect(host);
|
|
195
|
-
expect(host.hasAttribute('data-long-press-active')).toBe(false);
|
|
196
|
-
expect(host.hasAttribute('data-long-press-progress')).toBe(false);
|
|
197
|
-
// After disconnect, advancing time must not fire long-press.
|
|
198
|
-
const fireSpy = spyEvent(host, 'long-press');
|
|
199
|
-
vi.advanceTimersByTime(500);
|
|
200
|
-
expect(fireSpy.count).toBe(0);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('reconnect after disconnect picks up fresh state', () => {
|
|
204
|
-
const host = mountHost('button', {
|
|
205
|
-
'data-long-press-duration': '100',
|
|
206
|
-
});
|
|
207
|
-
const inst = connectTrait(longPress, host);
|
|
208
|
-
pointerDown(host);
|
|
209
|
-
inst.disconnect(host);
|
|
210
|
-
|
|
211
|
-
connectTrait(longPress, host);
|
|
212
|
-
const spy = spyEvent(host, 'long-press');
|
|
213
|
-
pointerDown(host);
|
|
214
|
-
vi.advanceTimersByTime(100);
|
|
215
|
-
expect(spy.count).toBe(1);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('schema declares the input-interaction category and full attr/event/config sets', () => {
|
|
219
|
-
expect(longPress.schema.name).toBe('long-press');
|
|
220
|
-
expect(longPress.schema.category).toBe('input-interaction');
|
|
221
|
-
expect(longPress.schema.attributes).toContain('data-long-press-active');
|
|
222
|
-
expect(longPress.schema.attributes).toContain('data-long-press-progress');
|
|
223
|
-
expect(longPress.schema.attributes).toContain('data-long-press-fired');
|
|
224
|
-
expect(longPress.schema.events).toEqual(
|
|
225
|
-
expect.arrayContaining(['long-press', 'long-press-cancelled', 'long-press-progress']),
|
|
226
|
-
);
|
|
227
|
-
expect(longPress.schema.config).toEqual(
|
|
228
|
-
expect.arrayContaining([
|
|
229
|
-
'data-long-press-duration',
|
|
230
|
-
'data-long-press-tolerance',
|
|
231
|
-
'data-long-press-progress-interval',
|
|
232
|
-
]),
|
|
233
|
-
);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('contextmenu is suppressed while a press is in flight', () => {
|
|
237
|
-
const host = mountHost('button', { 'data-long-press-duration': '200' });
|
|
238
|
-
connectTrait(longPress, host);
|
|
239
|
-
pointerDown(host);
|
|
240
|
-
const ev = new Event('contextmenu', { bubbles: true, cancelable: true });
|
|
241
|
-
host.dispatchEvent(ev);
|
|
242
|
-
expect(ev.defaultPrevented).toBe(true);
|
|
243
|
-
});
|
|
244
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { magneticHover } from './magnetic-hover.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('magnetic-hover', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('pointermove sets data-magnetic-hover-active', () => {
|
|
9
|
-
const host = mountHost();
|
|
10
|
-
connectTrait(magneticHover, host);
|
|
11
|
-
host.dispatchEvent(new PointerEvent('pointermove', { clientX: 50, clientY: 50, bubbles: true }));
|
|
12
|
-
expect(host.hasAttribute('data-magnetic-hover-active')).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('pointerleave clears active + resets translate', () => {
|
|
16
|
-
const host = mountHost();
|
|
17
|
-
connectTrait(magneticHover, host);
|
|
18
|
-
host.dispatchEvent(new PointerEvent('pointermove', { clientX: 50, clientY: 50, bubbles: true }));
|
|
19
|
-
host.dispatchEvent(new PointerEvent('pointerleave', { bubbles: true }));
|
|
20
|
-
expect(host.hasAttribute('data-magnetic-hover-active')).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('disconnect clears all listeners + active state', () => {
|
|
24
|
-
const host = mountHost();
|
|
25
|
-
const inst = connectTrait(magneticHover, host);
|
|
26
|
-
host.dispatchEvent(new PointerEvent('pointermove', { clientX: 0, clientY: 0, bubbles: true }));
|
|
27
|
-
inst.disconnect(host);
|
|
28
|
-
expect(host.hasAttribute('data-magnetic-hover-active')).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
});
|