gridsmith-ui 0.9.1 → 0.10.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/dist/index.js CHANGED
@@ -1,18 +1,19 @@
1
- import { useAppShell, ControlSizeContext, Button, Spinner, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, useDirtyGuard, DiscardConfirm, defaultMaxWidth } from './chunk-Z37HO5NU.js';
2
- export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell } from './chunk-Z37HO5NU.js';
1
+ import { useAppShell, ControlSizeContext, Button, Spinner, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, useDirtyGuard, DiscardConfirm, defaultMaxWidth } from './chunk-YMUCTZXS.js';
2
+ export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell } from './chunk-YMUCTZXS.js';
3
3
  import { Skeleton } from './chunk-M3AARGLI.js';
4
4
  export { Skeleton } from './chunk-M3AARGLI.js';
5
- import { useOverlayLayer, useCloseOnFocusOut, focusInitial, rovingTabStop, OVERLAY_CLOSE_ATTR, useNonModalDialog, useFocusTrap } from './chunk-RLUK575N.js';
6
- import { Link } from './chunk-ENBESZS2.js';
7
- export { CodeBlock, Divider, Link } from './chunk-ENBESZS2.js';
8
- import { Badge } from './chunk-OVVRMMCO.js';
9
- export { Badge } from './chunk-OVVRMMCO.js';
10
- import { inferColumnKinds, CardValue } from './chunk-FDIFQ5UF.js';
11
- import { SplitPaneCtx, TableCardCtx } from './chunk-RG4KUCRS.js';
5
+ import { useOverlayLayer, useCloseOnFocusOut, focusInitial, rovingTabStop, OVERLAY_CLOSE_ATTR, useNonModalDialog, useFocusTrap } from './chunk-NHQCMOAN.js';
6
+ import { Link } from './chunk-QJBCZ7BR.js';
7
+ export { CodeBlock, Divider, Link } from './chunk-QJBCZ7BR.js';
8
+ import { Badge } from './chunk-U2JT37GJ.js';
9
+ export { Badge } from './chunk-U2JT37GJ.js';
10
+ import { inferColumnKinds, CardValue } from './chunk-CR2KF73R.js';
11
+ import { SplitPaneCtx, usePreferences, TableCardCtx, preferenceActionKey } from './chunk-QNVSMY4R.js';
12
+ export { PreferencesContext, preferenceActionKey, usePreferences } from './chunk-QNVSMY4R.js';
12
13
  import { useBreakpoint } from './chunk-CRQ6KCRD.js';
13
14
  export { BreakpointWidthProvider, useBreakpoint } from './chunk-CRQ6KCRD.js';
14
- import { useTextOverflow, Tooltip, usePosition } from './chunk-CQADS7JZ.js';
15
- export { Tooltip, useTextOverflow } from './chunk-CQADS7JZ.js';
15
+ import { useTextOverflow, Tooltip, usePosition } from './chunk-IYD6ZDM2.js';
16
+ export { Tooltip, useTextOverflow } from './chunk-IYD6ZDM2.js';
16
17
  import { mergeRefs, Slot } from './chunk-PKLVP5ZB.js';
17
18
  import { useLatest } from './chunk-WH2FUFAC.js';
18
19
  import { Icon } from './chunk-X4CNZFWC.js';
@@ -1123,6 +1124,36 @@ function RadioGroup({ value, onChange, options, orientation = "vertical", classN
1123
1124
  )
1124
1125
  ] });
1125
1126
  }
1127
+
1128
+ // ../../showcase/src/lib/segments.ts
1129
+ function stepValue(spec, current, delta) {
1130
+ if (current === null) return delta > 0 ? spec.min : spec.max;
1131
+ const span = spec.max - spec.min + 1;
1132
+ return spec.min + ((current - spec.min + delta) % span + span) % span;
1133
+ }
1134
+ function typeDigit(spec, buffer, digit) {
1135
+ if (!/^\d$/.test(digit)) return { buffer, value: buffer ? Number(buffer) : null, advance: false };
1136
+ let next = buffer + digit;
1137
+ if (next.length > spec.maxDigits || Number(next) > spec.max) next = digit;
1138
+ const n = Number(next);
1139
+ const value = n >= spec.min && n <= spec.max ? n : null;
1140
+ const full = next.length >= spec.maxDigits;
1141
+ const noRoom = n > 0 && n * 10 > spec.max;
1142
+ return { buffer: next, value, advance: full || noRoom };
1143
+ }
1144
+ var pad2 = (n) => String(n).padStart(2, "0");
1145
+ function timeSegments(use24Hour) {
1146
+ return [
1147
+ { key: "hour", label: "Hours", min: use24Hour ? 0 : 1, max: use24Hour ? 23 : 12, maxDigits: 2, placeholder: "--" },
1148
+ { key: "minute", label: "Minutes", min: 0, max: 59, maxDigits: 2, placeholder: "--" }
1149
+ ];
1150
+ }
1151
+ function dateSegments(dayFirst, daysInMonth = 31) {
1152
+ const day = { key: "day", label: "Day", min: 1, max: daysInMonth, maxDigits: 2, placeholder: "dd" };
1153
+ const month = { key: "month", label: "Month", min: 1, max: 12, maxDigits: 2, placeholder: "mm" };
1154
+ const year = { key: "year", label: "Year", min: 1, max: 9999, maxDigits: 4, placeholder: "yyyy" };
1155
+ return dayFirst ? [day, month, year] : [month, day, year];
1156
+ }
1126
1157
  var CONTROL_HEIGHT2 = {
1127
1158
  sm: "h-[var(--ds-control-height-sm)]",
1128
1159
  md: "h-[var(--ds-control-height-md)]",
@@ -1160,6 +1191,44 @@ function isSameDay(a, b) {
1160
1191
  function formatDate(d) {
1161
1192
  return `${MONTHS[d.getMonth()].slice(0, 3)} ${d.getDate()}, ${d.getFullYear()}`;
1162
1193
  }
1194
+ function localeDayFirst() {
1195
+ try {
1196
+ const parts = new Intl.DateTimeFormat(void 0).formatToParts(new Date(2e3, 11, 25));
1197
+ return parts.findIndex((p) => p.type === "day") < parts.findIndex((p) => p.type === "month");
1198
+ } catch {
1199
+ return true;
1200
+ }
1201
+ }
1202
+ function validDate(year, monthIndex, day) {
1203
+ if (monthIndex < 0 || monthIndex > 11 || day < 1) return null;
1204
+ const d = new Date(year, monthIndex, day);
1205
+ return d.getFullYear() === year && d.getMonth() === monthIndex && d.getDate() === day ? d : null;
1206
+ }
1207
+ function parseTypedDate(text, dayFirst = localeDayFirst()) {
1208
+ const t = text.trim().replace(/\s+/g, " ");
1209
+ if (!t) return null;
1210
+ let m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
1211
+ if (m) return validDate(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
1212
+ const monthIndex = (name) => MONTHS.findIndex((mm) => mm.toLowerCase().startsWith(name.toLowerCase().replace(/\.$/, "")));
1213
+ m = /^([A-Za-z]{3,})\.?\s+(\d{1,2}),?\s+(\d{4})$/.exec(t);
1214
+ if (m) {
1215
+ const mi = monthIndex(m[1]);
1216
+ return mi < 0 ? null : validDate(Number(m[3]), mi, Number(m[2]));
1217
+ }
1218
+ m = /^(\d{1,2})\s+([A-Za-z]{3,})\.?,?\s+(\d{4})$/.exec(t);
1219
+ if (m) {
1220
+ const mi = monthIndex(m[2]);
1221
+ return mi < 0 ? null : validDate(Number(m[3]), mi, Number(m[1]));
1222
+ }
1223
+ m = /^(\d{1,2})[/.\-](\d{1,2})[/.\-](\d{2,4})$/.exec(t);
1224
+ if (m) {
1225
+ const a = Number(m[1]), b = Number(m[2]);
1226
+ const year = m[3].length === 2 ? 2e3 + Number(m[3]) : Number(m[3]);
1227
+ const [day, month] = a > 12 ? [a, b] : b > 12 ? [b, a] : dayFirst ? [a, b] : [b, a];
1228
+ return validDate(year, month - 1, day);
1229
+ }
1230
+ return null;
1231
+ }
1163
1232
  function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range }) {
1164
1233
  const contextSize = useContext(ControlSizeContext);
1165
1234
  const size = sizeProp ?? contextSize ?? "md";
@@ -1187,7 +1256,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1187
1256
  const keyHandler = (e) => {
1188
1257
  if (e.key === "Escape" && isTopLayer()) {
1189
1258
  setOpen(false);
1190
- triggerRef.current?.focus();
1259
+ focusSegment(0);
1191
1260
  }
1192
1261
  };
1193
1262
  document.addEventListener("mousedown", handler);
@@ -1207,7 +1276,102 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1207
1276
  }, [open]);
1208
1277
  const closeAndRefocus = () => {
1209
1278
  setOpen(false);
1210
- triggerRef.current?.focus();
1279
+ focusSegment(0);
1280
+ };
1281
+ const dayFirst = useMemo(() => localeDayFirst(), []);
1282
+ const [segs, setSegs] = useState(() => value ? { day: value.getDate(), month: value.getMonth() + 1, year: value.getFullYear() } : { day: null, month: null, year: null });
1283
+ const buffers = useRef({});
1284
+ const lastCommitted = useRef(null);
1285
+ useEffect(() => {
1286
+ if (value && value.getTime() === lastCommitted.current) return;
1287
+ setSegs(value ? { day: value.getDate(), month: value.getMonth() + 1, year: value.getFullYear() } : { day: null, month: null, year: null });
1288
+ buffers.current = {};
1289
+ }, [value]);
1290
+ const specs = dateSegments(dayFirst, segs.month && segs.year ? getDaysInMonth(segs.year, segs.month - 1) : 31);
1291
+ const segmentRefs = useRef([]);
1292
+ const focusSegment = (i) => {
1293
+ segmentRefs.current[Math.max(0, Math.min(specs.length - 1, i))]?.focus();
1294
+ };
1295
+ const [typedInvalid, setTypedInvalid] = useState(false);
1296
+ const commitIfComplete = (next) => {
1297
+ if (next.day === null || next.month === null || next.year === null) {
1298
+ setTypedInvalid(false);
1299
+ return;
1300
+ }
1301
+ const d = validDate(next.year, next.month - 1, next.day);
1302
+ const ok = !!d && (!min || d >= min) && (!max || d <= max);
1303
+ setTypedInvalid(!ok);
1304
+ if (d && ok && !(value && isSameDay(value, d))) {
1305
+ lastCommitted.current = d.getTime();
1306
+ onChange(d);
1307
+ setViewDate(d);
1308
+ }
1309
+ };
1310
+ const setSegment = (key, v, commit = true) => {
1311
+ setSegs((cur) => {
1312
+ const next = { ...cur, [key]: v };
1313
+ if (commit) commitIfComplete(next);
1314
+ return next;
1315
+ });
1316
+ };
1317
+ const applyTyped = (text) => {
1318
+ const d = parseTypedDate(text, dayFirst);
1319
+ if (!d) return;
1320
+ const next = { day: d.getDate(), month: d.getMonth() + 1, year: d.getFullYear() };
1321
+ setSegs(next);
1322
+ buffers.current = {};
1323
+ commitIfComplete(next);
1324
+ };
1325
+ const onSegmentKeyDown = (e, index, spec) => {
1326
+ const key = e.key;
1327
+ if (key === "Tab" || key === "Shift" || key === "Escape" || key.startsWith("F")) return;
1328
+ e.preventDefault();
1329
+ const k = spec.key;
1330
+ if (key === "ArrowLeft") {
1331
+ focusSegment(index - 1);
1332
+ return;
1333
+ }
1334
+ if (key === "ArrowRight") {
1335
+ focusSegment(index + 1);
1336
+ return;
1337
+ }
1338
+ if (key === "ArrowUp" || key === "ArrowDown") {
1339
+ buffers.current[k] = "";
1340
+ setSegment(k, stepValue(spec, segs[k], key === "ArrowUp" ? 1 : -1));
1341
+ return;
1342
+ }
1343
+ if (key === "Home") {
1344
+ buffers.current[k] = "";
1345
+ setSegment(k, spec.min);
1346
+ return;
1347
+ }
1348
+ if (key === "End") {
1349
+ buffers.current[k] = "";
1350
+ setSegment(k, spec.max);
1351
+ return;
1352
+ }
1353
+ if (key === "Backspace" || key === "Delete") {
1354
+ if (segs[k] === null && !buffers.current[k]) {
1355
+ focusSegment(index - 1);
1356
+ return;
1357
+ }
1358
+ buffers.current[k] = "";
1359
+ setSegment(k, null, false);
1360
+ return;
1361
+ }
1362
+ if (/^\d$/.test(key)) {
1363
+ const r = typeDigit(spec, buffers.current[k] ?? "", key);
1364
+ buffers.current[k] = r.advance ? "" : r.buffer;
1365
+ setSegment(k, r.value);
1366
+ if (r.advance) focusSegment(index + 1);
1367
+ }
1368
+ };
1369
+ const shown = (spec) => {
1370
+ const k = spec.key;
1371
+ const b = buffers.current[k];
1372
+ if (b && segs[k] === null) return b;
1373
+ if (segs[k] === null) return spec.placeholder;
1374
+ return k === "year" ? String(segs[k]) : pad2(segs[k]);
1211
1375
  };
1212
1376
  const selectDay = (day) => {
1213
1377
  const d = new Date(year, month, day);
@@ -1227,34 +1391,78 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1227
1391
  return /* @__PURE__ */ jsxs("div", { ref, className: cn(className), children: [
1228
1392
  label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm font-medium text-[var(--ds-text-primary)] mb-ds-1", children: label }),
1229
1393
  /* @__PURE__ */ jsxs(
1230
- "button",
1394
+ "div",
1231
1395
  {
1232
- id: inputId,
1233
1396
  ref: triggerRef,
1234
- type: "button",
1235
- onClick: () => !disabled && setOpen(!open),
1236
- disabled,
1237
- "aria-invalid": error || void 0,
1238
- "aria-haspopup": "dialog",
1239
- "aria-expanded": open,
1397
+ "data-date-field": "",
1398
+ role: "group",
1399
+ id: inputId,
1400
+ "aria-label": label ?? "Date",
1401
+ "aria-description": !value ? placeholder : void 0,
1402
+ "aria-invalid": error || typedInvalid || void 0,
1403
+ onPaste: (e) => {
1404
+ const text = e.clipboardData.getData("text");
1405
+ if (text) {
1406
+ e.preventDefault();
1407
+ applyTyped(text);
1408
+ }
1409
+ },
1240
1410
  className: cn(
1241
1411
  "flex items-center gap-ds-2 w-full",
1242
1412
  CONTROL_HEIGHT2[size],
1243
1413
  TEXT_SIZE2[size],
1244
1414
  "border-[length:var(--ds-border-width)]",
1245
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]",
1415
+ error || typedInvalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]",
1246
1416
  "bg-[var(--ds-input-bg)] rounded-[var(--ds-radius-md)]",
1247
1417
  "shadow-[var(--ds-input-shadow)]",
1248
- "transition-colors cursor-pointer",
1418
+ "transition-colors",
1249
1419
  "px-ds-3 py-ds-2",
1250
- !error && !disabled && !open && "hover:border-[var(--ds-input-border-hover)]",
1420
+ !error && !disabled && !open && "hover:border-[var(--ds-input-border-hover)] focus-within:border-[var(--ds-input-border-hover)]",
1251
1421
  open && ACTIVE_RING,
1252
1422
  disabled && "opacity-disabled cursor-not-allowed",
1253
- value ? "text-[var(--ds-text-primary)]" : "text-[var(--ds-text-muted)]"
1423
+ "text-[var(--ds-text-primary)]"
1254
1424
  ),
1255
1425
  children: [
1256
- /* @__PURE__ */ jsx(Icon, { name: "calendar", size: 14, className: "text-[var(--ds-text-muted)]" }),
1257
- value ? formatDate(value) : placeholder
1426
+ /* @__PURE__ */ jsx(Icon, { name: "calendar", size: 14, className: "text-[var(--ds-text-muted)] shrink-0" }),
1427
+ /* @__PURE__ */ jsx("div", { className: "flex items-center min-w-0 flex-1 tabular-nums", onClick: () => focusSegment(0), children: specs.map((spec, i) => {
1428
+ const k = spec.key;
1429
+ return /* @__PURE__ */ jsxs("span", { className: "contents", children: [
1430
+ i > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "px-[1px] text-[var(--ds-text-muted)]", children: "/" }),
1431
+ /* @__PURE__ */ jsx(
1432
+ "span",
1433
+ {
1434
+ ref: (el) => {
1435
+ segmentRefs.current[i] = el;
1436
+ },
1437
+ role: "spinbutton",
1438
+ tabIndex: disabled ? -1 : 0,
1439
+ "data-segment": spec.key,
1440
+ "aria-label": spec.label,
1441
+ "aria-valuemin": spec.min,
1442
+ "aria-valuemax": spec.max,
1443
+ "aria-valuenow": segs[k] ?? void 0,
1444
+ "aria-valuetext": segs[k] === null ? "empty" : String(segs[k]),
1445
+ onKeyDown: (e) => onSegmentKeyDown(e, i, spec),
1446
+ onClick: (e) => e.stopPropagation(),
1447
+ className: cn("rounded-[var(--ds-radius-sm)] px-[2px] outline-none focus:bg-[var(--ds-accent-subtle)] focus:text-[var(--ds-accent-primary)] cursor-text", segs[k] === null && !buffers.current[k] && "text-[var(--ds-text-muted)]"),
1448
+ children: shown(spec)
1449
+ }
1450
+ )
1451
+ ] }, spec.key);
1452
+ }) }),
1453
+ /* @__PURE__ */ jsx(
1454
+ "button",
1455
+ {
1456
+ type: "button",
1457
+ "aria-label": "Choose a date",
1458
+ "aria-haspopup": "dialog",
1459
+ "aria-expanded": open,
1460
+ onClick: () => !disabled && setOpen(!open),
1461
+ disabled,
1462
+ className: "shrink-0 -mr-ds-1 rounded-[var(--ds-radius-sm)] text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] focus-visible:outline-2 focus-visible:outline-[var(--ds-accent-primary)] disabled:cursor-not-allowed",
1463
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: 14 })
1464
+ }
1465
+ )
1258
1466
  ]
1259
1467
  }
1260
1468
  ),
@@ -1292,8 +1500,8 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1292
1500
  }
1293
1501
  ),
1294
1502
  /* @__PURE__ */ jsxs("div", { style: { padding: "var(--ds-space-2) var(--ds-space-3)" }, children: [
1295
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-ds-0.5 mb-ds-1", children: DAYS.map((d) => /* @__PURE__ */ jsx("div", { className: "text-center text-2xs font-medium text-[var(--ds-text-muted)] py-ds-1", children: d }, d)) }),
1296
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-ds-0.5", onMouseLeave: () => setHover(null), children: cells.map((day, i) => {
1503
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-ds-0.5 mb-ds-1 tabular-nums", children: DAYS.map((d) => /* @__PURE__ */ jsx("div", { className: "text-center text-2xs font-medium text-[var(--ds-text-muted)] py-ds-1", children: d }, d)) }),
1504
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-ds-0.5 tabular-nums", onMouseLeave: () => setHover(null), children: cells.map((day, i) => {
1297
1505
  if (day === null) return /* @__PURE__ */ jsx("div", {}, `empty-${i}`);
1298
1506
  const dateObj = new Date(year, month, day);
1299
1507
  const isSelected = !!value && isSameDay(dateObj, value);
@@ -2105,6 +2313,23 @@ function generateMinutes(step) {
2105
2313
  for (let m = 0; m < 60; m += step) mins.push(m);
2106
2314
  return mins;
2107
2315
  }
2316
+ function parseTypedTime(text, use24Hour) {
2317
+ const t = text.trim().toLowerCase().replace(/\s+/g, " ");
2318
+ if (!t) return null;
2319
+ const m = /^(\d{1,2})(?::?(\d{2}))?\s*(a\.?m\.?|p\.?m\.?|a|p)?$/.exec(t);
2320
+ if (!m) return null;
2321
+ let hour = Number(m[1]);
2322
+ const minute = m[2] === void 0 ? 0 : Number(m[2]);
2323
+ const suffix = m[3]?.replace(/\./g, "");
2324
+ if (Number.isNaN(hour) || minute < 0 || minute > 59) return null;
2325
+ if (suffix) {
2326
+ if (hour < 1 || hour > 12) return null;
2327
+ const pm = suffix.startsWith("p");
2328
+ hour = pm ? hour === 12 ? 12 : hour + 12 : hour === 12 ? 0 : hour;
2329
+ } else if (hour > 23) return null;
2330
+ else if (!use24Hour && hour >= 1 && hour <= 12 && m[2] === void 0) ;
2331
+ return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
2332
+ }
2108
2333
  function parseTime(value) {
2109
2334
  const [h, m] = value.split(":").map(Number);
2110
2335
  const hour = isNaN(h) ? 12 : h;
@@ -2149,7 +2374,7 @@ function TimePicker({
2149
2374
  const keyHandler = (e) => {
2150
2375
  if (e.key === "Escape" && isTopLayer()) {
2151
2376
  setOpen(false);
2152
- triggerRef.current?.focus();
2377
+ focusSegment(0);
2153
2378
  }
2154
2379
  };
2155
2380
  document.addEventListener("mousedown", handler);
@@ -2195,20 +2420,133 @@ function TimePicker({
2195
2420
  }
2196
2421
  onChange(`${pad(h24)}:${pad(minute)}`);
2197
2422
  }
2198
- const display = use24Hour ? value || "00:00" : `${pad(parsed.hour)}:${pad(parsed.minute)} ${parsed.period}`;
2423
+ const specs = timeSegments(use24Hour);
2424
+ const fromValue = (v) => {
2425
+ const m = /^(\d{1,2}):(\d{2})$/.exec(v);
2426
+ if (!m) return { hour: null, minute: null, period: null };
2427
+ const h24 = Number(m[1]);
2428
+ return use24Hour ? { hour: h24, minute: Number(m[2]), period: null } : { hour: h24 % 12 === 0 ? 12 : h24 % 12, minute: Number(m[2]), period: h24 >= 12 ? "PM" : "AM" };
2429
+ };
2430
+ const [segs, setSegs] = useState(() => fromValue(value));
2431
+ const buffers = useRef({});
2432
+ const lastCommitted = useRef(null);
2433
+ useEffect(() => {
2434
+ if (value === lastCommitted.current) return;
2435
+ setSegs(fromValue(value));
2436
+ buffers.current = {};
2437
+ }, [value, use24Hour]);
2438
+ const segmentRefs = useRef([]);
2439
+ const segmentCount = specs.length + (use24Hour ? 0 : 1);
2440
+ const focusSegment = (i) => {
2441
+ const el = segmentRefs.current[Math.max(0, Math.min(segmentCount - 1, i))];
2442
+ el?.focus();
2443
+ };
2444
+ const commitIfComplete = (next) => {
2445
+ if (next.hour === null || next.minute === null) return;
2446
+ let h24 = next.hour;
2447
+ if (!use24Hour) {
2448
+ const period = next.period ?? "AM";
2449
+ h24 = period === "AM" ? next.hour === 12 ? 0 : next.hour : next.hour === 12 ? 12 : next.hour + 12;
2450
+ }
2451
+ const out = `${pad2(h24)}:${pad2(next.minute)}`;
2452
+ lastCommitted.current = out;
2453
+ if (out !== value) onChange(out);
2454
+ };
2455
+ const setSegment = (key, v, commit = true) => {
2456
+ setSegs((s) => {
2457
+ const next = { ...s, [key]: v };
2458
+ if (commit) commitIfComplete(next);
2459
+ return next;
2460
+ });
2461
+ };
2462
+ const applyTyped = (text) => {
2463
+ const parsed2 = parseTypedTime(text, use24Hour);
2464
+ if (!parsed2) return;
2465
+ const next = fromValue(parsed2);
2466
+ setSegs(next);
2467
+ buffers.current = {};
2468
+ commitIfComplete(next);
2469
+ };
2470
+ const onSegmentKeyDown = (e, index, spec) => {
2471
+ const key = e.key;
2472
+ if (key === "Tab" || key === "Shift" || key === "Escape" || key.startsWith("F")) return;
2473
+ e.preventDefault();
2474
+ if (key === "ArrowLeft") {
2475
+ focusSegment(index - 1);
2476
+ return;
2477
+ }
2478
+ if (key === "ArrowRight") {
2479
+ focusSegment(index + 1);
2480
+ return;
2481
+ }
2482
+ if (!spec) {
2483
+ if (key === "ArrowUp" || key === "ArrowDown" || key === " ") setSegment("period", segs.period === "PM" ? "AM" : "PM");
2484
+ else if (/^[aA]$/.test(key)) setSegment("period", "AM");
2485
+ else if (/^[pP]$/.test(key)) setSegment("period", "PM");
2486
+ else if (key === "Backspace" || key === "Delete") {
2487
+ setSegment("period", null, false);
2488
+ }
2489
+ return;
2490
+ }
2491
+ const k = spec.key;
2492
+ if (key === "ArrowUp" || key === "ArrowDown") {
2493
+ buffers.current[k] = "";
2494
+ setSegment(k, stepValue(spec, segs[k], key === "ArrowUp" ? 1 : -1));
2495
+ return;
2496
+ }
2497
+ if (key === "Home") {
2498
+ buffers.current[k] = "";
2499
+ setSegment(k, spec.min);
2500
+ return;
2501
+ }
2502
+ if (key === "End") {
2503
+ buffers.current[k] = "";
2504
+ setSegment(k, spec.max);
2505
+ return;
2506
+ }
2507
+ if (key === "Backspace" || key === "Delete") {
2508
+ if (segs[k] === null && !buffers.current[k]) {
2509
+ focusSegment(index - 1);
2510
+ return;
2511
+ }
2512
+ buffers.current[k] = "";
2513
+ setSegment(k, null, false);
2514
+ return;
2515
+ }
2516
+ if (/^\d$/.test(key)) {
2517
+ const r = typeDigit(spec, buffers.current[k] ?? "", key);
2518
+ buffers.current[k] = r.advance ? "" : r.buffer;
2519
+ setSegment(k, r.value);
2520
+ if (r.advance) focusSegment(index + 1);
2521
+ return;
2522
+ }
2523
+ if (!use24Hour && /^[aApP]$/.test(key)) {
2524
+ setSegment("period", /^[aA]$/.test(key) ? "AM" : "PM");
2525
+ return;
2526
+ }
2527
+ };
2528
+ const shown = (k, spec) => {
2529
+ const b = buffers.current[k];
2530
+ if (b && segs[k] === null) return b;
2531
+ return segs[k] === null ? spec.placeholder : pad2(segs[k]);
2532
+ };
2199
2533
  return /* @__PURE__ */ jsxs("div", { ref, className: cn(className), children: [
2200
2534
  /* @__PURE__ */ jsxs(
2201
- "button",
2535
+ "div",
2202
2536
  {
2203
- id,
2204
2537
  ref: triggerRef,
2205
- type: "button",
2206
- onClick: () => !disabled && setOpen(!open),
2207
- disabled,
2538
+ "data-time-field": "",
2539
+ role: "group",
2540
+ id,
2541
+ "aria-label": "Time",
2208
2542
  "aria-invalid": error || void 0,
2209
- "aria-haspopup": "dialog",
2210
- "aria-expanded": open,
2211
- "aria-controls": open ? listboxId : void 0,
2543
+ onPaste: (e) => {
2544
+ const text = e.clipboardData.getData("text");
2545
+ if (text) {
2546
+ e.preventDefault();
2547
+ applyTyped(text);
2548
+ }
2549
+ },
2212
2550
  className: cn(
2213
2551
  "flex w-full items-center gap-ds-2",
2214
2552
  CONTROL_HEIGHT4[size],
@@ -2217,16 +2555,77 @@ function TimePicker({
2217
2555
  error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]",
2218
2556
  "bg-[var(--ds-input-bg)] rounded-[var(--ds-radius-md)]",
2219
2557
  "shadow-[var(--ds-input-shadow)]",
2220
- "transition-colors cursor-pointer",
2558
+ "transition-colors",
2221
2559
  "px-ds-3 py-ds-2",
2222
- !error && !disabled && !open && "hover:border-[var(--ds-input-border-hover)]",
2560
+ !error && !disabled && !open && "hover:border-[var(--ds-input-border-hover)] focus-within:border-[var(--ds-input-border-hover)]",
2223
2561
  open && ACTIVE_RING,
2224
2562
  disabled && "opacity-disabled cursor-not-allowed",
2225
- value ? "text-[var(--ds-text-primary)]" : "text-[var(--ds-text-muted)]"
2563
+ "text-[var(--ds-text-primary)]"
2226
2564
  ),
2227
2565
  children: [
2228
- /* @__PURE__ */ jsx(Icon, { name: "clock", size: 14, className: "text-[var(--ds-text-muted)]" }),
2229
- display
2566
+ /* @__PURE__ */ jsx(Icon, { name: "clock", size: 14, className: "text-[var(--ds-text-muted)] shrink-0" }),
2567
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center min-w-0 flex-1 tabular-nums", onClick: () => focusSegment(0), children: [
2568
+ specs.map((spec, i) => {
2569
+ const k = spec.key;
2570
+ return /* @__PURE__ */ jsxs("span", { className: "contents", children: [
2571
+ i > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "px-[1px]", children: ":" }),
2572
+ /* @__PURE__ */ jsx(
2573
+ "span",
2574
+ {
2575
+ ref: (el) => {
2576
+ segmentRefs.current[i] = el;
2577
+ },
2578
+ role: "spinbutton",
2579
+ tabIndex: disabled ? -1 : 0,
2580
+ "data-segment": spec.key,
2581
+ "aria-label": spec.label,
2582
+ "aria-valuemin": spec.min,
2583
+ "aria-valuemax": spec.max,
2584
+ "aria-valuenow": segs[k] ?? void 0,
2585
+ "aria-valuetext": segs[k] === null ? "empty" : pad2(segs[k]),
2586
+ onKeyDown: (e) => onSegmentKeyDown(e, i, spec),
2587
+ onClick: (e) => e.stopPropagation(),
2588
+ className: cn("rounded-[var(--ds-radius-sm)] px-[2px] outline-none focus:bg-[var(--ds-accent-subtle)] focus:text-[var(--ds-accent-primary)] cursor-text", segs[k] === null && !buffers.current[k] && "text-[var(--ds-text-muted)]"),
2589
+ children: shown(k, spec)
2590
+ }
2591
+ )
2592
+ ] }, spec.key);
2593
+ }),
2594
+ !use24Hour && /* @__PURE__ */ jsx(
2595
+ "span",
2596
+ {
2597
+ ref: (el) => {
2598
+ segmentRefs.current[specs.length] = el;
2599
+ },
2600
+ role: "spinbutton",
2601
+ tabIndex: disabled ? -1 : 0,
2602
+ "data-segment": "period",
2603
+ "aria-label": "AM or PM",
2604
+ "aria-valuemin": 0,
2605
+ "aria-valuemax": 1,
2606
+ "aria-valuenow": segs.period === null ? void 0 : segs.period === "AM" ? 0 : 1,
2607
+ "aria-valuetext": segs.period ?? "empty",
2608
+ onKeyDown: (e) => onSegmentKeyDown(e, specs.length, null),
2609
+ onClick: (e) => e.stopPropagation(),
2610
+ className: cn("ml-ds-1 rounded-[var(--ds-radius-sm)] px-[2px] outline-none focus:bg-[var(--ds-accent-subtle)] focus:text-[var(--ds-accent-primary)] cursor-text", segs.period === null && "text-[var(--ds-text-muted)]"),
2611
+ children: segs.period ?? "--"
2612
+ }
2613
+ )
2614
+ ] }),
2615
+ /* @__PURE__ */ jsx(
2616
+ "button",
2617
+ {
2618
+ type: "button",
2619
+ "aria-label": "Choose a time",
2620
+ "aria-haspopup": "dialog",
2621
+ "aria-expanded": open,
2622
+ "aria-controls": open ? listboxId : void 0,
2623
+ onClick: () => !disabled && setOpen(!open),
2624
+ disabled,
2625
+ className: "shrink-0 -mr-ds-1 rounded-[var(--ds-radius-sm)] text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] focus-visible:outline-2 focus-visible:outline-[var(--ds-accent-primary)] disabled:cursor-not-allowed",
2626
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: 14 })
2627
+ }
2628
+ )
2230
2629
  ]
2231
2630
  }
2232
2631
  ),
@@ -2247,7 +2646,7 @@ function TimePicker({
2247
2646
  "rounded-[var(--ds-radius-md)]"
2248
2647
  ),
2249
2648
  style: { top: pos.top, left: pos.left },
2250
- children: /* @__PURE__ */ jsxs("div", { className: "inline-flex p-ds-1 gap-ds-1", children: [
2649
+ children: /* @__PURE__ */ jsxs("div", { className: "inline-flex p-ds-1 gap-ds-1 tabular-nums", children: [
2251
2650
  /* @__PURE__ */ jsx("div", { "data-column": true, className: "max-h-48 overflow-y-auto w-ds-10", children: hours.map((h) => /* @__PURE__ */ jsx(
2252
2651
  "button",
2253
2652
  {
@@ -2575,7 +2974,7 @@ var Tag = forwardRef(function Tag2({
2575
2974
  className,
2576
2975
  children
2577
2976
  }, ref) {
2578
- const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1, disabled: !maxWidth });
2977
+ const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1 });
2579
2978
  const handleRemove = (e) => {
2580
2979
  e.stopPropagation();
2581
2980
  onRemove?.();
@@ -2596,7 +2995,7 @@ var Tag = forwardRef(function Tag2({
2596
2995
  }
2597
2996
  } : void 0,
2598
2997
  className: cn(
2599
- "inline-flex items-center font-medium max-w-full whitespace-nowrap",
2998
+ "inline-flex items-center font-medium max-w-full min-w-0 whitespace-nowrap",
2600
2999
  "rounded-[var(--ds-radius-sm)]",
2601
3000
  "transition-colors duration-[var(--ds-transition-speed)]",
2602
3001
  sizeStyles[size],
@@ -2607,7 +3006,7 @@ var Tag = forwardRef(function Tag2({
2607
3006
  ),
2608
3007
  style: maxWidth ? { maxWidth } : void 0,
2609
3008
  children: [
2610
- maxWidth ? /* @__PURE__ */ jsx("span", { ref: truncRef, className: "truncate", children }) : children,
3009
+ /* @__PURE__ */ jsx("span", { ref: truncRef, className: "truncate min-w-0", children }),
2611
3010
  onRemove && !disabled && /* @__PURE__ */ jsx(
2612
3011
  "button",
2613
3012
  {
@@ -3456,7 +3855,10 @@ function collectInvalidFields(form) {
3456
3855
  }
3457
3856
  return out;
3458
3857
  }
3459
- function FormWrapper({ onSubmit, validationMode = "onSubmit", children, actions, actionsAlign = "end", focusFirstError = true, errorSummary = true, className, ref }) {
3858
+ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, actions, actionsAlign: actionsAlignProp, focusFirstError = true, errorSummary = true, className, ref }) {
3859
+ const preferences = usePreferences();
3860
+ const validationMode = validationModeProp ?? preferences.validationTiming ?? "onSubmit";
3861
+ const actionsAlign = actionsAlignProp ?? preferences.formActions?.align ?? "end";
3460
3862
  const [errors, setErrors] = useState({});
3461
3863
  const [submitted, setSubmitted] = useState(false);
3462
3864
  const [submitCount, setSubmitCount] = useState(0);
@@ -4318,11 +4720,15 @@ var Card = forwardRef(function Card2({
4318
4720
  children: /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected })
4319
4721
  }
4320
4722
  ),
4321
- /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardCtx, { value: checkable, children: loading ? /* @__PURE__ */ jsxs("div", { className: "space-y-ds-3", children: [
4322
- /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-4 w-1/3" }),
4323
- /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-full" }),
4324
- /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-2/3" })
4325
- ] }) : children }) })
4723
+ /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardCtx, { value: checkable, children: loading ? (
4724
+ // Round 15 (the owner's 52.1): a placeholder stands where the content will stand — inside the card's
4725
+ // content inset (CardContent's padding), a title-height line then two body lines — never flush against the border.
4726
+ /* @__PURE__ */ jsx("div", { "data-card-loading": "", "aria-busy": "true", className: cn("pr-ds-5 py-ds-4", checkable ? "pl-ds-10" : "pl-ds-5"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-ds-3", children: [
4727
+ /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-5 w-1/3" }),
4728
+ /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-full" }),
4729
+ /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-2/3" })
4730
+ ] }) })
4731
+ ) : children }) })
4326
4732
  ]
4327
4733
  }
4328
4734
  );
@@ -4331,7 +4737,7 @@ var CardHeader = forwardRef(function CardHeader2({ className, children, actions,
4331
4737
  const isCheckable = useContext(CardCtx);
4332
4738
  const hasActions = actions || menuItems;
4333
4739
  const pl = isCheckable ? "pl-ds-10" : "pl-ds-5";
4334
- const actionsBlock = hasActions ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 shrink-0", children: [
4740
+ const actionsBlock = hasActions ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 shrink-0 ml-auto", children: [
4335
4741
  actions,
4336
4742
  menuItems && /* @__PURE__ */ jsx(
4337
4743
  DropdownMenu,
@@ -4355,8 +4761,8 @@ var CardHeader = forwardRef(function CardHeader2({ className, children, actions,
4355
4761
  ] }) : null;
4356
4762
  const iconBlock = icon ? /* @__PURE__ */ jsx("span", { className: "shrink-0 h-10 w-10 rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-muted)] flex items-center justify-center mb-ds-1", children: /* @__PURE__ */ jsx(Icon, { name: icon, size: 20, className: "text-[var(--ds-text-secondary)]" }) }) : null;
4357
4763
  if (hasActions) {
4358
- return /* @__PURE__ */ jsx("div", { ref, className: cn(pl, "pr-ds-5 pt-ds-4 last:pb-ds-4", className), ...props, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-ds-4", children: [
4359
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-ds-1.5 min-w-0", children: [
4764
+ return /* @__PURE__ */ jsx("div", { ref, className: cn(pl, "pr-ds-5 pt-ds-4 last:pb-ds-4", className), ...props, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-ds-4", children: [
4765
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-ds-1.5 grow basis-1/2 [min-width:min-content] break-words", children: [
4360
4766
  iconBlock,
4361
4767
  children
4362
4768
  ] }),
@@ -4767,7 +5173,7 @@ function Table({
4767
5173
  loading = false,
4768
5174
  emptyState,
4769
5175
  onRowClick,
4770
- mobileBreakpoint = "md",
5176
+ mobileBreakpoint: mobileBreakpointProp,
4771
5177
  ref
4772
5178
  }) {
4773
5179
  const [internalSortKey, setInternalSortKey] = useState(null);
@@ -4833,6 +5239,8 @@ function Table({
4833
5239
  (col) => col.kind ?? inferredKinds.get(col.key),
4834
5240
  [inferredKinds]
4835
5241
  );
5242
+ const preferences = usePreferences();
5243
+ const mobileBreakpoint = mobileBreakpointProp ?? preferences.tableToCardsBreakpoint ?? "md";
4836
5244
  const isMobile = useBreakpoint(mobileBreakpoint);
4837
5245
  if (isMobile) {
4838
5246
  if (loading) {
@@ -4915,7 +5323,7 @@ function Table({
4915
5323
  headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left",
4916
5324
  isSortable && "cursor-pointer select-none hover:text-[var(--ds-text-primary)]"
4917
5325
  ),
4918
- style: col.maxWidth ? { maxWidth: col.maxWidth } : void 0,
5326
+ style: col.maxWidth || col.width || col.minWidth ? { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth } : void 0,
4919
5327
  onClick: isSortable ? () => handleSort(col.key) : void 0,
4920
5328
  children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-ds-1", children: [
4921
5329
  col.header,
@@ -5005,6 +5413,10 @@ function Table({
5005
5413
  } else {
5006
5414
  inner = cellContent;
5007
5415
  }
5416
+ const align = col.align ?? (kind === "number" ? "right" : "left");
5417
+ if (align !== "left" && cellContent !== null && cellContent !== void 0 && typeof cellContent !== "string" && typeof cellContent !== "number") {
5418
+ inner = /* @__PURE__ */ jsx("div", { className: cn("flex items-center", align === "center" ? "justify-center" : "justify-end"), children: inner });
5419
+ }
5008
5420
  return /* @__PURE__ */ jsx(
5009
5421
  "td",
5010
5422
  {
@@ -5014,7 +5426,7 @@ function Table({
5014
5426
  alignClass,
5015
5427
  shrink && "w-px whitespace-nowrap"
5016
5428
  ),
5017
- style: col.maxWidth ? { maxWidth: col.maxWidth } : void 0,
5429
+ style: col.maxWidth || col.width || col.minWidth ? { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth } : void 0,
5018
5430
  children: inner
5019
5431
  },
5020
5432
  col.key
@@ -5027,7 +5439,7 @@ function Table({
5027
5439
  "td",
5028
5440
  {
5029
5441
  colSpan: columns.length + (selectable ? 1 : 0) + 1,
5030
- className: "bg-[var(--ds-bg-muted)] px-ds-4 py-ds-3 text-sm text-[var(--ds-text-secondary)]",
5442
+ className: "bg-[var(--ds-bg-muted)] px-ds-6 py-ds-4 text-sm text-[var(--ds-text-primary)]",
5031
5443
  children: renderExpanded(row, i)
5032
5444
  }
5033
5445
  ) })
@@ -6791,8 +7203,10 @@ function renderIcon(icon) {
6791
7203
  if (isValidElement(icon) && icon.type === Icon) return cloneElement(icon, { size: FRAME_ICON_SIZE });
6792
7204
  return icon;
6793
7205
  }
6794
- function EmptyState({ icon, iconFrame = "circle", title, description, action, variant = "empty", bordered = false, className }) {
6795
- const iconNode = icon === void 0 || icon === null ? void 0 : renderIcon(icon);
7206
+ function EmptyState({ icon, iconFrame = "circle", title, description, action, variant = "empty", bordered = false, style: styleProp, className }) {
7207
+ const preferences = usePreferences();
7208
+ const style = styleProp ?? (preferences.emptyStyle === "text-cta" ? "text" : "illustration");
7209
+ const iconNode = style === "text" ? void 0 : icon === void 0 || icon === null ? void 0 : renderIcon(icon);
6796
7210
  return /* @__PURE__ */ jsxs(
6797
7211
  "div",
6798
7212
  {
@@ -6803,8 +7217,9 @@ function EmptyState({ icon, iconFrame = "circle", title, description, action, va
6803
7217
  bordered && "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
6804
7218
  className
6805
7219
  ),
7220
+ "data-style": style,
6806
7221
  children: [
6807
- iconFrame === "none" && iconNode ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-ds-4", children: iconNode }) : /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-[var(--ds-radius-full)] bg-[var(--ds-bg-emphasis)] flex items-center justify-center mb-ds-4", children: iconNode ?? /* @__PURE__ */ jsx("span", { className: "text-2xl text-[var(--ds-text-muted)]", children: defaultIcons[variant] }) }),
7222
+ style === "text" ? null : iconFrame === "none" && iconNode ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-ds-4", children: iconNode }) : /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-[var(--ds-radius-full)] bg-[var(--ds-bg-emphasis)] flex items-center justify-center mb-ds-4", children: iconNode ?? /* @__PURE__ */ jsx("span", { className: "text-2xl text-[var(--ds-text-muted)]", children: defaultIcons[variant] }) }),
6808
7223
  /* @__PURE__ */ jsx("h3", { className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] text-center", children: title }),
6809
7224
  description && /* @__PURE__ */ jsx("p", { className: "mt-ds-1 text-sm text-[var(--ds-text-muted)] max-w-sm text-center", children: description }),
6810
7225
  action && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: action })
@@ -6818,8 +7233,9 @@ function initialsOf(name) {
6818
7233
  function looksLikeUrl(s) {
6819
7234
  return /^(?:https?:|data:|blob:|\/|\.{1,2}\/)/.test(s) || /\.[a-z]{2,5}(?:$|[?#])/i.test(s);
6820
7235
  }
6821
- function EntityCell({ avatar, src, initials, primary, secondary, size = "md", className }) {
7236
+ function EntityCell({ avatar, src, initials, primary, secondary, size: sizeProp, className }) {
6822
7237
  const inTableCard = useContext(TableCardCtx);
7238
+ const size = sizeProp ?? (inTableCard ? "sm" : "md");
6823
7239
  let avatarElement;
6824
7240
  if (typeof avatar === "string") {
6825
7241
  avatarElement = looksLikeUrl(avatar) ? /* @__PURE__ */ jsx(Avatar, { src: avatar, alt: primary, size }) : /* @__PURE__ */ jsx(Avatar, { initials: avatar, alt: primary, size });
@@ -8334,11 +8750,13 @@ var StackedList = forwardRef(function StackedList2({
8334
8750
  const interactive = !!item.onClick;
8335
8751
  const hasEmphasis = item.unread !== void 0;
8336
8752
  const multiline = !!item.description;
8753
+ const stackThis = stackMeta || isPhone && !!item.meta && !!item.actions;
8754
+ const tall = multiline || stackThis && !stackMeta && !!item.leading;
8337
8755
  const trailing = item.unread || item.meta ? /* @__PURE__ */ jsxs("div", { className: cn(
8338
8756
  "shrink-0 flex items-center gap-ds-2 text-sm",
8339
8757
  // Below sm an article's meta sits under the title, left-aligned, whatever alignment its own markup
8340
8758
  // carries (round 6: a builder's items-end column put its badge mid-row).
8341
- stackMeta && "self-start [&>*]:items-start! [&>*]:text-left!",
8759
+ stackThis && "self-start [&>*]:items-start! [&>*]:text-left!",
8342
8760
  isSelected ? "text-[var(--ds-nav-v-active-muted)]" : "text-[var(--ds-text-muted)]"
8343
8761
  ), children: [
8344
8762
  item.unread && /* @__PURE__ */ jsx(
@@ -8356,7 +8774,7 @@ var StackedList = forwardRef(function StackedList2({
8356
8774
  {
8357
8775
  className: cn(
8358
8776
  "flex gap-ds-3 py-ds-3",
8359
- multiline ? "items-start" : "items-center",
8777
+ tall ? "items-start" : "items-center",
8360
8778
  inset,
8361
8779
  interactive && [
8362
8780
  "cursor-pointer [transition:var(--ds-transition-interactive)]",
@@ -8383,7 +8801,7 @@ var StackedList = forwardRef(function StackedList2({
8383
8801
  }
8384
8802
  } : void 0,
8385
8803
  children: [
8386
- item.leading && /* @__PURE__ */ jsx("div", { className: cn("shrink-0", multiline && "mt-ds-0.5"), children: item.leading }),
8804
+ item.leading && /* @__PURE__ */ jsx("div", { className: cn("shrink-0", tall && "mt-ds-0.5"), children: item.leading }),
8387
8805
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
8388
8806
  /* @__PURE__ */ jsxs("div", { className: cn("flex gap-ds-2", stackMeta ? "flex-col" : "items-center justify-between"), children: [
8389
8807
  /* @__PURE__ */ jsx(
@@ -8401,9 +8819,10 @@ var StackedList = forwardRef(function StackedList2({
8401
8819
  (multiline || stackMeta) && trailing
8402
8820
  ] }),
8403
8821
  item.subtitle && /* @__PURE__ */ jsx(TruncText, { className: cn("text-sm", isSelected ? "text-[var(--ds-nav-v-active-muted)]" : "text-[var(--ds-text-secondary)]"), children: item.subtitle }),
8404
- item.description && /* @__PURE__ */ jsx(TruncText, { className: cn("text-xs", isSelected ? "text-[var(--ds-nav-v-active-muted)]" : "text-[var(--ds-text-muted)]"), children: item.description })
8822
+ item.description && /* @__PURE__ */ jsx(TruncText, { className: cn("text-xs", isSelected ? "text-[var(--ds-nav-v-active-muted)]" : "text-[var(--ds-text-muted)]"), children: item.description }),
8823
+ stackThis && !stackMeta && !multiline && trailing
8405
8824
  ] }),
8406
- !multiline && !stackMeta && trailing,
8825
+ !multiline && !stackThis && trailing,
8407
8826
  item.actions && /* @__PURE__ */ jsx(
8408
8827
  "div",
8409
8828
  {
@@ -8457,6 +8876,7 @@ var TONE_SURFACE = {
8457
8876
  )
8458
8877
  };
8459
8878
  var BUTTON_ELEVATION = "shadow-[var(--ds-shadow-button)] active:shadow-[var(--ds-shadow-button-pressed)] active:translate-x-[var(--ds-press-depth)] active:translate-y-[var(--ds-press-depth)]";
8879
+ var BUTTON_ELEVATION_SHADOW_ONLY = "shadow-[var(--ds-shadow-button)] active:shadow-[var(--ds-shadow-button-pressed)]";
8460
8880
  var CORNER_SHAPE = cn(BUTTON_ELEVATION, "hover:-translate-y-[var(--ds-hover-lift)]");
8461
8881
  var LAUNCHER_RADIUS = "calc(var(--ds-radius-base) * 4)";
8462
8882
  var ACCENT_GLYPH = cn(
@@ -8632,13 +9052,13 @@ function Launcher({
8632
9052
  variant === "pill" && cn(CORNER_SHAPE, PILL_SIZE[size]),
8633
9053
  isRibbon && !bottomRibbon && cn(
8634
9054
  "flex-col gap-ds-2 px-ds-2 py-ds-4 text-sm",
8635
- BUTTON_ELEVATION,
9055
+ align === "center" ? BUTTON_ELEVATION_SHADOW_ONLY : BUTTON_ELEVATION,
8636
9056
  side === "right" ? "rounded-l-[var(--ds-radius-md)]" : "rounded-r-[var(--ds-radius-md)]",
8637
9057
  align === "center" && "-translate-y-1/2"
8638
9058
  ),
8639
9059
  bottomRibbon && cn(
8640
9060
  "flex-row gap-ds-2 px-ds-4 py-ds-2 text-sm rounded-t-[var(--ds-radius-md)]",
8641
- BUTTON_ELEVATION,
9061
+ align === "center" ? BUTTON_ELEVATION_SHADOW_ONLY : BUTTON_ELEVATION,
8642
9062
  align === "center" && "-translate-x-1/2"
8643
9063
  ),
8644
9064
  className
@@ -8861,7 +9281,7 @@ function TabButton({
8861
9281
  className,
8862
9282
  hasPanel
8863
9283
  }) {
8864
- const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1, disabled: !truncate });
9284
+ const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1, disabled: !truncate && orientation !== "horizontal" });
8865
9285
  const isActive = activeTab === id;
8866
9286
  const tabId = `ds-tab-${id}`;
8867
9287
  const panelId = hasPanel === false ? void 0 : `ds-tabpanel-${id}`;
@@ -8920,6 +9340,8 @@ function TabButton({
8920
9340
  onKeyDown,
8921
9341
  className: cn(
8922
9342
  "px-ds-4 py-ds-2.5 text-sm font-medium",
9343
+ // never wrap: the strip scrolls when the tabs do not fit (round 17, 54.4)
9344
+ !fullWidth && !truncate && "shrink-0 whitespace-nowrap",
8923
9345
  disabled ? "cursor-not-allowed opacity-disabled" : "cursor-pointer",
8924
9346
  "transition-colors duration-[var(--ds-transition-speed)]",
8925
9347
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
@@ -8936,7 +9358,7 @@ function TabButton({
8936
9358
  countBadge && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: countBadge })
8937
9359
  ] }) : /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-ds-2 min-w-0", children: [
8938
9360
  icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16, className: "shrink-0" }),
8939
- label,
9361
+ /* @__PURE__ */ jsx("span", { ref: truncRef, className: "truncate max-w-[24ch]", children: label }),
8940
9362
  countBadge
8941
9363
  ] })
8942
9364
  }
@@ -9399,8 +9821,10 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
9399
9821
  )
9400
9822
  ] });
9401
9823
  }
9402
- function Pagination({ currentPage, totalPages, onPageChange, pageSize, pageSizeOptions, onPageSizeChange, className }) {
9824
+ function Pagination({ currentPage, totalPages, onPageChange, pageSize: pageSizeProp, pageSizeOptions, onPageSizeChange, className }) {
9403
9825
  const pages = buildPageRange(currentPage, totalPages);
9826
+ const preferences = usePreferences();
9827
+ const pageSize = pageSizeProp ?? preferences.itemsPerPage;
9404
9828
  return /* @__PURE__ */ jsxs("nav", { "aria-label": "Pagination", className: cn("flex flex-wrap items-center gap-ds-3", className), children: [
9405
9829
  pageSizeOptions && pageSize != null && onPageSizeChange && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
9406
9830
  /* @__PURE__ */ jsx("span", { className: "text-sm text-[var(--ds-text-muted)] whitespace-nowrap", children: "Rows per page" }),
@@ -10166,14 +10590,16 @@ function SidebarNav({
10166
10590
  item.active ? isRoot ? "bg-[var(--ds-sidebar-active-bg)] [background-image:var(--ds-sidebar-active-image)] text-[var(--ds-sidebar-active-text)] font-medium border-l-[length:var(--ds-sidebar-active-rail)] border-[var(--ds-sidebar-active-accent)]" : "bg-[var(--ds-sidebar-active-bg)] [background-image:var(--ds-sidebar-active-image)] text-[var(--ds-sidebar-active-accent)] font-medium" : isRoot ? "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)] border-l-[length:var(--ds-sidebar-active-rail)] border-transparent" : "text-[var(--ds-sidebar-text-muted)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
10167
10591
  ),
10168
10592
  children: [
10169
- item.icon && /* @__PURE__ */ jsx(
10593
+ item.icon && // Round 15 (the owner's 52.8): the icon sits in a box one line tall, so a collapsed (icon-only) item is exactly as
10594
+ // tall as the expanded item whose label sets that line — the rail no longer jumps when the sidebar collapses.
10595
+ /* @__PURE__ */ jsx("span", { className: "inline-flex items-center shrink-0 min-h-[1lh]", "data-sidebar-icon": "", children: /* @__PURE__ */ jsx(
10170
10596
  Icon,
10171
10597
  {
10172
10598
  name: item.icon,
10173
10599
  size: isRoot ? rootIconSize : childIconSize,
10174
10600
  className: cn("shrink-0", item.active && "text-[var(--ds-sidebar-active-accent)]")
10175
10601
  }
10176
- ),
10602
+ ) }),
10177
10603
  !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
10178
10604
  /* @__PURE__ */ jsx(TruncLabel3, { className: isRoot ? "flex-1" : void 0, container: portalContainer ?? void 0, children: item.label }),
10179
10605
  item.badge && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: item.badge })
@@ -10668,6 +11094,15 @@ var FLOATING_BLOCK = cn(
10668
11094
  "overflow-hidden"
10669
11095
  );
10670
11096
  var PageContainerContext = createContext(null);
11097
+ function useContentWidth(width) {
11098
+ const ctx = useContext(PageContainerContext);
11099
+ const request = ctx?.requestContentWidth;
11100
+ useEffect(() => {
11101
+ if (!request) return;
11102
+ request(width);
11103
+ return () => request(null);
11104
+ }, [request, width]);
11105
+ }
10671
11106
  function usePageContainer() {
10672
11107
  return useContext(PageContainerContext);
10673
11108
  }
@@ -10708,6 +11143,7 @@ function floatingBlockPadding(navbarFloating, sidebarFloating, overlap) {
10708
11143
  var PageContainer = forwardRef(function PageContainer2({
10709
11144
  width,
10710
11145
  maxWidth = "xl",
11146
+ contentWidth,
10711
11147
  padding = "default",
10712
11148
  verticalPadding,
10713
11149
  centered = true,
@@ -10728,8 +11164,19 @@ var PageContainer = forwardRef(function PageContainer2({
10728
11164
  }, []);
10729
11165
  const resolvedMaxWidth = width ? widthToMaxWidth[width] : maxWidth;
10730
11166
  const maxWidthValue = maxWidthValues[resolvedMaxWidth];
11167
+ const [requestedWidth, setRequestedWidth] = useState(null);
11168
+ const requestContentWidth = useCallback((w) => setRequestedWidth(w), []);
11169
+ const effectiveContentWidth = contentWidth ?? requestedWidth ?? void 0;
11170
+ const contentCap = effectiveContentWidth ? maxWidthValues[widthToMaxWidth[effectiveContentWidth]] : void 0;
11171
+ const capped = (content) => contentCap ? /* @__PURE__ */ jsx("div", { "data-content-width": effectiveContentWidth, style: { maxWidth: contentCap }, children: content }) : content;
10731
11172
  const kids = Children.toArray(children);
10732
11173
  const headerIdx = kids.findIndex(isPageHeaderElement);
11174
+ const headerCount = useRef(0);
11175
+ const [wrappedHeader, setWrappedHeader] = useState(false);
11176
+ const reportHeader = useCallback((present) => {
11177
+ headerCount.current = Math.max(0, headerCount.current + (present ? 1 : -1));
11178
+ setWrappedHeader(headerCount.current > 0);
11179
+ }, []);
10733
11180
  const launcherCorner = shell?.launcherCorner ?? null;
10734
11181
  const vPad = verticalPadding ?? "default";
10735
11182
  const launcherPadding = launcherCorner ? { paddingBottom: `max(${bottomPaddingValues[vPad]}, var(--ds-toast-inset-${launcherCorner}, 0px))` } : {};
@@ -10775,21 +11222,22 @@ var PageContainer = forwardRef(function PageContainer2({
10775
11222
  observedRef.current = null;
10776
11223
  }, []);
10777
11224
  if (headerIdx === -1 || !headerEl || !r) {
10778
- return /* @__PURE__ */ jsx(
11225
+ return /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement: "inside", containedBreadcrumbs: false, breadcrumbsInsideHeader: false, noBottomMargin: false, overlap: false, setPinnedHeight, reportHeader, requestContentWidth, insidePaddedColumn: true }, children: /* @__PURE__ */ jsx(
10779
11226
  "div",
10780
11227
  {
10781
- ref,
11228
+ ref: setRoot,
11229
+ "data-page-container": wrappedHeader ? "" : void 0,
10782
11230
  className: cn(
10783
11231
  "w-full",
10784
11232
  paddingClasses[padding],
10785
- verticalPaddingClasses[vPad],
11233
+ wrappedHeader ? bottomPaddingClasses[vPad] : verticalPaddingClasses[vPad],
10786
11234
  centered && "mx-auto",
10787
11235
  className
10788
11236
  ),
10789
11237
  style: { maxWidth: maxWidthValue, containerType: "inline-size", ...launcherPadding },
10790
- children
11238
+ children: capped(children)
10791
11239
  }
10792
- );
11240
+ ) });
10793
11241
  }
10794
11242
  const rest = kids.filter((_, i) => i !== headerIdx);
10795
11243
  const snapping = r.behavior === "snapping";
@@ -10801,15 +11249,15 @@ var PageContainer = forwardRef(function PageContainer2({
10801
11249
  const placement = block ? "block" : r.outside ? "outside" : "inside";
10802
11250
  const breadcrumbsInsideHeader = snapping && floatingHeader && contained;
10803
11251
  const strip = contained && !breadcrumbsInsideHeader && crumbs ? /* @__PURE__ */ jsx(Breadcrumbs, { variant: "contained", itemVariant: r.breadcrumbStyle, bleed: true, contentMaxWidth: maxWidthValue, separator: r.breadcrumbSeparator, items: headerEl.props.title ? [...crumbs, { label: headerEl.props.title }] : crumbs }) : null;
10804
- const columnClass = (pad2) => cn("w-full", paddingClasses[pad2], centered && "mx-auto");
11252
+ const columnClass = (pad3) => cn("w-full", paddingClasses[pad3], centered && "mx-auto");
10805
11253
  const columnStyle = { maxWidth: maxWidthValue, containerType: "inline-size" };
10806
- const header = /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement, containedBreadcrumbs: contained, breadcrumbsInsideHeader, noBottomMargin: block, overlap: overlapActive, setPinnedHeight }, children: renderHeader ? renderHeader(headerEl) : headerEl });
11254
+ const header = /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement, containedBreadcrumbs: contained, breadcrumbsInsideHeader, noBottomMargin: block, overlap: overlapActive, setPinnedHeight, reportHeader, requestContentWidth }, children: renderHeader ? renderHeader(headerEl) : headerEl });
10807
11255
  const contentPadding = floatingHeader && shell?.isMobile ? "none" : padding;
10808
11256
  const wrapperStyle = floatingHeader || overlapActive ? {
10809
11257
  ...floatingHeader ? { paddingRight: "var(--ds-space-3)", paddingLeft: r.sidebarFloating ? 0 : "var(--ds-space-3)" } : {},
10810
11258
  ...overlapActive ? { marginTop: "calc(-1 * var(--ds-overlap, 0px))", position: "relative", zIndex: 1 } : {}
10811
11259
  } : void 0;
10812
- const column = /* @__PURE__ */ jsx("div", { ref: columnRef, className: cn(columnClass(contentPadding), bottomPaddingClasses[vPad], className), style: { ...columnStyle, ...launcherPadding }, children: rest });
11260
+ const column = /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement: "inside", containedBreadcrumbs: false, breadcrumbsInsideHeader: false, noBottomMargin: false, overlap: false, setPinnedHeight, reportHeader, requestContentWidth, insidePaddedColumn: true }, children: /* @__PURE__ */ jsx("div", { ref: columnRef, className: cn(columnClass(contentPadding), bottomPaddingClasses[vPad], className), style: { ...columnStyle, ...launcherPadding }, children: capped(rest) }) });
10813
11261
  return /* @__PURE__ */ jsxs("div", { ref: setRoot, "data-page-container": "", className: "w-full", children: [
10814
11262
  placement === "block" ? /* @__PURE__ */ jsx("div", { style: floatingBlockPadding(r.navbarFloating, r.sidebarFloating, overlapActive), children: /* @__PURE__ */ jsxs("div", { className: FLOATING_BLOCK, children: [
10815
11263
  strip,
@@ -10968,6 +11416,12 @@ var PageHeader = forwardRef(function PageHeader2({
10968
11416
  }, ref) {
10969
11417
  const shell = useAppShell();
10970
11418
  const container = usePageContainer();
11419
+ const reportHeader = container?.reportHeader;
11420
+ useLayoutEffect(() => {
11421
+ if (!reportHeader) return;
11422
+ reportHeader(true);
11423
+ return () => reportHeader(false);
11424
+ }, [reportHeader]);
10971
11425
  const r = resolvePageHeader(shell?.theme, {
10972
11426
  variant: variantProp,
10973
11427
  behavior: behaviorProp,
@@ -10984,11 +11438,14 @@ var PageHeader = forwardRef(function PageHeader2({
10984
11438
  const bcPill = r.breadcrumbStyle === "pill";
10985
11439
  const bcSep = r.breadcrumbSeparator;
10986
11440
  const contentMaxWidth = contentMaxWidthProp ?? container?.contentMaxWidth;
11441
+ const insidePaddedColumn = !!container?.insidePaddedColumn;
11442
+ const gutterWidth = insidePaddedColumn ? void 0 : contentMaxWidth;
11443
+ const floating = isFloating && !insidePaddedColumn;
10987
11444
  const stickyTop = shell ? "top-0" : "top-[var(--ds-navbar-height)]";
10988
11445
  const snapping = behavior === "snapping";
10989
11446
  const inlineBreadcrumbs = container?.containedBreadcrumbs && !snapping ? void 0 : breadcrumbs;
10990
11447
  const hideExpandedBreadcrumbs = hideExpandedBreadcrumbsProp ?? (!!container?.containedBreadcrumbs && snapping);
10991
- const containedBreadcrumbs = containedBreadcrumbsProp ?? (container?.breadcrumbsInsideHeader && breadcrumbs?.length ? /* @__PURE__ */ jsx(Breadcrumbs, { variant: "contained", itemVariant: r.breadcrumbStyle, bleed: true, contentMaxWidth, separator: bcSep, items: [...breadcrumbs, { label: title }] }) : void 0);
11448
+ const containedBreadcrumbs = containedBreadcrumbsProp ?? (container?.breadcrumbsInsideHeader && breadcrumbs?.length ? /* @__PURE__ */ jsx(Breadcrumbs, { variant: "contained", itemVariant: r.breadcrumbStyle, bleed: true, contentMaxWidth: gutterWidth, separator: bcSep, items: [...breadcrumbs, { label: title }] }) : void 0);
10992
11449
  const noBottomMargin = noBottomMarginProp ?? container?.noBottomMargin;
10993
11450
  const overlapActive = container?.overlap ?? false;
10994
11451
  if (behavior === "snapping") {
@@ -11006,7 +11463,7 @@ var PageHeader = forwardRef(function PageHeader2({
11006
11463
  expandedContent,
11007
11464
  collapseOffset,
11008
11465
  pinned,
11009
- contentMaxWidth,
11466
+ contentMaxWidth: gutterWidth,
11010
11467
  variant,
11011
11468
  intensity,
11012
11469
  backgroundImage,
@@ -11018,7 +11475,7 @@ var PageHeader = forwardRef(function PageHeader2({
11018
11475
  as: Heading,
11019
11476
  className,
11020
11477
  stickyTop,
11021
- isFloating,
11478
+ isFloating: floating,
11022
11479
  navbarFloating,
11023
11480
  sidebarFloating,
11024
11481
  containedBreadcrumbs,
@@ -11028,7 +11485,7 @@ var PageHeader = forwardRef(function PageHeader2({
11028
11485
  }
11029
11486
  let header;
11030
11487
  if (variant === "minimal") {
11031
- header = /* @__PURE__ */ jsx(MinimalHeader, { title, badge, breadcrumbs: inlineBreadcrumbs, actions, breadcrumbPill: bcPill, breadcrumbSeparator: bcSep, as: Heading, className, headerRef: isFloating ? void 0 : ref, isFloating, contentMaxWidth });
11488
+ header = /* @__PURE__ */ jsx(MinimalHeader, { title, badge, breadcrumbs: inlineBreadcrumbs, actions, breadcrumbPill: bcPill, breadcrumbSeparator: bcSep, as: Heading, className, headerRef: floating ? void 0 : ref, isFloating: floating, contentMaxWidth: gutterWidth });
11032
11489
  } else if (variant === "tabs-integrated") {
11033
11490
  header = /* @__PURE__ */ jsx(
11034
11491
  TabsIntegratedHeader,
@@ -11045,15 +11502,16 @@ var PageHeader = forwardRef(function PageHeader2({
11045
11502
  breadcrumbSeparator: bcSep,
11046
11503
  as: Heading,
11047
11504
  className,
11048
- headerRef: isFloating ? void 0 : ref,
11049
- isFloating,
11050
- contentMaxWidth
11505
+ headerRef: floating ? void 0 : ref,
11506
+ isFloating: floating,
11507
+ contentMaxWidth: gutterWidth
11051
11508
  }
11052
11509
  );
11053
11510
  } else if (variant === "colourful") {
11054
11511
  header = /* @__PURE__ */ jsx(
11055
11512
  ColourfulHeader,
11056
11513
  {
11514
+ gutter: !insidePaddedColumn,
11057
11515
  title,
11058
11516
  description,
11059
11517
  badge,
@@ -11065,13 +11523,13 @@ var PageHeader = forwardRef(function PageHeader2({
11065
11523
  intensity,
11066
11524
  backgroundImage,
11067
11525
  overlapActive,
11068
- contentMaxWidth,
11526
+ contentMaxWidth: gutterWidth,
11069
11527
  breadcrumbPill: bcPill,
11070
11528
  breadcrumbSeparator: bcSep,
11071
11529
  as: Heading,
11072
11530
  className,
11073
- headerRef: isFloating ? void 0 : ref,
11074
- isFloating,
11531
+ headerRef: floating ? void 0 : ref,
11532
+ isFloating: floating,
11075
11533
  noBottomMargin
11076
11534
  }
11077
11535
  );
@@ -11092,13 +11550,13 @@ var PageHeader = forwardRef(function PageHeader2({
11092
11550
  breadcrumbSeparator: bcSep,
11093
11551
  as: Heading,
11094
11552
  className,
11095
- headerRef: isFloating ? void 0 : ref,
11096
- isFloating,
11097
- contentMaxWidth
11553
+ headerRef: floating ? void 0 : ref,
11554
+ isFloating: floating,
11555
+ contentMaxWidth: gutterWidth
11098
11556
  }
11099
11557
  );
11100
11558
  }
11101
- if (isFloating) {
11559
+ if (floating) {
11102
11560
  return /* @__PURE__ */ jsx(
11103
11561
  "div",
11104
11562
  {
@@ -11230,7 +11688,8 @@ function ColourfulHeader({
11230
11688
  className,
11231
11689
  headerRef,
11232
11690
  isFloating,
11233
- noBottomMargin
11691
+ noBottomMargin,
11692
+ gutter = true
11234
11693
  }) {
11235
11694
  const isBold = intensity === "bold";
11236
11695
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
@@ -11270,11 +11729,12 @@ function ColourfulHeader({
11270
11729
  "div",
11271
11730
  {
11272
11731
  className: cn(
11273
- "relative z-10 w-full mx-auto px-ds-6 pb-ds-6",
11732
+ "relative z-10 w-full mx-auto pb-ds-6",
11733
+ gutter && "px-ds-6",
11274
11734
  hasBreadcrumbs ? "pt-ds-3" : "pt-ds-6"
11275
11735
  ),
11276
11736
  style: {
11277
- maxWidth: contentMaxWidth ?? "var(--ds-bp-xl)",
11737
+ maxWidth: gutter ? contentMaxWidth ?? "var(--ds-bp-xl)" : void 0,
11278
11738
  containerType: "inline-size",
11279
11739
  ...overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : {}
11280
11740
  },
@@ -11556,7 +12016,7 @@ function SnappingHeader({
11556
12016
  }
11557
12017
  );
11558
12018
  if (effectiveMaxWidth) {
11559
- return /* @__PURE__ */ jsx("div", { className: cn("w-full mx-auto", collapsed ? "px-ds-3 @md:px-ds-6" : "px-ds-6"), style: { maxWidth: effectiveMaxWidth, containerType: "inline-size" }, children: innerContent });
12019
+ return /* @__PURE__ */ jsx("div", { className: "w-full mx-auto px-ds-6", style: { maxWidth: effectiveMaxWidth, containerType: "inline-size" }, children: innerContent });
11560
12020
  }
11561
12021
  return innerContent;
11562
12022
  })()
@@ -12196,8 +12656,7 @@ var Navbar = forwardRef(function Navbar2({
12196
12656
  hasBorder && "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
12197
12657
  GLASS_BACKDROP
12198
12658
  );
12199
- const isMobile = appShell?.isMobile;
12200
- const padStyle = resolvedSize === "sm" ? "var(--ds-space-2) var(--ds-space-4)" : resolvedSize === "lg" ? `var(--ds-space-2) var(${isMobile ? "--ds-space-3" : "--ds-space-6"})` : `var(--ds-space-1) var(${isMobile ? "--ds-space-3" : "--ds-space-6"})`;
12659
+ const padStyle = resolvedSize === "sm" ? "var(--ds-space-2) var(--ds-space-4)" : resolvedSize === "lg" ? `var(--ds-space-2) var(--ds-space-6)` : `var(--ds-space-1) var(--ds-space-6)`;
12201
12660
  const floatingClasses = isFloating ? cn("rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]", GLASS_SURFACE) : void 0;
12202
12661
  let headerEl;
12203
12662
  if (align === "center") {
@@ -12364,7 +12823,8 @@ function MobileMenuItem({
12364
12823
  }
12365
12824
  var DEFAULT_BUTTON_ATTR = "data-dialog-default-button";
12366
12825
  var sizeClasses = {
12367
- sm: "max-w-sm",
12826
+ // round 16 (the owner's item 5): the small dialog is 448 px, not 384 — a delete confirmation with a typed field was cramped
12827
+ sm: "max-w-md",
12368
12828
  md: "max-w-lg",
12369
12829
  lg: "max-w-2xl",
12370
12830
  xl: "max-w-4xl",
@@ -13483,8 +13943,11 @@ function useToast() {
13483
13943
  if (!ctx) throw new Error("useToast must be used within <ToastProvider>");
13484
13944
  return ctx;
13485
13945
  }
13946
+ function useToastOptional() {
13947
+ return useContext(ToastContext);
13948
+ }
13486
13949
  var toastSeq = 0;
13487
- function ToastProvider({ children, maxVisible = 1, position = "bottom-right", duration = 5e3, mode, pauseOnHover = true }) {
13950
+ function ToastProvider({ children, maxVisible = 1, position = "bottom-right", duration = 5e3, mode, pauseOnHover = true, coalesce = true }) {
13488
13951
  const [queue, setQueue] = useState([]);
13489
13952
  const isMobile = useBreakpoint("sm");
13490
13953
  const dismiss = useCallback((id) => {
@@ -13501,11 +13964,24 @@ function ToastProvider({ children, maxVisible = 1, position = "bottom-right", du
13501
13964
  message,
13502
13965
  variant: options.variant ?? "default",
13503
13966
  duration: options.duration ?? duration,
13504
- action: options.action
13967
+ action: options.action,
13968
+ key: options.key ?? options.variant ?? "default"
13505
13969
  };
13506
- setQueue((q) => q.some((t) => t.id === id) ? q.map((t) => t.id === id ? entry : t) : [...q, entry]);
13970
+ const slots = Math.max(1, maxVisible);
13971
+ setQueue((q) => {
13972
+ if (q.some((t) => t.id === id)) return q.map((t) => t.id === id ? entry : t);
13973
+ if (coalesce && q.length >= slots) {
13974
+ const waiting = q.findIndex((t, i) => i >= slots && t.key === entry.key);
13975
+ if (waiting >= 0) {
13976
+ const next = [...q];
13977
+ next[waiting] = entry;
13978
+ return next;
13979
+ }
13980
+ }
13981
+ return [...q, entry];
13982
+ });
13507
13983
  return id;
13508
- }, [duration]);
13984
+ }, [duration, maxVisible, coalesce]);
13509
13985
  const api = useMemo(() => ({
13510
13986
  toast,
13511
13987
  success: (m, o) => toast(m, { ...o, variant: "success" }),
@@ -13560,6 +14036,75 @@ function ToastProvider({ children, maxVisible = 1, position = "bottom-right", du
13560
14036
  )
13561
14037
  ] });
13562
14038
  }
14039
+ function useDestructiveAction(options) {
14040
+ const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, undoMessage, severity } = options;
14041
+ const preferences = usePreferences();
14042
+ const toast = useToastOptional();
14043
+ const [open, setOpen] = useState(false);
14044
+ const [typed, setTyped] = useState("");
14045
+ const [busy, setBusy] = useState(false);
14046
+ const key = action ?? preferenceActionKey(title.replace(/\?$/, ""));
14047
+ const mode = preferences.destructiveMode ?? "dialog";
14048
+ const high = severity === "high" || (preferences.highSeverityActions ?? []).includes(key);
14049
+ const label = confirmLabel ?? title.replace(/\?$/, "").split(/\s+/)[0];
14050
+ const listed = (preferences.typeToConfirmActions ?? []).includes(key) || high;
14051
+ const typedText = typeToConfirm && typeToConfirm.trim() ? typeToConfirm : label.toUpperCase();
14052
+ const typedRequired = listed;
14053
+ const undoable = mode === "undo" && !high && typeof onUndo === "function" && toast !== null;
14054
+ const close = () => {
14055
+ setOpen(false);
14056
+ setTyped("");
14057
+ };
14058
+ const failureMessage = (err) => err instanceof Error && err.message ? err.message : `${title.replace(/\?$/, "")} failed. Try again.`;
14059
+ const confirm = async () => {
14060
+ setBusy(true);
14061
+ try {
14062
+ await onConfirm();
14063
+ close();
14064
+ } catch (err) {
14065
+ toast?.error(failureMessage(err));
14066
+ } finally {
14067
+ setBusy(false);
14068
+ }
14069
+ };
14070
+ const run = () => {
14071
+ if (undoable) {
14072
+ void (async () => {
14073
+ try {
14074
+ await onConfirm();
14075
+ } catch (err) {
14076
+ toast.error(failureMessage(err));
14077
+ return;
14078
+ }
14079
+ const seconds = preferences.undoWindowSeconds ?? 5;
14080
+ toast.success(undoMessage ?? `${title.replace(/\?$/, "")} done`, { duration: seconds * 1e3, action: { label: "Undo", onClick: onUndo } });
14081
+ })();
14082
+ return;
14083
+ }
14084
+ setOpen(true);
14085
+ };
14086
+ const matches = !typedRequired || typed.trim().toLowerCase() === typedText.trim().toLowerCase();
14087
+ const element = open ? /* @__PURE__ */ jsx(
14088
+ Dialog,
14089
+ {
14090
+ open,
14091
+ onClose: close,
14092
+ title,
14093
+ description,
14094
+ variant: "destructive",
14095
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
14096
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: close, disabled: busy, children: "Cancel" }),
14097
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => void confirm(), disabled: !matches || busy, loading: busy, children: label })
14098
+ ] }),
14099
+ children: typedRequired && /* @__PURE__ */ jsx(Field, { label: /* @__PURE__ */ jsxs(Fragment, { children: [
14100
+ "Type ",
14101
+ /* @__PURE__ */ jsx("strong", { children: typedText }),
14102
+ " to confirm"
14103
+ ] }), children: /* @__PURE__ */ jsx(Input, { value: typed, onChange: (e) => setTyped(e.target.value), autoComplete: "off" }) })
14104
+ }
14105
+ ) : null;
14106
+ return { run, element, open };
14107
+ }
13563
14108
  var SectionHeading = forwardRef(function SectionHeading2({
13564
14109
  title,
13565
14110
  description,
@@ -13699,6 +14244,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
13699
14244
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
13700
14245
  });
13701
14246
 
13702
- export { Accordion, ActionPanel, ActionPanelGroup, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Dropdown, DropdownMenu, EmptyState, EntityCell, Field, FileUpload, FormWrapper, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimePicker, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, useFormContext, useSidebar, useToast };
14247
+ export { Accordion, ActionPanel, ActionPanelGroup, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Dropdown, DropdownMenu, EmptyState, EntityCell, Field, FileUpload, FormWrapper, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimePicker, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, useContentWidth, useDestructiveAction, useFormContext, useSidebar, useToast, useToastOptional };
13703
14248
  //# sourceMappingURL=index.js.map
13704
14249
  //# sourceMappingURL=index.js.map