@jirawatpyk/aura-react 5.12.0 → 5.14.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,34 @@ 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.14 — Chamber-OS addenda 15–16, the small items
190
+
191
+ - **EmptyState `headingLevel={false}`** (item 86): the title is a `<p>`, so an empty state inside a card or list adds nothing to the page's heading outline. EmptyState has no live-region role unless you pass `role`.
192
+ - **Stat from `/server`, with `headingLevel`** (item 88): `import { Stat } from '@jirawatpyk/aura-react/server'` renders the same HTML as the root Stat (no `onClick` there; `href` renders `linkComponent`, else `<a>`), `loading` included. `headingLevel={2}` makes the label a heading — on a link tile too; with `onClick` it stays a span (a heading can't sit in a button) and a dev notice says so.
193
+ - **Drawer body scroll padding** (item 91): a field scrolled into view in a Drawer (focus, a phone's keyboard) keeps 16px clear of the head and the foot, so its focus ring is never cut (WCAG 2.4.11).
194
+ - **AppShell `contentPadding={false}`** (item 97): `<main>` has no padding, for pages whose containers own the padding and column.
195
+ - **Button keeps a passed `aria-disabled`** (item 102): `<Button aria-disabled>` stays focusable and in the Tab order, is announced as unavailable, looks unavailable (disabled opacity, not-allowed cursor) and ignores clicks and Enter — a gated confirm. Hovering it changes nothing. `loading` still sets it. In tests, click such a button with `{ force: true }`. This is for buttons: a Button with `href` takes `disabled` (it drops the link and sets `aria-disabled` itself), and IconButton is unchanged.
196
+ - **Avatar from `/server`** (item 103): initials and colour, same HTML as the root Avatar. Falling back to initials when an image fails needs the client.
197
+ - **`--aura-shell-bar-height`** (item 104): the top bar's height on `.aura-shell`, for sticky offsets under it — `top: var(--aura-shell-bar-height)`. AppShell measures the bar, so a header that wraps to two lines on a phone gives its real height; the stylesheet's 56px covers the server's HTML until then. It is set inline on the shell, so read it rather than override it. It is 0 when there is no bar (no `header`, no `nav`), and from 1024px when the bar only held the menu button. The bar is now `box-sizing: border-box`, so it is 56px everywhere (it was 57px where no border-box reset applied).
198
+ - **`--aura-fg-warning`** (item 106): warning text on the page — amber-800 in light (7.1:1 on surface, 6.8:1 on canvas), amber-300 in dark (12.3:1, 13.9:1), checked in CI. `--aura-status-*-fg` are only for text on their pill's own fill; the warning one is near-black in dark mode.
199
+ - **DataTable cards start-align every field** (item 108): a column with `align: 'end'` is right-aligned in the grid and starts under its label in the stacked cards, with tabular figures in both. A visible change to cards with amount columns.
200
+ - **Tabs `current="location"`** (item 107): link tabs to sections of the same page mark the current one `aria-current="location"` (default `page`).
201
+
202
+ ## 5.13 — Chamber-OS addendum 13 (board parity)
203
+
204
+ - **DataTable card options** (item 80): per column, `card: 'hide'` leaves it out of the stacked card (the grid keeps it), `card: 'title' | 'pill' | 'field'` places it (a declared title or pill replaces the automatic one; the automatic title is the first column without a `card` — normally the first column — skipping `actions` columns), and `cardOrder` orders the fields (lowest first; others sort by column index) without touching the grid's order. `hideSelectionInCards` drops the row boxes and the select-all line from the cards only; the selection is kept, and Space doesn't change it while cards show. Screen readers and arrow keys follow the grid's order and skip what the card leaves out (the tab stop too); each remaining field keeps its label; loading skeletons leave out the same columns. With `getRowHref`, the link is the first column's text, so hiding that column in cards leaves the card clickable but without a link to Tab to. All in CSS, so the server HTML is already right.
205
+ ```tsx
206
+ <DataTable stackBelow={640} hideSelectionInCards columns={[{ key: 'company', label: 'COMPANY' }, { key: 'flag', label: 'COUNTRY', card: 'hide' }, { key: 'id', label: 'MEMBER NO.', cardOrder: 1 }, …]} />
207
+ ```
208
+ - **Table `align="middle"` and `bordered={false}`** (item 81): `middle` centres body and footer cells on their row (default `top`); `bordered={false}` drops the frame and the outer cells' side padding, so a table inside a Card lines up with its heading. Stacked rows keep their layout. `Table`'s type no longer accepts the obsolete HTML `align` attribute (`left | center | right`).
209
+ - **EmptyState `tone="danger"`** (item 82): the danger tint, a danger-coloured icon and, with `bordered`, a solid danger frame — for "couldn't load". It stays quiet (no role); pass `role="alert"` to interrupt. EmptyState now passes `id`, `data-*`, `aria-*`, `role` and `style` to its root.
210
+ - **FilterBar search filling the row** (item 83): that is 5.12's `searchGrow`; nothing new was needed.
211
+ - **Td `card="title" | "action"`** (item 84; also on a row header, `Th scope="row"`): in a stacked Table row the title takes the first line without a label, the action sits at the end of that line at its natural size, and the other cells fall two to a line below. A long title wraps before the action. The desktop table is unchanged.
212
+ ```tsx
213
+ <Tr><Td card="title">{company} <span className="aura-table__mono">{no}</span></Td><Td>{type}</Td><Td>{date}</Td>
214
+ <Td card="action"><Button size="sm" variant="secondary">Review</Button></Td></Tr>
215
+ ```
216
+
189
217
  ## 5.12 — Chamber-OS addendum 12 (compact filters)
190
218
 
191
219
  - **FilterSelect** (item 79): a filter for `FilterBar` drawn as one small button — "Status All ▾" — as wide as its words, so several filters and a toggle `Tag` share a row, three and a Tag even at 375px. It is Select underneath: it opens the same AURA list (on phones too, as Select does since 5.3), with the same keys (arrows, typing, Enter, Escape), `name` in a form and a real `<select>` before hydration (with JavaScript off a choice still posts with the form, but the face keeps the server's value). `onChange` gets the value, so with react-hook-form use a `Controller` (`render={({ field }) => <FilterSelect label="Plan" {...field} options={plans} />}`), not `register`. `label` is the filter's name and its accessible name; screen readers hear the chosen option as the value. The first option is the "all" choice: `allLabel` is the shorter word its face shows ("All" for "All statuses"). A long value ("Diamond Partnership") shows in full and the button moves to the next row instead of cutting it. Right to left, the list opens from the face's right edge.
@@ -674,12 +674,31 @@ window.Aura = (() => {
674
674
  );
675
675
  }
676
676
  function emptyStateElement(props, ref) {
677
- const HT = "h" + (props.headingLevel || 3);
677
+ const HT = props.headingLevel === false ? "p" : "h" + (props.headingLevel || 3);
678
+ const rest = omit(props, [
679
+ "title",
680
+ "description",
681
+ "icon",
682
+ "action",
683
+ "size",
684
+ "bordered",
685
+ "headingLevel",
686
+ "tone",
687
+ "className",
688
+ "children"
689
+ ]);
678
690
  return /* @__PURE__ */ React4.createElement(
679
691
  "div",
680
692
  {
693
+ ...rest,
681
694
  ref,
682
- className: cx("aura-empty", props.size === "sm" && "is-sm", props.bordered && "is-bordered", props.className)
695
+ className: cx(
696
+ "aura-empty",
697
+ props.size === "sm" && "is-sm",
698
+ props.bordered && "is-bordered",
699
+ props.tone === "danger" && "is-danger",
700
+ props.className
701
+ )
683
702
  },
684
703
  /* @__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" })),
685
704
  /* @__PURE__ */ React4.createElement(HT, { className: "aura-empty__title" }, props.title),
@@ -687,6 +706,66 @@ window.Aura = (() => {
687
706
  props.action ? /* @__PURE__ */ React4.createElement("div", { className: "aura-empty__action" }, props.action) : null
688
707
  );
689
708
  }
709
+ function statElement(props, ref, Link) {
710
+ const ch = props.change;
711
+ const dir = ch && (ch.direction || "flat");
712
+ const tone2 = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
713
+ const v = props.value;
714
+ const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
715
+ const Tag3 = props.href ? Link : props.onClick ? "button" : "div";
716
+ const interactive = !!(props.href || props.onClick);
717
+ if (props.headingLevel && Tag3 === "button")
718
+ devWarnOnce(
719
+ "stat-heading-button",
720
+ "Stat `headingLevel` is ignored with `onClick`: a heading can\u2019t sit in a button. Use `href`, or put the heading outside."
721
+ );
722
+ const LabelTag = props.headingLevel && Tag3 !== "button" ? "h" + props.headingLevel : "span";
723
+ const HeadTag = LabelTag === "span" ? "span" : "div";
724
+ return /* @__PURE__ */ React4.createElement(
725
+ Tag3,
726
+ {
727
+ ref,
728
+ className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
729
+ href: props.href,
730
+ onClick: props.onClick,
731
+ type: Tag3 === "button" ? "button" : void 0,
732
+ "aria-busy": props.loading || void 0
733
+ },
734
+ /* @__PURE__ */ React4.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React4.createElement(LabelTag, { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React4.createElement(Icon, { name: props.icon })) : null),
735
+ props.loading ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React4.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React4.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
736
+ ch && !props.loading || props.caption ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React4.createElement("span", { className: cx("aura-stat__change", "is-" + tone2) }, /* @__PURE__ */ React4.createElement(
737
+ Icon,
738
+ {
739
+ name: dir === "up" ? /* @__PURE__ */ React4.createElement(IconTrendingUp, null) : dir === "down" ? /* @__PURE__ */ React4.createElement(IconTrendingDown, null) : /* @__PURE__ */ React4.createElement(IconMinus, null),
740
+ size: 14
741
+ }
742
+ ), /* @__PURE__ */ React4.createElement("span", null, ch.value)) : null, ch && ch.label && !props.loading ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__caption" }, ch.label) : null, props.caption ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__caption" }, props.caption) : null) : null
743
+ );
744
+ }
745
+ var AVATAR_TONES = ["progress", "ready", "neutral", "warning"];
746
+ function initials(name) {
747
+ const parts = String(name || "?").trim().split(/\s+/);
748
+ return ((parts[0] || "?")[0] + (parts.length > 1 ? parts[parts.length - 1][0] : parts[0][1] || "")).toUpperCase();
749
+ }
750
+ function avatarElement(props, ref, broken, onError) {
751
+ const size = props.size || "md";
752
+ let hash = 0;
753
+ String(props.name || "").split("").forEach(function(ch) {
754
+ hash = hash * 31 + ch.charCodeAt(0) >>> 0;
755
+ });
756
+ const tone2 = AVATAR_TONES[hash % AVATAR_TONES.length];
757
+ return /* @__PURE__ */ React4.createElement(
758
+ "span",
759
+ {
760
+ ref,
761
+ className: cx("aura-avatar", "aura-avatar--" + size, "aura-avatar--" + tone2, props.className),
762
+ role: "img",
763
+ "aria-label": props.name + (props.status ? ", " + props.status : "")
764
+ },
765
+ props.src && broken !== props.src ? /* @__PURE__ */ React4.createElement("img", { key: props.src, src: props.src, alt: "", onError }) : /* @__PURE__ */ React4.createElement("span", { "aria-hidden": true }, initials(props.name)),
766
+ props.status === "online" ? /* @__PURE__ */ React4.createElement("span", { className: "aura-avatar__status", "aria-hidden": true }) : null
767
+ );
768
+ }
690
769
  function buttonClass(opts = {}) {
691
770
  return cx(
692
771
  "aura-btn",
@@ -1175,6 +1254,8 @@ window.Aura = (() => {
1175
1254
  const props = all;
1176
1255
  const variant = props.variant || "primary";
1177
1256
  const loading = !!props.loading;
1257
+ const ad = props["aria-disabled"];
1258
+ const blocked = loading || ad === true || ad === "true";
1178
1259
  const rest = omit(props, [
1179
1260
  "variant",
1180
1261
  "className",
@@ -1201,8 +1282,8 @@ window.Aura = (() => {
1201
1282
  className: props.className
1202
1283
  }),
1203
1284
  "aria-busy": loading || void 0,
1204
- "aria-disabled": loading || void 0,
1205
- onClick: loading ? function(e) {
1285
+ "aria-disabled": blocked || void 0,
1286
+ onClick: blocked ? function(e) {
1206
1287
  e.preventDefault();
1207
1288
  } : props.onClick
1208
1289
  },
@@ -4811,6 +4892,7 @@ window.Aura = (() => {
4811
4892
  [measure, levelShape]
4812
4893
  );
4813
4894
  const stacked = !!props.stackBelow && boxWidth[0] != null && below(boxWidth[0], props.stackBelow);
4895
+ const noCardSel = !!props.hideSelectionInCards && selectable;
4814
4896
  useIsoLayoutEffect(
4815
4897
  function() {
4816
4898
  if (measure && wrapRef.current) boxWidth[1](wrapRef.current.getBoundingClientRect().width);
@@ -4911,9 +4993,38 @@ window.Aura = (() => {
4911
4993
  const pillKey = (vis.filter(function(c, i) {
4912
4994
  return i > 0 && c.pill;
4913
4995
  })[0] || { key: "" }).key;
4914
- function cardPart(c, i) {
4915
- return i === 0 ? "title" : c.key === pillKey ? "pill" : c.actions ? "actions" : "field";
4996
+ const ownTitle = vis.some(function(c) {
4997
+ return c.card === "title";
4998
+ }), ownPill = vis.some(function(c) {
4999
+ return c.card === "pill";
5000
+ });
5001
+ let autoTitle = -1;
5002
+ if (!ownTitle) {
5003
+ for (let i = 0; i < vis.length; i++)
5004
+ if (!vis[i].card && (i === 0 || !vis[i].actions)) {
5005
+ autoTitle = i;
5006
+ break;
5007
+ }
4916
5008
  }
5009
+ function cardPart(c, i) {
5010
+ if (c.card) return c.card;
5011
+ if (i === autoTitle) return "title";
5012
+ if (c.key === pillKey && !ownPill) return "pill";
5013
+ return c.actions ? "actions" : "field";
5014
+ }
5015
+ const cardRank = {};
5016
+ if (vis.some(function(c) {
5017
+ return c.cardOrder != null;
5018
+ }))
5019
+ vis.map(function(c, i) {
5020
+ return { c, i, o: c.cardOrder != null ? c.cardOrder : i };
5021
+ }).filter(function(x) {
5022
+ return cardPart(x.c, x.i) === "field";
5023
+ }).sort(function(a, b) {
5024
+ return a.o - b.o || a.i - b.i;
5025
+ }).forEach(function(x, n3) {
5026
+ cardRank[x.c.key] = n3;
5027
+ });
4917
5028
  function cardAttrs(c, i) {
4918
5029
  const part = cardPart(c, i), lv = hideLevel(c);
4919
5030
  return {
@@ -4955,6 +5066,7 @@ window.Aura = (() => {
4955
5066
  s["--aura-cell-w"] = w + "px";
4956
5067
  }
4957
5068
  if (isPinned(c)) s["--aura-cell-left"] = "calc(var(--aura-space-6)" + selW + " + " + pinOffsets[c.key] + ")";
5069
+ if (cardRank[c.key] != null) s["--aura-card-order"] = String(5 + cardRank[c.key]);
4958
5070
  return s;
4959
5071
  }
4960
5072
  const sortCol = sort && sort.key ? byKey[sort.key] : void 0;
@@ -5095,9 +5207,37 @@ window.Aura = (() => {
5095
5207
  function colAt(ci) {
5096
5208
  return selectable ? ci === 0 ? null : vis[ci - 1] : vis[ci];
5097
5209
  }
5210
+ const cardHidden = [];
5211
+ for (let ci = 0; ci < nCols; ci++) {
5212
+ const col = colAt(ci);
5213
+ cardHidden.push(stacked && (col ? cardPart(col, vis.indexOf(col)) === "hide" : noCardSel));
5214
+ }
5215
+ function nearestShown(c, dir, ok) {
5216
+ if (ok(c)) return c;
5217
+ for (let d = 1; d < nCols; d++) {
5218
+ const a = c + d * dir, b = c - d * dir;
5219
+ if (a >= 0 && a < nCols && ok(a)) return a;
5220
+ if (b >= 0 && b < nCols && ok(b)) return b;
5221
+ }
5222
+ return c;
5223
+ }
5224
+ const acShown = nearestShown(ac, 1, function(cc) {
5225
+ return !cardHidden[cc];
5226
+ });
5227
+ const lastFocus = React31.useRef(null);
5228
+ React31.useEffect(
5229
+ function() {
5230
+ const was = lastFocus.current, g = gridRef.current;
5231
+ if (!stacked || !was || !g || !g.contains(was) || was.getClientRects().length > 0) return;
5232
+ if (document.activeElement && document.activeElement !== document.body && document.activeElement !== was) return;
5233
+ const el = g.querySelector('[data-rc="' + Math.max(1, ar) + ":" + acShown + '"]');
5234
+ if (el && el.getClientRects().length > 0) el.focus();
5235
+ },
5236
+ [stacked]
5237
+ );
5098
5238
  function tabFor(r, c) {
5099
5239
  if (!activeRendered) return r === 0 && c === ac ? 0 : -1;
5100
- return r === ar && c === ac ? 0 : -1;
5240
+ return r === ar && c === acShown ? 0 : -1;
5101
5241
  }
5102
5242
  React31.useEffect(function() {
5103
5243
  const p = pending.current;
@@ -5121,26 +5261,17 @@ window.Aura = (() => {
5121
5261
  el.focus();
5122
5262
  }
5123
5263
  });
5124
- function focusCell(r, c) {
5264
+ function focusCell(r, c, dir) {
5125
5265
  r = Math.max(0, Math.min(r, lastR));
5126
5266
  c = Math.max(0, Math.min(c, nCols - 1));
5127
- if (stacked && r === 0 && nRows > 0 && !(selectable && c === 0)) r = 1;
5128
- if (gridRef.current && r === T && hasTotal) {
5267
+ if (stacked && r === 0 && nRows > 0 && (!(selectable && c === 0) || noCardSel)) r = 1;
5268
+ if (gridRef.current && (r === T && hasTotal || stacked)) {
5129
5269
  const shown2 = function(cc) {
5270
+ if (cardHidden[cc]) return false;
5130
5271
  const e = gridRef.current.querySelector('[data-rc="' + r + ":" + cc + '"]');
5131
5272
  return !!e && e.getClientRects().length > 0;
5132
5273
  };
5133
- if (!shown2(c))
5134
- for (let d = 1; d < nCols; d++) {
5135
- if (c + d < nCols && shown2(c + d)) {
5136
- c = c + d;
5137
- break;
5138
- }
5139
- if (c - d >= 0 && shown2(c - d)) {
5140
- c = c - d;
5141
- break;
5142
- }
5143
- }
5274
+ c = nearestShown(c, dir || 1, shown2);
5144
5275
  }
5145
5276
  activeState[1]({ r, c });
5146
5277
  pending.current = { r, c };
@@ -5400,7 +5531,7 @@ window.Aura = (() => {
5400
5531
  } else if (r === 0 && col && (e.altKey && k === "ArrowDown" || k === "ContextMenu" || e.shiftKey && k === "F10") && controls)
5401
5532
  openMenu("col", col.key, target, { r: 0, c });
5402
5533
  else if (k === "ArrowRight") focusCell(r, c + 1);
5403
- else if (k === "ArrowLeft") focusCell(r, c - 1);
5534
+ else if (k === "ArrowLeft") focusCell(r, c - 1, -1);
5404
5535
  else if (k === "ArrowDown") {
5405
5536
  if (r < lastR) focusCell(r + 1, c);
5406
5537
  else if (pageSize && page < pageCount && goPage(page + 1, { r: 1, c })) activeState[1]({ r: 1, c });
@@ -5408,7 +5539,7 @@ window.Aura = (() => {
5408
5539
  if (r > 1 || r === 1 && !(pageSize && page > 1)) focusCell(r - 1, c);
5409
5540
  else if (r === 1 && goPage(page - 1, { r: pageSize, c })) activeState[1]({ r: pageSize, c });
5410
5541
  } else if (k === "Home") focusCell(e.ctrlKey ? 1 : r, 0);
5411
- else if (k === "End") focusCell(e.ctrlKey ? lastR : r, nCols - 1);
5542
+ else if (k === "End") focusCell(e.ctrlKey ? lastR : r, nCols - 1, -1);
5412
5543
  else if (k === "PageDown") {
5413
5544
  if (pageSize) {
5414
5545
  if (goPage(page + 1, { r: 1, c })) activeState[1]({ r: 1, c });
@@ -5428,7 +5559,7 @@ window.Aura = (() => {
5428
5559
  if (pageKeys.length) toggleAll();
5429
5560
  } else if (r === 0 && col && col.sortable && canSortAny) sortBy(col.key);
5430
5561
  else if (r > 0 && k === " " && selectable) {
5431
- if (canSelect(row)) toggle(row[rowKey], !selSet[row[rowKey]]);
5562
+ if (canSelect(row) && !(stacked && noCardSel)) toggle(row[rowKey], !selSet[row[rowKey]]);
5432
5563
  } else if (r > 0 && k === "Enter" && rowHref && !target.querySelector("button, a[href]:not(.aura-table__row-link), input, select, textarea"))
5433
5564
  followRow(target.closest('[role="row"]'), e);
5434
5565
  else if (r > 0 && k === "Enter" && target.querySelector("button, a[href], input, select, textarea"))
@@ -5488,6 +5619,7 @@ window.Aura = (() => {
5488
5619
  "aria-colindex": ci + 1,
5489
5620
  style: cellStyle(c, i),
5490
5621
  "data-hide": hideLevel(c) ? String(hideLevel(c)) : void 0,
5622
+ "data-card": cardPart(c, i) === "hide" ? "hide" : void 0,
5491
5623
  tabIndex: tabFor(0, ci),
5492
5624
  "data-rc": "0:" + ci,
5493
5625
  className: cx(
@@ -5853,7 +5985,7 @@ window.Aura = (() => {
5853
5985
  ),
5854
5986
  style: cellStyle(c, j),
5855
5987
  ...cardAttrs(c, j),
5856
- "data-label": j === 0 ? void 0 : c.label || t.totals
5988
+ "data-label": cardPart(c, j) === "title" ? void 0 : c.label || t.totals
5857
5989
  },
5858
5990
  props.footer[c.key]
5859
5991
  );
@@ -5881,6 +6013,7 @@ window.Aura = (() => {
5881
6013
  className: cx(
5882
6014
  "aura-table",
5883
6015
  stacked && "aura-table--stacked",
6016
+ noCardSel && "aura-table--cards-nosel",
5884
6017
  scrolledX[0] && "is-scrolled-x",
5885
6018
  refreshing && "is-refreshing",
5886
6019
  !levels.length && props.className
@@ -5898,8 +6031,16 @@ window.Aura = (() => {
5898
6031
  className: "aura-table__scroll",
5899
6032
  onMouseOver: showFull,
5900
6033
  onMouseLeave: hideFull,
5901
- onFocus: showFull,
5902
- onBlur: hideFull,
6034
+ onFocus: function(e) {
6035
+ lastFocus.current = e.target;
6036
+ showFull(e);
6037
+ },
6038
+ onBlur: function(e) {
6039
+ const to = e.relatedTarget;
6040
+ if ((!to || !e.currentTarget.contains(to)) && e.target.getClientRects().length > 0)
6041
+ lastFocus.current = null;
6042
+ hideFull();
6043
+ },
5903
6044
  style: scrollStyle,
5904
6045
  role: "grid",
5905
6046
  "aria-label": props.label,
@@ -6058,7 +6199,7 @@ window.Aura = (() => {
6058
6199
  key: t.id,
6059
6200
  href: t.href,
6060
6201
  className: tabCls(on, own.className),
6061
- "aria-current": on ? "page" : void 0,
6202
+ "aria-current": on ? props.current || "page" : void 0,
6062
6203
  onClick: function(e) {
6063
6204
  if (own.onClick) own.onClick(e);
6064
6205
  if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
@@ -6405,39 +6546,11 @@ window.Aura = (() => {
6405
6546
 
6406
6547
  // src/Avatar.tsx
6407
6548
  var React36 = __toESM(require_react(), 1);
6408
- var AVATAR_TONES = ["progress", "ready", "neutral", "warning"];
6409
- function initials(name) {
6410
- const parts = String(name || "?").trim().split(/\s+/);
6411
- return ((parts[0] || "?")[0] + (parts.length > 1 ? parts[parts.length - 1][0] : parts[0][1] || "")).toUpperCase();
6412
- }
6413
6549
  var Avatar = React36.forwardRef(function Avatar2(props, ref) {
6414
- const size = props.size || "md", errState = React36.useState(null);
6415
- let hash = 0;
6416
- String(props.name || "").split("").forEach(function(ch) {
6417
- hash = hash * 31 + ch.charCodeAt(0) >>> 0;
6550
+ const errState = React36.useState(null);
6551
+ return avatarElement(props, ref, errState[0], function() {
6552
+ errState[1](props.src || null);
6418
6553
  });
6419
- const tone2 = AVATAR_TONES[hash % AVATAR_TONES.length];
6420
- return /* @__PURE__ */ React36.createElement(
6421
- "span",
6422
- {
6423
- ref,
6424
- className: cx("aura-avatar", "aura-avatar--" + size, "aura-avatar--" + tone2, props.className),
6425
- role: "img",
6426
- "aria-label": props.name + (props.status ? ", " + props.status : "")
6427
- },
6428
- props.src && errState[0] !== props.src ? /* @__PURE__ */ React36.createElement(
6429
- "img",
6430
- {
6431
- key: props.src,
6432
- src: props.src,
6433
- alt: "",
6434
- onError: function() {
6435
- errState[1](props.src || null);
6436
- }
6437
- }
6438
- ) : /* @__PURE__ */ React36.createElement("span", { "aria-hidden": true }, initials(props.name)),
6439
- props.status === "online" ? /* @__PURE__ */ React36.createElement("span", { className: "aura-avatar__status", "aria-hidden": true }) : null
6440
- );
6441
6554
  });
6442
6555
 
6443
6556
  // src/Stack.tsx
@@ -6558,6 +6671,27 @@ window.Aura = (() => {
6558
6671
  if (shared) navState[1](id);
6559
6672
  if (navEl && navEl.props.onChange) navEl.props.onChange(id);
6560
6673
  }
6674
+ const rootRef = React41.useRef(null);
6675
+ const barRef = React41.useRef(null);
6676
+ const mergedRef = useMergedRef(ref, rootRef);
6677
+ const hasBar = !!(props.header || props.nav);
6678
+ useIsoLayoutEffect(
6679
+ function() {
6680
+ const root = rootRef.current, bar = barRef.current;
6681
+ if (!root || !bar) return;
6682
+ function sync() {
6683
+ root.style.setProperty("--aura-shell-bar-height", bar.getBoundingClientRect().height + "px");
6684
+ }
6685
+ sync();
6686
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(sync);
6687
+ if (ro) ro.observe(bar);
6688
+ return function() {
6689
+ if (ro) ro.disconnect();
6690
+ root.style.removeProperty("--aura-shell-bar-height");
6691
+ };
6692
+ },
6693
+ [hasBar]
6694
+ );
6561
6695
  const deskNav = navEl && shared ? React41.cloneElement(navEl, { value: navState[0], onChange: onNav }) : props.nav;
6562
6696
  const drawerNav = navEl ? React41.cloneElement(navEl, {
6563
6697
  value: shared ? navState[0] : navEl.props.value,
@@ -6569,32 +6703,56 @@ window.Aura = (() => {
6569
6703
  collapsed: false,
6570
6704
  collapsible: false
6571
6705
  }) : props.nav;
6572
- return /* @__PURE__ */ React41.createElement("div", { ref, className: cx("aura-shell", props.bottomNav && "aura-shell--bottomnav", props.className) }, props.nav ? /* @__PURE__ */ React41.createElement("div", { className: "aura-shell__nav" }, deskNav) : null, props.nav ? /* @__PURE__ */ React41.createElement(
6573
- Drawer,
6706
+ return /* @__PURE__ */ React41.createElement(
6707
+ "div",
6574
6708
  {
6575
- open,
6576
- onClose: function() {
6577
- setOpen(false);
6578
- },
6579
- side: "left",
6580
- size: "nav",
6581
- "aria-label": props.navLabel || t.navigation,
6582
- dismissible: true
6709
+ ref: mergedRef,
6710
+ className: cx(
6711
+ "aura-shell",
6712
+ props.bottomNav && "aura-shell--bottomnav",
6713
+ /* 5.14: no top bar at all, or one that only holds the menu button (gone from lg up): the bar-height token is 0. */
6714
+ !props.header && !props.nav && "aura-shell--no-bar",
6715
+ !props.header && props.nav && "aura-shell--menu-bar",
6716
+ props.className
6717
+ )
6583
6718
  },
6584
- drawerNav
6585
- ) : null, /* @__PURE__ */ React41.createElement("div", { className: "aura-shell__main" }, props.header || props.nav ? /* @__PURE__ */ React41.createElement("header", { className: cx("aura-shell__bar", !props.header && "aura-shell__bar--menu-only") }, props.nav ? /* @__PURE__ */ React41.createElement(
6586
- IconButton,
6587
- {
6588
- className: "aura-shell__menu",
6589
- icon: /* @__PURE__ */ React41.createElement(IconMenu, null),
6590
- label: props.menuLabel || t.openNav,
6591
- size: "md",
6592
- onClick: function() {
6593
- setOpen(true);
6719
+ props.nav ? /* @__PURE__ */ React41.createElement("div", { className: "aura-shell__nav" }, deskNav) : null,
6720
+ props.nav ? /* @__PURE__ */ React41.createElement(
6721
+ Drawer,
6722
+ {
6723
+ open,
6724
+ onClose: function() {
6725
+ setOpen(false);
6726
+ },
6727
+ side: "left",
6728
+ size: "nav",
6729
+ "aria-label": props.navLabel || t.navigation,
6730
+ dismissible: true
6594
6731
  },
6595
- "aria-expanded": open
6596
- }
6597
- ) : null, /* @__PURE__ */ React41.createElement("div", { className: "aura-shell__bar-content" }, props.header)) : null, /* @__PURE__ */ React41.createElement("main", { className: "aura-shell__content", id: props.mainId || "main", tabIndex: -1 }, props.children), props.bottomNav || null));
6732
+ drawerNav
6733
+ ) : null,
6734
+ /* @__PURE__ */ React41.createElement("div", { className: "aura-shell__main" }, props.header || props.nav ? /* @__PURE__ */ React41.createElement("header", { ref: barRef, className: cx("aura-shell__bar", !props.header && "aura-shell__bar--menu-only") }, props.nav ? /* @__PURE__ */ React41.createElement(
6735
+ IconButton,
6736
+ {
6737
+ className: "aura-shell__menu",
6738
+ icon: /* @__PURE__ */ React41.createElement(IconMenu, null),
6739
+ label: props.menuLabel || t.openNav,
6740
+ size: "md",
6741
+ onClick: function() {
6742
+ setOpen(true);
6743
+ },
6744
+ "aria-expanded": open
6745
+ }
6746
+ ) : null, /* @__PURE__ */ React41.createElement("div", { className: "aura-shell__bar-content" }, props.header)) : null, /* @__PURE__ */ React41.createElement(
6747
+ "main",
6748
+ {
6749
+ className: cx("aura-shell__content", props.contentPadding === false && "is-flush"),
6750
+ id: props.mainId || "main",
6751
+ tabIndex: -1
6752
+ },
6753
+ props.children
6754
+ ), props.bottomNav || null)
6755
+ );
6598
6756
  });
6599
6757
 
6600
6758
  // src/ActionBar.tsx
@@ -6704,7 +6862,7 @@ window.Aura = (() => {
6704
6862
  return out;
6705
6863
  }
6706
6864
  var Table = React44.forwardRef(function Table2(props, ref) {
6707
- const { caption, captionHidden, density, stackBelow, className, children, ...rest } = props;
6865
+ const { caption, captionHidden, density, stackBelow, align, bordered, className, children, ...rest } = props;
6708
6866
  const labels = stackBelow ? headerLabels(children) : null;
6709
6867
  if (labels && !labels.length)
6710
6868
  devWarnOnce(
@@ -6732,7 +6890,7 @@ window.Aura = (() => {
6732
6890
  "div",
6733
6891
  {
6734
6892
  ref: wrap,
6735
- className: cx("aura-tbl-wrap", stackBelow && "is-stackable"),
6893
+ className: cx("aura-tbl-wrap", stackBelow && "is-stackable", bordered === false && "is-flush"),
6736
6894
  "data-density": density,
6737
6895
  tabIndex: scrolls ? 0 : void 0,
6738
6896
  role: scrolls && (caption != null || rest["aria-label"]) ? "region" : void 0,
@@ -6743,7 +6901,12 @@ window.Aura = (() => {
6743
6901
  "table",
6744
6902
  {
6745
6903
  ref,
6746
- className: cx("aura-tbl", stackBelow && "aura-tbl--stack-" + stackBelow, className),
6904
+ className: cx(
6905
+ "aura-tbl",
6906
+ stackBelow && "aura-tbl--stack-" + stackBelow,
6907
+ align === "middle" && "aura-tbl--middle",
6908
+ className
6909
+ ),
6747
6910
  role: stackBelow ? "table" : void 0,
6748
6911
  ...rest
6749
6912
  },
@@ -6791,7 +6954,7 @@ window.Aura = (() => {
6791
6954
  );
6792
6955
  }
6793
6956
  var Th = React44.forwardRef(function Th2(props, ref) {
6794
- const { align, numeric, mono, className, scope, label, ...rest } = props;
6957
+ const { align, numeric, mono, className, scope, label, card, ...rest } = props;
6795
6958
  const stacked = React44.useContext(StackLabels) != null;
6796
6959
  return /* @__PURE__ */ React44.createElement(
6797
6960
  "th",
@@ -6800,14 +6963,16 @@ window.Aura = (() => {
6800
6963
  scope: scope || "col",
6801
6964
  className: cellClass("aura-tbl__th", props),
6802
6965
  role: stacked ? scope === "row" ? "rowheader" : "columnheader" : void 0,
6966
+ "data-card": stacked && (card === "title" || card === "action") ? card : void 0,
6803
6967
  ...rest
6804
6968
  }
6805
6969
  );
6806
6970
  });
6807
6971
  var Td = React44.forwardRef(function Td2(props, ref) {
6808
- const { align, numeric, mono, className, scope, label, ...rest } = props;
6972
+ const { align, numeric, mono, className, scope, label, card, ...rest } = props;
6809
6973
  const labels = React44.useContext(StackLabels), col = React44.useContext(ColumnIndex);
6810
- const shown = labels ? label != null ? label : col >= 0 ? labels[col] : void 0 : void 0;
6974
+ const slot = labels && (card === "title" || card === "action") ? card : void 0;
6975
+ const shown = labels && !slot ? label != null ? label : col >= 0 ? labels[col] : void 0 : void 0;
6811
6976
  return /* @__PURE__ */ React44.createElement(
6812
6977
  "td",
6813
6978
  {
@@ -6815,6 +6980,7 @@ window.Aura = (() => {
6815
6980
  className: cellClass("aura-tbl__td", props),
6816
6981
  role: labels ? "cell" : void 0,
6817
6982
  "data-label": shown || void 0,
6983
+ "data-card": slot,
6818
6984
  ...rest
6819
6985
  }
6820
6986
  );
@@ -6890,34 +7056,8 @@ window.Aura = (() => {
6890
7056
  // src/Stat.tsx
6891
7057
  var React47 = __toESM(require_react(), 1);
6892
7058
  var Stat = React47.forwardRef(function Stat2(props, ref) {
6893
- const ch = props.change;
6894
- const dir = ch && (ch.direction || "flat");
6895
- const tone2 = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
6896
7059
  const Link = useLinkComponent(props.linkComponent);
6897
- const v = props.value;
6898
- const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
6899
- const Tag3 = props.href ? Link : props.onClick ? "button" : "div";
6900
- const interactive = !!(props.href || props.onClick);
6901
- return /* @__PURE__ */ React47.createElement(
6902
- Tag3,
6903
- {
6904
- ref,
6905
- className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
6906
- href: props.href,
6907
- onClick: props.onClick,
6908
- type: Tag3 === "button" ? "button" : void 0,
6909
- "aria-busy": props.loading || void 0
6910
- },
6911
- /* @__PURE__ */ React47.createElement("span", { className: "aura-stat__head" }, /* @__PURE__ */ React47.createElement("span", { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React47.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React47.createElement(Icon, { name: props.icon })) : null),
6912
- props.loading ? /* @__PURE__ */ React47.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React47.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React47.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React47.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
6913
- ch && !props.loading || props.caption ? /* @__PURE__ */ React47.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React47.createElement("span", { className: cx("aura-stat__change", "is-" + tone2) }, /* @__PURE__ */ React47.createElement(
6914
- Icon,
6915
- {
6916
- name: dir === "up" ? /* @__PURE__ */ React47.createElement(IconTrendingUp, null) : dir === "down" ? /* @__PURE__ */ React47.createElement(IconTrendingDown, null) : /* @__PURE__ */ React47.createElement(IconMinus, null),
6917
- size: 14
6918
- }
6919
- ), /* @__PURE__ */ React47.createElement("span", null, ch.value)) : null, ch && ch.label && !props.loading ? /* @__PURE__ */ React47.createElement("span", { className: "aura-stat__caption" }, ch.label) : null, props.caption ? /* @__PURE__ */ React47.createElement("span", { className: "aura-stat__caption" }, props.caption) : null) : null
6920
- );
7060
+ return statElement(props, ref, Link);
6921
7061
  });
6922
7062
 
6923
7063
  // src/TimePicker.tsx