gridsmith-ui 0.9.2 → 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
  {
@@ -8862,7 +9281,7 @@ function TabButton({
8862
9281
  className,
8863
9282
  hasPanel
8864
9283
  }) {
8865
- const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1, disabled: !truncate });
9284
+ const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1, disabled: !truncate && orientation !== "horizontal" });
8866
9285
  const isActive = activeTab === id;
8867
9286
  const tabId = `ds-tab-${id}`;
8868
9287
  const panelId = hasPanel === false ? void 0 : `ds-tabpanel-${id}`;
@@ -8921,6 +9340,8 @@ function TabButton({
8921
9340
  onKeyDown,
8922
9341
  className: cn(
8923
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",
8924
9345
  disabled ? "cursor-not-allowed opacity-disabled" : "cursor-pointer",
8925
9346
  "transition-colors duration-[var(--ds-transition-speed)]",
8926
9347
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
@@ -8937,7 +9358,7 @@ function TabButton({
8937
9358
  countBadge && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: countBadge })
8938
9359
  ] }) : /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-ds-2 min-w-0", children: [
8939
9360
  icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16, className: "shrink-0" }),
8940
- label,
9361
+ /* @__PURE__ */ jsx("span", { ref: truncRef, className: "truncate max-w-[24ch]", children: label }),
8941
9362
  countBadge
8942
9363
  ] })
8943
9364
  }
@@ -9400,8 +9821,10 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
9400
9821
  )
9401
9822
  ] });
9402
9823
  }
9403
- function Pagination({ currentPage, totalPages, onPageChange, pageSize, pageSizeOptions, onPageSizeChange, className }) {
9824
+ function Pagination({ currentPage, totalPages, onPageChange, pageSize: pageSizeProp, pageSizeOptions, onPageSizeChange, className }) {
9404
9825
  const pages = buildPageRange(currentPage, totalPages);
9826
+ const preferences = usePreferences();
9827
+ const pageSize = pageSizeProp ?? preferences.itemsPerPage;
9405
9828
  return /* @__PURE__ */ jsxs("nav", { "aria-label": "Pagination", className: cn("flex flex-wrap items-center gap-ds-3", className), children: [
9406
9829
  pageSizeOptions && pageSize != null && onPageSizeChange && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
9407
9830
  /* @__PURE__ */ jsx("span", { className: "text-sm text-[var(--ds-text-muted)] whitespace-nowrap", children: "Rows per page" }),
@@ -10167,14 +10590,16 @@ function SidebarNav({
10167
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)]"
10168
10591
  ),
10169
10592
  children: [
10170
- 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(
10171
10596
  Icon,
10172
10597
  {
10173
10598
  name: item.icon,
10174
10599
  size: isRoot ? rootIconSize : childIconSize,
10175
10600
  className: cn("shrink-0", item.active && "text-[var(--ds-sidebar-active-accent)]")
10176
10601
  }
10177
- ),
10602
+ ) }),
10178
10603
  !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
10179
10604
  /* @__PURE__ */ jsx(TruncLabel3, { className: isRoot ? "flex-1" : void 0, container: portalContainer ?? void 0, children: item.label }),
10180
10605
  item.badge && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: item.badge })
@@ -10669,6 +11094,15 @@ var FLOATING_BLOCK = cn(
10669
11094
  "overflow-hidden"
10670
11095
  );
10671
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
+ }
10672
11106
  function usePageContainer() {
10673
11107
  return useContext(PageContainerContext);
10674
11108
  }
@@ -10709,6 +11143,7 @@ function floatingBlockPadding(navbarFloating, sidebarFloating, overlap) {
10709
11143
  var PageContainer = forwardRef(function PageContainer2({
10710
11144
  width,
10711
11145
  maxWidth = "xl",
11146
+ contentWidth,
10712
11147
  padding = "default",
10713
11148
  verticalPadding,
10714
11149
  centered = true,
@@ -10729,8 +11164,19 @@ var PageContainer = forwardRef(function PageContainer2({
10729
11164
  }, []);
10730
11165
  const resolvedMaxWidth = width ? widthToMaxWidth[width] : maxWidth;
10731
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;
10732
11172
  const kids = Children.toArray(children);
10733
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
+ }, []);
10734
11180
  const launcherCorner = shell?.launcherCorner ?? null;
10735
11181
  const vPad = verticalPadding ?? "default";
10736
11182
  const launcherPadding = launcherCorner ? { paddingBottom: `max(${bottomPaddingValues[vPad]}, var(--ds-toast-inset-${launcherCorner}, 0px))` } : {};
@@ -10776,21 +11222,22 @@ var PageContainer = forwardRef(function PageContainer2({
10776
11222
  observedRef.current = null;
10777
11223
  }, []);
10778
11224
  if (headerIdx === -1 || !headerEl || !r) {
10779
- 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(
10780
11226
  "div",
10781
11227
  {
10782
- ref,
11228
+ ref: setRoot,
11229
+ "data-page-container": wrappedHeader ? "" : void 0,
10783
11230
  className: cn(
10784
11231
  "w-full",
10785
11232
  paddingClasses[padding],
10786
- verticalPaddingClasses[vPad],
11233
+ wrappedHeader ? bottomPaddingClasses[vPad] : verticalPaddingClasses[vPad],
10787
11234
  centered && "mx-auto",
10788
11235
  className
10789
11236
  ),
10790
11237
  style: { maxWidth: maxWidthValue, containerType: "inline-size", ...launcherPadding },
10791
- children
11238
+ children: capped(children)
10792
11239
  }
10793
- );
11240
+ ) });
10794
11241
  }
10795
11242
  const rest = kids.filter((_, i) => i !== headerIdx);
10796
11243
  const snapping = r.behavior === "snapping";
@@ -10802,15 +11249,15 @@ var PageContainer = forwardRef(function PageContainer2({
10802
11249
  const placement = block ? "block" : r.outside ? "outside" : "inside";
10803
11250
  const breadcrumbsInsideHeader = snapping && floatingHeader && contained;
10804
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;
10805
- const columnClass = (pad2) => cn("w-full", paddingClasses[pad2], centered && "mx-auto");
11252
+ const columnClass = (pad3) => cn("w-full", paddingClasses[pad3], centered && "mx-auto");
10806
11253
  const columnStyle = { maxWidth: maxWidthValue, containerType: "inline-size" };
10807
- 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 });
10808
11255
  const contentPadding = floatingHeader && shell?.isMobile ? "none" : padding;
10809
11256
  const wrapperStyle = floatingHeader || overlapActive ? {
10810
11257
  ...floatingHeader ? { paddingRight: "var(--ds-space-3)", paddingLeft: r.sidebarFloating ? 0 : "var(--ds-space-3)" } : {},
10811
11258
  ...overlapActive ? { marginTop: "calc(-1 * var(--ds-overlap, 0px))", position: "relative", zIndex: 1 } : {}
10812
11259
  } : void 0;
10813
- 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) }) });
10814
11261
  return /* @__PURE__ */ jsxs("div", { ref: setRoot, "data-page-container": "", className: "w-full", children: [
10815
11262
  placement === "block" ? /* @__PURE__ */ jsx("div", { style: floatingBlockPadding(r.navbarFloating, r.sidebarFloating, overlapActive), children: /* @__PURE__ */ jsxs("div", { className: FLOATING_BLOCK, children: [
10816
11263
  strip,
@@ -10969,6 +11416,12 @@ var PageHeader = forwardRef(function PageHeader2({
10969
11416
  }, ref) {
10970
11417
  const shell = useAppShell();
10971
11418
  const container = usePageContainer();
11419
+ const reportHeader = container?.reportHeader;
11420
+ useLayoutEffect(() => {
11421
+ if (!reportHeader) return;
11422
+ reportHeader(true);
11423
+ return () => reportHeader(false);
11424
+ }, [reportHeader]);
10972
11425
  const r = resolvePageHeader(shell?.theme, {
10973
11426
  variant: variantProp,
10974
11427
  behavior: behaviorProp,
@@ -10985,11 +11438,14 @@ var PageHeader = forwardRef(function PageHeader2({
10985
11438
  const bcPill = r.breadcrumbStyle === "pill";
10986
11439
  const bcSep = r.breadcrumbSeparator;
10987
11440
  const contentMaxWidth = contentMaxWidthProp ?? container?.contentMaxWidth;
11441
+ const insidePaddedColumn = !!container?.insidePaddedColumn;
11442
+ const gutterWidth = insidePaddedColumn ? void 0 : contentMaxWidth;
11443
+ const floating = isFloating && !insidePaddedColumn;
10988
11444
  const stickyTop = shell ? "top-0" : "top-[var(--ds-navbar-height)]";
10989
11445
  const snapping = behavior === "snapping";
10990
11446
  const inlineBreadcrumbs = container?.containedBreadcrumbs && !snapping ? void 0 : breadcrumbs;
10991
11447
  const hideExpandedBreadcrumbs = hideExpandedBreadcrumbsProp ?? (!!container?.containedBreadcrumbs && snapping);
10992
- 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);
10993
11449
  const noBottomMargin = noBottomMarginProp ?? container?.noBottomMargin;
10994
11450
  const overlapActive = container?.overlap ?? false;
10995
11451
  if (behavior === "snapping") {
@@ -11007,7 +11463,7 @@ var PageHeader = forwardRef(function PageHeader2({
11007
11463
  expandedContent,
11008
11464
  collapseOffset,
11009
11465
  pinned,
11010
- contentMaxWidth,
11466
+ contentMaxWidth: gutterWidth,
11011
11467
  variant,
11012
11468
  intensity,
11013
11469
  backgroundImage,
@@ -11019,7 +11475,7 @@ var PageHeader = forwardRef(function PageHeader2({
11019
11475
  as: Heading,
11020
11476
  className,
11021
11477
  stickyTop,
11022
- isFloating,
11478
+ isFloating: floating,
11023
11479
  navbarFloating,
11024
11480
  sidebarFloating,
11025
11481
  containedBreadcrumbs,
@@ -11029,7 +11485,7 @@ var PageHeader = forwardRef(function PageHeader2({
11029
11485
  }
11030
11486
  let header;
11031
11487
  if (variant === "minimal") {
11032
- 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 });
11033
11489
  } else if (variant === "tabs-integrated") {
11034
11490
  header = /* @__PURE__ */ jsx(
11035
11491
  TabsIntegratedHeader,
@@ -11046,15 +11502,16 @@ var PageHeader = forwardRef(function PageHeader2({
11046
11502
  breadcrumbSeparator: bcSep,
11047
11503
  as: Heading,
11048
11504
  className,
11049
- headerRef: isFloating ? void 0 : ref,
11050
- isFloating,
11051
- contentMaxWidth
11505
+ headerRef: floating ? void 0 : ref,
11506
+ isFloating: floating,
11507
+ contentMaxWidth: gutterWidth
11052
11508
  }
11053
11509
  );
11054
11510
  } else if (variant === "colourful") {
11055
11511
  header = /* @__PURE__ */ jsx(
11056
11512
  ColourfulHeader,
11057
11513
  {
11514
+ gutter: !insidePaddedColumn,
11058
11515
  title,
11059
11516
  description,
11060
11517
  badge,
@@ -11066,13 +11523,13 @@ var PageHeader = forwardRef(function PageHeader2({
11066
11523
  intensity,
11067
11524
  backgroundImage,
11068
11525
  overlapActive,
11069
- contentMaxWidth,
11526
+ contentMaxWidth: gutterWidth,
11070
11527
  breadcrumbPill: bcPill,
11071
11528
  breadcrumbSeparator: bcSep,
11072
11529
  as: Heading,
11073
11530
  className,
11074
- headerRef: isFloating ? void 0 : ref,
11075
- isFloating,
11531
+ headerRef: floating ? void 0 : ref,
11532
+ isFloating: floating,
11076
11533
  noBottomMargin
11077
11534
  }
11078
11535
  );
@@ -11093,13 +11550,13 @@ var PageHeader = forwardRef(function PageHeader2({
11093
11550
  breadcrumbSeparator: bcSep,
11094
11551
  as: Heading,
11095
11552
  className,
11096
- headerRef: isFloating ? void 0 : ref,
11097
- isFloating,
11098
- contentMaxWidth
11553
+ headerRef: floating ? void 0 : ref,
11554
+ isFloating: floating,
11555
+ contentMaxWidth: gutterWidth
11099
11556
  }
11100
11557
  );
11101
11558
  }
11102
- if (isFloating) {
11559
+ if (floating) {
11103
11560
  return /* @__PURE__ */ jsx(
11104
11561
  "div",
11105
11562
  {
@@ -11231,7 +11688,8 @@ function ColourfulHeader({
11231
11688
  className,
11232
11689
  headerRef,
11233
11690
  isFloating,
11234
- noBottomMargin
11691
+ noBottomMargin,
11692
+ gutter = true
11235
11693
  }) {
11236
11694
  const isBold = intensity === "bold";
11237
11695
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
@@ -11271,11 +11729,12 @@ function ColourfulHeader({
11271
11729
  "div",
11272
11730
  {
11273
11731
  className: cn(
11274
- "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",
11275
11734
  hasBreadcrumbs ? "pt-ds-3" : "pt-ds-6"
11276
11735
  ),
11277
11736
  style: {
11278
- maxWidth: contentMaxWidth ?? "var(--ds-bp-xl)",
11737
+ maxWidth: gutter ? contentMaxWidth ?? "var(--ds-bp-xl)" : void 0,
11279
11738
  containerType: "inline-size",
11280
11739
  ...overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : {}
11281
11740
  },
@@ -11557,7 +12016,7 @@ function SnappingHeader({
11557
12016
  }
11558
12017
  );
11559
12018
  if (effectiveMaxWidth) {
11560
- 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 });
11561
12020
  }
11562
12021
  return innerContent;
11563
12022
  })()
@@ -12197,8 +12656,7 @@ var Navbar = forwardRef(function Navbar2({
12197
12656
  hasBorder && "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
12198
12657
  GLASS_BACKDROP
12199
12658
  );
12200
- const isMobile = appShell?.isMobile;
12201
- 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)`;
12202
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;
12203
12661
  let headerEl;
12204
12662
  if (align === "center") {
@@ -12365,7 +12823,8 @@ function MobileMenuItem({
12365
12823
  }
12366
12824
  var DEFAULT_BUTTON_ATTR = "data-dialog-default-button";
12367
12825
  var sizeClasses = {
12368
- 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",
12369
12828
  md: "max-w-lg",
12370
12829
  lg: "max-w-2xl",
12371
12830
  xl: "max-w-4xl",
@@ -13484,8 +13943,11 @@ function useToast() {
13484
13943
  if (!ctx) throw new Error("useToast must be used within <ToastProvider>");
13485
13944
  return ctx;
13486
13945
  }
13946
+ function useToastOptional() {
13947
+ return useContext(ToastContext);
13948
+ }
13487
13949
  var toastSeq = 0;
13488
- 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 }) {
13489
13951
  const [queue, setQueue] = useState([]);
13490
13952
  const isMobile = useBreakpoint("sm");
13491
13953
  const dismiss = useCallback((id) => {
@@ -13502,11 +13964,24 @@ function ToastProvider({ children, maxVisible = 1, position = "bottom-right", du
13502
13964
  message,
13503
13965
  variant: options.variant ?? "default",
13504
13966
  duration: options.duration ?? duration,
13505
- action: options.action
13967
+ action: options.action,
13968
+ key: options.key ?? options.variant ?? "default"
13506
13969
  };
13507
- 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
+ });
13508
13983
  return id;
13509
- }, [duration]);
13984
+ }, [duration, maxVisible, coalesce]);
13510
13985
  const api = useMemo(() => ({
13511
13986
  toast,
13512
13987
  success: (m, o) => toast(m, { ...o, variant: "success" }),
@@ -13561,6 +14036,75 @@ function ToastProvider({ children, maxVisible = 1, position = "bottom-right", du
13561
14036
  )
13562
14037
  ] });
13563
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
+ }
13564
14108
  var SectionHeading = forwardRef(function SectionHeading2({
13565
14109
  title,
13566
14110
  description,
@@ -13700,6 +14244,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
13700
14244
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
13701
14245
  });
13702
14246
 
13703
- 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 };
13704
14248
  //# sourceMappingURL=index.js.map
13705
14249
  //# sourceMappingURL=index.js.map