@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.
- package/CHANGELOG.md +134 -0
- package/components/action-list/action-list.css +1 -1
- package/components/agent-artifact/agent-artifact.class.js +10 -10
- package/components/agent-artifact/agent-artifact.css +1 -1
- package/components/agent-reasoning/agent-reasoning.class.js +51 -0
- package/components/agent-reasoning/agent-reasoning.css +49 -22
- package/components/alert/alert.class.js +8 -1
- package/components/alert/alert.css +13 -1
- package/components/avatar/avatar.a2ui.json +2 -14
- package/components/avatar/avatar.class.js +3 -15
- package/components/avatar/avatar.d.ts +2 -4
- package/components/avatar/avatar.yaml +1 -18
- package/components/breadcrumb/breadcrumb.css +4 -1
- package/components/button/button.a2ui.json +3 -0
- package/components/button/button.css +14 -3
- package/components/button/button.yaml +5 -0
- package/components/calendar-grid/calendar-grid.css +1 -1
- package/components/calendar-picker/calendar-picker.css +5 -2
- package/components/chart/chart.a2ui.json +0 -18
- package/components/chart/chart.class.js +8 -50
- package/components/chart/chart.css +1 -15
- package/components/chart/chart.d.ts +0 -4
- package/components/chart/chart.yaml +0 -24
- package/components/color-input/color-input.css +4 -1
- package/components/combobox/combobox.class.js +11 -0
- package/components/combobox/combobox.css +8 -0
- package/components/date-range-picker/date-range-picker.class.js +5 -1
- package/components/date-range-picker/date-range-picker.css +12 -2
- package/components/datetime-picker/datetime-picker.class.js +3 -0
- package/components/datetime-picker/datetime-picker.css +16 -2
- package/components/empty-state/empty-state.css +11 -4
- package/components/field/field.css +17 -6
- package/components/grid/grid.a2ui.json +5 -0
- package/components/grid/grid.class.js +16 -6
- package/components/grid/grid.css +17 -3
- package/components/grid/grid.d.ts +2 -0
- package/components/grid/grid.yaml +9 -0
- package/components/heatmap/heatmap.class.js +9 -3
- package/components/heatmap/heatmap.css +19 -2
- package/components/image/image.css +4 -1
- package/components/input/input.class.js +38 -0
- package/components/input/input.css +9 -5
- package/components/input/input.test.js +57 -0
- package/components/integration-card/integration-card.class.js +31 -7
- package/components/integration-card/integration-card.test.js +12 -1
- package/components/kbd/kbd.a2ui.json +3 -2
- package/components/kbd/kbd.css +7 -4
- package/components/kbd/kbd.d.ts +2 -2
- package/components/kbd/kbd.yaml +2 -1
- package/components/list/list.class.js +8 -1
- package/components/menu/menu.class.js +12 -3
- package/components/menu/menu.css +4 -1
- package/components/menu/menu.test.js +130 -0
- package/components/modal/modal.class.js +10 -1
- package/components/modal/modal.css +9 -0
- package/components/option-card/option-card.a2ui.json +3 -0
- package/components/option-card/option-card.css +44 -19
- package/components/option-card/option-card.yaml +5 -0
- package/components/otp-input/otp-input.css +25 -10
- package/components/page/page.css +64 -11
- package/components/pagination/pagination.class.js +1 -1
- package/components/pagination/pagination.css +9 -1
- package/components/pipeline-status/pipeline-status.css +6 -0
- package/components/popover/popover.css +12 -1
- package/components/preview/preview.css +30 -3
- package/components/progress-row/progress-row.css +3 -1
- package/components/qr-code/qr-code.css +4 -1
- package/components/segmented/segmented.css +4 -1
- package/components/select/select.a2ui.json +1 -1
- package/components/select/select.class.js +63 -7
- package/components/select/select.css +18 -0
- package/components/select/select.yaml +9 -2
- package/components/stack/stack.a2ui.json +12 -1
- package/components/stack/stack.d.ts +2 -2
- package/components/stack/stack.yaml +13 -1
- package/components/stat/stat.a2ui.json +5 -0
- package/components/stat/stat.css +55 -0
- package/components/stat/stat.d.ts +2 -0
- package/components/stat/stat.js +4 -0
- package/components/stat/stat.yaml +9 -0
- package/components/swiper/swiper.class.js +14 -6
- package/components/switch/switch.css +13 -0
- package/components/table/table.a2ui.json +2 -2
- package/components/table/table.css +13 -1
- package/components/table/table.yaml +2 -2
- package/components/time-picker/time-picker.css +4 -1
- package/components/timeline/timeline.class.js +3 -3
- package/components/timeline/timeline.css +23 -5
- package/components/toggle-group/toggle-group.css +4 -1
- package/components/toggle-scheme/toggle-scheme.css +4 -1
- package/components/tree/tree.class.js +24 -4
- package/components/tree/tree.test.js +108 -0
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +83 -83
- package/package.json +3 -3
- package/styles/api/layout.css +7 -0
- package/styles/api/text.css +9 -5
- package/styles/index.css +11 -2
- package/styles/prose.css +8 -0
- package/styles/resets.css +5 -5
- package/styles/themes.css +8 -1
- package/styles/tokens.css +3 -3
- package/styles/type/elements.css +73 -0
- package/styles/type/roles.css +14 -49
- package/styles/type/scale.css +0 -5
- package/styles/typography.css +3 -3
|
@@ -281,11 +281,28 @@ export class UITreeItem extends UIElement {
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
connected() {
|
|
284
|
-
|
|
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
|
-
|
|
373
|
+
row.setAttribute('aria-expanded', String(this.open));
|
|
356
374
|
} else {
|
|
357
|
-
|
|
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
|
+
});
|