@jirawatpyk/aura-react 5.77.0 → 5.79.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,31 @@ 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.79 — visual audit, set 3 (component by component)
190
+
191
+ - **One disabled look for every Button variant:** a faint fill (`--aura-button-disabled-bg`: zinc-100 light, none in dark), a faint edge (`--aura-button-disabled-border` — the variant's own border, or 1px inside for primary and danger) and the tertiary label, at full opacity — primary, secondary, danger, creative and `aria-disabled` alike; a ghost button has neither fill nor edge. (5.30 gave filled buttons a grey fill that read as a To Do pill and faded outline ones.)
192
+ - **ActionBar `look="edge"` in an AppShell on a desktop** starts after the sidebar (the rail width when collapsed; the right edge in RTL) and lines its contents up with the content's 32px padding (not with `contentPadding={false}`), instead of running under the sidebar. It reads the sidebar's width from `--aura-sidenav-width` / `--aura-sidenav-rail-width`: to change it, set the token on `.aura-shell` or `:root`, not on the nav — and a custom `nav` set them to its width. Outside an AppShell it keeps the screen's width; `edgeBelow` is unchanged (no sidebar there).
193
+ - **A list box** (`<Select multiple>` or `size` > 1) shows its rows, as tall as they need, without the chevron.
194
+ - **Combobox `multiple`:** a chip no longer shrinks to "V.."; it keeps its label and moves to the next line (a label wider than the field still ends in "…").
195
+ - **Switch with an `icon`** (a locked, read-only switch): the lock sits 8px after the label, not at the row's far end. Its colours stay as 5.19 set them.
196
+ - **Menu / DropdownMenu:** a menu whose items have no icon, check box or radio has no empty icon column; it opens on its trigger's left edge when ending on its right would cross the screen (LTR; positioning stays physical, as before); and it is lifted with `--aura-shadow-overlay`, as Popover and the list boxes.
197
+ - **Small fixes:** FileUpload's hint names formats (PNG, JPEG, SVG, DOCX, XLSX), not "IMAGE/PNG"; an outline Badge has a quieter edge (its colour at 45%), so it doesn't read as a secondary Button; a date range is a violet-100 band in light (`--aura-calendar-range-bg`, the brand's 100 step in `createTheme`), visible on white; a disabled field's edge shows in dark (`--aura-input-disabled-border`, zinc-700).
198
+ - **Thai:** a Calendar with a Thai locale carries `lang="th"` and its weekday row is 13px; two-line Thai SideNav labels get room between lines; Combobox options have fixed line heights, so Thai and English rows are spaced alike.
199
+ - **PageHeader:** where `actionsFullWidthBelow` reaches wider than `actionsBarBelow`, every button in the row is `radius-md` between the two breakpoints (5.78 left the bar's buttons as pills there).
200
+ - **Stories:** the ⋯ beside outline buttons in action rows is `variant="outline"` (keep it so in an action row); the launch page shows with its own styles.
201
+
202
+ ## 5.78 — visual audit, set 2 (phones)
203
+
204
+ - **Bottom strips on a phone follow one rule:** ActionBar's edge strip (`look="edge"`, `edgeBelow`) now lays out as PageHeader's bar (5.48) — the status on its own row at the top, the actions sharing the row under it (an IconButton keeps its square; labels too long to share take a row each, as PageHeader's) — and both strips are 16px from either side, the page's phone gutter (they were 16 and 12). `edgeBelow="md"` follows it up to 768px. A single label wider than the screen still overflows, as in PageHeader's strip — keep bar labels short.
205
+ - **PageHeader `actionsFullWidthBelow`:** on a phone its buttons are `--aura-radius-md` boxes, so a label that wraps to two lines is a rounded box, not a lozenge (the ⋯ IconButton stays round) — also the header's own actions beside an `actionsBar` strip. The bottom strip keeps its pill buttons.
206
+ - **A document number in a PageHeader title stays whole:** in a plain-text title, a short token with a digit and a hyphen ("Invoice SC-2026-900001") never breaks at its hyphens; the line breaks before it. The text is unchanged (a `.aura-nowrap` span, a utility you can use too).
207
+ - **DataTable cards with `columnControls`:** the column picker's slot no longer squeezes the card's title (it took 48px of the first line).
208
+ - **StatusTile:** a host or a path in a plain-text title ("api.example.co.th") breaks after a dot or a slash, not in the middle of a label.
209
+ - **Tabs:** a tab list wider than its box fades the end that has more tabs (both ends once scrolled; mirrored in RTL), so a cut tab reads as "scroll for more". `data-aura-more="start" | "end" | "start end"` on the list.
210
+ - **Combobox and TimePicker:** room is kept for the clear button only while there is one — an empty TimePicker in a narrow column showed "hh:m".
211
+ - **Development warning:** a `DataTable` or `Table` that scrolls sideways on a screen under 640px without `stackBelow` warns once — its last columns are off screen. `stackBelow` stays opt-in in 5.x (it changes the layout) and becomes the default in 6.0.
212
+ - **Stories:** the examples that scrolled the page sideways at 390px (fixed grids, rows that didn't wrap, fixed-width demo boxes) and the demo tables now fit a phone.
213
+
189
214
  ## 5.77 — Chamber-OS addendum 95 follow-up (#194)
190
215
 
191
216
  - **A confirmation opens at its top on a short screen:** when a Dialog scrolls as one box (5.76) and its first focus lands in the footer — `data-autofocus` on Cancel, or a dialog with nothing to fill in — it no longer scrolls to its end on open: the title, the description and the summary of what is being confirmed are what show first, with Cancel focused below. Tab or a scroll reaches the buttons as before. A field in the body that takes the first focus is still scrolled into view (it is what you type in next). By design the focused Cancel then starts below the fold: focus is in the dialog, on the safe action, and a screen reader reads the title and description first. Above 30rem nothing changes — where the panel can't scroll, the browser keeps the focused button in view as before; a Drawer is unaffected. With `autoFocus={false}`, a footer button you focus yourself scrolls as the browser does.
@@ -174,15 +174,16 @@ window.Aura = (() => {
174
174
  return TONES.indexOf(t) >= 0 ? t : "neutral";
175
175
  }
176
176
  var warned = {};
177
- function devWarnOnce(key, message) {
178
- if (warned[key]) return;
179
- let dev = false;
177
+ function isDev() {
180
178
  try {
181
- dev = true;
179
+ return true;
182
180
  } catch (e) {
183
- dev = false;
181
+ return false;
184
182
  }
185
- if (!dev) return;
183
+ }
184
+ function devWarnOnce(key, message) {
185
+ if (warned[key]) return;
186
+ if (!isDev()) return;
186
187
  warned[key] = true;
187
188
  console.warn("[AURA] " + message);
188
189
  }
@@ -518,6 +519,48 @@ window.Aura = (() => {
518
519
  function pointerModality() {
519
520
  setModality("pointer");
520
521
  }
522
+ function useScrollEdges(r2, key) {
523
+ React3.useEffect(
524
+ function() {
525
+ const el = r2.current;
526
+ if (!el || typeof ResizeObserver === "undefined") return;
527
+ let raf = 0;
528
+ const mark = function() {
529
+ raf = 0;
530
+ const max = el.scrollWidth - el.clientWidth;
531
+ const x = Math.abs(el.scrollLeft);
532
+ const v = ((x > 1 ? "start " : "") + (x < max - 1 ? "end" : "")).trim();
533
+ if ((el.getAttribute("data-aura-more") || "") === v) return;
534
+ if (v) el.setAttribute("data-aura-more", v);
535
+ else el.removeAttribute("data-aura-more");
536
+ };
537
+ const on = function() {
538
+ if (!raf) raf = requestAnimationFrame(mark);
539
+ };
540
+ mark();
541
+ el.addEventListener("scroll", on, { passive: true });
542
+ const ro = new ResizeObserver(on);
543
+ ro.observe(el);
544
+ Array.prototype.forEach.call(el.children, function(c) {
545
+ ro.observe(c);
546
+ });
547
+ const mo = new MutationObserver(function() {
548
+ Array.prototype.forEach.call(el.children, function(c) {
549
+ ro.observe(c);
550
+ });
551
+ on();
552
+ });
553
+ mo.observe(el, { childList: true });
554
+ return function() {
555
+ cancelAnimationFrame(raf);
556
+ el.removeEventListener("scroll", on);
557
+ ro.disconnect();
558
+ mo.disconnect();
559
+ };
560
+ },
561
+ [key]
562
+ );
563
+ }
521
564
 
522
565
  // src/display.tsx
523
566
  var React4 = __toESM(require_react(), 1);
@@ -1886,7 +1929,8 @@ window.Aura = (() => {
1886
1929
  top = r2.top - above - 4;
1887
1930
  } else maxHeight = below2;
1888
1931
  }
1889
- const left = Math.max(8, Math.min(r2.right - mw, window.innerWidth - mw - 8));
1932
+ const endAligned = r2.right - mw;
1933
+ const left = Math.max(8, Math.min(endAligned >= 8 ? endAligned : r2.left, window.innerWidth - mw - 8));
1890
1934
  setPos(function(p) {
1891
1935
  return p && p.top === top && p.left === left && p.maxHeight === maxHeight ? p : { top, left, maxHeight };
1892
1936
  });
@@ -1985,12 +2029,15 @@ window.Aura = (() => {
1985
2029
  });
1986
2030
  return out;
1987
2031
  }
2032
+ const anyLead = items2.some(function(it) {
2033
+ return !it.separator && (!!it.icon || it.type === "radio" || it.checked !== void 0);
2034
+ });
1988
2035
  function renderItem(it, i) {
1989
2036
  if (it.separator) return /* @__PURE__ */ React9.createElement("div", { key: "s" + i, role: "separator", className: "aura-menu__sep" });
1990
2037
  const isRadio = it.type === "radio";
1991
2038
  const isCheck = !isRadio && it.checked !== void 0;
1992
2039
  const checkAtEnd = isCheck && !!it.icon && it.checkPlacement !== "start";
1993
- const lead = isRadio ? /* @__PURE__ */ React9.createElement("span", { className: cx("aura-menu__radio", it.checked && "is-on") }) : isCheck && !checkAtEnd ? /* @__PURE__ */ React9.createElement("span", { className: cx("aura-menu__check", it.checked && "is-on") }, it.checked ? /* @__PURE__ */ React9.createElement(Icon, { name: /* @__PURE__ */ React9.createElement(IconCheck, null), size: 12, strokeWidth: 3 }) : null) : it.icon ? /* @__PURE__ */ React9.createElement(Icon, { name: it.icon }) : /* @__PURE__ */ React9.createElement("span", { className: "aura-menu__blank" });
2040
+ const lead = isRadio ? /* @__PURE__ */ React9.createElement("span", { className: cx("aura-menu__radio", it.checked && "is-on") }) : isCheck && !checkAtEnd ? /* @__PURE__ */ React9.createElement("span", { className: cx("aura-menu__check", it.checked && "is-on") }, it.checked ? /* @__PURE__ */ React9.createElement(Icon, { name: /* @__PURE__ */ React9.createElement(IconCheck, null), size: 12, strokeWidth: 3 }) : null) : it.icon ? /* @__PURE__ */ React9.createElement(Icon, { name: it.icon }) : anyLead ? /* @__PURE__ */ React9.createElement("span", { className: "aura-menu__blank" }) : null;
1994
2041
  const body = [
1995
2042
  /* @__PURE__ */ React9.createElement(React9.Fragment, { key: "l" }, lead),
1996
2043
  /* @__PURE__ */ React9.createElement("span", { key: "t", className: "aura-menu__label" }, it.label),
@@ -3401,7 +3448,7 @@ window.Aura = (() => {
3401
3448
  setFocus(addMonths(focusDate, 1));
3402
3449
  }
3403
3450
  }
3404
- )), /* @__PURE__ */ React18.createElement("table", { role: "grid", className: "aura-cal__grid", "aria-label": monthTitle }, /* @__PURE__ */ React18.createElement("thead", null, /* @__PURE__ */ React18.createElement("tr", null, weekdayNames.map(function(n3, i) {
3451
+ )), /* @__PURE__ */ React18.createElement("table", { role: "grid", className: "aura-cal__grid", "aria-label": monthTitle, lang: th2 ? "th" : void 0 }, /* @__PURE__ */ React18.createElement("thead", null, /* @__PURE__ */ React18.createElement("tr", null, weekdayNames.map(function(n3, i) {
3405
3452
  return /* @__PURE__ */ React18.createElement("th", { key: i, scope: "col", abbr: n3.long }, /* @__PURE__ */ React18.createElement("span", { "aria-hidden": true }, n3.short));
3406
3453
  }))), /* @__PURE__ */ React18.createElement(
3407
3454
  "tbody",
@@ -6199,6 +6246,24 @@ window.Aura = (() => {
6199
6246
  [measure, levelShape]
6200
6247
  );
6201
6248
  const stacked = !!props.stackBelow && boxWidth[0] != null && below(boxWidth[0], props.stackBelow);
6249
+ React34.useEffect(
6250
+ function() {
6251
+ if (!isDev() || props.stackBelow || typeof window === "undefined" || window.innerWidth >= 640 || !wrapRef.current)
6252
+ return;
6253
+ const sc = wrapRef.current.querySelector(".aura-table__scroll");
6254
+ const raf = requestAnimationFrame(function() {
6255
+ if (sc && sc.scrollWidth > sc.clientWidth + 1)
6256
+ devWarnOnce(
6257
+ "datatable-no-stack",
6258
+ 'DataTable "' + props.label + '" scrolls sideways at ' + window.innerWidth + "px wide, so its last columns are off screen. On a phone pass stackBelow={640} to show each row as a card. It becomes the default in 6.0."
6259
+ );
6260
+ });
6261
+ return function() {
6262
+ cancelAnimationFrame(raf);
6263
+ };
6264
+ },
6265
+ [props.stackBelow]
6266
+ );
6202
6267
  const noCardSel = !!props.hideSelectionInCards && selectable;
6203
6268
  useIsoLayoutEffect(
6204
6269
  function() {
@@ -7593,6 +7658,8 @@ window.Aura = (() => {
7593
7658
  };
7594
7659
  const rootCls = cx("aura-tabs", seg && "aura-tabs--segmented", touchClass(props.touchHeight));
7595
7660
  const [focusId, setFocusId] = React36.useState(null);
7661
+ const listRef = React36.useRef(null);
7662
+ useScrollEdges(listRef, asLinks);
7596
7663
  items2.forEach(function(t) {
7597
7664
  const al = t.tabProps && t.tabProps["aria-label"];
7598
7665
  if (al && al.toLowerCase().indexOf(String(t.label).toLowerCase()) !== 0)
@@ -7609,7 +7676,7 @@ window.Aura = (() => {
7609
7676
  "aria-label": props.label,
7610
7677
  className: cx(rootCls, "aura-tabs--links", props.className)
7611
7678
  },
7612
- /* @__PURE__ */ React36.createElement("div", { className: listCls }, items2.map(function(t) {
7679
+ /* @__PURE__ */ React36.createElement("div", { ref: listRef, className: listCls }, items2.map(function(t) {
7613
7680
  const on = t.id === cur2;
7614
7681
  const inner = [
7615
7682
  t.icon ? /* @__PURE__ */ React36.createElement(Icon, { key: "i", name: t.icon }) : null,
@@ -7662,7 +7729,11 @@ window.Aura = (() => {
7662
7729
  const t = enabled[(i + enabled.length) % enabled.length];
7663
7730
  if (manual) setFocusId(t.id);
7664
7731
  else st[1](t.id);
7665
- if (refs.current[t.id]) refs.current[t.id].focus();
7732
+ if (refs.current[t.id]) {
7733
+ refs.current[t.id].focus();
7734
+ const tab = refs.current[t.id];
7735
+ if (typeof tab.scrollIntoView === "function") tab.scrollIntoView({ block: "nearest", inline: "nearest" });
7736
+ }
7666
7737
  }
7667
7738
  const stop2 = manual && focusId && items2.filter(function(t) {
7668
7739
  return t.id === focusId && !t.disabled;
@@ -7670,6 +7741,7 @@ window.Aura = (() => {
7670
7741
  return /* @__PURE__ */ React36.createElement("div", { ref, className: cx(rootCls, props.className) }, /* @__PURE__ */ React36.createElement(
7671
7742
  "div",
7672
7743
  {
7744
+ ref: listRef,
7673
7745
  role: "tablist",
7674
7746
  "aria-label": props.label,
7675
7747
  className: listCls,
@@ -8182,6 +8254,15 @@ window.Aura = (() => {
8182
8254
  var React44 = __toESM(require_react(), 1);
8183
8255
  var h8 = React44.createElement;
8184
8256
  var LAZY = Symbol.for("react.lazy");
8257
+ var ID = /([A-Za-z0-9]+(?:-[A-Za-z0-9]+)+)/;
8258
+ function keepIds(title) {
8259
+ if (typeof title !== "string" || title.indexOf("-") < 0) return title;
8260
+ const parts = title.split(ID);
8261
+ if (parts.length < 2) return title;
8262
+ return parts.map(function(p, i) {
8263
+ return i % 2 && /\d/.test(p) && p.length <= 24 ? h8("span", { key: i, className: "aura-nowrap" }, p) : p;
8264
+ });
8265
+ }
8185
8266
  function chipsOnly(node) {
8186
8267
  let el = 0;
8187
8268
  const walk = function(n3) {
@@ -8233,7 +8314,7 @@ window.Aura = (() => {
8233
8314
  tabIndex: props.focusTitleOnMount ? -1 : void 0,
8234
8315
  "data-aura-focus-on-mount": props.focusTitleOnMount ? "" : void 0
8235
8316
  },
8236
- props.title
8317
+ keepIds(props.title)
8237
8318
  );
8238
8319
  const hasActions = props.actions != null && props.actions !== false;
8239
8320
  const hasBarActions = props.actionsBar != null && props.actionsBar !== false;
@@ -8415,6 +8496,21 @@ window.Aura = (() => {
8415
8496
  ok: "statusOk",
8416
8497
  unknown: "statusUnknown"
8417
8498
  };
8499
+ function breakAtDots(title) {
8500
+ if (typeof title !== "string" || !/[./][A-Za-z]/.test(title)) return title;
8501
+ const out = [];
8502
+ let run = "";
8503
+ for (let i = 0; i < title.length; i++) {
8504
+ const c = title.charAt(i);
8505
+ run += c;
8506
+ if ((c === "." || c === "/") && i + 1 < title.length && /[A-Za-z]/.test(title.charAt(i + 1))) {
8507
+ out.push(run, h9("wbr", { key: i }));
8508
+ run = "";
8509
+ }
8510
+ }
8511
+ if (run) out.push(run);
8512
+ return out;
8513
+ }
8418
8514
  function statusWord(status, t) {
8419
8515
  return String(t[STATUS_WORD[status]] || STATUS_WORD[status]);
8420
8516
  }
@@ -8453,7 +8549,7 @@ window.Aura = (() => {
8453
8549
  "span",
8454
8550
  { className: "aura-status-tile__head" },
8455
8551
  h9(Icon, { name: h9(STATUS_ICON[status]), size: 16, className: "aura-status-tile__icon" }),
8456
- h9("span", { className: "aura-status-tile__title", ref: titleRef }, props.title, comma),
8552
+ h9("span", { className: "aura-status-tile__title", ref: titleRef }, breakAtDots(props.title), comma),
8457
8553
  h9("span", { className: "aura-sr-only" }, word + (hasValue || hasMeta || markers.length ? "," : "")),
8458
8554
  hasValue ? h9(
8459
8555
  "span",
@@ -9284,6 +9380,11 @@ window.Aura = (() => {
9284
9380
  function check() {
9285
9381
  const sideways = el.scrollWidth > el.clientWidth + 1;
9286
9382
  wide[1](sideways);
9383
+ if (sideways && !stackBelow && !outer && typeof window !== "undefined" && window.innerWidth < 640)
9384
+ devWarnOnce(
9385
+ "table-no-stack",
9386
+ "Table" + (rest["aria-label"] ? ' "' + rest["aria-label"] + '"' : "") + " scrolls sideways at " + window.innerWidth + "px wide, so its last columns are off screen. On a phone pass stackBelow={640} (cards or rows). It becomes the default in 6.0."
9387
+ );
9287
9388
  sc[1](sideways || el.scrollHeight > el.clientHeight + 1);
9288
9389
  }
9289
9390
  check();
@@ -9977,6 +10078,19 @@ window.Aura = (() => {
9977
10078
  a = a.trim();
9978
10079
  if (a === "image/*") return t.images;
9979
10080
  if (a === "application/pdf") return "PDF";
10081
+ const known = {
10082
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "DOCX",
10083
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "XLSX",
10084
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "PPTX",
10085
+ "application/msword": "DOC",
10086
+ "application/vnd.ms-excel": "XLS",
10087
+ "application/vnd.ms-powerpoint": "PPT",
10088
+ "text/plain": "TXT"
10089
+ };
10090
+ if (known[a]) return known[a];
10091
+ const slash = a.indexOf("/");
10092
+ if (slash > 0 && a.slice(slash + 1) !== "*")
10093
+ a = a.slice(slash + 1).replace(/^(?:vnd\.|x-)/, "").replace(/\+xml$/, "");
9980
10094
  return a.replace(/^\./, "").toUpperCase();
9981
10095
  }).filter(function(x, i, arr) {
9982
10096
  return arr.indexOf(x) === i;
@@ -10385,6 +10499,7 @@ window.Aura = (() => {
10385
10499
  L["accent-violet"] = L["fg-accent"];
10386
10500
  L["focus-ring"] = fit(b[700], lightGrounds.concat([b[50]]), 3, -1);
10387
10501
  L["bg-selected"] = b[50];
10502
+ L["calendar-range-bg"] = b[100];
10388
10503
  L["status-progress-bg"] = b[100];
10389
10504
  L["status-progress-fg"] = fit(b[800], [b[100]], 4.5, -1);
10390
10505
  L["alert-info-bg"] = b[50];
@@ -10424,6 +10539,7 @@ window.Aura = (() => {
10424
10539
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
10425
10540
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
10426
10541
  D["nav-active-edge"] = D["control-checked-bg"];
10542
+ D["calendar-range-bg"] = D["bg-selected"];
10427
10543
  const shadowLight = INK, shadowDark = D["accent-violet"];
10428
10544
  if (o.primary === "brand") {
10429
10545
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -10481,6 +10597,7 @@ window.Aura = (() => {
10481
10597
  check("dark", "chart-1", "chart-2 (lightness step)", 1.8, D["chart-1"], CHART_2);
10482
10598
  check("light", "fg-primary", "bg-selected", 4.5, INK, L["bg-selected"]);
10483
10599
  check("dark", "fg-primary", "bg-selected", 4.5, "#ffffff", D["bg-selected"]);
10600
+ check("light", "fg-primary", "calendar-range-bg", 4.5, INK, L["calendar-range-bg"]);
10484
10601
  check("light", "status-progress-fg", "status-progress-bg", 4.5, L["status-progress-fg"], L["status-progress-bg"]);
10485
10602
  check("dark", "status-progress-fg", "status-progress-bg", 4.5, D["status-progress-fg"], D["status-progress-bg"]);
10486
10603
  check("light", "alert-info-fg", "alert-info-bg", 4.5, L["alert-info-fg"], L["alert-info-bg"]);
@@ -156,15 +156,16 @@ function tone(t) {
156
156
  return TONES.indexOf(t) >= 0 ? t : "neutral";
157
157
  }
158
158
  var warned = {};
159
- function devWarnOnce(key, message) {
160
- if (warned[key]) return;
161
- let dev = false;
159
+ function isDev() {
162
160
  try {
163
- dev = process.env.NODE_ENV !== "production";
161
+ return process.env.NODE_ENV !== "production";
164
162
  } catch (e) {
165
- dev = false;
163
+ return false;
166
164
  }
167
- if (!dev) return;
165
+ }
166
+ function devWarnOnce(key, message) {
167
+ if (warned[key]) return;
168
+ if (!isDev()) return;
168
169
  warned[key] = true;
169
170
  console.warn("[AURA] " + message);
170
171
  }
@@ -500,6 +501,48 @@ function useInputModality() {
500
501
  function pointerModality() {
501
502
  setModality("pointer");
502
503
  }
504
+ function useScrollEdges(r2, key) {
505
+ React3.useEffect(
506
+ function() {
507
+ const el = r2.current;
508
+ if (!el || typeof ResizeObserver === "undefined") return;
509
+ let raf = 0;
510
+ const mark = function() {
511
+ raf = 0;
512
+ const max = el.scrollWidth - el.clientWidth;
513
+ const x = Math.abs(el.scrollLeft);
514
+ const v = ((x > 1 ? "start " : "") + (x < max - 1 ? "end" : "")).trim();
515
+ if ((el.getAttribute("data-aura-more") || "") === v) return;
516
+ if (v) el.setAttribute("data-aura-more", v);
517
+ else el.removeAttribute("data-aura-more");
518
+ };
519
+ const on = function() {
520
+ if (!raf) raf = requestAnimationFrame(mark);
521
+ };
522
+ mark();
523
+ el.addEventListener("scroll", on, { passive: true });
524
+ const ro = new ResizeObserver(on);
525
+ ro.observe(el);
526
+ Array.prototype.forEach.call(el.children, function(c) {
527
+ ro.observe(c);
528
+ });
529
+ const mo = new MutationObserver(function() {
530
+ Array.prototype.forEach.call(el.children, function(c) {
531
+ ro.observe(c);
532
+ });
533
+ on();
534
+ });
535
+ mo.observe(el, { childList: true });
536
+ return function() {
537
+ cancelAnimationFrame(raf);
538
+ el.removeEventListener("scroll", on);
539
+ ro.disconnect();
540
+ mo.disconnect();
541
+ };
542
+ },
543
+ [key]
544
+ );
545
+ }
503
546
 
504
547
  // src/display.tsx
505
548
  var React4 = __toESM(require("react"), 1);
@@ -1868,7 +1911,8 @@ var Menu = React9.forwardRef(function Menu2(props, ref) {
1868
1911
  top = r2.top - above - 4;
1869
1912
  } else maxHeight = below2;
1870
1913
  }
1871
- const left = Math.max(8, Math.min(r2.right - mw, window.innerWidth - mw - 8));
1914
+ const endAligned = r2.right - mw;
1915
+ const left = Math.max(8, Math.min(endAligned >= 8 ? endAligned : r2.left, window.innerWidth - mw - 8));
1872
1916
  setPos(function(p) {
1873
1917
  return p && p.top === top && p.left === left && p.maxHeight === maxHeight ? p : { top, left, maxHeight };
1874
1918
  });
@@ -1967,12 +2011,15 @@ var Menu = React9.forwardRef(function Menu2(props, ref) {
1967
2011
  });
1968
2012
  return out;
1969
2013
  }
2014
+ const anyLead = items2.some(function(it) {
2015
+ return !it.separator && (!!it.icon || it.type === "radio" || it.checked !== void 0);
2016
+ });
1970
2017
  function renderItem(it, i) {
1971
2018
  if (it.separator) return /* @__PURE__ */ React9.createElement("div", { key: "s" + i, role: "separator", className: "aura-menu__sep" });
1972
2019
  const isRadio = it.type === "radio";
1973
2020
  const isCheck = !isRadio && it.checked !== void 0;
1974
2021
  const checkAtEnd = isCheck && !!it.icon && it.checkPlacement !== "start";
1975
- const lead = isRadio ? /* @__PURE__ */ React9.createElement("span", { className: cx("aura-menu__radio", it.checked && "is-on") }) : isCheck && !checkAtEnd ? /* @__PURE__ */ React9.createElement("span", { className: cx("aura-menu__check", it.checked && "is-on") }, it.checked ? /* @__PURE__ */ React9.createElement(Icon, { name: /* @__PURE__ */ React9.createElement(IconCheck, null), size: 12, strokeWidth: 3 }) : null) : it.icon ? /* @__PURE__ */ React9.createElement(Icon, { name: it.icon }) : /* @__PURE__ */ React9.createElement("span", { className: "aura-menu__blank" });
2022
+ const lead = isRadio ? /* @__PURE__ */ React9.createElement("span", { className: cx("aura-menu__radio", it.checked && "is-on") }) : isCheck && !checkAtEnd ? /* @__PURE__ */ React9.createElement("span", { className: cx("aura-menu__check", it.checked && "is-on") }, it.checked ? /* @__PURE__ */ React9.createElement(Icon, { name: /* @__PURE__ */ React9.createElement(IconCheck, null), size: 12, strokeWidth: 3 }) : null) : it.icon ? /* @__PURE__ */ React9.createElement(Icon, { name: it.icon }) : anyLead ? /* @__PURE__ */ React9.createElement("span", { className: "aura-menu__blank" }) : null;
1976
2023
  const body = [
1977
2024
  /* @__PURE__ */ React9.createElement(React9.Fragment, { key: "l" }, lead),
1978
2025
  /* @__PURE__ */ React9.createElement("span", { key: "t", className: "aura-menu__label" }, it.label),
@@ -3383,7 +3430,7 @@ var Calendar = React18.forwardRef(function Calendar2(props, ref) {
3383
3430
  setFocus(addMonths(focusDate, 1));
3384
3431
  }
3385
3432
  }
3386
- )), /* @__PURE__ */ React18.createElement("table", { role: "grid", className: "aura-cal__grid", "aria-label": monthTitle }, /* @__PURE__ */ React18.createElement("thead", null, /* @__PURE__ */ React18.createElement("tr", null, weekdayNames.map(function(n3, i) {
3433
+ )), /* @__PURE__ */ React18.createElement("table", { role: "grid", className: "aura-cal__grid", "aria-label": monthTitle, lang: th2 ? "th" : void 0 }, /* @__PURE__ */ React18.createElement("thead", null, /* @__PURE__ */ React18.createElement("tr", null, weekdayNames.map(function(n3, i) {
3387
3434
  return /* @__PURE__ */ React18.createElement("th", { key: i, scope: "col", abbr: n3.long }, /* @__PURE__ */ React18.createElement("span", { "aria-hidden": true }, n3.short));
3388
3435
  }))), /* @__PURE__ */ React18.createElement(
3389
3436
  "tbody",
@@ -6181,6 +6228,24 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
6181
6228
  [measure, levelShape]
6182
6229
  );
6183
6230
  const stacked = !!props.stackBelow && boxWidth[0] != null && below(boxWidth[0], props.stackBelow);
6231
+ React34.useEffect(
6232
+ function() {
6233
+ if (!isDev() || props.stackBelow || typeof window === "undefined" || window.innerWidth >= 640 || !wrapRef.current)
6234
+ return;
6235
+ const sc = wrapRef.current.querySelector(".aura-table__scroll");
6236
+ const raf = requestAnimationFrame(function() {
6237
+ if (sc && sc.scrollWidth > sc.clientWidth + 1)
6238
+ devWarnOnce(
6239
+ "datatable-no-stack",
6240
+ 'DataTable "' + props.label + '" scrolls sideways at ' + window.innerWidth + "px wide, so its last columns are off screen. On a phone pass stackBelow={640} to show each row as a card. It becomes the default in 6.0."
6241
+ );
6242
+ });
6243
+ return function() {
6244
+ cancelAnimationFrame(raf);
6245
+ };
6246
+ },
6247
+ [props.stackBelow]
6248
+ );
6184
6249
  const noCardSel = !!props.hideSelectionInCards && selectable;
6185
6250
  useIsoLayoutEffect(
6186
6251
  function() {
@@ -7575,6 +7640,8 @@ var Tabs = React36.forwardRef(function Tabs2(props, ref) {
7575
7640
  };
7576
7641
  const rootCls = cx("aura-tabs", seg && "aura-tabs--segmented", touchClass(props.touchHeight));
7577
7642
  const [focusId, setFocusId] = React36.useState(null);
7643
+ const listRef = React36.useRef(null);
7644
+ useScrollEdges(listRef, asLinks);
7578
7645
  items2.forEach(function(t) {
7579
7646
  const al = t.tabProps && t.tabProps["aria-label"];
7580
7647
  if (al && al.toLowerCase().indexOf(String(t.label).toLowerCase()) !== 0)
@@ -7591,7 +7658,7 @@ var Tabs = React36.forwardRef(function Tabs2(props, ref) {
7591
7658
  "aria-label": props.label,
7592
7659
  className: cx(rootCls, "aura-tabs--links", props.className)
7593
7660
  },
7594
- /* @__PURE__ */ React36.createElement("div", { className: listCls }, items2.map(function(t) {
7661
+ /* @__PURE__ */ React36.createElement("div", { ref: listRef, className: listCls }, items2.map(function(t) {
7595
7662
  const on = t.id === cur2;
7596
7663
  const inner = [
7597
7664
  t.icon ? /* @__PURE__ */ React36.createElement(Icon, { key: "i", name: t.icon }) : null,
@@ -7644,7 +7711,11 @@ var Tabs = React36.forwardRef(function Tabs2(props, ref) {
7644
7711
  const t = enabled[(i + enabled.length) % enabled.length];
7645
7712
  if (manual) setFocusId(t.id);
7646
7713
  else st[1](t.id);
7647
- if (refs.current[t.id]) refs.current[t.id].focus();
7714
+ if (refs.current[t.id]) {
7715
+ refs.current[t.id].focus();
7716
+ const tab = refs.current[t.id];
7717
+ if (typeof tab.scrollIntoView === "function") tab.scrollIntoView({ block: "nearest", inline: "nearest" });
7718
+ }
7648
7719
  }
7649
7720
  const stop2 = manual && focusId && items2.filter(function(t) {
7650
7721
  return t.id === focusId && !t.disabled;
@@ -7652,6 +7723,7 @@ var Tabs = React36.forwardRef(function Tabs2(props, ref) {
7652
7723
  return /* @__PURE__ */ React36.createElement("div", { ref, className: cx(rootCls, props.className) }, /* @__PURE__ */ React36.createElement(
7653
7724
  "div",
7654
7725
  {
7726
+ ref: listRef,
7655
7727
  role: "tablist",
7656
7728
  "aria-label": props.label,
7657
7729
  className: listCls,
@@ -8164,6 +8236,15 @@ var React45 = __toESM(require("react"), 1);
8164
8236
  var React44 = __toESM(require("react"), 1);
8165
8237
  var h8 = React44.createElement;
8166
8238
  var LAZY = Symbol.for("react.lazy");
8239
+ var ID = /([A-Za-z0-9]+(?:-[A-Za-z0-9]+)+)/;
8240
+ function keepIds(title) {
8241
+ if (typeof title !== "string" || title.indexOf("-") < 0) return title;
8242
+ const parts = title.split(ID);
8243
+ if (parts.length < 2) return title;
8244
+ return parts.map(function(p, i) {
8245
+ return i % 2 && /\d/.test(p) && p.length <= 24 ? h8("span", { key: i, className: "aura-nowrap" }, p) : p;
8246
+ });
8247
+ }
8167
8248
  function chipsOnly(node) {
8168
8249
  let el = 0;
8169
8250
  const walk = function(n3) {
@@ -8215,7 +8296,7 @@ var PageHeader = React44.forwardRef(function PageHeader2(props, ref) {
8215
8296
  tabIndex: props.focusTitleOnMount ? -1 : void 0,
8216
8297
  "data-aura-focus-on-mount": props.focusTitleOnMount ? "" : void 0
8217
8298
  },
8218
- props.title
8299
+ keepIds(props.title)
8219
8300
  );
8220
8301
  const hasActions = props.actions != null && props.actions !== false;
8221
8302
  const hasBarActions = props.actionsBar != null && props.actionsBar !== false;
@@ -8397,6 +8478,21 @@ var STATUS_WORD = {
8397
8478
  ok: "statusOk",
8398
8479
  unknown: "statusUnknown"
8399
8480
  };
8481
+ function breakAtDots(title) {
8482
+ if (typeof title !== "string" || !/[./][A-Za-z]/.test(title)) return title;
8483
+ const out = [];
8484
+ let run = "";
8485
+ for (let i = 0; i < title.length; i++) {
8486
+ const c = title.charAt(i);
8487
+ run += c;
8488
+ if ((c === "." || c === "/") && i + 1 < title.length && /[A-Za-z]/.test(title.charAt(i + 1))) {
8489
+ out.push(run, h9("wbr", { key: i }));
8490
+ run = "";
8491
+ }
8492
+ }
8493
+ if (run) out.push(run);
8494
+ return out;
8495
+ }
8400
8496
  function statusWord(status, t) {
8401
8497
  return String(t[STATUS_WORD[status]] || STATUS_WORD[status]);
8402
8498
  }
@@ -8435,7 +8531,7 @@ function statusTileElement(props, ref, Link, t, titleRef) {
8435
8531
  "span",
8436
8532
  { className: "aura-status-tile__head" },
8437
8533
  h9(Icon, { name: h9(STATUS_ICON[status]), size: 16, className: "aura-status-tile__icon" }),
8438
- h9("span", { className: "aura-status-tile__title", ref: titleRef }, props.title, comma),
8534
+ h9("span", { className: "aura-status-tile__title", ref: titleRef }, breakAtDots(props.title), comma),
8439
8535
  h9("span", { className: "aura-sr-only" }, word + (hasValue || hasMeta || markers.length ? "," : "")),
8440
8536
  hasValue ? h9(
8441
8537
  "span",
@@ -9266,6 +9362,11 @@ var Table = React53.forwardRef(function Table2(props, ref) {
9266
9362
  function check() {
9267
9363
  const sideways = el.scrollWidth > el.clientWidth + 1;
9268
9364
  wide[1](sideways);
9365
+ if (sideways && !stackBelow && !outer && typeof window !== "undefined" && window.innerWidth < 640)
9366
+ devWarnOnce(
9367
+ "table-no-stack",
9368
+ "Table" + (rest["aria-label"] ? ' "' + rest["aria-label"] + '"' : "") + " scrolls sideways at " + window.innerWidth + "px wide, so its last columns are off screen. On a phone pass stackBelow={640} (cards or rows). It becomes the default in 6.0."
9369
+ );
9269
9370
  sc[1](sideways || el.scrollHeight > el.clientHeight + 1);
9270
9371
  }
9271
9372
  check();
@@ -9959,6 +10060,19 @@ function describeAccept(accept, t) {
9959
10060
  a = a.trim();
9960
10061
  if (a === "image/*") return t.images;
9961
10062
  if (a === "application/pdf") return "PDF";
10063
+ const known = {
10064
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "DOCX",
10065
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "XLSX",
10066
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "PPTX",
10067
+ "application/msword": "DOC",
10068
+ "application/vnd.ms-excel": "XLS",
10069
+ "application/vnd.ms-powerpoint": "PPT",
10070
+ "text/plain": "TXT"
10071
+ };
10072
+ if (known[a]) return known[a];
10073
+ const slash = a.indexOf("/");
10074
+ if (slash > 0 && a.slice(slash + 1) !== "*")
10075
+ a = a.slice(slash + 1).replace(/^(?:vnd\.|x-)/, "").replace(/\+xml$/, "");
9962
10076
  return a.replace(/^\./, "").toUpperCase();
9963
10077
  }).filter(function(x, i, arr) {
9964
10078
  return arr.indexOf(x) === i;
@@ -10367,6 +10481,7 @@ function createTheme(opts) {
10367
10481
  L["accent-violet"] = L["fg-accent"];
10368
10482
  L["focus-ring"] = fit(b[700], lightGrounds.concat([b[50]]), 3, -1);
10369
10483
  L["bg-selected"] = b[50];
10484
+ L["calendar-range-bg"] = b[100];
10370
10485
  L["status-progress-bg"] = b[100];
10371
10486
  L["status-progress-fg"] = fit(b[800], [b[100]], 4.5, -1);
10372
10487
  L["alert-info-bg"] = b[50];
@@ -10406,6 +10521,7 @@ function createTheme(opts) {
10406
10521
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
10407
10522
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
10408
10523
  D["nav-active-edge"] = D["control-checked-bg"];
10524
+ D["calendar-range-bg"] = D["bg-selected"];
10409
10525
  const shadowLight = INK, shadowDark = D["accent-violet"];
10410
10526
  if (o.primary === "brand") {
10411
10527
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -10463,6 +10579,7 @@ function createTheme(opts) {
10463
10579
  check("dark", "chart-1", "chart-2 (lightness step)", 1.8, D["chart-1"], CHART_2);
10464
10580
  check("light", "fg-primary", "bg-selected", 4.5, INK, L["bg-selected"]);
10465
10581
  check("dark", "fg-primary", "bg-selected", 4.5, "#ffffff", D["bg-selected"]);
10582
+ check("light", "fg-primary", "calendar-range-bg", 4.5, INK, L["calendar-range-bg"]);
10466
10583
  check("light", "status-progress-fg", "status-progress-bg", 4.5, L["status-progress-fg"], L["status-progress-bg"]);
10467
10584
  check("dark", "status-progress-fg", "status-progress-bg", 4.5, D["status-progress-fg"], D["status-progress-bg"]);
10468
10585
  check("light", "alert-info-fg", "alert-info-bg", 4.5, L["alert-info-fg"], L["alert-info-bg"]);
@@ -2,7 +2,15 @@
2
2
  import * as React from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { useStrings, useLinkComponent } from "./locale.js";
5
- import { cx, devWarnOnce, useMaybeControlled, compare, useIsoLayoutEffect, useMergedRef } from "./internal.js";
5
+ import {
6
+ cx,
7
+ devWarnOnce,
8
+ isDev,
9
+ useMaybeControlled,
10
+ compare,
11
+ useIsoLayoutEffect,
12
+ useMergedRef
13
+ } from "./internal.js";
6
14
  import { Icon } from "./Icon.js";
7
15
  import { IconButton } from "./IconButton.js";
8
16
  import { Checkbox } from "./Checkbox.js";
@@ -177,6 +185,24 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
177
185
  [measure, levelShape]
178
186
  );
179
187
  const stacked = !!props.stackBelow && boxWidth[0] != null && below(boxWidth[0], props.stackBelow);
188
+ React.useEffect(
189
+ function() {
190
+ if (!isDev() || props.stackBelow || typeof window === "undefined" || window.innerWidth >= 640 || !wrapRef.current)
191
+ return;
192
+ const sc = wrapRef.current.querySelector(".aura-table__scroll");
193
+ const raf = requestAnimationFrame(function() {
194
+ if (sc && sc.scrollWidth > sc.clientWidth + 1)
195
+ devWarnOnce(
196
+ "datatable-no-stack",
197
+ 'DataTable "' + props.label + '" scrolls sideways at ' + window.innerWidth + "px wide, so its last columns are off screen. On a phone pass stackBelow={640} to show each row as a card. It becomes the default in 6.0."
198
+ );
199
+ });
200
+ return function() {
201
+ cancelAnimationFrame(raf);
202
+ };
203
+ },
204
+ [props.stackBelow]
205
+ );
180
206
  const noCardSel = !!props.hideSelectionInCards && selectable;
181
207
  useIsoLayoutEffect(
182
208
  function() {
@@ -226,7 +226,7 @@ const Calendar = React.forwardRef(function Calendar2(props, ref) {
226
226
  setFocus(addMonths(focusDate, 1));
227
227
  }
228
228
  }
229
- )), /* @__PURE__ */ React.createElement("table", { role: "grid", className: "aura-cal__grid", "aria-label": monthTitle }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, weekdayNames.map(function(n, i) {
229
+ )), /* @__PURE__ */ React.createElement("table", { role: "grid", className: "aura-cal__grid", "aria-label": monthTitle, lang: th ? "th" : void 0 }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, weekdayNames.map(function(n, i) {
230
230
  return /* @__PURE__ */ React.createElement("th", { key: i, scope: "col", abbr: n.long }, /* @__PURE__ */ React.createElement("span", { "aria-hidden": true }, n.short));
231
231
  }))), /* @__PURE__ */ React.createElement(
232
232
  "tbody",