gridsmith-ui 0.21.0 → 0.23.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.
Files changed (48) hide show
  1. package/dist/chat.js +3 -3
  2. package/dist/chunk-3BRQRKDO.js +72 -0
  3. package/dist/chunk-3BRQRKDO.js.map +1 -0
  4. package/dist/{chunk-GGVASDXT.js → chunk-6PL7FXYH.js} +284 -521
  5. package/dist/chunk-6PL7FXYH.js.map +1 -0
  6. package/dist/chunk-7TOJ4G7S.js +197 -0
  7. package/dist/chunk-7TOJ4G7S.js.map +1 -0
  8. package/dist/chunk-CKWITUSH.js +40 -0
  9. package/dist/chunk-CKWITUSH.js.map +1 -0
  10. package/dist/{chunk-ILIODA7W.js → chunk-CXMV7SXJ.js} +4 -3
  11. package/dist/chunk-CXMV7SXJ.js.map +1 -0
  12. package/dist/{chunk-EQDR57AM.js → chunk-EYMGLK4M.js} +3 -3
  13. package/dist/{chunk-EQDR57AM.js.map → chunk-EYMGLK4M.js.map} +1 -1
  14. package/dist/chunk-ICNWPNMM.js +1363 -0
  15. package/dist/chunk-ICNWPNMM.js.map +1 -0
  16. package/dist/chunk-IVCVNAXG.js +115 -0
  17. package/dist/chunk-IVCVNAXG.js.map +1 -0
  18. package/dist/{chunk-XYDIBW3J.js → chunk-SBTBMAIU.js} +4 -3
  19. package/dist/chunk-SBTBMAIU.js.map +1 -0
  20. package/dist/data-grid.d.ts +38 -4
  21. package/dist/data-grid.js +305 -264
  22. package/dist/data-grid.js.map +1 -1
  23. package/dist/editor.js +10 -3
  24. package/dist/editor.js.map +1 -1
  25. package/dist/index.d.ts +122 -146
  26. package/dist/index.js +1091 -1885
  27. package/dist/index.js.map +1 -1
  28. package/dist/kanban.js +4 -2
  29. package/dist/kanban.js.map +1 -1
  30. package/dist/resizable.d.ts +1 -1
  31. package/dist/resizable.js +26 -14
  32. package/dist/resizable.js.map +1 -1
  33. package/dist/roots.css +24 -4
  34. package/dist/{table-utils-GounRE6s.d.ts → table-utils-BkjNEWts.d.ts} +5 -1
  35. package/dist/tokens.css +20 -3
  36. package/dist/use-pagination-DiJo0aW5.d.ts +153 -0
  37. package/dist/virtualized.d.ts +11 -1
  38. package/dist/virtualized.js +69 -18
  39. package/dist/virtualized.js.map +1 -1
  40. package/dist-static/tokens.css +20 -3
  41. package/package.json +1 -1
  42. package/dist/chunk-GGVASDXT.js.map +0 -1
  43. package/dist/chunk-ILIODA7W.js.map +0 -1
  44. package/dist/chunk-JN37OVTP.js +0 -67
  45. package/dist/chunk-JN37OVTP.js.map +0 -1
  46. package/dist/chunk-TKVTZWBN.js +0 -50
  47. package/dist/chunk-TKVTZWBN.js.map +0 -1
  48. package/dist/chunk-XYDIBW3J.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,25 +1,29 @@
1
- import { Button, useAppShell, REACT_VERSION_PROBLEM, ControlSizeContext, triggerType, useOverlayMark, useDirtyGuard, OverlayMarkScope, OverlayCloseCtx, DiscardConfirm, OVERLAY_ROOT_ATTR, Spinner, OverlayMarkCtx, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-GGVASDXT.js';
2
- export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-GGVASDXT.js';
1
+ import { useAppShell, REACT_VERSION_PROBLEM, useOverlayMark, useDirtyGuard, useScrollbarInset, DrawerFooterCtx, OverlayMarkScope, OverlayCloseCtx, insetLessScrollbar, DiscardConfirm, OVERLAY_ROOT_ATTR, OverlayMarkCtx, useCompact, useElementWidth, DrawerPanel, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-6PL7FXYH.js';
2
+ export { AppShell, Drawer, SkipLink, useAppShell, useUnsavedChanges } from './chunk-6PL7FXYH.js';
3
3
  import { Skeleton, TITLE_TEXT } from './chunk-NCIZQYXQ.js';
4
4
  export { Skeleton } from './chunk-NCIZQYXQ.js';
5
- import { FOCUSABLE, usePopupFocus, FIELD, ZOOM_KEYS, FormChangeCtx, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, useReportedChange, focusAfterRemoval, useOptionalFormContext, rovingTabStop, useGridKeyboard, FormCtx, useNonModalDialog, usePressMarks, useReturnFocus, useCloseOnFocusOut } from './chunk-RT7RYGC3.js';
6
- export { useFormContext, useGridKeyboard } from './chunk-RT7RYGC3.js';
7
- import { Link } from './chunk-EQDR57AM.js';
8
- export { CodeBlock, Divider, Link } from './chunk-EQDR57AM.js';
9
- import { Badge } from './chunk-XYDIBW3J.js';
10
- export { Badge } from './chunk-XYDIBW3J.js';
11
- import { inferColumnKinds, CardValue } from './chunk-JN37OVTP.js';
12
- import { SplitPaneCtx, useShellPreferences, TableCardCtx, PREFERENCE_HOUSE_DEFAULTS, preferenceActionKey } from './chunk-TKVTZWBN.js';
13
- export { PreferencesContext, preferenceActionKey, usePreferences } from './chunk-TKVTZWBN.js';
5
+ import { useScrollEdges, EDGE_FADE } from './chunk-3BRQRKDO.js';
6
+ import { Link } from './chunk-EYMGLK4M.js';
7
+ export { CodeBlock, Divider, Link } from './chunk-EYMGLK4M.js';
8
+ import { Badge } from './chunk-SBTBMAIU.js';
9
+ export { Badge } from './chunk-SBTBMAIU.js';
10
+ import { inferColumnKinds, useCardsSwitch, CARDS_BELOW, kindOfRenderedText, CardValue } from './chunk-IVCVNAXG.js';
11
+ import { useWholeListCheck, pagerPart, Pagination, CHECK_FILL, Dropdown, FilterRowContext, SWITCH_KNOB_ON, SWITCH_KNOB_OFF, SWITCH_KNOB_MOTION, SWITCH_TRACK_ON, SWITCH_TRACK_OFF, useFieldControl, useField, warnOnce, Label, InlineFieldError, AddAnotherRowCtx, Input, Field } from './chunk-ICNWPNMM.js';
12
+ export { Dropdown, Field, InlineFieldError, Input, Label, NumberInput, Pagination, Popover, Textarea, Toggle, usePagination } from './chunk-ICNWPNMM.js';
13
+ import { Button, ControlSizeContext, triggerType, Spinner, useShellPreferences, preferenceActionKey } from './chunk-7TOJ4G7S.js';
14
+ export { Button, ButtonSizeContext, ControlSizeContext, PreferencesContext, Spinner, preferenceActionKey, usePreferences } from './chunk-7TOJ4G7S.js';
14
15
  import { useBreakpoint, readBreakpointPx } from './chunk-CRQ6KCRD.js';
15
16
  export { BreakpointWidthProvider, useBreakpoint } from './chunk-CRQ6KCRD.js';
16
- import { useTextOverflow, Tooltip, usePosition } from './chunk-ILIODA7W.js';
17
- export { Tooltip, useTextOverflow } from './chunk-ILIODA7W.js';
17
+ import { FOCUSABLE, usePopupFocus, FIELD, ZOOM_KEYS, FormChangeCtx, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, useReportedChange, rovingTabStop, useGridKeyboard, useOptionalFormContext, focusAfterRemoval, FormCtx, useNonModalDialog, useReturnFocus, useCloseOnFocusOut } from './chunk-RT7RYGC3.js';
18
+ export { useFormContext, useGridKeyboard } from './chunk-RT7RYGC3.js';
19
+ import { CardSurfaceCtx, useScrollsInside, SplitPaneCtx, SurfaceBoundary, useFramesItself, TableCardCtx } from './chunk-CKWITUSH.js';
20
+ import { useTextOverflow, Tooltip, usePosition } from './chunk-CXMV7SXJ.js';
21
+ export { Tooltip, useTextOverflow } from './chunk-CXMV7SXJ.js';
18
22
  import { mergeRefs, Slot } from './chunk-7Q46JXSK.js';
19
23
  import { useLatest } from './chunk-WH2FUFAC.js';
20
24
  import { Icon } from './chunk-4LDYR7QP.js';
21
- import { cn, GLASS_SURFACE, GLASS_BACKDROP, GLASS_DROPDOWN, lockBodyScroll, unlockBodyScroll, OVERLAY_SURFACE, GLASS_OVERLAY, ERROR_ACTIVE_RING, ACTIVE_RING, ERROR_FOCUS_RING, FOCUS_RING, ERROR_HAS_FOCUS_RING, HAS_FOCUS_RING, ERROR_FOCUS_WITHIN_RING, FOCUS_WITHIN_RING } from './chunk-M2QRNBDQ.js';
22
- import { createContext, forwardRef, useState, useRef, useCallback, useLayoutEffect, useMemo, useContext, Children, isValidElement, useId, memo, useEffect, useImperativeHandle, Fragment as Fragment$1, cloneElement, useSyncExternalStore } from 'react';
25
+ import { cn, GLASS_SURFACE, GLASS_BACKDROP, GLASS_DROPDOWN, lockBodyScroll, unlockBodyScroll, OVERLAY_SURFACE, GLASS_OVERLAY, ERROR_HAS_FOCUS_RING, HAS_FOCUS_RING, ACTIVE_RING, ERROR_ACTIVE_RING, ERROR_FOCUS_RING, FOCUS_RING, FOCUS_WITHIN_RING } from './chunk-M2QRNBDQ.js';
26
+ import { createContext, forwardRef, useState, useRef, useCallback, useLayoutEffect, useMemo, useContext, isValidElement, useId, memo, useEffect, useImperativeHandle, Children, Fragment as Fragment$1, cloneElement, useSyncExternalStore } from 'react';
23
27
  import { createPortal, flushSync } from 'react-dom';
24
28
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
25
29
 
@@ -83,6 +87,8 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
83
87
  Slot,
84
88
  {
85
89
  ...triggerType(trigger),
90
+ "aria-haspopup": "menu",
91
+ "aria-expanded": open,
86
92
  ref: triggerRef,
87
93
  onClick: () => {
88
94
  setOpen(!open);
@@ -208,733 +214,6 @@ function BulkActionBar({ selectedCount, actions, onClearSelection, className })
208
214
  }
209
215
  );
210
216
  }
211
- var UNCONTROLLED = /* @__PURE__ */ Symbol("uncontrolled");
212
- var valueOf = (e) => {
213
- const t = e.target;
214
- if (!t) return void 0;
215
- return t instanceof HTMLInputElement && (t.type === "checkbox" || t.type === "radio") ? t.checked : t.value;
216
- };
217
- var INPUT_HEIGHT = {
218
- sm: "h-[var(--ds-control-height-sm)]",
219
- md: "h-[var(--ds-control-height-md)]",
220
- lg: "h-[var(--ds-control-height-lg)]"
221
- };
222
- var INPUT_TEXT = {
223
- sm: "px-ds-2 py-ds-1 text-xs",
224
- md: "px-ds-3 py-ds-2 text-sm",
225
- lg: "px-ds-4 py-ds-3 text-base"
226
- };
227
- function Input({ className, size: sizeProp, error = false, prefix, suffix, ...props }) {
228
- const contextSize = useContext(ControlSizeContext);
229
- const size = sizeProp ?? contextSize ?? "md";
230
- const onChange = useReportedChange(props.onChange, props.value === void 0 ? UNCONTROLLED : props.value, valueOf);
231
- const borderColor = error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]";
232
- const hoverBorder = !error && !props.disabled && !props.readOnly ? "hover:border-[var(--ds-input-border-hover)]" : "";
233
- if (prefix || suffix) {
234
- return /* @__PURE__ */ jsxs(
235
- "div",
236
- {
237
- className: cn(
238
- "flex items-center w-full",
239
- INPUT_HEIGHT[size],
240
- "bg-[var(--ds-input-bg)]",
241
- "border-[length:var(--ds-border-width)]",
242
- borderColor,
243
- "rounded-[var(--ds-radius-md)]",
244
- "shadow-[var(--ds-input-shadow)]",
245
- hoverBorder,
246
- error ? ERROR_HAS_FOCUS_RING : HAS_FOCUS_RING,
247
- "transition-colors duration-[var(--ds-transition-speed)]",
248
- props.disabled && "cursor-not-allowed opacity-disabled",
249
- props.readOnly && "bg-[var(--ds-bg-muted)] cursor-default",
250
- className
251
- ),
252
- children: [
253
- prefix && /* @__PURE__ */ jsx("span", { className: "shrink-0 pl-ds-3 text-[var(--ds-text-muted)] flex items-center", children: prefix }),
254
- /* @__PURE__ */ jsx(
255
- "input",
256
- {
257
- "aria-invalid": error || void 0,
258
- className: cn(
259
- "flex-1 min-w-0 bg-transparent outline-none",
260
- INPUT_TEXT[size],
261
- "text-[var(--ds-text-primary)]",
262
- "placeholder:text-[var(--ds-text-muted)]",
263
- "disabled:cursor-not-allowed",
264
- "read-only:text-[var(--ds-text-secondary)] read-only:cursor-default"
265
- ),
266
- ...props,
267
- onChange: props.onChange || props.value === void 0 ? onChange : void 0
268
- }
269
- ),
270
- suffix && /* @__PURE__ */ jsx("span", { className: "shrink-0 pr-ds-3 text-[var(--ds-text-muted)] flex items-center", children: suffix })
271
- ]
272
- }
273
- );
274
- }
275
- return /* @__PURE__ */ jsx(
276
- "input",
277
- {
278
- "aria-invalid": error || void 0,
279
- className: cn(
280
- "flex w-full",
281
- INPUT_TEXT[size],
282
- INPUT_HEIGHT[size],
283
- "bg-[var(--ds-input-bg)] text-[var(--ds-text-primary)]",
284
- "border-[length:var(--ds-border-width)]",
285
- borderColor,
286
- "rounded-[var(--ds-radius-md)]",
287
- "shadow-[var(--ds-input-shadow)]",
288
- "placeholder:text-[var(--ds-text-muted)]",
289
- hoverBorder,
290
- error ? ERROR_FOCUS_RING : FOCUS_RING,
291
- "disabled:cursor-not-allowed disabled:opacity-disabled",
292
- "read-only:bg-[var(--ds-bg-muted)] read-only:text-[var(--ds-text-secondary)] read-only:cursor-default",
293
- "transition-colors duration-[var(--ds-transition-speed)]",
294
- className
295
- ),
296
- ...props,
297
- onChange: props.onChange || props.value === void 0 ? onChange : void 0
298
- }
299
- );
300
- }
301
- function Textarea({ className, autoResize, maxRows, error = false, ref, ...props }) {
302
- const onChange = useReportedChange(props.onChange, props.value === void 0 ? UNCONTROLLED : props.value, valueOf);
303
- const internalRef = useRef(null);
304
- const textareaRef = ref ?? internalRef;
305
- const resize = useCallback(() => {
306
- const el = textareaRef.current;
307
- if (!el || !autoResize) return;
308
- el.style.height = "auto";
309
- let target = el.scrollHeight;
310
- if (maxRows) {
311
- const lineHeight = parseFloat(getComputedStyle(el).lineHeight) || 20;
312
- const maxHeight = lineHeight * maxRows + parseFloat(getComputedStyle(el).paddingTop) + parseFloat(getComputedStyle(el).paddingBottom);
313
- target = Math.min(target, maxHeight);
314
- }
315
- el.style.height = `${target}px`;
316
- }, [autoResize, maxRows, textareaRef]);
317
- useEffect(() => {
318
- resize();
319
- }, [resize, props.value, props.defaultValue]);
320
- return /* @__PURE__ */ jsx(
321
- "textarea",
322
- {
323
- ref: textareaRef,
324
- "aria-invalid": error || void 0,
325
- className: cn(
326
- "flex min-h-[var(--ds-control-height-md)] w-full px-ds-3 py-ds-2 text-sm",
327
- "bg-[var(--ds-input-bg)] text-[var(--ds-text-primary)]",
328
- "border-[length:var(--ds-border-width)]",
329
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)] hover:border-[var(--ds-input-border-hover)]",
330
- "rounded-[var(--ds-radius-md)]",
331
- "shadow-[var(--ds-input-shadow)]",
332
- "placeholder:text-[var(--ds-text-muted)]",
333
- error ? ERROR_FOCUS_RING : FOCUS_RING,
334
- "disabled:cursor-not-allowed disabled:opacity-disabled disabled:hover:border-[var(--ds-input-border)]",
335
- "read-only:bg-[var(--ds-bg-muted)] read-only:text-[var(--ds-text-secondary)] read-only:cursor-default read-only:hover:border-[var(--ds-input-border)]",
336
- "transition-colors duration-[var(--ds-transition-speed)]",
337
- autoResize && "resize-none overflow-hidden",
338
- className
339
- ),
340
- onInput: autoResize ? resize : void 0,
341
- ...props,
342
- onChange: props.onChange || props.value === void 0 ? onChange : void 0
343
- }
344
- );
345
- }
346
- function Label({ className, children, ...props }) {
347
- return /* @__PURE__ */ jsx(
348
- "label",
349
- {
350
- className: cn(
351
- "text-sm font-medium leading-none text-[var(--ds-text-secondary)]",
352
- "peer-disabled:cursor-not-allowed peer-disabled:opacity-disabled",
353
- className
354
- ),
355
- ...props,
356
- children
357
- }
358
- );
359
- }
360
- var TOGGLE_SIZES = {
361
- sm: { w: 32, h: 18, thumb: 12 },
362
- md: { w: 44, h: 24, thumb: 16 }
363
- };
364
- function Toggle({ checked, onChange, label, disabled, error = false, layout = "full", size = "md", "aria-labelledby": ariaLabelledBy, ...rest }) {
365
- const change = useReportedChange(onChange, checked);
366
- const dims = TOGGLE_SIZES[size];
367
- const travel = dims.w - dims.thumb - 3;
368
- const switchButton = /* @__PURE__ */ jsx(
369
- "button",
370
- {
371
- type: "button",
372
- role: "switch",
373
- "aria-checked": checked,
374
- "aria-label": !label && !ariaLabelledBy ? "Toggle" : void 0,
375
- "aria-labelledby": ariaLabelledBy,
376
- "aria-invalid": error || void 0,
377
- disabled,
378
- onClick: () => !disabled && change(!checked),
379
- ...rest,
380
- className: cn(
381
- "relative inline-flex shrink-0 items-center overflow-hidden",
382
- "border-[length:var(--ds-border-width)]",
383
- "rounded-[var(--ds-radius-full)]",
384
- "transition-colors duration-[var(--ds-transition-speed)]",
385
- "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
386
- disabled ? "cursor-not-allowed" : "cursor-pointer",
387
- checked ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-bg-muted)]",
388
- error ? "border-[var(--ds-status-error)]" : checked ? "border-transparent" : "border-[var(--ds-border-default)]"
389
- ),
390
- style: { width: dims.w, height: dims.h },
391
- children: /* @__PURE__ */ jsx(
392
- "span",
393
- {
394
- className: cn(
395
- "pointer-events-none inline-block shrink-0",
396
- "bg-[var(--ds-bg-surface)]",
397
- "border-[length:var(--ds-border-width)] border-transparent",
398
- "rounded-[var(--ds-radius-full)]",
399
- "transition-transform"
400
- ),
401
- style: {
402
- width: dims.thumb,
403
- height: dims.thumb,
404
- transform: checked ? `translateX(calc(${travel}px - var(--ds-border-width)))` : "translateX(calc(1px + var(--ds-border-width)))"
405
- }
406
- }
407
- )
408
- }
409
- );
410
- if (!label) {
411
- return /* @__PURE__ */ jsx("div", { className: cn(disabled && "opacity-disabled"), children: switchButton });
412
- }
413
- return /* @__PURE__ */ jsxs(
414
- "label",
415
- {
416
- className: cn(
417
- layout === "full" ? "flex items-center justify-between gap-ds-4" : "inline-flex items-center gap-ds-3",
418
- disabled ? "cursor-not-allowed opacity-disabled" : "cursor-pointer"
419
- ),
420
- children: [
421
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[var(--ds-text-primary)]", children: label }),
422
- switchButton
423
- ]
424
- }
425
- );
426
- }
427
- function InlineFieldError({ message, id, className }) {
428
- return /* @__PURE__ */ jsxs(
429
- "p",
430
- {
431
- id,
432
- role: "alert",
433
- className: cn(
434
- "flex items-center gap-ds-1.5 text-xs text-[var(--ds-status-error)]",
435
- "mt-ds-1.5",
436
- className
437
- ),
438
- children: [
439
- /* @__PURE__ */ jsx(Icon, { name: "error", size: 14, className: "shrink-0" }),
440
- /* @__PURE__ */ jsx("span", { children: message })
441
- ]
442
- }
443
- );
444
- }
445
- var FieldContext = createContext(null);
446
- function useField() {
447
- return useContext(FieldContext);
448
- }
449
- function useFieldControl(own, options = {}) {
450
- const field = useField();
451
- const id = own.id ?? field?.controlId;
452
- const invalid = !!own.error || own["aria-invalid"] === true || own["aria-invalid"] === "true" || !!field?.invalid;
453
- const required = !!own.required || own["aria-required"] === true || own["aria-required"] === "true" || !!field?.required;
454
- const noteId = options.requiredInDescription && required && id ? `${id}-required` : void 0;
455
- const ids = [own["aria-describedby"], field?.describedBy, noteId].filter(Boolean).join(" ").split(/\s+/).filter(Boolean);
456
- return {
457
- id,
458
- labelId: own["aria-labelledby"] ?? field?.labelId,
459
- describedBy: [...new Set(ids)].join(" ") || void 0,
460
- invalid,
461
- required,
462
- requiredNote: noteId ? /* @__PURE__ */ jsx("span", { id: noteId, hidden: true, children: "Required" }) : null
463
- };
464
- }
465
- var AddAnotherRowCtx = createContext(false);
466
- function Field({ label, children, hint, error, required, labelAction, hideLabel = false, id: idProp, validate, className }) {
467
- const inGroupRow = useContext(AddAnotherRowCtx);
468
- const generated = useId();
469
- const id = idProp ?? `field-${generated}`;
470
- const hintId = `${id}-hint`;
471
- const errorId = `${id}-error`;
472
- const form = useOptionalFormContext();
473
- const mode = form?.validationMode ?? "onBlur";
474
- const [localError, setLocalError] = useState(void 0);
475
- const [left, setLeft] = useState(false);
476
- const checkError = validate ? form ? form.errors[id] : localError : void 0;
477
- const child = typeof children === "function" ? null : Children.only(children);
478
- const childProps = child && isValidElement(child) ? child.props : null;
479
- const latest = useLatest({ validate, childProps, id, mode, left, showing: !!checkError, form });
480
- const check = useCallback(() => {
481
- const { validate: fn, childProps: props, id: controlId } = latest.current;
482
- if (!fn) return void 0;
483
- const value = props && "value" in props ? props.value : props && "checked" in props ? props.checked : document.getElementById(controlId)?.value;
484
- const message = fn(value);
485
- return typeof message === "string" && message.length > 0 ? message : void 0;
486
- }, [latest]);
487
- const show = useCallback((message) => {
488
- const { form: f, id: controlId } = latest.current;
489
- if (f) f.setError(controlId, message);
490
- else setLocalError(message);
491
- }, [latest]);
492
- const register = form?.register;
493
- const checks = !!validate;
494
- useEffect(() => register && checks ? register(id, check) : void 0, [register, checks, id, check]);
495
- const leaving = useRef(null);
496
- useEffect(() => () => {
497
- if (leaving.current) clearTimeout(leaving.current);
498
- }, []);
499
- const onFocusCapture = () => {
500
- if (leaving.current) {
501
- clearTimeout(leaving.current);
502
- leaving.current = null;
503
- }
504
- };
505
- const onBlurCapture = () => {
506
- if (!validate) return;
507
- if (leaving.current) clearTimeout(leaving.current);
508
- leaving.current = setTimeout(() => {
509
- leaving.current = null;
510
- setLeft(true);
511
- if (latest.current.mode !== "onSubmit" || latest.current.showing) show(check());
512
- }, 0);
513
- };
514
- const recheck = () => {
515
- if (!validate) return;
516
- setTimeout(() => {
517
- const { mode: m, left: wasLeft, showing } = latest.current;
518
- if (m === "onChange" && wasLeft || showing) show(check());
519
- }, 0);
520
- };
521
- const shownError = typeof error === "string" && error.length > 0 ? error : checkError;
522
- const hasError = typeof shownError === "string" && shownError.length > 0;
523
- const describedBy = [hint ? hintId : null, hasError ? errorId : null].filter(Boolean).join(" ") || void 0;
524
- const labelId = `${id}-label`;
525
- const fieldContext = useMemo(() => ({ controlId: id, labelId, describedBy, invalid: hasError, required: !!required }), [id, labelId, describedBy, hasError, required]);
526
- const controlProps = {
527
- id,
528
- "aria-describedby": describedBy,
529
- ...hasError ? { "aria-invalid": true, error: true } : {},
530
- ...required ? { "aria-required": true, required: true } : {}
531
- };
532
- let control;
533
- if (typeof children === "function") {
534
- control = children(controlProps);
535
- } else {
536
- if (!child || !isValidElement(child)) throw new Error("Field expects a single form control as its child");
537
- const isNative = typeof child.type === "string";
538
- const { error: errorFlag, ...ariaOnly } = controlProps;
539
- const ownOnChange = childProps?.onChange;
540
- const onChange = validate && !isNative ? { onChange: (...args) => {
541
- ownOnChange?.(...args);
542
- recheck();
543
- } } : {};
544
- control = cloneElement(child, isNative ? ariaOnly : { ...ariaOnly, ...errorFlag ? { error: true } : {}, ...onChange });
545
- }
546
- const labelNode = /* @__PURE__ */ jsxs(Label, { id: labelId, htmlFor: id, className: cn(hideLabel && "sr-only", hideLabel && inGroupRow && "@max-[30rem]:not-sr-only"), children: [
547
- label,
548
- required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-ds-0.5 text-[var(--ds-status-error)]", children: "*" })
549
- ] });
550
- return (
551
- // In a settings panel a field keeps the narrow reading width however wide the page is (--ds-form-max, Sprint 25.9 A4)
552
- /* @__PURE__ */ jsxs(
553
- "div",
554
- {
555
- className: cn("flex flex-col gap-ds-1.5 max-w-[var(--ds-form-max,none)]", className),
556
- ...validate ? { onFocusCapture, onBlurCapture, onInputCapture: recheck } : {},
557
- children: [
558
- hideLabel && !labelAction ? labelNode : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-2", children: [
559
- labelNode,
560
- labelAction
561
- ] }),
562
- /* @__PURE__ */ jsx(FieldContext.Provider, { value: fieldContext, children: control }),
563
- hint && /* @__PURE__ */ jsx("p", { id: hintId, className: "text-xs text-[var(--ds-text-muted)]", children: hint }),
564
- hasError && /* @__PURE__ */ jsx(InlineFieldError, { id: errorId, message: shownError, className: "mt-0" })
565
- ]
566
- }
567
- )
568
- );
569
- }
570
- var DROPDOWN_HEIGHT = {
571
- sm: "px-ds-2 py-ds-1 text-xs h-[var(--ds-control-height-sm)]",
572
- md: "px-ds-3 py-ds-2 text-sm h-[var(--ds-control-height-md)]",
573
- lg: "px-ds-4 py-ds-3 text-base h-[var(--ds-control-height-lg)]"
574
- };
575
- function Dropdown({
576
- options,
577
- value,
578
- onChange: onChangeProp,
579
- label,
580
- disabled,
581
- placeholder = "Select...",
582
- multiple,
583
- searchable,
584
- size: sizeProp,
585
- className,
586
- ref,
587
- container,
588
- error = false,
589
- showChips = true,
590
- "aria-label": ariaLabel,
591
- ...wiring
592
- }) {
593
- const onChange = useReportedChange(onChangeProp, value);
594
- const contextSize = useContext(ControlSizeContext);
595
- const generatedId = useId();
596
- const control = useFieldControl({ ...wiring, error: error || void 0 }, { requiredInDescription: true });
597
- const triggerId = control.id ?? generatedId;
598
- const labelSpanId = `${triggerId}-label`;
599
- const labelledBy = control.labelId ? `${control.labelId} ${triggerId}` : label && !ariaLabel ? `${labelSpanId} ${triggerId}` : void 0;
600
- const invalid = control.invalid;
601
- const size = sizeProp ?? contextSize ?? "md";
602
- const [open, setOpen] = useState(false);
603
- const [search, setSearch] = useState("");
604
- const [positioned, setPositioned] = useState(false);
605
- const triggerRef = useRef(null);
606
- const menuRef = useRef(null);
607
- const searchRef = useRef(null);
608
- const [pos, setPos] = useState({ top: 0, left: 0, width: 0 });
609
- const selectedValues = Array.isArray(value) ? value : [value].filter(Boolean);
610
- const updatePos = useCallback(() => {
611
- if (!triggerRef.current) return;
612
- const rect = triggerRef.current.getBoundingClientRect();
613
- let top = rect.bottom + 4;
614
- let left = rect.left;
615
- const width = rect.width;
616
- if (menuRef.current) {
617
- const menuRect = menuRef.current.getBoundingClientRect();
618
- const vw = window.innerWidth;
619
- const vh = window.innerHeight;
620
- if (left + menuRect.width > vw) left = Math.max(0, vw - menuRect.width - 8);
621
- if (top + menuRect.height > vh) {
622
- const above = rect.top - 4 - menuRect.height;
623
- if (above >= 0) top = above;
624
- else top = Math.max(0, vh - menuRect.height - 8);
625
- }
626
- }
627
- setPos({ top, left, width });
628
- }, []);
629
- const [prevOpen, setPrevOpen] = useState(open);
630
- if (open !== prevOpen) {
631
- setPrevOpen(open);
632
- if (open) setSearch("");
633
- else setPositioned(false);
634
- }
635
- useEffect(() => {
636
- if (!open) return;
637
- updatePos();
638
- const raf = requestAnimationFrame(() => {
639
- updatePos();
640
- setPositioned(true);
641
- });
642
- const onScroll = () => updatePos();
643
- window.addEventListener("scroll", onScroll, true);
644
- window.addEventListener("resize", updatePos);
645
- return () => {
646
- cancelAnimationFrame(raf);
647
- window.removeEventListener("scroll", onScroll, true);
648
- window.removeEventListener("resize", updatePos);
649
- };
650
- }, [open, updatePos]);
651
- const moved = useRef(false);
652
- useLayoutEffect(() => {
653
- if (!open || !positioned) return;
654
- moved.current = false;
655
- if (searchRef.current) {
656
- searchRef.current.focus();
657
- return;
658
- }
659
- const els = Array.from(menuRef.current?.querySelectorAll('[role="option"]') ?? []);
660
- (els.find((el) => el.getAttribute("aria-selected") === "true") ?? els[0])?.focus();
661
- }, [open, positioned]);
662
- useEffect(() => {
663
- if (!open) return;
664
- const handler = (e) => {
665
- const target = e.target;
666
- if (triggerRef.current?.contains(target) || menuRef.current?.contains(target)) return;
667
- setOpen(false);
668
- };
669
- document.addEventListener("mousedown", handler);
670
- return () => document.removeEventListener("mousedown", handler);
671
- }, [open]);
672
- const commitMoved = () => {
673
- if (multiple || !moved.current) return;
674
- const el = document.activeElement;
675
- const id = el instanceof HTMLElement && menuRef.current?.contains(el) ? el.getAttribute("data-option-id") : null;
676
- if (id) onChange(id);
677
- };
678
- const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: menuRef, close: () => setOpen(false), tabExits: "any", onTabOut: commitMoved });
679
- const typeahead = useRef({ buffer: "", at: 0 });
680
- useEffect(() => {
681
- if (!open) return;
682
- const optionButtons = () => Array.from(menuRef.current?.querySelectorAll('[role="option"]') ?? []);
683
- const focusOption = (idx) => {
684
- const els = optionButtons();
685
- if (els.length === 0) return;
686
- els[(idx % els.length + els.length) % els.length].focus();
687
- moved.current = true;
688
- };
689
- const onKey = (e) => {
690
- const inMenu = menuRef.current?.contains(document.activeElement) || triggerRef.current === document.activeElement;
691
- if (!inMenu) return;
692
- const els = optionButtons();
693
- const current = els.findIndex((el) => el === document.activeElement);
694
- if (e.key === "Escape") {
695
- if (e.defaultPrevented || !isTopLayer()) return;
696
- e.preventDefault();
697
- returnFocus();
698
- setOpen(false);
699
- return;
700
- }
701
- if (e.key === "ArrowDown") {
702
- e.preventDefault();
703
- focusOption(current < 0 ? 0 : current + 1);
704
- return;
705
- }
706
- if (e.key === "ArrowUp") {
707
- e.preventDefault();
708
- focusOption(current < 0 ? els.length - 1 : current - 1);
709
- return;
710
- }
711
- const inSearch = document.activeElement === searchRef.current;
712
- if (e.key === "Home" && !inSearch) {
713
- e.preventDefault();
714
- focusOption(0);
715
- return;
716
- }
717
- if (e.key === "End" && !inSearch) {
718
- e.preventDefault();
719
- focusOption(els.length - 1);
720
- return;
721
- }
722
- if (!searchable && e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
723
- const now = Date.now();
724
- typeahead.current.buffer = now - typeahead.current.at > 700 ? e.key : typeahead.current.buffer + e.key;
725
- typeahead.current.at = now;
726
- const q = typeahead.current.buffer.toLowerCase();
727
- const hit = els.findIndex((el) => (el.textContent ?? "").trim().toLowerCase().startsWith(q));
728
- if (hit >= 0) {
729
- e.preventDefault();
730
- focusOption(hit);
731
- }
732
- }
733
- };
734
- document.addEventListener("keydown", onKey);
735
- return () => document.removeEventListener("keydown", onKey);
736
- }, [open, searchable, isTopLayer, returnFocus]);
737
- const filtered = searchable && search ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : options;
738
- const groups = /* @__PURE__ */ new Map();
739
- for (const opt of filtered) {
740
- const g = opt.group ?? "";
741
- if (!groups.has(g)) groups.set(g, []);
742
- groups.get(g).push(opt);
743
- }
744
- const hasGroups = Array.from(groups.keys()).some((k) => k !== "");
745
- const handleSelect = (id) => {
746
- if (multiple) {
747
- const arr = selectedValues.includes(id) ? selectedValues.filter((v) => v !== id) : [...selectedValues, id];
748
- onChange(arr);
749
- } else {
750
- returnFocus();
751
- onChange(id);
752
- setOpen(false);
753
- }
754
- };
755
- const chipsRef = useRef(null);
756
- const pendingChipFocus = useRef(null);
757
- useLayoutEffect(() => {
758
- const pending = pendingChipFocus.current;
759
- if (pending === null) return;
760
- pendingChipFocus.current = null;
761
- if (selectedValues.includes(pending.value)) return;
762
- const chips = Array.from(chipsRef.current?.querySelectorAll("[data-chip-remove]") ?? []);
763
- focusAfterRemoval(chips, pending.index, triggerRef.current);
764
- });
765
- const displayLabel = multiple ? selectedValues.length > 0 ? `${selectedValues.length} selected` : placeholder : options.find((o) => o.id === selectedValues[0])?.label ?? placeholder;
766
- const { ref: overflowRef, isTruncated } = useTextOverflow({ lines: 1, content: displayLabel });
767
- return /* @__PURE__ */ jsxs("div", { ref, className: cn("relative", className), children: [
768
- label && /* @__PURE__ */ jsx("span", { id: labelSpanId, className: "block mb-ds-1.5 text-sm font-medium text-[var(--ds-text-secondary)]", children: label }),
769
- control.requiredNote,
770
- /* @__PURE__ */ jsxs(
771
- "button",
772
- {
773
- ref: triggerRef,
774
- id: triggerId,
775
- type: "button",
776
- onClick: () => !disabled && setOpen(!open),
777
- onKeyDown: (e) => {
778
- if (disabled || open) return;
779
- if (e.key === "ArrowDown" || e.key === "ArrowUp") {
780
- e.preventDefault();
781
- setOpen(true);
782
- }
783
- },
784
- disabled,
785
- "aria-invalid": invalid || void 0,
786
- "aria-haspopup": "listbox",
787
- "aria-expanded": open,
788
- "aria-label": labelledBy ? void 0 : ariaLabel,
789
- "aria-labelledby": labelledBy,
790
- "aria-describedby": control.describedBy,
791
- className: cn(
792
- "w-full flex items-center justify-between gap-ds-2",
793
- DROPDOWN_HEIGHT[size],
794
- "bg-[var(--ds-input-bg)] text-[var(--ds-text-primary)]",
795
- "border-[length:var(--ds-border-width)]",
796
- invalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]",
797
- "rounded-[var(--ds-radius-md)]",
798
- "shadow-[var(--ds-input-shadow)]",
799
- "transition-colors",
800
- !invalid && !disabled && !open && "hover:border-[var(--ds-input-border-hover)]",
801
- open && (invalid ? ERROR_ACTIVE_RING : ACTIVE_RING),
802
- invalid ? ERROR_FOCUS_RING : FOCUS_RING,
803
- "cursor-pointer disabled:cursor-not-allowed disabled:opacity-disabled",
804
- !selectedValues.length && "text-[var(--ds-text-muted)]"
805
- ),
806
- children: [
807
- isTruncated ? /* @__PURE__ */ jsx(Tooltip, { content: displayLabel, children: /* @__PURE__ */ jsx("span", { ref: overflowRef, className: "truncate", children: displayLabel }) }) : /* @__PURE__ */ jsx("span", { ref: overflowRef, className: "truncate", children: displayLabel }),
808
- /* @__PURE__ */ jsx("span", { className: cn("transition-transform", open && "rotate-180"), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: "rotate-90" }) })
809
- ]
810
- }
811
- ),
812
- open && createPortal(
813
- /* @__PURE__ */ jsxs(
814
- "div",
815
- {
816
- ref: menuRef,
817
- tabIndex: -1,
818
- onClick: (e) => e.stopPropagation(),
819
- className: cn(
820
- "fixed z-[var(--ds-z-popover)] outline-none",
821
- "bg-[var(--ds-bg-surface-elevated)]",
822
- "[backdrop-filter:var(--ds-glass-filter)] [-webkit-backdrop-filter:var(--ds-glass-filter)]",
823
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
824
- "rounded-[var(--ds-radius-md)] overflow-hidden",
825
- "shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]"
826
- ),
827
- style: { top: pos.top, left: pos.left, minWidth: pos.width, width: "max-content", maxWidth: "calc(100vw - 16px)", visibility: positioned ? "visible" : "hidden" },
828
- children: [
829
- searchable && /* @__PURE__ */ jsx("div", { className: "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]", style: { padding: "var(--ds-space-2)" }, children: /* @__PURE__ */ jsx(
830
- "input",
831
- {
832
- ref: searchRef,
833
- type: "text",
834
- value: search,
835
- onChange: (e) => setSearch(e.target.value),
836
- placeholder: "Search\u2026",
837
- className: cn("w-full bg-transparent text-[var(--ds-text-primary)] placeholder:text-[var(--ds-text-muted)] outline-none", size === "sm" ? "text-xs" : "text-sm")
838
- }
839
- ) }),
840
- /* @__PURE__ */ jsx("div", { role: "listbox", className: "overflow-y-auto", style: { padding: "var(--ds-space-1)", maxHeight: "var(--ds-dropdown-max-height)" }, children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: cn("text-[var(--ds-text-muted)] text-center py-ds-4", size === "sm" ? "text-xs" : "text-sm"), children: "No options found" }) : hasGroups ? Array.from(groups.entries()).map(([group, opts]) => /* @__PURE__ */ jsxs("div", { children: [
841
- group && /* @__PURE__ */ jsx("p", { className: "text-2xs font-medium uppercase tracking-wider text-[var(--ds-text-muted)] px-ds-2 pt-ds-2 pb-ds-1", children: group }),
842
- opts.map((opt) => /* @__PURE__ */ jsx(
843
- OptionButton,
844
- {
845
- opt,
846
- isSelected: selectedValues.includes(opt.id),
847
- multiple,
848
- onSelect: handleSelect,
849
- size
850
- },
851
- opt.id
852
- ))
853
- ] }, group)) : filtered.map((opt) => /* @__PURE__ */ jsx(
854
- OptionButton,
855
- {
856
- opt,
857
- isSelected: selectedValues.includes(opt.id),
858
- multiple,
859
- onSelect: handleSelect,
860
- size
861
- },
862
- opt.id
863
- )) })
864
- ]
865
- }
866
- ),
867
- container ?? document.body
868
- ),
869
- multiple && showChips && selectedValues.length > 0 && /* @__PURE__ */ jsx("div", { ref: chipsRef, className: "flex flex-wrap gap-ds-1 mt-ds-1.5", children: selectedValues.map((v, i) => {
870
- const opt = options.find((o) => o.id === v);
871
- return opt ? /* @__PURE__ */ jsxs(
872
- "span",
873
- {
874
- className: "inline-flex items-center gap-ds-1 text-xs rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-muted)] text-[var(--ds-text-secondary)] px-ds-1.5 py-ds-0.5",
875
- children: [
876
- opt.label,
877
- /* @__PURE__ */ jsx(
878
- "button",
879
- {
880
- type: "button",
881
- "data-chip-remove": "",
882
- onClick: () => {
883
- pendingChipFocus.current = { index: i, value: v };
884
- handleSelect(v);
885
- },
886
- className: "hover:text-[var(--ds-text-primary)] cursor-pointer",
887
- "aria-label": `Remove ${opt.label}`,
888
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 10 })
889
- }
890
- )
891
- ]
892
- },
893
- v
894
- ) : null;
895
- }) })
896
- ] });
897
- }
898
- function OptionButton({
899
- opt,
900
- isSelected,
901
- multiple,
902
- onSelect,
903
- size = "md"
904
- }) {
905
- return /* @__PURE__ */ jsxs(
906
- "button",
907
- {
908
- type: "button",
909
- role: "option",
910
- "data-option-id": opt.id,
911
- "aria-selected": isSelected,
912
- tabIndex: -1,
913
- onClick: () => onSelect(opt.id),
914
- className: cn(
915
- "w-full text-left cursor-pointer rounded-[var(--ds-radius-sm)]",
916
- "transition-colors flex items-center gap-ds-2",
917
- "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
918
- size === "sm" ? "px-ds-2 py-ds-1 text-xs" : "px-ds-2 py-ds-1.5 text-sm",
919
- isSelected && !multiple ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-emphasis)]"
920
- ),
921
- children: [
922
- multiple && /* @__PURE__ */ jsx(
923
- "span",
924
- {
925
- className: cn(
926
- "w-[var(--ds-icon-size-sm)] h-[var(--ds-icon-size-sm)] rounded-[var(--ds-radius-sm)] border-[length:var(--ds-border-width)] shrink-0 flex items-center justify-center",
927
- isSelected ? "bg-[var(--ds-accent-primary)] border-[var(--ds-accent-primary)]" : "border-[var(--ds-border-default)]"
928
- ),
929
- children: isSelected && /* @__PURE__ */ jsx(Icon, { name: "check", size: 10, className: "text-[var(--ds-text-on-primary)]" })
930
- }
931
- ),
932
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: opt.label }),
933
- isSelected && !multiple && /* @__PURE__ */ jsx(Icon, { name: "check", size: size === "sm" ? 12 : 14 })
934
- ]
935
- }
936
- );
937
- }
938
217
  var CONTROL_HEIGHT = {
939
218
  sm: "h-[var(--ds-control-height-sm)]",
940
219
  md: "h-[var(--ds-control-height-md)]",
@@ -1230,7 +509,7 @@ function Checkbox({ checked, onChange: onChangeProp, label, size = "md", disable
1230
509
  "border-[2px]",
1231
510
  "transition-all",
1232
511
  "peer-focus-visible:shadow-[var(--ds-shadow-focus)]",
1233
- filled ? "bg-[var(--ds-check-bg)] [background-image:var(--ds-gradient)] border-[var(--ds-check-border)]" : "bg-[var(--ds-input-bg)] border-[var(--ds-input-border)]",
512
+ filled ? cn(CHECK_FILL, "border-[var(--ds-check-border)]") : "bg-[var(--ds-input-bg)] border-[var(--ds-input-border)]",
1234
513
  disabled && "pointer-events-none"
1235
514
  ),
1236
515
  children: /* @__PURE__ */ jsx(
@@ -1351,7 +630,8 @@ function RadioGroup({ value, onChange: onChangeProp, options, orientation = "ver
1351
630
  selected && "border-[var(--ds-check-border)]",
1352
631
  "group-focus-visible:shadow-[var(--ds-shadow-focus)]"
1353
632
  ),
1354
- children: selected && /* @__PURE__ */ jsx("span", { className: "h-2.5 w-2.5 rounded-[var(--ds-radius-full)] bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)]" })
633
+ children: selected && // Sprint 25.14 A20: the checked colour, as a checkbox's fill (a preset whose accent falls short of 3:1 sets it)
634
+ /* @__PURE__ */ jsx("span", { className: cn("h-2.5 w-2.5 rounded-[var(--ds-radius-full)]", CHECK_FILL) })
1355
635
  }
1356
636
  ),
1357
637
  /* @__PURE__ */ jsx("span", { className: "text-sm text-[var(--ds-text-primary)]", children: option.label })
@@ -2488,21 +1768,6 @@ function Progress({ value, max = 100, variant = "default", size = "md", indeterm
2488
1768
  ] })
2489
1769
  ] });
2490
1770
  }
2491
-
2492
- // ../../showcase/src/lib/dev-warn.ts
2493
- var warned = /* @__PURE__ */ new Set();
2494
- function warnOnce(component, message) {
2495
- let dev = false;
2496
- try {
2497
- dev = process.env.NODE_ENV !== "production";
2498
- } catch {
2499
- dev = false;
2500
- }
2501
- const text = `${component}: ${message}`;
2502
- if (!dev || warned.has(text)) return;
2503
- warned.add(text);
2504
- console.warn(text);
2505
- }
2506
1771
  function TruncFileName({ children, className }) {
2507
1772
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
2508
1773
  const el = /* @__PURE__ */ jsx("p", { ref, className: cn("truncate", className), children });
@@ -2631,9 +1896,9 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2631
1896
  if (new Set(ids).size !== ids.length) warnOnce("FileUpload", "two files in the list share an id: give each file its own (the same file picked twice is two files).");
2632
1897
  }, [controlled, onFiles, upload, onRemove, shown]);
2633
1898
  const [heard, setHeard] = useState({ text: "", n: 0 });
2634
- const signature2 = shown.map((i) => `${i.id}\0${i.status}`).join("");
2635
- const [seen, setSeen] = useState(() => ({ signature: signature2, statuses: new Map(shown.map((i) => [i.id, i.status])) }));
2636
- if (signature2 !== seen.signature) {
1899
+ const signature = shown.map((i) => `${i.id}\0${i.status}`).join("");
1900
+ const [seen, setSeen] = useState(() => ({ signature, statuses: new Map(shown.map((i) => [i.id, i.status])) }));
1901
+ if (signature !== seen.signature) {
2637
1902
  const said = [];
2638
1903
  for (const item of shown) {
2639
1904
  const before = seen.statuses.get(item.id);
@@ -2641,7 +1906,7 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2641
1906
  if (item.status === "done") said.push(`${item.name} uploaded`);
2642
1907
  else if (item.status === "error") said.push(`${item.name} didn't upload: ${item.error ?? "the upload failed"}`);
2643
1908
  }
2644
- setSeen({ signature: signature2, statuses: new Map(shown.map((i) => [i.id, i.status])) });
1909
+ setSeen({ signature, statuses: new Map(shown.map((i) => [i.id, i.status])) });
2645
1910
  if (said.length) setHeard((h) => ({ text: said.join(". "), n: h.n + 1 }));
2646
1911
  }
2647
1912
  const pendingFocus = useRef(null);
@@ -2728,7 +1993,7 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2728
1993
  // The real file input is visually hidden inside; Tab lands on it and
2729
1994
  // Enter/Space open the picker natively, so the zone shows its focus.
2730
1995
  invalid ? ERROR_HAS_FOCUS_RING : HAS_FOCUS_RING,
2731
- dragOver ? `${ACTIVE_RING} bg-[var(--ds-accent-whisper)]` : invalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-default)] hover:border-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-whisper)]"
1996
+ dragOver ? `${ACTIVE_RING} bg-[var(--ds-accent-whisper)]` : invalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)] hover:border-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-whisper)]"
2732
1997
  ),
2733
1998
  style: { padding: "var(--ds-space-8) var(--ds-space-4)" },
2734
1999
  children: [
@@ -2902,7 +2167,7 @@ function SliderRange({
2902
2167
  !trackStyle && /* @__PURE__ */ jsx(
2903
2168
  "div",
2904
2169
  {
2905
- className: "absolute h-1.5 rounded-[var(--ds-radius-full)] bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)]",
2170
+ className: cn("absolute h-1.5 rounded-[var(--ds-radius-full)]", CHECK_FILL),
2906
2171
  style: {
2907
2172
  left: `${isRange ? pct(low) : 0}%`,
2908
2173
  width: `${isRange ? pct(high) - pct(low) : pct(low)}%`
@@ -2926,7 +2191,7 @@ function SliderRange({
2926
2191
  className: cn(
2927
2192
  "absolute w-ds-4 h-ds-4 -translate-x-1/2 rounded-[var(--ds-radius-full)]",
2928
2193
  "bg-[var(--ds-bg-surface)] border-2",
2929
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-accent-primary)]",
2194
+ error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-check-border)]",
2930
2195
  "shadow-[var(--ds-shadow-sm)]",
2931
2196
  "transition-[shadow,transform] duration-200 ease-out",
2932
2197
  "focus-visible:shadow-[var(--ds-shadow-focus)]",
@@ -2952,7 +2217,7 @@ function SliderRange({
2952
2217
  className: cn(
2953
2218
  "absolute w-ds-4 h-ds-4 -translate-x-1/2 rounded-[var(--ds-radius-full)]",
2954
2219
  "bg-[var(--ds-bg-surface)] border-2",
2955
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-accent-primary)]",
2220
+ error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-check-border)]",
2956
2221
  "shadow-[var(--ds-shadow-sm)]",
2957
2222
  "transition-[shadow,transform] duration-200 ease-out",
2958
2223
  "focus-visible:shadow-[var(--ds-shadow-focus)]",
@@ -3372,259 +2637,43 @@ function TimePicker({
3372
2637
  onFocus: () => holdStop("minute", i),
3373
2638
  "data-selected": m === parsed.minute,
3374
2639
  onMouseDown: (e) => e.preventDefault(),
3375
- onClick: () => emitChange(parsed.hour, m, parsed.period),
3376
- className: cn(
3377
- "w-full text-center text-sm py-ds-1 rounded-[var(--ds-radius-sm)] cursor-pointer",
3378
- "transition-colors focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
3379
- m === parsed.minute ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)]"
3380
- ),
3381
- children: pad(m)
3382
- },
3383
- m
3384
- )) }),
3385
- !use24Hour && /* @__PURE__ */ jsxs(Fragment, { children: [
3386
- /* @__PURE__ */ jsx("div", { className: "w-px bg-[var(--ds-border-subtle)]" }),
3387
- /* @__PURE__ */ jsx("div", { "data-column": true, className: "flex flex-col gap-ds-1 w-ds-10", children: ["AM", "PM"].map((p, i) => /* @__PURE__ */ jsx(
3388
- "button",
3389
- {
3390
- type: "button",
3391
- tabIndex: i === stopOf("period", chosenPeriod) ? 0 : -1,
3392
- onFocus: () => holdStop("period", i),
3393
- "data-selected": p === parsed.period,
3394
- onMouseDown: (e) => e.preventDefault(),
3395
- onClick: () => emitChange(parsed.hour, parsed.minute, p),
3396
- className: cn(
3397
- "text-xs font-medium py-ds-1 rounded-[var(--ds-radius-sm)] cursor-pointer",
3398
- "transition-colors text-center focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
3399
- p === parsed.period ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)]"
3400
- ),
3401
- children: p
3402
- },
3403
- p
3404
- )) })
3405
- ] })
3406
- ] })
3407
- }
3408
- ),
3409
- container ?? document.body
3410
- )
3411
- ] });
3412
- }
3413
- var sizes = {
3414
- sm: { box: "h-[var(--ds-control-height-sm)]", input: "text-xs self-stretch", btn: "w-7 text-xs" },
3415
- md: { box: "h-[var(--ds-control-height-md)]", input: "text-sm self-stretch", btn: "w-9 text-sm" },
3416
- lg: { box: "h-[var(--ds-control-height-lg)]", input: "text-base self-stretch", btn: "w-10 text-base" }
3417
- };
3418
- function NumberInput({
3419
- value,
3420
- onChange: onChangeProp,
3421
- min = -Infinity,
3422
- max = Infinity,
3423
- step = 1,
3424
- disabled = false,
3425
- placeholder = "0",
3426
- prefix,
3427
- suffix,
3428
- size: sizeProp,
3429
- className,
3430
- error = false,
3431
- allowDecimals,
3432
- id
3433
- }) {
3434
- const onChange = useReportedChange(onChangeProp, value);
3435
- const contextSize = useContext(ControlSizeContext);
3436
- const size = sizeProp ?? contextSize ?? "md";
3437
- const intervalRef = useRef(void 0);
3438
- const timeoutRef = useRef(void 0);
3439
- const inputRef = useRef(null);
3440
- const [focused, setFocused] = useState(false);
3441
- const [draft, setDraft] = useState(String(value));
3442
- const decimals = allowDecimals ?? step % 1 !== 0;
3443
- const [prevSync, setPrevSync] = useState({ value, focused });
3444
- if (value !== prevSync.value || focused !== prevSync.focused) {
3445
- setPrevSync({ value, focused });
3446
- if (!focused) setDraft(String(value));
3447
- }
3448
- const clamp = useCallback(
3449
- (v) => Math.min(max, Math.max(min, v)),
3450
- [min, max]
3451
- );
3452
- const valueRef = useLatest(value);
3453
- const commitValue = useCallback((v, own = false) => {
3454
- const clamped = clamp(v);
3455
- valueRef.current = clamped;
3456
- setDraft(String(clamped));
3457
- (own ? onChangeProp : onChange)(clamped);
3458
- return clamped;
3459
- }, [onChange, onChangeProp, clamp, valueRef]);
3460
- const increment = useCallback(() => commitValue(valueRef.current + step), [commitValue, step, valueRef]);
3461
- const decrement = useCallback(() => commitValue(valueRef.current - step), [commitValue, step, valueRef]);
3462
- const startRepeat = useCallback(
3463
- (fn) => {
3464
- fn();
3465
- timeoutRef.current = setTimeout(() => {
3466
- intervalRef.current = setInterval(fn, 75);
3467
- }, 400);
3468
- },
3469
- []
3470
- );
3471
- const stopRepeat = useCallback(() => {
3472
- clearTimeout(timeoutRef.current);
3473
- clearInterval(intervalRef.current);
3474
- }, []);
3475
- const handleInput = useCallback(
3476
- (e) => {
3477
- const raw = e.target.value;
3478
- if (raw === "" || raw === "-") {
3479
- setDraft(raw);
3480
- return;
3481
- }
3482
- if (!decimals && raw.includes(".")) return;
3483
- const validPattern = decimals ? /^-?\d*\.?\d*$/ : /^-?\d*$/;
3484
- if (!validPattern.test(raw)) return;
3485
- setDraft(raw);
3486
- const parsed = parseFloat(raw);
3487
- if (!isNaN(parsed) && isFinite(parsed)) {
3488
- onChange(clamp(parsed));
3489
- }
3490
- },
3491
- [onChange, clamp, decimals]
3492
- );
3493
- const handleBlur = useCallback(() => {
3494
- setFocused(false);
3495
- const parsed = parseFloat(draft);
3496
- if (!isNaN(parsed) && isFinite(parsed)) {
3497
- const committed = commitValue(parsed, draft === String(value));
3498
- setDraft(String(committed));
3499
- } else {
3500
- const fallback = isFinite(min) ? min : 0;
3501
- const committed = commitValue(value !== void 0 ? value : fallback, true);
3502
- setDraft(String(committed));
3503
- }
3504
- }, [draft, commitValue, min, value]);
3505
- const handleFocus = useCallback(() => {
3506
- setFocused(true);
3507
- }, []);
3508
- const handleKeyDown = useCallback(
3509
- (e) => {
3510
- if (e.key === "ArrowUp") {
3511
- e.preventDefault();
3512
- increment();
3513
- }
3514
- if (e.key === "ArrowDown") {
3515
- e.preventDefault();
3516
- decrement();
3517
- }
3518
- },
3519
- [increment, decrement]
3520
- );
3521
- const s = sizes[size];
3522
- const atMin = value <= min;
3523
- const atMax = value >= max;
3524
- return /* @__PURE__ */ jsxs(
3525
- "div",
3526
- {
3527
- className: cn(
3528
- "flex items-center w-full",
3529
- s.box,
3530
- "border-[length:var(--ds-border-width)]",
3531
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]",
3532
- "rounded-[var(--ds-radius-md)]",
3533
- "shadow-[var(--ds-input-shadow)]",
3534
- "bg-[var(--ds-input-bg)]",
3535
- "transition-colors duration-[var(--ds-transition-speed)]",
3536
- !error && !disabled && "hover:border-[var(--ds-input-border-hover)]",
3537
- error ? ERROR_FOCUS_WITHIN_RING : cn(FOCUS_WITHIN_RING, "focus-within:hover:border-[var(--ds-border-focus)]"),
3538
- disabled && "opacity-disabled cursor-not-allowed",
3539
- className
3540
- ),
3541
- children: [
3542
- /* @__PURE__ */ jsx(
3543
- "button",
3544
- {
3545
- type: "button",
3546
- tabIndex: -1,
3547
- disabled: disabled || atMin,
3548
- onMouseDown: (e) => {
3549
- e.preventDefault();
3550
- inputRef.current?.focus();
3551
- if (!disabled && !atMin) startRepeat(decrement);
3552
- },
3553
- onMouseUp: stopRepeat,
3554
- onMouseLeave: stopRepeat,
3555
- className: cn(
3556
- "inline-flex items-center justify-center shrink-0",
3557
- "border-r-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
3558
- "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
3559
- "transition-colors cursor-pointer",
3560
- "rounded-l-[var(--ds-radius-md)]",
3561
- s.btn,
3562
- s.input,
3563
- (disabled || atMin) && "opacity-disabled cursor-not-allowed hover:bg-transparent hover:text-[var(--ds-text-muted)]"
3564
- ),
3565
- "aria-label": "Decrease",
3566
- children: "\u2212"
3567
- }
3568
- ),
3569
- /* @__PURE__ */ jsxs("div", { className: cn("flex items-center flex-1 min-w-0 px-ds-2 gap-ds-1", s.input), children: [
3570
- prefix && /* @__PURE__ */ jsx("span", { className: "text-[var(--ds-text-muted)] shrink-0 text-xs", children: prefix }),
3571
- /* @__PURE__ */ jsx(
3572
- "input",
3573
- {
3574
- ref: inputRef,
3575
- id,
3576
- type: "text",
3577
- inputMode: decimals ? "decimal" : "numeric",
3578
- "aria-invalid": error || void 0,
3579
- value: focused ? draft : String(value),
3580
- onChange: handleInput,
3581
- onFocus: handleFocus,
3582
- onBlur: handleBlur,
3583
- onKeyDown: handleKeyDown,
3584
- disabled,
3585
- placeholder,
3586
- className: cn(
3587
- "w-full min-w-0 bg-transparent text-center outline-none",
3588
- "text-[var(--ds-text-primary)]",
3589
- "placeholder:text-[var(--ds-text-muted)]",
3590
- "font-[family-name:var(--ds-font-mono)]",
3591
- s.input,
3592
- disabled && "cursor-not-allowed"
3593
- )
3594
- }
3595
- ),
3596
- suffix && /* @__PURE__ */ jsx("span", { className: "text-[var(--ds-text-muted)] shrink-0 text-xs", children: suffix })
3597
- ] }),
3598
- /* @__PURE__ */ jsx(
3599
- "button",
3600
- {
3601
- type: "button",
3602
- tabIndex: -1,
3603
- disabled: disabled || atMax,
3604
- onMouseDown: (e) => {
3605
- e.preventDefault();
3606
- inputRef.current?.focus();
3607
- if (!disabled && !atMax) startRepeat(increment);
3608
- },
3609
- onMouseUp: stopRepeat,
3610
- onMouseLeave: stopRepeat,
3611
- className: cn(
3612
- "inline-flex items-center justify-center shrink-0",
3613
- "border-l-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
3614
- "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
3615
- "transition-colors cursor-pointer",
3616
- "rounded-r-[var(--ds-radius-md)]",
3617
- s.btn,
3618
- s.input,
3619
- (disabled || atMax) && "opacity-disabled cursor-not-allowed hover:bg-transparent hover:text-[var(--ds-text-muted)]"
3620
- ),
3621
- "aria-label": "Increase",
3622
- children: "+"
3623
- }
3624
- )
3625
- ]
3626
- }
3627
- );
2640
+ onClick: () => emitChange(parsed.hour, m, parsed.period),
2641
+ className: cn(
2642
+ "w-full text-center text-sm py-ds-1 rounded-[var(--ds-radius-sm)] cursor-pointer",
2643
+ "transition-colors focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
2644
+ m === parsed.minute ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)]"
2645
+ ),
2646
+ children: pad(m)
2647
+ },
2648
+ m
2649
+ )) }),
2650
+ !use24Hour && /* @__PURE__ */ jsxs(Fragment, { children: [
2651
+ /* @__PURE__ */ jsx("div", { className: "w-px bg-[var(--ds-border-subtle)]" }),
2652
+ /* @__PURE__ */ jsx("div", { "data-column": true, className: "flex flex-col gap-ds-1 w-ds-10", children: ["AM", "PM"].map((p, i) => /* @__PURE__ */ jsx(
2653
+ "button",
2654
+ {
2655
+ type: "button",
2656
+ tabIndex: i === stopOf("period", chosenPeriod) ? 0 : -1,
2657
+ onFocus: () => holdStop("period", i),
2658
+ "data-selected": p === parsed.period,
2659
+ onMouseDown: (e) => e.preventDefault(),
2660
+ onClick: () => emitChange(parsed.hour, parsed.minute, p),
2661
+ className: cn(
2662
+ "text-xs font-medium py-ds-1 rounded-[var(--ds-radius-sm)] cursor-pointer",
2663
+ "transition-colors text-center focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
2664
+ p === parsed.period ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)]"
2665
+ ),
2666
+ children: p
2667
+ },
2668
+ p
2669
+ )) })
2670
+ ] })
2671
+ ] })
2672
+ }
2673
+ ),
2674
+ container ?? document.body
2675
+ )
2676
+ ] });
3628
2677
  }
3629
2678
  var variantStyles = {
3630
2679
  default: "bg-[var(--ds-bg-emphasis)] text-[var(--ds-text-primary)]",
@@ -3867,7 +2916,7 @@ function TagInput({
3867
2916
  }
3868
2917
  );
3869
2918
  }
3870
- var sizes2 = {
2919
+ var sizes = {
3871
2920
  sm: "w-[var(--ds-control-height-sm)] h-[var(--ds-control-height-sm)] text-sm",
3872
2921
  md: "w-[var(--ds-control-height-md)] h-[var(--ds-control-height-md)] text-base",
3873
2922
  lg: "w-[var(--ds-control-height-lg)] h-[var(--ds-control-height-lg)] text-lg"
@@ -3941,7 +2990,7 @@ function PinInput({
3941
2990
  role: "group",
3942
2991
  "aria-label": "PIN input",
3943
2992
  "aria-invalid": error || void 0,
3944
- className: cn("inline-flex items-center gap-ds-2", className),
2993
+ className: cn("inline-flex w-fit items-center gap-ds-2", className),
3945
2994
  children: digits.map((digit, i) => /* @__PURE__ */ jsx(
3946
2995
  "input",
3947
2996
  {
@@ -3971,7 +3020,7 @@ function PinInput({
3971
3020
  !error && !disabled && "hover:border-[var(--ds-input-border-hover)]",
3972
3021
  error ? ERROR_FOCUS_RING : FOCUS_RING,
3973
3022
  "placeholder:text-[var(--ds-text-muted)]",
3974
- sizes2[size],
3023
+ sizes[size],
3975
3024
  disabled && "opacity-disabled cursor-not-allowed"
3976
3025
  )
3977
3026
  },
@@ -4106,7 +3155,9 @@ function Rating({
4106
3155
  "aria-required": readOnly ? void 0 : control.required || void 0,
4107
3156
  onKeyDown: readOnly ? void 0 : handleKeyDown,
4108
3157
  className: cn(
4109
- "inline-flex items-center gap-ds-0.5",
3158
+ // Sprint 25.14 A21: its own width in a Field's column, not the field's; w-fit, not self-start, so a row or a
3159
+ // centred column still aligns it (the review of 30 September)
3160
+ "inline-flex w-fit items-center gap-ds-0.5",
4110
3161
  error && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)] rounded-[var(--ds-radius-sm)] p-ds-0.5",
4111
3162
  className
4112
3163
  ),
@@ -5312,46 +4363,20 @@ function MentionInput({
5312
4363
  )
5313
4364
  ] });
5314
4365
  }
5315
- var HEADER_LAYOUT = {
5316
- row: "flex flex-wrap items-start justify-between gap-ds-4",
5317
- block: "flex flex-col space-y-ds-1.5 grow basis-1/2 break-words max-w-full has-[[data-title-one-line]]:min-w-0",
5318
- titleLeads: "grow basis-1/2 max-w-max break-words has-[[data-title-one-line]]:min-w-0",
5319
- floorOff: "min-w-0",
5320
- actions: "flex items-center gap-ds-2 shrink-0 ml-auto",
5321
- below: "mt-ds-1.5"
5322
- };
5323
- function useTitleFloorFits(rowRef, active) {
5324
- const [fits, setFits] = useState(true);
5325
- useLayoutEffect(() => {
5326
- const row = rowRef.current;
5327
- if (!active || !row) return;
5328
- const measure = () => {
5329
- const title = row.querySelector("h1, h2, h3, h4, h5, h6");
5330
- if (!title) return;
5331
- const width = title.style.width;
5332
- title.style.width = "min-content";
5333
- const longest = title.getBoundingClientRect().width;
5334
- title.style.width = width;
5335
- if (longest === 0) return;
5336
- const next = longest <= row.clientWidth + 0.5;
5337
- setFits((was) => was === next ? was : next);
5338
- };
5339
- measure();
5340
- if (typeof ResizeObserver === "undefined") return;
5341
- const observer = new ResizeObserver(measure);
5342
- observer.observe(row);
5343
- return () => observer.disconnect();
5344
- }, [rowRef, active]);
5345
- return fits;
5346
- }
5347
- function actionsAreBadges(actions) {
5348
- const items = Children.toArray(actions);
5349
- return items.length > 0 && items.every((item) => isValidElement(item) && (item.type === Fragment$1 ? actionsAreBadges(item.props.children) : item.type === Badge));
4366
+ var CardRowCtx = createContext(null);
4367
+ var TITLE_LINE = "h-[calc(var(--ds-font-size-lg)*var(--ds-line-height-lg))]";
4368
+ var HeaderRowCtx = createContext(false);
4369
+ function holdsHeader(children) {
4370
+ let found = false;
4371
+ Children.forEach(children, (child) => {
4372
+ if (found || !isValidElement(child)) return;
4373
+ if (child.type === Fragment$1) found = holdsHeader(child.props.children);
4374
+ else if (child.type === CardHeader) found = true;
4375
+ });
4376
+ return found;
5350
4377
  }
5351
- var CardCtx = createContext(false);
5352
4378
  var CardActionCtx = createContext(null);
5353
4379
  var CARD_ACTION_ATTR = "data-card-action";
5354
- var CardSurfaceCtx = createContext(false);
5355
4380
  var variantStyles3 = {
5356
4381
  default: `bg-[var(--ds-bg-surface)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] ${GLASS_SURFACE}`,
5357
4382
  featured: [
@@ -5387,7 +4412,8 @@ function RadioIndicator({ checked, disabled }) {
5387
4412
  {
5388
4413
  className: cn(
5389
4414
  "h-2.5 w-2.5 rounded-[var(--ds-radius-full)] transition-transform duration-[var(--ds-transition-speed)]",
5390
- checked ? "bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)] scale-100" : "scale-0"
4415
+ // Sprint 25.14 A20: the radio's selected dot (lib/control-looks.ts)
4416
+ checked ? cn(CHECK_FILL, "scale-100") : "scale-0"
5391
4417
  )
5392
4418
  }
5393
4419
  )
@@ -5405,7 +4431,8 @@ function CheckboxIndicator({ checked, indeterminate, disabled }) {
5405
4431
  "rounded-[calc(var(--ds-radius-sm)*0.6)]",
5406
4432
  "border-[2px] transition-all duration-[var(--ds-transition-speed)]",
5407
4433
  disabled && "opacity-disabled",
5408
- filled ? "bg-[var(--ds-check-bg)] [background-image:var(--ds-gradient)] border-[var(--ds-check-border)]" : "bg-[var(--ds-input-bg)] border-[var(--ds-input-border)]"
4434
+ // Sprint 25.14 A20: the checkbox's checked fill (lib/control-looks.ts)
4435
+ filled ? cn(CHECK_FILL, "border-[var(--ds-check-border)]") : "bg-[var(--ds-input-bg)] border-[var(--ds-input-border)]"
5409
4436
  ),
5410
4437
  children: /* @__PURE__ */ jsx(
5411
4438
  "svg",
@@ -5433,13 +4460,14 @@ function ToggleIndicator({ checked, disabled }) {
5433
4460
  "border-[length:var(--ds-border-width)] rounded-[var(--ds-radius-full)]",
5434
4461
  "transition-colors duration-[var(--ds-transition-speed)]",
5435
4462
  disabled && "opacity-disabled",
5436
- checked ? "bg-[var(--ds-accent-primary)] border-transparent" : "bg-[var(--ds-bg-muted)] border-[var(--ds-border-default)]"
4463
+ // Sprint 25.14 A20: the Toggle's track and knob, on and off (lib/control-looks.ts)
4464
+ checked ? SWITCH_TRACK_ON : SWITCH_TRACK_OFF
5437
4465
  ),
5438
4466
  style: { width: 44, height: 24 },
5439
4467
  children: /* @__PURE__ */ jsx(
5440
4468
  "span",
5441
4469
  {
5442
- className: "pointer-events-none inline-block shrink-0 bg-[var(--ds-bg-surface)] rounded-[var(--ds-radius-full)] transition-transform",
4470
+ className: cn("pointer-events-none inline-block shrink-0 rounded-[var(--ds-radius-full)]", checked ? SWITCH_KNOB_ON : SWITCH_KNOB_OFF, SWITCH_KNOB_MOTION),
5443
4471
  style: {
5444
4472
  width: 16,
5445
4473
  height: 16,
@@ -5499,7 +4527,7 @@ var Card = forwardRef(function Card2({
5499
4527
  const handleKeyDown = isSelectable && !disabled ? (e) => {
5500
4528
  if (e.key === "Enter" || e.key === " ") {
5501
4529
  e.preventDefault();
5502
- onSelectedChange?.(!selected);
4530
+ if (!e.repeat) onSelectedChange?.(!selected);
5503
4531
  }
5504
4532
  } : isAction && !disabled ? (e) => {
5505
4533
  if (e.target !== e.currentTarget) return;
@@ -5516,13 +4544,84 @@ var Card = forwardRef(function Card2({
5516
4544
  } : void 0;
5517
4545
  const handleCheckableClick = (e) => {
5518
4546
  e.stopPropagation();
5519
- onSelectedChange?.(!selected);
4547
+ if (!disabled) onSelectedChange?.(!selected);
5520
4548
  };
5521
4549
  const isButton = isSelectable || isAction && !holdsControls;
5522
4550
  const role = selectType === "radio" ? "radio" : selectType === "checkbox" ? "checkbox" : selectType === "toggle" ? "switch" : isButton ? "button" : void 0;
5523
4551
  const ariaAttr = selectType === "radio" || selectType === "checkbox" || selectType === "toggle" ? { "aria-checked": !!selected } : isSelectable ? { "aria-pressed": !!selected } : {};
5524
4552
  const indicator = selectType === "radio" ? /* @__PURE__ */ jsx(RadioIndicator, { checked: !!selected, disabled }) : selectType === "checkbox" ? /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected, indeterminate, disabled }) : selectType === "toggle" ? /* @__PURE__ */ jsx(ToggleIndicator, { checked: !!selected, disabled }) : null;
5525
- const defaultCheckBadge = !hasSelectType && !checkable && selected && !loading ? /* @__PURE__ */ jsx("div", { className: "absolute top-ds-3 right-ds-3 w-5 h-5 rounded-[var(--ds-radius-full)] bg-[var(--ds-accent-primary)] flex items-center justify-center", children: /* @__PURE__ */ jsx(Icon, { name: "check", size: 12, className: "text-[var(--ds-text-on-primary)]" }) }) : null;
4553
+ const defaultCheckBadge = !hasSelectType && !checkable && selected && !loading ? /* @__PURE__ */ jsx("span", { "data-card-check": "", className: "w-5 h-5 rounded-[var(--ds-radius-full)] bg-[var(--ds-accent-primary)] flex items-center justify-center", children: /* @__PURE__ */ jsx(Icon, { name: "check", size: 12, className: "text-[var(--ds-text-on-primary)]" }) }) : null;
4554
+ const checkboxes = useRef(/* @__PURE__ */ new Set());
4555
+ const checkboxFocused = useRef(false);
4556
+ const checkboxRef = useCallback((node) => {
4557
+ if (!node) return;
4558
+ checkboxes.current.add(node);
4559
+ return () => {
4560
+ checkboxes.current.delete(node);
4561
+ };
4562
+ }, []);
4563
+ useLayoutEffect(() => {
4564
+ if (!checkboxFocused.current) return;
4565
+ const active = document.activeElement;
4566
+ if (active && active !== document.body) return;
4567
+ for (const node of checkboxes.current) {
4568
+ if (node.isConnected) {
4569
+ node.focus({ preventScroll: true });
4570
+ return;
4571
+ }
4572
+ }
4573
+ });
4574
+ const checkbox = checkable ? /* @__PURE__ */ jsx(
4575
+ "div",
4576
+ {
4577
+ ref: checkboxRef,
4578
+ "data-card-checkbox": "",
4579
+ className: cn(
4580
+ "rounded-[calc(var(--ds-radius-sm)*0.6)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
4581
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
4582
+ ),
4583
+ onClick: handleCheckableClick,
4584
+ role: "checkbox",
4585
+ "aria-checked": !!selected,
4586
+ "aria-disabled": disabled || void 0,
4587
+ ...inner.titleId ? { "aria-labelledby": inner.titleId } : { "aria-label": "Select" },
4588
+ tabIndex: 0,
4589
+ onFocus: () => {
4590
+ checkboxFocused.current = true;
4591
+ },
4592
+ onBlur: (e) => {
4593
+ const node = e.currentTarget;
4594
+ queueMicrotask(() => {
4595
+ if (node.isConnected) checkboxFocused.current = false;
4596
+ });
4597
+ },
4598
+ onKeyDown: (e) => {
4599
+ if (e.key !== " ") return;
4600
+ e.preventDefault();
4601
+ e.stopPropagation();
4602
+ if (!e.repeat && !disabled) onSelectedChange?.(!selected);
4603
+ },
4604
+ children: /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected, disabled })
4605
+ }
4606
+ ) : null;
4607
+ const check = indicator ?? defaultCheckBadge;
4608
+ const headers = useRef([]);
4609
+ const [owner, setOwner] = useState(null);
4610
+ const [guess, setGuess] = useState(() => holdsHeader(children));
4611
+ const register = useCallback((header) => {
4612
+ headers.current = [...headers.current, header];
4613
+ setOwner(headers.current[0]);
4614
+ return () => {
4615
+ headers.current = headers.current.filter((h) => h !== header);
4616
+ setOwner(headers.current[0] ?? null);
4617
+ setGuess(false);
4618
+ };
4619
+ }, []);
4620
+ useLayoutEffect(() => {
4621
+ if (headers.current.length === 0) setGuess(false);
4622
+ }, []);
4623
+ const hasHeader = owner !== null || guess;
4624
+ const rowParts = { checkbox, check, owner, register };
5526
4625
  return /* @__PURE__ */ jsxs(
5527
4626
  "div",
5528
4627
  {
@@ -5553,32 +4652,22 @@ var Card = forwardRef(function Card2({
5553
4652
  ...ariaAttr,
5554
4653
  ...props,
5555
4654
  children: [
5556
- defaultCheckBadge,
5557
- indicator && /* @__PURE__ */ jsx("div", { className: "absolute top-ds-3 right-ds-3 z-[1]", children: indicator }),
5558
- checkable && /* @__PURE__ */ jsx(
5559
- "div",
5560
- {
5561
- className: "absolute top-ds-3 left-ds-3 z-[1] cursor-pointer",
5562
- onClick: handleCheckableClick,
5563
- role: "checkbox",
5564
- "aria-checked": !!selected,
5565
- ...inner.titleId ? { "aria-labelledby": inner.titleId } : { "aria-label": "Select" },
5566
- tabIndex: 0,
5567
- onKeyDown: (e) => {
5568
- if (e.key === "Enter" || e.key === " ") {
5569
- e.preventDefault();
5570
- e.stopPropagation();
5571
- onSelectedChange?.(!selected);
5572
- }
5573
- },
5574
- children: /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected })
5575
- }
5576
- ),
5577
- /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardActionCtx, { value: action, children: /* @__PURE__ */ jsx(CardCtx, { value: checkable, children: loading ? (
4655
+ !loading && !hasHeader && checkbox && /* @__PURE__ */ jsxs("div", { "data-card-header-row": "", className: "flex items-center gap-ds-2 px-ds-5 pt-ds-4", children: [
4656
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "grow" }),
4657
+ check && /* @__PURE__ */ jsx("span", { "data-card-selection": "", className: "shrink-0 flex", children: check }),
4658
+ checkbox
4659
+ ] }),
4660
+ !loading && !hasHeader && !checkbox && check && /* @__PURE__ */ jsx("div", { "data-card-selection": "", className: "absolute top-ds-3 right-ds-3 z-[1] flex", children: check }),
4661
+ /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardActionCtx, { value: action, children: /* @__PURE__ */ jsx(CardRowCtx, { value: rowParts, children: loading ? (
5578
4662
  // Round 15 (the owner's 52.1): a placeholder stands where the content will stand — inside the card's
5579
4663
  // content inset (CardContent's padding), a title-height line then two body lines — never flush against the border.
5580
- /* @__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: [
5581
- /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-5 w-1/3" }),
4664
+ // Its header holds a badge-sized block (A2), so a grid of item cards keeps its columns when they load.
4665
+ /* @__PURE__ */ jsx("div", { "data-card-loading": "", "aria-busy": "true", className: "px-ds-5 py-ds-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-ds-3", children: [
4666
+ /* @__PURE__ */ jsxs("div", { "data-card-header-row": "", className: "flex items-center gap-ds-2", children: [
4667
+ checkbox,
4668
+ /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-5 w-1/3" }),
4669
+ /* @__PURE__ */ jsx("div", { "data-header-actions": "", className: "contents", children: /* @__PURE__ */ jsx("div", { "data-gs-badge": "", className: "shrink-0", children: /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-5 w-14 rounded-[var(--ds-radius-full)]" }) }) })
4670
+ ] }),
5582
4671
  /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-full" }),
5583
4672
  /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-2/3" })
5584
4673
  ] }) })
@@ -5587,60 +4676,95 @@ var Card = forwardRef(function Card2({
5587
4676
  }
5588
4677
  );
5589
4678
  });
5590
- var CardHeader = forwardRef(function CardHeader2({ className, children, actions, icon, menuItems, ...props }, ref) {
5591
- const isCheckable = useContext(CardCtx);
5592
- const hasActions = actions || menuItems;
5593
- const pl = isCheckable ? "pl-ds-10" : "pl-ds-5";
5594
- const actionsBlock = hasActions ? /* @__PURE__ */ jsxs("div", { className: HEADER_LAYOUT.actions, children: [
5595
- actions,
5596
- menuItems && /* @__PURE__ */ jsx(
5597
- DropdownMenu,
5598
- {
5599
- trigger: /* @__PURE__ */ jsx(
5600
- "button",
4679
+ function partsOf(children) {
4680
+ const parts = [];
4681
+ const open = (node) => Children.toArray(node).forEach((child) => {
4682
+ if (isValidElement(child) && child.type === Fragment$1) open(child.props.children);
4683
+ else if (child !== "") parts.push(child);
4684
+ });
4685
+ open(children);
4686
+ return parts;
4687
+ }
4688
+ function splitTitle(children) {
4689
+ const parts = partsOf(children);
4690
+ const at = parts.findIndex((p) => isValidElement(p) && p.type === CardTitle);
4691
+ if (at < 0) return { lead: [], title: parts[0] ?? null, rest: parts.slice(1) };
4692
+ return { lead: parts.slice(0, at), title: parts[at], rest: parts.slice(at + 1) };
4693
+ }
4694
+ var TITLE_FLOOR = "basis-[calc(var(--ds-font-size-lg)*4.5)]";
4695
+ var TITLE_ROW = cn(
4696
+ "flex flex-wrap items-start justify-end gap-x-ds-2 gap-y-ds-2 min-w-0 grow basis-0",
4697
+ "[&>[data-header-actions]>*]:order-5",
4698
+ "[&>[data-header-actions]>:has([data-gs-badge]):not(:has(a,button,input,select,textarea,[role=button],[tabindex]:not([tabindex='-1'])))]:order-2",
4699
+ "[&>[data-header-actions]>:has([aria-haspopup])]:order-6"
4700
+ );
4701
+ var CardHeader = forwardRef(function CardHeader2(allProps, ref) {
4702
+ const { className, children, actions, icon, menuItems, ...props } = allProps;
4703
+ const row = useContext(CardRowCtx);
4704
+ const [self] = useState(() => ({}));
4705
+ const register = row?.register;
4706
+ useLayoutEffect(() => register?.(self), [register, self]);
4707
+ const owns = !!row && row.owner === self;
4708
+ const iconBlock = icon ? (
4709
+ // the icon stays where it was, a tile above the title (28 of 66 headers with actions have one)
4710
+ /* @__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", children: /* @__PURE__ */ jsx(Icon, { name: icon, size: 20, className: "text-[var(--ds-text-secondary)]" }) })
4711
+ ) : null;
4712
+ const menu = menuItems ? /* @__PURE__ */ jsx(
4713
+ DropdownMenu,
4714
+ {
4715
+ trigger: /* @__PURE__ */ jsx(
4716
+ "button",
4717
+ {
4718
+ type: "button",
4719
+ "aria-label": "More actions",
4720
+ className: cn(
4721
+ "inline-flex items-center justify-center h-8 w-8 rounded-[var(--ds-radius-md)]",
4722
+ "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
4723
+ "transition-colors duration-[var(--ds-transition-speed)]"
4724
+ ),
4725
+ onClick: (e) => e.stopPropagation(),
4726
+ children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: 16 })
4727
+ }
4728
+ ),
4729
+ items: menuItems
4730
+ }
4731
+ ) : null;
4732
+ const band = (order) => cn("shrink-0 self-start flex items-center", TITLE_LINE, order);
4733
+ const checkbox = owns && row.checkbox ? /* @__PURE__ */ jsx("span", { className: band("order-5"), children: row.checkbox }) : null;
4734
+ const check = owns && row.check ? /* @__PURE__ */ jsx("span", { "data-card-selection": "", className: band("order-4"), children: row.check }) : null;
4735
+ const menuPart = menu ? /* @__PURE__ */ jsx("span", { className: band("order-6"), children: menu }) : null;
4736
+ const { lead, title, rest } = splitTitle(children);
4737
+ const leadBlock = lead.length > 0 ? /* @__PURE__ */ jsx("div", { "data-card-lead": "", className: "flex flex-col space-y-ds-1.5 min-w-0 grow", children: lead }) : null;
4738
+ const topLine = iconBlock ?? leadBlock;
4739
+ const ownBlock = title !== null && !(isValidElement(title) && title.type === CardTitle);
4740
+ const actionsSlot = "actions" in allProps ? /* @__PURE__ */ jsx("div", { "data-header-actions": "", className: "contents", children: partsOf(actions).map((item, i) => /* @__PURE__ */ jsx("span", { className: cn("flex items-center shrink-0", TITLE_LINE), children: item }, isValidElement(item) && item.key !== null ? item.key : i)) }) : null;
4741
+ return /* @__PURE__ */ jsxs("div", { ref, "data-card-header": "", className: cn("flex flex-col px-ds-5 pt-ds-4 last:pb-ds-4 min-w-0", className), ...props, children: [
4742
+ topLine && /* @__PURE__ */ jsxs("div", { "data-card-icon-row": iconBlock ? "" : void 0, "data-card-lead-row": iconBlock ? void 0 : "", className: cn("flex items-start gap-ds-2", iconBlock ? "mb-ds-2.5" : "mb-ds-1.5"), children: [
4743
+ topLine,
4744
+ iconBlock && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "grow -ml-ds-2" }),
4745
+ check,
4746
+ checkbox,
4747
+ menuPart
4748
+ ] }),
4749
+ iconBlock && leadBlock && /* @__PURE__ */ jsx("div", { className: "flex mb-ds-1.5", children: leadBlock }),
4750
+ /* @__PURE__ */ jsxs("div", { "data-card-header-row": "", className: "flex items-start gap-ds-2 min-w-0", children: [
4751
+ /* @__PURE__ */ jsxs("div", { "data-card-title-line": "", className: TITLE_ROW, children: [
4752
+ title && /* @__PURE__ */ jsx(
4753
+ "div",
5601
4754
  {
5602
- type: "button",
5603
- "aria-label": "More actions",
5604
- className: cn(
5605
- "inline-flex items-center justify-center h-8 w-8 rounded-[var(--ds-radius-md)]",
5606
- "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
5607
- "transition-colors duration-[var(--ds-transition-speed)]"
5608
- ),
5609
- onClick: (e) => e.stopPropagation(),
5610
- children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: 16 })
4755
+ "data-card-title-row": "",
4756
+ className: cn("order-1 min-w-0 me-auto", TITLE_FLOOR, ownBlock ? "grow" : "grow-[999] max-w-max"),
4757
+ children: /* @__PURE__ */ jsx(HeaderRowCtx, { value: true, children: title })
5611
4758
  }
5612
4759
  ),
5613
- items: menuItems
5614
- }
5615
- )
5616
- ] }) : null;
5617
- 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;
5618
- const parts = Children.toArray(children);
5619
- const titleLeads = !!hasActions && !iconBlock && !menuItems && actionsAreBadges(actions) && parts.length > 0 && isValidElement(parts[0]) && parts[0].type === CardTitle;
5620
- const leadRowRef = useRef(null);
5621
- const leadFloorFits = useTitleFloorFits(leadRowRef, titleLeads);
5622
- if (hasActions) {
5623
- if (titleLeads) {
5624
- const below = parts.slice(1);
5625
- return /* @__PURE__ */ jsxs("div", { ref, className: cn(pl, "pr-ds-5 pt-ds-4 last:pb-ds-4", className), ...props, children: [
5626
- /* @__PURE__ */ jsxs("div", { ref: leadRowRef, className: HEADER_LAYOUT.row, children: [
5627
- /* @__PURE__ */ jsx("div", { "data-card-title-row": "", className: cn(HEADER_LAYOUT.titleLeads, !leadFloorFits && HEADER_LAYOUT.floorOff), children: parts[0] }),
5628
- actionsBlock
5629
- ] }),
5630
- below.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("flex flex-col space-y-ds-1.5 break-words", HEADER_LAYOUT.below), children: below })
5631
- ] });
5632
- }
5633
- 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: HEADER_LAYOUT.row, children: [
5634
- /* @__PURE__ */ jsxs("div", { className: HEADER_LAYOUT.block, children: [
5635
- iconBlock,
5636
- children
4760
+ actionsSlot,
4761
+ !ownBlock && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "order-3 grow basis-0 -ml-ds-2" })
5637
4762
  ] }),
5638
- actionsBlock
5639
- ] }) });
5640
- }
5641
- return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex flex-col space-y-ds-1.5 pr-ds-5 pt-ds-4 last:pb-ds-4", pl, className), ...props, children: [
5642
- iconBlock,
5643
- children
4763
+ !topLine && check,
4764
+ !topLine && checkbox,
4765
+ !topLine && menuPart
4766
+ ] }),
4767
+ rest.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col space-y-ds-1.5 break-words mt-ds-1.5 min-w-0", children: rest })
5644
4768
  ] });
5645
4769
  });
5646
4770
  var cardLineClampClasses = {
@@ -5651,7 +4775,10 @@ var cardLineClampClasses = {
5651
4775
  5: "line-clamp-5",
5652
4776
  6: "line-clamp-6"
5653
4777
  };
5654
- var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2, id, ...props }, forwardedRef) {
4778
+ var WHOLE_ON_KEYBOARD = "[[data-card]:has(:focus-visible)_&]:whitespace-normal [[data-card]:has(:focus-visible)_&]:line-clamp-none";
4779
+ var CardTitle = forwardRef(function CardTitle2({ className, children, lines: linesProp, id, ...props }, forwardedRef) {
4780
+ const inHeaderRow = useContext(HeaderRowCtx);
4781
+ const lines = linesProp ?? (inHeaderRow ? 1 : 2);
5655
4782
  const action = useContext(CardActionCtx);
5656
4783
  const generatedId = useId();
5657
4784
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines, disabled: lines === 0 });
@@ -5667,7 +4794,7 @@ var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2,
5667
4794
  id: id ?? generatedId,
5668
4795
  "data-card-title": "",
5669
4796
  ...lines === 1 ? { "data-title-one-line": "" } : {},
5670
- className: cn(TITLE_TEXT, !action && lines > 0 && lineClampClass, action && lines === 1 && "text-ellipsis", className),
4797
+ className: cn(TITLE_TEXT, !action && lines > 0 && [lineClampClass, WHOLE_ON_KEYBOARD], action && lines === 1 && "text-ellipsis", className),
5671
4798
  ...props,
5672
4799
  children: action ? (
5673
4800
  // The card's action when the card holds controls of its own (Sprint 25.7): a real button, which a click
@@ -5684,14 +4811,13 @@ var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2,
5684
4811
  "block w-full text-left cursor-pointer [text-overflow:inherit] focus-visible:outline-none disabled:cursor-not-allowed",
5685
4812
  "after:absolute after:inset-0 after:rounded-[var(--ds-radius-md)] after:pointer-events-none focus-visible:after:shadow-[var(--ds-shadow-focus)]"
5686
4813
  ),
5687
- children: /* @__PURE__ */ jsx("span", { ref: (node) => overflowRef(lines > 0 ? node : null), className: cn("block [text-overflow:inherit]", lines > 0 && lineClampClass), children })
4814
+ children: /* @__PURE__ */ jsx("span", { ref: (node) => overflowRef(lines > 0 ? node : null), className: cn("block [text-overflow:inherit]", lines > 0 && [lineClampClass, WHOLE_ON_KEYBOARD]), children })
5688
4815
  }
5689
4816
  )
5690
4817
  ) : children
5691
4818
  }
5692
4819
  );
5693
- if (!isTruncated || lines === 0) return heading;
5694
- return /* @__PURE__ */ jsx(Tooltip, { content: children, children: heading });
4820
+ return /* @__PURE__ */ jsx(Tooltip, { content: children, disabled: !isTruncated, children: heading });
5695
4821
  });
5696
4822
  var CardDescription = forwardRef(function CardDescription2({ className, children, lines = 2, ...props }, forwardedRef) {
5697
4823
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines, disabled: lines === 0 });
@@ -5709,23 +4835,20 @@ var CardDescription = forwardRef(function CardDescription2({ className, children
5709
4835
  children
5710
4836
  }
5711
4837
  );
5712
- if (!isTruncated || lines === 0) return el;
5713
- return /* @__PURE__ */ jsx(Tooltip, { content: children, children: el });
4838
+ return /* @__PURE__ */ jsx(Tooltip, { content: children, disabled: !isTruncated, children: el });
5714
4839
  });
5715
4840
  var CardContent = forwardRef(function CardContent2({ className, children, ...props }, ref) {
5716
- const isCheckable = useContext(CardCtx);
5717
- return /* @__PURE__ */ jsx("div", { ref, className: cn("flex-1 pr-ds-5 py-ds-4", isCheckable ? "pl-ds-10" : "pl-ds-5", className), ...props, children });
4841
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("flex-1 px-ds-5 py-ds-4", className), ...props, children });
5718
4842
  });
5719
4843
  var CardFooter = forwardRef(function CardFooter2({ className, children, ...props }, ref) {
5720
- const isCheckable = useContext(CardCtx);
5721
4844
  return (
5722
4845
  // a footer that holds only buttons puts them at the end (round 11: a lone Save sat on the left); text keeps the start
5723
- /* @__PURE__ */ jsx("div", { ref, className: cn("flex items-center gap-ds-2 pr-ds-5 pb-ds-4 pt-ds-2 mt-auto min-w-0 [&:not(:has(>:not(button)))]:justify-end", isCheckable ? "pl-ds-10" : "pl-ds-5", className), ...props, children })
4846
+ /* @__PURE__ */ jsx("div", { ref, className: cn("flex items-center gap-ds-2 px-ds-5 pb-ds-4 pt-ds-2 mt-auto min-w-0 [&:not(:has(>:not(button)))]:justify-end", className), ...props, children })
5724
4847
  );
5725
4848
  });
5726
4849
  function TruncLabel({ children }) {
5727
4850
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
5728
- const el = /* @__PURE__ */ jsx("span", { ref, className: "text-xs font-medium text-[var(--ds-text-muted)] uppercase tracking-wider truncate min-w-0", children });
4851
+ const el = /* @__PURE__ */ jsx("span", { ref, className: "block text-xs font-medium text-[var(--ds-text-muted)] uppercase tracking-wider truncate min-w-0", children });
5729
4852
  if (!isTruncated) return el;
5730
4853
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: el });
5731
4854
  }
@@ -5770,13 +4893,15 @@ function StatsCard({ value, label, trend, icon, layout = "card", interactive, on
5770
4893
  } : void 0,
5771
4894
  children: [
5772
4895
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-ds-2 min-w-0", children: [
5773
- /* @__PURE__ */ jsx(TruncLabel, { children: label }),
4896
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
4897
+ /* @__PURE__ */ jsx(TruncLabel, { children: label }),
4898
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-ds-2 mt-ds-2 flex-wrap", children: [
4899
+ /* @__PURE__ */ jsx("span", { className: "font-[family-name:var(--ds-font-display)] text-2xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] leading-none tabular-nums", children: value }),
4900
+ trend && /* @__PURE__ */ jsx(TrendBadge, { trend })
4901
+ ] })
4902
+ ] }),
5774
4903
  icon && /* @__PURE__ */ jsx("div", { className: "shrink-0 flex items-center justify-center w-8 h-8 rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-muted)]", children: /* @__PURE__ */ jsx(Icon, { name: icon, size: 16, className: "text-[var(--ds-text-muted)]" }) })
5775
4904
  ] }),
5776
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-ds-2 mt-ds-2 flex-wrap", children: [
5777
- /* @__PURE__ */ jsx("span", { className: "font-[family-name:var(--ds-font-display)] text-2xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] leading-none tabular-nums", children: value }),
5778
- trend && /* @__PURE__ */ jsx(TrendBadge, { trend })
5779
- ] }),
5780
4905
  children && /* @__PURE__ */ jsx("div", { className: "mt-ds-3 text-xs text-[var(--ds-text-muted)]", children })
5781
4906
  ]
5782
4907
  }
@@ -5836,6 +4961,9 @@ function DataList({ items, columns = 1, className }) {
5836
4961
  );
5837
4962
  }
5838
4963
  var COUNT_FROM_PAGE = [
4964
+ // one card counts too: a lone card fell back to React's count, which counts components that draw nothing, and a grid
4965
+ // with one card inside another took the outer grid's count (the review of 30 September)
4966
+ "[&:has(>:only-child)]:[--gs-cards:1]",
5839
4967
  "[&:has(>:nth-child(2):last-child)]:[--gs-cards:2]",
5840
4968
  "[&:has(>:nth-child(3):last-child)]:[--gs-cards:3]",
5841
4969
  "[&:has(>:nth-child(4):last-child)]:[--gs-cards:4]",
@@ -5848,6 +4976,7 @@ var COUNT_FROM_PAGE = [
5848
4976
  "[&:has(>:nth-child(11):last-child)]:[--gs-cards:11]",
5849
4977
  "[&:has(>:nth-child(12))]:[--gs-cards:12]"
5850
4978
  ].join(" ");
4979
+ var MIN_FROM_HEADERS = "[--gs-card-min:14rem] has-[[data-header-actions]]:[--gs-card-min:20rem]";
5851
4980
  function countCards(children) {
5852
4981
  let n = 0;
5853
4982
  Children.forEach(children, (child) => {
@@ -5856,14 +4985,19 @@ function countCards(children) {
5856
4985
  });
5857
4986
  return n;
5858
4987
  }
5859
- function CardGrid({ minWidth = "14rem", maxColumns = 4, compare = false, gap = "var(--ds-space-4)", className, style, children, ref, ...props }) {
4988
+ function CardGrid({ minWidth, maxColumns = 4, compare = false, gap = "var(--ds-space-4)", fill: fill2 = true, className, style, children, ref, ...props }) {
4989
+ useFramesItself();
5860
4990
  const most = Math.max(1, Math.floor(maxColumns));
5861
- const n = compare ? "var(--gs-cards, var(--gs-cards-counted))" : String(most);
5862
- const share = `calc((100% - (${n} - 1) * var(--gs-card-gap)) / ${n})`;
5863
- const column = compare ? `max(${share}, min(100%, calc((${n} * ${minWidth} + (${n} - 1) * var(--gs-card-gap) - 100%) * 999)))` : `max(min(${minWidth}, 100%), ${share})`;
4991
+ const drawn = "var(--gs-cards, var(--gs-cards-counted))";
4992
+ const min = "var(--gs-card-min)";
4993
+ const shareOf = (n) => `calc((100% - (${n} - 1) * var(--gs-card-gap)) / ${n})`;
4994
+ const collection = `max(min(${min}, 100%), ${shareOf(String(most))})`;
4995
+ const column = compare ? `max(${shareOf(drawn)}, min(100%, calc((${drawn} * ${min} + (${drawn} - 1) * var(--gs-card-gap) - 100%) * 999)))` : fill2 ? `max(${shareOf(`max(${drawn}, 2)`)}, ${collection})` : collection;
5864
4996
  const template = {
5865
4997
  "--gs-card-gap": gap,
5866
- ...compare ? { "--gs-cards-counted": Math.max(1, countCards(children)) } : {},
4998
+ // A2: a width passed wins; a comparison keeps 14rem; a collection's comes from its headers (MIN_FROM_HEADERS)
4999
+ ...minWidth ? { "--gs-card-min": minWidth } : compare ? { "--gs-card-min": "14rem" } : {},
5000
+ ...compare || fill2 ? { "--gs-cards-counted": Math.max(1, countCards(children)) } : {},
5867
5001
  gap: "var(--gs-card-gap)",
5868
5002
  gridTemplateColumns: `repeat(auto-fill, minmax(${column}, 1fr))`,
5869
5003
  // a comparison's cards share their rows: the headers, the contents and the footers line up across the row
@@ -5876,7 +5010,8 @@ function CardGrid({ minWidth = "14rem", maxColumns = 4, compare = false, gap = "
5876
5010
  "data-card-grid": compare ? "compare" : "",
5877
5011
  className: cn(
5878
5012
  "grid",
5879
- compare && COUNT_FROM_PAGE,
5013
+ !minWidth && !compare && MIN_FROM_HEADERS,
5014
+ (compare || fill2) && COUNT_FROM_PAGE,
5880
5015
  // each card spans its three rows and lays its parts in them; anything else just fills them
5881
5016
  compare && "[&>*]:row-span-3 [&>[data-card]]:grid [&>[data-card]]:grid-rows-subgrid [&>[data-card]]:gap-y-0",
5882
5017
  className
@@ -6160,68 +5295,41 @@ function Accordion({ items, type = "single", defaultOpen = [], open: controlledO
6160
5295
  ] }, i);
6161
5296
  }) });
6162
5297
  }
6163
- var EDGE_FADE = 24;
6164
- var NONE = { left: false, right: false, vertical: false, gutterRight: 0, gutterBottom: 0 };
6165
- var same = (a, b) => a.left === b.left && a.right === b.right && a.vertical === b.vertical && a.gutterRight === b.gutterRight && a.gutterBottom === b.gutterBottom;
6166
- function useScrollEdges(options = {}) {
6167
- const { enabled = true, fade = EDGE_FADE } = options;
6168
- const elRef = useRef(null);
6169
- const observer = useRef(null);
6170
- const borders = useRef({ x: 0, y: 0 });
6171
- const [state, setState] = useState(NONE);
6172
- const measure = useCallback(() => {
6173
- const el = elRef.current;
6174
- if (!el) return;
6175
- const next = {
6176
- left: el.scrollLeft > 1,
6177
- right: el.scrollLeft + el.clientWidth < el.scrollWidth - 1,
6178
- vertical: el.scrollHeight > el.clientHeight + 1,
6179
- gutterRight: Math.max(0, el.offsetWidth - el.clientWidth - borders.current.x),
6180
- gutterBottom: Math.max(0, el.offsetHeight - el.clientHeight - borders.current.y)
5298
+ var HEADER_LAYOUT = {
5299
+ row: "flex flex-wrap items-start justify-between gap-ds-4",
5300
+ block: "flex flex-col space-y-ds-1.5 grow basis-1/2 break-words max-w-full has-[[data-title-one-line]]:min-w-0",
5301
+ titleLeads: "grow basis-1/2 max-w-max break-words has-[[data-title-one-line]]:min-w-0",
5302
+ floorOff: "min-w-0",
5303
+ actions: "flex items-center gap-ds-2 shrink-0 ml-auto",
5304
+ below: "mt-ds-1.5"
5305
+ };
5306
+ function useTitleFloorFits(rowRef, active) {
5307
+ const [fits, setFits] = useState(true);
5308
+ useLayoutEffect(() => {
5309
+ const row = rowRef.current;
5310
+ if (!active || !row) return;
5311
+ const measure = () => {
5312
+ const title = row.querySelector("h1, h2, h3, h4, h5, h6");
5313
+ if (!title) return;
5314
+ const width = title.style.width;
5315
+ title.style.width = "min-content";
5316
+ const longest = title.getBoundingClientRect().width;
5317
+ title.style.width = width;
5318
+ if (longest === 0) return;
5319
+ const next = longest <= row.clientWidth + 0.5;
5320
+ setFits((was) => was === next ? was : next);
6181
5321
  };
6182
- setState((prev) => same(prev, next) ? prev : next);
6183
- }, []);
6184
- const ref = useCallback((el) => {
6185
- observer.current?.disconnect();
6186
- observer.current = null;
6187
- elRef.current = el;
6188
- if (!el || !enabled) return;
6189
- const style = getComputedStyle(el);
6190
- const px = (v) => parseFloat(v) || 0;
6191
- borders.current = { x: px(style.borderLeftWidth) + px(style.borderRightWidth), y: px(style.borderTopWidth) + px(style.borderBottomWidth) };
6192
5322
  measure();
6193
5323
  if (typeof ResizeObserver === "undefined") return;
6194
- const o = new ResizeObserver(measure);
6195
- o.observe(el);
6196
- for (const child of Array.from(el.children)) o.observe(child);
6197
- observer.current = o;
6198
- }, [enabled, measure]);
6199
- const shown = enabled && (state.left || state.right);
6200
- const { gutterRight: gr, gutterBottom: gb } = state;
6201
- const fadeLayer = `linear-gradient(to right, ${state.left ? `transparent 0, black ${fade}px` : "black 0"}, ${state.right ? `black calc(100% - ${gr + fade}px), transparent calc(100% - ${gr}px)` : "black 100%"})`;
6202
- const gutters = [gb > 0 && { size: `100% ${gb}px`, position: "0 100%" }, gr > 0 && { size: `${gr}px 100%`, position: "100% 0" }].filter(Boolean);
6203
- const mask = shown ? {
6204
- image: [fadeLayer, ...gutters.map(() => "linear-gradient(black, black)")].join(", "),
6205
- size: ["100% 100%", ...gutters.map((g) => g.size)].join(", "),
6206
- position: ["0 0", ...gutters.map((g) => g.position)].join(", ")
6207
- } : void 0;
6208
- return {
6209
- /** The scroller's ref (a callback). */
6210
- ref,
6211
- /** The scroller itself, for code that moves it. */
6212
- elRef,
6213
- measure,
6214
- /** Content hides past a side edge: the scroller moves sideways, and its edges fade. */
6215
- overflowing: shown,
6216
- /** It scrolls either way, sideways or up and down: a keyboard needs to reach it. */
6217
- scrollable: enabled && (state.left || state.right || state.vertical),
6218
- edges: { left: state.left, right: state.right },
6219
- /** For the scroller: `data-fade` names the faded edges ("left", "right" or "left right"), the style masks them. */
6220
- fadeProps: {
6221
- "data-fade": shown ? [state.left && "left", state.right && "right"].filter(Boolean).join(" ") : void 0,
6222
- style: mask ? { maskImage: mask.image, WebkitMaskImage: mask.image, maskSize: mask.size, WebkitMaskSize: mask.size, maskPosition: mask.position, WebkitMaskPosition: mask.position, maskRepeat: "no-repeat", WebkitMaskRepeat: "no-repeat" } : void 0
6223
- }
6224
- };
5324
+ const observer = new ResizeObserver(measure);
5325
+ observer.observe(row);
5326
+ return () => observer.disconnect();
5327
+ }, [rowRef, active]);
5328
+ return fits;
5329
+ }
5330
+ function actionsAreBadges(actions) {
5331
+ const items = Children.toArray(actions);
5332
+ return items.length > 0 && items.every((item) => isValidElement(item) && (item.type === Fragment$1 ? actionsAreBadges(item.props.children) : item.type === Badge));
6225
5333
  }
6226
5334
  function TruncCell({ children, lines }) {
6227
5335
  const { ref, isTruncated } = useTextOverflow({ lines });
@@ -6237,6 +5345,95 @@ var FRAME_CLASSES = cn(
6237
5345
  GLASS_SURFACE,
6238
5346
  "bg-[var(--ds-bg-surface)]"
6239
5347
  );
5348
+ var PINNED_BACKGROUND = "linear-gradient(var(--gs-row-tint, transparent), var(--gs-row-tint, transparent)), linear-gradient(var(--gs-pinned-surface), var(--gs-pinned-surface)), var(--gs-pinned-canvas)";
5349
+ var PINNED_LAYERS = cn(
5350
+ "[--gs-pinned-surface:var(--ds-bg-surface)] [--gs-pinned-canvas:var(--ds-bg-canvas)]",
5351
+ "supports-[color:oklch(from_red_l_c_h)]:[--gs-pinned-surface:oklch(from_var(--ds-bg-surface)_l_c_h_/_max(alpha,min(1,alpha*1000)))]",
5352
+ "supports-[color:oklch(from_red_l_c_h)]:[--gs-pinned-canvas:oklch(from_var(--ds-bg-canvas)_l_c_h_/_1)]"
5353
+ );
5354
+ var PIN_SHADOW = "oklch(0 0 0 / 0.18)";
5355
+ var ROW_CONTROL = `${FOCUSABLE}, label, [role="button"], [role="checkbox"], [role="switch"], [role="menuitem"], [role="link"]`;
5356
+ function clickIsRows(e) {
5357
+ const target = e.target;
5358
+ if (!(target instanceof Element) || !e.currentTarget.contains(target)) return false;
5359
+ const selection = typeof window !== "undefined" ? window.getSelection() : null;
5360
+ if (selection && !selection.isCollapsed && selection.anchorNode && e.currentTarget.contains(selection.anchorNode)) return false;
5361
+ const control = target.closest(ROW_CONTROL);
5362
+ return !control || control === e.currentTarget || !e.currentTarget.contains(control);
5363
+ }
5364
+ function sortableValue(v) {
5365
+ if (v === null || v === void 0 || v === "") return void 0;
5366
+ if (v instanceof Date) return v.getTime();
5367
+ if (typeof v === "string" || typeof v === "number") return v;
5368
+ if (typeof v === "boolean") return v ? 1 : 0;
5369
+ return isValidElement(v) || typeof v === "object" ? null : String(v);
5370
+ }
5371
+ var COLLATOR = new Intl.Collator(void 0, { numeric: true });
5372
+ function compareValues(a, b) {
5373
+ if (typeof a === "number" && typeof b === "number") return a - b;
5374
+ return COLLATOR.compare(String(a), String(b));
5375
+ }
5376
+ var UNIT_SCALE = {
5377
+ "": 1,
5378
+ "%": 1,
5379
+ b: 1,
5380
+ byte: 1,
5381
+ bytes: 1,
5382
+ kb: 1e3,
5383
+ kib: 1024,
5384
+ mb: 1e6,
5385
+ mib: 1024 ** 2,
5386
+ gb: 1e9,
5387
+ gib: 1024 ** 3,
5388
+ tb: 1e12,
5389
+ tib: 1024 ** 4,
5390
+ pb: 1e15,
5391
+ pib: 1024 ** 5,
5392
+ ms: 1,
5393
+ s: 1e3,
5394
+ sec: 1e3,
5395
+ min: 6e4,
5396
+ h: 36e5,
5397
+ hr: 36e5,
5398
+ hrs: 36e5,
5399
+ g: 1,
5400
+ kg: 1e3,
5401
+ m: 1,
5402
+ km: 1e3,
5403
+ "\xB0c": 1,
5404
+ "\xB0f": 1
5405
+ };
5406
+ function numberOf(text) {
5407
+ const m = /^\s*([-\u2212])?\s*[$€£¥]?\s*([-\u2212])?\s*(\d[\d,]*\.?\d*|\.\d+)\s*(%|°[CF]|[a-z]*)\s*$/i.exec(text);
5408
+ const scale = m ? UNIT_SCALE[m[4].toLowerCase()] : void 0;
5409
+ if (!m || scale === void 0) return void 0;
5410
+ const n = parseFloat(m[3].replace(/,/g, ""));
5411
+ return Number.isFinite(n) ? (m[1] || m[2] ? -1 : 1) * n * scale : void 0;
5412
+ }
5413
+ function timeOf(text) {
5414
+ const t = Date.parse(text);
5415
+ return Number.isNaN(t) ? void 0 : t;
5416
+ }
5417
+ function rowIdsOf(data, field) {
5418
+ const seen = /* @__PURE__ */ new Set();
5419
+ let repeated = null;
5420
+ const ids = data.map((row, index) => {
5421
+ const v = row[field];
5422
+ if (typeof v !== "string" && typeof v !== "number") return index;
5423
+ if (!seen.has(String(v))) {
5424
+ seen.add(String(v));
5425
+ return v;
5426
+ }
5427
+ repeated ??= String(v);
5428
+ return `${v}~${index}`;
5429
+ });
5430
+ return { ids, repeated };
5431
+ }
5432
+ function rowMarks(row, index, field, id) {
5433
+ const v = row[field];
5434
+ return typeof v === "string" || typeof v === "number" ? { "data-row-key": String(id) } : { "data-row-index": String(index) };
5435
+ }
5436
+ var idPart = (id) => String(id).replace(/\s+/g, "_");
6240
5437
  function TableHeading({ title, description, actions, titleId, descriptionId, inset }) {
6241
5438
  const heading = /* @__PURE__ */ jsx("h3", { id: titleId, className: TITLE_CLASSES, children: title });
6242
5439
  const actionsBlock = actions ? /* @__PURE__ */ jsx("div", { className: HEADER_LAYOUT.actions, children: actions }) : null;
@@ -6258,8 +5455,13 @@ function TableHeading({ title, description, actions, titleId, descriptionId, ins
6258
5455
  descriptionText(HEADER_LAYOUT.below)
6259
5456
  ] }) });
6260
5457
  }
5458
+ var NO_PINS = { start: [], end: [], nameFits: false, frame: 0, panel: null };
5459
+ var samePins = (a, b) => a.nameFits === b.nameFits && a.frame === b.frame && a.start.join() === b.start.join() && a.end.join() === b.end.join() && a.panel?.join() === b.panel?.join();
5460
+ function PanelContent({ children }) {
5461
+ return /* @__PURE__ */ jsx(SurfaceBoundary, { children });
5462
+ }
6261
5463
  function Table({
6262
- columns,
5464
+ columns: columnsProp,
6263
5465
  data,
6264
5466
  className,
6265
5467
  sortable,
@@ -6271,6 +5473,10 @@ function Table({
6271
5473
  onSelectionChange,
6272
5474
  expandable,
6273
5475
  renderExpanded,
5476
+ rowKey = "id",
5477
+ expandedRows: expandedRowsProp,
5478
+ defaultExpandedRows,
5479
+ onExpandedChange,
6274
5480
  density = "default",
6275
5481
  stickyHeader = false,
6276
5482
  striped = false,
@@ -6278,18 +5484,24 @@ function Table({
6278
5484
  emptyState,
6279
5485
  onRowClick,
6280
5486
  mobileBreakpoint: mobileBreakpointProp,
5487
+ cardsBelow = CARDS_BELOW,
5488
+ pager,
6281
5489
  title,
6282
5490
  description,
6283
5491
  actions,
6284
5492
  "aria-label": ariaLabel,
6285
5493
  ref
6286
5494
  }) {
5495
+ useFramesItself();
6287
5496
  const headingId = useId();
6288
5497
  const titleId = `${headingId}-title`;
6289
5498
  const descriptionId = `${headingId}-description`;
6290
5499
  const [internalSortKey, setInternalSortKey] = useState(null);
6291
5500
  const [internalSortDir, setInternalSortDir] = useState(null);
6292
- const [expandedRows, setExpandedRows] = useState(/* @__PURE__ */ new Set());
5501
+ const columns = useMemo(
5502
+ () => [...columnsProp.filter((c) => c.pin === "start"), ...columnsProp.filter((c) => !c.pin), ...columnsProp.filter((c) => c.pin === "end")],
5503
+ [columnsProp]
5504
+ );
6293
5505
  const isControlled = controlledSortKey !== void 0;
6294
5506
  const sortKey = isControlled ? controlledSortKey : internalSortKey;
6295
5507
  const sortDir = isControlled ? controlledSortDir ?? null : internalSortDir;
@@ -6309,6 +5521,7 @@ function Table({
6309
5521
  nextKey = key;
6310
5522
  nextDir = "asc";
6311
5523
  }
5524
+ pager?.setPage(1);
6312
5525
  if (onSortChange) {
6313
5526
  onSortChange(nextKey, nextDir);
6314
5527
  } else {
@@ -6316,139 +5529,351 @@ function Table({
6316
5529
  setInternalSortDir(nextDir);
6317
5530
  }
6318
5531
  },
6319
- [sortKey, sortDir, onSortChange]
5532
+ [sortKey, sortDir, onSortChange, pager]
5533
+ );
5534
+ const columnKeys = useMemo(() => columns.map((c) => c.key), [columns]);
5535
+ const inferredKinds = useMemo(() => inferColumnKinds(data, columnKeys), [data, columnKeys]);
5536
+ const kindOf = useCallback(
5537
+ (col) => col.kind ?? inferredKinds.get(col.key),
5538
+ [inferredKinds]
6320
5539
  );
6321
- const sortedWithIndices = sortKey && sortDir ? data.map((row, i) => ({ row, originalIdx: i })).sort((a, b) => {
6322
- const aVal = a.row[sortKey];
6323
- const bVal = b.row[sortKey];
6324
- if (aVal == null || bVal == null) return 0;
6325
- const cmp = String(aVal).localeCompare(String(bVal), void 0, { numeric: true });
6326
- return sortDir === "asc" ? cmp : -cmp;
6327
- }) : data.map((row, i) => ({ row, originalIdx: i }));
5540
+ const sortColumn = sortKey ? columns.find((c) => c.key === sortKey) : void 0;
5541
+ const sortKind = sortColumn ? kindOf(sortColumn) : void 0;
5542
+ const { sortedAll, unsortable } = useMemo(() => {
5543
+ const indexed = data.map((row, i) => ({ row, originalIdx: i }));
5544
+ if (!sortKey || !sortDir) return { sortedAll: indexed, unsortable: null };
5545
+ const read = (row) => {
5546
+ if (sortColumn?.sortValue) return sortableValue(sortColumn.sortValue(row));
5547
+ const v = sortableValue(row[sortKey]);
5548
+ if (typeof v !== "string") return v;
5549
+ return (sortKind === "number" ? numberOf(v) : sortKind === "date" ? timeOf(v) : void 0) ?? v;
5550
+ };
5551
+ const valued = indexed.map((entry) => ({ ...entry, value: read(entry.row) }));
5552
+ const elements = valued.some((v) => v.value === null);
5553
+ const sorted = elements ? indexed : valued.sort((a, b) => {
5554
+ if (a.value === void 0 || b.value === void 0) return a.value === b.value ? 0 : a.value === void 0 ? 1 : -1;
5555
+ const cmp = compareValues(a.value, b.value);
5556
+ return sortDir === "asc" ? cmp : -cmp;
5557
+ });
5558
+ return { sortedAll: sorted, unsortable: elements ? sortKey : null };
5559
+ }, [data, sortKey, sortDir, sortColumn, sortKind]);
5560
+ const canSort = useMemo(() => {
5561
+ const can = /* @__PURE__ */ new Set();
5562
+ for (const col of columns) if (col.sortValue || data.every((row) => sortableValue(row[col.key]) !== null)) can.add(col.key);
5563
+ return can;
5564
+ }, [columns, data]);
5565
+ useEffect(() => {
5566
+ if (unsortable) warnOnce("Table", `the column "${unsortable}" is sortable, but its cells are elements, which can't be compared: give it a sortValue, (row) => the name, number or date it shows (data_display#1).`);
5567
+ }, [unsortable]);
5568
+ useEffect(() => {
5569
+ if (pager && data.length !== pager.totalItems) warnOnce("Table", `the pager counts ${pager.totalItems} rows but data holds ${data.length}: give the table and usePagination the same rows, every row of the list, and the table draws the pager's part (Sprint 25.13 A3).`);
5570
+ }, [pager, data.length]);
5571
+ const rangeStart = pager ? pager.range[0] : null;
5572
+ const rangeEnd2 = pager ? pager.range[1] : null;
5573
+ const drawn = useMemo(
5574
+ () => rangeStart === null ? sortedAll : rangeStart === 0 ? [] : sortedAll.slice(rangeStart - 1, rangeEnd2 ?? void 0),
5575
+ [sortedAll, rangeStart, rangeEnd2]
5576
+ );
5577
+ const drawnStart = rangeStart ? rangeStart - 1 : 0;
6328
5578
  const selectedSet = new Set(selectedRows);
6329
- const allSelected = data.length > 0 && selectedRows.length === data.length;
6330
- const someSelected = selectedRows.length > 0 && !allSelected;
5579
+ const drawnIdx = drawn.map((d) => d.originalIdx);
5580
+ const allSelected = drawnIdx.length > 0 && drawnIdx.every((i) => selectedSet.has(i));
5581
+ const someSelected = !allSelected && drawnIdx.some((i) => selectedSet.has(i));
6331
5582
  const toggleAll = () => {
6332
- if (allSelected) onSelectionChange?.([]);
6333
- else onSelectionChange?.(data.map((_, i) => i));
5583
+ const shown = new Set(drawnIdx);
5584
+ if (allSelected) onSelectionChange?.(selectedRows.filter((r) => !shown.has(r)));
5585
+ else onSelectionChange?.([...selectedRows.filter((r) => !shown.has(r)), ...drawnIdx]);
6334
5586
  };
6335
5587
  const toggleRow = (idx) => {
6336
5588
  if (selectedSet.has(idx)) onSelectionChange?.(selectedRows.filter((r) => r !== idx));
6337
5589
  else onSelectionChange?.([...selectedRows, idx]);
6338
5590
  };
6339
- const toggleExpand = (idx) => {
6340
- setExpandedRows((prev) => {
6341
- const next = new Set(prev);
6342
- if (next.has(idx)) next.delete(idx);
6343
- else next.add(idx);
6344
- return next;
6345
- });
5591
+ const expandControlled = expandedRowsProp !== void 0;
5592
+ const [ownExpanded, setOwnExpanded] = useState(defaultExpandedRows ?? []);
5593
+ const expanded = expandControlled ? expandedRowsProp : ownExpanded;
5594
+ const expandedSet = new Set(expanded.map(String));
5595
+ const { ids: rowIds, repeated } = useMemo(() => rowIdsOf(data, rowKey), [data, rowKey]);
5596
+ useEffect(() => {
5597
+ if (repeated !== null) warnOnce("Table", `two rows share the ${rowKey} "${repeated}": give each row its own, or name a field that is one (rowKey), so an opened row and focus stay on their row.`);
5598
+ }, [repeated, rowKey]);
5599
+ const idOf = (_row, index) => rowIds[index] ?? index;
5600
+ const marksOf = (row, index) => rowMarks(row, index, rowKey, idOf(row, index));
5601
+ const openedHere = useRef(null);
5602
+ const toggleExpand = (id) => {
5603
+ const opening = !expandedSet.has(String(id));
5604
+ const next = opening ? [...expanded, id] : expanded.filter((x) => String(x) !== String(id));
5605
+ openedHere.current = opening ? String(id) : null;
5606
+ if (!expandControlled) setOwnExpanded(next);
5607
+ onExpandedChange?.(next);
6346
5608
  };
6347
- const columnKeys = useMemo(() => columns.map((c) => c.key), [columns]);
6348
- const inferredKinds = useMemo(() => inferColumnKinds(data, columnKeys), [data, columnKeys]);
6349
- const kindOf = useCallback(
6350
- (col) => col.kind ?? inferredKinds.get(col.key),
6351
- [inferredKinds]
6352
- );
5609
+ const canOpenByClick = !!expandable && !!renderExpanded && !onRowClick;
6353
5610
  const preferences = useShellPreferences();
6354
5611
  const mobileBreakpoint = mobileBreakpointProp ?? preferences.tableToCardsBreakpoint ?? "md";
6355
- const isMobile = useBreakpoint(mobileBreakpoint);
6356
- const { ref: scrollRef, measure: measureScroll, scrollable, fadeProps } = useScrollEdges({ enabled: !isMobile });
5612
+ const narrowScreen = useBreakpoint(mobileBreakpoint);
5613
+ const { cards: isMobile, rootRef } = useCardsSwitch(narrowScreen, cardsBelow, title);
5614
+ const setRoot = useCallback((node) => {
5615
+ rootRef.current = node;
5616
+ if (typeof ref === "function") ref(node);
5617
+ else if (ref) ref.current = node;
5618
+ }, [ref, rootRef]);
5619
+ const tableRef = useRef(null);
5620
+ const [pins, setPins] = useState(NO_PINS);
5621
+ const lead = (selectable ? 1 : 0) + (expandable ? 1 : 0);
5622
+ const endCount = columns.filter((c) => c.pin === "end").length;
5623
+ const nameIndex = columns.length > endCount ? lead : -1;
5624
+ const { ref: scrollRef, elRef: scrollEl, measure: measureScroll, scrollable, edges, fadeProps } = useScrollEdges({
5625
+ enabled: !isMobile,
5626
+ noFade: { left: lead + (pins.nameFits ? 1 : 0) > 0, right: endCount > 0 }
5627
+ });
6357
5628
  const [regionFocused, setRegionFocused] = useState(false);
5629
+ useLayoutEffect(() => {
5630
+ const table2 = tableRef.current;
5631
+ const scroller = scrollEl.current;
5632
+ if (!table2 || !scroller || isMobile) return;
5633
+ const measure = () => {
5634
+ const cells = Array.from(table2.querySelectorAll(":scope > thead > tr > th"));
5635
+ if (!cells.length) return;
5636
+ const widths = cells.map((c) => c.getBoundingClientRect().width);
5637
+ if (!widths.some((w) => w > 0)) return;
5638
+ const start = [];
5639
+ let at = 0;
5640
+ for (let i = 0; i < lead; i++) {
5641
+ start.push(at);
5642
+ at += widths[i] ?? 0;
5643
+ }
5644
+ const nameWidth = nameIndex >= 0 ? widths[nameIndex] ?? 0 : 0;
5645
+ const end = [];
5646
+ let fromRight = 0;
5647
+ for (let i = cells.length - 1; i >= cells.length - endCount; i--) {
5648
+ end.unshift(fromRight);
5649
+ fromRight += widths[i] ?? 0;
5650
+ }
5651
+ const frame = scroller.clientWidth;
5652
+ const nameFits = nameIndex >= 0 && frame > 0 && at + nameWidth + fromRight <= frame / 2;
5653
+ if (nameFits) start.push(at);
5654
+ const pad3 = (cell, side) => cell ? parseFloat(getComputedStyle(cell)[side]) || 0 : 0;
5655
+ const panel = cells.length > lead ? [at + pad3(cells[lead], "paddingLeft"), pad3(cells[cells.length - 1], "paddingRight")] : null;
5656
+ const next = { start, end, nameFits, frame, panel };
5657
+ setPins((prev) => samePins(prev, next) ? prev : next);
5658
+ };
5659
+ measure();
5660
+ if (typeof ResizeObserver === "undefined") return;
5661
+ const observer = new ResizeObserver(measure);
5662
+ observer.observe(table2);
5663
+ observer.observe(scroller);
5664
+ return () => observer.disconnect();
5665
+ }, [isMobile, lead, endCount, nameIndex, scrollEl, columns, drawn.length]);
5666
+ const pinnedStart = lead + (pins.nameFits ? 1 : 0);
5667
+ const pinStyle = (position, total) => {
5668
+ if (position < pinnedStart && pins.start[position] !== void 0) {
5669
+ const inner = position === pinnedStart - 1;
5670
+ return { position: "sticky", left: pins.start[position], zIndex: 1, background: PINNED_BACKGROUND, boxShadow: inner && edges.left ? `6px 0 6px -6px ${PIN_SHADOW}` : void 0 };
5671
+ }
5672
+ const fromEnd = total - 1 - position;
5673
+ if (fromEnd < endCount && pins.end[endCount - 1 - fromEnd] !== void 0) {
5674
+ const inner = fromEnd === endCount - 1;
5675
+ return { position: "sticky", right: pins.end[endCount - 1 - fromEnd], zIndex: 1, background: PINNED_BACKGROUND, boxShadow: inner && edges.right ? `-6px 0 6px -6px ${PIN_SHADOW}` : void 0 };
5676
+ }
5677
+ return void 0;
5678
+ };
5679
+ const fade = fadeProps;
5680
+ const [renderedKinds, setRenderedKinds] = useState(() => /* @__PURE__ */ new Map());
5681
+ useLayoutEffect(() => {
5682
+ const table2 = tableRef.current;
5683
+ if (!table2 || isMobile) return;
5684
+ const next = /* @__PURE__ */ new Map();
5685
+ for (const col of columns) {
5686
+ if (kindOf(col)) continue;
5687
+ const texts = Array.from(table2.querySelectorAll(`:scope > tbody > tr > td[data-col="${CSS.escape(col.key)}"]`)).slice(0, 50).map((td) => td.textContent ?? "");
5688
+ const kind = texts.length ? kindOfRenderedText(texts) : void 0;
5689
+ if (kind) next.set(col.key, kind);
5690
+ }
5691
+ setRenderedKinds((prev) => prev.size === next.size && [...next].every(([k, v]) => prev.get(k) === v) ? prev : next);
5692
+ }, [columns, kindOf, drawn, isMobile]);
5693
+ const expandedKey = expanded.map(String).join("");
5694
+ const shownBefore = useRef(/* @__PURE__ */ new Set());
5695
+ const pendingScroll = useRef(null);
5696
+ const scrollToRow = useCallback((key) => {
5697
+ const row = rootRef.current?.querySelector(`[data-row-key="${CSS.escape(key)}"], [data-row-index="${CSS.escape(key)}"]`);
5698
+ if (row && typeof row.scrollIntoView === "function") row.scrollIntoView({ block: "nearest" });
5699
+ }, [rootRef]);
5700
+ useEffect(() => {
5701
+ const now = new Set(expanded.map(String));
5702
+ const added = [...now].filter((k) => !shownBefore.current.has(k) && k !== openedHere.current);
5703
+ shownBefore.current = now;
5704
+ openedHere.current = null;
5705
+ if (!expandControlled) return;
5706
+ const key = added[added.length - 1];
5707
+ if (key === void 0) return;
5708
+ if (pager) {
5709
+ const position = sortedAll.findIndex((r) => String(idOf(r.row, r.originalIdx)) === key);
5710
+ if (position >= 0 && (position < drawnStart || position >= drawnStart + drawn.length)) {
5711
+ pendingScroll.current = key;
5712
+ pager.setPage(Math.floor(position / pager.pageSize) + 1);
5713
+ return;
5714
+ }
5715
+ }
5716
+ scrollToRow(key);
5717
+ }, [expandedKey]);
5718
+ useEffect(() => {
5719
+ const key = pendingScroll.current;
5720
+ if (!key || !drawn.some((r) => String(idOf(r.row, r.originalIdx)) === key)) return;
5721
+ pendingScroll.current = null;
5722
+ scrollToRow(key);
5723
+ }, [drawnStart, drawn.length, scrollToRow]);
6358
5724
  const cardHeading = title ? /* @__PURE__ */ jsx(TableHeading, { title, description, actions, titleId, descriptionId, inset: false }) : null;
6359
- const titledCards = (body) => /* @__PURE__ */ jsxs("div", { ref, "data-ds-list": "", "data-gs": "table", className: cn("flex flex-col", className), children: [
5725
+ const titledCards = (body) => /* @__PURE__ */ jsxs("div", { ref: listRoot, "data-ds-list": "", "data-gs": "table", className: cn("flex flex-col", listClass), onFocus: noteFocus, onBlur: forgetFocus, children: [
6360
5726
  cardHeading,
6361
5727
  body
6362
5728
  ] });
5729
+ const control = pager && !loading && data.length > 0 ? /* @__PURE__ */ jsx(Pagination, { ...pager.props }) : null;
5730
+ const listRoot = control ? void 0 : setRoot;
5731
+ const listClass = control ? void 0 : className;
5732
+ const withControl = (list) => control ? /* @__PURE__ */ jsxs("div", { ref: setRoot, "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
5733
+ list,
5734
+ control
5735
+ ] }) : list;
5736
+ const focusWas = useRef(null);
5737
+ const noteFocus = (e) => {
5738
+ const target = e.target;
5739
+ const row = target.closest("[data-row-key], [data-row-index]");
5740
+ if (!row) {
5741
+ focusWas.current = null;
5742
+ return;
5743
+ }
5744
+ const control2 = target === row ? null : target.getAttribute("aria-labelledby") ?? target.getAttribute("aria-label") ?? target.textContent;
5745
+ focusWas.current = { row: row.hasAttribute("data-row-key") ? `[data-row-key="${CSS.escape(row.getAttribute("data-row-key"))}"]` : `[data-row-index="${CSS.escape(row.getAttribute("data-row-index"))}"]`, control: control2 };
5746
+ };
5747
+ const forgetFocus = (e) => {
5748
+ const node = e.target;
5749
+ queueMicrotask(() => {
5750
+ if (node.isConnected && !rootRef.current?.contains(document.activeElement)) focusWas.current = null;
5751
+ });
5752
+ };
5753
+ useLayoutEffect(() => {
5754
+ const was = focusWas.current;
5755
+ const active = document.activeElement;
5756
+ if (!was || active && active !== document.body) return;
5757
+ const row = rootRef.current?.querySelector(was.row);
5758
+ if (!row) return;
5759
+ const controls = Array.from(row.querySelectorAll(FOCUSABLE));
5760
+ const same = was.control === null ? null : controls.find((el) => (el.getAttribute("aria-labelledby") ?? el.getAttribute("aria-label") ?? el.textContent) === was.control);
5761
+ (same ?? (row.tabIndex >= 0 ? row : controls[0]))?.focus({ preventScroll: true });
5762
+ }, [isMobile, rootRef]);
6363
5763
  if (isMobile) {
6364
5764
  if (loading) {
6365
5765
  if (title) return titledCards(/* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-ds-10", children: /* @__PURE__ */ jsx(Spinner, { label: "Loading table data" }) }));
6366
- return /* @__PURE__ */ jsx("div", { ref, "data-ds-list": "", "data-gs": "table", className: cn("flex items-center justify-center py-ds-10", className), children: /* @__PURE__ */ jsx(Spinner, { label: "Loading table data" }) });
5766
+ return /* @__PURE__ */ jsx("div", { ref: setRoot, "data-ds-list": "", "data-gs": "table", className: cn("flex items-center justify-center py-ds-10", className), children: /* @__PURE__ */ jsx(Spinner, { label: "Loading table data" }) });
6367
5767
  }
6368
5768
  if (data.length === 0 && emptyState) {
6369
5769
  if (title) return titledCards(/* @__PURE__ */ jsx("div", { className: "py-ds-10 text-center text-sm text-[var(--ds-text-muted)]", children: emptyState }));
6370
- return /* @__PURE__ */ jsx("div", { ref, "data-ds-list": "", "data-gs": "table", className: cn("py-ds-10 text-center text-sm text-[var(--ds-text-muted)]", className), children: emptyState });
5770
+ return /* @__PURE__ */ jsx("div", { ref: setRoot, "data-ds-list": "", "data-gs": "table", className: cn("py-ds-10 text-center text-sm text-[var(--ds-text-muted)]", className), children: emptyState });
6371
5771
  }
6372
- const cards = /* @__PURE__ */ jsx(TableCardCtx, { value: true, children: /* @__PURE__ */ jsx("div", { ref: title ? void 0 : ref, "data-ds-list": title ? void 0 : "", "data-gs": title ? void 0 : "table", className: cn("flex flex-col gap-ds-3 overflow-y-auto", title ? "min-h-0" : className), children: sortedWithIndices.map(({ row, originalIdx }, i) => {
6373
- const isSelected = selectedSet.has(originalIdx);
6374
- return /* @__PURE__ */ jsxs(
6375
- "div",
6376
- {
6377
- "data-row": "",
6378
- className: cn(
6379
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
6380
- "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
6381
- "p-ds-4 space-y-ds-3",
6382
- isSelected && ACTIVE_RING,
6383
- onRowClick && "cursor-pointer hover:bg-[var(--ds-bg-muted)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
6384
- ),
6385
- onClick: onRowClick ? () => onRowClick(row, originalIdx) : void 0,
6386
- tabIndex: onRowClick ? 0 : void 0,
6387
- onKeyDown: onRowClick ? (e) => {
6388
- if (e.target !== e.currentTarget) return;
6389
- if (e.key === "Enter" || e.key === " ") {
6390
- e.preventDefault();
6391
- onRowClick(row, originalIdx);
6392
- }
6393
- } : void 0,
6394
- children: [
6395
- selectable && /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => toggleRow(originalIdx), label: "Select" }),
6396
- columns.map((col) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-ds-2 text-sm", children: [
6397
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--ds-text-secondary)] font-medium text-xs uppercase tracking-wider", children: col.header }),
6398
- /* @__PURE__ */ jsx(CardValue, { value: row[col.key] })
6399
- ] }, col.key)),
6400
- expandable && renderExpanded && /* @__PURE__ */ jsx("div", { className: "pt-ds-2 border-t-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] text-sm text-[var(--ds-text-secondary)]", children: renderExpanded(row, i) })
6401
- ]
6402
- },
6403
- i
6404
- );
6405
- }) }) });
6406
- return title ? titledCards(cards) : cards;
5772
+ const cards = /* @__PURE__ */ jsx(TableCardCtx, { value: true, children: /* @__PURE__ */ jsxs("div", { ref: title ? void 0 : listRoot, "data-ds-list": title ? void 0 : "", "data-gs": title ? void 0 : "table", className: cn("flex flex-col gap-ds-3 overflow-y-auto", title ? "min-h-0" : listClass), onFocus: title ? void 0 : noteFocus, onBlur: title ? void 0 : forgetFocus, children: [
5773
+ selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select`, children: "Select" }),
5774
+ drawn.map(({ row, originalIdx }) => {
5775
+ const isSelected = selectedSet.has(originalIdx);
5776
+ const id = idOf(row, originalIdx);
5777
+ return /* @__PURE__ */ jsxs(
5778
+ "div",
5779
+ {
5780
+ "data-row": "",
5781
+ ...marksOf(row, originalIdx),
5782
+ className: cn(
5783
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
5784
+ "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
5785
+ "p-ds-4 space-y-ds-3",
5786
+ isSelected && ACTIVE_RING,
5787
+ onRowClick && "cursor-pointer hover:bg-[var(--ds-bg-muted)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
5788
+ ),
5789
+ onClick: onRowClick ? (e) => {
5790
+ if (clickIsRows(e)) onRowClick(row, originalIdx);
5791
+ } : void 0,
5792
+ tabIndex: onRowClick ? 0 : void 0,
5793
+ onKeyDown: onRowClick ? (e) => {
5794
+ if (e.target !== e.currentTarget) return;
5795
+ if (e.key === "Enter" || e.key === " ") {
5796
+ e.preventDefault();
5797
+ onRowClick(row, originalIdx);
5798
+ }
5799
+ } : void 0,
5800
+ children: [
5801
+ selectable && // "Select" and the row's name, as in the table (T15)
5802
+ /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => toggleRow(originalIdx), "aria-labelledby": nameIndex >= 0 ? `${headingId}-select ${headingId}-card-name-${idPart(id)}` : `${headingId}-select` }),
5803
+ columns.map((col, c) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-ds-2 text-sm", children: [
5804
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--ds-text-secondary)] font-medium text-xs uppercase tracking-wider", children: col.header }),
5805
+ /* @__PURE__ */ jsx(CardValue, { value: row[col.key], id: lead + c === nameIndex ? `${headingId}-card-name-${idPart(id)}` : void 0 })
5806
+ ] }, col.key)),
5807
+ expandable && renderExpanded && /* @__PURE__ */ jsx("div", { className: "pt-ds-2 border-t-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] text-sm text-[var(--ds-text-secondary)]", children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) }) })
5808
+ ]
5809
+ },
5810
+ String(id)
5811
+ );
5812
+ })
5813
+ ] }) });
5814
+ return withControl(title ? titledCards(cards) : cards);
6407
5815
  }
6408
- const table = /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", ...title ? { "aria-labelledby": titleId, "aria-describedby": description ? descriptionId : void 0 } : ariaLabel ? { "aria-label": ariaLabel } : {}, children: [
6409
- /* @__PURE__ */ jsx("thead", { className: cn("bg-[var(--ds-bg-muted)]", stickyHeader && "sticky top-0 z-10"), children: /* @__PURE__ */ jsxs("tr", { className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]", children: [
6410
- selectable && /* @__PURE__ */ jsx("th", { className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3"), children: /* @__PURE__ */ jsx(
5816
+ const cellCount = columns.length + lead;
5817
+ const table = /* @__PURE__ */ jsxs("table", { ref: tableRef, className: cn("w-full text-sm", PINNED_LAYERS), ...title ? { "aria-labelledby": titleId, "aria-describedby": description ? descriptionId : void 0 } : ariaLabel ? { "aria-label": ariaLabel } : {}, children: [
5818
+ /* @__PURE__ */ jsx("thead", { className: cn("bg-[var(--ds-bg-muted)]", stickyHeader && "sticky top-0 z-10"), children: /* @__PURE__ */ jsxs("tr", { className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] [--gs-row-tint:var(--ds-bg-muted)]", children: [
5819
+ selectable && /* @__PURE__ */ jsx("th", { className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3"), style: pinStyle(0, cellCount), children: /* @__PURE__ */ jsx(
6411
5820
  Checkbox,
6412
5821
  {
6413
5822
  checked: allSelected,
6414
5823
  indeterminate: someSelected,
6415
- onChange: toggleAll
5824
+ onChange: toggleAll,
5825
+ "aria-labelledby": `${headingId}-select-all`
6416
5826
  }
6417
5827
  ) }),
6418
- expandable && /* @__PURE__ */ jsx("th", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3") }),
6419
- columns.map((col) => {
6420
- const isSortable = sortable || col.sortable;
6421
- const isActiveSort = sortKey === col.key;
5828
+ expandable && /* @__PURE__ */ jsx("th", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3"), style: pinStyle(selectable ? 1 : 0, cellCount) }),
5829
+ columns.map((col, c) => {
5830
+ const isSortable = col.header.trim() !== "" && (col.sortable ?? (!!sortable && canSort.has(col.key)));
5831
+ const isActiveSort = sortKey === col.key && !!sortDir && unsortable !== col.key;
6422
5832
  const headerKind = kindOf(col);
6423
5833
  const headerAlign = col.align ?? (headerKind === "number" ? "right" : "left");
6424
5834
  const shrink = (headerKind === "number" || headerKind === "date") && !col.maxWidth;
5835
+ const style = { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth, ...pinStyle(lead + c, cellCount) };
6425
5836
  return /* @__PURE__ */ jsx(
6426
5837
  "th",
6427
5838
  {
5839
+ "aria-sort": isSortable && isActiveSort ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6428
5840
  className: cn(
6429
5841
  density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6430
5842
  shrink && "w-px whitespace-nowrap",
6431
5843
  "font-[var(--ds-weight-heading)] text-[var(--ds-text-secondary)]",
6432
5844
  "text-xs uppercase tracking-wider",
6433
- headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left",
6434
- isSortable && "cursor-pointer select-none hover:text-[var(--ds-text-primary)]"
5845
+ headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left"
6435
5846
  ),
6436
- style: col.maxWidth || col.width || col.minWidth ? { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth } : void 0,
6437
- onClick: isSortable ? () => handleSort(col.key) : void 0,
6438
- children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-ds-1", children: [
6439
- col.header,
6440
- isSortable && isActiveSort && /* @__PURE__ */ jsx(
6441
- Icon,
5847
+ style,
5848
+ children: isSortable ? (
5849
+ // A47: a button with the column's name, reached with Tab and pressed with Enter or Space
5850
+ /* @__PURE__ */ jsxs(
5851
+ "button",
6442
5852
  {
6443
- name: "chevron-right",
6444
- size: 12,
5853
+ type: "button",
5854
+ onClick: () => handleSort(col.key),
6445
5855
  className: cn(
6446
- "transition-transform",
6447
- sortDir === "asc" ? "-rotate-90" : "rotate-90"
6448
- )
5856
+ "inline-flex items-center gap-ds-1 uppercase tracking-wider cursor-pointer select-none",
5857
+ "[font:inherit] text-inherit hover:text-[var(--ds-text-primary)]",
5858
+ "rounded-[var(--ds-radius-sm)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
5859
+ ),
5860
+ children: [
5861
+ col.header,
5862
+ /* @__PURE__ */ jsx(
5863
+ Icon,
5864
+ {
5865
+ name: "chevron-right",
5866
+ size: 12,
5867
+ className: cn(
5868
+ "transition-transform",
5869
+ !isActiveSort ? "invisible" : sortDir === "asc" ? "-rotate-90" : "rotate-90"
5870
+ )
5871
+ }
5872
+ )
5873
+ ]
6449
5874
  }
6450
5875
  )
6451
- ] })
5876
+ ) : /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-ds-1", children: col.header })
6452
5877
  },
6453
5878
  col.key
6454
5879
  );
@@ -6457,31 +5882,45 @@ function Table({
6457
5882
  /* @__PURE__ */ jsx("tbody", { children: loading ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6458
5883
  "td",
6459
5884
  {
6460
- colSpan: columns.length + (selectable ? 1 : 0) + (expandable ? 1 : 0),
5885
+ colSpan: cellCount,
6461
5886
  className: "px-ds-4 py-ds-10 text-center",
6462
5887
  children: /* @__PURE__ */ jsx(Spinner, { label: "Loading table data" })
6463
5888
  }
6464
5889
  ) }) : data.length === 0 && emptyState ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6465
5890
  "td",
6466
5891
  {
6467
- colSpan: columns.length + (selectable ? 1 : 0) + (expandable ? 1 : 0),
5892
+ colSpan: cellCount,
6468
5893
  className: "px-ds-4 py-ds-10 text-center text-sm text-[var(--ds-text-muted)]",
6469
5894
  children: emptyState
6470
5895
  }
6471
- ) }) : sortedWithIndices.map(({ row, originalIdx }, i) => {
5896
+ ) }) : drawn.map(({ row, originalIdx }, i) => {
6472
5897
  const isSelected = selectedSet.has(originalIdx);
6473
- const isExpanded = expandedRows.has(i);
5898
+ const id = idOf(row, originalIdx);
5899
+ const isExpanded = expandedSet.has(String(id));
5900
+ const clickable = !!onRowClick || canOpenByClick;
6474
5901
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6475
5902
  /* @__PURE__ */ jsxs(
6476
5903
  "tr",
6477
5904
  {
5905
+ ...marksOf(row, originalIdx),
6478
5906
  className: cn(
6479
- "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
6480
- "transition-colors",
6481
- isSelected ? "bg-[var(--ds-accent-subtle)]" : striped && i % 2 === 1 ? "bg-[var(--ds-bg-muted)]/50" : "hover:bg-[var(--ds-bg-muted)]",
6482
- onRowClick && "cursor-pointer focus-visible:outline-none focus-visible:shadow-[inset_var(--ds-shadow-focus)]"
5907
+ // an opened row's divider is under its panel (A6)
5908
+ !(expandable && isExpanded && renderExpanded) && "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
5909
+ // the row's background and its tint change together, so its pinned cells and the rest fade as one
5910
+ // (roots.css registers --gs-row-tint as a colour so it can)
5911
+ "transition-[background-color,--gs-row-tint]",
5912
+ // the row's tint, as a variable too, so its pinned cells draw it over their solid ground (A35)
5913
+ isSelected ? "bg-[var(--ds-accent-subtle)] [--gs-row-tint:var(--ds-accent-subtle)]" : striped && i % 2 === 1 ? "bg-[var(--ds-bg-muted)]/50 [--gs-row-tint:color-mix(in_oklab,var(--ds-bg-muted)_50%,transparent)]" : "hover:bg-[var(--ds-bg-muted)] hover:[--gs-row-tint:var(--ds-bg-muted)]",
5914
+ clickable && "cursor-pointer",
5915
+ // an outline inside the row: it shows over the pinned cells, whose solid ground hid the inset
5916
+ // shadow the row drew before (the review of 30 September)
5917
+ onRowClick && "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ds-border-focus)]"
6483
5918
  ),
6484
- onClick: onRowClick ? () => onRowClick(row, originalIdx) : void 0,
5919
+ onClick: clickable ? (e) => {
5920
+ if (!clickIsRows(e)) return;
5921
+ if (onRowClick) onRowClick(row, originalIdx);
5922
+ else toggleExpand(id);
5923
+ } : void 0,
6485
5924
  tabIndex: onRowClick ? 0 : void 0,
6486
5925
  onKeyDown: onRowClick ? (e) => {
6487
5926
  if (e.target !== e.currentTarget) return;
@@ -6491,15 +5930,15 @@ function Table({
6491
5930
  }
6492
5931
  } : void 0,
6493
5932
  children: [
6494
- selectable && /* @__PURE__ */ jsx("td", { className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3"), children: /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => toggleRow(originalIdx) }) }),
6495
- expandable && /* @__PURE__ */ jsx("td", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3"), children: /* @__PURE__ */ jsx(
5933
+ selectable && /* @__PURE__ */ jsx("td", { className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3"), style: pinStyle(0, cellCount), children: /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => toggleRow(originalIdx), "aria-labelledby": nameIndex >= 0 ? `${headingId}-select ${headingId}-name-${idPart(id)}` : `${headingId}-select` }) }),
5934
+ expandable && /* @__PURE__ */ jsx("td", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3"), style: pinStyle(selectable ? 1 : 0, cellCount), children: /* @__PURE__ */ jsx(
6496
5935
  "button",
6497
5936
  {
6498
5937
  type: "button",
6499
- onClick: () => toggleExpand(i),
5938
+ onClick: () => toggleExpand(id),
6500
5939
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] cursor-pointer",
6501
5940
  "aria-expanded": isExpanded,
6502
- "aria-label": "Expand row",
5941
+ ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-details ${headingId}-name-${idPart(id)}` } : { "aria-label": "Details" },
6503
5942
  children: /* @__PURE__ */ jsx(
6504
5943
  Icon,
6505
5944
  {
@@ -6510,12 +5949,13 @@ function Table({
6510
5949
  )
6511
5950
  }
6512
5951
  ) }),
6513
- columns.map((col) => {
5952
+ columns.map((col, c) => {
6514
5953
  const truncLines = col.truncate === true ? 1 : col.truncate ? col.truncate.lines : 0;
6515
5954
  const kind = kindOf(col);
6516
5955
  const alignClass = col.align ? col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "text-left" : kind === "number" ? "text-right" : "text-left";
6517
5956
  const cellContent = row[col.key];
6518
5957
  const shrink = (kind === "number" || kind === "date") && !col.maxWidth;
5958
+ const whole = !kind && renderedKinds.has(col.key);
6519
5959
  let inner;
6520
5960
  if (truncLines > 0) {
6521
5961
  inner = /* @__PURE__ */ jsx(TruncCell, { lines: truncLines, children: cellContent });
@@ -6531,13 +5971,17 @@ function Table({
6531
5971
  return /* @__PURE__ */ jsx(
6532
5972
  "td",
6533
5973
  {
5974
+ id: lead > 0 && lead + c === nameIndex ? `${headingId}-name-${idPart(id)}` : void 0,
5975
+ "data-col": col.key,
6534
5976
  className: cn(
6535
5977
  density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6536
5978
  "text-[var(--ds-text-primary)]",
6537
5979
  alignClass,
6538
- shrink && "w-px whitespace-nowrap"
5980
+ shrink && "w-px whitespace-nowrap",
5981
+ whole && "whitespace-nowrap",
5982
+ kind === "identifier" && "[overflow-wrap:anywhere]"
6539
5983
  ),
6540
- style: col.maxWidth || col.width || col.minWidth ? { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth } : void 0,
5984
+ style: col.maxWidth || col.width || col.minWidth || c + lead < pinnedStart || c >= columns.length - endCount ? { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth, ...pinStyle(lead + c, cellCount) } : void 0,
6541
5985
  children: inner
6542
5986
  },
6543
5987
  col.key
@@ -6546,53 +5990,69 @@ function Table({
6546
5990
  ]
6547
5991
  }
6548
5992
  ),
6549
- expandable && isExpanded && renderExpanded && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
5993
+ expandable && isExpanded && renderExpanded && /* @__PURE__ */ jsx("tr", { className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0", children: /* @__PURE__ */ jsx(
6550
5994
  "td",
6551
5995
  {
6552
- colSpan: columns.length + (selectable ? 1 : 0) + 1,
6553
- className: "bg-[var(--ds-bg-muted)] px-ds-6 py-ds-4 text-sm text-[var(--ds-text-primary)]",
6554
- children: renderExpanded(row, i)
5996
+ colSpan: cellCount,
5997
+ className: cn("py-ds-4 text-sm text-[var(--ds-text-primary)]", !pins.panel && (density === "compact" ? "px-ds-3" : "px-ds-4")),
5998
+ style: pins.panel ? { paddingLeft: pins.panel[0], paddingRight: pins.panel[1] } : void 0,
5999
+ children: /* @__PURE__ */ jsx(
6000
+ "div",
6001
+ {
6002
+ "data-row-panel": "",
6003
+ className: "sticky",
6004
+ style: pins.panel ? { left: pins.panel[0], maxWidth: pins.frame ? pins.frame - pins.panel[0] - pins.panel[1] : void 0 } : { left: 0 },
6005
+ children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) })
6006
+ }
6007
+ )
6555
6008
  }
6556
6009
  ) })
6557
- ] }, i);
6010
+ ] }, String(id));
6558
6011
  }) })
6559
6012
  ] });
6560
6013
  const region = scrollable || regionFocused ? { tabIndex: 0, role: "region", ...title ? { "aria-labelledby": titleId } : { "aria-label": ariaLabel ?? "Table" } } : {};
6561
- return /* @__PURE__ */ jsxs(
6562
- "div",
6563
- {
6564
- ref,
6565
- "data-ds-list": "",
6566
- "data-gs": "table",
6567
- className: cn("w-full min-w-0 flex flex-col", FRAME_CLASSES, "has-[>[data-table-scroll]:focus-visible]:shadow-[var(--ds-shadow-focus)]", className),
6568
- children: [
6569
- title && /* @__PURE__ */ jsx(TableHeading, { title, description, actions, titleId, descriptionId, inset: true }),
6570
- /* @__PURE__ */ jsx(
6571
- "div",
6572
- {
6573
- ref: scrollRef,
6574
- "data-table-scroll": "",
6575
- onScroll: measureScroll,
6576
- onFocus: (e) => {
6577
- if (e.target === e.currentTarget) setRegionFocused(true);
6578
- },
6579
- onBlur: (e) => {
6580
- if (e.target === e.currentTarget) setRegionFocused(false);
6581
- },
6582
- ...fadeProps,
6583
- ...region,
6584
- className: cn("min-h-0 overflow-x-auto focus-visible:outline-none", title ? "rounded-b-[inherit]" : "rounded-[inherit]"),
6585
- children: table
6586
- }
6587
- )
6588
- ]
6589
- }
6014
+ return withControl(
6015
+ /* @__PURE__ */ jsxs(
6016
+ "div",
6017
+ {
6018
+ ref: listRoot,
6019
+ "data-ds-list": "",
6020
+ "data-gs": "table",
6021
+ className: cn("w-full min-w-0 flex flex-col", FRAME_CLASSES, "has-[>[data-table-scroll]:focus-visible]:shadow-[var(--ds-shadow-focus)]", control && "min-h-0", listClass),
6022
+ onFocus: noteFocus,
6023
+ onBlur: forgetFocus,
6024
+ children: [
6025
+ title && /* @__PURE__ */ jsx(TableHeading, { title, description, actions, titleId, descriptionId, inset: true }),
6026
+ selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select-all`, children: "Select all rows shown" }),
6027
+ selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select`, children: "Select" }),
6028
+ expandable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-details`, children: "Details" }),
6029
+ /* @__PURE__ */ jsx(
6030
+ "div",
6031
+ {
6032
+ ref: scrollRef,
6033
+ "data-table-scroll": "",
6034
+ onScroll: measureScroll,
6035
+ onFocus: (e) => {
6036
+ if (e.target === e.currentTarget) setRegionFocused(true);
6037
+ },
6038
+ onBlur: (e) => {
6039
+ if (e.target === e.currentTarget) setRegionFocused(false);
6040
+ },
6041
+ ...fade,
6042
+ ...region,
6043
+ className: cn("min-h-0 overflow-x-auto focus-visible:outline-none", title ? "rounded-b-[inherit]" : "rounded-[inherit]"),
6044
+ children: table
6045
+ }
6046
+ )
6047
+ ]
6048
+ }
6049
+ )
6590
6050
  );
6591
6051
  }
6592
6052
  function optionsKeyForFit(options) {
6593
6053
  return options.map((o) => `${o.value}:${o.label}`).join("|");
6594
6054
  }
6595
- var sizes3 = {
6055
+ var sizes2 = {
6596
6056
  xs: "text-xs px-ds-2",
6597
6057
  sm: "text-xs px-ds-3",
6598
6058
  md: "text-sm px-ds-3",
@@ -6745,12 +6205,15 @@ function SegmentedControl({
6745
6205
  onKeyDown: handleKeyDown,
6746
6206
  className: cn(
6747
6207
  "relative items-center",
6748
- fullWidth ? "flex w-full" : "inline-flex",
6208
+ // Sprint 25.14 A21: in a Field's column a track that isn't full width keeps its own width; w-fit, not self-start,
6209
+ // so a row or a centred column still aligns it (the review of 30 September)
6210
+ fullWidth ? "flex w-full" : "inline-flex w-fit",
6749
6211
  containerPadding[size],
6750
6212
  containerHeight[size],
6751
6213
  "bg-[var(--ds-bg-muted)]",
6752
6214
  "rounded-[var(--ds-radius-lg)]",
6753
- variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
6215
+ // Sprint 25.14 A20: the track's edge is a control's, at 3:1
6216
+ variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-input-border)]",
6754
6217
  control.invalid && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]",
6755
6218
  className
6756
6219
  ),
@@ -6794,7 +6257,7 @@ function SegmentedControl({
6794
6257
  "rounded-[var(--ds-radius-md)]",
6795
6258
  "transition-colors cursor-pointer",
6796
6259
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
6797
- sizes3[size],
6260
+ sizes2[size],
6798
6261
  opt.value === value ? "text-[var(--ds-text-primary)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]",
6799
6262
  opt.disabled && "opacity-disabled cursor-not-allowed"
6800
6263
  ),
@@ -7237,6 +6700,7 @@ function WeekView({
7237
6700
  const hasAllDay = allDayEvents.some((d) => d.length > 0);
7238
6701
  const containerRef = useRef(null);
7239
6702
  const syncingScroll = useRef(false);
6703
+ const { ref: edgesRef, measure: measureEdges, fadeProps: edgeFade } = useScrollEdges();
7240
6704
  const handleScroll = useCallback((e) => {
7241
6705
  if (syncingScroll.current) return;
7242
6706
  syncingScroll.current = true;
@@ -7262,7 +6726,7 @@ function WeekView({
7262
6726
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "flex flex-col h-full overflow-hidden", children: [
7263
6727
  /* @__PURE__ */ jsxs("div", { className: "flex border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]", children: [
7264
6728
  /* @__PURE__ */ jsx("div", { className: "w-14 shrink-0 border-r-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]" }),
7265
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6729
+ /* @__PURE__ */ jsx("div", { ref: edgesRef, onScroll: measureEdges, ...edgeFade, className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
7266
6730
  "div",
7267
6731
  {
7268
6732
  className: "grid",
@@ -7302,7 +6766,7 @@ function WeekView({
7302
6766
  ] }),
7303
6767
  hasAllDay && /* @__PURE__ */ jsxs("div", { className: "flex border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-bg-muted)]/30", children: [
7304
6768
  /* @__PURE__ */ jsx("div", { className: "w-14 shrink-0 border-r-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] text-2xs text-[var(--ds-text-muted)] px-ds-1 py-ds-1 text-right", children: "all-day" }),
7305
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6769
+ /* @__PURE__ */ jsx("div", { ...edgeFade, className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
7306
6770
  "div",
7307
6771
  {
7308
6772
  className: "grid",
@@ -7339,7 +6803,7 @@ function WeekView({
7339
6803
  },
7340
6804
  h
7341
6805
  )) }),
7342
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6806
+ /* @__PURE__ */ jsx("div", { ...edgeFade, className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
7343
6807
  "div",
7344
6808
  {
7345
6809
  className: "relative",
@@ -8383,7 +7847,6 @@ function AvatarGroup({ items, max = 4, size = "sm", ring = "canvas", onOverflowC
8383
7847
  alt: item.alt ?? item.name ?? initials,
8384
7848
  initials,
8385
7849
  size,
8386
- status: item.status,
8387
7850
  color: item.color,
8388
7851
  className: ringClass
8389
7852
  },
@@ -8701,7 +8164,10 @@ function VerticalLayout({ items, className }) {
8701
8164
  ] })
8702
8165
  ] }, i)) });
8703
8166
  }
8704
- function Timeline({ items, orientation = "vertical", minItemWidth = 200, className, ref }) {
8167
+ function Timeline({ items: allItems, orientation = "vertical", minItemWidth = 200, className, ref, pager }) {
8168
+ useScrollsInside();
8169
+ useWholeListCheck("Timeline", allItems.length, pager);
8170
+ const items = pagerPart(allItems, pager);
8705
8171
  const measureRef = useRef(null);
8706
8172
  const width = useElementWidth(measureRef);
8707
8173
  const setRefs = useCallback((node) => {
@@ -8711,7 +8177,12 @@ function Timeline({ items, orientation = "vertical", minItemWidth = 200, classNa
8711
8177
  }, [ref]);
8712
8178
  const fits = width === 0 || width >= items.length * minItemWidth;
8713
8179
  const horizontal = orientation === "horizontal" || orientation === "responsive" && fits;
8714
- return /* @__PURE__ */ jsx("div", { ref: setRefs, "data-ds-list": "", "data-timeline-orientation": horizontal ? "horizontal" : "vertical", className, children: horizontal ? /* @__PURE__ */ jsx(HorizontalLayout, { items, minItemWidth }) : /* @__PURE__ */ jsx(VerticalLayout, { items }) });
8180
+ const list = /* @__PURE__ */ jsx("div", { ref: setRefs, "data-ds-list": "", "data-timeline-orientation": horizontal ? "horizontal" : "vertical", className: pager ? void 0 : className, children: horizontal ? /* @__PURE__ */ jsx(HorizontalLayout, { items, minItemWidth }) : /* @__PURE__ */ jsx(VerticalLayout, { items }) });
8181
+ if (!pager) return list;
8182
+ return /* @__PURE__ */ jsxs("div", { "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
8183
+ list,
8184
+ allItems.length > 0 && /* @__PURE__ */ jsx(Pagination, { ...pager.props })
8185
+ ] });
8715
8186
  }
8716
8187
  var CollapsibleContext = createContext(null);
8717
8188
  function useCollapsible() {
@@ -8781,12 +8252,27 @@ var Collapsible = Object.assign(CollapsibleRoot, {
8781
8252
  Trigger: CollapsibleTrigger,
8782
8253
  Content: CollapsibleContent
8783
8254
  });
8255
+ var SYMBOL = /[⌘⇧⌥⌃⎋⏎↵⌫⌦⇥↑↓←→]/u;
8256
+ function keyParts(text) {
8257
+ const parts = [];
8258
+ let run = "";
8259
+ for (const ch of Array.from(text)) {
8260
+ if (SYMBOL.test(ch)) {
8261
+ if (run.trim()) parts.push(run.trim());
8262
+ run = "";
8263
+ parts.push(ch);
8264
+ } else run += ch;
8265
+ }
8266
+ if (run.trim()) parts.push(run.trim());
8267
+ return parts;
8268
+ }
8784
8269
  function Kbd({ children, className }) {
8270
+ const parts = typeof children === "string" ? keyParts(children) : null;
8785
8271
  return /* @__PURE__ */ jsx(
8786
8272
  "kbd",
8787
8273
  {
8788
8274
  className: cn(
8789
- "inline-flex items-center justify-center min-w-[2.5em] px-ds-1.5 py-ds-0.5",
8275
+ "inline-flex items-center justify-center gap-[0.25em] min-w-[2.5em] px-ds-1.5 py-ds-0.5",
8790
8276
  "text-xs leading-none font-[family-name:var(--ds-font-mono)]",
8791
8277
  "text-[var(--ds-text-secondary)]",
8792
8278
  "bg-[var(--ds-bg-surface)]",
@@ -8795,7 +8281,7 @@ function Kbd({ children, className }) {
8795
8281
  "rounded-[var(--ds-radius-sm)]",
8796
8282
  className
8797
8283
  ),
8798
- children
8284
+ children: parts && parts.length > 1 ? parts.map((p, i) => /* @__PURE__ */ jsx("span", { children: p }, i)) : children
8799
8285
  }
8800
8286
  );
8801
8287
  }
@@ -9392,7 +8878,8 @@ function ChatInputBar({ onSend, busy, onStop, onAttach, onVoice, placeholder })
9392
8878
  className: cn(
9393
8879
  "flex flex-col",
9394
8880
  "rounded-[var(--ds-radius-lg)]",
9395
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
8881
+ // Sprint 25.14 A20: the composer is a field, its edge at 3:1 like every other control's
8882
+ "border-[length:var(--ds-border-width)] border-[var(--ds-input-border)]",
9396
8883
  "bg-[var(--ds-input-bg)]",
9397
8884
  "shadow-[var(--ds-input-shadow)]",
9398
8885
  FOCUS_WITHIN_RING,
@@ -9685,6 +9172,28 @@ function useCornerInset(corner, inset, priority, target) {
9685
9172
  return claimCornerInset(corner, inset, priority, el);
9686
9173
  }, [corner, inset, priority, target]);
9687
9174
  }
9175
+ function Sheet({ open, onClose: onCloseProp, title, children, footer, dirty, onDiscard, snapPoints = [0.5, 0.92], container, flush, keepMounted = false, id, labelledBy }) {
9176
+ const { marked, mark } = useOverlayMark(open);
9177
+ const guard = useDirtyGuard(dirty ?? marked, onCloseProp, onDiscard);
9178
+ return /* @__PURE__ */ jsx(
9179
+ DrawerPanel,
9180
+ {
9181
+ form: "sheet",
9182
+ open,
9183
+ onClose: guard.requestClose,
9184
+ title,
9185
+ footer,
9186
+ container,
9187
+ flush,
9188
+ snapPoints,
9189
+ keepMounted,
9190
+ id,
9191
+ labelledBy,
9192
+ confirm: /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
9193
+ children: /* @__PURE__ */ jsx(DrawerFooterCtx, { value: null, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: guard.requestClose, children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children }) }) })
9194
+ }
9195
+ );
9196
+ }
9688
9197
  var DEFAULT_OFFSET = "var(--ds-space-4)";
9689
9198
  var CORNER_PANEL_ANCHOR_INSET = "calc(var(--ds-space-12) + var(--ds-space-3))";
9690
9199
  function CornerPanel({
@@ -9966,8 +9475,10 @@ var StackedList = forwardRef(function StackedList2({
9966
9475
  selectedId,
9967
9476
  className,
9968
9477
  flush,
9969
- hover
9478
+ hover,
9479
+ pager
9970
9480
  }, ref) {
9481
+ useScrollsInside();
9971
9482
  const isPhone = useBreakpoint("sm");
9972
9483
  const inCard = useContext(CardSurfaceCtx);
9973
9484
  const inPane = useContext(SplitPaneCtx);
@@ -9979,20 +9490,22 @@ var StackedList = forwardRef(function StackedList2({
9979
9490
  console.warn('StackedList: `flush` and `hover` are deprecated \u2014 pass variant="articles" for an article list; the default variant takes its inset from the pane or card it sits in.');
9980
9491
  }, [legacy]);
9981
9492
  const articles = variant === "articles";
9493
+ useWholeListCheck("StackedList", items.length, pager);
9494
+ const shown = pagerPart(items, pager);
9982
9495
  const stackMeta = isPhone && articles;
9983
9496
  const selectable = selectedId !== void 0 || items.some((i) => i.selected !== void 0);
9984
9497
  const inset = articles ? inCard ? "px-ds-5" : "px-0" : inCard ? "px-ds-5" : inPane ? "pl-ds-3 pr-ds-4" : selectable ? "pl-ds-3 pr-0" : "px-0";
9985
- return /* @__PURE__ */ jsx(
9498
+ const list = /* @__PURE__ */ jsx(
9986
9499
  "ul",
9987
9500
  {
9988
9501
  ref,
9989
9502
  "data-ds-list": "",
9990
9503
  className: cn(
9991
9504
  divided && "divide-y-[length:var(--ds-border-width)] divide-[var(--ds-border-subtle)]",
9992
- className
9505
+ !pager && className
9993
9506
  ),
9994
9507
  role: "list",
9995
- children: items.map((item) => {
9508
+ children: shown.map((item) => {
9996
9509
  const isSelected = item.selected ?? (selectedId !== void 0 && item.id === selectedId);
9997
9510
  const interactive = !!item.onClick;
9998
9511
  const hasEmphasis = item.unread !== void 0;
@@ -10084,6 +9597,11 @@ var StackedList = forwardRef(function StackedList2({
10084
9597
  })
10085
9598
  }
10086
9599
  );
9600
+ if (!pager) return list;
9601
+ return /* @__PURE__ */ jsxs("div", { "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
9602
+ list,
9603
+ items.length > 0 && /* @__PURE__ */ jsx(Pagination, { ...pager.props })
9604
+ ] });
10087
9605
  });
10088
9606
  var TIME_ZOOMS = ["day", "week", "month", "quarter"];
10089
9607
  var DAY_MS = 864e5;
@@ -11119,7 +10637,7 @@ var SELECTED = "outline-solid outline-2 outline-offset-2 outline-[var(--ds-text-
11119
10637
  var UNDATED_INSET = 16;
11120
10638
  var NO_GROUPS = [];
11121
10639
  var NO_STATUSES = {};
11122
- var warned2 = /* @__PURE__ */ new Set();
10640
+ var warned = /* @__PURE__ */ new Set();
11123
10641
  function warnOnce2(message) {
11124
10642
  let dev = false;
11125
10643
  try {
@@ -11127,8 +10645,8 @@ function warnOnce2(message) {
11127
10645
  } catch {
11128
10646
  dev = false;
11129
10647
  }
11130
- if (!dev || warned2.has(message)) return;
11131
- warned2.add(message);
10648
+ if (!dev || warned.has(message)) return;
10649
+ warned.add(message);
11132
10650
  console.warn(`Gantt: ${message}`);
11133
10651
  }
11134
10652
  function GanttArrows({ arrows, width, height }) {
@@ -11192,6 +10710,7 @@ function Gantt({
11192
10710
  "aria-label": ariaLabel,
11193
10711
  className
11194
10712
  }) {
10713
+ useScrollsInside();
11195
10714
  const [zoomState, setZoomState] = useState(defaultZoom);
11196
10715
  const zoom = zoomProp ?? zoomState;
11197
10716
  const changeZoom = (next) => {
@@ -12009,14 +11528,17 @@ function TabButton({
12009
11528
  onClick: disabled ? void 0 : () => onTabChange(id),
12010
11529
  onKeyDown,
12011
11530
  className: cn(
12012
- "px-ds-4 py-ds-2.5 text-sm font-medium",
11531
+ // Sprint 25.14 A38: a flex box, so the label sits in the middle of the tab. On a text line an icon's baseline
11532
+ // pushed the line 3 px taller and the label to its top (R28-72); a tab with an icon is now as tall as one
11533
+ // without, as a truncating tab (flex already) always was.
11534
+ "flex items-center justify-center px-ds-4 py-ds-2.5 text-sm font-medium",
12013
11535
  // never wrap: the strip scrolls when the tabs do not fit (round 17, 54.4)
12014
11536
  !fullWidth && !truncate && "shrink-0 whitespace-nowrap",
12015
11537
  disabled ? "cursor-not-allowed opacity-disabled" : "cursor-pointer",
12016
11538
  "transition-colors duration-[var(--ds-transition-speed)]",
12017
11539
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
12018
11540
  fullWidth && "flex-1",
12019
- truncate && "flex-1 min-w-0 flex items-center",
11541
+ truncate && "flex-1 min-w-0 justify-start",
12020
11542
  "rounded-[var(--ds-nav-h-item-radius)]",
12021
11543
  isActive ? "bg-[var(--ds-nav-h-active-bg)] text-[var(--ds-nav-h-active-text)] border-[var(--ds-accent-primary)]" : "text-[var(--ds-nav-h-inactive-text)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-nav-h-hover-bg)]",
12022
11544
  className
@@ -12429,337 +11951,62 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
12429
11951
  {
12430
11952
  type: "button",
12431
11953
  "data-index": idx,
12432
- role: "menuitem",
12433
- tabIndex: idx === activeIdx ? 0 : -1,
12434
- className: cn(
12435
- "flex items-center w-full text-left text-sm cursor-pointer",
12436
- "px-ds-3 py-ds-1.5 transition-colors",
12437
- "text-[var(--ds-text-secondary)]",
12438
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
12439
- "focus-visible:outline-none focus-visible:bg-[var(--ds-bg-muted)] focus-visible:text-[var(--ds-text-primary)] focus-visible:shadow-[inset_var(--ds-shadow-focus)]",
12440
- gapClass
12441
- ),
12442
- onClick: () => {
12443
- setDropdownOpen(false);
12444
- ci.onClick?.();
12445
- },
12446
- children: [
12447
- showIcons && ci.icon && /* @__PURE__ */ jsx(Icon, { name: ci.icon, size: iconSize, className: "shrink-0" }),
12448
- ci.label
12449
- ]
12450
- },
12451
- idx
12452
- ))
12453
- }
12454
- ),
12455
- container ?? document.body
12456
- );
12457
- if (isContainedBleed) {
12458
- return /* @__PURE__ */ jsxs(
12459
- "nav",
12460
- {
12461
- ref,
12462
- "aria-label": "Breadcrumb",
12463
- className: cn(
12464
- "w-full bg-[var(--ds-bg-muted)]",
12465
- // Hairline under the band: it sits within ~1.2:1 of both canvas and a tinted header, so the edge needs a line.
12466
- "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
12467
- className
12468
- ),
12469
- children: [
12470
- /* @__PURE__ */ jsx(
12471
- "div",
12472
- {
12473
- className: cn("w-full mx-auto px-ds-6", size === "sm" ? "py-ds-1" : "py-ds-1.5"),
12474
- style: { maxWidth: contentMaxWidth ?? "var(--ds-bp-xl)" },
12475
- children: listContent
12476
- }
12477
- ),
12478
- dropdown
12479
- ]
12480
- }
12481
- );
12482
- }
12483
- return /* @__PURE__ */ jsxs("nav", { ref, "aria-label": "Breadcrumb", className: wrapperClasses, children: [
12484
- listContent,
12485
- dropdown
12486
- ] });
12487
- });
12488
- function Popover({ open, onOpenChange, trigger, children, side = "bottom", align = "center", width, className, container }) {
12489
- const triggerRef = useRef(null);
12490
- const floatingRef = useRef(null);
12491
- const [triggerWidth, setTriggerWidth] = useState(void 0);
12492
- const setTriggerNode = useCallback((node) => {
12493
- triggerRef.current = node;
12494
- setTriggerWidth(node?.offsetWidth);
12495
- }, []);
12496
- const pos = usePosition(triggerRef, floatingRef, open, { side, align, offset: 8, flip: true });
12497
- const onOpenChangeRef = useLatest(onOpenChange);
12498
- const { props: pressMarks, marked } = usePressMarks();
12499
- const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => onOpenChangeRef.current(false), tabExits: "edges", marked });
12500
- useEffect(() => {
12501
- if (!open) return;
12502
- const onKeyDown = (e) => {
12503
- if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
12504
- const active = document.activeElement;
12505
- if (active && active !== document.body && !triggerRef.current?.contains(active) && !floatingRef.current?.contains(active)) return;
12506
- e.preventDefault();
12507
- returnFocus();
12508
- onOpenChangeRef.current(false);
12509
- };
12510
- const onPress = (e) => {
12511
- const target = e.target;
12512
- if (marked(e) || triggerRef.current?.contains(target) || floatingRef.current?.contains(target)) return;
12513
- onOpenChangeRef.current(false);
12514
- };
12515
- document.addEventListener("keydown", onKeyDown);
12516
- document.addEventListener("mousedown", onPress);
12517
- return () => {
12518
- document.removeEventListener("keydown", onKeyDown);
12519
- document.removeEventListener("mousedown", onPress);
12520
- };
12521
- }, [open, onOpenChangeRef, isTopLayer, returnFocus, marked]);
12522
- useEffect(() => {
12523
- if (open) focusInitial(floatingRef.current);
12524
- }, [open]);
12525
- return /* @__PURE__ */ jsxs(Fragment, { children: [
12526
- /* @__PURE__ */ jsx(
12527
- Slot,
12528
- {
12529
- ...triggerType(trigger),
12530
- ref: setTriggerNode,
12531
- onClick: () => {
12532
- setTriggerWidth(triggerRef.current?.offsetWidth);
12533
- onOpenChange(!open);
12534
- },
12535
- children: trigger
12536
- }
12537
- ),
12538
- open && createPortal(
12539
- /* @__PURE__ */ jsx(
12540
- "div",
12541
- {
12542
- ref: floatingRef,
12543
- "data-gs": "popover",
12544
- ...pressMarks,
12545
- onClick: (e) => e.stopPropagation(),
12546
- className: cn(
12547
- "fixed z-[var(--ds-z-popover)]",
12548
- "bg-[var(--ds-bg-surface-elevated)]",
12549
- GLASS_DROPDOWN,
12550
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
12551
- "rounded-[var(--ds-radius-md)]",
12552
- className
12553
- ),
12554
- style: {
12555
- top: pos.top,
12556
- left: pos.left,
12557
- width: width === "trigger" ? triggerWidth : width === "auto" ? void 0 : width
12558
- },
12559
- children
12560
- }
12561
- ),
12562
- container ?? document.body
12563
- )
12564
- ] });
12565
- }
12566
- function Pagination(props) {
12567
- if ((props.mode === "load-more" || props.mode === "infinite-scroll") && props.onLoadMore) return /* @__PURE__ */ jsx(LoadMore, { ...props, onLoadMore: props.onLoadMore, scroll: props.mode === "infinite-scroll" });
12568
- return /* @__PURE__ */ jsx(PageNumbers, { ...props });
12569
- }
12570
- function contentTop(el) {
12571
- let top = el.getBoundingClientRect().top + window.scrollY;
12572
- for (let n = el.parentElement; n && n !== document.documentElement; n = n.parentElement) top += n.scrollTop;
12573
- return top;
12574
- }
12575
- function LoadMore({ hasMore, onLoadMore, loadMoreLabel, currentPage, scroll, className }) {
12576
- const marker = useRef(null);
12577
- const load = useLatest(onLoadMore);
12578
- const loadedAt = useRef(null);
12579
- useEffect(() => {
12580
- const el = marker.current;
12581
- if (!scroll || !hasMore || !el || typeof IntersectionObserver === "undefined") return;
12582
- const observer = new IntersectionObserver((entries) => {
12583
- if (!entries.some((e) => e.isIntersecting)) return;
12584
- const at = contentTop(el);
12585
- if (loadedAt.current !== null && Math.abs(at - loadedAt.current) < 1) return;
12586
- loadedAt.current = at;
12587
- load.current();
12588
- }, { rootMargin: "200px 0px" });
12589
- observer.observe(el);
12590
- const rearm = () => {
12591
- loadedAt.current = null;
12592
- };
12593
- window.addEventListener("scroll", rearm, { capture: true, passive: true });
12594
- return () => {
12595
- observer.disconnect();
12596
- window.removeEventListener("scroll", rearm, { capture: true });
12597
- };
12598
- }, [scroll, hasMore, currentPage, load]);
12599
- if (!hasMore) return null;
12600
- return /* @__PURE__ */ jsxs("div", { "data-ds-pager": "", "data-pagination-mode": scroll ? "infinite-scroll" : "load-more", className: cn("flex flex-col items-center", className), children: [
12601
- scroll && /* @__PURE__ */ jsx("div", { ref: marker, "data-load-more-trigger": "", "aria-hidden": "true", className: "h-px w-full" }),
12602
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onLoadMore, children: loadMoreLabel ?? "Load more" })
12603
- ] });
12604
- }
12605
- function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSizeProp, pageSizeOptions, onPageSizeChange, className }) {
12606
- const pages = buildPageRange(currentPage, totalPages);
12607
- const preferences = useShellPreferences();
12608
- const pageSize = pageSizeProp ?? preferences.itemsPerPage;
12609
- return /* @__PURE__ */ jsxs("nav", { "aria-label": "Pagination", "data-ds-pager": "", className: cn("flex flex-wrap items-center gap-ds-3", className), children: [
12610
- pageSizeOptions && pageSize != null && onPageSizeChange && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12611
- /* @__PURE__ */ jsx("span", { className: "text-sm text-[var(--ds-text-muted)] whitespace-nowrap", children: "Rows per page" }),
12612
- /* @__PURE__ */ jsx(FormChangeCtx, { value: null, children: /* @__PURE__ */ jsx(
12613
- Dropdown,
12614
- {
12615
- options: pageSizeOptions.map((n) => ({ id: String(n), label: String(n) })),
12616
- value: String(pageSize),
12617
- onChange: (v) => onPageSizeChange(Number(v)),
12618
- size: "sm",
12619
- className: "w-[4.5rem]"
12620
- }
12621
- ) })
12622
- ] }),
12623
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-1", children: [
12624
- /* @__PURE__ */ jsx(
12625
- "button",
12626
- {
12627
- type: "button",
12628
- onClick: () => onPageChange(currentPage - 1),
12629
- disabled: currentPage <= 1,
12630
- "aria-label": "Previous page",
12631
- className: cn(
12632
- "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-sm cursor-pointer",
12633
- "rounded-[var(--ds-radius-md)]",
12634
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
12635
- "bg-[var(--ds-bg-surface)] text-[var(--ds-text-secondary)]",
12636
- "transition-colors duration-[var(--ds-transition-speed)]",
12637
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
12638
- "disabled:opacity-disabled disabled:cursor-not-allowed"
12639
- ),
12640
- children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 16, className: "rotate-180" })
12641
- }
12642
- ),
12643
- pages.map(
12644
- (p, i) => p === "..." ? /* @__PURE__ */ jsx(EllipsisJumper, { totalPages, onPageChange }, `ellipsis-${i}`) : /* @__PURE__ */ jsx(
12645
- "button",
12646
- {
12647
- type: "button",
12648
- onClick: () => onPageChange(p),
12649
- "aria-current": p === currentPage ? "page" : void 0,
11954
+ role: "menuitem",
11955
+ tabIndex: idx === activeIdx ? 0 : -1,
12650
11956
  className: cn(
12651
- "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-sm font-medium cursor-pointer",
12652
- "rounded-[var(--ds-radius-md)]",
12653
- "border-[length:var(--ds-border-width)]",
12654
- "transition-colors duration-[var(--ds-transition-speed)]",
12655
- p === currentPage ? "bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)] text-[var(--ds-text-on-primary)] border-[var(--ds-accent-primary)]" : "bg-[var(--ds-bg-surface)] text-[var(--ds-text-secondary)] border-[var(--ds-border-default)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
11957
+ "flex items-center w-full text-left text-sm cursor-pointer",
11958
+ "px-ds-3 py-ds-1.5 transition-colors",
11959
+ "text-[var(--ds-text-secondary)]",
11960
+ "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
11961
+ "focus-visible:outline-none focus-visible:bg-[var(--ds-bg-muted)] focus-visible:text-[var(--ds-text-primary)] focus-visible:shadow-[inset_var(--ds-shadow-focus)]",
11962
+ gapClass
12656
11963
  ),
12657
- children: p
11964
+ onClick: () => {
11965
+ setDropdownOpen(false);
11966
+ ci.onClick?.();
11967
+ },
11968
+ children: [
11969
+ showIcons && ci.icon && /* @__PURE__ */ jsx(Icon, { name: ci.icon, size: iconSize, className: "shrink-0" }),
11970
+ ci.label
11971
+ ]
12658
11972
  },
12659
- p
12660
- )
12661
- ),
12662
- /* @__PURE__ */ jsx(
12663
- "button",
12664
- {
12665
- type: "button",
12666
- onClick: () => onPageChange(currentPage + 1),
12667
- disabled: currentPage >= totalPages,
12668
- "aria-label": "Next page",
12669
- className: cn(
12670
- "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-sm cursor-pointer",
12671
- "rounded-[var(--ds-radius-md)]",
12672
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
12673
- "bg-[var(--ds-bg-surface)] text-[var(--ds-text-secondary)]",
12674
- "transition-colors duration-[var(--ds-transition-speed)]",
12675
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
12676
- "disabled:opacity-disabled disabled:cursor-not-allowed"
12677
- ),
12678
- children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 16 })
12679
- }
12680
- )
12681
- ] })
12682
- ] });
12683
- }
12684
- function buildPageRange(current, total) {
12685
- if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
12686
- const pages = [1];
12687
- if (current > 3) pages.push("...");
12688
- const start = Math.max(2, current - 1);
12689
- const end = Math.min(total - 1, current + 1);
12690
- for (let i = start; i <= end; i++) pages.push(i);
12691
- if (current < total - 2) pages.push("...");
12692
- pages.push(total);
12693
- return pages;
12694
- }
12695
- function EllipsisJumper({ totalPages, onPageChange }) {
12696
- const [open, setOpen] = useState(false);
12697
- const [jumpPage, setJumpPage] = useState(1);
12698
- const handleOpenChange = (next) => {
12699
- if (next) setJumpPage(1);
12700
- setOpen(next);
12701
- };
12702
- const handleGo = () => {
12703
- const clamped = Math.min(totalPages, Math.max(1, jumpPage));
12704
- onPageChange(clamped);
12705
- setOpen(false);
12706
- };
12707
- return /* @__PURE__ */ jsx(
12708
- Popover,
12709
- {
12710
- open,
12711
- onOpenChange: handleOpenChange,
12712
- trigger: /* @__PURE__ */ jsx(
12713
- "button",
12714
- {
12715
- type: "button",
12716
- "aria-label": "Go to page",
12717
- onClick: () => handleOpenChange(true),
12718
- className: cn(
12719
- "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-sm cursor-pointer",
12720
- "rounded-[var(--ds-radius-md)]",
12721
- "text-[var(--ds-text-muted)]",
12722
- "transition-colors duration-[var(--ds-transition-speed)]",
12723
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
12724
- ),
12725
- children: "\u2026"
12726
- }
12727
- ),
12728
- children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-ds-2 p-ds-3", children: [
12729
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-[var(--ds-text-secondary)]", children: "Go to page" }),
12730
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
11973
+ idx
11974
+ ))
11975
+ }
11976
+ ),
11977
+ container ?? document.body
11978
+ );
11979
+ if (isContainedBleed) {
11980
+ return /* @__PURE__ */ jsxs(
11981
+ "nav",
11982
+ {
11983
+ ref,
11984
+ "aria-label": "Breadcrumb",
11985
+ className: cn(
11986
+ "w-full bg-[var(--ds-bg-muted)]",
11987
+ // Hairline under the band: it sits within ~1.2:1 of both canvas and a tinted header, so the edge needs a line.
11988
+ "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11989
+ className
11990
+ ),
11991
+ children: [
12731
11992
  /* @__PURE__ */ jsx(
12732
- NumberInput,
11993
+ "div",
12733
11994
  {
12734
- value: jumpPage,
12735
- onChange: setJumpPage,
12736
- min: 1,
12737
- max: totalPages,
12738
- step: 1,
12739
- size: "sm",
12740
- className: "w-[5rem]"
11995
+ className: cn("w-full mx-auto px-ds-6", size === "sm" ? "py-ds-1" : "py-ds-1.5"),
11996
+ style: { maxWidth: contentMaxWidth ?? "var(--ds-bp-xl)" },
11997
+ children: listContent
12741
11998
  }
12742
11999
  ),
12743
- /* @__PURE__ */ jsx(
12744
- "button",
12745
- {
12746
- type: "button",
12747
- onClick: handleGo,
12748
- className: cn(
12749
- "px-ds-2.5 h-[var(--ds-control-height-sm)] text-xs font-medium cursor-pointer",
12750
- "rounded-[var(--ds-radius-md)]",
12751
- "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]",
12752
- "transition-colors duration-[var(--ds-transition-speed)]",
12753
- "hover:opacity-90"
12754
- ),
12755
- children: "Go"
12756
- }
12757
- )
12758
- ] })
12759
- ] })
12760
- }
12761
- );
12762
- }
12000
+ dropdown
12001
+ ]
12002
+ }
12003
+ );
12004
+ }
12005
+ return /* @__PURE__ */ jsxs("nav", { ref, "aria-label": "Breadcrumb", className: wrapperClasses, children: [
12006
+ listContent,
12007
+ dropdown
12008
+ ] });
12009
+ });
12763
12010
  var HeaderTabRowContext = createContext(null);
12764
12011
  function useHeaderTabRow() {
12765
12012
  return useContext(HeaderTabRowContext);
@@ -14498,7 +13745,8 @@ function pageMaxWidth(resolved) {
14498
13745
  }
14499
13746
  var paddingClasses = {
14500
13747
  none: "",
14501
- default: "px-ds-6",
13748
+ // the page's gutter, which an overlay's header, body and footer share (Sprint 25.14 A36)
13749
+ default: "px-[var(--ds-page-gutter)]",
14502
13750
  wide: "px-ds-10"
14503
13751
  };
14504
13752
  var verticalPaddingClasses = {
@@ -14826,10 +14074,18 @@ function Caption({ children, onLight, onTint }) {
14826
14074
  onLight ? "text-[var(--ds-text-on-primary)]/80" : onTint ? "text-[var(--ds-text-muted-on-tint)]" : "text-[var(--ds-text-secondary)]"
14827
14075
  ), children });
14828
14076
  }
14077
+ var ON_BAND_STRIP = cn(
14078
+ "rounded-[var(--ds-radius-md)] px-ds-3 py-ds-2 border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
14079
+ "bg-[var(--ds-bg-surface)] supports-[color:oklch(from_red_l_c_h)]:bg-[oklch(from_var(--ds-bg-surface)_l_c_h_/_max(alpha,min(1,alpha*1000)*0.6))]",
14080
+ GLASS_SURFACE
14081
+ );
14829
14082
  function TabRow({ children, hasCaption, className, onBand }) {
14830
14083
  const [row, setRow] = useState(null);
14831
14084
  const value = useMemo(() => ({ row, hasCaption }), [row, hasCaption]);
14832
- return /* @__PURE__ */ jsx("div", { ref: setRow, className: cn(className, onBand && "mx-0 rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)] px-ds-3 py-ds-2"), children: /* @__PURE__ */ jsx(HeaderTabRowContext.Provider, { value, children }) });
14085
+ return /* @__PURE__ */ jsx("div", { ref: setRow, className: cn(className, onBand && cn("mx-0", ON_BAND_STRIP)), children: /* @__PURE__ */ jsx(HeaderTabRowContext.Provider, { value, children }) });
14086
+ }
14087
+ function FilterRow({ children, className, onBand }) {
14088
+ return /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx(FilterRowContext.Provider, { value: true, children: /* @__PURE__ */ jsx("div", { "data-gs": "filter-row", className: cn("flex items-center flex-wrap gap-ds-3", className, onBand && cn("w-fit max-w-full", ON_BAND_STRIP)), children }) }) });
14833
14089
  }
14834
14090
  var FLOATING_HEADER = cn(
14835
14091
  "rounded-[var(--ds-radius-md)]",
@@ -15117,7 +14373,7 @@ function DefaultHeader({
15117
14373
  ] }),
15118
14374
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
15119
14375
  ] }),
15120
- filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
14376
+ filters && /* @__PURE__ */ jsx(FilterRow, { className: "mt-ds-4", children: filters }),
15121
14377
  tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", children: tabs })
15122
14378
  ] })
15123
14379
  ]
@@ -15229,7 +14485,9 @@ function ColourfulHeader({
15229
14485
  style: {
15230
14486
  maxWidth: gutter ? contentMaxWidth ?? "var(--ds-bp-xl)" : void 0,
15231
14487
  containerType: "inline-size",
15232
- ...overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : {}
14488
+ // under the overlap, the gap from the header's last row to the cards is the page's own (space-6): the band's
14489
+ // bottom edge counts in it (the owner, 30 September: 26 px above the cards, 24 below)
14490
+ ...overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px) - ${isBold && !isFloating ? "2px" : "var(--ds-border-width)"})` } : {}
15233
14491
  },
15234
14492
  children: [
15235
14493
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight: isBold }),
@@ -15258,7 +14516,7 @@ function ColourfulHeader({
15258
14516
  ] }),
15259
14517
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
15260
14518
  ] }),
15261
- filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
14519
+ filters && /* @__PURE__ */ jsx(FilterRow, { className: "mt-ds-4", onBand: isBold, children: filters }),
15262
14520
  tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", onBand: isBold, children: tabs })
15263
14521
  ]
15264
14522
  }
@@ -15444,7 +14702,7 @@ function SnappingHeader({
15444
14702
  actions && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }) })
15445
14703
  ] }),
15446
14704
  description && /* @__PURE__ */ jsx(ClampedText, { className: "mt-ds-1 text-sm text-[var(--ds-text-secondary)]", children: description }),
15447
- filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-3", children: filters }) }),
14705
+ filters && /* @__PURE__ */ jsx(FilterRow, { className: "mt-ds-3", children: filters }),
15448
14706
  expandedContent && /* @__PURE__ */ jsx("div", { className: "mt-ds-3", children: expandedContent }),
15449
14707
  tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-3 -mx-ds-1", children: tabs })
15450
14708
  ] }) : /* @__PURE__ */ jsxs(
@@ -15458,7 +14716,7 @@ function SnappingHeader({
15458
14716
  hasBreadcrumbs && !hideExpandedBreadcrumbs ? "pt-ds-3" : "pt-ds-6"
15459
14717
  ]
15460
14718
  ),
15461
- style: !collapsed && isColourful && overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : void 0,
14719
+ style: !collapsed && isColourful && overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px) - ${isBoldColourful && !(isFloating && !wrapperHasCard) ? "2px" : "var(--ds-border-width)"})` } : void 0,
15462
14720
  children: [
15463
14721
  !collapsed && focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight }),
15464
14722
  !collapsed && hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, onLight, onTint, pill: breadcrumbPill, separator: breadcrumbSeparator }),
@@ -15513,7 +14771,7 @@ function SnappingHeader({
15513
14771
  ] }),
15514
14772
  actions && (collapsed ? /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }) }) : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }))
15515
14773
  ] }),
15516
- !collapsed && filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
14774
+ !collapsed && filters && /* @__PURE__ */ jsx(FilterRow, { className: "mt-ds-4", onBand: isBoldColourful, children: filters }),
15517
14775
  !collapsed && expandedContent && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: expandedContent }),
15518
14776
  !collapsed && tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", onBand: isBoldColourful, children: tabs })
15519
14777
  ]
@@ -15750,6 +15008,73 @@ function Dock({
15750
15008
  }
15751
15009
  );
15752
15010
  }
15011
+ function resolveNavItemType(type) {
15012
+ return type === "default" ? "tint" : type;
15013
+ }
15014
+ var NavbarTypeContext = createContext("tint");
15015
+ var NavItem = forwardRef(function NavItem2({ label, icon, active = false, href, onClick, type: typeProp, className, children }, ref) {
15016
+ const contextType = useContext(NavbarTypeContext);
15017
+ const type = resolveNavItemType(typeProp ?? contextType);
15018
+ const baseClasses = cn(
15019
+ "inline-flex items-center gap-ds-2 cursor-pointer select-none whitespace-nowrap",
15020
+ // one line: the navbar measures each link (round 12)
15021
+ "transition-colors duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
15022
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
15023
+ "text-[length:var(--ds-font-size-sm)]"
15024
+ );
15025
+ const typeClasses = getTypeClasses(type, active);
15026
+ return /* @__PURE__ */ jsxs(
15027
+ "a",
15028
+ {
15029
+ ref,
15030
+ href: href ?? "#",
15031
+ onClick,
15032
+ "aria-current": active ? "page" : void 0,
15033
+ className: cn(baseClasses, typeClasses, className),
15034
+ children: [
15035
+ icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16 }),
15036
+ children ?? label,
15037
+ type === "underline" && active && /* @__PURE__ */ jsx(NavUnderline, {})
15038
+ ]
15039
+ }
15040
+ );
15041
+ });
15042
+ function getTypeClasses(type, active) {
15043
+ switch (type) {
15044
+ case "pill":
15045
+ return cn(
15046
+ "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
15047
+ active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
15048
+ );
15049
+ case "underline":
15050
+ return cn(
15051
+ "px-ds-2 py-ds-2 relative",
15052
+ active ? "text-[var(--ds-text-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]"
15053
+ );
15054
+ case "minimal":
15055
+ return cn(
15056
+ "px-ds-2 py-ds-1",
15057
+ active ? "text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]"
15058
+ );
15059
+ case "tint":
15060
+ return cn(
15061
+ "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
15062
+ active ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
15063
+ );
15064
+ }
15065
+ }
15066
+ function NavUnderline() {
15067
+ return /* @__PURE__ */ jsx(
15068
+ "span",
15069
+ {
15070
+ className: "absolute bottom-0 left-ds-1 right-ds-1 rounded-t-full bg-[var(--ds-accent-primary)]",
15071
+ style: { height: "2px" }
15072
+ }
15073
+ );
15074
+ }
15075
+ function navItemClasses(type, active) {
15076
+ return cn("text-[length:var(--ds-font-size-sm)]", getTypeClasses(type, active));
15077
+ }
15753
15078
  function TruncSpan({ children, className }) {
15754
15079
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
15755
15080
  const el = /* @__PURE__ */ jsx("span", { ref, className: cn("truncate", className), children });
@@ -15760,7 +15085,7 @@ function NavMenu({
15760
15085
  label,
15761
15086
  items,
15762
15087
  active = false,
15763
- type = "tint",
15088
+ type: typeProp,
15764
15089
  onItemClick,
15765
15090
  className
15766
15091
  }) {
@@ -15808,27 +15133,14 @@ function NavMenu({
15808
15133
  const hasActiveChild = active || items.some(
15809
15134
  (it) => it.active || it.children?.some((c) => c.active)
15810
15135
  );
15136
+ const contextType = useContext(NavbarTypeContext);
15137
+ const type = resolveNavItemType(typeProp ?? contextType);
15811
15138
  const isUnderline = type === "underline";
15812
- const isMinimal = type === "minimal";
15813
- const isPill = type === "pill";
15814
15139
  const triggerClasses = cn(
15815
- "inline-flex items-center gap-ds-1 cursor-pointer transition-colors whitespace-nowrap",
15140
+ "inline-flex items-center gap-ds-1 cursor-pointer select-none whitespace-nowrap",
15141
+ "transition-colors duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
15816
15142
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
15817
- isUnderline ? cn(
15818
- "px-ds-3 py-ds-2 text-sm border-b-2 -mb-[2px]",
15819
- hasActiveChild ? "border-[var(--ds-accent-primary)] text-[var(--ds-text-primary)] font-medium" : "border-transparent text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:border-[var(--ds-border-default)]"
15820
- ) : isMinimal ? cn(
15821
- "px-ds-2 py-ds-1 text-sm",
15822
- hasActiveChild ? "text-[var(--ds-text-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)]"
15823
- ) : isPill ? cn(
15824
- "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)]",
15825
- hasActiveChild ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
15826
- ) : cn(
15827
- // tint (and the deprecated `default` alias) — the same soft accent fill
15828
- // NavItem uses, so a menu trigger and a link read as one row (decision 30).
15829
- "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)]",
15830
- hasActiveChild ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
15831
- ),
15143
+ navItemClasses(type, hasActiveChild),
15832
15144
  className
15833
15145
  );
15834
15146
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -15850,7 +15162,8 @@ function NavMenu({
15850
15162
  size: 12,
15851
15163
  className: cn("transition-transform", open && "rotate-180")
15852
15164
  }
15853
- )
15165
+ ),
15166
+ isUnderline && hasActiveChild && /* @__PURE__ */ jsx(NavUnderline, {})
15854
15167
  ]
15855
15168
  }
15856
15169
  ),
@@ -15961,67 +15274,6 @@ function NavMenuGroup({
15961
15274
  )) })
15962
15275
  ] });
15963
15276
  }
15964
- function resolveNavItemType(type) {
15965
- return type === "default" ? "tint" : type;
15966
- }
15967
- var NavbarTypeContext = createContext("tint");
15968
- var NavItem = forwardRef(function NavItem2({ label, icon, active = false, href, onClick, type: typeProp, className, children }, ref) {
15969
- const contextType = useContext(NavbarTypeContext);
15970
- const type = resolveNavItemType(typeProp ?? contextType);
15971
- const baseClasses = cn(
15972
- "inline-flex items-center gap-ds-2 cursor-pointer select-none whitespace-nowrap",
15973
- // one line: the navbar measures each link (round 12)
15974
- "transition-colors duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
15975
- "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
15976
- "text-[length:var(--ds-font-size-sm)]"
15977
- );
15978
- const typeClasses = getTypeClasses(type, active);
15979
- return /* @__PURE__ */ jsxs(
15980
- "a",
15981
- {
15982
- ref,
15983
- href: href ?? "#",
15984
- onClick,
15985
- "aria-current": active ? "page" : void 0,
15986
- className: cn(baseClasses, typeClasses, className),
15987
- children: [
15988
- icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16 }),
15989
- children ?? label,
15990
- type === "underline" && active && /* @__PURE__ */ jsx(
15991
- "span",
15992
- {
15993
- className: "absolute bottom-0 left-ds-1 right-ds-1 rounded-t-full bg-[var(--ds-accent-primary)]",
15994
- style: { height: "2px" }
15995
- }
15996
- )
15997
- ]
15998
- }
15999
- );
16000
- });
16001
- function getTypeClasses(type, active) {
16002
- switch (type) {
16003
- case "pill":
16004
- return cn(
16005
- "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
16006
- active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
16007
- );
16008
- case "underline":
16009
- return cn(
16010
- "px-ds-2 py-ds-2 relative",
16011
- active ? "text-[var(--ds-text-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]"
16012
- );
16013
- case "minimal":
16014
- return cn(
16015
- "px-ds-2 py-ds-1",
16016
- active ? "text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]"
16017
- );
16018
- case "tint":
16019
- return cn(
16020
- "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
16021
- active ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
16022
- );
16023
- }
16024
- }
16025
15277
  var DEFAULT_BUTTON_ATTR = "data-dialog-default-button";
16026
15278
  var sizeClasses = {
16027
15279
  // round 16 (the owner's item 5): the small dialog is 448 px, not 384 — a delete confirmation with a typed field was cramped
@@ -16042,6 +15294,7 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
16042
15294
  const dialogRef = useRef(null);
16043
15295
  const opener = useRef(null);
16044
15296
  const defaultButtonRef = useRef(null);
15297
+ const bodyRef = useScrollbarInset();
16045
15298
  const onCloseRef = useLatest(onClose);
16046
15299
  const onSubmitRef = useLatest(onSubmit);
16047
15300
  const isTopLayer = useOverlayLayer(open);
@@ -16087,7 +15340,7 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
16087
15340
  "div",
16088
15341
  {
16089
15342
  ref: overlayRef,
16090
- className: "fixed inset-0 z-[var(--ds-z-modal)] flex items-center justify-center p-ds-4",
15343
+ className: cn("fixed inset-0 z-[var(--ds-z-modal)] flex items-center justify-center", !isMobile && "p-ds-4"),
16091
15344
  children: [
16092
15345
  /* @__PURE__ */ jsx(
16093
15346
  "div",
@@ -16139,29 +15392,20 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
16139
15392
  "aria-describedby": description ? descriptionId : void 0,
16140
15393
  style: { containerType: "inline-size" },
16141
15394
  className: cn(
16142
- "relative z-10 w-full",
15395
+ // a column: the title row, a body that scrolls when it is taller than the room, and the footer (Sprint 25.14,
15396
+ // with A36's full-screen phone dialog: its content ran past the screen with the footer cut off)
15397
+ "relative z-10 w-full flex flex-col max-h-full",
16143
15398
  isMobile ? "max-w-none h-full" : sizeClasses[size],
16144
15399
  OVERLAY_SURFACE,
16145
15400
  "text-[var(--ds-text-primary)]",
16146
15401
  GLASS_OVERLAY,
16147
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
16148
- isMobile ? "rounded-none" : "rounded-[var(--ds-radius-xl)]",
15402
+ // full screen on a phone: no edge to draw, so the content sits at the gutter from the screen's edge
15403
+ isMobile ? "rounded-none" : "rounded-[var(--ds-radius-xl)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
16149
15404
  "overflow-hidden",
16150
15405
  "animate-in fade-in-0 zoom-in-95"
16151
15406
  ),
16152
15407
  children: [
16153
- /* @__PURE__ */ jsx(
16154
- "button",
16155
- {
16156
- type: "button",
16157
- onClick: onClose,
16158
- ...{ [OVERLAY_CLOSE_ATTR]: "" },
16159
- className: "absolute right-4 top-4 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
16160
- "aria-label": "Close",
16161
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
16162
- }
16163
- ),
16164
- /* @__PURE__ */ jsx("div", { className: "p-ds-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-ds-3", children: [
15408
+ /* @__PURE__ */ jsxs("div", { className: cn("shrink-0 flex items-start gap-ds-3 pt-ds-6 px-[var(--ds-page-gutter)]", children ? "pb-ds-4" : "pb-ds-6"), children: [
16165
15409
  variant === "destructive" && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center shrink-0 w-10 h-10 rounded-[var(--ds-radius-full)] bg-[var(--ds-status-error-bg)]", children: /* @__PURE__ */ jsx(Icon, { name: "warning", size: 20, className: "text-[var(--ds-status-error)]" }) }),
16166
15410
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
16167
15411
  /* @__PURE__ */ jsx(
@@ -16172,16 +15416,39 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
16172
15416
  children: title
16173
15417
  }
16174
15418
  ),
16175
- description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-ds-2 text-sm text-[var(--ds-text-muted)]", children: description }),
16176
- children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) }) })
16177
- ] })
16178
- ] }) }),
15419
+ description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-ds-2 text-sm text-[var(--ds-text-muted)]", children: description })
15420
+ ] }),
15421
+ /* @__PURE__ */ jsx(
15422
+ "button",
15423
+ {
15424
+ type: "button",
15425
+ onClick: onClose,
15426
+ ...{ [OVERLAY_CLOSE_ATTR]: "" },
15427
+ className: "shrink-0 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
15428
+ "aria-label": "Close",
15429
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
15430
+ }
15431
+ )
15432
+ ] }),
15433
+ children && /* @__PURE__ */ jsx(
15434
+ "div",
15435
+ {
15436
+ ref: bodyRef,
15437
+ className: "min-h-0 flex-[0_1_auto] overflow-y-auto pb-ds-6",
15438
+ style: {
15439
+ paddingLeft: variant === "destructive" ? "calc(var(--ds-page-gutter) + 2.5rem + var(--ds-space-3))" : "var(--ds-page-gutter)",
15440
+ paddingRight: insetLessScrollbar("var(--ds-page-gutter)")
15441
+ },
15442
+ children: /* @__PURE__ */ jsx(SurfaceBoundary, { children: /* @__PURE__ */ jsx(DrawerFooterCtx, { value: null, children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) }) }) })
15443
+ }
15444
+ ),
15445
+ isMobile && /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "flex-1 min-h-0" }),
16179
15446
  /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
16180
15447
  footer && /* @__PURE__ */ jsx("div", { className: cn(
16181
- "flex justify-end gap-ds-2 p-ds-4",
15448
+ "shrink-0 flex justify-end gap-ds-2 py-ds-4 px-[var(--ds-page-gutter)]",
16182
15449
  "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
16183
15450
  "bg-[var(--ds-bg-muted)]",
16184
- "rounded-b-[var(--ds-radius-xl)]"
15451
+ !isMobile && "rounded-b-[var(--ds-radius-xl)]"
16185
15452
  ), children: footer })
16186
15453
  ]
16187
15454
  }
@@ -17468,67 +16735,6 @@ function getPopoverPosition(rect, placement, padding) {
17468
16735
  top = Math.max(8, Math.min(top, window.innerHeight - popH - 8));
17469
16736
  return { top, left };
17470
16737
  }
17471
- function firstKey(item) {
17472
- if (item !== null && typeof item === "object") return "id" in item ? item.id : void 0;
17473
- return item;
17474
- }
17475
- function signature(list, resetOn) {
17476
- let reset = "";
17477
- try {
17478
- reset = JSON.stringify(resetOn ?? null);
17479
- } catch {
17480
- reset = String(resetOn);
17481
- }
17482
- let first = "";
17483
- try {
17484
- first = JSON.stringify(firstKey(list[0]) ?? null);
17485
- } catch {
17486
- first = "";
17487
- }
17488
- return `${list.length}|${first}|${reset}`;
17489
- }
17490
- function usePagination(list, options = {}) {
17491
- const shell = useShellPreferences();
17492
- const style = options.style ?? shell.paginationStyle ?? PREFERENCE_HOUSE_DEFAULTS.paginationStyle;
17493
- const [chosenSize, setChosenSize] = useState(null);
17494
- const pageSize = Math.max(1, Math.floor(chosenSize ?? options.pageSize ?? shell.itemsPerPage ?? PREFERENCE_HOUSE_DEFAULTS.itemsPerPage));
17495
- const [page, setPageState] = useState(1);
17496
- const sig = signature(list, options.resetOn);
17497
- const [seen, setSeen] = useState(sig);
17498
- if (seen !== sig) {
17499
- setSeen(sig);
17500
- if (page !== 1) setPageState(1);
17501
- }
17502
- const totalItems = list.length;
17503
- const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
17504
- const current = Math.min(Math.max(1, seen !== sig ? 1 : page), totalPages);
17505
- const grows = style !== "pagination";
17506
- const start = grows ? 0 : (current - 1) * pageSize;
17507
- const items = list.slice(start, current * pageSize);
17508
- const range2 = items.length === 0 ? [0, 0] : [start + 1, start + items.length];
17509
- const hasMore = current < totalPages;
17510
- const setPage = (next) => setPageState(Math.min(Math.max(1, Math.floor(next)), totalPages));
17511
- const loadMore = () => setPageState((p) => Math.min(p + 1, totalPages));
17512
- const setPageSize = (size) => {
17513
- setChosenSize(Math.max(1, Math.floor(size)));
17514
- setPageState(1);
17515
- };
17516
- return {
17517
- style,
17518
- items,
17519
- page: current,
17520
- pageSize,
17521
- totalItems,
17522
- totalPages,
17523
- range: range2,
17524
- shown: items.length,
17525
- hasMore,
17526
- loadMore,
17527
- setPage,
17528
- setPageSize,
17529
- props: { currentPage: current, totalPages, onPageChange: setPage, pageSize, onPageSizeChange: setPageSize, mode: style, hasMore, onLoadMore: loadMore }
17530
- };
17531
- }
17532
16738
  function useDestructiveAction(options) {
17533
16739
  const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, doneMessage, undoMessage, restoredMessage, severity } = options;
17534
16740
  const preferences = useShellPreferences();
@@ -17675,7 +16881,7 @@ var PAST_TENSE = {
17675
16881
  reject: "Rejected",
17676
16882
  decline: "Declined"
17677
16883
  };
17678
- var warned3 = /* @__PURE__ */ new Set();
16884
+ var warned2 = /* @__PURE__ */ new Set();
17679
16885
  function pastTenseOf(label) {
17680
16886
  const words = label.trim().toLowerCase().split(/\s+/);
17681
16887
  const found = PAST_TENSE[words.slice(0, 2).join(" ")] ?? PAST_TENSE[words[0] ?? ""];
@@ -17686,8 +16892,8 @@ function pastTenseOf(label) {
17686
16892
  } catch {
17687
16893
  dev = false;
17688
16894
  }
17689
- if (dev && !warned3.has(label)) {
17690
- warned3.add(label);
16895
+ if (dev && !warned2.has(label)) {
16896
+ warned2.add(label);
17691
16897
  console.warn(`useDestructiveAction: "${label}" isn't a verb the hook knows; pass doneMessage so the confirmation says what happened.`);
17692
16898
  }
17693
16899
  return "Done";
@@ -17832,6 +17038,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
17832
17038
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
17833
17039
  });
17834
17040
 
17835
- export { Accordion, ActionPanel, ActionPanelGroup, AddAnother, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardGrid, 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, Gantt, 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, TIME_ZOOMS, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, dateOfDay, dayOf, endDayOf, fitLabel, fitLabels, formatDateRange, timeExtent, useContentWidth, useDestructiveAction, useFittedLabels, usePagination, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
17041
+ export { Accordion, ActionPanel, ActionPanelGroup, AddAnother, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, DropdownMenu, EmptyState, EntityCell, FileUpload, FormWrapper, Gantt, HoverCard, Kbd, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, PageContainer, PageHeader, PinInput, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sheet, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, Toast, ToastProvider, Tour, TransferList, TreeView, dateOfDay, dayOf, endDayOf, fitLabel, fitLabels, formatDateRange, timeExtent, useContentWidth, useDestructiveAction, useFittedLabels, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
17836
17042
  //# sourceMappingURL=index.js.map
17837
17043
  //# sourceMappingURL=index.js.map