@adia-ai/web-components 0.6.50 → 0.7.1

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 (106) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/components/action-list/action-list.css +1 -1
  3. package/components/agent-artifact/agent-artifact.class.js +10 -10
  4. package/components/agent-artifact/agent-artifact.css +1 -1
  5. package/components/agent-reasoning/agent-reasoning.class.js +51 -0
  6. package/components/agent-reasoning/agent-reasoning.css +49 -22
  7. package/components/alert/alert.class.js +8 -1
  8. package/components/alert/alert.css +13 -1
  9. package/components/avatar/avatar.a2ui.json +2 -14
  10. package/components/avatar/avatar.class.js +3 -15
  11. package/components/avatar/avatar.d.ts +2 -4
  12. package/components/avatar/avatar.yaml +1 -18
  13. package/components/breadcrumb/breadcrumb.css +4 -1
  14. package/components/button/button.a2ui.json +3 -0
  15. package/components/button/button.css +14 -3
  16. package/components/button/button.yaml +5 -0
  17. package/components/calendar-grid/calendar-grid.css +1 -1
  18. package/components/calendar-picker/calendar-picker.css +5 -2
  19. package/components/chart/chart.a2ui.json +0 -18
  20. package/components/chart/chart.class.js +8 -50
  21. package/components/chart/chart.css +1 -15
  22. package/components/chart/chart.d.ts +0 -4
  23. package/components/chart/chart.yaml +0 -24
  24. package/components/color-input/color-input.css +4 -1
  25. package/components/combobox/combobox.class.js +11 -0
  26. package/components/combobox/combobox.css +8 -0
  27. package/components/date-range-picker/date-range-picker.class.js +5 -1
  28. package/components/date-range-picker/date-range-picker.css +12 -2
  29. package/components/datetime-picker/datetime-picker.class.js +3 -0
  30. package/components/datetime-picker/datetime-picker.css +16 -2
  31. package/components/empty-state/empty-state.css +11 -4
  32. package/components/field/field.css +17 -6
  33. package/components/grid/grid.a2ui.json +5 -0
  34. package/components/grid/grid.class.js +16 -6
  35. package/components/grid/grid.css +17 -3
  36. package/components/grid/grid.d.ts +2 -0
  37. package/components/grid/grid.yaml +9 -0
  38. package/components/heatmap/heatmap.class.js +9 -3
  39. package/components/heatmap/heatmap.css +19 -2
  40. package/components/image/image.css +4 -1
  41. package/components/input/input.class.js +38 -0
  42. package/components/input/input.css +9 -5
  43. package/components/input/input.test.js +57 -0
  44. package/components/integration-card/integration-card.class.js +31 -7
  45. package/components/integration-card/integration-card.test.js +12 -1
  46. package/components/kbd/kbd.a2ui.json +3 -2
  47. package/components/kbd/kbd.css +7 -4
  48. package/components/kbd/kbd.d.ts +2 -2
  49. package/components/kbd/kbd.yaml +2 -1
  50. package/components/list/list.class.js +8 -1
  51. package/components/menu/menu.class.js +12 -3
  52. package/components/menu/menu.css +4 -1
  53. package/components/menu/menu.test.js +130 -0
  54. package/components/modal/modal.class.js +10 -1
  55. package/components/modal/modal.css +9 -0
  56. package/components/option-card/option-card.a2ui.json +3 -0
  57. package/components/option-card/option-card.css +44 -19
  58. package/components/option-card/option-card.yaml +5 -0
  59. package/components/otp-input/otp-input.css +25 -10
  60. package/components/page/page.css +64 -11
  61. package/components/pagination/pagination.class.js +1 -1
  62. package/components/pagination/pagination.css +9 -1
  63. package/components/pipeline-status/pipeline-status.css +6 -0
  64. package/components/popover/popover.css +12 -1
  65. package/components/preview/preview.css +30 -3
  66. package/components/progress-row/progress-row.css +3 -1
  67. package/components/qr-code/qr-code.css +4 -1
  68. package/components/segmented/segmented.css +4 -1
  69. package/components/select/select.a2ui.json +1 -1
  70. package/components/select/select.class.js +63 -7
  71. package/components/select/select.css +18 -0
  72. package/components/select/select.yaml +9 -2
  73. package/components/stack/stack.a2ui.json +12 -1
  74. package/components/stack/stack.d.ts +2 -2
  75. package/components/stack/stack.yaml +13 -1
  76. package/components/stat/stat.a2ui.json +5 -0
  77. package/components/stat/stat.css +55 -0
  78. package/components/stat/stat.d.ts +2 -0
  79. package/components/stat/stat.js +4 -0
  80. package/components/stat/stat.yaml +9 -0
  81. package/components/swiper/swiper.class.js +14 -6
  82. package/components/switch/switch.css +13 -0
  83. package/components/table/table.a2ui.json +2 -2
  84. package/components/table/table.css +13 -1
  85. package/components/table/table.yaml +2 -2
  86. package/components/time-picker/time-picker.css +4 -1
  87. package/components/timeline/timeline.class.js +3 -3
  88. package/components/timeline/timeline.css +23 -5
  89. package/components/toggle-group/toggle-group.css +4 -1
  90. package/components/toggle-scheme/toggle-scheme.css +4 -1
  91. package/components/tree/tree.class.js +24 -4
  92. package/components/tree/tree.test.js +108 -0
  93. package/dist/web-components.min.css +1 -1
  94. package/dist/web-components.min.js +83 -83
  95. package/package.json +3 -3
  96. package/styles/api/layout.css +7 -0
  97. package/styles/api/text.css +9 -5
  98. package/styles/index.css +11 -2
  99. package/styles/prose.css +8 -0
  100. package/styles/resets.css +5 -5
  101. package/styles/themes.css +8 -1
  102. package/styles/tokens.css +3 -3
  103. package/styles/type/elements.css +73 -0
  104. package/styles/type/roles.css +14 -49
  105. package/styles/type/scale.css +0 -5
  106. package/styles/typography.css +3 -3
@@ -281,11 +281,28 @@ export class UITreeItem extends UIElement {
281
281
  }
282
282
 
283
283
  connected() {
284
- this.setAttribute('role', 'treeitem');
284
+ // The host is the structural GROUP container; the focusable [slot="row"]
285
+ // is the treeitem (FEEDBACK-91). The WAI-ARIA tree pattern requires every
286
+ // treeitem to be contained by a `group` or `tree`. Roling the host as
287
+ // `group` — rather than wrapping nested children in a new element — keeps
288
+ // the light-DOM structure intact, so it works for BOTH static markup AND
289
+ // template-engine `.map()` children (whose display:contents wrappers must
290
+ // not be re-homed) and sidesteps the parent-before-child upgrade race.
291
+ // axe-core: `tree` accepts `group` as a required child, `group` has no
292
+ // required parent, and the row's required `group` parent is the host.
293
+ this.setAttribute('role', 'group');
285
294
 
286
295
  if (!this.querySelector(':scope > [slot="row"]')) {
287
296
  this.#stamp();
288
297
  }
298
+
299
+ // Mark the row as the treeitem (whether we stamped it or the consumer
300
+ // supplied a declarative [slot="row"]) and ensure it stays focusable.
301
+ const row = this.querySelector(':scope > [slot="row"]');
302
+ if (row) {
303
+ row.setAttribute('role', 'treeitem');
304
+ if (!row.hasAttribute('tabindex')) row.setAttribute('tabindex', '0');
305
+ }
289
306
  }
290
307
 
291
308
  #stamp() {
@@ -350,12 +367,15 @@ export class UITreeItem extends UIElement {
350
367
  // Update depth-based indent
351
368
  row.style.paddingInlineStart = `${this.depth * 16 + 4}px`;
352
369
 
353
- // Update aria
370
+ // Update aria — expanded/selected belong on the treeitem (the row), not
371
+ // the host group (FEEDBACK-91).
354
372
  if (this.hasChildren) {
355
- this.setAttribute('aria-expanded', String(this.open));
373
+ row.setAttribute('aria-expanded', String(this.open));
356
374
  } else {
357
- this.removeAttribute('aria-expanded');
375
+ row.removeAttribute('aria-expanded');
358
376
  }
377
+ if (this.selected) row.setAttribute('aria-selected', 'true');
378
+ else row.removeAttribute('aria-selected');
359
379
 
360
380
  // Update text
361
381
  const textEl = row.querySelector('[slot="text"]');
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
10
+ import { html, stamp } from '../../core/template.js';
10
11
 
11
12
  beforeAll(async () => {
12
13
  await import('./tree.js');
@@ -153,3 +154,110 @@ describe('<tree-item-ui> caret slot + actions adoption (caret convention + FB-89
153
154
  expect(carets[0].getAttribute('name')).toBe('folder');
154
155
  });
155
156
  });
157
+
158
+ describe('<tree-item-ui> ARIA tree containment (FEEDBACK-91)', () => {
159
+ let host;
160
+ const settle = () => new Promise((r) => setTimeout(r, 30));
161
+
162
+ beforeEach(() => {
163
+ host = document.createElement('div');
164
+ document.body.appendChild(host);
165
+ });
166
+ afterEach(() => host.remove());
167
+
168
+ // Mirrors axe-core's `aria-required-parent` check for `treeitem`: walking up
169
+ // from the treeitem, the first ancestor carrying a *non-generic* role must be
170
+ // `group` or `tree`. Roleless / presentation / display:contents wrapper spans
171
+ // are transparent pass-throughs (exactly the consumer's `.map()` case).
172
+ function requiredParentSatisfied(treeitem, root) {
173
+ let p = treeitem.parentElement;
174
+ while (p && p !== root.parentElement) {
175
+ const role = p.getAttribute('role');
176
+ if (role === 'group' || role === 'tree') return true;
177
+ if (role && role !== 'presentation' && role !== 'none') return false;
178
+ p = p.parentElement;
179
+ }
180
+ return false;
181
+ }
182
+
183
+ function buildStaticTree() {
184
+ const tree = document.createElement('tree-ui');
185
+ const parent = document.createElement('tree-item-ui');
186
+ parent.setAttribute('text', 'Colors');
187
+ parent.setAttribute('open', '');
188
+ for (const t of ['Neutral', 'Brand']) {
189
+ const c = document.createElement('tree-item-ui');
190
+ c.setAttribute('text', t);
191
+ parent.appendChild(c);
192
+ }
193
+ tree.appendChild(parent);
194
+ host.appendChild(tree);
195
+ return tree;
196
+ }
197
+
198
+ it('roles the host as group and the [slot="row"] as treeitem', async () => {
199
+ const tree = buildStaticTree();
200
+ await settle();
201
+ expect(tree.getAttribute('role')).toBe('tree');
202
+ for (const item of tree.querySelectorAll('tree-item-ui')) {
203
+ expect(item.getAttribute('role')).toBe('group');
204
+ const row = item.querySelector(':scope > [slot="row"]');
205
+ expect(row.getAttribute('role')).toBe('treeitem');
206
+ expect(row.getAttribute('tabindex')).toBe('0');
207
+ }
208
+ });
209
+
210
+ it('every treeitem row has a group/tree required parent (static markup)', async () => {
211
+ const tree = buildStaticTree();
212
+ await settle();
213
+ const rows = tree.querySelectorAll('[role="treeitem"]');
214
+ expect(rows.length).toBe(3); // parent + 2 children — was the axe-critical case
215
+ for (const row of rows) expect(requiredParentSatisfied(row, tree)).toBe(true);
216
+ });
217
+
218
+ it('nested treeitems satisfy required-parent when rendered via the template engine .map() (consumer repro)', async () => {
219
+ const ITEMS = [{ id: 'neutral', text: 'Neutral' }, { id: 'brand', text: 'Brand' }];
220
+ stamp(html`
221
+ <tree-ui>
222
+ <tree-item-ui text="Colors" open>
223
+ ${ITEMS.map((i) => html`<tree-item-ui .text=${i.text} .value=${i.id}></tree-item-ui>`)}
224
+ </tree-item-ui>
225
+ </tree-ui>
226
+ `, host);
227
+ await settle();
228
+
229
+ const tree = host.querySelector('tree-ui');
230
+ const rows = tree.querySelectorAll('[role="treeitem"]');
231
+ expect(rows.length).toBe(3);
232
+ for (const row of rows) expect(requiredParentSatisfied(row, tree)).toBe(true);
233
+
234
+ // The nested items sit BEHIND the template engine's display:contents wrapper
235
+ // span — proving the fix works without re-homing engine-owned nodes.
236
+ const child = tree.querySelector('tree-item-ui[value="neutral"]');
237
+ expect(child.parentElement.style.display).toBe('contents');
238
+ expect(child.getAttribute('role')).toBe('group');
239
+ });
240
+
241
+ it('aria-expanded lives on the row (treeitem), not the host group', async () => {
242
+ const tree = buildStaticTree();
243
+ await settle();
244
+ const parent = tree.querySelector('tree-item-ui');
245
+ const parentRow = parent.querySelector(':scope > [slot="row"]');
246
+ expect(parentRow.getAttribute('aria-expanded')).toBe('true');
247
+ expect(parent.hasAttribute('aria-expanded')).toBe(false); // not stranded on the host
248
+
249
+ const leaf = tree.querySelectorAll('tree-item-ui')[1]; // a child leaf
250
+ const leafRow = leaf.querySelector(':scope > [slot="row"]');
251
+ expect(leafRow.hasAttribute('aria-expanded')).toBe(false); // leaves carry none
252
+ });
253
+
254
+ it('aria-selected reflects onto the selected row only', async () => {
255
+ const tree = buildStaticTree();
256
+ await settle();
257
+ const [parent, neutral] = tree.querySelectorAll('tree-item-ui');
258
+ tree.select(neutral);
259
+ await settle();
260
+ expect(neutral.querySelector(':scope > [slot="row"]').getAttribute('aria-selected')).toBe('true');
261
+ expect(parent.querySelector(':scope > [slot="row"]').hasAttribute('aria-selected')).toBe(false);
262
+ });
263
+ });