@jirawatpyk/aura-react 5.50.0 → 5.51.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
@@ -186,6 +186,23 @@ DataTable with sort and page in the URL (4.17): `onStateChange={({ sort, page })
186
186
  - **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`.
187
187
  - **Toasts** queue past three instead of dropping: six in a row all show, in order, three at a time.
188
188
 
189
+ ## 5.51 — Chamber-OS addenda 58–64 (#157–163)
190
+
191
+ - **DataTable column `sortDirection`** (#157): `'ascending' | 'descending'` — for a list the page orders (from the URL, a line under the table), puts `aria-sort` on that column's header with no sort button, icon or click, in the server's HTML too. Give it to one column (two warn in development); move it when the order changes. Once the table has its own sort (a sortable header was used, or `sort` is set) that wins and `sortDirection` is ignored, so one header at most claims the order.
192
+ - **DataTable `selectAllRef`** (#157): a ref to the select-all checkbox, e.g. to return focus there after a bulk bar's Clear.
193
+ - **The row minimum counts each flexible column's `minWidth`** (#159), the value its cells already used, instead of 160px for every one — eight columns with minimums of 80–132px now fit a 959px box without a sideways scroll. Columns without `minWidth` still count 160.
194
+ - **`StatusPill wrap`** (#158): in a box narrower than the words they wrap inside the pill (the icon beside the first line, the pill's own line height, Thai's too), and its corners are `aura-radius-lg` (16px) instead of full (addendum 61): on one line that still gives fully round ends, so it looks exactly as before; wrapped it is a rounded rectangle rather than a tall lozenge instead of running out of it — for a narrow DataTable column or a stacked card. In a wide box, or without `wrap`, a pill is one line as before.
195
+ - **Toggle Tags among FilterBar's controls show their whole label** (#160): from 640px a selectable Tag inside FilterBar isn't cut at 24ch, only when wider than the bar, and in the Filters sheet it never is; below 640px in the bar itself, and for a Tag outside FilterBar, the cap and its tip stay. Applied-filter chips were already uncapped (5.28).
196
+ - **The wheel over a DataTable scrolls the page again** (#163): `.aura-table__scroll` contains only the sideways axis (`overscroll-behavior-x: contain`), so a vertical wheel over a grid that scrolls sideways scrolls the page, a sideways swipe stays in the grid (no back navigation), and a fixed-`height` grid scrolls inside first, then the page at its end. Remove any `overscroll-behavior-y` override you added for it.
197
+ - **DataTable column `grow`** (#162): a flexible column's share of the spare width (its cells' flex-grow; default 1), e.g. `{ key: 'actions', minWidth: 128, grow: 3 }` puts Approve and Reject side by side once the table is wide. At the row minimum nothing changes, the row minimum ignores it, and fixed-`width` columns and stacked cards ignore it. `grow: 0` keeps a column at its minimum; leave at least one flexible column above 0 or the rows won't fill a wide table.
198
+ - **DataTable `keyboard="tab"`** (#161): every checkbox, sort and column button, row link and row action is an ordinary Tab stop in reading order (Shift+Tab walks back), each with its focus ring, in the grid and in stacked cards — for lists whose rows carry actions people expect to Tab to. The cells stop being Tab stops; the arrows still move between them once focus is in one (click a cell, or Escape from a control), and the grid's row and column semantics stay. Without it (`'grid'`, the default) the table is one Tab stop as before. Keyboard resizing and reordering of columns (Alt/Ctrl+Shift with the arrows on a header cell) need focus on that header cell, so they stay with the grid keyboard.
199
+ - **5.50 follow-up:** a disabled box whose control has `aria-invalid="true"` keeps its disabled edge inside an enabled custom `Field` too.
200
+
201
+ ```jsx
202
+ <DataTable columns={[{ key: 'timeInStage', label: 'Time in stage', minWidth: 132, sortDirection: 'descending' }, …]} selectAllRef={selectAll} … />
203
+ <StatusPill tone="progress" wrap>Awaiting marketing review</StatusPill>
204
+ ```
205
+
189
206
  ## 5.50 — Chamber-OS addendum 57 (#156)
190
207
 
191
208
  - **`invalid` on TextField and Textarea** (and PasswordField, which is a TextField; `Field` for a custom control): marks the value invalid without AURA's message — the danger edge (`--aura-border-danger`, light and dark, also on hover and focus) and `aria-invalid="true"` on the control, no error line, and `aria-describedby` as it would be without it (the addon, the hint, then your own ids). For a page that explains the problem itself — a live contrast readout, a reason line a disabled Save points at. Point `aria-describedby` at that explanation, or a screen reader hears "invalid" with no reason. `error` still wins: its message replaces the hint and its id the hint's. A disabled field keeps its disabled edge.
@@ -684,8 +684,17 @@ window.Aura = (() => {
684
684
  };
685
685
  function statusPillElement(props, ref) {
686
686
  const tone2 = props.tone || toneFor(props.children);
687
- const rest = omit(props, ["tone", "className", "children"]);
688
- return /* @__PURE__ */ React4.createElement("span", { ...rest, ref, className: cx("aura-pill", "aura-pill--" + tone2, props.className) }, /* @__PURE__ */ React4.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }), props.children);
687
+ const rest = omit(props, ["tone", "className", "children", "wrap"]);
688
+ return /* @__PURE__ */ React4.createElement(
689
+ "span",
690
+ {
691
+ ...rest,
692
+ ref,
693
+ className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
694
+ },
695
+ /* @__PURE__ */ React4.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
696
+ props.wrap ? /* @__PURE__ */ React4.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
697
+ );
689
698
  }
690
699
  function badgeElement(props, ref) {
691
700
  const rest = omit(props, ["tone", "variant", "icon", "className", "children"]);
@@ -5759,6 +5768,8 @@ window.Aura = (() => {
5759
5768
  const refreshing = busy && manual && rows.length > 0;
5760
5769
  const loading = busy && !refreshing;
5761
5770
  const selectable = !!props.selectable;
5771
+ const tabMode = props.keyboard === "tab";
5772
+ const tabIdx = tabMode ? void 0 : -1;
5762
5773
  const reorderable = props.reorderable !== false && !!props.columnControls;
5763
5774
  const controls = !!props.columnControls;
5764
5775
  const oneCallback = !!props.onStateChange;
@@ -5951,7 +5962,7 @@ window.Aura = (() => {
5951
5962
  const pending = React33.useRef(null);
5952
5963
  const resizing = React33.useRef(false);
5953
5964
  useIsoLayoutEffect(function() {
5954
- if (!gridRef.current) return;
5965
+ if (!gridRef.current || tabMode) return;
5955
5966
  const els = gridRef.current.querySelectorAll(
5956
5967
  '.aura-table__td button, .aura-table__td a[href], .aura-table__td input, .aura-table__td select, .aura-table__td textarea, .aura-table__td [tabindex="0"]'
5957
5968
  );
@@ -6035,7 +6046,7 @@ window.Aura = (() => {
6035
6046
  });
6036
6047
  let fixedSum = 0, hideTerms = "";
6037
6048
  vis.forEach(function(c) {
6038
- const w = widthOf(c) != null ? widthOf(c) : FLEX_MIN, lv = hideLevel(c);
6049
+ const w = widthOf(c) != null ? widthOf(c) : c.minWidth || FLEX_MIN, lv = hideLevel(c);
6039
6050
  if (lv && boxWidth[0] == null) hideTerms += " + " + w + "px * var(--aura-h" + lv + "-on, 1)";
6040
6051
  else fixedSum += w;
6041
6052
  });
@@ -6047,13 +6058,21 @@ window.Aura = (() => {
6047
6058
  acc += " + " + widthOf(c) + "px" + (lv && boxWidth[0] == null ? " * var(--aura-h" + lv + "-on, 1)" : "");
6048
6059
  }
6049
6060
  });
6061
+ if (vis.filter(function(c) {
6062
+ return !!c.sortDirection;
6063
+ }).length > 1)
6064
+ devWarnOnce(
6065
+ "datatable-sortdirection-twice",
6066
+ "DataTable: more than one column has sortDirection. The order follows one column; give it to that one."
6067
+ );
6050
6068
  const selW = selectable ? " + var(--aura-table-select-width)" : "";
6051
6069
  const gutterR = controls ? "var(--aura-space-12)" : "var(--aura-space-6)";
6052
6070
  const rowMinWidth = "calc(" + fixedSum + "px" + hideTerms + " + var(--aura-space-6) + " + gutterR + selW + ")";
6053
6071
  function cellStyle(c, i) {
6054
6072
  const w = widthOf(c), s = {};
6055
6073
  if (w == null) {
6056
- s["--aura-cell-flex"] = "1 1 0";
6074
+ const g = typeof c.grow === "number" && c.grow >= 0 && isFinite(c.grow) ? c.grow : 1;
6075
+ s["--aura-cell-flex"] = g + " 1 0";
6057
6076
  s["--aura-cell-min"] = (c.minWidth || FLEX_MIN) + "px";
6058
6077
  } else {
6059
6078
  s["--aura-cell-flex"] = !hasFlex && i === vis.length - 1 ? "1 0 auto" : "none";
@@ -6230,6 +6249,7 @@ window.Aura = (() => {
6230
6249
  [stacked]
6231
6250
  );
6232
6251
  function tabFor(r2, c) {
6252
+ if (tabMode) return -1;
6233
6253
  if (!activeRendered) return r2 === 0 && c === ac ? 0 : -1;
6234
6254
  return r2 === ar && c === acShown ? 0 : -1;
6235
6255
  }
@@ -6252,7 +6272,7 @@ window.Aura = (() => {
6252
6272
  const el = gridRef.current.querySelector('[data-rc="' + p.r + ":" + c + '"]');
6253
6273
  if (el) {
6254
6274
  pending.current = null;
6255
- el.focus();
6275
+ (p.menuBtn && el.querySelector(".aura-table__menu-btn") || el).focus();
6256
6276
  }
6257
6277
  });
6258
6278
  function focusCell(r2, c, dir) {
@@ -6528,7 +6548,7 @@ window.Aura = (() => {
6528
6548
  const m = menu;
6529
6549
  setMenu(null);
6530
6550
  if (restore && m) {
6531
- if (m.kind === "col") pending.current = { r: 0, key: m.key, c: m.rc ? m.rc.c : 0 };
6551
+ if (m.kind === "col") pending.current = { r: 0, key: m.key, c: m.rc ? m.rc.c : 0, menuBtn: tabMode };
6532
6552
  else if (m.anchor) m.anchor.focus();
6533
6553
  }
6534
6554
  }
@@ -6616,10 +6636,11 @@ window.Aura = (() => {
6616
6636
  pageKeys.length ? /* @__PURE__ */ React33.createElement(
6617
6637
  Checkbox,
6618
6638
  {
6639
+ ref: props.selectAllRef,
6619
6640
  hideLabel: true,
6620
6641
  checked: all,
6621
6642
  indeterminate: nSel > 0 && !all,
6622
- tabIndex: -1,
6643
+ tabIndex: tabIdx,
6623
6644
  label: all ? t.deselectAllRows : t.selectAllRows,
6624
6645
  onChange: toggleAll
6625
6646
  }
@@ -6631,7 +6652,8 @@ window.Aura = (() => {
6631
6652
  const ci = i + (selectable ? 1 : 0);
6632
6653
  const isSorted = sort && sort.key === c.key;
6633
6654
  const canSort = c.sortable && canSortAny;
6634
- const ariaSort = isSorted && canSort ? sort.dir === "desc" ? "descending" : "ascending" : canSort ? "none" : void 0;
6655
+ const own = c.sortDirection === "ascending" || c.sortDirection === "descending" ? c.sortDirection : void 0;
6656
+ const ariaSort = sort && canSortAny ? isSorted && canSort ? sort.dir === "desc" ? "descending" : "ascending" : canSort ? "none" : void 0 : own || (canSort ? "none" : void 0);
6635
6657
  const pin = isPinned(c), edge = pin && i === nPinned - 1;
6636
6658
  head.push(
6637
6659
  /* @__PURE__ */ React33.createElement(
@@ -6689,7 +6711,7 @@ window.Aura = (() => {
6689
6711
  "button",
6690
6712
  {
6691
6713
  type: "button",
6692
- tabIndex: -1,
6714
+ tabIndex: tabIdx,
6693
6715
  className: cx("aura-table__sort", isSorted && "is-active"),
6694
6716
  onClick: function() {
6695
6717
  sortBy(c.key);
@@ -6708,7 +6730,7 @@ window.Aura = (() => {
6708
6730
  "button",
6709
6731
  {
6710
6732
  type: "button",
6711
- tabIndex: -1,
6733
+ tabIndex: tabIdx,
6712
6734
  className: "aura-table__menu-btn",
6713
6735
  "aria-label": t.columnOptions(c.label),
6714
6736
  "aria-haspopup": "menu",
@@ -6783,7 +6805,7 @@ window.Aura = (() => {
6783
6805
  {
6784
6806
  hideLabel: true,
6785
6807
  checked: isSel,
6786
- tabIndex: -1,
6808
+ tabIndex: tabIdx,
6787
6809
  label: selLabel,
6788
6810
  onChange: function(on) {
6789
6811
  const shift = shiftClick.current;
@@ -666,8 +666,17 @@ var PILL_ICON = {
666
666
  };
667
667
  function statusPillElement(props, ref) {
668
668
  const tone2 = props.tone || toneFor(props.children);
669
- const rest = omit(props, ["tone", "className", "children"]);
670
- return /* @__PURE__ */ React4.createElement("span", { ...rest, ref, className: cx("aura-pill", "aura-pill--" + tone2, props.className) }, /* @__PURE__ */ React4.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }), props.children);
669
+ const rest = omit(props, ["tone", "className", "children", "wrap"]);
670
+ return /* @__PURE__ */ React4.createElement(
671
+ "span",
672
+ {
673
+ ...rest,
674
+ ref,
675
+ className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
676
+ },
677
+ /* @__PURE__ */ React4.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
678
+ props.wrap ? /* @__PURE__ */ React4.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
679
+ );
671
680
  }
672
681
  function badgeElement(props, ref) {
673
682
  const rest = omit(props, ["tone", "variant", "icon", "className", "children"]);
@@ -5741,6 +5750,8 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
5741
5750
  const refreshing = busy && manual && rows.length > 0;
5742
5751
  const loading = busy && !refreshing;
5743
5752
  const selectable = !!props.selectable;
5753
+ const tabMode = props.keyboard === "tab";
5754
+ const tabIdx = tabMode ? void 0 : -1;
5744
5755
  const reorderable = props.reorderable !== false && !!props.columnControls;
5745
5756
  const controls = !!props.columnControls;
5746
5757
  const oneCallback = !!props.onStateChange;
@@ -5933,7 +5944,7 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
5933
5944
  const pending = React33.useRef(null);
5934
5945
  const resizing = React33.useRef(false);
5935
5946
  useIsoLayoutEffect(function() {
5936
- if (!gridRef.current) return;
5947
+ if (!gridRef.current || tabMode) return;
5937
5948
  const els = gridRef.current.querySelectorAll(
5938
5949
  '.aura-table__td button, .aura-table__td a[href], .aura-table__td input, .aura-table__td select, .aura-table__td textarea, .aura-table__td [tabindex="0"]'
5939
5950
  );
@@ -6017,7 +6028,7 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6017
6028
  });
6018
6029
  let fixedSum = 0, hideTerms = "";
6019
6030
  vis.forEach(function(c) {
6020
- const w = widthOf(c) != null ? widthOf(c) : FLEX_MIN, lv = hideLevel(c);
6031
+ const w = widthOf(c) != null ? widthOf(c) : c.minWidth || FLEX_MIN, lv = hideLevel(c);
6021
6032
  if (lv && boxWidth[0] == null) hideTerms += " + " + w + "px * var(--aura-h" + lv + "-on, 1)";
6022
6033
  else fixedSum += w;
6023
6034
  });
@@ -6029,13 +6040,21 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6029
6040
  acc += " + " + widthOf(c) + "px" + (lv && boxWidth[0] == null ? " * var(--aura-h" + lv + "-on, 1)" : "");
6030
6041
  }
6031
6042
  });
6043
+ if (vis.filter(function(c) {
6044
+ return !!c.sortDirection;
6045
+ }).length > 1)
6046
+ devWarnOnce(
6047
+ "datatable-sortdirection-twice",
6048
+ "DataTable: more than one column has sortDirection. The order follows one column; give it to that one."
6049
+ );
6032
6050
  const selW = selectable ? " + var(--aura-table-select-width)" : "";
6033
6051
  const gutterR = controls ? "var(--aura-space-12)" : "var(--aura-space-6)";
6034
6052
  const rowMinWidth = "calc(" + fixedSum + "px" + hideTerms + " + var(--aura-space-6) + " + gutterR + selW + ")";
6035
6053
  function cellStyle(c, i) {
6036
6054
  const w = widthOf(c), s = {};
6037
6055
  if (w == null) {
6038
- s["--aura-cell-flex"] = "1 1 0";
6056
+ const g = typeof c.grow === "number" && c.grow >= 0 && isFinite(c.grow) ? c.grow : 1;
6057
+ s["--aura-cell-flex"] = g + " 1 0";
6039
6058
  s["--aura-cell-min"] = (c.minWidth || FLEX_MIN) + "px";
6040
6059
  } else {
6041
6060
  s["--aura-cell-flex"] = !hasFlex && i === vis.length - 1 ? "1 0 auto" : "none";
@@ -6212,6 +6231,7 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6212
6231
  [stacked]
6213
6232
  );
6214
6233
  function tabFor(r2, c) {
6234
+ if (tabMode) return -1;
6215
6235
  if (!activeRendered) return r2 === 0 && c === ac ? 0 : -1;
6216
6236
  return r2 === ar && c === acShown ? 0 : -1;
6217
6237
  }
@@ -6234,7 +6254,7 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6234
6254
  const el = gridRef.current.querySelector('[data-rc="' + p.r + ":" + c + '"]');
6235
6255
  if (el) {
6236
6256
  pending.current = null;
6237
- el.focus();
6257
+ (p.menuBtn && el.querySelector(".aura-table__menu-btn") || el).focus();
6238
6258
  }
6239
6259
  });
6240
6260
  function focusCell(r2, c, dir) {
@@ -6510,7 +6530,7 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6510
6530
  const m = menu;
6511
6531
  setMenu(null);
6512
6532
  if (restore && m) {
6513
- if (m.kind === "col") pending.current = { r: 0, key: m.key, c: m.rc ? m.rc.c : 0 };
6533
+ if (m.kind === "col") pending.current = { r: 0, key: m.key, c: m.rc ? m.rc.c : 0, menuBtn: tabMode };
6514
6534
  else if (m.anchor) m.anchor.focus();
6515
6535
  }
6516
6536
  }
@@ -6598,10 +6618,11 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6598
6618
  pageKeys.length ? /* @__PURE__ */ React33.createElement(
6599
6619
  Checkbox,
6600
6620
  {
6621
+ ref: props.selectAllRef,
6601
6622
  hideLabel: true,
6602
6623
  checked: all,
6603
6624
  indeterminate: nSel > 0 && !all,
6604
- tabIndex: -1,
6625
+ tabIndex: tabIdx,
6605
6626
  label: all ? t.deselectAllRows : t.selectAllRows,
6606
6627
  onChange: toggleAll
6607
6628
  }
@@ -6613,7 +6634,8 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6613
6634
  const ci = i + (selectable ? 1 : 0);
6614
6635
  const isSorted = sort && sort.key === c.key;
6615
6636
  const canSort = c.sortable && canSortAny;
6616
- const ariaSort = isSorted && canSort ? sort.dir === "desc" ? "descending" : "ascending" : canSort ? "none" : void 0;
6637
+ const own = c.sortDirection === "ascending" || c.sortDirection === "descending" ? c.sortDirection : void 0;
6638
+ const ariaSort = sort && canSortAny ? isSorted && canSort ? sort.dir === "desc" ? "descending" : "ascending" : canSort ? "none" : void 0 : own || (canSort ? "none" : void 0);
6617
6639
  const pin = isPinned(c), edge = pin && i === nPinned - 1;
6618
6640
  head.push(
6619
6641
  /* @__PURE__ */ React33.createElement(
@@ -6671,7 +6693,7 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6671
6693
  "button",
6672
6694
  {
6673
6695
  type: "button",
6674
- tabIndex: -1,
6696
+ tabIndex: tabIdx,
6675
6697
  className: cx("aura-table__sort", isSorted && "is-active"),
6676
6698
  onClick: function() {
6677
6699
  sortBy(c.key);
@@ -6690,7 +6712,7 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6690
6712
  "button",
6691
6713
  {
6692
6714
  type: "button",
6693
- tabIndex: -1,
6715
+ tabIndex: tabIdx,
6694
6716
  className: "aura-table__menu-btn",
6695
6717
  "aria-label": t.columnOptions(c.label),
6696
6718
  "aria-haspopup": "menu",
@@ -6765,7 +6787,7 @@ var DataTableImpl = React33.forwardRef(function DataTable(props, ref) {
6765
6787
  {
6766
6788
  hideLabel: true,
6767
6789
  checked: isSel,
6768
- tabIndex: -1,
6790
+ tabIndex: tabIdx,
6769
6791
  label: selLabel,
6770
6792
  onChange: function(on) {
6771
6793
  const shift = shiftClick.current;
@@ -71,6 +71,8 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
71
71
  const refreshing = busy && manual && rows.length > 0;
72
72
  const loading = busy && !refreshing;
73
73
  const selectable = !!props.selectable;
74
+ const tabMode = props.keyboard === "tab";
75
+ const tabIdx = tabMode ? void 0 : -1;
74
76
  const reorderable = props.reorderable !== false && !!props.columnControls;
75
77
  const controls = !!props.columnControls;
76
78
  const oneCallback = !!props.onStateChange;
@@ -263,7 +265,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
263
265
  const pending = React.useRef(null);
264
266
  const resizing = React.useRef(false);
265
267
  useIsoLayoutEffect(function() {
266
- if (!gridRef.current) return;
268
+ if (!gridRef.current || tabMode) return;
267
269
  const els = gridRef.current.querySelectorAll(
268
270
  '.aura-table__td button, .aura-table__td a[href], .aura-table__td input, .aura-table__td select, .aura-table__td textarea, .aura-table__td [tabindex="0"]'
269
271
  );
@@ -347,7 +349,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
347
349
  });
348
350
  let fixedSum = 0, hideTerms = "";
349
351
  vis.forEach(function(c) {
350
- const w = widthOf(c) != null ? widthOf(c) : FLEX_MIN, lv = hideLevel(c);
352
+ const w = widthOf(c) != null ? widthOf(c) : c.minWidth || FLEX_MIN, lv = hideLevel(c);
351
353
  if (lv && boxWidth[0] == null) hideTerms += " + " + w + "px * var(--aura-h" + lv + "-on, 1)";
352
354
  else fixedSum += w;
353
355
  });
@@ -359,13 +361,21 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
359
361
  acc += " + " + widthOf(c) + "px" + (lv && boxWidth[0] == null ? " * var(--aura-h" + lv + "-on, 1)" : "");
360
362
  }
361
363
  });
364
+ if (vis.filter(function(c) {
365
+ return !!c.sortDirection;
366
+ }).length > 1)
367
+ devWarnOnce(
368
+ "datatable-sortdirection-twice",
369
+ "DataTable: more than one column has sortDirection. The order follows one column; give it to that one."
370
+ );
362
371
  const selW = selectable ? " + var(--aura-table-select-width)" : "";
363
372
  const gutterR = controls ? "var(--aura-space-12)" : "var(--aura-space-6)";
364
373
  const rowMinWidth = "calc(" + fixedSum + "px" + hideTerms + " + var(--aura-space-6) + " + gutterR + selW + ")";
365
374
  function cellStyle(c, i) {
366
375
  const w = widthOf(c), s = {};
367
376
  if (w == null) {
368
- s["--aura-cell-flex"] = "1 1 0";
377
+ const g = typeof c.grow === "number" && c.grow >= 0 && isFinite(c.grow) ? c.grow : 1;
378
+ s["--aura-cell-flex"] = g + " 1 0";
369
379
  s["--aura-cell-min"] = (c.minWidth || FLEX_MIN) + "px";
370
380
  } else {
371
381
  s["--aura-cell-flex"] = !hasFlex && i === vis.length - 1 ? "1 0 auto" : "none";
@@ -542,6 +552,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
542
552
  [stacked]
543
553
  );
544
554
  function tabFor(r, c) {
555
+ if (tabMode) return -1;
545
556
  if (!activeRendered) return r === 0 && c === ac ? 0 : -1;
546
557
  return r === ar && c === acShown ? 0 : -1;
547
558
  }
@@ -564,7 +575,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
564
575
  const el = gridRef.current.querySelector('[data-rc="' + p.r + ":" + c + '"]');
565
576
  if (el) {
566
577
  pending.current = null;
567
- el.focus();
578
+ (p.menuBtn && el.querySelector(".aura-table__menu-btn") || el).focus();
568
579
  }
569
580
  });
570
581
  function focusCell(r, c, dir) {
@@ -840,7 +851,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
840
851
  const m = menu;
841
852
  setMenu(null);
842
853
  if (restore && m) {
843
- if (m.kind === "col") pending.current = { r: 0, key: m.key, c: m.rc ? m.rc.c : 0 };
854
+ if (m.kind === "col") pending.current = { r: 0, key: m.key, c: m.rc ? m.rc.c : 0, menuBtn: tabMode };
844
855
  else if (m.anchor) m.anchor.focus();
845
856
  }
846
857
  }
@@ -928,10 +939,11 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
928
939
  pageKeys.length ? /* @__PURE__ */ React.createElement(
929
940
  Checkbox,
930
941
  {
942
+ ref: props.selectAllRef,
931
943
  hideLabel: true,
932
944
  checked: all,
933
945
  indeterminate: nSel > 0 && !all,
934
- tabIndex: -1,
946
+ tabIndex: tabIdx,
935
947
  label: all ? t.deselectAllRows : t.selectAllRows,
936
948
  onChange: toggleAll
937
949
  }
@@ -943,7 +955,8 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
943
955
  const ci = i + (selectable ? 1 : 0);
944
956
  const isSorted = sort && sort.key === c.key;
945
957
  const canSort = c.sortable && canSortAny;
946
- const ariaSort = isSorted && canSort ? sort.dir === "desc" ? "descending" : "ascending" : canSort ? "none" : void 0;
958
+ const own = c.sortDirection === "ascending" || c.sortDirection === "descending" ? c.sortDirection : void 0;
959
+ const ariaSort = sort && canSortAny ? isSorted && canSort ? sort.dir === "desc" ? "descending" : "ascending" : canSort ? "none" : void 0 : own || (canSort ? "none" : void 0);
947
960
  const pin = isPinned(c), edge = pin && i === nPinned - 1;
948
961
  head.push(
949
962
  /* @__PURE__ */ React.createElement(
@@ -1001,7 +1014,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1001
1014
  "button",
1002
1015
  {
1003
1016
  type: "button",
1004
- tabIndex: -1,
1017
+ tabIndex: tabIdx,
1005
1018
  className: cx("aura-table__sort", isSorted && "is-active"),
1006
1019
  onClick: function() {
1007
1020
  sortBy(c.key);
@@ -1020,7 +1033,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1020
1033
  "button",
1021
1034
  {
1022
1035
  type: "button",
1023
- tabIndex: -1,
1036
+ tabIndex: tabIdx,
1024
1037
  className: "aura-table__menu-btn",
1025
1038
  "aria-label": t.columnOptions(c.label),
1026
1039
  "aria-haspopup": "menu",
@@ -1095,7 +1108,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
1095
1108
  {
1096
1109
  hideLabel: true,
1097
1110
  checked: isSel,
1098
- tabIndex: -1,
1111
+ tabIndex: tabIdx,
1099
1112
  label: selLabel,
1100
1113
  onChange: function(on) {
1101
1114
  const shift = shiftClick.current;
@@ -85,8 +85,17 @@ const PILL_ICON = {
85
85
  };
86
86
  function statusPillElement(props, ref) {
87
87
  const tone = props.tone || toneFor(props.children);
88
- const rest = omit(props, ["tone", "className", "children"]);
89
- return /* @__PURE__ */ React.createElement("span", { ...rest, ref, className: cx("aura-pill", "aura-pill--" + tone, props.className) }, /* @__PURE__ */ React.createElement(Icon, { name: h(PILL_ICON[tone] || IconCircle), size: 12 }), props.children);
88
+ const rest = omit(props, ["tone", "className", "children", "wrap"]);
89
+ return /* @__PURE__ */ React.createElement(
90
+ "span",
91
+ {
92
+ ...rest,
93
+ ref,
94
+ className: cx("aura-pill", "aura-pill--" + tone, props.wrap && "is-wrap", props.className)
95
+ },
96
+ /* @__PURE__ */ React.createElement(Icon, { name: h(PILL_ICON[tone] || IconCircle), size: 12 }),
97
+ props.wrap ? /* @__PURE__ */ React.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
98
+ );
90
99
  }
91
100
  function badgeElement(props, ref) {
92
101
  const rest = omit(props, ["tone", "variant", "icon", "className", "children"]);
package/dist/index.d.cts CHANGED
@@ -89,12 +89,22 @@ export interface DataTableColumn<Row extends Record<string, any> = Record<string
89
89
  tones?: Record<string, StatusTone> | undefined;
90
90
  /** Header becomes a button cycling asc → desc → unsorted. */
91
91
  sortable?: boolean | undefined;
92
+ /** 5.51 (Chamber-OS 157): the order this column is in when the page sets it (from the URL, say), not a header
93
+ * button — puts `aria-sort` on the header, with no button, icon or click. Give it to one column. Once the table has
94
+ * its own sort (someone used a sortable header, or `sort` is set) that wins and this is ignored, so only one header
95
+ * claims the order. */
96
+ sortDirection?: "ascending" | "descending" | undefined;
92
97
  /** Value to sort by, when not the cell value (dates, amounts). */
93
98
  sortValue?: RowCallback<string | number | null, Row> | undefined;
94
99
  /** Custom cell content. */
95
100
  render?: RowCallback<React$1.ReactNode, Row> | undefined;
96
101
  /** Set false to keep a sized column fixed when the table is `resizable`. */
97
102
  resizable?: boolean | undefined;
103
+ /** 5.51 (Chamber-OS 162): a flexible column's share of the spare width (its cells' flex-grow; default 1) — e.g.
104
+ * `grow: 3` lets row actions sit side by side once the table is wide. At the minimum width nothing changes; the row
105
+ * minimum ignores it; a fixed `width` column and stacked cards ignore it. `0` keeps the column at its minimum; leave
106
+ * at least one flexible column above 0, or the rows won't fill a wide table. */
107
+ grow?: number | undefined;
98
108
  /** Resize limits in px. Defaults 64 / 480. For the flexible column, minWidth defaults to 160. */
99
109
  minWidth?: number | undefined;
100
110
  maxWidth?: number | undefined;
@@ -160,6 +170,15 @@ export interface DataTableProps<Row extends Record<string, any> = Record<string,
160
170
  onSortChange?: ((sort: DataTableSort | null) => void) | undefined;
161
171
  /** Adds a checkbox column. */
162
172
  selectable?: boolean | undefined;
173
+ /** 5.51 (Chamber-OS 161): how the keyboard moves through the table. `'grid'` (default): the table is one Tab stop,
174
+ * the arrows move between cells and Enter or F2 enters a cell's controls (APG grid). `'tab'`: every checkbox, sort
175
+ * and column button, row link and row action is an ordinary Tab stop in reading order (Shift+Tab walks back), for a
176
+ * list whose rows carry actions people expect to Tab to; the cells are not Tab stops, the arrows still move between
177
+ * them once focus is in one (click a cell, or Escape from a control), and the grid's row and column semantics stay. */
178
+ keyboard?: "grid" | "tab" | undefined;
179
+ /** 5.51 (Chamber-OS 157): a ref to the select-all checkbox (an `<input>`), e.g. to return focus to it after a bulk
180
+ * bar's Clear. Null while the page has no selectable rows. */
181
+ selectAllRef?: React$1.Ref<HTMLInputElement> | undefined;
163
182
  /** Which rows can be selected (5.6). A row it rejects shows no checkbox (the cell stays, so columns line up), is
164
183
  * skipped by select-all, Space and the count, and never appears in `onSelectionChange` — even if passed in. */
165
184
  isRowSelectable?: RowCallback<boolean, Row> | undefined;
@@ -307,6 +326,9 @@ export type StatusTone = "neutral" | "progress" | "ready" | "warning" | "blocked
307
326
  export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanElement>, "children"> {
308
327
  /** The status word shown, e.g. "In Progress". Other attributes (`id`, `data-*`, `aria-*`) go on the pill (5.8). */
309
328
  children: React$1.ReactNode;
329
+ /** 5.51 (Chamber-OS 158): in a box narrower than the words, the text wraps inside the pill (still rounded, the icon
330
+ * on the first line) instead of running out of it. Without it a pill stays on one line. */
331
+ wrap?: boolean | undefined;
310
332
  /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
311
333
  tone?: StatusTone | undefined;
312
334
  className?: string | undefined;
package/dist/index.d.ts CHANGED
@@ -89,12 +89,22 @@ export interface DataTableColumn<Row extends Record<string, any> = Record<string
89
89
  tones?: Record<string, StatusTone> | undefined;
90
90
  /** Header becomes a button cycling asc → desc → unsorted. */
91
91
  sortable?: boolean | undefined;
92
+ /** 5.51 (Chamber-OS 157): the order this column is in when the page sets it (from the URL, say), not a header
93
+ * button — puts `aria-sort` on the header, with no button, icon or click. Give it to one column. Once the table has
94
+ * its own sort (someone used a sortable header, or `sort` is set) that wins and this is ignored, so only one header
95
+ * claims the order. */
96
+ sortDirection?: "ascending" | "descending" | undefined;
92
97
  /** Value to sort by, when not the cell value (dates, amounts). */
93
98
  sortValue?: RowCallback<string | number | null, Row> | undefined;
94
99
  /** Custom cell content. */
95
100
  render?: RowCallback<React$1.ReactNode, Row> | undefined;
96
101
  /** Set false to keep a sized column fixed when the table is `resizable`. */
97
102
  resizable?: boolean | undefined;
103
+ /** 5.51 (Chamber-OS 162): a flexible column's share of the spare width (its cells' flex-grow; default 1) — e.g.
104
+ * `grow: 3` lets row actions sit side by side once the table is wide. At the minimum width nothing changes; the row
105
+ * minimum ignores it; a fixed `width` column and stacked cards ignore it. `0` keeps the column at its minimum; leave
106
+ * at least one flexible column above 0, or the rows won't fill a wide table. */
107
+ grow?: number | undefined;
98
108
  /** Resize limits in px. Defaults 64 / 480. For the flexible column, minWidth defaults to 160. */
99
109
  minWidth?: number | undefined;
100
110
  maxWidth?: number | undefined;
@@ -160,6 +170,15 @@ export interface DataTableProps<Row extends Record<string, any> = Record<string,
160
170
  onSortChange?: ((sort: DataTableSort | null) => void) | undefined;
161
171
  /** Adds a checkbox column. */
162
172
  selectable?: boolean | undefined;
173
+ /** 5.51 (Chamber-OS 161): how the keyboard moves through the table. `'grid'` (default): the table is one Tab stop,
174
+ * the arrows move between cells and Enter or F2 enters a cell's controls (APG grid). `'tab'`: every checkbox, sort
175
+ * and column button, row link and row action is an ordinary Tab stop in reading order (Shift+Tab walks back), for a
176
+ * list whose rows carry actions people expect to Tab to; the cells are not Tab stops, the arrows still move between
177
+ * them once focus is in one (click a cell, or Escape from a control), and the grid's row and column semantics stay. */
178
+ keyboard?: "grid" | "tab" | undefined;
179
+ /** 5.51 (Chamber-OS 157): a ref to the select-all checkbox (an `<input>`), e.g. to return focus to it after a bulk
180
+ * bar's Clear. Null while the page has no selectable rows. */
181
+ selectAllRef?: React$1.Ref<HTMLInputElement> | undefined;
163
182
  /** Which rows can be selected (5.6). A row it rejects shows no checkbox (the cell stays, so columns line up), is
164
183
  * skipped by select-all, Space and the count, and never appears in `onSelectionChange` — even if passed in. */
165
184
  isRowSelectable?: RowCallback<boolean, Row> | undefined;
@@ -307,6 +326,9 @@ export type StatusTone = "neutral" | "progress" | "ready" | "warning" | "blocked
307
326
  export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanElement>, "children"> {
308
327
  /** The status word shown, e.g. "In Progress". Other attributes (`id`, `data-*`, `aria-*`) go on the pill (5.8). */
309
328
  children: React$1.ReactNode;
329
+ /** 5.51 (Chamber-OS 158): in a box narrower than the words, the text wraps inside the pill (still rounded, the icon
330
+ * on the first line) instead of running out of it. Without it a pill stays on one line. */
331
+ wrap?: boolean | undefined;
310
332
  /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
311
333
  tone?: StatusTone | undefined;
312
334
  className?: string | undefined;
@@ -1240,8 +1240,17 @@ var PILL_ICON = {
1240
1240
  };
1241
1241
  function statusPillElement(props, ref) {
1242
1242
  const tone2 = props.tone || toneFor(props.children);
1243
- const rest = omit(props, ["tone", "className", "children"]);
1244
- return /* @__PURE__ */ React3.createElement("span", { ...rest, ref, className: cx("aura-pill", "aura-pill--" + tone2, props.className) }, /* @__PURE__ */ React3.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }), props.children);
1243
+ const rest = omit(props, ["tone", "className", "children", "wrap"]);
1244
+ return /* @__PURE__ */ React3.createElement(
1245
+ "span",
1246
+ {
1247
+ ...rest,
1248
+ ref,
1249
+ className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
1250
+ },
1251
+ /* @__PURE__ */ React3.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
1252
+ props.wrap ? /* @__PURE__ */ React3.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
1253
+ );
1245
1254
  }
1246
1255
  function badgeElement(props, ref) {
1247
1256
  const rest = omit(props, ["tone", "variant", "icon", "className", "children"]);
@@ -46,6 +46,9 @@ export type StatusTone = "neutral" | "progress" | "ready" | "warning" | "blocked
46
46
  export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanElement>, "children"> {
47
47
  /** The status word shown, e.g. "In Progress". Other attributes (`id`, `data-*`, `aria-*`) go on the pill (5.8). */
48
48
  children: React$1.ReactNode;
49
+ /** 5.51 (Chamber-OS 158): in a box narrower than the words, the text wraps inside the pill (still rounded, the icon
50
+ * on the first line) instead of running out of it. Without it a pill stays on one line. */
51
+ wrap?: boolean | undefined;
49
52
  /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
50
53
  tone?: StatusTone | undefined;
51
54
  className?: string | undefined;
@@ -46,6 +46,9 @@ export type StatusTone = "neutral" | "progress" | "ready" | "warning" | "blocked
46
46
  export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanElement>, "children"> {
47
47
  /** The status word shown, e.g. "In Progress". Other attributes (`id`, `data-*`, `aria-*`) go on the pill (5.8). */
48
48
  children: React$1.ReactNode;
49
+ /** 5.51 (Chamber-OS 158): in a box narrower than the words, the text wraps inside the pill (still rounded, the icon
50
+ * on the first line) instead of running out of it. Without it a pill stays on one line. */
51
+ wrap?: boolean | undefined;
49
52
  /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
50
53
  tone?: StatusTone | undefined;
51
54
  className?: string | undefined;
@@ -1176,8 +1176,17 @@ var PILL_ICON = {
1176
1176
  };
1177
1177
  function statusPillElement(props, ref) {
1178
1178
  const tone2 = props.tone || toneFor(props.children);
1179
- const rest = omit(props, ["tone", "className", "children"]);
1180
- return /* @__PURE__ */ React3.createElement("span", { ...rest, ref, className: cx("aura-pill", "aura-pill--" + tone2, props.className) }, /* @__PURE__ */ React3.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }), props.children);
1179
+ const rest = omit(props, ["tone", "className", "children", "wrap"]);
1180
+ return /* @__PURE__ */ React3.createElement(
1181
+ "span",
1182
+ {
1183
+ ...rest,
1184
+ ref,
1185
+ className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
1186
+ },
1187
+ /* @__PURE__ */ React3.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
1188
+ props.wrap ? /* @__PURE__ */ React3.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
1189
+ );
1181
1190
  }
1182
1191
  function badgeElement(props, ref) {
1183
1192
  const rest = omit(props, ["tone", "variant", "icon", "className", "children"]);
package/dist/styles.css CHANGED
@@ -105,7 +105,10 @@ button.aura-btn--creative[aria-disabled='true']:hover { box-shadow: var(--aura-s
105
105
  background: var(--aura-bg-surface);
106
106
  color: var(--aura-fg-primary);
107
107
  }
108
- .aura-table__scroll { position: relative; overflow: auto; overscroll-behavior: contain; height: var(--aura-table-h, auto); }
108
+ /* 5.51 (Chamber-OS 163): only the sideways axis is contained (a trackpad swipe in a wide grid never goes back a page);
109
+ * a vertical wheel over a grid that scrolls sideways still scrolls the page, and a fixed-height grid scrolls inside
110
+ * first, then chains to the page at its end. */
111
+ .aura-table__scroll { position: relative; overflow: auto; overscroll-behavior-x: contain; overscroll-behavior-y: auto; height: var(--aura-table-h, auto); }
109
112
  /* 5.30: more columns past the end edge — a soft shadow over the grid's end edge (not over its scrollbars, its footer
110
113
  * or the column button), so it reads as scrolling sideways. JavaScript places it (top, height, scrollbar inset). */
111
114
  .aura-table__edge {
@@ -440,6 +443,13 @@ a.aura-icon-btn { text-decoration: none; }
440
443
  background: var(--aura-status-neutral-bg);
441
444
  color: var(--aura-status-neutral-fg);
442
445
  }
446
+ /* 5.51 (Chamber-OS 158, addendum 61): wrap — the words break inside the pill when its box is narrower; the icon stays
447
+ * beside the first line. Corners are aura-radius-lg (16px), not full: on one line (20px tall) that still clamps to fully
448
+ * round ends, and a wrapped pill is a rounded rectangle, not a tall lozenge. */
449
+ .aura-pill.is-wrap { align-items: flex-start; white-space: normal; border-radius: var(--aura-radius-lg); }
450
+ .aura-pill.is-wrap > .aura-icon { margin-top: calc((max(16px, 1rem) - 12px) / 2); }
451
+ .aura-pill.is-wrap:lang(th) > .aura-icon { margin-top: calc((max(18px, 1.125rem) - 12px) / 2); }
452
+ .aura-pill__text { min-width: 0; overflow-wrap: anywhere; }
443
453
  .aura-pill--progress { background: var(--aura-status-progress-bg); color: var(--aura-status-progress-fg); }
444
454
  .aura-pill--ready { background: var(--aura-status-ready-bg); color: var(--aura-status-ready-fg); }
445
455
  .aura-pill--warning { background: var(--aura-status-warning-bg); color: var(--aura-status-warning-fg); }
@@ -529,9 +539,10 @@ a.aura-icon-btn { text-decoration: none; }
529
539
  .aura-input__suffix { padding: 0 var(--aura-space-3) 0 0; color: var(--aura-fg-secondary); font-size: max(13px, .8125rem); white-space: nowrap; }
530
540
  .aura-field.is-invalid .aura-input, .aura-field.is-invalid .aura-textarea { border-color: var(--aura-border-danger); }
531
541
  /* 5.50 (Chamber-OS 156): a control the page marks aria-invalid="true" itself gets the same edge, message or not (a
532
- * disabled field keeps its disabled edge, as with is-invalid). */
533
- .aura-field:not(.is-disabled) .aura-input:has(> .aura-input__control[aria-invalid="true"]),
534
- .aura-field:not(.is-disabled) .aura-textarea[aria-invalid="true"] { border-color: var(--aura-border-danger); }
542
+ * disabled field keeps its disabled edge, as with is-invalid; 5.51: so does a disabled control inside an enabled custom
543
+ * Field). */
544
+ .aura-field:not(.is-disabled) .aura-input:has(> .aura-input__control[aria-invalid="true"]:not(:disabled)),
545
+ .aura-field:not(.is-disabled) .aura-textarea[aria-invalid="true"]:not(:disabled) { border-color: var(--aura-border-danger); }
535
546
  .aura-field.is-disabled .aura-input { background: var(--aura-bg-input-disabled); color: var(--aura-fg-secondary); border-color: var(--aura-border-default); }
536
547
  .aura-field.is-disabled .aura-input__control { cursor: not-allowed; }
537
548
  /* A read-only text field. Only an input: `:read-only` also matches any element that isn't editable — a
@@ -1864,6 +1875,16 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1864
1875
  * (So a FilterBar with only chips needs a width from its parent — it is full width in a block.) */
1865
1876
  .aura-filterbar__chips { contain: inline-size; }
1866
1877
  .aura-filterbar__chips > .aura-tag .aura-tag__text { max-width: none; min-width: 0; }
1878
+ /* 5.51 (Chamber-OS 160): a toggle Tag among FilterBar's controls (or in its sheet) is a filter, not a label to cut:
1879
+ * from 640px it shows its whole text, cut only when wider than the bar. Below 640px (outside the sheet) the 24ch cap
1880
+ * and its tip stay. */
1881
+ @media (min-width: 640px) {
1882
+ .aura-filterbar .aura-tag.is-selectable { max-width: 100%; min-width: 0; }
1883
+ .aura-filterbar .aura-tag.is-selectable .aura-tag__text { max-width: none; min-width: 0; }
1884
+ }
1885
+ /* …and in the Filters sheet at any width: it is full width, with every control labelled. */
1886
+ .aura-filterbar--sheet .aura-tag.is-selectable { max-width: 100%; min-width: 0; }
1887
+ .aura-filterbar--sheet .aura-tag.is-selectable .aura-tag__text { max-width: none; min-width: 0; }
1867
1888
  .aura-filterbar__clear {
1868
1889
  border: 0; background: transparent; padding: 4px var(--aura-space-2); border-radius: var(--aura-radius-sm);
1869
1890
  font: inherit; font-size: max(13px, .8125rem); font-weight: 500; color: var(--aura-fg-accent); cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
@@ -107,7 +107,10 @@ button.aura-btn--creative[aria-disabled='true']:hover { box-shadow: var(--aura-s
107
107
  background: var(--aura-bg-surface);
108
108
  color: var(--aura-fg-primary);
109
109
  }
110
- .aura-table__scroll { position: relative; overflow: auto; overscroll-behavior: contain; height: var(--aura-table-h, auto); }
110
+ /* 5.51 (Chamber-OS 163): only the sideways axis is contained (a trackpad swipe in a wide grid never goes back a page);
111
+ * a vertical wheel over a grid that scrolls sideways still scrolls the page, and a fixed-height grid scrolls inside
112
+ * first, then chains to the page at its end. */
113
+ .aura-table__scroll { position: relative; overflow: auto; overscroll-behavior-x: contain; overscroll-behavior-y: auto; height: var(--aura-table-h, auto); }
111
114
  /* 5.30: more columns past the end edge — a soft shadow over the grid's end edge (not over its scrollbars, its footer
112
115
  * or the column button), so it reads as scrolling sideways. JavaScript places it (top, height, scrollbar inset). */
113
116
  .aura-table__edge {
@@ -442,6 +445,13 @@ a.aura-icon-btn { text-decoration: none; }
442
445
  background: var(--aura-status-neutral-bg);
443
446
  color: var(--aura-status-neutral-fg);
444
447
  }
448
+ /* 5.51 (Chamber-OS 158, addendum 61): wrap — the words break inside the pill when its box is narrower; the icon stays
449
+ * beside the first line. Corners are aura-radius-lg (16px), not full: on one line (20px tall) that still clamps to fully
450
+ * round ends, and a wrapped pill is a rounded rectangle, not a tall lozenge. */
451
+ .aura-pill.is-wrap { align-items: flex-start; white-space: normal; border-radius: var(--aura-radius-lg); }
452
+ .aura-pill.is-wrap > .aura-icon { margin-top: calc((max(16px, 1rem) - 12px) / 2); }
453
+ .aura-pill.is-wrap:lang(th) > .aura-icon { margin-top: calc((max(18px, 1.125rem) - 12px) / 2); }
454
+ .aura-pill__text { min-width: 0; overflow-wrap: anywhere; }
445
455
  .aura-pill--progress { background: var(--aura-status-progress-bg); color: var(--aura-status-progress-fg); }
446
456
  .aura-pill--ready { background: var(--aura-status-ready-bg); color: var(--aura-status-ready-fg); }
447
457
  .aura-pill--warning { background: var(--aura-status-warning-bg); color: var(--aura-status-warning-fg); }
@@ -531,9 +541,10 @@ a.aura-icon-btn { text-decoration: none; }
531
541
  .aura-input__suffix { padding: 0 var(--aura-space-3) 0 0; color: var(--aura-fg-secondary); font-size: max(13px, .8125rem); white-space: nowrap; }
532
542
  .aura-field.is-invalid .aura-input, .aura-field.is-invalid .aura-textarea { border-color: var(--aura-border-danger); }
533
543
  /* 5.50 (Chamber-OS 156): a control the page marks aria-invalid="true" itself gets the same edge, message or not (a
534
- * disabled field keeps its disabled edge, as with is-invalid). */
535
- .aura-field:not(.is-disabled) .aura-input:has(> .aura-input__control[aria-invalid="true"]),
536
- .aura-field:not(.is-disabled) .aura-textarea[aria-invalid="true"] { border-color: var(--aura-border-danger); }
544
+ * disabled field keeps its disabled edge, as with is-invalid; 5.51: so does a disabled control inside an enabled custom
545
+ * Field). */
546
+ .aura-field:not(.is-disabled) .aura-input:has(> .aura-input__control[aria-invalid="true"]:not(:disabled)),
547
+ .aura-field:not(.is-disabled) .aura-textarea[aria-invalid="true"]:not(:disabled) { border-color: var(--aura-border-danger); }
537
548
  .aura-field.is-disabled .aura-input { background: var(--aura-bg-input-disabled); color: var(--aura-fg-secondary); border-color: var(--aura-border-default); }
538
549
  .aura-field.is-disabled .aura-input__control { cursor: not-allowed; }
539
550
  /* A read-only text field. Only an input: `:read-only` also matches any element that isn't editable — a
@@ -1866,6 +1877,16 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1866
1877
  * (So a FilterBar with only chips needs a width from its parent — it is full width in a block.) */
1867
1878
  .aura-filterbar__chips { contain: inline-size; }
1868
1879
  .aura-filterbar__chips > .aura-tag .aura-tag__text { max-width: none; min-width: 0; }
1880
+ /* 5.51 (Chamber-OS 160): a toggle Tag among FilterBar's controls (or in its sheet) is a filter, not a label to cut:
1881
+ * from 640px it shows its whole text, cut only when wider than the bar. Below 640px (outside the sheet) the 24ch cap
1882
+ * and its tip stay. */
1883
+ @media (min-width: 640px) {
1884
+ .aura-filterbar .aura-tag.is-selectable { max-width: 100%; min-width: 0; }
1885
+ .aura-filterbar .aura-tag.is-selectable .aura-tag__text { max-width: none; min-width: 0; }
1886
+ }
1887
+ /* …and in the Filters sheet at any width: it is full width, with every control labelled. */
1888
+ .aura-filterbar--sheet .aura-tag.is-selectable { max-width: 100%; min-width: 0; }
1889
+ .aura-filterbar--sheet .aura-tag.is-selectable .aura-tag__text { max-width: none; min-width: 0; }
1869
1890
  .aura-filterbar__clear {
1870
1891
  border: 0; background: transparent; padding: 4px var(--aura-space-2); border-radius: var(--aura-radius-sm);
1871
1892
  font: inherit; font-size: max(13px, .8125rem); font-weight: 500; color: var(--aura-fg-accent); cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "5.50.0",
3
+ "version": "5.51.0",
4
4
  "description": "AURA Design System — React components (Enterprise Standard, Human Creative). Thai-first: Buddhist-era dates, Thai collation.",
5
5
  "license": "MIT",
6
6
  "type": "module",