@jirawatpyk/aura-react 5.85.0 → 5.87.0

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/README.md CHANGED
@@ -248,6 +248,27 @@ DataTable with sort and page in the URL (4.17): `onStateChange={({ sort, page })
248
248
  - **Time zone**: `timeZone` on AuraProvider, DatePicker, DateRangePicker and Calendar decides "today" (the marker, `min`/`max="today"`, the first month shown); or pass `today` as an ISO date. `todayIn('Asia/Bangkok')` is exported from the root and `/server`.
249
249
  - **Toasts** queue past three instead of dropping: six in a row all show, in order, three at a time.
250
250
 
251
+ ## 5.87 — Chamber-OS 203–205 (one full-width button shape, DataTable stacked list and headers)
252
+
253
+ - **Full-width buttons** (#203): one shape wherever they sit — PageHeader's `actionsFullWidthBelow` actions, a `fullWidth` / `fullWidth="below-sm" | "below-md"` Button (in an Alert, a Card, a stacked card's `footer`), a phone sheet's or drawer's footer button. At the default 44px a one-line label is the lozenge; a label that wraps is a rounded box with 22px corners (half the 44px button), not a stretched pill. Before, PageHeader's full-width actions were `--aura-radius-md` boxes even on one line (5.78) while every other full-width button stayed a pill. CSS only (the corners are `min(var(--aura-button-radius), 22px)`, so a theme with square buttons keeps them square); no JS, no flash. Above the breakpoint buttons are pills as before. In the pinned bottom strips (ActionBar `edge`, PageHeader `actionsBarBelow`) buttons share the row or take one each; a label too long even for its own row wraps there too.
254
+ - **DataTable `stackStyle`** (#204): `'cards'` (default, as before) or `'list'` — below `stackBelow` the rows keep the stacked layout (title, pill and actions on the first line, fields two to a line) but are one list divided by `--aura-border-default` rules: no frame, radius or gap per row; the select-all line, totals row and pager are unframed, after a rule. So a DataTable sits inside a framed `Card` (with `bleed`, level with the Card's content) without cards in a card. The grid above `stackBelow` is unchanged; same in the server HTML (a class).
255
+
256
+ ```tsx
257
+ <Card title="Invoices">
258
+ <DataTable label="Invoices" stackBelow={640} stackStyle="list" bleed columns={cols} rows={rows} />
259
+ </Card>
260
+ ```
261
+
262
+ - **DataTable stacked headers** (#205): while the cards show, the grid's column headers are out of the accessibility tree (`display: none`), card-only `grid: false` columns included; only the select-all line stays (when it shows). Each card field's label is now read with its value ("Amount 12,400.00 THB") instead of being silent generated content beside a hidden header. A column whose `label` is `''` has a header with no name — before, it fell back to the column `key` ("cardSummary"); `actions` columns still read "Actions". A column the grid shows should keep a `label` (axe's best-practice `empty-table-header` flags an unnamed header); `''` is for card-only (`grid: false`) columns. A tab stop left on a header when the table narrows into cards moves to the first card. The cards have no sort or column menu (they were never reachable from the keyboard there); sort them with your own control bound to `sort` / `onSortChange` while stacked.
263
+ - **Long action labels:** a label longer than the phone's row wraps in PageHeader's `actionsFullWidthBelow` actions (with no IconButton beside it), in its `actionsBar` / `actionsBarBelow` strip and in ActionBar's edge strip — inside its button, which becomes the 22px-cornered box (5.87) — instead of pushing the page sideways. Labels that fit still share the row or take one each as before (the share is `min-width: fit-content`, falling back to the old `max-content` in browsers without it). On one line a button keeps its height: a `size="sm"` one in the page, and 44px in a strip on a phone whatever the density (5.54/5.55).
264
+ - **Tests:** the layout-before-hydration pages hold their first paint until the whole server HTML is parsed (`<link rel="expect" blocking="render">`). A half-parsed paint — a card row whose pill (`order: 2`) isn't parsed yet puts a field on its first line — failed the check at random, about one run in four on a slow machine. A negative control (a block added after hydration) checks that shifts are still counted.
265
+
266
+ ## 5.86 — Chamber-OS 199–202 (Card names, DataTable column numbering, EmptyState icons)
267
+
268
+ - **Card `titleId`** (#199): a titled Card labels its root by its title (`aria-labelledby`) only where the root takes a name — the default `section`, `article`, `aside`, `nav`, `main`, `search`, `form`, `fieldset`, `details`, `dialog`, `figure`, `li`, `ul`, `ol`, `menu`, or any root with a `role` (the first token of a role list; except `generic`, `none`, `presentation` and the other roles that prohibit a name). With `as="div"` and no role the heading keeps its `id` and the root stays unlabelled; before, it carried an `aria-labelledby` that ARIA 1.2 prohibits (axe `aria-prohibited-attr`) and screen readers ignored. A step card in a list of steps can now use `title`. Same from `/server`.
269
+ - **DataTable `aria-colcount` / `aria-colindex` with `grid: false`** (#200, a 5.85 gap): in the grid (and before the first measure) card-only columns are left out of the count and the numbering, so the visible columns read 1…N of N; in the stacked cards every column counts. The hidden cells carry no `aria-colindex`.
270
+ - **EmptyState `icon={false}`** (#201): no icon and no icon circle, for a short placeholder in a table cell or a small panel. **`tone="danger"` defaults to `triangle-alert`** (#202) instead of the inbox, so a failed read doesn't look like "nothing here"; a passed `icon` still wins.
271
+
251
272
  ## 5.85 — Chamber-OS 198 (DataTable columns for the cards only)
252
273
 
253
274
  - **DataTable column `grid: false`** (#198): the column shows only in the stacked cards (below `stackBelow`), never in the grid — the reverse of `card: 'hide'`. Its place in the card still follows `card` (`'title'`, `'pill'`, a field…), so a grid that puts the status under the number can give the phone card its own title and pill. It spends no width-query level (any `hideBelow` on it is ignored), takes no width in the grid, isn't pinned, isn't in the columns button and is skipped by the arrow keys there. Without `stackBelow` it isn't rendered. Replaces the `hideBelow: 100_000` workaround.
@@ -284,8 +305,8 @@ DataTable with sort and page in the URL (4.17): `onStateChange={({ sort, page })
284
305
 
285
306
  ## 5.78 — visual audit, set 2 (phones)
286
307
 
287
- - **Bottom strips on a phone follow one rule:** ActionBar's edge strip (`look="edge"`, `edgeBelow`) now lays out as PageHeader's bar (5.48) — the status on its own row at the top, the actions sharing the row under it (an IconButton keeps its square; labels too long to share take a row each, as PageHeader's) — and both strips are 16px from either side, the page's phone gutter (they were 16 and 12). `edgeBelow="md"` follows it up to 768px. A single label wider than the screen still overflows, as in PageHeader's strip — keep bar labels short.
288
- - **PageHeader `actionsFullWidthBelow`:** on a phone its buttons are `--aura-radius-md` boxes, so a label that wraps to two lines is a rounded box, not a lozenge (the ⋯ IconButton stays round) — also the header's own actions beside an `actionsBar` strip. The bottom strip keeps its pill buttons.
308
+ - **Bottom strips on a phone follow one rule:** ActionBar's edge strip (`look="edge"`, `edgeBelow`) now lays out as PageHeader's bar (5.48) — the status on its own row at the top, the actions sharing the row under it (an IconButton keeps its square; labels too long to share take a row each, as PageHeader's) — and both strips are 16px from either side, the page's phone gutter (they were 16 and 12). `edgeBelow="md"` follows it up to 768px. A single label wider than the screen wraps inside its button (5.87; before, it overflowed).
309
+ - **PageHeader `actionsFullWidthBelow`:** on a phone its buttons are `--aura-radius-md` boxes, so a label that wraps to two lines is a rounded box, not a lozenge (the ⋯ IconButton stays round) — also the header's own actions beside an `actionsBar` strip. The bottom strip keeps its pill buttons (5.87: one shape for full-width buttons — 22px corners, the same lozenge on one line).
289
310
  - **A document number in a PageHeader title stays whole:** in a plain-text title, a short token with a digit and a hyphen ("Invoice SC-2026-900001") never breaks at its hyphens; the line breaks before it. The text is unchanged (a `.aura-nowrap` span, a utility you can use too).
290
311
  - **DataTable cards with `columnControls`:** the column picker's slot no longer squeezes the card's title (it took 48px of the first line).
291
312
  - **StatusTile:** a host or a path in a plain-text title ("api.example.co.th") breaks after a dot or a slash, not in the middle of a label.
@@ -843,6 +843,45 @@ window.Aura = (() => {
843
843
  close
844
844
  );
845
845
  }
846
+ var NAMED_ROOTS = [
847
+ "section",
848
+ "article",
849
+ "aside",
850
+ "nav",
851
+ "main",
852
+ "search",
853
+ "form",
854
+ "fieldset",
855
+ "details",
856
+ "dialog",
857
+ "figure",
858
+ "li",
859
+ "ul",
860
+ "ol",
861
+ "menu"
862
+ ];
863
+ var NAMELESS_ROLES = [
864
+ "generic",
865
+ "none",
866
+ "presentation",
867
+ "paragraph",
868
+ "caption",
869
+ "code",
870
+ "deletion",
871
+ "emphasis",
872
+ "insertion",
873
+ "strong",
874
+ "subscript",
875
+ "superscript",
876
+ "term",
877
+ "time",
878
+ "mark"
879
+ ];
880
+ function cardTakesName(as, role) {
881
+ const first = role ? role.trim().split(/\s+/)[0].toLowerCase() : "";
882
+ if (first) return NAMELESS_ROLES.indexOf(first) < 0;
883
+ return NAMED_ROOTS.indexOf(as) >= 0;
884
+ }
846
885
  function cardElement(props, ref) {
847
886
  const creative = props.variant === "creative";
848
887
  const rest = omit(props, [
@@ -872,8 +911,10 @@ window.Aura = (() => {
872
911
  props.flushBelow && "aura-card--flush-below-" + props.flushBelow,
873
912
  props.className
874
913
  ),
875
- /* A titled card is labelled by its title; otherwise the caller's aria-labelledby stays (5.8). */
876
- "aria-labelledby": !head && props.title && props.titleId ? props.titleId : props["aria-labelledby"]
914
+ /* A titled card is labelled by its title; otherwise the caller's aria-labelledby stays (5.8). 5.86 (Chamber-OS
915
+ * 199): only where a name is allowed — a root that takes one (section, article…) or one with a role; a plain
916
+ * div is generic, where ARIA 1.2 prohibits a name. The title keeps its id either way. */
917
+ "aria-labelledby": !head && props.title && props.titleId && cardTakesName(props.as || "section", props.role) ? props.titleId : props["aria-labelledby"]
877
918
  }),
878
919
  head || props.title || props.actions ? /* @__PURE__ */ React4.createElement("div", { className: "aura-card__head" }, /* @__PURE__ */ React4.createElement("div", { className: "aura-card__heading" }, head ? props.header : null, !head && props.title ? h2("h" + (props.headingLevel || 3), { className: "aura-card__title", id: props.titleId }, props.title) : null, !head && props.description ? /* @__PURE__ */ React4.createElement("p", { className: "aura-card__desc" }, props.description) : null), props.actions ? /* @__PURE__ */ React4.createElement("div", { className: "aura-card__actions" }, props.actions) : null) : null,
879
920
  props.children ? /* @__PURE__ */ React4.createElement("div", { className: "aura-card__body" }, props.children) : null,
@@ -973,7 +1014,13 @@ window.Aura = (() => {
973
1014
  props.className
974
1015
  )
975
1016
  },
976
- /* @__PURE__ */ React4.createElement("span", { className: "aura-empty__icon", "aria-hidden": true }, /* @__PURE__ */ React4.createElement(Icon, { name: props.icon || /* @__PURE__ */ React4.createElement(IconInbox, null), size: props.size === "sm" ? "md" : "lg" })),
1017
+ props.icon === false ? null : /* @__PURE__ */ React4.createElement("span", { className: "aura-empty__icon", "aria-hidden": true }, /* @__PURE__ */ React4.createElement(
1018
+ Icon,
1019
+ {
1020
+ name: props.icon || (props.tone === "danger" ? /* @__PURE__ */ React4.createElement(IconTriangleAlert, null) : /* @__PURE__ */ React4.createElement(IconInbox, null)),
1021
+ size: props.size === "sm" ? "md" : "lg"
1022
+ }
1023
+ )),
977
1024
  /* @__PURE__ */ React4.createElement(HT, { className: "aura-empty__title" }, props.title),
978
1025
  props.description ? /* @__PURE__ */ React4.createElement("p", { className: "aura-empty__text" }, props.description) : null,
979
1026
  props.action ? /* @__PURE__ */ React4.createElement("div", { className: "aura-empty__action" }, props.action) : null
@@ -6630,6 +6677,12 @@ window.Aura = (() => {
6630
6677
  }
6631
6678
  const visibleRows = Math.max(1, Math.floor(bodyH / ROW_H));
6632
6679
  const nCols = vis.length + (selectable ? 1 : 0);
6680
+ const ariaIdx = [];
6681
+ let ariaColCount = 0;
6682
+ for (let ci = 0; ci < nCols; ci++) {
6683
+ const col = selectable ? ci === 0 ? null : vis[ci - 1] : vis[ci];
6684
+ ariaIdx.push(!stacked && col && col.grid === false ? void 0 : ++ariaColCount);
6685
+ }
6633
6686
  const nRows = loading ? 0 : pageRows.length;
6634
6687
  const hasTotal = !!(props.footer && rows.length && !loading), T = nRows + 1, lastR = nRows + (hasTotal ? 1 : 0);
6635
6688
  const active = activeState[0];
@@ -6671,10 +6724,11 @@ window.Aura = (() => {
6671
6724
  },
6672
6725
  [stacked]
6673
6726
  );
6727
+ const tabR = stacked && ar === 0 && nRows > 0 && (!(selectable && acShown === 0) || noCardSel) ? 1 : ar;
6674
6728
  function tabFor(r2, c) {
6675
6729
  if (tabMode) return -1;
6676
6730
  if (!activeRendered) return r2 === 0 && c === ac ? 0 : -1;
6677
- return r2 === ar && c === acShown ? 0 : -1;
6731
+ return r2 === tabR && c === acShown ? 0 : -1;
6678
6732
  }
6679
6733
  React34.useEffect(function() {
6680
6734
  const p = pending.current;
@@ -7093,7 +7147,7 @@ window.Aura = (() => {
7093
7147
  key: c.key,
7094
7148
  role: "columnheader",
7095
7149
  "aria-sort": ariaSort,
7096
- "aria-colindex": ci + 1,
7150
+ "aria-colindex": ariaIdx[ci],
7097
7151
  style: cellStyle(c, i),
7098
7152
  "data-hide": hideLevel(c) ? String(hideLevel(c)) : void 0,
7099
7153
  "data-card": cardPart(c, i) === "hide" ? "hide" : void 0,
@@ -7158,7 +7212,7 @@ window.Aura = (() => {
7158
7212
  size: 12
7159
7213
  }
7160
7214
  )
7161
- ) : /* @__PURE__ */ React34.createElement("span", { className: cx("aura-table__th-label", !c.label && "aura-sr-only") }, c.label || (c.actions ? t.actions : c.key)), pin ? /* @__PURE__ */ React34.createElement(Icon, { name: /* @__PURE__ */ React34.createElement(IconPin, null), size: 12, className: "aura-table__pin-icon", label: t.pinned }) : null, controls ? /* @__PURE__ */ React34.createElement(
7215
+ ) : /* @__PURE__ */ React34.createElement("span", { className: cx("aura-table__th-label", !c.label && "aura-sr-only") }, c.label || (c.actions ? t.actions : null)), pin ? /* @__PURE__ */ React34.createElement(Icon, { name: /* @__PURE__ */ React34.createElement(IconPin, null), size: 12, className: "aura-table__pin-icon", label: t.pinned }) : null, controls ? /* @__PURE__ */ React34.createElement(
7162
7216
  "button",
7163
7217
  {
7164
7218
  type: "button",
@@ -7256,7 +7310,7 @@ window.Aura = (() => {
7256
7310
  {
7257
7311
  key: c.key,
7258
7312
  role: "gridcell",
7259
- "aria-colindex": ci + 1,
7313
+ "aria-colindex": ariaIdx[ci],
7260
7314
  tabIndex: tabFor(ri, ci),
7261
7315
  "data-rc": ri + ":" + ci,
7262
7316
  className: cx(
@@ -7484,7 +7538,7 @@ window.Aura = (() => {
7484
7538
  {
7485
7539
  key: c.key,
7486
7540
  role: "gridcell",
7487
- "aria-colindex": j + (selectable ? 2 : 1),
7541
+ "aria-colindex": ariaIdx[j + (selectable ? 1 : 0)],
7488
7542
  tabIndex: tabFor(T, ci),
7489
7543
  "data-rc": T + ":" + ci,
7490
7544
  onFocus: function() {
@@ -7534,6 +7588,7 @@ window.Aura = (() => {
7534
7588
  props.bordered === false && "aura-table--borderless",
7535
7589
  stacked && "aura-table--stacked",
7536
7590
  noCardSel && "aura-table--cards-nosel",
7591
+ props.stackStyle === "list" && "aura-table--stack-list",
7537
7592
  scrolledX[0] && "is-scrolled-x",
7538
7593
  edgeState[0] && !stacked && "has-more-x",
7539
7594
  refreshing && "is-refreshing",
@@ -7579,7 +7634,7 @@ window.Aura = (() => {
7579
7634
  "aria-label": props.label,
7580
7635
  "aria-busy": busy || void 0,
7581
7636
  "aria-rowcount": loading || manual && props.totalRows == null && pageSize && rows.length >= pageSize ? -1 : shownTotal + 1 + (totalRow ? 1 : 0),
7582
- "aria-colcount": nCols,
7637
+ "aria-colcount": ariaColCount,
7583
7638
  "aria-multiselectable": selectable || void 0,
7584
7639
  onKeyDown: onGridKey,
7585
7640
  onScroll: function(e) {
@@ -825,6 +825,45 @@ function alertElement(props, ref, close, autoId) {
825
825
  close
826
826
  );
827
827
  }
828
+ var NAMED_ROOTS = [
829
+ "section",
830
+ "article",
831
+ "aside",
832
+ "nav",
833
+ "main",
834
+ "search",
835
+ "form",
836
+ "fieldset",
837
+ "details",
838
+ "dialog",
839
+ "figure",
840
+ "li",
841
+ "ul",
842
+ "ol",
843
+ "menu"
844
+ ];
845
+ var NAMELESS_ROLES = [
846
+ "generic",
847
+ "none",
848
+ "presentation",
849
+ "paragraph",
850
+ "caption",
851
+ "code",
852
+ "deletion",
853
+ "emphasis",
854
+ "insertion",
855
+ "strong",
856
+ "subscript",
857
+ "superscript",
858
+ "term",
859
+ "time",
860
+ "mark"
861
+ ];
862
+ function cardTakesName(as, role) {
863
+ const first = role ? role.trim().split(/\s+/)[0].toLowerCase() : "";
864
+ if (first) return NAMELESS_ROLES.indexOf(first) < 0;
865
+ return NAMED_ROOTS.indexOf(as) >= 0;
866
+ }
828
867
  function cardElement(props, ref) {
829
868
  const creative = props.variant === "creative";
830
869
  const rest = omit(props, [
@@ -854,8 +893,10 @@ function cardElement(props, ref) {
854
893
  props.flushBelow && "aura-card--flush-below-" + props.flushBelow,
855
894
  props.className
856
895
  ),
857
- /* A titled card is labelled by its title; otherwise the caller's aria-labelledby stays (5.8). */
858
- "aria-labelledby": !head && props.title && props.titleId ? props.titleId : props["aria-labelledby"]
896
+ /* A titled card is labelled by its title; otherwise the caller's aria-labelledby stays (5.8). 5.86 (Chamber-OS
897
+ * 199): only where a name is allowed — a root that takes one (section, article…) or one with a role; a plain
898
+ * div is generic, where ARIA 1.2 prohibits a name. The title keeps its id either way. */
899
+ "aria-labelledby": !head && props.title && props.titleId && cardTakesName(props.as || "section", props.role) ? props.titleId : props["aria-labelledby"]
859
900
  }),
860
901
  head || props.title || props.actions ? /* @__PURE__ */ React4.createElement("div", { className: "aura-card__head" }, /* @__PURE__ */ React4.createElement("div", { className: "aura-card__heading" }, head ? props.header : null, !head && props.title ? h2("h" + (props.headingLevel || 3), { className: "aura-card__title", id: props.titleId }, props.title) : null, !head && props.description ? /* @__PURE__ */ React4.createElement("p", { className: "aura-card__desc" }, props.description) : null), props.actions ? /* @__PURE__ */ React4.createElement("div", { className: "aura-card__actions" }, props.actions) : null) : null,
861
902
  props.children ? /* @__PURE__ */ React4.createElement("div", { className: "aura-card__body" }, props.children) : null,
@@ -955,7 +996,13 @@ function emptyStateElement(props, ref) {
955
996
  props.className
956
997
  )
957
998
  },
958
- /* @__PURE__ */ React4.createElement("span", { className: "aura-empty__icon", "aria-hidden": true }, /* @__PURE__ */ React4.createElement(Icon, { name: props.icon || /* @__PURE__ */ React4.createElement(IconInbox, null), size: props.size === "sm" ? "md" : "lg" })),
999
+ props.icon === false ? null : /* @__PURE__ */ React4.createElement("span", { className: "aura-empty__icon", "aria-hidden": true }, /* @__PURE__ */ React4.createElement(
1000
+ Icon,
1001
+ {
1002
+ name: props.icon || (props.tone === "danger" ? /* @__PURE__ */ React4.createElement(IconTriangleAlert, null) : /* @__PURE__ */ React4.createElement(IconInbox, null)),
1003
+ size: props.size === "sm" ? "md" : "lg"
1004
+ }
1005
+ )),
959
1006
  /* @__PURE__ */ React4.createElement(HT, { className: "aura-empty__title" }, props.title),
960
1007
  props.description ? /* @__PURE__ */ React4.createElement("p", { className: "aura-empty__text" }, props.description) : null,
961
1008
  props.action ? /* @__PURE__ */ React4.createElement("div", { className: "aura-empty__action" }, props.action) : null
@@ -6612,6 +6659,12 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
6612
6659
  }
6613
6660
  const visibleRows = Math.max(1, Math.floor(bodyH / ROW_H));
6614
6661
  const nCols = vis.length + (selectable ? 1 : 0);
6662
+ const ariaIdx = [];
6663
+ let ariaColCount = 0;
6664
+ for (let ci = 0; ci < nCols; ci++) {
6665
+ const col = selectable ? ci === 0 ? null : vis[ci - 1] : vis[ci];
6666
+ ariaIdx.push(!stacked && col && col.grid === false ? void 0 : ++ariaColCount);
6667
+ }
6615
6668
  const nRows = loading ? 0 : pageRows.length;
6616
6669
  const hasTotal = !!(props.footer && rows.length && !loading), T = nRows + 1, lastR = nRows + (hasTotal ? 1 : 0);
6617
6670
  const active = activeState[0];
@@ -6653,10 +6706,11 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
6653
6706
  },
6654
6707
  [stacked]
6655
6708
  );
6709
+ const tabR = stacked && ar === 0 && nRows > 0 && (!(selectable && acShown === 0) || noCardSel) ? 1 : ar;
6656
6710
  function tabFor(r2, c) {
6657
6711
  if (tabMode) return -1;
6658
6712
  if (!activeRendered) return r2 === 0 && c === ac ? 0 : -1;
6659
- return r2 === ar && c === acShown ? 0 : -1;
6713
+ return r2 === tabR && c === acShown ? 0 : -1;
6660
6714
  }
6661
6715
  React34.useEffect(function() {
6662
6716
  const p = pending.current;
@@ -7075,7 +7129,7 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
7075
7129
  key: c.key,
7076
7130
  role: "columnheader",
7077
7131
  "aria-sort": ariaSort,
7078
- "aria-colindex": ci + 1,
7132
+ "aria-colindex": ariaIdx[ci],
7079
7133
  style: cellStyle(c, i),
7080
7134
  "data-hide": hideLevel(c) ? String(hideLevel(c)) : void 0,
7081
7135
  "data-card": cardPart(c, i) === "hide" ? "hide" : void 0,
@@ -7140,7 +7194,7 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
7140
7194
  size: 12
7141
7195
  }
7142
7196
  )
7143
- ) : /* @__PURE__ */ React34.createElement("span", { className: cx("aura-table__th-label", !c.label && "aura-sr-only") }, c.label || (c.actions ? t.actions : c.key)), pin ? /* @__PURE__ */ React34.createElement(Icon, { name: /* @__PURE__ */ React34.createElement(IconPin, null), size: 12, className: "aura-table__pin-icon", label: t.pinned }) : null, controls ? /* @__PURE__ */ React34.createElement(
7197
+ ) : /* @__PURE__ */ React34.createElement("span", { className: cx("aura-table__th-label", !c.label && "aura-sr-only") }, c.label || (c.actions ? t.actions : null)), pin ? /* @__PURE__ */ React34.createElement(Icon, { name: /* @__PURE__ */ React34.createElement(IconPin, null), size: 12, className: "aura-table__pin-icon", label: t.pinned }) : null, controls ? /* @__PURE__ */ React34.createElement(
7144
7198
  "button",
7145
7199
  {
7146
7200
  type: "button",
@@ -7238,7 +7292,7 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
7238
7292
  {
7239
7293
  key: c.key,
7240
7294
  role: "gridcell",
7241
- "aria-colindex": ci + 1,
7295
+ "aria-colindex": ariaIdx[ci],
7242
7296
  tabIndex: tabFor(ri, ci),
7243
7297
  "data-rc": ri + ":" + ci,
7244
7298
  className: cx(
@@ -7466,7 +7520,7 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
7466
7520
  {
7467
7521
  key: c.key,
7468
7522
  role: "gridcell",
7469
- "aria-colindex": j + (selectable ? 2 : 1),
7523
+ "aria-colindex": ariaIdx[j + (selectable ? 1 : 0)],
7470
7524
  tabIndex: tabFor(T, ci),
7471
7525
  "data-rc": T + ":" + ci,
7472
7526
  onFocus: function() {
@@ -7516,6 +7570,7 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
7516
7570
  props.bordered === false && "aura-table--borderless",
7517
7571
  stacked && "aura-table--stacked",
7518
7572
  noCardSel && "aura-table--cards-nosel",
7573
+ props.stackStyle === "list" && "aura-table--stack-list",
7519
7574
  scrolledX[0] && "is-scrolled-x",
7520
7575
  edgeState[0] && !stacked && "has-more-x",
7521
7576
  refreshing && "is-refreshing",
@@ -7561,7 +7616,7 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
7561
7616
  "aria-label": props.label,
7562
7617
  "aria-busy": busy || void 0,
7563
7618
  "aria-rowcount": loading || manual && props.totalRows == null && pageSize && rows.length >= pageSize ? -1 : shownTotal + 1 + (totalRow ? 1 : 0),
7564
- "aria-colcount": nCols,
7619
+ "aria-colcount": ariaColCount,
7565
7620
  "aria-multiselectable": selectable || void 0,
7566
7621
  onKeyDown: onGridKey,
7567
7622
  onScroll: function(e) {
@@ -552,6 +552,12 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
552
552
  }
553
553
  const visibleRows = Math.max(1, Math.floor(bodyH / ROW_H));
554
554
  const nCols = vis.length + (selectable ? 1 : 0);
555
+ const ariaIdx = [];
556
+ let ariaColCount = 0;
557
+ for (let ci = 0; ci < nCols; ci++) {
558
+ const col = selectable ? ci === 0 ? null : vis[ci - 1] : vis[ci];
559
+ ariaIdx.push(!stacked && col && col.grid === false ? void 0 : ++ariaColCount);
560
+ }
555
561
  const nRows = loading ? 0 : pageRows.length;
556
562
  const hasTotal = !!(props.footer && rows.length && !loading), T = nRows + 1, lastR = nRows + (hasTotal ? 1 : 0);
557
563
  const active = activeState[0];
@@ -593,10 +599,11 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
593
599
  },
594
600
  [stacked]
595
601
  );
602
+ const tabR = stacked && ar === 0 && nRows > 0 && (!(selectable && acShown === 0) || noCardSel) ? 1 : ar;
596
603
  function tabFor(r, c) {
597
604
  if (tabMode) return -1;
598
605
  if (!activeRendered) return r === 0 && c === ac ? 0 : -1;
599
- return r === ar && c === acShown ? 0 : -1;
606
+ return r === tabR && c === acShown ? 0 : -1;
600
607
  }
601
608
  React.useEffect(function() {
602
609
  const p = pending.current;
@@ -1015,7 +1022,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1015
1022
  key: c.key,
1016
1023
  role: "columnheader",
1017
1024
  "aria-sort": ariaSort,
1018
- "aria-colindex": ci + 1,
1025
+ "aria-colindex": ariaIdx[ci],
1019
1026
  style: cellStyle(c, i),
1020
1027
  "data-hide": hideLevel(c) ? String(hideLevel(c)) : void 0,
1021
1028
  "data-card": cardPart(c, i) === "hide" ? "hide" : void 0,
@@ -1080,7 +1087,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1080
1087
  size: 12
1081
1088
  }
1082
1089
  )
1083
- ) : /* @__PURE__ */ React.createElement("span", { className: cx("aura-table__th-label", !c.label && "aura-sr-only") }, c.label || (c.actions ? t.actions : c.key)), pin ? /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconPin, null), size: 12, className: "aura-table__pin-icon", label: t.pinned }) : null, controls ? /* @__PURE__ */ React.createElement(
1090
+ ) : /* @__PURE__ */ React.createElement("span", { className: cx("aura-table__th-label", !c.label && "aura-sr-only") }, c.label || (c.actions ? t.actions : null)), pin ? /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconPin, null), size: 12, className: "aura-table__pin-icon", label: t.pinned }) : null, controls ? /* @__PURE__ */ React.createElement(
1084
1091
  "button",
1085
1092
  {
1086
1093
  type: "button",
@@ -1178,7 +1185,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1178
1185
  {
1179
1186
  key: c.key,
1180
1187
  role: "gridcell",
1181
- "aria-colindex": ci + 1,
1188
+ "aria-colindex": ariaIdx[ci],
1182
1189
  tabIndex: tabFor(ri, ci),
1183
1190
  "data-rc": ri + ":" + ci,
1184
1191
  className: cx(
@@ -1406,7 +1413,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1406
1413
  {
1407
1414
  key: c.key,
1408
1415
  role: "gridcell",
1409
- "aria-colindex": j + (selectable ? 2 : 1),
1416
+ "aria-colindex": ariaIdx[j + (selectable ? 1 : 0)],
1410
1417
  tabIndex: tabFor(T, ci),
1411
1418
  "data-rc": T + ":" + ci,
1412
1419
  onFocus: function() {
@@ -1456,6 +1463,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1456
1463
  props.bordered === false && "aura-table--borderless",
1457
1464
  stacked && "aura-table--stacked",
1458
1465
  noCardSel && "aura-table--cards-nosel",
1466
+ props.stackStyle === "list" && "aura-table--stack-list",
1459
1467
  scrolledX[0] && "is-scrolled-x",
1460
1468
  edgeState[0] && !stacked && "has-more-x",
1461
1469
  refreshing && "is-refreshing",
@@ -1501,7 +1509,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1501
1509
  "aria-label": props.label,
1502
1510
  "aria-busy": busy || void 0,
1503
1511
  "aria-rowcount": loading || manual && props.totalRows == null && pageSize && rows.length >= pageSize ? -1 : shownTotal + 1 + (totalRow ? 1 : 0),
1504
- "aria-colcount": nCols,
1512
+ "aria-colcount": ariaColCount,
1505
1513
  "aria-multiselectable": selectable || void 0,
1506
1514
  onKeyDown: onGridKey,
1507
1515
  onScroll: function(e) {
@@ -57,6 +57,45 @@ function alertElement(props, ref, close, autoId) {
57
57
  close
58
58
  );
59
59
  }
60
+ const NAMED_ROOTS = [
61
+ "section",
62
+ "article",
63
+ "aside",
64
+ "nav",
65
+ "main",
66
+ "search",
67
+ "form",
68
+ "fieldset",
69
+ "details",
70
+ "dialog",
71
+ "figure",
72
+ "li",
73
+ "ul",
74
+ "ol",
75
+ "menu"
76
+ ];
77
+ const NAMELESS_ROLES = [
78
+ "generic",
79
+ "none",
80
+ "presentation",
81
+ "paragraph",
82
+ "caption",
83
+ "code",
84
+ "deletion",
85
+ "emphasis",
86
+ "insertion",
87
+ "strong",
88
+ "subscript",
89
+ "superscript",
90
+ "term",
91
+ "time",
92
+ "mark"
93
+ ];
94
+ function cardTakesName(as, role) {
95
+ const first = role ? role.trim().split(/\s+/)[0].toLowerCase() : "";
96
+ if (first) return NAMELESS_ROLES.indexOf(first) < 0;
97
+ return NAMED_ROOTS.indexOf(as) >= 0;
98
+ }
60
99
  function cardElement(props, ref) {
61
100
  const creative = props.variant === "creative";
62
101
  const rest = omit(props, [
@@ -86,8 +125,10 @@ function cardElement(props, ref) {
86
125
  props.flushBelow && "aura-card--flush-below-" + props.flushBelow,
87
126
  props.className
88
127
  ),
89
- /* A titled card is labelled by its title; otherwise the caller's aria-labelledby stays (5.8). */
90
- "aria-labelledby": !head && props.title && props.titleId ? props.titleId : props["aria-labelledby"]
128
+ /* A titled card is labelled by its title; otherwise the caller's aria-labelledby stays (5.8). 5.86 (Chamber-OS
129
+ * 199): only where a name is allowed — a root that takes one (section, article…) or one with a role; a plain
130
+ * div is generic, where ARIA 1.2 prohibits a name. The title keeps its id either way. */
131
+ "aria-labelledby": !head && props.title && props.titleId && cardTakesName(props.as || "section", props.role) ? props.titleId : props["aria-labelledby"]
91
132
  }),
92
133
  head || props.title || props.actions ? /* @__PURE__ */ React.createElement("div", { className: "aura-card__head" }, /* @__PURE__ */ React.createElement("div", { className: "aura-card__heading" }, head ? props.header : null, !head && props.title ? h("h" + (props.headingLevel || 3), { className: "aura-card__title", id: props.titleId }, props.title) : null, !head && props.description ? /* @__PURE__ */ React.createElement("p", { className: "aura-card__desc" }, props.description) : null), props.actions ? /* @__PURE__ */ React.createElement("div", { className: "aura-card__actions" }, props.actions) : null) : null,
93
134
  props.children ? /* @__PURE__ */ React.createElement("div", { className: "aura-card__body" }, props.children) : null,
@@ -187,7 +228,13 @@ function emptyStateElement(props, ref) {
187
228
  props.className
188
229
  )
189
230
  },
190
- /* @__PURE__ */ React.createElement("span", { className: "aura-empty__icon", "aria-hidden": true }, /* @__PURE__ */ React.createElement(Icon, { name: props.icon || /* @__PURE__ */ React.createElement(IconInbox, null), size: props.size === "sm" ? "md" : "lg" })),
231
+ props.icon === false ? null : /* @__PURE__ */ React.createElement("span", { className: "aura-empty__icon", "aria-hidden": true }, /* @__PURE__ */ React.createElement(
232
+ Icon,
233
+ {
234
+ name: props.icon || (props.tone === "danger" ? /* @__PURE__ */ React.createElement(IconTriangleAlert, null) : /* @__PURE__ */ React.createElement(IconInbox, null)),
235
+ size: props.size === "sm" ? "md" : "lg"
236
+ }
237
+ )),
191
238
  /* @__PURE__ */ React.createElement(HT, { className: "aura-empty__title" }, props.title),
192
239
  props.description ? /* @__PURE__ */ React.createElement("p", { className: "aura-empty__text" }, props.description) : null,
193
240
  props.action ? /* @__PURE__ */ React.createElement("div", { className: "aura-empty__action" }, props.action) : null
package/dist/index.d.cts CHANGED
@@ -255,6 +255,11 @@ export interface DataTableProps<Row extends Record<string, any> = Record<string,
255
255
  /** Stacked cards (`stackBelow`) without the row checkboxes and the select-all box (5.13). The grid keeps them and
256
256
  * the selection is kept; Space doesn't change it while the cards show. */
257
257
  hideSelectionInCards?: boolean | undefined;
258
+ /** How the rows look below `stackBelow` (5.87, Chamber-OS 204): `cards` (default) — each row its own framed card,
259
+ * 8px apart; `list` — the same layout (title, pill and actions on the first line, fields two to a line) as one list
260
+ * divided by `--aura-border-default` rules, with no frame, radius or gap per row, so it sits inside a framed `Card`.
261
+ * The grid above `stackBelow` is unchanged. */
262
+ stackStyle?: "cards" | "list" | undefined;
258
263
  /** `'auto'` (5.11): rows grow to fit content that wraps (two badges, a long name); cells stay vertically centred and
259
264
  * rows keep at least the density's row height. Needs every row in the DOM, so it is ignored with `height`
260
265
  * (virtualized rows are one fixed height). Default: one fixed-height line per row, long text ends in an ellipsis. */
@@ -878,7 +883,10 @@ export interface CardProps extends Omit<React$1.HTMLAttributes<HTMLElement>, "ti
878
883
  interactive?: boolean | undefined;
879
884
  as?: keyof React$1.JSX.IntrinsicElements | undefined;
880
885
  className?: string | undefined;
881
- /** id for the title element; the card is then labelled by it (aria-labelledby). */
886
+ /** id for the title element; the card is then labelled by it (aria-labelledby) — when its root takes a name: the
887
+ * default `section`, `article`, `aside`, `nav`, `form`, `fieldset`… or any root with a `role` (5.86, Chamber-OS 199).
888
+ * With `as="div"` and no role the title keeps its id but the root stays unlabelled (ARIA 1.2 prohibits a name on a
889
+ * generic element). */
882
890
  titleId?: string | undefined;
883
891
  /** Free head content in place of `title` and `description` (5.15, Chamber-OS 87): skeleton bars while the card
884
892
  * loads, a status pill above a title you mark up yourself. No heading is added; `actions` still sit top-right. */
@@ -1795,7 +1803,9 @@ export interface SkeletonProps {
1795
1803
  export interface EmptyStateProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, "title" | "children"> {
1796
1804
  title: React$1.ReactNode;
1797
1805
  description?: React$1.ReactNode | undefined;
1798
- icon?: IconInput | undefined;
1806
+ /** Default `inbox`, or `triangle-alert` with `tone="danger"` (5.86, Chamber-OS 202). `false` (5.86, Chamber-OS 201)
1807
+ * draws no icon and no circle — a short placeholder in a table cell or a small panel. */
1808
+ icon?: IconInput | false | undefined;
1799
1809
  /** One or two buttons. */
1800
1810
  action?: React$1.ReactNode | undefined;
1801
1811
  size?: "sm" | "md" | undefined;
package/dist/index.d.ts CHANGED
@@ -255,6 +255,11 @@ export interface DataTableProps<Row extends Record<string, any> = Record<string,
255
255
  /** Stacked cards (`stackBelow`) without the row checkboxes and the select-all box (5.13). The grid keeps them and
256
256
  * the selection is kept; Space doesn't change it while the cards show. */
257
257
  hideSelectionInCards?: boolean | undefined;
258
+ /** How the rows look below `stackBelow` (5.87, Chamber-OS 204): `cards` (default) — each row its own framed card,
259
+ * 8px apart; `list` — the same layout (title, pill and actions on the first line, fields two to a line) as one list
260
+ * divided by `--aura-border-default` rules, with no frame, radius or gap per row, so it sits inside a framed `Card`.
261
+ * The grid above `stackBelow` is unchanged. */
262
+ stackStyle?: "cards" | "list" | undefined;
258
263
  /** `'auto'` (5.11): rows grow to fit content that wraps (two badges, a long name); cells stay vertically centred and
259
264
  * rows keep at least the density's row height. Needs every row in the DOM, so it is ignored with `height`
260
265
  * (virtualized rows are one fixed height). Default: one fixed-height line per row, long text ends in an ellipsis. */
@@ -878,7 +883,10 @@ export interface CardProps extends Omit<React$1.HTMLAttributes<HTMLElement>, "ti
878
883
  interactive?: boolean | undefined;
879
884
  as?: keyof React$1.JSX.IntrinsicElements | undefined;
880
885
  className?: string | undefined;
881
- /** id for the title element; the card is then labelled by it (aria-labelledby). */
886
+ /** id for the title element; the card is then labelled by it (aria-labelledby) — when its root takes a name: the
887
+ * default `section`, `article`, `aside`, `nav`, `form`, `fieldset`… or any root with a `role` (5.86, Chamber-OS 199).
888
+ * With `as="div"` and no role the title keeps its id but the root stays unlabelled (ARIA 1.2 prohibits a name on a
889
+ * generic element). */
882
890
  titleId?: string | undefined;
883
891
  /** Free head content in place of `title` and `description` (5.15, Chamber-OS 87): skeleton bars while the card
884
892
  * loads, a status pill above a title you mark up yourself. No heading is added; `actions` still sit top-right. */
@@ -1795,7 +1803,9 @@ export interface SkeletonProps {
1795
1803
  export interface EmptyStateProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, "title" | "children"> {
1796
1804
  title: React$1.ReactNode;
1797
1805
  description?: React$1.ReactNode | undefined;
1798
- icon?: IconInput | undefined;
1806
+ /** Default `inbox`, or `triangle-alert` with `tone="danger"` (5.86, Chamber-OS 202). `false` (5.86, Chamber-OS 201)
1807
+ * draws no icon and no circle — a short placeholder in a table cell or a small panel. */
1808
+ icon?: IconInput | false | undefined;
1799
1809
  /** One or two buttons. */
1800
1810
  action?: React$1.ReactNode | undefined;
1801
1811
  size?: "sm" | "md" | undefined;