@adia-ai/web-components 0.2.0 → 0.2.2

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 (102) hide show
  1. package/README.md +5 -2
  2. package/components/chat-thread/chat-input.css +107 -19
  3. package/components/index.js +2 -1
  4. package/components/table/cell-types.js +1 -1
  5. package/core/element.js +63 -2
  6. package/package.json +1 -3
  7. package/styles/colors/semantics.css +4 -4
  8. package/styles/components.css +1 -1
  9. package/traits/_catalog.json +509 -0
  10. package/traits/_motion.js +57 -0
  11. package/traits/_smoke.test.js +111 -0
  12. package/traits/_test-helpers.js +82 -0
  13. package/traits/active-state.js +2 -0
  14. package/traits/active-state.test.js +28 -0
  15. package/traits/anchor-positioning.js +2 -0
  16. package/traits/anchor-positioning.test.js +49 -0
  17. package/traits/attention-pulse.js +11 -0
  18. package/traits/attention-pulse.test.js +26 -0
  19. package/traits/confetti-burst.js +27 -0
  20. package/traits/confetti-burst.test.js +38 -0
  21. package/traits/confetti.js +18 -0
  22. package/traits/confetti.test.js +27 -0
  23. package/traits/count-up.js +17 -0
  24. package/traits/count-up.test.js +54 -0
  25. package/traits/declarative.test.js +138 -0
  26. package/traits/define.js +43 -3
  27. package/traits/dirty-state.js +2 -0
  28. package/traits/dirty-state.test.js +45 -0
  29. package/traits/drag-ghost.js +2 -0
  30. package/traits/drag-ghost.test.js +19 -0
  31. package/traits/draggable.js +2 -0
  32. package/traits/draggable.test.js +60 -0
  33. package/traits/fade-presence.js +2 -0
  34. package/traits/fade-presence.test.js +20 -0
  35. package/traits/focus-trap.js +2 -0
  36. package/traits/focus-trap.test.js +42 -0
  37. package/traits/focusable.js +2 -0
  38. package/traits/focusable.test.js +53 -0
  39. package/traits/glow-focus.js +6 -1
  40. package/traits/glow-focus.test.js +31 -0
  41. package/traits/gradient-shift.js +9 -0
  42. package/traits/gradient-shift.test.js +22 -0
  43. package/traits/haptic-feedback.js +2 -0
  44. package/traits/haptic-feedback.test.js +52 -0
  45. package/traits/hotkey.js +2 -0
  46. package/traits/hotkey.test.js +61 -0
  47. package/traits/hoverable.js +2 -0
  48. package/traits/hoverable.test.js +24 -0
  49. package/traits/index.js +50 -37
  50. package/traits/inertia-drag.js +2 -0
  51. package/traits/inertia-drag.test.js +33 -0
  52. package/traits/intersection-observer.js +2 -0
  53. package/traits/intersection-observer.test.js +38 -0
  54. package/traits/keyboard-nav.js +2 -0
  55. package/traits/keyboard-nav.test.js +41 -0
  56. package/traits/magnetic-hover.js +8 -0
  57. package/traits/magnetic-hover.test.js +30 -0
  58. package/traits/noise-texture.js +2 -0
  59. package/traits/noise-texture.test.js +20 -0
  60. package/traits/parallax.js +9 -0
  61. package/traits/parallax.test.js +26 -0
  62. package/traits/portal.js +2 -0
  63. package/traits/portal.test.js +30 -0
  64. package/traits/pressable.js +2 -0
  65. package/traits/pressable.test.js +73 -0
  66. package/traits/resettable.js +40 -0
  67. package/traits/resettable.test.js +67 -0
  68. package/traits/resizable.js +2 -0
  69. package/traits/resizable.test.js +20 -0
  70. package/traits/resize-observer.js +2 -0
  71. package/traits/resize-observer.test.js +38 -0
  72. package/traits/ripple.js +9 -0
  73. package/traits/ripple.test.js +32 -0
  74. package/traits/roving-tabindex.js +2 -0
  75. package/traits/roving-tabindex.test.js +28 -0
  76. package/traits/scale-press.js +2 -0
  77. package/traits/scale-press.test.js +39 -0
  78. package/traits/scroll-lock.js +2 -0
  79. package/traits/scroll-lock.test.js +45 -0
  80. package/traits/shimmer-loading.js +20 -0
  81. package/traits/shimmer-loading.test.js +43 -0
  82. package/traits/snap-to-grid.js +2 -0
  83. package/traits/snap-to-grid.test.js +40 -0
  84. package/traits/sound-feedback.js +2 -0
  85. package/traits/sound-feedback.test.js +26 -0
  86. package/traits/spring-animate.js +2 -0
  87. package/traits/spring-animate.test.js +28 -0
  88. package/traits/tilt-hover.js +8 -0
  89. package/traits/tilt-hover.test.js +32 -0
  90. package/traits/tossable.js +2 -0
  91. package/traits/tossable.test.js +31 -0
  92. package/traits/traits-host.js +53 -0
  93. package/traits/traits-host.test.js +73 -0
  94. package/traits/typeahead.js +2 -0
  95. package/traits/typeahead.test.js +38 -0
  96. package/traits/typewriter.js +17 -0
  97. package/traits/typewriter.test.js +47 -0
  98. package/traits/validation.js +2 -0
  99. package/traits/validation.test.js +93 -0
  100. package/a2ui/index.js +0 -25
  101. /package/components/stat/{stat.css → stat-ui.css} +0 -0
  102. /package/components/stat/{stat.js → stat-ui.js} +0 -0
@@ -4,6 +4,8 @@ const BUFFER_TIMEOUT = 500;
4
4
 
5
5
  export const typeahead = defineTrait({
6
6
  name: 'typeahead',
7
+ category: 'keyboard-navigation',
8
+ description: 'Incremental search within a collection',
7
9
  attributes: ['data-typeahead-match'],
8
10
  events: ['typeahead-match'],
9
11
  config: [],
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { typeahead } from './typeahead.js';
3
+ import { mountHost, connectTrait, spyEvent, resetDOM, wait } from './_test-helpers.js';
4
+
5
+ function child(host, text) {
6
+ const li = document.createElement('div');
7
+ li.textContent = text;
8
+ li.setAttribute('role', 'option');
9
+ host.appendChild(li);
10
+ return li;
11
+ }
12
+
13
+ describe('typeahead', () => {
14
+ beforeEach(resetDOM);
15
+
16
+ it('typing first letter matches a child by textContent', async () => {
17
+ const host = mountHost();
18
+ child(host, 'Apple');
19
+ child(host, 'Banana');
20
+ child(host, 'Cherry');
21
+ connectTrait(typeahead, host);
22
+ const spy = spyEvent(host, 'typeahead-match');
23
+ host.dispatchEvent(new KeyboardEvent('keydown', { key: 'b' }));
24
+ await wait(20);
25
+ expect(spy.count).toBeGreaterThanOrEqual(1);
26
+ expect(spy.last.element?.textContent).toBe('Banana');
27
+ });
28
+
29
+ it('disconnect removes the keydown listener', () => {
30
+ const host = mountHost();
31
+ child(host, 'Apple');
32
+ const inst = connectTrait(typeahead, host);
33
+ const spy = spyEvent(host, 'typeahead-match');
34
+ inst.disconnect(host);
35
+ host.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
36
+ expect(spy.count).toBe(0);
37
+ });
38
+ });
@@ -1,13 +1,30 @@
1
1
  import { defineTrait } from './define.js';
2
+ import { prefersReducedMotion } from './_motion.js';
2
3
 
3
4
  export const typewriter = defineTrait({
4
5
  name: 'typewriter',
6
+ category: 'audio-haptics-sensory',
7
+ description: 'Animated text reveal character by character',
5
8
  attributes: ['data-typewriter-active'],
6
9
  events: ['typewriter-done'],
7
10
  config: ['data-typewriter-speed'],
8
11
  setup({ host }) {
9
12
  const speed = parseInt(host.getAttribute('data-typewriter-speed'), 10) || 50;
10
13
  const originalText = host.textContent;
14
+
15
+ // Reduced-motion: show the full text immediately and fire the done event.
16
+ if (prefersReducedMotion()) {
17
+ host.setAttribute('data-typewriter-active', '');
18
+ queueMicrotask(() => {
19
+ host.removeAttribute('data-typewriter-active');
20
+ host.dispatchEvent(new CustomEvent('typewriter-done', { bubbles: true }));
21
+ });
22
+ return () => {
23
+ host.textContent = originalText;
24
+ host.removeAttribute('data-typewriter-active');
25
+ };
26
+ }
27
+
11
28
  let index = 0;
12
29
  let timerId = null;
13
30
 
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { typewriter } from './typewriter.js';
3
+ import { mountHost, connectTrait, spyEvent, resetDOM, wait } from './_test-helpers.js';
4
+
5
+ describe('typewriter', () => {
6
+ beforeEach(resetDOM);
7
+
8
+ it('starts with empty content + active attribute on connect', () => {
9
+ const host = mountHost();
10
+ host.textContent = 'Hello';
11
+ connectTrait(typewriter, host, { speed: 5 });
12
+ host.setAttribute('data-typewriter-speed', '5');
13
+ // Re-connect after setting speed (setup reads on connect)
14
+ typewriter().connect(host);
15
+ expect(host.hasAttribute('data-typewriter-active')).toBe(true);
16
+ });
17
+
18
+ it('reveals full text and fires typewriter-done', async () => {
19
+ const host = mountHost();
20
+ host.textContent = 'Hi';
21
+ host.setAttribute('data-typewriter-speed', '5');
22
+ const spy = spyEvent(host, 'typewriter-done');
23
+ typewriter().connect(host);
24
+ await wait(80);
25
+ expect(host.textContent).toBe('Hi');
26
+ expect(spy.count).toBeGreaterThanOrEqual(1);
27
+ });
28
+
29
+ it('disconnect mid-reveal restores original text', async () => {
30
+ const host = mountHost();
31
+ host.textContent = 'A long sentence to reveal';
32
+ host.setAttribute('data-typewriter-speed', '20');
33
+ const inst = typewriter();
34
+ inst.connect(host);
35
+ await wait(40);
36
+ inst.disconnect(host);
37
+ expect(host.textContent).toBe('A long sentence to reveal');
38
+ expect(host.hasAttribute('data-typewriter-active')).toBe(false);
39
+ });
40
+
41
+ it('respects data-typewriter-speed default (50ms)', () => {
42
+ const host = mountHost();
43
+ host.textContent = 'X';
44
+ typewriter().connect(host);
45
+ expect(host.hasAttribute('data-typewriter-active')).toBe(true);
46
+ });
47
+ });
@@ -74,6 +74,8 @@ function validateValue(value, rules) {
74
74
 
75
75
  export const validation = defineTrait({
76
76
  name: 'validation',
77
+ category: 'forms-data',
78
+ description: 'Validation rules: required, minlength, pattern, email',
77
79
  attributes: ['data-validation-invalid', 'data-validation-valid', 'data-validation-message'],
78
80
  events: ['validated'],
79
81
  config: ['data-validate'],
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { validation } from './validation.js';
3
+ import { mountHost, connectTrait, spyEvent, resetDOM } from './_test-helpers.js';
4
+
5
+ function setValue(host, value) {
6
+ host.value = value;
7
+ host.dispatchEvent(new Event('input'));
8
+ }
9
+
10
+ describe('validation', () => {
11
+ beforeEach(resetDOM);
12
+
13
+ it('required: empty value is invalid', () => {
14
+ const host = mountHost('input', { 'data-validate': 'required' });
15
+ connectTrait(validation, host);
16
+ setValue(host, '');
17
+ expect(host.hasAttribute('data-validation-invalid')).toBe(true);
18
+ expect(host.getAttribute('data-validation-message')).toBe('This field is required');
19
+ });
20
+
21
+ it('required: non-empty value is valid', () => {
22
+ const host = mountHost('input', { 'data-validate': 'required' });
23
+ connectTrait(validation, host);
24
+ setValue(host, 'hello');
25
+ expect(host.hasAttribute('data-validation-valid')).toBe(true);
26
+ expect(host.hasAttribute('data-validation-invalid')).toBe(false);
27
+ });
28
+
29
+ it('email: rejects malformed input', () => {
30
+ const host = mountHost('input', { 'data-validate': 'email' });
31
+ connectTrait(validation, host);
32
+ setValue(host, 'not-an-email');
33
+ expect(host.getAttribute('data-validation-message')).toBe('Invalid email address');
34
+ });
35
+
36
+ it('email: accepts well-formed addresses', () => {
37
+ const host = mountHost('input', { 'data-validate': 'email' });
38
+ connectTrait(validation, host);
39
+ setValue(host, 'user@example.com');
40
+ expect(host.hasAttribute('data-validation-valid')).toBe(true);
41
+ });
42
+
43
+ it('minlength: enforces minimum character count', () => {
44
+ const host = mountHost('input', { 'data-validate': 'minlength:5' });
45
+ connectTrait(validation, host);
46
+ setValue(host, 'hi');
47
+ expect(host.getAttribute('data-validation-message')).toBe('Minimum length is 5');
48
+ setValue(host, 'hello!');
49
+ expect(host.hasAttribute('data-validation-valid')).toBe(true);
50
+ });
51
+
52
+ it('pattern: enforces regex match', () => {
53
+ const host = mountHost('input', { 'data-validate': 'pattern:^[A-Z]+$' });
54
+ connectTrait(validation, host);
55
+ setValue(host, 'lower');
56
+ expect(host.hasAttribute('data-validation-invalid')).toBe(true);
57
+ setValue(host, 'UPPER');
58
+ expect(host.hasAttribute('data-validation-valid')).toBe(true);
59
+ });
60
+
61
+ it('combined rules: required + email both checked', () => {
62
+ const host = mountHost('input', { 'data-validate': 'required, email' });
63
+ connectTrait(validation, host);
64
+ const spy = spyEvent(host, 'validated');
65
+ setValue(host, '');
66
+ expect(spy.last.errors).toContain('This field is required');
67
+ setValue(host, 'bad');
68
+ expect(spy.last.errors).toContain('Invalid email address');
69
+ setValue(host, 'ok@example.com');
70
+ expect(spy.last.valid).toBe(true);
71
+ });
72
+
73
+ it('dispatches "validated" event with detail.valid and detail.errors', () => {
74
+ const host = mountHost('input', { 'data-validate': 'required' });
75
+ connectTrait(validation, host);
76
+ const spy = spyEvent(host, 'validated');
77
+ setValue(host, '');
78
+ expect(spy.count).toBe(1);
79
+ expect(spy.last.valid).toBe(false);
80
+ expect(spy.last.errors.length).toBe(1);
81
+ });
82
+
83
+ it('disconnect clears all validation attributes', () => {
84
+ const host = mountHost('input', { 'data-validate': 'required' });
85
+ const inst = connectTrait(validation, host);
86
+ setValue(host, '');
87
+ expect(host.hasAttribute('data-validation-invalid')).toBe(true);
88
+ inst.disconnect(host);
89
+ expect(host.hasAttribute('data-validation-invalid')).toBe(false);
90
+ expect(host.hasAttribute('data-validation-valid')).toBe(false);
91
+ expect(host.hasAttribute('data-validation-message')).toBe(false);
92
+ });
93
+ });
package/a2ui/index.js DELETED
@@ -1,25 +0,0 @@
1
- /**
2
- * DEPRECATED — the `@adia-ai/web-components/a2ui` subpath.
3
- *
4
- * The A2UI runtime was extracted into its own package `@adia-ai/a2ui-utils`
5
- * in 0.0.4. This file is a temporary re-export shim for one release so
6
- * existing consumers don't break on upgrade. It will be removed in 0.1.0.
7
- *
8
- * Migrate:
9
- * - import { A2UIRenderer } from '@adia-ai/web-components/a2ui';
10
- * + import { A2UIRenderer } from '@adia-ai/a2ui-utils';
11
- *
12
- * The declarative `<a2ui-root>` custom element also moved — it now lives
13
- * in the patterns directory. Import it via:
14
- * import '@adia-ai/web-modules/runtime/a2ui-root/a2ui-root.js';
15
- */
16
-
17
- if (typeof console !== 'undefined' && !globalThis.__a2ui_subpath_warned) {
18
- globalThis.__a2ui_subpath_warned = true;
19
- console.warn(
20
- '[@adia-ai/web-components] The `/a2ui` subpath export is deprecated and will be removed in 0.1.0. ' +
21
- 'Import from `@adia-ai/a2ui-utils` instead.'
22
- );
23
- }
24
-
25
- export * from '@adia-ai/a2ui-utils';
File without changes
File without changes