@adia-ai/web-components 0.2.2 → 0.2.3

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.
@@ -18,7 +18,11 @@
18
18
  --agent-trace-padding-y: var(--a-space-2);
19
19
  /* Component-intrinsic measurement; no --a-space-* equivalent */
20
20
  --agent-trace-dot-size: 6px;
21
- --agent-trace-row-label-col: 80px;
21
+ /* STAGE column width — `max-content` lets the longest label set the
22
+ track so multi-word labels ("Rows returned", "Query duration",
23
+ "Drift vs. SFDC") stay on one line; the `7rem` floor stops the
24
+ column from collapsing when only short labels are present. */
25
+ --agent-trace-row-label-col: minmax(7rem, max-content);
22
26
  /* Shared across every detail DL so values tabulate at the same x. */
23
27
  --agent-trace-detail-label-col: 9rem;
24
28
 
@@ -113,6 +117,17 @@
113
117
  right of "7%"'s detail column. Subgrid pulls the track widths up
114
118
  to the parent so every row sees the same column stops. */
115
119
  [data-trace-rows] {
120
+ /* Track plan:
121
+ [STAGE max-content] ← label column; widened from 80px so multi-word
122
+ labels ("Rows returned", "Query duration",
123
+ "Drift vs. SFDC") stay on one line
124
+ [SCORE max-content] ← value column, right-aligned
125
+ [DETAIL 1fr] ← detail column, right-aligned content
126
+ The DETAIL column still takes the leftover width, but its text is
127
+ right-anchored ([data-trace-aux] { text-align: end }) so the
128
+ previously-empty right edge becomes the alignment edge for detail
129
+ text. The whitespace between SCORE and DETAIL reads as breathing
130
+ room between key-value and qualifier rather than dead air. */
116
131
  --trace-row-cols: var(--agent-trace-row-label-col) max-content 1fr;
117
132
  display: grid;
118
133
  grid-template-columns: var(--trace-row-cols);
@@ -157,8 +172,9 @@
157
172
  color: var(--agent-trace-fg-subtle);
158
173
  }
159
174
 
160
- [data-trace-header]:nth-of-type(2) {
161
- text-align: right;
175
+ [data-trace-header]:nth-of-type(2),
176
+ [data-trace-header]:nth-of-type(3) {
177
+ text-align: end;
162
178
  }
163
179
 
164
180
  /* Rows */
@@ -198,10 +214,15 @@
198
214
  }
199
215
 
200
216
  [data-trace-aux] {
217
+ /* Right-anchor the detail text so it sits flush with the right edge
218
+ of the row. Without this, the 1fr detail column left-aligns its
219
+ content (typically a 1-3 word qualifier like "warehouse" or
220
+ "reconciled") and the rest of the column reads as dead width. */
201
221
  color: var(--agent-trace-fg-muted);
202
222
  overflow: hidden;
203
223
  text-overflow: ellipsis;
204
224
  white-space: nowrap;
225
+ text-align: end;
205
226
  }
206
227
 
207
228
  /* Chevron in the trailing column */
@@ -92,20 +92,62 @@
92
92
  @scope (list-item-ui) {
93
93
  :where(:scope) {
94
94
  /* ── Layout ── */
95
- --list-item-inset: var(--a-inset);
95
+ --list-item-inset: var(--a-inset);
96
+ --list-item-gap-column: var(--a-space-3);
97
+ --list-item-gap-row: var(--a-space-1);
96
98
 
97
99
  /* ── Colors ── */
98
100
  --list-item-text-color: var(--a-fg);
101
+ --list-item-desc-color: var(--a-fg-muted);
102
+ --list-item-icon-color: var(--a-fg-muted);
99
103
 
100
104
  /* ── Typography ── */
101
- --list-item-font-size: var(--a-ui-size);
105
+ --list-item-font-size: var(--a-ui-size);
106
+ --list-item-desc-font-size: var(--a-ui-sm);
102
107
  }
103
108
 
109
+ /* Anatomy:
110
+ col 1 = icon (auto width; collapses to 0 when no icon)
111
+ col 2 = content stack (text on row 1, description on row 2)
112
+ The icon spans both rows + centers vertically with the stack.
113
+ `[slot="content"]` (used when consumer provides custom rendering)
114
+ spans all columns. */
104
115
  :scope {
105
116
  box-sizing: border-box;
106
- display: block;
117
+ display: grid;
118
+ grid-template-columns: auto 1fr;
119
+ column-gap: var(--list-item-gap-column);
120
+ row-gap: var(--list-item-gap-row);
107
121
  font-size: var(--list-item-font-size);
108
122
  color: var(--list-item-text-color);
109
123
  line-height: 1.4;
110
124
  }
125
+
126
+ :scope > [slot="icon"] {
127
+ grid-column: 1;
128
+ grid-row: 1 / -1;
129
+ align-self: center;
130
+ color: var(--list-item-icon-color);
131
+ }
132
+
133
+ :scope > [slot="text"] {
134
+ grid-column: 2;
135
+ grid-row: 1;
136
+ }
137
+
138
+ :scope > [slot="description"] {
139
+ grid-column: 2;
140
+ grid-row: 2;
141
+ color: var(--list-item-desc-color);
142
+ font-size: var(--list-item-desc-font-size);
143
+ line-height: 1.3;
144
+ }
145
+
146
+ /* Custom-content escape hatch — consumer authored a [slot="content"]
147
+ child, the auto-stamp early-returned, and the consumer owns the
148
+ full body. Span all columns so the consumer's layout isn't boxed
149
+ into the content column. */
150
+ :scope > [slot="content"] {
151
+ grid-column: 1 / -1;
152
+ }
111
153
  }
@@ -42,7 +42,7 @@
42
42
  "default": ""
43
43
  },
44
44
  "variant": {
45
- "description": "Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children — matches the prior <section-nav-group-ui>. When the parent <nav-ui> carries variant=\"section\", this group inherits it via CSS cascade unless an explicit variant is set on the group.",
45
+ "description": "Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children. When the parent <nav-ui> carries variant=\"section\", this group inherits it via CSS cascade unless an explicit variant is set on the group.",
46
46
  "type": "string",
47
47
  "enum": [
48
48
  "",
@@ -264,11 +264,11 @@ nav-group-ui [slot="popover"] [role="option"][aria-selected="true"]::before {
264
264
  }
265
265
 
266
266
  /* ── Section variant — groups render as kicker labels ──
267
- The prior <section-nav-group-ui> defaulted to collapsible:false
268
- (header was a label, children always visible). The consolidated
269
- nav-group-ui defaults collapsible:true (the primary-variant default).
270
- Section variant restores the old kicker rendering so subnav rails
271
- show all children without an explicit [open] toggle.
267
+ Primary variant defaults to collapsible:true (caret-toggleable).
268
+ Section variant flips to collapsible:false-shape header is a
269
+ static kicker label, children always visible — so subnav rails
270
+ show their full structure without requiring an explicit [open]
271
+ toggle.
272
272
 
273
273
  Two ways to enable: either
274
274
  (a) `<nav-group-ui variant="section">` directly, or
@@ -39,7 +39,7 @@ props:
39
39
  type: string
40
40
  default: ''
41
41
  enum: ['', section]
42
- description: "Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children — matches the prior <section-nav-group-ui>. When the parent <nav-ui> carries variant=\"section\", this group inherits it via CSS cascade unless an explicit variant is set on the group."
42
+ description: "Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children. When the parent <nav-ui> carries variant=\"section\", this group inherits it via CSS cascade unless an explicit variant is set on the group."
43
43
 
44
44
  events:
45
45
  group-toggle:
@@ -47,7 +47,7 @@
47
47
  "default": ""
48
48
  },
49
49
  "variant": {
50
- "description": "Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected — matching the prior <section-nav-item-ui>. When the parent <nav-ui> carries variant=\"section\", this item inherits it via CSS cascade unless an explicit variant is set.",
50
+ "description": "Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected. When the parent <nav-ui> carries variant=\"section\", this item inherits it via CSS cascade unless an explicit variant is set.",
51
51
  "type": "string",
52
52
  "enum": [
53
53
  "",
@@ -162,10 +162,9 @@ nav-item-ui[selected] [slot="icon"]:empty::before {
162
162
  }
163
163
 
164
164
  /* ── Section variant — items render flat (no icon space, no indent) ──
165
- The prior <section-nav-item-ui> didn't reserve space for an absent icon
166
- and used a left-edge accent bar for the selected state. Restore that
167
- shape so subnav rails read as plain links rather than a primary-style
168
- row with a hidden icon slot.
165
+ Section-variant rails read as plain links rather than primary-style
166
+ rows: no reserved icon space when absent, left-edge accent bar for
167
+ the selected state instead of an in-icon accent.
169
168
 
170
169
  Two ways to enable: either
171
170
  (a) `<nav-item-ui variant="section">` directly, or
@@ -43,7 +43,7 @@ props:
43
43
  type: string
44
44
  default: ''
45
45
  enum: ['', section]
46
- description: "Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected — matching the prior <section-nav-item-ui>. When the parent <nav-ui> carries variant=\"section\", this item inherits it via CSS cascade unless an explicit variant is set."
46
+ description: "Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected. When the parent <nav-ui> carries variant=\"section\", this item inherits it via CSS cascade unless an explicit variant is set."
47
47
 
48
48
  events:
49
49
  nav-select:
package/core/icons.js CHANGED
@@ -224,6 +224,7 @@ const ICON_ALIASES = {
224
224
  'attachment': 'paperclip', 'attach': 'paperclip',
225
225
  'expand': 'arrows-out', 'collapse': 'arrows-in',
226
226
  'fullscreen': 'arrows-out', 'exit-fullscreen': 'arrows-in',
227
+ 'fade': 'gradient', 'fade-presence': 'gradient',
227
228
  'notification': 'bell', 'notifications': 'bell',
228
229
  'bookmark': 'bookmark-simple', 'favorite': 'heart', 'like': 'heart',
229
230
  'comment': 'chat-circle', 'chat': 'chat-circle',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-utils.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -230,7 +230,7 @@
230
230
  {
231
231
  "name": "confetti-burst",
232
232
  "category": "interaction-delight",
233
- "description": "Upward fountain particle burst",
233
+ "description": "Upward fountain particle burst — fires on each `press` event",
234
234
  "attributes": [
235
235
  "data-confetti-burst-active"
236
236
  ],
@@ -4,88 +4,114 @@ import { prefersReducedMotion } from './_motion.js';
4
4
  export const confettiBurst = defineTrait({
5
5
  name: 'confetti-burst',
6
6
  category: 'interaction-delight',
7
- description: 'Upward fountain particle burst',
7
+ description: 'Upward fountain particle burst — fires on each `press` event',
8
8
  attributes: ['data-confetti-burst-active'],
9
9
  events: ['confetti-burst-done'],
10
10
  config: [],
11
11
  setup({ host }) {
12
- // Reduced-motion: skip the burst, fire done event immediately.
13
- if (prefersReducedMotion()) {
14
- host.setAttribute('data-confetti-burst-active', '');
15
- queueMicrotask(() => {
16
- host.removeAttribute('data-confetti-burst-active');
17
- host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
18
- });
19
- return () => host.removeAttribute('data-confetti-burst-active');
20
- }
12
+ // Per-burst state — separate from the trait's lifecycle so multiple
13
+ // presses queue up cleanly. Each `fireBurst()` spawns its own canvas +
14
+ // raf loop and tears them down 2s later. Listening for `press` lets
15
+ // declarative usage (`<button-ui traits="confetti-burst">`) fire on
16
+ // every click; programmatic usage (animation page's
17
+ // `confettiBurst().connect(el)` per-click) gets the immediate burst
18
+ // it expects via the on-connect call below.
19
+ const inflight = new Set();
21
20
 
22
- const canvas = document.createElement('canvas');
23
- canvas.style.cssText = 'position:absolute;inset:0;pointer-events:none;z-index:9999;';
24
- host.style.position = host.style.position || 'relative';
25
- host.appendChild(canvas);
26
-
27
- const ctx = canvas.getContext('2d');
28
- let rafId = null;
29
-
30
- // Bail gracefully when canvas 2D isn't available (SSR, JSDOM, happy-dom).
31
- if (!ctx) {
21
+ function reducedMotionBurst() {
32
22
  host.setAttribute('data-confetti-burst-active', '');
33
- // Fire the done event on next tick so listeners still hear it.
34
23
  queueMicrotask(() => {
35
24
  host.removeAttribute('data-confetti-burst-active');
36
25
  host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
37
26
  });
38
- return () => {
39
- canvas.remove();
40
- host.removeAttribute('data-confetti-burst-active');
41
- };
42
27
  }
43
28
 
44
- const colors = ['#f44', '#4a4', '#44f', '#ff4', '#f4f', '#4ff'];
45
- const particles = Array.from({ length: 80 }, () => ({
46
- x: 0.5,
47
- y: 0.5,
48
- vx: (Math.random() - 0.5) * 8,
49
- vy: (Math.random() - 0.5) * 8 - 2,
50
- size: Math.random() * 4 + 2,
51
- color: colors[Math.floor(Math.random() * colors.length)],
52
- life: 1,
53
- }));
29
+ function fireBurst() {
30
+ if (prefersReducedMotion()) {
31
+ reducedMotionBurst();
32
+ return;
33
+ }
34
+
35
+ const canvas = document.createElement('canvas');
36
+ canvas.style.cssText = 'position:absolute;inset:0;pointer-events:none;z-index:9999;';
37
+ host.style.position = host.style.position || 'relative';
38
+ host.appendChild(canvas);
54
39
 
55
- host.setAttribute('data-confetti-burst-active', '');
56
- const startTime = performance.now();
40
+ const ctx = canvas.getContext('2d');
41
+ let rafId = null;
57
42
 
58
- function tick(now) {
59
- const elapsed = now - startTime;
60
- if (elapsed > 2000) {
43
+ // Bail gracefully when canvas 2D isn't available (SSR, JSDOM, happy-dom).
44
+ if (!ctx) {
45
+ host.setAttribute('data-confetti-burst-active', '');
46
+ queueMicrotask(() => {
47
+ host.removeAttribute('data-confetti-burst-active');
48
+ host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
49
+ });
61
50
  canvas.remove();
62
- host.removeAttribute('data-confetti-burst-active');
63
- host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
64
51
  return;
65
52
  }
66
53
 
67
- canvas.width = host.offsetWidth;
68
- canvas.height = host.offsetHeight;
69
- ctx.clearRect(0, 0, canvas.width, canvas.height);
54
+ const colors = ['#f44', '#4a4', '#44f', '#ff4', '#f4f', '#4ff'];
55
+ const particles = Array.from({ length: 80 }, () => ({
56
+ x: 0.5,
57
+ y: 0.5,
58
+ vx: (Math.random() - 0.5) * 8,
59
+ vy: (Math.random() - 0.5) * 8 - 2,
60
+ size: Math.random() * 4 + 2,
61
+ color: colors[Math.floor(Math.random() * colors.length)],
62
+ life: 1,
63
+ }));
64
+
65
+ host.setAttribute('data-confetti-burst-active', '');
66
+ const startTime = performance.now();
67
+ const burstHandle = { canvas, cancel: () => { if (rafId) cancelAnimationFrame(rafId); canvas.remove(); } };
68
+ inflight.add(burstHandle);
69
+
70
+ function tick(now) {
71
+ const elapsed = now - startTime;
72
+ if (elapsed > 2000) {
73
+ inflight.delete(burstHandle);
74
+ canvas.remove();
75
+ if (inflight.size === 0) host.removeAttribute('data-confetti-burst-active');
76
+ host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
77
+ return;
78
+ }
79
+
80
+ canvas.width = host.offsetWidth;
81
+ canvas.height = host.offsetHeight;
82
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
70
83
 
71
- for (const p of particles) {
72
- p.x += p.vx * 0.004;
73
- p.y += p.vy * 0.004;
74
- p.vy += 0.15;
75
- p.life = Math.max(0, 1 - elapsed / 2000);
76
- ctx.globalAlpha = p.life;
77
- ctx.fillStyle = p.color;
78
- ctx.fillRect(p.x * canvas.width, p.y * canvas.height, p.size, p.size);
84
+ for (const p of particles) {
85
+ p.x += p.vx * 0.004;
86
+ p.y += p.vy * 0.004;
87
+ p.vy += 0.15;
88
+ p.life = Math.max(0, 1 - elapsed / 2000);
89
+ ctx.globalAlpha = p.life;
90
+ ctx.fillStyle = p.color;
91
+ ctx.fillRect(p.x * canvas.width, p.y * canvas.height, p.size, p.size);
92
+ }
93
+ ctx.globalAlpha = 1;
94
+ rafId = requestAnimationFrame(tick);
79
95
  }
80
- ctx.globalAlpha = 1;
96
+
81
97
  rafId = requestAnimationFrame(tick);
82
98
  }
83
99
 
84
- rafId = requestAnimationFrame(tick);
100
+ // Fire one burst at connect-time. Preserves the existing animation
101
+ // page semantic (`confettiBurst().connect(burstEl)` on every click of
102
+ // a separate trigger button).
103
+ fireBurst();
104
+
105
+ // Listen for `press` so the declarative case
106
+ // (`<button-ui traits="confetti-burst">`) fires a fresh burst on
107
+ // each click. button-ui dispatches `press` from its #onClick.
108
+ const onPress = () => fireBurst();
109
+ host.addEventListener('press', onPress);
85
110
 
86
111
  return () => {
87
- if (rafId) cancelAnimationFrame(rafId);
88
- canvas.remove();
112
+ host.removeEventListener('press', onPress);
113
+ for (const handle of inflight) handle.cancel();
114
+ inflight.clear();
89
115
  host.removeAttribute('data-confetti-burst-active');
90
116
  };
91
117
  },
@@ -9,15 +9,25 @@ export const dragGhost = defineTrait({
9
9
  config: [],
10
10
  setup({ host }) {
11
11
  let ghost = null;
12
+ let clickOffsetX = 0;
13
+ let clickOffsetY = 0;
14
+
15
+ function onPointerDown(e) {
16
+ const rect = host.getBoundingClientRect();
17
+ clickOffsetX = e.clientX - rect.left;
18
+ clickOffsetY = e.clientY - rect.top;
19
+ }
12
20
 
13
21
  function onDragStart(e) {
14
22
  ghost = host.cloneNode(true);
23
+ const rect = host.getBoundingClientRect();
15
24
  ghost.style.cssText = `
16
25
  position: fixed; top: -9999px; left: -9999px;
26
+ width: ${rect.width}px; height: ${rect.height}px;
17
27
  pointer-events: none; opacity: 0.8; z-index: 99999;
18
28
  `;
19
29
  document.body.appendChild(ghost);
20
- e.dataTransfer?.setDragImage(ghost, 0, 0);
30
+ e.dataTransfer?.setDragImage(ghost, clickOffsetX, clickOffsetY);
21
31
  host.setAttribute('data-drag-ghost-active', '');
22
32
  }
23
33
 
@@ -27,10 +37,12 @@ export const dragGhost = defineTrait({
27
37
  }
28
38
 
29
39
  host.setAttribute('draggable', 'true');
40
+ host.addEventListener('pointerdown', onPointerDown);
30
41
  host.addEventListener('dragstart', onDragStart);
31
42
  host.addEventListener('dragend', onDragEnd);
32
43
 
33
44
  return () => {
45
+ host.removeEventListener('pointerdown', onPointerDown);
34
46
  host.removeEventListener('dragstart', onDragStart);
35
47
  host.removeEventListener('dragend', onDragEnd);
36
48
  if (ghost) { ghost.remove(); ghost = null; }
@@ -14,10 +14,15 @@ export const springAnimate = defineTrait({
14
14
  let rafId = null;
15
15
  const target = 0;
16
16
 
17
- // Read current translate as starting position
17
+ // Read current translate as starting position. The trait writes to
18
+ // `style.translate` (not `transform`), so read that first; fall back to
19
+ // the transform matrix for callers that nudged via `transform`.
18
20
  const cs = getComputedStyle(host);
19
- const matrix = new DOMMatrixReadOnly(cs.transform);
20
- let position = matrix.m41 || 0;
21
+ let position = parseFloat(cs.translate) || 0;
22
+ if (!position) {
23
+ const matrix = new DOMMatrixReadOnly(cs.transform);
24
+ position = matrix.m41 || 0;
25
+ }
21
26
  let velocity = 0;
22
27
 
23
28
  host.setAttribute('data-spring-animate-active', '');