@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,114 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { errorShake } from './error-shake.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM, wait } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('error-shake', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('schema declares animation-feedback category', () => {
|
|
9
|
-
expect(errorShake.schema.category).toBe('animation-feedback');
|
|
10
|
-
expect(errorShake.schema.events).toContain('error-shake-done');
|
|
11
|
-
expect(errorShake.schema.attributes).toContain('data-error-shake-active');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('connect → disconnect leaves no managed attribute', () => {
|
|
15
|
-
const host = mountHost();
|
|
16
|
-
const inst = connectTrait(errorShake, host);
|
|
17
|
-
inst.disconnect(host);
|
|
18
|
-
expect(host.hasAttribute('data-error-shake-active')).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('fires on validated event with detail.valid === false', async () => {
|
|
22
|
-
const host = mountHost();
|
|
23
|
-
connectTrait(errorShake, host);
|
|
24
|
-
const spy = spyEvent(host, 'error-shake-done');
|
|
25
|
-
|
|
26
|
-
host.dispatchEvent(new CustomEvent('validated', {
|
|
27
|
-
detail: { valid: false, errors: ['nope'] },
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
expect(host.hasAttribute('data-error-shake-active')).toBe(true);
|
|
31
|
-
// Wait through the default 400ms duration + a small grace window.
|
|
32
|
-
await wait(450);
|
|
33
|
-
expect(spy.count).toBe(1);
|
|
34
|
-
expect(host.hasAttribute('data-error-shake-active')).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('does not fire on validated event with detail.valid === true', async () => {
|
|
38
|
-
const host = mountHost();
|
|
39
|
-
connectTrait(errorShake, host);
|
|
40
|
-
const spy = spyEvent(host, 'error-shake-done');
|
|
41
|
-
|
|
42
|
-
host.dispatchEvent(new CustomEvent('validated', {
|
|
43
|
-
detail: { valid: true, errors: [] },
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
|
-
await wait(50);
|
|
47
|
-
expect(spy.count).toBe(0);
|
|
48
|
-
expect(host.hasAttribute('data-error-shake-active')).toBe(false);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('fires when data-validation-invalid is set (false → true)', async () => {
|
|
52
|
-
const host = mountHost();
|
|
53
|
-
connectTrait(errorShake, host);
|
|
54
|
-
const spy = spyEvent(host, 'error-shake-done');
|
|
55
|
-
|
|
56
|
-
host.setAttribute('data-validation-invalid', '');
|
|
57
|
-
// MutationObserver microtask flush.
|
|
58
|
-
await wait(0);
|
|
59
|
-
expect(host.hasAttribute('data-error-shake-active')).toBe(true);
|
|
60
|
-
await wait(450);
|
|
61
|
-
expect(spy.count).toBe(1);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('does not re-fire when data-validation-invalid stays true across mutations', async () => {
|
|
65
|
-
const host = mountHost('div', { 'data-validation-invalid': '' });
|
|
66
|
-
connectTrait(errorShake, host);
|
|
67
|
-
const spy = spyEvent(host, 'error-shake-done');
|
|
68
|
-
|
|
69
|
-
// Re-set the attribute (no false→true edge — it was already true).
|
|
70
|
-
host.setAttribute('data-validation-invalid', '');
|
|
71
|
-
await wait(20);
|
|
72
|
-
expect(spy.count).toBe(0);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('respects data-error-shake-trigger attribute toggle', async () => {
|
|
76
|
-
const host = mountHost();
|
|
77
|
-
connectTrait(errorShake, host);
|
|
78
|
-
const spy = spyEvent(host, 'error-shake-done');
|
|
79
|
-
|
|
80
|
-
host.setAttribute('data-error-shake-trigger', '');
|
|
81
|
-
await wait(0);
|
|
82
|
-
expect(host.hasAttribute('data-error-shake-active')).toBe(true);
|
|
83
|
-
await wait(450);
|
|
84
|
-
expect(spy.count).toBe(1);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('respects data-error-shake-duration config', async () => {
|
|
88
|
-
const host = mountHost('div', { 'data-error-shake-duration': '100' });
|
|
89
|
-
connectTrait(errorShake, host);
|
|
90
|
-
const spy = spyEvent(host, 'error-shake-done');
|
|
91
|
-
|
|
92
|
-
host.dispatchEvent(new CustomEvent('validated', {
|
|
93
|
-
detail: { valid: false, errors: [''] },
|
|
94
|
-
}));
|
|
95
|
-
|
|
96
|
-
// Should resolve in ~100ms, not the default 400ms.
|
|
97
|
-
await wait(150);
|
|
98
|
-
expect(spy.count).toBe(1);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('disconnect clears the keyframe <style> from the document', () => {
|
|
102
|
-
const host = mountHost();
|
|
103
|
-
const inst = connectTrait(errorShake, host);
|
|
104
|
-
host.dispatchEvent(new CustomEvent('validated', {
|
|
105
|
-
detail: { valid: false, errors: [''] },
|
|
106
|
-
}));
|
|
107
|
-
const styleCount = document.querySelectorAll('style').length;
|
|
108
|
-
inst.disconnect(host);
|
|
109
|
-
// After disconnect the keyframe style should be gone — exact count
|
|
110
|
-
// depends on what other styles exist, so we just assert non-greater.
|
|
111
|
-
expect(document.querySelectorAll('style').length).toBeLessThan(styleCount + 1);
|
|
112
|
-
expect(host.style.animation).toBe('');
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { fadePresence } from './fade-presence.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('fade-presence', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('connect attaches without throwing', () => {
|
|
9
|
-
const host = mountHost();
|
|
10
|
-
expect(() => connectTrait(fadePresence, host)).not.toThrow();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('disconnect clears managed attributes', () => {
|
|
14
|
-
const host = mountHost();
|
|
15
|
-
const inst = connectTrait(fadePresence, host);
|
|
16
|
-
inst.disconnect(host);
|
|
17
|
-
expect(host.hasAttribute('data-fade-presence-entering')).toBe(false);
|
|
18
|
-
expect(host.hasAttribute('data-fade-presence-exiting')).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { focusRestore } from './focus-restore.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
function focusableTrigger(label = 'Trigger') {
|
|
6
|
-
const btn = document.createElement('button');
|
|
7
|
-
btn.textContent = label;
|
|
8
|
-
document.body.appendChild(btn);
|
|
9
|
-
return btn;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function focusableChild(host, label = 'child', tag = 'button') {
|
|
13
|
-
const el = document.createElement(tag);
|
|
14
|
-
el.textContent = label;
|
|
15
|
-
if (tag !== 'button') el.setAttribute('tabindex', '0');
|
|
16
|
-
host.appendChild(el);
|
|
17
|
-
return el;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('focus-restore', () => {
|
|
21
|
-
beforeEach(resetDOM);
|
|
22
|
-
|
|
23
|
-
it('connect sets data-focus-restore-active', () => {
|
|
24
|
-
const host = mountHost();
|
|
25
|
-
connectTrait(focusRestore, host);
|
|
26
|
-
expect(host.hasAttribute('data-focus-restore-active')).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('disconnect clears data-focus-restore-active', () => {
|
|
30
|
-
const host = mountHost();
|
|
31
|
-
const inst = connectTrait(focusRestore, host);
|
|
32
|
-
inst.disconnect(host);
|
|
33
|
-
expect(host.hasAttribute('data-focus-restore-active')).toBe(false);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('captures activeElement on connect and restores it on disconnect', () => {
|
|
37
|
-
const trigger = focusableTrigger('Open');
|
|
38
|
-
trigger.focus();
|
|
39
|
-
expect(document.activeElement).toBe(trigger);
|
|
40
|
-
|
|
41
|
-
const host = mountHost();
|
|
42
|
-
const inst = connectTrait(focusRestore, host);
|
|
43
|
-
|
|
44
|
-
// Some other element steals focus while the surface is open.
|
|
45
|
-
const distract = focusableTrigger('Distract');
|
|
46
|
-
distract.focus();
|
|
47
|
-
expect(document.activeElement).toBe(distract);
|
|
48
|
-
|
|
49
|
-
inst.disconnect(host);
|
|
50
|
-
|
|
51
|
-
// Focus snaps back to the originally-captured trigger.
|
|
52
|
-
expect(document.activeElement).toBe(trigger);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('falls back to body when the captured target is removed from DOM', () => {
|
|
56
|
-
const trigger = focusableTrigger('Will be removed');
|
|
57
|
-
trigger.focus();
|
|
58
|
-
|
|
59
|
-
const host = mountHost();
|
|
60
|
-
const inst = connectTrait(focusRestore, host);
|
|
61
|
-
|
|
62
|
-
// Trigger gets nuked while the surface is open — common pattern when
|
|
63
|
-
// the container that opened the modal re-renders.
|
|
64
|
-
trigger.remove();
|
|
65
|
-
expect(trigger.isConnected).toBe(false);
|
|
66
|
-
|
|
67
|
-
inst.disconnect(host);
|
|
68
|
-
|
|
69
|
-
// Restore must not throw. Focus lands on body (or null) — never the
|
|
70
|
-
// removed trigger. The contract is "don't dangle" not "always lands".
|
|
71
|
-
expect(document.activeElement).not.toBe(trigger);
|
|
72
|
-
expect(document.activeElement === document.body || document.activeElement === null).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('falls back when captured target is disabled', () => {
|
|
76
|
-
const trigger = focusableTrigger('Will be disabled');
|
|
77
|
-
trigger.focus();
|
|
78
|
-
|
|
79
|
-
const host = mountHost();
|
|
80
|
-
const inst = connectTrait(focusRestore, host);
|
|
81
|
-
|
|
82
|
-
// Trigger gets disabled while surface is open.
|
|
83
|
-
trigger.setAttribute('disabled', '');
|
|
84
|
-
|
|
85
|
-
inst.disconnect(host);
|
|
86
|
-
|
|
87
|
-
// Disabled element shouldn't receive focus — fallback path engages.
|
|
88
|
-
expect(document.activeElement).not.toBe(trigger);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('dispatches focus-restored on disconnect with detail.restoredTo + detail.capturedTarget', () => {
|
|
92
|
-
const trigger = focusableTrigger('Open');
|
|
93
|
-
trigger.focus();
|
|
94
|
-
|
|
95
|
-
const host = mountHost();
|
|
96
|
-
const inst = connectTrait(focusRestore, host);
|
|
97
|
-
const spy = spyEvent(host, 'focus-restored');
|
|
98
|
-
|
|
99
|
-
inst.disconnect(host);
|
|
100
|
-
|
|
101
|
-
expect(spy.count).toBe(1);
|
|
102
|
-
expect(spy.last.capturedTarget).toBe(trigger);
|
|
103
|
-
expect(spy.last.restoredTo).toBe(trigger);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('data-focus-restore-on-mount="none" (default): does NOT move focus on connect', () => {
|
|
107
|
-
const trigger = focusableTrigger('Stay focused');
|
|
108
|
-
trigger.focus();
|
|
109
|
-
|
|
110
|
-
const host = mountHost();
|
|
111
|
-
focusableChild(host, 'inside');
|
|
112
|
-
connectTrait(focusRestore, host);
|
|
113
|
-
|
|
114
|
-
// Default behavior — captures, doesn't move focus.
|
|
115
|
-
expect(document.activeElement).toBe(trigger);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('data-focus-restore-on-mount="host": moves focus to the host on connect', () => {
|
|
119
|
-
const trigger = focusableTrigger('Open');
|
|
120
|
-
trigger.focus();
|
|
121
|
-
|
|
122
|
-
const host = mountHost('div', {
|
|
123
|
-
tabindex: '-1',
|
|
124
|
-
'data-focus-restore-on-mount': 'host',
|
|
125
|
-
});
|
|
126
|
-
connectTrait(focusRestore, host);
|
|
127
|
-
|
|
128
|
-
expect(document.activeElement).toBe(host);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('data-focus-restore-on-mount="first-focusable": focuses first tabbable descendant', () => {
|
|
132
|
-
const trigger = focusableTrigger('Open');
|
|
133
|
-
trigger.focus();
|
|
134
|
-
|
|
135
|
-
const host = mountHost('div', {
|
|
136
|
-
'data-focus-restore-on-mount': 'first-focusable',
|
|
137
|
-
});
|
|
138
|
-
const first = focusableChild(host, 'First');
|
|
139
|
-
focusableChild(host, 'Second');
|
|
140
|
-
connectTrait(focusRestore, host);
|
|
141
|
-
|
|
142
|
-
expect(document.activeElement).toBe(first);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('first-focusable: skips disabled descendants', () => {
|
|
146
|
-
const trigger = focusableTrigger('Open');
|
|
147
|
-
trigger.focus();
|
|
148
|
-
|
|
149
|
-
const host = mountHost('div', {
|
|
150
|
-
'data-focus-restore-on-mount': 'first-focusable',
|
|
151
|
-
});
|
|
152
|
-
const disabled = focusableChild(host, 'Disabled');
|
|
153
|
-
disabled.setAttribute('disabled', '');
|
|
154
|
-
const enabled = focusableChild(host, 'Enabled');
|
|
155
|
-
connectTrait(focusRestore, host);
|
|
156
|
-
|
|
157
|
-
expect(document.activeElement).toBe(enabled);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('on-mount captures BEFORE moving focus, so disconnect restores correctly', () => {
|
|
161
|
-
// Regression guard: the trait must capture activeElement before
|
|
162
|
-
// its own focus shift, otherwise disconnect would restore to the
|
|
163
|
-
// host (or first child), not the original trigger.
|
|
164
|
-
const trigger = focusableTrigger('Open');
|
|
165
|
-
trigger.focus();
|
|
166
|
-
|
|
167
|
-
const host = mountHost('div', {
|
|
168
|
-
tabindex: '-1',
|
|
169
|
-
'data-focus-restore-on-mount': 'host',
|
|
170
|
-
});
|
|
171
|
-
const inst = connectTrait(focusRestore, host);
|
|
172
|
-
expect(document.activeElement).toBe(host);
|
|
173
|
-
|
|
174
|
-
inst.disconnect(host);
|
|
175
|
-
expect(document.activeElement).toBe(trigger);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('connect → disconnect → connect cycle works', () => {
|
|
179
|
-
const trigger = focusableTrigger('Reusable');
|
|
180
|
-
trigger.focus();
|
|
181
|
-
|
|
182
|
-
const host = mountHost();
|
|
183
|
-
const inst1 = connectTrait(focusRestore, host);
|
|
184
|
-
inst1.disconnect(host);
|
|
185
|
-
expect(document.activeElement).toBe(trigger);
|
|
186
|
-
|
|
187
|
-
// Refocus and run again — fresh capture, fresh restore.
|
|
188
|
-
const trigger2 = focusableTrigger('Different trigger');
|
|
189
|
-
trigger2.focus();
|
|
190
|
-
const inst2 = connectTrait(focusRestore, host);
|
|
191
|
-
inst2.disconnect(host);
|
|
192
|
-
expect(document.activeElement).toBe(trigger2);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('schema declares the documented contract', () => {
|
|
196
|
-
expect(focusRestore.schema.name).toBe('focus-restore');
|
|
197
|
-
expect(focusRestore.schema.category).toBe('keyboard-navigation');
|
|
198
|
-
expect(focusRestore.schema.attributes).toContain('data-focus-restore-active');
|
|
199
|
-
expect(focusRestore.schema.events).toContain('focus-restored');
|
|
200
|
-
expect(focusRestore.schema.config).toContain('data-focus-restore-on-mount');
|
|
201
|
-
});
|
|
202
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { focusTrap } from './focus-trap.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
function focusableChild(host, label) {
|
|
6
|
-
const btn = document.createElement('button');
|
|
7
|
-
btn.textContent = label;
|
|
8
|
-
host.appendChild(btn);
|
|
9
|
-
return btn;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
describe('focus-trap', () => {
|
|
13
|
-
beforeEach(resetDOM);
|
|
14
|
-
|
|
15
|
-
it('connect sets data-focus-trap-active', () => {
|
|
16
|
-
const host = mountHost();
|
|
17
|
-
focusableChild(host, 'A');
|
|
18
|
-
focusableChild(host, 'B');
|
|
19
|
-
connectTrait(focusTrap, host);
|
|
20
|
-
expect(host.hasAttribute('data-focus-trap-active')).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('Escape fires focus-trap-escape event', () => {
|
|
24
|
-
const host = mountHost();
|
|
25
|
-
focusableChild(host, 'A');
|
|
26
|
-
connectTrait(focusTrap, host);
|
|
27
|
-
const spy = spyEvent(host, 'focus-trap-escape');
|
|
28
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
29
|
-
expect(spy.count).toBeGreaterThanOrEqual(1);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('disconnect clears attribute and stops listening', () => {
|
|
33
|
-
const host = mountHost();
|
|
34
|
-
focusableChild(host, 'A');
|
|
35
|
-
const inst = connectTrait(focusTrap, host);
|
|
36
|
-
const spy = spyEvent(host, 'focus-trap-escape');
|
|
37
|
-
inst.disconnect(host);
|
|
38
|
-
expect(host.hasAttribute('data-focus-trap-active')).toBe(false);
|
|
39
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
40
|
-
expect(spy.count).toBe(0);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { focusable } from './focusable.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('focusable', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('keyboard focus → sets data-focusable-keyboard', () => {
|
|
9
|
-
const host = mountHost();
|
|
10
|
-
connectTrait(focusable, host);
|
|
11
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
12
|
-
expect(host.hasAttribute('data-focusable-keyboard')).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('mouse focus → does NOT set data-focusable-keyboard', () => {
|
|
16
|
-
const host = mountHost();
|
|
17
|
-
connectTrait(focusable, host);
|
|
18
|
-
host.dispatchEvent(new MouseEvent('mousedown'));
|
|
19
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
20
|
-
expect(host.hasAttribute('data-focusable-keyboard')).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('blur clears the attribute', () => {
|
|
24
|
-
const host = mountHost();
|
|
25
|
-
connectTrait(focusable, host);
|
|
26
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
27
|
-
expect(host.hasAttribute('data-focusable-keyboard')).toBe(true);
|
|
28
|
-
host.dispatchEvent(new FocusEvent('blur'));
|
|
29
|
-
expect(host.hasAttribute('data-focusable-keyboard')).toBe(false);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('mousedown→blur→keyboard-focus correctly distinguishes the second visit', () => {
|
|
33
|
-
const host = mountHost();
|
|
34
|
-
connectTrait(focusable, host);
|
|
35
|
-
// First visit via mouse
|
|
36
|
-
host.dispatchEvent(new MouseEvent('mousedown'));
|
|
37
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
38
|
-
expect(host.hasAttribute('data-focusable-keyboard')).toBe(false);
|
|
39
|
-
host.dispatchEvent(new FocusEvent('blur'));
|
|
40
|
-
// Second visit via keyboard
|
|
41
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
42
|
-
expect(host.hasAttribute('data-focusable-keyboard')).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('disconnect clears attribute and removes listeners', () => {
|
|
46
|
-
const host = mountHost();
|
|
47
|
-
const inst = connectTrait(focusable, host);
|
|
48
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
49
|
-
expect(host.hasAttribute('data-focusable-keyboard')).toBe(true);
|
|
50
|
-
inst.disconnect(host);
|
|
51
|
-
expect(host.hasAttribute('data-focusable-keyboard')).toBe(false);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { glowFocus } from './glow-focus.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('glow-focus', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('focus sets data-glow-focus-active and applies box-shadow', () => {
|
|
9
|
-
const host = mountHost();
|
|
10
|
-
connectTrait(glowFocus, host);
|
|
11
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
12
|
-
expect(host.hasAttribute('data-glow-focus-active')).toBe(true);
|
|
13
|
-
expect(host.style.boxShadow).toContain('rgba(59, 130, 246');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('blur clears active + restores box-shadow', () => {
|
|
17
|
-
const host = mountHost();
|
|
18
|
-
connectTrait(glowFocus, host);
|
|
19
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
20
|
-
host.dispatchEvent(new FocusEvent('blur'));
|
|
21
|
-
expect(host.hasAttribute('data-glow-focus-active')).toBe(false);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('disconnect mid-focus clears active', () => {
|
|
25
|
-
const host = mountHost();
|
|
26
|
-
const inst = connectTrait(glowFocus, host);
|
|
27
|
-
host.dispatchEvent(new FocusEvent('focus'));
|
|
28
|
-
inst.disconnect(host);
|
|
29
|
-
expect(host.hasAttribute('data-glow-focus-active')).toBe(false);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { gradientShift } from './gradient-shift.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('gradient-shift', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('connect sets data-gradient-shift-active', () => {
|
|
9
|
-
const host = mountHost();
|
|
10
|
-
connectTrait(gradientShift, host);
|
|
11
|
-
expect(host.hasAttribute('data-gradient-shift-active')).toBe(true);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('disconnect clears active + reverts inline styles', () => {
|
|
15
|
-
const host = mountHost();
|
|
16
|
-
const inst = connectTrait(gradientShift, host);
|
|
17
|
-
inst.disconnect(host);
|
|
18
|
-
expect(host.hasAttribute('data-gradient-shift-active')).toBe(false);
|
|
19
|
-
expect(host.style.animation).toBe('');
|
|
20
|
-
expect(host.style.backgroundSize).toBe('');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
|
-
import { hapticFeedback } from './haptic-feedback.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('haptic-feedback', () => {
|
|
6
|
-
let originalVibrate;
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
resetDOM();
|
|
10
|
-
originalVibrate = navigator.vibrate;
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (originalVibrate) navigator.vibrate = originalVibrate;
|
|
15
|
-
else delete navigator.vibrate;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('without navigator.vibrate: no-op cleanup, no crash', () => {
|
|
19
|
-
delete navigator.vibrate;
|
|
20
|
-
const host = mountHost();
|
|
21
|
-
expect(() => connectTrait(hapticFeedback, host)).not.toThrow();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('with navigator.vibrate: press fires vibrate with parsed pattern', () => {
|
|
25
|
-
const vibrate = vi.fn();
|
|
26
|
-
navigator.vibrate = vibrate;
|
|
27
|
-
const host = mountHost('div', { 'data-haptic-pattern': '20,10,20' });
|
|
28
|
-
connectTrait(hapticFeedback, host);
|
|
29
|
-
expect(host.hasAttribute('data-haptic-feedback-active')).toBe(true);
|
|
30
|
-
host.dispatchEvent(new CustomEvent('press', { bubbles: true }));
|
|
31
|
-
expect(vibrate).toHaveBeenCalledWith([20, 10, 20]);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('default pattern is [10] when no data-haptic-pattern set', () => {
|
|
35
|
-
const vibrate = vi.fn();
|
|
36
|
-
navigator.vibrate = vibrate;
|
|
37
|
-
const host = mountHost();
|
|
38
|
-
connectTrait(hapticFeedback, host);
|
|
39
|
-
host.dispatchEvent(new CustomEvent('press', { bubbles: true }));
|
|
40
|
-
expect(vibrate).toHaveBeenCalledWith([10]);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('disconnect removes the press listener', () => {
|
|
44
|
-
const vibrate = vi.fn();
|
|
45
|
-
navigator.vibrate = vibrate;
|
|
46
|
-
const host = mountHost();
|
|
47
|
-
const inst = connectTrait(hapticFeedback, host);
|
|
48
|
-
inst.disconnect(host);
|
|
49
|
-
host.dispatchEvent(new CustomEvent('press', { bubbles: true }));
|
|
50
|
-
expect(vibrate).not.toHaveBeenCalled();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { hotkey } from './hotkey.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('hotkey', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('fires "hotkey" event on matching combo', () => {
|
|
9
|
-
const host = mountHost('div', { 'data-hotkey': 'ctrl+k' });
|
|
10
|
-
connectTrait(hotkey, host);
|
|
11
|
-
const spy = spyEvent(host, 'hotkey');
|
|
12
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', ctrlKey: true }));
|
|
13
|
-
expect(spy.count).toBe(1);
|
|
14
|
-
expect(spy.last.combo).toBe('ctrl+k');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('does not fire on non-matching combo', () => {
|
|
18
|
-
const host = mountHost('div', { 'data-hotkey': 'ctrl+k' });
|
|
19
|
-
connectTrait(hotkey, host);
|
|
20
|
-
const spy = spyEvent(host, 'hotkey');
|
|
21
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'j', ctrlKey: true }));
|
|
22
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'k' }));
|
|
23
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', altKey: true }));
|
|
24
|
-
expect(spy.count).toBe(0);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('shift modifier required when declared', () => {
|
|
28
|
-
const host = mountHost('div', { 'data-hotkey': 'shift+a' });
|
|
29
|
-
connectTrait(hotkey, host);
|
|
30
|
-
const spy = spyEvent(host, 'hotkey');
|
|
31
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
|
32
|
-
expect(spy.count).toBe(0);
|
|
33
|
-
host.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', shiftKey: true }));
|
|
34
|
-
expect(spy.count).toBe(1);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('global mode listens on document', () => {
|
|
38
|
-
const host = mountHost('div', { 'data-hotkey': 'ctrl+/', 'data-hotkey-global': '' });
|
|
39
|
-
connectTrait(hotkey, host);
|
|
40
|
-
const spy = spyEvent(host, 'hotkey');
|
|
41
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: '/', ctrlKey: true }));
|
|
42
|
-
expect(spy.count).toBe(1);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('non-global mode does not listen on document', () => {
|
|
46
|
-
const host = mountHost('div', { 'data-hotkey': 'ctrl+/' });
|
|
47
|
-
connectTrait(hotkey, host);
|
|
48
|
-
const spy = spyEvent(host, 'hotkey');
|
|
49
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: '/', ctrlKey: true }));
|
|
50
|
-
expect(spy.count).toBe(0);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('disconnect removes the keydown listener', () => {
|
|
54
|
-
const host = mountHost('div', { 'data-hotkey': 'ctrl+k', 'data-hotkey-global': '' });
|
|
55
|
-
const inst = connectTrait(hotkey, host);
|
|
56
|
-
const spy = spyEvent(host, 'hotkey');
|
|
57
|
-
inst.disconnect(host);
|
|
58
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', ctrlKey: true }));
|
|
59
|
-
expect(spy.count).toBe(0);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { hoverable } from './hoverable.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('hoverable', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('pointerenter sets data-hoverable-hover; pointerleave clears it', () => {
|
|
9
|
-
const host = mountHost();
|
|
10
|
-
connectTrait(hoverable, host);
|
|
11
|
-
host.dispatchEvent(new PointerEvent('pointerenter'));
|
|
12
|
-
expect(host.hasAttribute('data-hoverable-hover')).toBe(true);
|
|
13
|
-
host.dispatchEvent(new PointerEvent('pointerleave'));
|
|
14
|
-
expect(host.hasAttribute('data-hoverable-hover')).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('disconnect clears attribute even mid-hover', () => {
|
|
18
|
-
const host = mountHost();
|
|
19
|
-
const inst = connectTrait(hoverable, host);
|
|
20
|
-
host.dispatchEvent(new PointerEvent('pointerenter'));
|
|
21
|
-
inst.disconnect(host);
|
|
22
|
-
expect(host.hasAttribute('data-hoverable-hover')).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { inertiaDrag } from './inertia-drag.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
function pointer(host, type, x, y) {
|
|
6
|
-
host.dispatchEvent(new PointerEvent(type, { clientX: x, clientY: y, pointerId: 1, bubbles: true }));
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe('inertia-drag', () => {
|
|
10
|
-
beforeEach(resetDOM);
|
|
11
|
-
|
|
12
|
-
it('connect attaches without throwing', () => {
|
|
13
|
-
const host = mountHost();
|
|
14
|
-
expect(() => connectTrait(inertiaDrag, host)).not.toThrow();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('drag sequence does not throw + disconnect cleans up', () => {
|
|
18
|
-
const host = mountHost();
|
|
19
|
-
const inst = connectTrait(inertiaDrag, host);
|
|
20
|
-
pointer(host, 'pointerdown', 0, 0);
|
|
21
|
-
pointer(host, 'pointermove', 30, 30);
|
|
22
|
-
pointer(host, 'pointerup', 30, 30);
|
|
23
|
-
expect(() => inst.disconnect(host)).not.toThrow();
|
|
24
|
-
expect(host.hasAttribute('data-inertia-drag-coasting')).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('respects [disabled] — pointerdown is a no-op', () => {
|
|
28
|
-
const host = mountHost('div', { disabled: '' });
|
|
29
|
-
connectTrait(inertiaDrag, host);
|
|
30
|
-
pointer(host, 'pointerdown', 0, 0);
|
|
31
|
-
expect(host.hasAttribute('data-inertia-drag-coasting')).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
});
|