@jirawatpyk/aura-react 5.28.0 → 5.30.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,54 @@ 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.30 — Visual polish (audit, Oct 2026) and Chamber-OS addenda 38–40
190
+
191
+ One release: everything below ships together in 5.30.0.
192
+
193
+ ### Menus stay open on scroll; StatusPill weight (addenda 39–40)
194
+
195
+ - **An open menu follows its trigger** (item 136): DropdownMenu and every menu built on it (DataTable's row ⋯ and column menus) now move with the trigger on scroll and resize — a slight finger drag during a tap, the phone's address bar or keyboard no longer close it, as Popover already behaved. It closes once the trigger is wholly out of view (the page's, or a box around it that clips it — a fixed bar inside a scrolling wrapper isn't clipped by it, and `overflow-x: clip` only clips sideways), without taking focus back so the page doesn't jump; inside a Dialog or Drawer focus goes back to the trigger without scrolling, so it stays in the dialog. Escape, a pointerdown outside and choosing an item close it as before, with focus back on the trigger. No new prop.
196
+ - **StatusPill is 500 (medium)** (item 137) wherever it sits — before, it took its parent's weight (regular beside a heading, bold inside bold text). Badge stays 600, so the two stay distinct.
197
+
198
+ ### 44px fields and choice rows (addendum 38)
199
+
200
+ - **`touchHeight` on TextField, Textarea, Select, RadioGroup and Checkbox** (item 135): `touchHeight="always"` gives a 44px input box and 44px choice rows at every width — also with a mouse and inside `density="compact"` — for a form whose spec asks for 44px targets everywhere (Chamber-OS's issue dialog: zero-rate radios, certificate number, typed phrase). `touchHeight` (`true`) does it only below 640px or on a coarse pointer, as Button's `touchHeight`; touch screens already had 44px fields and rows. A one-line option's label is centred in its row (a Checkbox row is 45px: its label sits on a 21px line); a row with a description grows. On a Checkbox it applies to a labelled box; a bare box uses `hitArea`. Textarea accepts it too, but is always at least 96px tall, so nothing changes there. In a horizontal RadioGroup each option keeps its own height (one-line options stay centred beside a taller one with a description).
201
+ - **How:** the field's root gets `is-touch-always` / `is-touch`, which sets its own `--aura-input-height` to `--aura-touch-target` (44px) — any control inside that reads the input height follows, whatever density is around it. Label, hint and error ids are unchanged; Button's `touchHeight` is unchanged.
202
+
203
+ ### Visual polish, part 2
204
+
205
+ - **"Chosen" is violet everywhere** (audit item 4): a checked Checkbox or Radio, a Switch that is on, the active Tab's underline, the current Pagination page, the selected Calendar day and year, a completed Stepper step and a checked menu item use the new `--aura-control-checked-bg` / `-fg` (violet-700 with a white mark in light, violet-300 with a zinc-900 mark in dark). Buttons stay ink / off-white. A disabled Switch that is on keeps its whole violet track (faded, it fell under 3:1 from a disabled one that is off) and fades its thumb instead.
206
+ - **Tables say when they scroll** (item 9): a Table wider than its box shows a soft shadow at the edge with more to scroll (CSS only, `--aura-scroll-shadow`; body rows show it, the header band and totals rows cover it; set `--aura-tbl-bg` if you give the box another ground); a DataTable with columns past its end edge shows the same shadow over the grid's end edge, inside its scrollbars (`has-more-x`), until scrolled to the end. Stacking stays opt-in (`stackBelow`) — no layout changes by default.
207
+ - **Table columns are never squeezed to one letter** (item 8): from 640px a cell is at least 10ch wide; a longer word (an email, a URL) still breaks to fit rather than widening the table; numbers already stay on one line. Phones are unchanged (a table still fits its box): give a table that is too wide for a phone `stackBelow`.
208
+ - **Totals** (item 20): a 2px rule in the hairline colour over the totals (DataTable and Table), not the strong border that glared in dark; with several totals rows the last is 14px bold; a pinned DataTable totals row casts a soft shadow over the rows under it.
209
+ - **SegmentedControl** (items 11–12) is as tall as a field with a mouse (`--aura-input-height`: 44px, 36px in compact; `size="sm"` unchanged; icon-only options stay square; on touch screens each option is 44px, as before), and the selected option is 600 weight on its own thumb colour (`--aura-segmented-thumb-bg`), which is lighter than the track in dark instead of darker.
210
+ - **FilterSelect** (item 11): from 640px the face has a field's inset and 14px text, like the search box beside it; phones keep the tight face so three filters and a chip share a row.
211
+ - **Smaller fixes**: the show-password button is fg-secondary like a field's other icons (item 14); a removable Tag's × is 14px (item 17); a plain link (no class) inside a Card, Stat, Alert, EmptyState, field, tab panel, AppShell main, table or Dialog takes the accent colour, at zero specificity so your own link rules win (item 18).
212
+ - **Not changed, on purpose**: a ghost button that starts an ActionBar keeps its box at the bar's edge (Chamber-OS 116 relies on it); the Progress track keeps its 3:1 edge (WCAG 1.4.11); DatePicker's calendar button and TimePicker's clock stay where they are (one is a button, one a label icon).
213
+ - Size budgets: `styles.css` 32 kB, DataTable 21 kB, `aura.bundle.js` and the whole package 66 kB.
214
+
215
+ ### Visual polish
216
+
217
+ A visual audit of every Storybook story in light, dark and at 390px. This release takes the system-wide fixes; colour hues and layout defaults are unchanged.
218
+
219
+ - **Dark mode:** the primary button, checked box, selected day, completed step, `bg-surface-strong` and `bg-inverted` (tooltips, counts) are off-white `zinc-200` instead of pure white, which was the brightest thing on a dark page. Primary hover is `zinc-100`.
220
+ - **Status fills:** Blocked is `red-800` in light (quieter in a long table, and further from the lime Ready fill); Warning is `amber-600` in dark too (amber-500 glowed). Ready, Warning and Blocked still differ by lightness, not only hue.
221
+ - **Neutral fills show on the light canvas:** `--aura-status-neutral-bg` is `zinc-200` (was `zinc-100` on a `zinc-50` page), so the neutral StatusPill, tab counts, Avatar and SideNav initials are one step darker in light. The neutral Badge and the EmptyState icon disc use it too.
222
+ - **DataTable header band** has its own token, `--aura-table-head-bg`: the canvas in light, `#111114` in dark — between the page and the card, so it no longer reads as a hole. Table's header and totals rows (also the totals card) and DataTable's totals row use it.
223
+ - **Disabled filled buttons** (primary, danger, creative) use new tokens `--aura-button-disabled-bg` / `-fg`: a grey fill with muted text at full opacity, not a faded copy (faded red read as pink, faded ink as a separate grey button); the creative shadow goes. Secondary, ghost and danger-secondary keep the fade. In forced colours every disabled button shows GrayText and the fade.
224
+ - **Buttons are always the AURA sans** (`font-family: var(--font-sans)`), also outside a root that sets the font. An app that sets its own font on `body` should set `--font-sans` too, or its buttons stay Inter.
225
+ - **Table caption** has 12px below it, so it no longer touches the header.
226
+ - **DataTable skeleton action bars** take the button's own shape (`--aura-button-radius`).
227
+ - **Thai:** where the language is Thai (`lang="th"` on the page or a part), StatusPill, Badge and tab counts are 12px (the loading pill matches), BottomNav labels 12px, Stepper descriptions 13px with a 1.6 line, SideNav labels on a 22px line, field labels, hints and errors on a 1.6 line, Radio and Checkbox descriptions 13px on a 1.6 line, and a stacked card's field labels 12px. A `lang="en"` part inside keeps the Latin sizes. These follow the `lang` attribute, not `AuraProvider locale`: set `<html lang="th">` (Next.js: the root layout's `<html lang>`), so portals (Dialog, Menu, Popover) get it too.
228
+
229
+ ## 5.29 — Chamber-OS addendum 37
230
+
231
+ - **DataTable loading rows match the rows they stand in for** (item 134), so the layout doesn't jump when the rows arrive:
232
+ - **Height:** with `rowHeight="auto"` skeleton rows get the same `--auto` class and minimum height as real rows. A column's new **`skeletonLines`** (default 1, at most 10; a `pill` column keeps one pill) draws that many bars, one per text line — `skeletonLines: 2` for a reason and its evidence. On an `align: 'end'` column they line up at the end. Each bar sits on a 20px body line in the grid (a caption line is 16–18px, so a row can come out a few px taller than the real one) and on the 15px value line in a card.
233
+ - **Stacked cards:** skeleton cards keep each field's label (`data-label`) above its bar, as a real card does.
234
+ - **Row actions:** an `actions` cell draws one square and a `card: 'footer'` cell a bar across the cell (in a card, the full row, as a Button there fills it), at the small button's height (32px), in the grid as well as in cards. When the column's buttons use `touchHeight`, give the column the new **`skeletonTouch`**: its bars are then 44px below 640px and on a coarse pointer, as those buttons are. A footer that holds only an IconButton still gets the full-width bar; the heights match.
235
+ - **Unchanged:** skeleton rows stay `aria-hidden`, the pulse stops under reduced motion, `skeletonRows`, `bleed` / `bleedEnd`. Without `rowHeight="auto"` the grid's skeleton rows keep the fixed row height.
236
+
189
237
  ## 5.28 — Chamber-OS addenda 35–36
190
238
 
191
239
  - **Applied-filter chips show their whole text** (item 132): in `FilterBar`'s chips row a chip takes its text's own width ("Submitted: 28 Aug – 3 Sept 2026", "Member: Midsommar Hospitality Co., Ltd."); only a chip wider than the row is cut, with an ellipsis, and a long chip never widens the FilterBar or its parents (the chips row takes its width from the bar; a bar holding only chips needs a width from its parent, as a block has). A Tag outside FilterBar keeps its 24ch cap. **Whenever a Tag's text is cut** — the 24ch cap or the row — the full text shows on hover (`title`) and, on a removable Tag, in a Tooltip on the remove button's hover and focus; the remove button's name already carries the full text for screen readers. On touch screens a cut chip can't be hovered — keep labels short enough for a phone row where you can.
@@ -158,6 +158,9 @@ window.Aura = (() => {
158
158
  out[k] = src[k];
159
159
  return out;
160
160
  }
161
+ function touchClass(t) {
162
+ return t === "always" ? "is-touch-always" : !!t && "is-touch";
163
+ }
161
164
  var TONES = ["neutral", "accent", "success", "warning", "danger"];
162
165
  function tone(t) {
163
166
  return TONES.indexOf(t) >= 0 ? t : "neutral";
@@ -1364,6 +1367,39 @@ window.Aura = (() => {
1364
1367
  var React8 = __toESM(require_react(), 1);
1365
1368
  var import_react_dom = __toESM(require_react_dom(), 1);
1366
1369
  var ITEMS = '[role^="menuitem"]';
1370
+ function makesBlock(s) {
1371
+ return s.transform !== "none" || s.translate !== "none" || s.rotate !== "none" || s.scale !== "none" || s.filter !== "none" || s.backdropFilter !== "none" || s.perspective !== "none" || s.contentVisibility === "auto" || /paint|layout|strict|content/.test(s.contain) || /transform|translate|rotate|scale|filter|perspective/.test(s.willChange);
1372
+ }
1373
+ function inView(el) {
1374
+ if (!el.isConnected) return false;
1375
+ const r = el.getBoundingClientRect();
1376
+ if (r.width === 0 && r.height === 0) return false;
1377
+ let top = r.top, bottom = r.bottom, left = r.left, right = r.right;
1378
+ let pos = getComputedStyle(el).position;
1379
+ for (let p = el.parentElement; p && p !== document.body && p !== document.documentElement; p = p.parentElement) {
1380
+ const s = getComputedStyle(p);
1381
+ const block = makesBlock(s);
1382
+ if (pos === "fixed" && !block) continue;
1383
+ if (pos === "absolute" && s.position === "static" && !block) continue;
1384
+ if (s.display !== "contents") {
1385
+ const b = p.getBoundingClientRect();
1386
+ if (s.overflowX !== "visible") {
1387
+ left = Math.max(left, b.left);
1388
+ right = Math.min(right, b.right);
1389
+ }
1390
+ if (s.overflowY !== "visible") {
1391
+ top = Math.max(top, b.top);
1392
+ bottom = Math.min(bottom, b.bottom);
1393
+ }
1394
+ }
1395
+ pos = s.position === "fixed" || s.position === "absolute" ? s.position : "static";
1396
+ }
1397
+ top = Math.max(top, 0);
1398
+ left = Math.max(left, 0);
1399
+ bottom = Math.min(bottom, window.innerHeight);
1400
+ right = Math.min(right, window.innerWidth);
1401
+ return bottom > top && right > left;
1402
+ }
1367
1403
  var Menu = React8.forwardRef(function Menu2(props, ref) {
1368
1404
  const own = React8.useRef(null), merged = useMergedRef(ref, own);
1369
1405
  const posState = React8.useState(null);
@@ -1373,21 +1409,30 @@ window.Aura = (() => {
1373
1409
  const headerId = uid(), itemId = uid();
1374
1410
  const hasHeader = props.header != null && props.header !== false && props.header !== "";
1375
1411
  const Link = useLinkComponent(props.linkComponent);
1412
+ function place() {
1413
+ const a = props.anchor, m = own.current;
1414
+ if (!a || !m) return;
1415
+ const r = a.getBoundingClientRect(), mh = m.scrollHeight, mw = m.offsetWidth, below2 = window.innerHeight - r.bottom - 12, above = r.top - 12;
1416
+ let top = r.bottom + 4, maxHeight;
1417
+ if (mh > below2) {
1418
+ if (mh <= above) top = r.top - mh - 4;
1419
+ else if (above > below2) {
1420
+ maxHeight = above;
1421
+ top = r.top - above - 4;
1422
+ } else maxHeight = below2;
1423
+ }
1424
+ const left = Math.max(8, Math.min(r.right - mw, window.innerWidth - mw - 8));
1425
+ setPos(function(p) {
1426
+ return p && p.top === top && p.left === left && p.maxHeight === maxHeight ? p : { top, left, maxHeight };
1427
+ });
1428
+ }
1429
+ const placeRef = React8.useRef(place);
1430
+ placeRef.current = place;
1431
+ const live = React8.useRef({ anchor: props.anchor, onClose: props.onClose });
1432
+ live.current = { anchor: props.anchor, onClose: props.onClose };
1376
1433
  useIsoLayoutEffect(
1377
1434
  function() {
1378
- const a = props.anchor, m = own.current;
1379
- if (!a || !m) return;
1380
- const r = a.getBoundingClientRect(), mh = m.scrollHeight, mw = m.offsetWidth, below2 = window.innerHeight - r.bottom - 12, above = r.top - 12;
1381
- let top = r.bottom + 4, maxHeight;
1382
- if (mh > below2) {
1383
- if (mh <= above) top = r.top - mh - 4;
1384
- else if (above > below2) {
1385
- maxHeight = above;
1386
- top = r.top - above - 4;
1387
- } else maxHeight = below2;
1388
- }
1389
- const left = Math.max(8, Math.min(r.right - mw, window.innerWidth - mw - 8));
1390
- setPos({ top, left, maxHeight });
1435
+ place();
1391
1436
  },
1392
1437
  [props.anchor, mounted, hasHeader]
1393
1438
  );
@@ -1417,7 +1462,12 @@ window.Aura = (() => {
1417
1462
  props.onClose(false);
1418
1463
  }
1419
1464
  function onScroll(e) {
1420
- if (own.current && !own.current.contains(e.target)) props.onClose(false);
1465
+ if (own.current && e.target instanceof Node && own.current.contains(e.target)) return;
1466
+ const a = live.current.anchor;
1467
+ if (a && !inView(a)) {
1468
+ live.current.onClose(false);
1469
+ if (a instanceof HTMLElement && a.closest('[aria-modal="true"]')) a.focus({ preventScroll: true });
1470
+ } else placeRef.current();
1421
1471
  }
1422
1472
  document.addEventListener("pointerdown", outside, true);
1423
1473
  window.addEventListener("scroll", onScroll, true);
@@ -1627,7 +1677,8 @@ window.Aura = (() => {
1627
1677
  "className",
1628
1678
  "tabIndex",
1629
1679
  "disabled",
1630
- "hitArea"
1680
+ "hitArea",
1681
+ "touchHeight"
1631
1682
  ]);
1632
1683
  const text2 = props.children;
1633
1684
  const labelled = text2 != null && text2 !== "";
@@ -1646,6 +1697,7 @@ window.Aura = (() => {
1646
1697
  className: cx(
1647
1698
  "aura-check",
1648
1699
  labelled && "aura-check--labelled",
1700
+ labelled && touchClass(props.touchHeight),
1649
1701
  hit && "has-hit",
1650
1702
  props.disabled && "is-disabled",
1651
1703
  props.className
@@ -1721,7 +1773,8 @@ window.Aura = (() => {
1721
1773
  "id",
1722
1774
  "suffix",
1723
1775
  "options",
1724
- "placeholder"
1776
+ "placeholder",
1777
+ "touchHeight"
1725
1778
  ];
1726
1779
 
1727
1780
  // src/TextField.tsx
@@ -1739,7 +1792,7 @@ window.Aura = (() => {
1739
1792
  required: props.required,
1740
1793
  optional: props.optional,
1741
1794
  disabled: props.disabled,
1742
- className: props.className
1795
+ className: cx(touchClass(props.touchHeight), props.className)
1743
1796
  },
1744
1797
  /* @__PURE__ */ React13.createElement("div", { className: cx("aura-input", props.icon && "has-icon", props.suffix && "has-suffix") }, props.icon ? /* @__PURE__ */ React13.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, /* @__PURE__ */ React13.createElement(
1745
1798
  "input",
@@ -1773,7 +1826,7 @@ window.Aura = (() => {
1773
1826
  required: props.required,
1774
1827
  optional: props.optional,
1775
1828
  disabled: props.disabled,
1776
- className: props.className
1829
+ className: cx(touchClass(props.touchHeight), props.className)
1777
1830
  },
1778
1831
  /* @__PURE__ */ React14.createElement(
1779
1832
  "textarea",
@@ -2207,7 +2260,7 @@ window.Aura = (() => {
2207
2260
  required: props.required,
2208
2261
  optional: props.optional,
2209
2262
  disabled: props.disabled,
2210
- className: props.className
2263
+ className: cx(touchClass(props.touchHeight), props.className)
2211
2264
  },
2212
2265
  child
2213
2266
  );
@@ -3439,7 +3492,12 @@ window.Aura = (() => {
3439
3492
  "fieldset",
3440
3493
  {
3441
3494
  ref: setRef,
3442
- className: cx("aura-field aura-radio-group", props.error && "is-invalid", props.className),
3495
+ className: cx(
3496
+ "aura-field aura-radio-group",
3497
+ props.error && "is-invalid",
3498
+ touchClass(props.touchHeight),
3499
+ props.className
3500
+ ),
3443
3501
  "aria-describedby": describedBy(id, props),
3444
3502
  "aria-invalid": props.error ? true : void 0,
3445
3503
  disabled: props.disabled
@@ -4607,7 +4665,7 @@ window.Aura = (() => {
4607
4665
  "aria-label": props.removeLabel || t.remove(name),
4608
4666
  onClick: props.onRemove
4609
4667
  },
4610
- /* @__PURE__ */ React27.createElement(Icon, { name: /* @__PURE__ */ React27.createElement(IconX, null), size: 12 })
4668
+ /* @__PURE__ */ React27.createElement(Icon, { name: /* @__PURE__ */ React27.createElement(IconX, null), size: 14 })
4611
4669
  );
4612
4670
  return /* @__PURE__ */ React27.createElement(Tooltip, { content: fullText, open: fullText ? void 0 : false }, btn);
4613
4671
  }() : null);
@@ -5408,6 +5466,28 @@ window.Aura = (() => {
5408
5466
  return px != null && below(boxWidth[0], px);
5409
5467
  }
5410
5468
  const scrollRef = React32.useRef(null);
5469
+ const edgeState = React32.useState("");
5470
+ const checkMoreX = function() {
5471
+ const el = scrollRef.current;
5472
+ if (!el) return;
5473
+ const more = Math.abs(el.scrollLeft) + el.clientWidth < el.scrollWidth - 1;
5474
+ const next = more ? [el.offsetTop, el.clientHeight, el.offsetWidth - el.clientWidth].join(" ") : "";
5475
+ if (next !== edgeState[0]) edgeState[1](next);
5476
+ };
5477
+ const checkRef = React32.useRef(checkMoreX);
5478
+ checkRef.current = checkMoreX;
5479
+ React32.useEffect(function() {
5480
+ const el = scrollRef.current;
5481
+ if (!el || typeof ResizeObserver === "undefined") return;
5482
+ const ro = new ResizeObserver(function() {
5483
+ checkRef.current();
5484
+ });
5485
+ ro.observe(el);
5486
+ if (el.firstElementChild) ro.observe(el.firstElementChild);
5487
+ return function() {
5488
+ ro.disconnect();
5489
+ };
5490
+ }, []);
5411
5491
  const tipState = React32.useState(null), tip = tipState[0], setTip = tipState[1];
5412
5492
  function showFull(e) {
5413
5493
  const el = e.target.closest ? e.target.closest(".aura-table__td") : null;
@@ -6339,23 +6419,45 @@ window.Aura = (() => {
6339
6419
  /* @__PURE__ */ React32.createElement("span", { key: "__sel", className: cx("aura-table__sel", nPinned && "is-pinned") }, /* @__PURE__ */ React32.createElement("span", { className: "aura-skel aura-skel--box" }))
6340
6420
  );
6341
6421
  vis.forEach(function(c, j) {
6422
+ const part = cardPart(c, j);
6423
+ const action = part === "footer" || part === "actions";
6424
+ const lines = c.pill ? 1 : Math.max(1, Math.min(10, Math.floor(Number(c.skeletonLines)) || 1));
6425
+ const bar = function(n4) {
6426
+ return /* @__PURE__ */ React32.createElement(
6427
+ "span",
6428
+ {
6429
+ key: n4,
6430
+ className: cx("aura-skel", c.pill && "aura-skel--pill", lines > 1 && "aura-skel--text"),
6431
+ style: c.pill ? void 0 : { width: SKELETON_WIDTHS[(i + j + n4) % SKELETON_WIDTHS.length] }
6432
+ }
6433
+ );
6434
+ };
6435
+ const bars = [];
6436
+ for (let b = 0; b < lines; b++) bars.push(bar(b));
6342
6437
  sk.push(
6343
6438
  /* @__PURE__ */ React32.createElement(
6344
6439
  "span",
6345
6440
  {
6346
6441
  key: c.key,
6347
- className: cx("aura-table__td", c.align === "end" && "is-end", isPinned(c) && "is-pinned"),
6442
+ className: cx(
6443
+ "aura-table__td",
6444
+ autoRows && "aura-table__td--auto",
6445
+ c.align === "end" && "is-end",
6446
+ isPinned(c) && "is-pinned"
6447
+ ),
6348
6448
  style: cellStyle(c, j),
6349
- ...cardAttrs(c, j),
6350
- "data-label": void 0
6449
+ ...cardAttrs(c, j)
6351
6450
  },
6352
- /* @__PURE__ */ React32.createElement(
6451
+ action ? /* @__PURE__ */ React32.createElement(
6353
6452
  "span",
6354
6453
  {
6355
- className: cx("aura-skel", c.pill && "aura-skel--pill"),
6356
- style: c.pill ? void 0 : { width: SKELETON_WIDTHS[(i + j) % SKELETON_WIDTHS.length] }
6454
+ className: cx(
6455
+ "aura-skel aura-skel--action",
6456
+ part === "footer" && "is-footer",
6457
+ c.skeletonTouch && "is-touch"
6458
+ )
6357
6459
  }
6358
- )
6460
+ ) : lines > 1 ? /* @__PURE__ */ React32.createElement("span", { className: "aura-skel-lines" }, bars) : bars[0]
6359
6461
  )
6360
6462
  );
6361
6463
  });
@@ -6364,7 +6466,16 @@ window.Aura = (() => {
6364
6466
  /* @__PURE__ */ React32.createElement("span", { key: "__gr", className: cx("aura-table__gutter aura-table__gutter--end", controls && "has-picker") })
6365
6467
  );
6366
6468
  skRows.push(
6367
- /* @__PURE__ */ React32.createElement("div", { key: "sk" + i, className: "aura-table__row aura-table__row--skeleton", "aria-hidden": true, style: rowStyle }, sk)
6469
+ /* @__PURE__ */ React32.createElement(
6470
+ "div",
6471
+ {
6472
+ key: "sk" + i,
6473
+ className: cx("aura-table__row aura-table__row--skeleton", autoRows && "aura-table__row--auto"),
6474
+ "aria-hidden": true,
6475
+ style: rowStyle
6476
+ },
6477
+ sk
6478
+ )
6368
6479
  );
6369
6480
  }
6370
6481
  } else if (!rows.length) {
@@ -6540,12 +6651,25 @@ window.Aura = (() => {
6540
6651
  stacked && "aura-table--stacked",
6541
6652
  noCardSel && "aura-table--cards-nosel",
6542
6653
  scrolledX[0] && "is-scrolled-x",
6654
+ edgeState[0] && !stacked && "has-more-x",
6543
6655
  refreshing && "is-refreshing",
6544
6656
  !levels.length && props.className
6545
6657
  ),
6546
6658
  style: levels.length ? { ["--aura-q-back"]: String(levels[levels.length - 1].px / Q) } : void 0
6547
6659
  },
6548
6660
  refreshing ? /* @__PURE__ */ React32.createElement("span", { className: "aura-table__busy-bar", "aria-hidden": true }) : null,
6661
+ edgeState[0] && !stacked ? /* @__PURE__ */ React32.createElement(
6662
+ "span",
6663
+ {
6664
+ className: "aura-table__edge",
6665
+ "aria-hidden": true,
6666
+ style: {
6667
+ "--aura-edge-top": edgeState[0].split(" ")[0] + "px",
6668
+ "--aura-edge-h": edgeState[0].split(" ")[1] + "px",
6669
+ "--aura-edge-sb": edgeState[0].split(" ")[2] + "px"
6670
+ }
6671
+ }
6672
+ ) : null,
6549
6673
  /* @__PURE__ */ React32.createElement(
6550
6674
  "div",
6551
6675
  {
@@ -6581,6 +6705,7 @@ window.Aura = (() => {
6581
6705
  setScrollTop(t2.scrollTop);
6582
6706
  const sx = t2.scrollLeft > 0;
6583
6707
  if (sx !== scrolledX[0]) scrolledX[1](sx);
6708
+ checkMoreX();
6584
6709
  }
6585
6710
  },
6586
6711
  /* @__PURE__ */ React32.createElement(