@adia-ai/web-components 0.6.20 → 0.6.22

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 (252) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/components/accordion/accordion-item.a2ui.json +20 -3
  3. package/components/accordion/accordion-item.yaml +24 -0
  4. package/components/accordion/accordion.a2ui.json +1 -1
  5. package/components/accordion/accordion.d.ts +8 -1
  6. package/components/accordion/accordion.yaml +15 -2
  7. package/components/action-list/action-item.a2ui.json +19 -3
  8. package/components/action-list/action-item.yaml +24 -0
  9. package/components/action-list/action-list.a2ui.json +12 -2
  10. package/components/action-list/action-list.yaml +13 -3
  11. package/components/agent-artifact/agent-artifact.a2ui.json +9 -2
  12. package/components/agent-artifact/agent-artifact.d.ts +1 -1
  13. package/components/agent-artifact/agent-artifact.yaml +17 -3
  14. package/components/agent-feedback-bar/agent-feedback-bar.a2ui.json +9 -2
  15. package/components/agent-feedback-bar/agent-feedback-bar.d.ts +8 -1
  16. package/components/agent-feedback-bar/agent-feedback-bar.yaml +19 -3
  17. package/components/agent-questions/agent-questions.a2ui.json +8 -2
  18. package/components/agent-questions/agent-questions.d.ts +8 -1
  19. package/components/agent-questions/agent-questions.yaml +19 -3
  20. package/components/agent-reasoning/agent-reasoning.yaml +9 -1
  21. package/components/agent-suggestions/agent-suggestions.a2ui.json +9 -2
  22. package/components/agent-suggestions/agent-suggestions.d.ts +7 -1
  23. package/components/agent-suggestions/agent-suggestions.yaml +18 -3
  24. package/components/agent-trace/agent-trace.a2ui.json +9 -2
  25. package/components/agent-trace/agent-trace.d.ts +1 -1
  26. package/components/agent-trace/agent-trace.yaml +16 -3
  27. package/components/alert/alert.a2ui.json +1 -1
  28. package/components/alert/alert.css +8 -0
  29. package/components/alert/alert.d.ts +9 -1
  30. package/components/alert/alert.yaml +16 -2
  31. package/components/aside/aside.a2ui.json +7 -1
  32. package/components/aside/aside.yaml +33 -2
  33. package/components/avatar/avatar-group.a2ui.json +20 -3
  34. package/components/avatar/avatar-group.yaml +24 -0
  35. package/components/avatar/avatar.a2ui.json +1 -1
  36. package/components/avatar/avatar.d.ts +7 -1
  37. package/components/avatar/avatar.yaml +14 -2
  38. package/components/badge/badge.a2ui.json +1 -1
  39. package/components/badge/badge.d.ts +7 -1
  40. package/components/badge/badge.yaml +14 -2
  41. package/components/block/block.a2ui.json +9 -4
  42. package/components/block/block.d.ts +9 -3
  43. package/components/block/block.yaml +25 -5
  44. package/components/block/class.js +23 -0
  45. package/components/breadcrumb/breadcrumb.a2ui.json +5 -1
  46. package/components/breadcrumb/breadcrumb.yaml +33 -2
  47. package/components/button/button.a2ui.json +22 -2
  48. package/components/button/button.yaml +21 -3
  49. package/components/calendar-picker/calendar-picker.a2ui.json +1 -1
  50. package/components/calendar-picker/calendar-picker.yaml +13 -2
  51. package/components/canvas/canvas.a2ui.json +6 -2
  52. package/components/canvas/canvas.yaml +20 -3
  53. package/components/card/card.css +23 -2
  54. package/components/card/card.yaml +12 -0
  55. package/components/chart/chart.css +4 -2
  56. package/components/chart/chart.yaml +9 -1
  57. package/components/chart-legend/chart-legend.yaml +7 -1
  58. package/components/chat-thread/chat-thread.a2ui.json +6 -2
  59. package/components/chat-thread/chat-thread.d.ts +8 -1
  60. package/components/chat-thread/chat-thread.yaml +21 -3
  61. package/components/check/check.a2ui.json +13 -3
  62. package/components/check/check.yaml +18 -3
  63. package/components/code/code.a2ui.json +1 -1
  64. package/components/code/code.yaml +13 -2
  65. package/components/col/class.js +39 -0
  66. package/components/col/col.a2ui.json +12 -5
  67. package/components/col/col.d.ts +12 -4
  68. package/components/col/col.yaml +27 -7
  69. package/components/color-input/color-input.yaml +27 -1
  70. package/components/color-picker/color-picker.a2ui.json +8 -2
  71. package/components/color-picker/color-picker.yaml +15 -3
  72. package/components/command/command.a2ui.json +7 -2
  73. package/components/command/command.d.ts +9 -1
  74. package/components/command/command.yaml +39 -3
  75. package/components/demo-toggle/demo-toggle.yaml +7 -1
  76. package/components/description-list/description-list.a2ui.json +5 -1
  77. package/components/description-list/description-list.yaml +11 -2
  78. package/components/divider/divider.a2ui.json +1 -1
  79. package/components/divider/divider.d.ts +8 -1
  80. package/components/divider/divider.yaml +15 -2
  81. package/components/drawer/drawer.yaml +32 -1
  82. package/components/embed/embed.a2ui.json +1 -1
  83. package/components/embed/embed.d.ts +8 -1
  84. package/components/embed/embed.yaml +17 -2
  85. package/components/empty-state/empty-state.a2ui.json +19 -2
  86. package/components/empty-state/empty-state.css +14 -0
  87. package/components/empty-state/empty-state.d.ts +3 -1
  88. package/components/empty-state/empty-state.yaml +50 -3
  89. package/components/feed/feed-item.a2ui.json +21 -3
  90. package/components/feed/feed-item.yaml +25 -0
  91. package/components/feed/feed.a2ui.json +23 -3
  92. package/components/feed/feed.css +9 -4
  93. package/components/feed/feed.yaml +26 -0
  94. package/components/fields/fields.yaml +7 -1
  95. package/components/footer/footer.a2ui.json +7 -1
  96. package/components/footer/footer.yaml +27 -2
  97. package/components/grid/class.js +57 -0
  98. package/components/grid/grid.a2ui.json +3 -3
  99. package/components/grid/grid.d.ts +3 -3
  100. package/components/grid/grid.yaml +22 -8
  101. package/components/header/header.a2ui.json +8 -1
  102. package/components/header/header.yaml +30 -2
  103. package/components/heatmap/heatmap.yaml +7 -1
  104. package/components/icon/icon.a2ui.json +9 -2
  105. package/components/icon/icon.d.ts +1 -1
  106. package/components/icon/icon.yaml +32 -3
  107. package/components/image/image.yaml +7 -1
  108. package/components/input/input.css +8 -1
  109. package/components/input/input.yaml +29 -1
  110. package/components/inspector/inspector.a2ui.json +7 -2
  111. package/components/inspector/inspector.d.ts +9 -1
  112. package/components/inspector/inspector.yaml +23 -3
  113. package/components/kbd/kbd.a2ui.json +1 -1
  114. package/components/kbd/kbd.d.ts +1 -1
  115. package/components/kbd/kbd.yaml +11 -2
  116. package/components/list/list-item.a2ui.json +21 -3
  117. package/components/list/list-item.yaml +25 -0
  118. package/components/list/list.a2ui.json +7 -2
  119. package/components/list/list.d.ts +9 -1
  120. package/components/list/list.yaml +21 -3
  121. package/components/menu/menu-divider.a2ui.json +17 -3
  122. package/components/menu/menu-divider.yaml +35 -0
  123. package/components/menu/menu-item.a2ui.json +19 -3
  124. package/components/menu/menu-item.yaml +42 -0
  125. package/components/menu/menu.a2ui.json +7 -1
  126. package/components/menu/menu.yaml +33 -2
  127. package/components/modal/modal.a2ui.json +7 -2
  128. package/components/modal/modal.d.ts +10 -1
  129. package/components/modal/modal.yaml +48 -3
  130. package/components/nav/nav.a2ui.json +16 -2
  131. package/components/nav/nav.yaml +40 -3
  132. package/components/nav-group/nav-group.a2ui.json +12 -2
  133. package/components/nav-group/nav-group.yaml +37 -3
  134. package/components/nav-item/nav-item.a2ui.json +13 -2
  135. package/components/nav-item/nav-item.yaml +40 -3
  136. package/components/noodles/noodles.a2ui.json +12 -2
  137. package/components/noodles/noodles.yaml +14 -3
  138. package/components/option-card/option-card.yaml +27 -1
  139. package/components/otp-input/otp-input.yaml +24 -1
  140. package/components/page/page.a2ui.json +7 -1
  141. package/components/page/page.yaml +9 -2
  142. package/components/pagination/pagination.a2ui.json +1 -1
  143. package/components/pagination/pagination.d.ts +1 -1
  144. package/components/pagination/pagination.yaml +13 -2
  145. package/components/pane/pane.a2ui.json +1 -1
  146. package/components/pane/pane.d.ts +7 -1
  147. package/components/pane/pane.yaml +33 -2
  148. package/components/pipeline-status/pipeline-status.a2ui.json +6 -2
  149. package/components/pipeline-status/pipeline-status.d.ts +1 -1
  150. package/components/pipeline-status/pipeline-status.yaml +18 -3
  151. package/components/popover/popover.yaml +30 -1
  152. package/components/progress/progress.a2ui.json +1 -1
  153. package/components/progress/progress.d.ts +1 -1
  154. package/components/progress/progress.yaml +13 -2
  155. package/components/progress-row/progress-row.a2ui.json +12 -2
  156. package/components/progress-row/progress-row.yaml +13 -3
  157. package/components/radio/radio.a2ui.json +1 -1
  158. package/components/radio/radio.yaml +9 -1
  159. package/components/range/range.yaml +7 -1
  160. package/components/rating/rating.a2ui.json +4 -1
  161. package/components/rating/rating.yaml +10 -2
  162. package/components/richtext/richtext.a2ui.json +6 -2
  163. package/components/richtext/richtext.d.ts +9 -1
  164. package/components/richtext/richtext.yaml +20 -3
  165. package/components/row/class.js +34 -0
  166. package/components/row/row.a2ui.json +11 -5
  167. package/components/row/row.d.ts +11 -4
  168. package/components/row/row.yaml +25 -7
  169. package/components/search/search.a2ui.json +5 -1
  170. package/components/search/search.yaml +11 -2
  171. package/components/section/section.a2ui.json +7 -1
  172. package/components/section/section.yaml +36 -2
  173. package/components/segment/segment.a2ui.json +8 -2
  174. package/components/segment/segment.d.ts +7 -1
  175. package/components/segment/segment.yaml +16 -3
  176. package/components/segmented/segmented.a2ui.json +6 -1
  177. package/components/segmented/segmented.css +5 -0
  178. package/components/segmented/segmented.yaml +12 -2
  179. package/components/select/select.a2ui.json +1 -1
  180. package/components/select/select.yaml +34 -2
  181. package/components/skeleton/skeleton.a2ui.json +7 -2
  182. package/components/skeleton/skeleton.d.ts +1 -1
  183. package/components/skeleton/skeleton.yaml +17 -3
  184. package/components/slider/slider.yaml +7 -1
  185. package/components/stack/stack.a2ui.json +14 -3
  186. package/components/stack/stack.d.ts +9 -1
  187. package/components/stack/stack.yaml +22 -4
  188. package/components/stat/stat.a2ui.json +6 -2
  189. package/components/stat/stat.css +5 -2
  190. package/components/stat/stat.d.ts +1 -1
  191. package/components/stat/stat.yaml +16 -3
  192. package/components/step-progress/step-progress.yaml +7 -1
  193. package/components/stepper/stepper-item.a2ui.json +20 -3
  194. package/components/stepper/stepper-item.yaml +24 -0
  195. package/components/stepper/stepper.a2ui.json +1 -1
  196. package/components/stepper/stepper.d.ts +1 -1
  197. package/components/stepper/stepper.yaml +13 -2
  198. package/components/stream/stream.a2ui.json +12 -2
  199. package/components/stream/stream.yaml +15 -3
  200. package/components/swatch/swatch.yaml +7 -1
  201. package/components/swiper/swiper.yaml +7 -1
  202. package/components/switch/switch.a2ui.json +6 -2
  203. package/components/switch/switch.yaml +16 -2
  204. package/components/table/cell-types.js +23 -10
  205. package/components/table/class.js +12 -2
  206. package/components/table/table.a2ui.json +6 -1
  207. package/components/table/table.css +68 -1
  208. package/components/table/table.d.ts +3 -1
  209. package/components/table/table.test.js +64 -0
  210. package/components/table/table.yaml +39 -2
  211. package/components/table-toolbar/table-toolbar.yaml +27 -1
  212. package/components/tabs/tab.a2ui.json +17 -3
  213. package/components/tabs/tab.yaml +39 -0
  214. package/components/tabs/tabs.a2ui.json +1 -1
  215. package/components/tabs/tabs.d.ts +1 -1
  216. package/components/tabs/tabs.yaml +36 -2
  217. package/components/tag/tag.a2ui.json +6 -2
  218. package/components/tag/tag.d.ts +9 -1
  219. package/components/tag/tag.yaml +38 -3
  220. package/components/text/class.js +13 -0
  221. package/components/text/text.a2ui.json +9 -2
  222. package/components/text/text.d.ts +1 -1
  223. package/components/text/text.yaml +12 -5
  224. package/components/textarea/textarea.a2ui.json +15 -3
  225. package/components/textarea/textarea.yaml +41 -4
  226. package/components/timeline/timeline-item.a2ui.json +20 -3
  227. package/components/timeline/timeline-item.yaml +24 -0
  228. package/components/timeline/timeline.a2ui.json +8 -1
  229. package/components/timeline/timeline.yaml +9 -2
  230. package/components/toast/toast.a2ui.json +6 -2
  231. package/components/toast/toast.yaml +19 -3
  232. package/components/toggle-group/toggle-group.a2ui.json +13 -3
  233. package/components/toggle-group/toggle-group.d.ts +1 -1
  234. package/components/toggle-group/toggle-group.yaml +19 -4
  235. package/components/toggle-group/toggle-option.a2ui.json +18 -3
  236. package/components/toggle-group/toggle-option.yaml +23 -0
  237. package/components/toggle-scheme/toggle-scheme.yaml +4 -0
  238. package/components/toolbar/toolbar-group.a2ui.json +18 -3
  239. package/components/toolbar/toolbar-group.yaml +23 -0
  240. package/components/toolbar/toolbar.yaml +7 -1
  241. package/components/tooltip/tooltip.yaml +24 -1
  242. package/components/tree/tree-item.a2ui.json +19 -3
  243. package/components/tree/tree-item.yaml +42 -0
  244. package/components/tree/tree.a2ui.json +6 -1
  245. package/components/tree/tree.yaml +31 -2
  246. package/components/upload/upload.yaml +7 -1
  247. package/core/index.js +1 -0
  248. package/core/responsive.d.ts +29 -0
  249. package/core/responsive.js +120 -0
  250. package/core/responsive.test.js +121 -0
  251. package/package.json +1 -1
  252. package/styles/tokens.css +6 -6
@@ -6,6 +6,19 @@ function registerCellType(name, config) {
6
6
  cellTypes[name] = config;
7
7
  }
8
8
 
9
+ // text-overflow:ellipsis on a flex container doesn't reach text nodes —
10
+ // they become anonymous flex items that the container can't truncate.
11
+ // Wrapping in a <span> gives CSS a real element to target.
12
+ function _t(cell, text) {
13
+ let s = cell.firstElementChild;
14
+ if (!s || s.tagName !== 'SPAN') {
15
+ cell.replaceChildren();
16
+ s = cell.appendChild(document.createElement('span'));
17
+ }
18
+ s.textContent = text;
19
+ cell.title = text;
20
+ }
21
+
9
22
  // Sort functions
10
23
  export const sortFns = {
11
24
  basic: (a, b) => (a < b ? -1 : a > b ? 1 : 0),
@@ -22,7 +35,7 @@ export const sortFns = {
22
35
  // 1. text (default)
23
36
  registerCellType('text', {
24
37
  render(value, row, cell) {
25
- cell.textContent = value ?? '';
38
+ _t(cell, String(value ?? ''));
26
39
  },
27
40
  format(value) {
28
41
  return String(value ?? '');
@@ -34,8 +47,7 @@ registerCellType('text', {
34
47
  // 2. number
35
48
  registerCellType('number', {
36
49
  render(value, row, cell) {
37
- cell.textContent =
38
- value != null ? new Intl.NumberFormat().format(value) : '';
50
+ _t(cell, value != null ? new Intl.NumberFormat().format(value) : '');
39
51
  },
40
52
  format(value) {
41
53
  return String(value ?? '');
@@ -47,13 +59,13 @@ registerCellType('number', {
47
59
  // 3. currency
48
60
  registerCellType('currency', {
49
61
  render(value, row, cell, meta) {
50
- cell.textContent =
62
+ _t(cell,
51
63
  value != null
52
64
  ? new Intl.NumberFormat('en-US', {
53
65
  style: 'currency',
54
66
  currency: meta?.currency || 'USD',
55
67
  }).format(value)
56
- : '';
68
+ : '');
57
69
  },
58
70
  format(value, row, meta) {
59
71
  return value != null
@@ -70,7 +82,7 @@ registerCellType('currency', {
70
82
  // 4. percent
71
83
  registerCellType('percent', {
72
84
  render(value, row, cell) {
73
- cell.textContent = value != null ? `${value}%` : '';
85
+ _t(cell, value != null ? `${value}%` : '');
74
86
  },
75
87
  format(value) {
76
88
  return value != null ? `${value}%` : '';
@@ -84,9 +96,9 @@ const dateFmtOpts = { year: 'numeric', month: 'short', day: 'numeric' };
84
96
 
85
97
  registerCellType('date', {
86
98
  render(value, row, cell) {
87
- cell.textContent = value
99
+ _t(cell, value
88
100
  ? new Intl.DateTimeFormat('en-US', dateFmtOpts).format(new Date(value))
89
- : '';
101
+ : '');
90
102
  },
91
103
  format(value) {
92
104
  return value
@@ -108,11 +120,11 @@ const datetimeFmtOpts = {
108
120
 
109
121
  registerCellType('datetime', {
110
122
  render(value, row, cell) {
111
- cell.textContent = value
123
+ _t(cell, value
112
124
  ? new Intl.DateTimeFormat('en-US', datetimeFmtOpts).format(
113
125
  new Date(value)
114
126
  )
115
- : '';
127
+ : '');
116
128
  },
117
129
  format(value) {
118
130
  return value
@@ -188,6 +200,7 @@ registerCellType('avatar', {
188
200
  const span = wrapper.querySelector('span');
189
201
  av.setAttribute('text', String(value ?? ''));
190
202
  span.textContent = String(value ?? '');
203
+ cell.title = String(value ?? '');
191
204
  },
192
205
  format(value) {
193
206
  return String(value ?? '');
@@ -111,6 +111,7 @@ export class UITable extends UIElement {
111
111
  selectable: { type: Boolean, default: false, reflect: true },
112
112
  expandable: { type: Boolean, default: false, reflect: true },
113
113
  striped: { type: Boolean, default: false, reflect: true },
114
+ wrap: { type: Boolean, default: false, reflect: true },
114
115
  raw: { type: Boolean, default: false, reflect: true },
115
116
  density: { type: String, default: 'standard', reflect: true },
116
117
  paginate: { type: Number, default: 0, reflect: true },
@@ -806,13 +807,17 @@ export class UITable extends UIElement {
806
807
  cell.innerHTML = result;
807
808
  }
808
809
  } else if (typeof col.format === 'function') {
809
- cell.textContent = col.format(value, data);
810
+ const t = col.format(value, data);
811
+ cell.appendChild(Object.assign(document.createElement('span'), { textContent: t }));
812
+ cell.title = t;
810
813
  } else {
811
814
  const typeDef = cellTypes[col.type || 'text'];
812
815
  if (typeDef?.render) {
813
816
  typeDef.render(value, data, cell, col.meta);
814
817
  } else {
815
- cell.textContent = value != null ? value : '';
818
+ const t = value != null ? String(value) : '';
819
+ cell.appendChild(Object.assign(document.createElement('span'), { textContent: t }));
820
+ cell.title = t;
816
821
  }
817
822
  }
818
823
 
@@ -827,6 +832,11 @@ export class UITable extends UIElement {
827
832
  cell.setAttribute('data-pinned', col.pinned);
828
833
  }
829
834
 
835
+ // Wrap (per-column opt-in to multi-line cell content)
836
+ if (col.wrap) {
837
+ cell.setAttribute('data-wrap', '');
838
+ }
839
+
830
840
  cells.push(cell);
831
841
  }
832
842
 
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "properties": {
16
16
  "columns": {
17
- "description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.",
17
+ "description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.",
18
18
  "type": "array",
19
19
  "default": []
20
20
  },
@@ -75,6 +75,11 @@
75
75
  "description": "Alternate row background colors for visual scanning.",
76
76
  "type": "boolean",
77
77
  "default": false
78
+ },
79
+ "wrap": {
80
+ "description": "Allow body-cell content to wrap onto multiple lines. Default is single-line with ellipsis truncation (matches the row convention shared by <select-ui> + <nav-item-ui>); long unbreakable strings clip gracefully rather than rewrapping the row. With [wrap], row height auto-grows to fit wrapped content. For surgical opt-in on a single column, set [data-wrap] on the cell / col-def instead.",
81
+ "type": "boolean",
82
+ "default": false
78
83
  }
79
84
  },
80
85
  "required": [
@@ -2,7 +2,7 @@
2
2
  :where(:scope) {
3
3
  /* ── Tokens ── */
4
4
  --table-font-size: var(--a-ui-size);
5
- --table-heading-size: var(--a-ui-sm);
5
+ --table-heading-size: var(--a-ui-xs);
6
6
  --table-heading-weight: var(--a-weight-medium);
7
7
  --table-heading-tracking: 0.06em;
8
8
  --table-heading-fg: var(--a-fg-muted);
@@ -165,6 +165,19 @@
165
165
  letter-spacing: var(--table-heading-tracking);
166
166
  white-space: nowrap;
167
167
  position: relative;
168
+ display: flex;
169
+ align-items: center;
170
+ }
171
+
172
+ /* Label span truncates; sort icon + filter button remain visible.
173
+ Cannot put overflow:hidden on the columnheader itself — it would
174
+ clip the position:absolute filter dropdown. */
175
+ [role="columnheader"] > span {
176
+ flex: 1;
177
+ min-width: 0;
178
+ overflow: hidden;
179
+ text-overflow: ellipsis;
180
+ white-space: nowrap;
168
181
  }
169
182
 
170
183
  /* ═══════ Sortable headers ═══════ */
@@ -216,6 +229,60 @@
216
229
  padding: var(--table-py) var(--table-px);
217
230
  border-bottom: 1px solid var(--table-border);
218
231
  min-width: 0;
232
+ /* Default: single line with ellipsis. Authors opt in to wrapping
233
+ via [wrap] on the host (whole table) or [data-wrap] on a column /
234
+ individual cell. Matches `<select-ui>` / `<nav-item-ui>` single-line
235
+ row convention; long unbreakable strings (URLs, IDs) clip gracefully
236
+ rather than rewrapping the row. */
237
+ white-space: nowrap;
238
+ overflow: hidden;
239
+ text-overflow: ellipsis;
240
+ }
241
+
242
+ /* text-overflow on a flex container doesn't reach text nodes (anonymous
243
+ flex items). Built-in cell types now wrap text in <span>; link type
244
+ creates <a>; avatar/progress composite types use row-ui > span. */
245
+ [data-body] [role="gridcell"] > span,
246
+ [data-body] [role="gridcell"] > a {
247
+ min-width: 0;
248
+ flex: 1;
249
+ overflow: hidden;
250
+ text-overflow: ellipsis;
251
+ white-space: nowrap;
252
+ }
253
+
254
+ [data-body] [role="gridcell"] > row-ui {
255
+ min-width: 0;
256
+ overflow: hidden;
257
+ }
258
+
259
+ [data-body] [role="gridcell"] > row-ui > span {
260
+ flex: 1;
261
+ min-width: 0;
262
+ overflow: hidden;
263
+ text-overflow: ellipsis;
264
+ white-space: nowrap;
265
+ }
266
+
267
+ /* Wrap opt-in: per-table (all body cells) or per-cell (one column).
268
+ Restores the legacy `overflow-wrap: anywhere` behavior so wrapped
269
+ content can break inside long unbroken tokens (URLs, IDs). Row
270
+ height auto-grows to fit. */
271
+ :scope[wrap] [data-body] [role="gridcell"],
272
+ [data-body] [role="gridcell"][data-wrap] {
273
+ white-space: normal;
274
+ overflow: visible;
275
+ text-overflow: clip;
276
+ overflow-wrap: anywhere;
277
+ }
278
+
279
+ :scope[wrap] [data-body] [role="gridcell"] > span,
280
+ :scope[wrap] [data-body] [role="gridcell"] > a,
281
+ [data-body] [role="gridcell"][data-wrap] > span,
282
+ [data-body] [role="gridcell"][data-wrap] > a {
283
+ white-space: normal;
284
+ overflow: visible;
285
+ text-overflow: clip;
219
286
  overflow-wrap: anywhere;
220
287
  }
221
288
 
@@ -86,7 +86,7 @@ export interface TableSortEventDetail {
86
86
  export type TableSortEvent = CustomEvent<TableSortEventDetail>;
87
87
 
88
88
  export class UITable extends UIElement {
89
- /** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children. */
89
+ /** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children. */
90
90
  columns: unknown[];
91
91
  /** Row records. Array of plain objects keyed to columns[].key. */
92
92
  data: unknown[];
@@ -108,6 +108,8 @@ export class UITable extends UIElement {
108
108
  sortable: boolean;
109
109
  /** Alternate row background colors for visual scanning. */
110
110
  striped: boolean;
111
+ /** Allow body-cell content to wrap onto multiple lines. Default is single-line with ellipsis truncation (matches the row convention shared by <select-ui> + <nav-item-ui>); long unbreakable strings clip gracefully rather than rewrapping the row. With [wrap], row height auto-grows to fit wrapped content. For surgical opt-in on a single column, set [data-wrap] on the cell / col-def instead. */
112
+ wrap: boolean;
111
113
 
112
114
  addEventListener<K extends keyof HTMLElementEventMap>(
113
115
  type: K,
@@ -172,3 +172,67 @@ describe('table-ui — v0.6.18 loading=skeleton-rows (FB-12 P2)', () => {
172
172
  expect(afterHeader.children.length).toBe(3);
173
173
  });
174
174
  });
175
+
176
+ /**
177
+ * Body-cell wrap default + opt-in paths (PATCH-class behavior change).
178
+ *
179
+ * Default: cells truncate single-line with ellipsis (the CSS rule sets
180
+ * white-space: nowrap; overflow: hidden; text-overflow: ellipsis). Three
181
+ * opt-in surfaces lift the truncation:
182
+ * - host [wrap] attribute (reflected from the `wrap` prop)
183
+ * - col.wrap=true in columns[] → renderer sets [data-wrap] on each
184
+ * body cell in that column
185
+ * - [data-wrap] directly on a hand-authored <td>
186
+ *
187
+ * Tests lock the attribute contract; CSS-rule application is covered by
188
+ * the source-grep stylistic invariants of table.css.
189
+ */
190
+ describe('table-ui — wrap default + opt-in', () => {
191
+ beforeEach(() => { document.body.innerHTML = ''; });
192
+
193
+ it('host [wrap] attribute reflects from the wrap property', async () => {
194
+ const el = mount('<table-ui></table-ui>');
195
+ el.columns = COLS;
196
+ el.data = ROWS;
197
+ await raf();
198
+ expect(el.hasAttribute('wrap')).toBe(false);
199
+ el.wrap = true;
200
+ await raf();
201
+ expect(el.hasAttribute('wrap')).toBe(true);
202
+ el.wrap = false;
203
+ await raf();
204
+ expect(el.hasAttribute('wrap')).toBe(false);
205
+ });
206
+
207
+ it('col.wrap=true marks body cells in that column with [data-wrap]', async () => {
208
+ const el = mount('<table-ui></table-ui>');
209
+ el.columns = [
210
+ { key: 'id', label: 'ID' },
211
+ { key: 'name', label: 'Name', wrap: true },
212
+ { key: 'email', label: 'Email' },
213
+ ];
214
+ el.data = ROWS;
215
+ await raf();
216
+ const bodyRows = el.querySelectorAll(':scope > [data-body] > [role="row"]');
217
+ expect(bodyRows.length).toBe(2);
218
+ for (const row of bodyRows) {
219
+ const cells = row.querySelectorAll('[role="gridcell"]');
220
+ expect(cells.length).toBe(3);
221
+ expect(cells[0].hasAttribute('data-wrap')).toBe(false);
222
+ expect(cells[1].hasAttribute('data-wrap')).toBe(true);
223
+ expect(cells[2].hasAttribute('data-wrap')).toBe(false);
224
+ }
225
+ });
226
+
227
+ it('omitting col.wrap leaves all body cells without [data-wrap]', async () => {
228
+ const el = mount('<table-ui></table-ui>');
229
+ el.columns = COLS;
230
+ el.data = ROWS;
231
+ await raf();
232
+ const cells = el.querySelectorAll(':scope > [data-body] [role="gridcell"]');
233
+ expect(cells.length).toBeGreaterThan(0);
234
+ for (const cell of cells) {
235
+ expect(cell.hasAttribute('data-wrap')).toBe(false);
236
+ }
237
+ });
238
+ });
@@ -19,7 +19,7 @@ composes:
19
19
  - badge-ui
20
20
  props:
21
21
  columns:
22
- description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.
22
+ description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.
23
23
  type: array
24
24
  default: []
25
25
  data:
@@ -76,6 +76,17 @@ props:
76
76
  description: Alternate row background colors for visual scanning.
77
77
  type: boolean
78
78
  default: false
79
+ wrap:
80
+ description: >-
81
+ Allow body-cell content to wrap onto multiple lines. Default is
82
+ single-line with ellipsis truncation (matches the row convention
83
+ shared by <select-ui> + <nav-item-ui>); long unbreakable strings
84
+ clip gracefully rather than rewrapping the row. With [wrap], row
85
+ height auto-grows to fit wrapped content. For surgical opt-in on
86
+ a single column, set [data-wrap] on the cell / col-def instead.
87
+ type: boolean
88
+ default: false
89
+ reflect: true
79
90
  events:
80
91
  cell-click:
81
92
  description: Fired when a data cell is clicked. detail carries the cell location + value.
@@ -218,7 +229,33 @@ requiredIcons:
218
229
  - funnel-simple
219
230
  - funnel-simple-fill
220
231
  a2ui:
221
- rules: []
232
+ rules:
233
+ - >-
234
+ Canonical composition: wrap <table-ui> in <card-ui><section bleed>
235
+ for edge-to-edge tables. The [bleed] removes section padding so
236
+ columns span the full card width (see apps/saas/members,
237
+ billing, admin-dashboard).
238
+ - >-
239
+ Pair with <table-toolbar-ui for="<table-id>"> for any table that
240
+ needs search / filter / sort / columns visibility. Do NOT
241
+ re-implement those affordances in the card header — the toolbar
242
+ auto-wires search/filter/sort/columns changes into the bound
243
+ table.
244
+ - >-
245
+ Cells truncate single-line by default (v0.6.21 §403
246
+ truncate-default). Opt out per-table with [wrap] for whole-table
247
+ multiline, or per-cell with [data-wrap] on a single column /
248
+ cell.
249
+ - >-
250
+ Use [raw] in production app consumers — it disables the demo
251
+ seed data so the table renders only consumer-provided rows /
252
+ columns / data props.
253
+ - >-
254
+ Listen for the `sort` event with detail.key + detail.dir (NOT
255
+ .column / .direction). `cell-click` detail carries {key, row,
256
+ value, dataIndex}. Per ADR-0027, table-ui composes check-ui,
257
+ icon-ui, progress-ui, pagination-ui, skeleton-ui, badge-ui —
258
+ consumer pages must explicitly import the ones they use.
222
259
  anti_patterns: []
223
260
  examples:
224
261
  - name: api-key-table
@@ -118,7 +118,33 @@ requiredIcons:
118
118
  - arrow-down
119
119
  - caret-up-down
120
120
  a2ui:
121
- rules: []
121
+ rules:
122
+ - >-
123
+ Pair <table-toolbar-ui> with <table-ui> via [for="<table-id>"]
124
+ (or rely on first-sibling fallback when both are inside the
125
+ same parent). One toolbar per table. Do NOT also use
126
+ <card-ui>'s <header> on the same card — that produces a doubled
127
+ chrome row.
128
+ - >-
129
+ All four affordances (search, filter, sort, columns) default ON.
130
+ Opt out individually via [no-search] / [no-filter] / [no-sort]
131
+ / [no-columns]. The previous [searchable] / [filterable]
132
+ attributes are deprecated — do NOT emit them.
133
+ - >-
134
+ Place the toolbar ABOVE the <card-ui> containing the table-ui,
135
+ or use [variant="card"] when standing alone outside a card-ui
136
+ parent (the variant wraps the toolbar in card-style chrome).
137
+ - >-
138
+ Use slot="action" (or [slot="actions"]) for trailing primary
139
+ buttons (Invite, Export, +New). Use [text] / [count] props for
140
+ the left cluster, or slotted [slot="title"] / [slot="count"]
141
+ when content is markup (a <span> + <badge-ui>, etc.).
142
+ - >-
143
+ Listen for toolbar events (`search`, `filter-change`,
144
+ `sort-change`, `columns-change`) only to mirror state to URL /
145
+ persistence / analytics. The toolbar already wires its changes
146
+ into the bound table — you don't need to manually update the
147
+ table.
122
148
  anti_patterns: []
123
149
  examples:
124
150
  - name: members-toolbar
@@ -44,12 +44,26 @@
44
44
  "composes": [],
45
45
  "events": {},
46
46
  "examples": [],
47
- "keywords": [],
47
+ "keywords": [
48
+ "tab",
49
+ "tab-panel",
50
+ "view-switcher-child",
51
+ "tabs-child"
52
+ ],
48
53
  "name": "UITab",
49
- "related": [],
54
+ "related": [
55
+ "Tabs",
56
+ "Nav",
57
+ "Segmented"
58
+ ],
50
59
  "slots": {},
51
60
  "states": [],
52
- "synonyms": {},
61
+ "synonyms": {
62
+ "tab": [
63
+ "view-tab",
64
+ "switcher-tab"
65
+ ]
66
+ },
53
67
  "tag": "tab-ui",
54
68
  "tokens": {},
55
69
  "traits": [],
@@ -31,3 +31,42 @@ props:
31
31
  description: Whether the tab is selectable.
32
32
  type: boolean
33
33
  default: false
34
+
35
+ a2ui:
36
+ rules:
37
+ - >-
38
+ <tab-ui> only renders inside <tabs-ui>. Never use it standalone.
39
+ The parent reads each tab's [text] + [icon] + [value] to render
40
+ the button strip; the tab's default slot is the panel content
41
+ that the parent auto-hides when inactive.
42
+ - >-
43
+ [value] is required and must be unique among siblings — the
44
+ parent <tabs-ui> matches its own [value] against each
45
+ <tab-ui>[value] to decide which is active. [text] is the visible
46
+ button label; optional [icon] is a Phosphor icon name shown
47
+ leading the label.
48
+ - >-
49
+ Use the default slot for panel content. Inactive <tab-ui>
50
+ children are auto-hidden by the parent's [hidden] toggling; do
51
+ NOT set [hidden] yourself unless you want to remove the button
52
+ from the strip entirely (i.e. a temporarily-disabled tab whose
53
+ strip button shouldn't render at all).
54
+ - >-
55
+ Use [disabled] to keep a tab visible in the strip but
56
+ non-selectable. Do not pair <tab-ui> with <button-ui> wrappers —
57
+ the strip button is parent-rendered. Do not nest <tab-ui> inside
58
+ another <tab-ui>.
59
+
60
+ keywords:
61
+ - tab
62
+ - tab-panel
63
+ - view-switcher-child
64
+ - tabs-child
65
+
66
+ related:
67
+ - Tabs
68
+ - Nav
69
+ - Segmented
70
+
71
+ synonyms:
72
+ tab: [view-tab, switcher-tab]
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/Tabs.json",
4
4
  "title": "Tabs",
5
- "description": "Tabbed panel switcher. Renders button strip from child tab-ui elements.",
5
+ "description": "Tabbed panel switcher. Renders a button strip from child <tab-ui> elements and toggles their associated panel via the active attribute. Use for switching between equivalent peer views in one region; for navigation between routes use <nav-ui> instead.",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `<tabs-ui>` — Tabbed panel switcher. Renders button strip from child tab-ui elements.
2
+ * `<tabs-ui>` — Tabbed panel switcher. Renders a button strip from child <tab-ui> elements and toggles their associated panel via the active attribute. Use for switching between equivalent peer views in one region; for navigation between routes use <nav-ui> instead.
3
3
  *
4
4
  * @see https://ui-kit.exe.xyz/site/components/tabs
5
5
  *
@@ -6,7 +6,11 @@ tag: tabs-ui
6
6
  component: Tabs
7
7
  category: container
8
8
  version: 1
9
- description: Tabbed panel switcher. Renders button strip from child tab-ui elements.
9
+ description: >-
10
+ Tabbed panel switcher. Renders a button strip from child <tab-ui>
11
+ elements and toggles their associated panel via the active attribute.
12
+ Use for switching between equivalent peer views in one region; for
13
+ navigation between routes use <nav-ui> instead.
10
14
  # Per ADR-0027 — primitives that programmatically create other primitives
11
15
  # do NOT auto-import them. Consumer (or demo shell) must explicitly import.
12
16
  composes:
@@ -76,7 +80,37 @@ tokens:
76
80
  --tabs-radius:
77
81
  description: Override base border radius
78
82
  a2ui:
79
- rules: []
83
+ rules:
84
+ - >-
85
+ Decision rule: use <tabs-ui> when switching VIEWS within the same
86
+ logical page (no route change, no URL change). For navigating
87
+ AWAY (different page / route / anchor), use <nav-ui> instead. For
88
+ a form-control segmented selector that returns a value, use
89
+ <segmented-ui>.
90
+ - >-
91
+ Children of <tabs-ui> MUST be <tab-ui> elements. The button strip
92
+ is rendered from each child's [text] + optional [icon]; the
93
+ parent auto-toggles [hidden] on inactive <tab-ui> children. Do
94
+ not place arbitrary markup directly inside <tabs-ui> — wrap it
95
+ in <tab-ui>.
96
+ - >-
97
+ Canonical placements: inside <card-ui>'s <header> for in-card
98
+ section switching; inside <editor-canvas-toolbar> for editor
99
+ sub-views (see the editor sub-views recipe in patterns-recipes.md);
100
+ or standalone as an in-page switcher. When standalone, wire
101
+ sibling <div data-view="…"> panels via the `change` event
102
+ (detail.value); for tabs whose content lives inside the <tab-ui>
103
+ child, the auto-hide handles visibility.
104
+ - >-
105
+ Set [value] to the initially active tab. If omitted, the first
106
+ non-disabled <tab-ui> becomes active on connect. Set
107
+ [orientation="vertical"] for left-rail tab strips.
108
+ - >-
109
+ Variant caveat: only [variant="bordered"] is implemented (adds a
110
+ subtle divider). [variant="underline"] is widely used in source
111
+ but is equivalent to default (no-op). [variant="pills"] and
112
+ [variant="segmented"] are declared in the enum but NOT styled —
113
+ do NOT emit them; for a form-style selector use <segmented-ui>.
80
114
  anti_patterns: []
81
115
  examples:
82
116
  - name: tabs-panels
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/Tag.json",
4
4
  "title": "Tag",
5
- "description": "Inline tag/chip with optional dismiss button.",
5
+ "description": "Inline INTERACTIVE chip / pill with optional dismiss affordance.\nText rendered via CSS `attr(text)`. Use for filter chips\n(removable=true + `remove` event), autocomplete tokens, and\nuser-managed labels. Distinct from <badge-ui>, which is READ-ONLY\nand includes the [status] shorthand for Beta / New / Deprecated\nmarkers — badge-ui has no remove event. For navigation grouping\nuse <nav-group-ui>; for inline command actions use\n<action-list-ui>.\n",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -97,7 +97,11 @@
97
97
  "pill"
98
98
  ],
99
99
  "name": "UITag",
100
- "related": [],
100
+ "related": [
101
+ "Badge",
102
+ "NavGroup",
103
+ "ActionList"
104
+ ],
101
105
  "slots": {},
102
106
  "states": [
103
107
  {
@@ -1,5 +1,13 @@
1
1
  /**
2
- * `<tag-ui>` — Inline tag/chip with optional dismiss button.
2
+ * `<tag-ui>` — Inline INTERACTIVE chip / pill with optional dismiss affordance.
3
+ Text rendered via CSS `attr(text)`. Use for filter chips
4
+ (removable=true + `remove` event), autocomplete tokens, and
5
+ user-managed labels. Distinct from <badge-ui>, which is READ-ONLY
6
+ and includes the [status] shorthand for Beta / New / Deprecated
7
+ markers — badge-ui has no remove event. For navigation grouping
8
+ use <nav-group-ui>; for inline command actions use
9
+ <action-list-ui>.
10
+
3
11
  *
4
12
  * @see https://ui-kit.exe.xyz/site/components/tag
5
13
  *
@@ -6,7 +6,15 @@ tag: tag-ui
6
6
  component: Tag
7
7
  category: display
8
8
  version: 1
9
- description: Inline tag/chip with optional dismiss button.
9
+ description: |
10
+ Inline INTERACTIVE chip / pill with optional dismiss affordance.
11
+ Text rendered via CSS `attr(text)`. Use for filter chips
12
+ (removable=true + `remove` event), autocomplete tokens, and
13
+ user-managed labels. Distinct from <badge-ui>, which is READ-ONLY
14
+ and includes the [status] shorthand for Beta / New / Deprecated
15
+ markers — badge-ui has no remove event. For navigation grouping
16
+ use <nav-group-ui>; for inline command actions use
17
+ <action-list-ui>.
10
18
  props:
11
19
  disabled:
12
20
  description: Disables interaction and dims the tag.
@@ -64,7 +72,31 @@ tokens: {}
64
72
  requiredIcons:
65
73
  - x
66
74
  a2ui:
67
- rules: []
75
+ rules:
76
+ - >-
77
+ Use <tag-ui> for INTERACTIVE / DISMISSABLE labels — filter chips,
78
+ autocomplete tokens, user-managed labels. Tag-ui fires a `remove`
79
+ event when [removable] is set. For READ-ONLY status flags
80
+ (counts, Beta / New / Deprecated markers, notification dots) use
81
+ <badge-ui> instead — badge has no remove event and includes the
82
+ [status] shorthand.
83
+ - >-
84
+ Set [removable] and listen for the `remove` event (detail:
85
+ {text, value}) when the tag represents a user-applied filter or
86
+ selection that can be cleared.
87
+ - >-
88
+ [variant] maps to semantic state of the underlying record:
89
+ success = active / approved, warning = pending,
90
+ danger = blocked / error, info = neutral-emphasis. Default
91
+ (no variant) for unlabeled categories.
92
+ - >-
93
+ Use [size="sm"] for inline-with-text contexts (doc page headers,
94
+ table cells, badges next to titles); [size="md"] (default) for
95
+ filter-bar chips and standalone tag rows.
96
+ - >-
97
+ Group multiple tags inside a <row-ui gap="2"> — never stack them
98
+ vertically; vertical lists of dismissable items are an
99
+ <action-list-ui> use case, not <tag-ui>.
68
100
  anti_patterns: []
69
101
  examples:
70
102
  - name: basic-tag
@@ -131,4 +163,7 @@ synonyms:
131
163
  pill:
132
164
  - badge
133
165
  - tag
134
- related: []
166
+ related:
167
+ - Badge
168
+ - NavGroup
169
+ - ActionList