@adia-ai/web-components 0.0.26 → 0.0.28

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 (60) hide show
  1. package/components/agent-artifact/agent-artifact.a2ui.json +1 -1
  2. package/components/agent-artifact/agent-artifact.css +11 -0
  3. package/components/agent-artifact/agent-artifact.js +23 -2
  4. package/components/agent-artifact/agent-artifact.yaml +1 -1
  5. package/components/agent-questions/agent-questions.css +20 -1
  6. package/components/agent-reasoning/agent-reasoning.css +11 -0
  7. package/components/agent-reasoning/agent-reasoning.js +16 -0
  8. package/components/agent-trace/agent-trace.css +36 -12
  9. package/components/alert/alert.a2ui.json +10 -4
  10. package/components/alert/alert.css +13 -0
  11. package/components/alert/alert.js +1 -1
  12. package/components/alert/alert.yaml +21 -4
  13. package/components/badge/badge.a2ui.json +0 -2
  14. package/components/badge/badge.css +20 -0
  15. package/components/badge/badge.js +10 -2
  16. package/components/badge/badge.yaml +0 -2
  17. package/components/breadcrumb/breadcrumb.a2ui.json +16 -1
  18. package/components/breadcrumb/breadcrumb.css +27 -0
  19. package/components/breadcrumb/breadcrumb.js +95 -17
  20. package/components/breadcrumb/breadcrumb.yaml +15 -1
  21. package/components/calendar-picker/calendar-picker.css +17 -0
  22. package/components/chart/chart.css +20 -13
  23. package/components/chart/chart.js +49 -17
  24. package/components/chart-legend/chart-legend.css +30 -54
  25. package/components/chart-legend/chart-legend.js +48 -30
  26. package/components/code/code.css +41 -0
  27. package/components/code/code.js +44 -3
  28. package/components/command/command.js +52 -1
  29. package/components/empty-state/empty-state.js +32 -21
  30. package/components/feed/feed-item.yaml +50 -0
  31. package/components/feed/feed.a2ui.json +59 -0
  32. package/components/feed/feed.css +141 -0
  33. package/components/feed/feed.js +276 -0
  34. package/components/feed/feed.yaml +33 -0
  35. package/components/index.js +2 -0
  36. package/components/list/list.js +20 -16
  37. package/components/menu/menu.css +18 -0
  38. package/components/menu/menu.js +24 -10
  39. package/components/pane/pane.css +5 -0
  40. package/components/pipeline-status/pipeline-status.css +15 -1
  41. package/components/popover/popover.css +17 -0
  42. package/components/select/select.css +18 -0
  43. package/components/swatch/swatch.a2ui.json +116 -0
  44. package/components/swatch/swatch.css +141 -0
  45. package/components/swatch/swatch.js +121 -0
  46. package/components/swatch/swatch.yaml +101 -0
  47. package/components/swiper/swiper.css +9 -0
  48. package/components/table/table.css +5 -0
  49. package/components/table/table.js +45 -1
  50. package/components/table-toolbar/table-toolbar.css +13 -0
  51. package/components/tag/tag.css +10 -0
  52. package/components/timeline/timeline.css +15 -4
  53. package/components/toast/toast.css +93 -48
  54. package/components/toast/toast.js +101 -22
  55. package/components/toolbar/toolbar.css +13 -0
  56. package/components/tooltip/tooltip.css +11 -3
  57. package/core/provider.js +1 -0
  58. package/package.json +1 -1
  59. package/styles/colors/semantics.css +1 -1
  60. package/styles/components.css +1 -0
@@ -29,6 +29,7 @@ class AdiaCommand extends AdiaElement {
29
29
  static template = () => null;
30
30
 
31
31
  #items = [];
32
+ #recents = [];
32
33
  #activeIdx = -1;
33
34
  #inputEl = null;
34
35
  #listEl = null;
@@ -37,6 +38,8 @@ class AdiaCommand extends AdiaElement {
37
38
  #bound = false;
38
39
  #itemByEl = new WeakMap();
39
40
 
41
+ static #RECENTS_MAX = 3;
42
+
40
43
  #onListClick = (e) => {
41
44
  const el = e.target instanceof Element ? e.target.closest('[role="option"]:not([aria-disabled])') : null;
42
45
  if (!el) return;
@@ -99,7 +102,15 @@ class AdiaCommand extends AdiaElement {
99
102
  // ── Public API ──
100
103
 
101
104
  get value() { return this.#inputEl?.value || ''; }
102
- set value(v) { if (this.#inputEl) this.#inputEl.value = v; }
105
+ set value(v) {
106
+ if (!this.#inputEl) return;
107
+ const next = v ?? '';
108
+ this.#inputEl.value = next;
109
+ // Keep the rendered list in sync with the visible input — the host
110
+ // commonly clears value on (re)open, and consumers expect to see the
111
+ // full list (plus recents) rather than the previous filter's residue.
112
+ this.#renderItems(next);
113
+ }
103
114
 
104
115
  focus() { this.#inputEl?.focus(); }
105
116
 
@@ -148,6 +159,26 @@ class AdiaCommand extends AdiaElement {
148
159
  const flat = this.#items;
149
160
  let visibleCount = 0;
150
161
 
162
+ // Recents — only when no filter is applied. Resolved against the
163
+ // current item set so a stale recent (item removed via setItems)
164
+ // is silently dropped.
165
+ if (!q && this.#recents.length) {
166
+ const resolved = this.#recents
167
+ .map(v => this.#findItem(v))
168
+ .filter(it => it && !it.disabled);
169
+ if (resolved.length) {
170
+ const groupEl = document.createElement('div');
171
+ groupEl.setAttribute('data-group', '');
172
+ groupEl.setAttribute('data-recent', '');
173
+ groupEl.innerHTML = `<div data-group-label>Recent</div>`;
174
+ for (const item of resolved) {
175
+ groupEl.appendChild(this.#createItemEl(item, visibleCount));
176
+ visibleCount++;
177
+ }
178
+ this.#listEl.appendChild(groupEl);
179
+ }
180
+ }
181
+
151
182
  for (const entry of flat) {
152
183
  if (entry.items) {
153
184
  // Group
@@ -182,6 +213,24 @@ class AdiaCommand extends AdiaElement {
182
213
  if (visibleCount > 0) this.#activate(0);
183
214
  }
184
215
 
216
+ #findItem(value) {
217
+ for (const entry of this.#items) {
218
+ if (entry.items) {
219
+ const hit = entry.items.find(i => i.value === value);
220
+ if (hit) return hit;
221
+ } else if (entry.value === value) {
222
+ return entry;
223
+ }
224
+ }
225
+ return null;
226
+ }
227
+
228
+ #pushRecent(value) {
229
+ if (!value) return;
230
+ this.#recents = [value, ...this.#recents.filter(v => v !== value)]
231
+ .slice(0, AdiaCommand.#RECENTS_MAX);
232
+ }
233
+
185
234
  #createItemEl(item, idx) {
186
235
  const el = document.createElement('div');
187
236
  el.setAttribute('role', 'option');
@@ -235,6 +284,7 @@ class AdiaCommand extends AdiaElement {
235
284
  }
236
285
 
237
286
  #select(item) {
287
+ this.#pushRecent(item.value);
238
288
  this.dispatchEvent(new CustomEvent('select', {
239
289
  bubbles: true,
240
290
  detail: { value: item.value, label: item.label },
@@ -246,6 +296,7 @@ class AdiaCommand extends AdiaElement {
246
296
  if (!active) return;
247
297
  const value = active.dataset.value;
248
298
  const label = active.querySelector('[data-text]')?.textContent || '';
299
+ this.#pushRecent(value);
249
300
  this.dispatchEvent(new CustomEvent('select', {
250
301
  bubbles: true,
251
302
  detail: { value, label },
@@ -24,10 +24,15 @@ class AdiaEmptyState extends AdiaElement {
24
24
  #headingEl = null;
25
25
  #descEl = null;
26
26
 
27
+ // Mark slot elements we create so render() never overrides consumer-provided ones.
28
+ // See ADR-0010 (slot content is source of truth).
29
+ #stampMark(el) { el.dataset.emptyStateStamped = '1'; return el; }
30
+ #wasStamped(el) { return el?.dataset?.emptyStateStamped === '1'; }
31
+
27
32
  connected() {
28
33
  this.#iconEl = this.querySelector(':scope > [slot="icon"]');
29
34
  if (!this.#iconEl) {
30
- this.#iconEl = document.createElement('icon-ui');
35
+ this.#iconEl = this.#stampMark(document.createElement('icon-ui'));
31
36
  this.#iconEl.setAttribute('slot', 'icon');
32
37
  this.#iconEl.setAttribute('size', 'lg');
33
38
  this.insertBefore(this.#iconEl, this.firstChild);
@@ -35,14 +40,14 @@ class AdiaEmptyState extends AdiaElement {
35
40
 
36
41
  this.#headingEl = this.querySelector(':scope > [slot="heading"]');
37
42
  if (!this.#headingEl) {
38
- this.#headingEl = document.createElement('span');
43
+ this.#headingEl = this.#stampMark(document.createElement('span'));
39
44
  this.#headingEl.setAttribute('slot', 'heading');
40
45
  this.insertBefore(this.#headingEl, this.querySelector('[slot="action"]'));
41
46
  }
42
47
 
43
48
  this.#descEl = this.querySelector(':scope > [slot="description"]');
44
49
  if (!this.#descEl) {
45
- this.#descEl = document.createElement('span');
50
+ this.#descEl = this.#stampMark(document.createElement('span'));
46
51
  this.#descEl.setAttribute('slot', 'description');
47
52
  this.insertBefore(this.#descEl, this.querySelector('[slot="action"]'));
48
53
  }
@@ -51,28 +56,34 @@ class AdiaEmptyState extends AdiaElement {
51
56
  render() {
52
57
  if (!this.#iconEl) return;
53
58
 
54
- // Icon
55
- if (this.icon) {
56
- this.#iconEl.setAttribute('name', this.icon);
57
- this.#iconEl.hidden = false;
58
- } else {
59
- this.#iconEl.hidden = true;
59
+ // Icon — only mutate stamped elements; visibility follows the source of truth.
60
+ if (this.#wasStamped(this.#iconEl)) {
61
+ if (this.icon) {
62
+ this.#iconEl.setAttribute('name', this.icon);
63
+ this.#iconEl.hidden = false;
64
+ } else {
65
+ this.#iconEl.hidden = true;
66
+ }
60
67
  }
61
68
 
62
- // Heading
63
- if (this.heading) {
64
- this.#headingEl.textContent = this.heading;
65
- this.#headingEl.hidden = false;
66
- } else {
67
- this.#headingEl.hidden = true;
69
+ // Heading — same policy.
70
+ if (this.#wasStamped(this.#headingEl)) {
71
+ if (this.heading) {
72
+ this.#headingEl.textContent = this.heading;
73
+ this.#headingEl.hidden = false;
74
+ } else {
75
+ this.#headingEl.hidden = true;
76
+ }
68
77
  }
69
78
 
70
- // Description
71
- if (this.description) {
72
- this.#descEl.textContent = this.description;
73
- this.#descEl.hidden = false;
74
- } else {
75
- this.#descEl.hidden = true;
79
+ // Description — same policy.
80
+ if (this.#wasStamped(this.#descEl)) {
81
+ if (this.description) {
82
+ this.#descEl.textContent = this.description;
83
+ this.#descEl.hidden = false;
84
+ } else {
85
+ this.#descEl.hidden = true;
86
+ }
76
87
  }
77
88
  }
78
89
 
@@ -0,0 +1,50 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: AdiaFeedItem
3
+ tag: feed-item-ui
4
+ component: FeedItem
5
+ category: feedback
6
+ version: 1
7
+ description: >-
8
+ Atomic feed entry inside a `<feed-ui>` lane. Three dismiss policies
9
+ are inferred from the prop shape — auto-fade (duration > 0 + no
10
+ action), sticky-dismissible (duration null/0), action-required
11
+ (duration null/0 + action; future phase). Posted via
12
+ `AdiaFeed.post()` rather than authored directly.
13
+ props:
14
+ text:
15
+ description: Body copy
16
+ type: string
17
+ default: ""
18
+ heading:
19
+ description: Optional emphasis line above text
20
+ type: string
21
+ default: ""
22
+ icon:
23
+ description: Optional leading icon name
24
+ type: string
25
+ default: ""
26
+ variant:
27
+ description: Semantic variant
28
+ type: string
29
+ default: default
30
+ enum:
31
+ - default
32
+ - info
33
+ - success
34
+ - warning
35
+ - danger
36
+ duration:
37
+ description: Auto-fade timer in ms; null/0 = sticky (requires user input)
38
+ type: number
39
+ default: 4000
40
+ dismissible:
41
+ description: Render an x close button (default true for sticky, false for auto-fade)
42
+ type: boolean
43
+ default: false
44
+ events:
45
+ close:
46
+ description: Fired after the item finishes its exit animation
47
+ slots:
48
+ body:
49
+ description: Default content slot (also accepts the `text` / `heading` props)
50
+ states: {}
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/Feed.json",
4
+ "title": "Feed",
5
+ "description": "Shared top-layer feed channel. Per docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into document.body via Popover API; consumers post via the static API (`AdiaFeed.post()`) or the global 'feed' CustomEvent.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "component": {
17
+ "const": "Feed"
18
+ },
19
+ "max": {
20
+ "description": "Cap on simultaneously visible items per lane",
21
+ "type": "number",
22
+ "default": 5
23
+ },
24
+ "position": {
25
+ "description": "Lane the feed renders into",
26
+ "type": "string",
27
+ "enum": [
28
+ "top-left",
29
+ "top-center",
30
+ "top-right",
31
+ "bottom-left",
32
+ "bottom-center",
33
+ "bottom-right",
34
+ "inline"
35
+ ],
36
+ "default": "bottom-right"
37
+ }
38
+ },
39
+ "required": [
40
+ "component"
41
+ ],
42
+ "unevaluatedProperties": false,
43
+ "x-adiaui": {
44
+ "anti_patterns": [],
45
+ "category": "container",
46
+ "events": {},
47
+ "examples": [],
48
+ "keywords": [],
49
+ "name": "AdiaFeedContainer",
50
+ "related": [],
51
+ "slots": {},
52
+ "states": {},
53
+ "synonyms": {},
54
+ "tag": "feed-ui",
55
+ "tokens": {},
56
+ "traits": [],
57
+ "version": 1
58
+ }
59
+ }
@@ -0,0 +1,141 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ FEED-UI — Shared top-layer feed channel.
3
+ Per docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001).
4
+ ═══════════════════════════════════════════════════════════════ */
5
+
6
+ /* Safari 17.x bug — same as toast: `:scope[data-open]` flavor B
7
+ doesn't reliably restyle on attribute toggling inside @scope.
8
+ Selectors moved out as plain feed-item-ui[data-…] rules. */
9
+ feed-item-ui[data-open] {
10
+ transition: transform var(--feed-item-duration) var(--feed-item-easing),
11
+ opacity var(--feed-item-duration) var(--feed-item-easing);
12
+ transform: translateY(0);
13
+ opacity: 1;
14
+ }
15
+ feed-item-ui[data-closing] {
16
+ transition: transform var(--feed-item-duration) var(--feed-item-easing),
17
+ opacity var(--feed-item-duration) var(--feed-item-easing);
18
+ opacity: 0;
19
+ }
20
+
21
+ /* Container — per-position lane, mounted into document.body via
22
+ Popover API for top-layer placement. */
23
+ @scope (feed-ui) {
24
+ :where(:scope) {
25
+ --feed-gap: var(--a-space-2);
26
+ --feed-padding: var(--a-space-3);
27
+ --feed-max-width: 22rem;
28
+ --feed-offset: var(--a-space-4);
29
+ }
30
+
31
+ :scope {
32
+ position: fixed;
33
+ z-index: 9999;
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: var(--feed-gap);
37
+ padding: 0;
38
+ background: transparent;
39
+ border: 0;
40
+ pointer-events: none; /* Items re-enable pointer-events. */
41
+ width: max-content;
42
+ max-width: var(--feed-max-width);
43
+ }
44
+ /* Reset native popover defaults so the lane is invisible until
45
+ items render. */
46
+ :scope[popover] { background: none; color: inherit; }
47
+
48
+ /* Position variants. Default = bottom-right. */
49
+ :scope,
50
+ :scope[position="bottom-right"] {
51
+ bottom: var(--feed-offset); right: var(--feed-offset);
52
+ }
53
+ :scope[position="bottom-left"] {
54
+ bottom: var(--feed-offset); left: var(--feed-offset); right: auto;
55
+ }
56
+ :scope[position="bottom-center"] {
57
+ bottom: var(--feed-offset);
58
+ left: 50%; right: auto;
59
+ transform: translateX(-50%);
60
+ }
61
+ :scope[position="top-right"] {
62
+ top: var(--feed-offset); right: var(--feed-offset); bottom: auto;
63
+ flex-direction: column-reverse;
64
+ }
65
+ :scope[position="top-left"] {
66
+ top: var(--feed-offset); left: var(--feed-offset); right: auto; bottom: auto;
67
+ flex-direction: column-reverse;
68
+ }
69
+ :scope[position="top-center"] {
70
+ top: var(--feed-offset); bottom: auto;
71
+ left: 50%; right: auto;
72
+ transform: translateX(-50%);
73
+ flex-direction: column-reverse;
74
+ }
75
+ :scope[position="inline"] {
76
+ position: relative;
77
+ inset: auto;
78
+ z-index: 1;
79
+ }
80
+ }
81
+
82
+ /* Item — atomic feed entry. */
83
+ @scope (feed-item-ui) {
84
+ :where(:scope) {
85
+ --feed-item-bg: var(--a-bg);
86
+ --feed-item-fg: var(--a-fg);
87
+ --feed-item-border: var(--a-border-subtle);
88
+ --feed-item-radius: var(--a-radius-md);
89
+ --feed-item-px: var(--a-space-3);
90
+ --feed-item-py: var(--a-space-2-5);
91
+ --feed-item-shadow: var(--a-shadow-md);
92
+ --feed-item-icon-size: var(--a-icon-size);
93
+ --feed-item-duration: var(--a-duration);
94
+ --feed-item-easing: var(--a-easing-out);
95
+ --feed-item-gap: var(--a-space-3);
96
+ --feed-item-max-width: var(--a-feed-max-width, 22rem);
97
+ }
98
+ :scope[variant="info"] { --feed-item-fg: var(--a-info-fg); --feed-item-bg: var(--a-info-muted); }
99
+ :scope[variant="success"] { --feed-item-fg: var(--a-success-fg); --feed-item-bg: var(--a-success-muted); }
100
+ :scope[variant="warning"] { --feed-item-fg: var(--a-warning-fg); --feed-item-bg: var(--a-warning-muted); }
101
+ :scope[variant="danger"] { --feed-item-fg: var(--a-danger-fg); --feed-item-bg: var(--a-danger-muted); }
102
+
103
+ :scope {
104
+ box-sizing: border-box;
105
+ display: flex;
106
+ align-items: center;
107
+ gap: var(--feed-item-gap);
108
+ max-width: var(--feed-item-max-width);
109
+ padding: var(--feed-item-py) var(--feed-item-px);
110
+ background: var(--feed-item-bg);
111
+ color: var(--feed-item-fg);
112
+ border: 1px solid var(--feed-item-border);
113
+ border-radius: var(--feed-item-radius);
114
+ box-shadow: var(--feed-item-shadow);
115
+ opacity: 0;
116
+ transform: translateY(0.5rem);
117
+ pointer-events: auto;
118
+ }
119
+ :scope > [slot="body"] {
120
+ flex: 1;
121
+ min-width: 0;
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: 0.125rem;
125
+ }
126
+ :scope > [slot="body"] strong {
127
+ font-weight: var(--a-font-weight-strong, 600);
128
+ }
129
+ :scope > [data-feed-close] {
130
+ flex-shrink: 0;
131
+ }
132
+ }
133
+
134
+ /* Reduced-motion: skip slide-in/out, keep opacity. */
135
+ @media (prefers-reduced-motion: reduce) {
136
+ feed-item-ui[data-open],
137
+ feed-item-ui[data-closing] {
138
+ transition: opacity var(--feed-item-duration) var(--feed-item-easing);
139
+ transform: none !important;
140
+ }
141
+ }