gridsmith-ui 0.21.0 → 0.22.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 (41) hide show
  1. package/dist/chat.js +3 -3
  2. package/dist/chunk-2RV23OBK.js +1349 -0
  3. package/dist/chunk-2RV23OBK.js.map +1 -0
  4. package/dist/chunk-7TOJ4G7S.js +197 -0
  5. package/dist/chunk-7TOJ4G7S.js.map +1 -0
  6. package/dist/{chunk-GGVASDXT.js → chunk-AAYCQXZQ.js} +8 -155
  7. package/dist/chunk-AAYCQXZQ.js.map +1 -0
  8. package/dist/{chunk-ILIODA7W.js → chunk-CXMV7SXJ.js} +4 -3
  9. package/dist/chunk-CXMV7SXJ.js.map +1 -0
  10. package/dist/{chunk-EQDR57AM.js → chunk-EYMGLK4M.js} +3 -3
  11. package/dist/{chunk-EQDR57AM.js.map → chunk-EYMGLK4M.js.map} +1 -1
  12. package/dist/chunk-IVCVNAXG.js +115 -0
  13. package/dist/chunk-IVCVNAXG.js.map +1 -0
  14. package/dist/chunk-RG4KUCRS.js +9 -0
  15. package/dist/chunk-RG4KUCRS.js.map +1 -0
  16. package/dist/{chunk-XYDIBW3J.js → chunk-SBTBMAIU.js} +4 -3
  17. package/dist/chunk-SBTBMAIU.js.map +1 -0
  18. package/dist/data-grid.d.ts +38 -4
  19. package/dist/data-grid.js +304 -264
  20. package/dist/data-grid.js.map +1 -1
  21. package/dist/editor.js +1 -1
  22. package/dist/index.d.ts +94 -139
  23. package/dist/index.js +794 -1616
  24. package/dist/index.js.map +1 -1
  25. package/dist/kanban.js +2 -2
  26. package/dist/resizable.js +5 -4
  27. package/dist/resizable.js.map +1 -1
  28. package/dist/roots.css +10 -0
  29. package/dist/{table-utils-GounRE6s.d.ts → table-utils-BkjNEWts.d.ts} +5 -1
  30. package/dist/use-pagination-DiJo0aW5.d.ts +153 -0
  31. package/dist/virtualized.d.ts +11 -1
  32. package/dist/virtualized.js +67 -18
  33. package/dist/virtualized.js.map +1 -1
  34. package/package.json +1 -1
  35. package/dist/chunk-GGVASDXT.js.map +0 -1
  36. package/dist/chunk-ILIODA7W.js.map +0 -1
  37. package/dist/chunk-JN37OVTP.js +0 -67
  38. package/dist/chunk-JN37OVTP.js.map +0 -1
  39. package/dist/chunk-TKVTZWBN.js +0 -50
  40. package/dist/chunk-TKVTZWBN.js.map +0 -1
  41. package/dist/chunk-XYDIBW3J.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,25 +1,28 @@
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, OverlayMarkScope, OverlayCloseCtx, DiscardConfirm, OVERLAY_ROOT_ATTR, OverlayMarkCtx, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-AAYCQXZQ.js';
2
+ export { AppShell, Drawer, Sheet, SkipLink, useAppShell, useUnsavedChanges } from './chunk-AAYCQXZQ.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 { Link } from './chunk-EYMGLK4M.js';
6
+ export { CodeBlock, Divider, Link } from './chunk-EYMGLK4M.js';
7
+ import { Badge } from './chunk-SBTBMAIU.js';
8
+ export { Badge } from './chunk-SBTBMAIU.js';
9
+ import { inferColumnKinds, useCardsSwitch, CARDS_BELOW, kindOfRenderedText, CardValue } from './chunk-IVCVNAXG.js';
10
+ import { useWholeListCheck, pagerPart, Pagination, Dropdown, useFieldControl, useField, warnOnce, Label, InlineFieldError, AddAnotherRowCtx, Input, Field } from './chunk-2RV23OBK.js';
11
+ export { Dropdown, Field, InlineFieldError, Input, Label, NumberInput, Pagination, Popover, Textarea, Toggle, usePagination } from './chunk-2RV23OBK.js';
12
+ import { SplitPaneCtx, TableCardCtx } from './chunk-RG4KUCRS.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 { useTextOverflow, Tooltip, usePosition } from './chunk-CXMV7SXJ.js';
20
+ export { Tooltip, useTextOverflow } from './chunk-CXMV7SXJ.js';
18
21
  import { mergeRefs, Slot } from './chunk-7Q46JXSK.js';
19
22
  import { useLatest } from './chunk-WH2FUFAC.js';
20
23
  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';
24
+ 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';
25
+ import { createContext, forwardRef, useState, useRef, useCallback, useLayoutEffect, useMemo, useContext, isValidElement, useId, memo, useEffect, useImperativeHandle, Children, Fragment as Fragment$1, cloneElement, useSyncExternalStore } from 'react';
23
26
  import { createPortal, flushSync } from 'react-dom';
24
27
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
25
28
 
@@ -83,6 +86,8 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
83
86
  Slot,
84
87
  {
85
88
  ...triggerType(trigger),
89
+ "aria-haspopup": "menu",
90
+ "aria-expanded": open,
86
91
  ref: triggerRef,
87
92
  onClick: () => {
88
93
  setOpen(!open);
@@ -208,733 +213,6 @@ function BulkActionBar({ selectedCount, actions, onClearSelection, className })
208
213
  }
209
214
  );
210
215
  }
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
216
  var CONTROL_HEIGHT = {
939
217
  sm: "h-[var(--ds-control-height-sm)]",
940
218
  md: "h-[var(--ds-control-height-md)]",
@@ -2488,21 +1766,6 @@ function Progress({ value, max = 100, variant = "default", size = "md", indeterm
2488
1766
  ] })
2489
1767
  ] });
2490
1768
  }
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
1769
  function TruncFileName({ children, className }) {
2507
1770
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
2508
1771
  const el = /* @__PURE__ */ jsx("p", { ref, className: cn("truncate", className), children });
@@ -2631,9 +1894,9 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2631
1894
  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
1895
  }, [controlled, onFiles, upload, onRemove, shown]);
2633
1896
  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) {
1897
+ const signature = shown.map((i) => `${i.id}\0${i.status}`).join("");
1898
+ const [seen, setSeen] = useState(() => ({ signature, statuses: new Map(shown.map((i) => [i.id, i.status])) }));
1899
+ if (signature !== seen.signature) {
2637
1900
  const said = [];
2638
1901
  for (const item of shown) {
2639
1902
  const before = seen.statuses.get(item.id);
@@ -2641,7 +1904,7 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2641
1904
  if (item.status === "done") said.push(`${item.name} uploaded`);
2642
1905
  else if (item.status === "error") said.push(`${item.name} didn't upload: ${item.error ?? "the upload failed"}`);
2643
1906
  }
2644
- setSeen({ signature: signature2, statuses: new Map(shown.map((i) => [i.id, i.status])) });
1907
+ setSeen({ signature, statuses: new Map(shown.map((i) => [i.id, i.status])) });
2645
1908
  if (said.length) setHeard((h) => ({ text: said.join(". "), n: h.n + 1 }));
2646
1909
  }
2647
1910
  const pendingFocus = useRef(null);
@@ -3394,237 +2657,21 @@ function TimePicker({
3394
2657
  onMouseDown: (e) => e.preventDefault(),
3395
2658
  onClick: () => emitChange(parsed.hour, parsed.minute, p),
3396
2659
  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
2660
+ "text-xs font-medium py-ds-1 rounded-[var(--ds-radius-sm)] cursor-pointer",
2661
+ "transition-colors text-center focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
2662
+ 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)]"
2663
+ ),
2664
+ children: p
2665
+ },
2666
+ p
2667
+ )) })
2668
+ ] })
2669
+ ] })
2670
+ }
3540
2671
  ),
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
- );
2672
+ container ?? document.body
2673
+ )
2674
+ ] });
3628
2675
  }
3629
2676
  var variantStyles = {
3630
2677
  default: "bg-[var(--ds-bg-emphasis)] text-[var(--ds-text-primary)]",
@@ -3867,7 +2914,7 @@ function TagInput({
3867
2914
  }
3868
2915
  );
3869
2916
  }
3870
- var sizes2 = {
2917
+ var sizes = {
3871
2918
  sm: "w-[var(--ds-control-height-sm)] h-[var(--ds-control-height-sm)] text-sm",
3872
2919
  md: "w-[var(--ds-control-height-md)] h-[var(--ds-control-height-md)] text-base",
3873
2920
  lg: "w-[var(--ds-control-height-lg)] h-[var(--ds-control-height-lg)] text-lg"
@@ -3971,7 +3018,7 @@ function PinInput({
3971
3018
  !error && !disabled && "hover:border-[var(--ds-input-border-hover)]",
3972
3019
  error ? ERROR_FOCUS_RING : FOCUS_RING,
3973
3020
  "placeholder:text-[var(--ds-text-muted)]",
3974
- sizes2[size],
3021
+ sizes[size],
3975
3022
  disabled && "opacity-disabled cursor-not-allowed"
3976
3023
  )
3977
3024
  },
@@ -5312,43 +4359,18 @@ function MentionInput({
5312
4359
  )
5313
4360
  ] });
5314
4361
  }
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));
4362
+ var CardRowCtx = createContext(null);
4363
+ var TITLE_LINE = "h-[calc(var(--ds-font-size-lg)*var(--ds-line-height-lg))]";
4364
+ var HeaderRowCtx = createContext(false);
4365
+ function holdsHeader(children) {
4366
+ let found = false;
4367
+ Children.forEach(children, (child) => {
4368
+ if (found || !isValidElement(child)) return;
4369
+ if (child.type === Fragment$1) found = holdsHeader(child.props.children);
4370
+ else if (child.type === CardHeader) found = true;
4371
+ });
4372
+ return found;
5350
4373
  }
5351
- var CardCtx = createContext(false);
5352
4374
  var CardActionCtx = createContext(null);
5353
4375
  var CARD_ACTION_ATTR = "data-card-action";
5354
4376
  var CardSurfaceCtx = createContext(false);
@@ -5499,7 +4521,7 @@ var Card = forwardRef(function Card2({
5499
4521
  const handleKeyDown = isSelectable && !disabled ? (e) => {
5500
4522
  if (e.key === "Enter" || e.key === " ") {
5501
4523
  e.preventDefault();
5502
- onSelectedChange?.(!selected);
4524
+ if (!e.repeat) onSelectedChange?.(!selected);
5503
4525
  }
5504
4526
  } : isAction && !disabled ? (e) => {
5505
4527
  if (e.target !== e.currentTarget) return;
@@ -5516,13 +4538,84 @@ var Card = forwardRef(function Card2({
5516
4538
  } : void 0;
5517
4539
  const handleCheckableClick = (e) => {
5518
4540
  e.stopPropagation();
5519
- onSelectedChange?.(!selected);
4541
+ if (!disabled) onSelectedChange?.(!selected);
5520
4542
  };
5521
4543
  const isButton = isSelectable || isAction && !holdsControls;
5522
4544
  const role = selectType === "radio" ? "radio" : selectType === "checkbox" ? "checkbox" : selectType === "toggle" ? "switch" : isButton ? "button" : void 0;
5523
4545
  const ariaAttr = selectType === "radio" || selectType === "checkbox" || selectType === "toggle" ? { "aria-checked": !!selected } : isSelectable ? { "aria-pressed": !!selected } : {};
5524
4546
  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;
4547
+ 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;
4548
+ const checkboxes = useRef(/* @__PURE__ */ new Set());
4549
+ const checkboxFocused = useRef(false);
4550
+ const checkboxRef = useCallback((node) => {
4551
+ if (!node) return;
4552
+ checkboxes.current.add(node);
4553
+ return () => {
4554
+ checkboxes.current.delete(node);
4555
+ };
4556
+ }, []);
4557
+ useLayoutEffect(() => {
4558
+ if (!checkboxFocused.current) return;
4559
+ const active = document.activeElement;
4560
+ if (active && active !== document.body) return;
4561
+ for (const node of checkboxes.current) {
4562
+ if (node.isConnected) {
4563
+ node.focus({ preventScroll: true });
4564
+ return;
4565
+ }
4566
+ }
4567
+ });
4568
+ const checkbox = checkable ? /* @__PURE__ */ jsx(
4569
+ "div",
4570
+ {
4571
+ ref: checkboxRef,
4572
+ "data-card-checkbox": "",
4573
+ className: cn(
4574
+ "rounded-[calc(var(--ds-radius-sm)*0.6)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
4575
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
4576
+ ),
4577
+ onClick: handleCheckableClick,
4578
+ role: "checkbox",
4579
+ "aria-checked": !!selected,
4580
+ "aria-disabled": disabled || void 0,
4581
+ ...inner.titleId ? { "aria-labelledby": inner.titleId } : { "aria-label": "Select" },
4582
+ tabIndex: 0,
4583
+ onFocus: () => {
4584
+ checkboxFocused.current = true;
4585
+ },
4586
+ onBlur: (e) => {
4587
+ const node = e.currentTarget;
4588
+ queueMicrotask(() => {
4589
+ if (node.isConnected) checkboxFocused.current = false;
4590
+ });
4591
+ },
4592
+ onKeyDown: (e) => {
4593
+ if (e.key !== " ") return;
4594
+ e.preventDefault();
4595
+ e.stopPropagation();
4596
+ if (!e.repeat && !disabled) onSelectedChange?.(!selected);
4597
+ },
4598
+ children: /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected, disabled })
4599
+ }
4600
+ ) : null;
4601
+ const check = indicator ?? defaultCheckBadge;
4602
+ const headers = useRef([]);
4603
+ const [owner, setOwner] = useState(null);
4604
+ const [guess, setGuess] = useState(() => holdsHeader(children));
4605
+ const register = useCallback((header) => {
4606
+ headers.current = [...headers.current, header];
4607
+ setOwner(headers.current[0]);
4608
+ return () => {
4609
+ headers.current = headers.current.filter((h) => h !== header);
4610
+ setOwner(headers.current[0] ?? null);
4611
+ setGuess(false);
4612
+ };
4613
+ }, []);
4614
+ useLayoutEffect(() => {
4615
+ if (headers.current.length === 0) setGuess(false);
4616
+ }, []);
4617
+ const hasHeader = owner !== null || guess;
4618
+ const rowParts = { checkbox, check, owner, register };
5526
4619
  return /* @__PURE__ */ jsxs(
5527
4620
  "div",
5528
4621
  {
@@ -5553,32 +4646,22 @@ var Card = forwardRef(function Card2({
5553
4646
  ...ariaAttr,
5554
4647
  ...props,
5555
4648
  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 ? (
4649
+ !loading && !hasHeader && checkbox && /* @__PURE__ */ jsxs("div", { "data-card-header-row": "", className: "flex items-center gap-ds-2 px-ds-5 pt-ds-4", children: [
4650
+ checkbox,
4651
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "grow" }),
4652
+ check && /* @__PURE__ */ jsx("span", { "data-card-selection": "", className: "shrink-0 flex", children: check })
4653
+ ] }),
4654
+ !loading && !hasHeader && !checkbox && check && /* @__PURE__ */ jsx("div", { "data-card-selection": "", className: "absolute top-ds-3 right-ds-3 z-[1] flex", children: check }),
4655
+ /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardActionCtx, { value: action, children: /* @__PURE__ */ jsx(CardRowCtx, { value: rowParts, children: loading ? (
5578
4656
  // Round 15 (the owner's 52.1): a placeholder stands where the content will stand — inside the card's
5579
4657
  // 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" }),
4658
+ // Its header holds a badge-sized block (A2), so a grid of item cards keeps its columns when they load.
4659
+ /* @__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: [
4660
+ /* @__PURE__ */ jsxs("div", { "data-card-header-row": "", className: "flex items-center gap-ds-2", children: [
4661
+ checkbox,
4662
+ /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-5 w-1/3" }),
4663
+ /* @__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)]" }) }) })
4664
+ ] }),
5582
4665
  /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-full" }),
5583
4666
  /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-2/3" })
5584
4667
  ] }) })
@@ -5587,60 +4670,95 @@ var Card = forwardRef(function Card2({
5587
4670
  }
5588
4671
  );
5589
4672
  });
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",
4673
+ function partsOf(children) {
4674
+ const parts = [];
4675
+ const open = (node) => Children.toArray(node).forEach((child) => {
4676
+ if (isValidElement(child) && child.type === Fragment$1) open(child.props.children);
4677
+ else if (child !== "") parts.push(child);
4678
+ });
4679
+ open(children);
4680
+ return parts;
4681
+ }
4682
+ function splitTitle(children) {
4683
+ const parts = partsOf(children);
4684
+ const at = parts.findIndex((p) => isValidElement(p) && p.type === CardTitle);
4685
+ if (at < 0) return { lead: [], title: parts[0] ?? null, rest: parts.slice(1) };
4686
+ return { lead: parts.slice(0, at), title: parts[at], rest: parts.slice(at + 1) };
4687
+ }
4688
+ var TITLE_FLOOR = "basis-[calc(var(--ds-font-size-lg)*4.5)]";
4689
+ var TITLE_ROW = cn(
4690
+ "flex flex-wrap items-start justify-end gap-x-ds-2 gap-y-ds-2 min-w-0 grow basis-0",
4691
+ "[&>[data-header-actions]>*]:order-5",
4692
+ "[&>[data-header-actions]>:has([data-gs-badge]):not(:has(a,button,input,select,textarea,[role=button],[tabindex]:not([tabindex='-1'])))]:order-2",
4693
+ "[&>[data-header-actions]>:has([aria-haspopup])]:order-6"
4694
+ );
4695
+ var CardHeader = forwardRef(function CardHeader2(allProps, ref) {
4696
+ const { className, children, actions, icon, menuItems, ...props } = allProps;
4697
+ const row = useContext(CardRowCtx);
4698
+ const [self] = useState(() => ({}));
4699
+ const register = row?.register;
4700
+ useLayoutEffect(() => register?.(self), [register, self]);
4701
+ const owns = !!row && row.owner === self;
4702
+ const iconBlock = icon ? (
4703
+ // the icon stays where it was, a tile above the title (28 of 66 headers with actions have one)
4704
+ /* @__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)]" }) })
4705
+ ) : null;
4706
+ const menu = menuItems ? /* @__PURE__ */ jsx(
4707
+ DropdownMenu,
4708
+ {
4709
+ trigger: /* @__PURE__ */ jsx(
4710
+ "button",
4711
+ {
4712
+ type: "button",
4713
+ "aria-label": "More actions",
4714
+ className: cn(
4715
+ "inline-flex items-center justify-center h-8 w-8 rounded-[var(--ds-radius-md)]",
4716
+ "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
4717
+ "transition-colors duration-[var(--ds-transition-speed)]"
4718
+ ),
4719
+ onClick: (e) => e.stopPropagation(),
4720
+ children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: 16 })
4721
+ }
4722
+ ),
4723
+ items: menuItems
4724
+ }
4725
+ ) : null;
4726
+ const band = (order) => cn("shrink-0 self-start flex items-center", TITLE_LINE, order);
4727
+ const checkbox = owns && row.checkbox ? /* @__PURE__ */ jsx("span", { className: band("order-0"), children: row.checkbox }) : null;
4728
+ const check = owns && row.check ? /* @__PURE__ */ jsx("span", { "data-card-selection": "", className: band("order-4"), children: row.check }) : null;
4729
+ const menuPart = menu ? /* @__PURE__ */ jsx("span", { className: band("order-6"), children: menu }) : null;
4730
+ const { lead, title, rest } = splitTitle(children);
4731
+ 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;
4732
+ const topLine = iconBlock ?? leadBlock;
4733
+ const ownBlock = title !== null && !(isValidElement(title) && title.type === CardTitle);
4734
+ 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;
4735
+ 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: [
4736
+ 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: [
4737
+ checkbox,
4738
+ topLine,
4739
+ iconBlock && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "grow -ml-ds-2" }),
4740
+ check,
4741
+ menuPart
4742
+ ] }),
4743
+ iconBlock && leadBlock && /* @__PURE__ */ jsx("div", { className: "flex mb-ds-1.5", children: leadBlock }),
4744
+ /* @__PURE__ */ jsxs("div", { "data-card-header-row": "", className: "flex items-start gap-ds-2 min-w-0", children: [
4745
+ !topLine && checkbox,
4746
+ /* @__PURE__ */ jsxs("div", { "data-card-title-line": "", className: TITLE_ROW, children: [
4747
+ title && /* @__PURE__ */ jsx(
4748
+ "div",
5601
4749
  {
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 })
4750
+ "data-card-title-row": "",
4751
+ className: cn("order-1 min-w-0 me-auto", TITLE_FLOOR, ownBlock ? "grow" : "grow-[999] max-w-max"),
4752
+ children: /* @__PURE__ */ jsx(HeaderRowCtx, { value: true, children: title })
5611
4753
  }
5612
4754
  ),
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
4755
+ actionsSlot,
4756
+ !ownBlock && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "order-3 grow basis-0 -ml-ds-2" })
5637
4757
  ] }),
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
4758
+ !topLine && check,
4759
+ !topLine && menuPart
4760
+ ] }),
4761
+ 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
4762
  ] });
5645
4763
  });
5646
4764
  var cardLineClampClasses = {
@@ -5651,7 +4769,10 @@ var cardLineClampClasses = {
5651
4769
  5: "line-clamp-5",
5652
4770
  6: "line-clamp-6"
5653
4771
  };
5654
- var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2, id, ...props }, forwardedRef) {
4772
+ var WHOLE_ON_KEYBOARD = "[[data-card]:has(:focus-visible)_&]:whitespace-normal [[data-card]:has(:focus-visible)_&]:line-clamp-none";
4773
+ var CardTitle = forwardRef(function CardTitle2({ className, children, lines: linesProp, id, ...props }, forwardedRef) {
4774
+ const inHeaderRow = useContext(HeaderRowCtx);
4775
+ const lines = linesProp ?? (inHeaderRow ? 1 : 2);
5655
4776
  const action = useContext(CardActionCtx);
5656
4777
  const generatedId = useId();
5657
4778
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines, disabled: lines === 0 });
@@ -5667,7 +4788,7 @@ var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2,
5667
4788
  id: id ?? generatedId,
5668
4789
  "data-card-title": "",
5669
4790
  ...lines === 1 ? { "data-title-one-line": "" } : {},
5670
- className: cn(TITLE_TEXT, !action && lines > 0 && lineClampClass, action && lines === 1 && "text-ellipsis", className),
4791
+ className: cn(TITLE_TEXT, !action && lines > 0 && [lineClampClass, WHOLE_ON_KEYBOARD], action && lines === 1 && "text-ellipsis", className),
5671
4792
  ...props,
5672
4793
  children: action ? (
5673
4794
  // The card's action when the card holds controls of its own (Sprint 25.7): a real button, which a click
@@ -5684,14 +4805,13 @@ var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2,
5684
4805
  "block w-full text-left cursor-pointer [text-overflow:inherit] focus-visible:outline-none disabled:cursor-not-allowed",
5685
4806
  "after:absolute after:inset-0 after:rounded-[var(--ds-radius-md)] after:pointer-events-none focus-visible:after:shadow-[var(--ds-shadow-focus)]"
5686
4807
  ),
5687
- children: /* @__PURE__ */ jsx("span", { ref: (node) => overflowRef(lines > 0 ? node : null), className: cn("block [text-overflow:inherit]", lines > 0 && lineClampClass), children })
4808
+ 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
4809
  }
5689
4810
  )
5690
4811
  ) : children
5691
4812
  }
5692
4813
  );
5693
- if (!isTruncated || lines === 0) return heading;
5694
- return /* @__PURE__ */ jsx(Tooltip, { content: children, children: heading });
4814
+ return /* @__PURE__ */ jsx(Tooltip, { content: children, disabled: !isTruncated, children: heading });
5695
4815
  });
5696
4816
  var CardDescription = forwardRef(function CardDescription2({ className, children, lines = 2, ...props }, forwardedRef) {
5697
4817
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines, disabled: lines === 0 });
@@ -5709,18 +4829,15 @@ var CardDescription = forwardRef(function CardDescription2({ className, children
5709
4829
  children
5710
4830
  }
5711
4831
  );
5712
- if (!isTruncated || lines === 0) return el;
5713
- return /* @__PURE__ */ jsx(Tooltip, { content: children, children: el });
4832
+ return /* @__PURE__ */ jsx(Tooltip, { content: children, disabled: !isTruncated, children: el });
5714
4833
  });
5715
4834
  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 });
4835
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("flex-1 px-ds-5 py-ds-4", className), ...props, children });
5718
4836
  });
5719
4837
  var CardFooter = forwardRef(function CardFooter2({ className, children, ...props }, ref) {
5720
- const isCheckable = useContext(CardCtx);
5721
4838
  return (
5722
4839
  // 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 })
4840
+ /* @__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
4841
  );
5725
4842
  });
5726
4843
  function TruncLabel({ children }) {
@@ -5836,6 +4953,9 @@ function DataList({ items, columns = 1, className }) {
5836
4953
  );
5837
4954
  }
5838
4955
  var COUNT_FROM_PAGE = [
4956
+ // one card counts too: a lone card fell back to React's count, which counts components that draw nothing, and a grid
4957
+ // with one card inside another took the outer grid's count (the review of 30 September)
4958
+ "[&:has(>:only-child)]:[--gs-cards:1]",
5839
4959
  "[&:has(>:nth-child(2):last-child)]:[--gs-cards:2]",
5840
4960
  "[&:has(>:nth-child(3):last-child)]:[--gs-cards:3]",
5841
4961
  "[&:has(>:nth-child(4):last-child)]:[--gs-cards:4]",
@@ -5848,6 +4968,7 @@ var COUNT_FROM_PAGE = [
5848
4968
  "[&:has(>:nth-child(11):last-child)]:[--gs-cards:11]",
5849
4969
  "[&:has(>:nth-child(12))]:[--gs-cards:12]"
5850
4970
  ].join(" ");
4971
+ var MIN_FROM_HEADERS = "[--gs-card-min:14rem] has-[[data-header-actions]]:[--gs-card-min:20rem]";
5851
4972
  function countCards(children) {
5852
4973
  let n = 0;
5853
4974
  Children.forEach(children, (child) => {
@@ -5856,14 +4977,18 @@ function countCards(children) {
5856
4977
  });
5857
4978
  return n;
5858
4979
  }
5859
- function CardGrid({ minWidth = "14rem", maxColumns = 4, compare = false, gap = "var(--ds-space-4)", className, style, children, ref, ...props }) {
4980
+ function CardGrid({ minWidth, maxColumns = 4, compare = false, gap = "var(--ds-space-4)", fill: fill2 = true, className, style, children, ref, ...props }) {
5860
4981
  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})`;
4982
+ const drawn = "var(--gs-cards, var(--gs-cards-counted))";
4983
+ const min = "var(--gs-card-min)";
4984
+ const shareOf = (n) => `calc((100% - (${n} - 1) * var(--gs-card-gap)) / ${n})`;
4985
+ const collection = `max(min(${min}, 100%), ${shareOf(String(most))})`;
4986
+ 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
4987
  const template = {
5865
4988
  "--gs-card-gap": gap,
5866
- ...compare ? { "--gs-cards-counted": Math.max(1, countCards(children)) } : {},
4989
+ // A2: a width passed wins; a comparison keeps 14rem; a collection's comes from its headers (MIN_FROM_HEADERS)
4990
+ ...minWidth ? { "--gs-card-min": minWidth } : compare ? { "--gs-card-min": "14rem" } : {},
4991
+ ...compare || fill2 ? { "--gs-cards-counted": Math.max(1, countCards(children)) } : {},
5867
4992
  gap: "var(--gs-card-gap)",
5868
4993
  gridTemplateColumns: `repeat(auto-fill, minmax(${column}, 1fr))`,
5869
4994
  // a comparison's cards share their rows: the headers, the contents and the footers line up across the row
@@ -5876,7 +5001,8 @@ function CardGrid({ minWidth = "14rem", maxColumns = 4, compare = false, gap = "
5876
5001
  "data-card-grid": compare ? "compare" : "",
5877
5002
  className: cn(
5878
5003
  "grid",
5879
- compare && COUNT_FROM_PAGE,
5004
+ !minWidth && !compare && MIN_FROM_HEADERS,
5005
+ (compare || fill2) && COUNT_FROM_PAGE,
5880
5006
  // each card spans its three rows and lays its parts in them; anything else just fills them
5881
5007
  compare && "[&>*]:row-span-3 [&>[data-card]]:grid [&>[data-card]]:grid-rows-subgrid [&>[data-card]]:gap-y-0",
5882
5008
  className
@@ -6164,7 +5290,7 @@ var EDGE_FADE = 24;
6164
5290
  var NONE = { left: false, right: false, vertical: false, gutterRight: 0, gutterBottom: 0 };
6165
5291
  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
5292
  function useScrollEdges(options = {}) {
6167
- const { enabled = true, fade = EDGE_FADE } = options;
5293
+ const { enabled = true, fade = EDGE_FADE, noFade } = options;
6168
5294
  const elRef = useRef(null);
6169
5295
  const observer = useRef(null);
6170
5296
  const borders = useRef({ x: 0, y: 0 });
@@ -6196,9 +5322,11 @@ function useScrollEdges(options = {}) {
6196
5322
  for (const child of Array.from(el.children)) o.observe(child);
6197
5323
  observer.current = o;
6198
5324
  }, [enabled, measure]);
6199
- const shown = enabled && (state.left || state.right);
5325
+ const fadeLeft = state.left && !noFade?.left;
5326
+ const fadeRight = state.right && !noFade?.right;
5327
+ const shown = enabled && (fadeLeft || fadeRight);
6200
5328
  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%"})`;
5329
+ const fadeLayer = `linear-gradient(to right, ${fadeLeft ? `transparent 0, black ${fade}px` : "black 0"}, ${fadeRight ? `black calc(100% - ${gr + fade}px), transparent calc(100% - ${gr}px)` : "black 100%"})`;
6202
5330
  const gutters = [gb > 0 && { size: `100% ${gb}px`, position: "0 100%" }, gr > 0 && { size: `${gr}px 100%`, position: "100% 0" }].filter(Boolean);
6203
5331
  const mask = shown ? {
6204
5332
  image: [fadeLayer, ...gutters.map(() => "linear-gradient(black, black)")].join(", "),
@@ -6211,18 +5339,54 @@ function useScrollEdges(options = {}) {
6211
5339
  /** The scroller itself, for code that moves it. */
6212
5340
  elRef,
6213
5341
  measure,
6214
- /** Content hides past a side edge: the scroller moves sideways, and its edges fade. */
6215
- overflowing: shown,
5342
+ /** Content hides past a side edge: the scroller moves sideways, and its edges fade (but those in `noFade`). */
5343
+ overflowing: enabled && (state.left || state.right),
6216
5344
  /** It scrolls either way, sideways or up and down: a keyboard needs to reach it. */
6217
5345
  scrollable: enabled && (state.left || state.right || state.vertical),
6218
5346
  edges: { left: state.left, right: state.right },
6219
5347
  /** For the scroller: `data-fade` names the faded edges ("left", "right" or "left right"), the style masks them. */
6220
5348
  fadeProps: {
6221
- "data-fade": shown ? [state.left && "left", state.right && "right"].filter(Boolean).join(" ") : void 0,
5349
+ "data-fade": shown ? [fadeLeft && "left", fadeRight && "right"].filter(Boolean).join(" ") : void 0,
6222
5350
  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
5351
  }
6224
5352
  };
6225
5353
  }
5354
+ var HEADER_LAYOUT = {
5355
+ row: "flex flex-wrap items-start justify-between gap-ds-4",
5356
+ 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",
5357
+ titleLeads: "grow basis-1/2 max-w-max break-words has-[[data-title-one-line]]:min-w-0",
5358
+ floorOff: "min-w-0",
5359
+ actions: "flex items-center gap-ds-2 shrink-0 ml-auto",
5360
+ below: "mt-ds-1.5"
5361
+ };
5362
+ function useTitleFloorFits(rowRef, active) {
5363
+ const [fits, setFits] = useState(true);
5364
+ useLayoutEffect(() => {
5365
+ const row = rowRef.current;
5366
+ if (!active || !row) return;
5367
+ const measure = () => {
5368
+ const title = row.querySelector("h1, h2, h3, h4, h5, h6");
5369
+ if (!title) return;
5370
+ const width = title.style.width;
5371
+ title.style.width = "min-content";
5372
+ const longest = title.getBoundingClientRect().width;
5373
+ title.style.width = width;
5374
+ if (longest === 0) return;
5375
+ const next = longest <= row.clientWidth + 0.5;
5376
+ setFits((was) => was === next ? was : next);
5377
+ };
5378
+ measure();
5379
+ if (typeof ResizeObserver === "undefined") return;
5380
+ const observer = new ResizeObserver(measure);
5381
+ observer.observe(row);
5382
+ return () => observer.disconnect();
5383
+ }, [rowRef, active]);
5384
+ return fits;
5385
+ }
5386
+ function actionsAreBadges(actions) {
5387
+ const items = Children.toArray(actions);
5388
+ return items.length > 0 && items.every((item) => isValidElement(item) && (item.type === Fragment$1 ? actionsAreBadges(item.props.children) : item.type === Badge));
5389
+ }
6226
5390
  function TruncCell({ children, lines }) {
6227
5391
  const { ref, isTruncated } = useTextOverflow({ lines });
6228
5392
  const clampClass = lines === 1 ? "truncate" : `line-clamp-${lines}`;
@@ -6237,6 +5401,95 @@ var FRAME_CLASSES = cn(
6237
5401
  GLASS_SURFACE,
6238
5402
  "bg-[var(--ds-bg-surface)]"
6239
5403
  );
5404
+ 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)";
5405
+ var PINNED_LAYERS = cn(
5406
+ "[--gs-pinned-surface:var(--ds-bg-surface)] [--gs-pinned-canvas:var(--ds-bg-canvas)]",
5407
+ "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)))]",
5408
+ "supports-[color:oklch(from_red_l_c_h)]:[--gs-pinned-canvas:oklch(from_var(--ds-bg-canvas)_l_c_h_/_1)]"
5409
+ );
5410
+ var PIN_SHADOW = "oklch(0 0 0 / 0.18)";
5411
+ var ROW_CONTROL = `${FOCUSABLE}, label, [role="button"], [role="checkbox"], [role="switch"], [role="menuitem"], [role="link"]`;
5412
+ function clickIsRows(e) {
5413
+ const target = e.target;
5414
+ if (!(target instanceof Element) || !e.currentTarget.contains(target)) return false;
5415
+ const selection = typeof window !== "undefined" ? window.getSelection() : null;
5416
+ if (selection && !selection.isCollapsed && selection.anchorNode && e.currentTarget.contains(selection.anchorNode)) return false;
5417
+ const control = target.closest(ROW_CONTROL);
5418
+ return !control || control === e.currentTarget || !e.currentTarget.contains(control);
5419
+ }
5420
+ function sortableValue(v) {
5421
+ if (v === null || v === void 0 || v === "") return void 0;
5422
+ if (v instanceof Date) return v.getTime();
5423
+ if (typeof v === "string" || typeof v === "number") return v;
5424
+ if (typeof v === "boolean") return v ? 1 : 0;
5425
+ return isValidElement(v) || typeof v === "object" ? null : String(v);
5426
+ }
5427
+ var COLLATOR = new Intl.Collator(void 0, { numeric: true });
5428
+ function compareValues(a, b) {
5429
+ if (typeof a === "number" && typeof b === "number") return a - b;
5430
+ return COLLATOR.compare(String(a), String(b));
5431
+ }
5432
+ var UNIT_SCALE = {
5433
+ "": 1,
5434
+ "%": 1,
5435
+ b: 1,
5436
+ byte: 1,
5437
+ bytes: 1,
5438
+ kb: 1e3,
5439
+ kib: 1024,
5440
+ mb: 1e6,
5441
+ mib: 1024 ** 2,
5442
+ gb: 1e9,
5443
+ gib: 1024 ** 3,
5444
+ tb: 1e12,
5445
+ tib: 1024 ** 4,
5446
+ pb: 1e15,
5447
+ pib: 1024 ** 5,
5448
+ ms: 1,
5449
+ s: 1e3,
5450
+ sec: 1e3,
5451
+ min: 6e4,
5452
+ h: 36e5,
5453
+ hr: 36e5,
5454
+ hrs: 36e5,
5455
+ g: 1,
5456
+ kg: 1e3,
5457
+ m: 1,
5458
+ km: 1e3,
5459
+ "\xB0c": 1,
5460
+ "\xB0f": 1
5461
+ };
5462
+ function numberOf(text) {
5463
+ const m = /^\s*([-\u2212])?\s*[$€£¥]?\s*([-\u2212])?\s*(\d[\d,]*\.?\d*|\.\d+)\s*(%|°[CF]|[a-z]*)\s*$/i.exec(text);
5464
+ const scale = m ? UNIT_SCALE[m[4].toLowerCase()] : void 0;
5465
+ if (!m || scale === void 0) return void 0;
5466
+ const n = parseFloat(m[3].replace(/,/g, ""));
5467
+ return Number.isFinite(n) ? (m[1] || m[2] ? -1 : 1) * n * scale : void 0;
5468
+ }
5469
+ function timeOf(text) {
5470
+ const t = Date.parse(text);
5471
+ return Number.isNaN(t) ? void 0 : t;
5472
+ }
5473
+ function rowIdsOf(data, field) {
5474
+ const seen = /* @__PURE__ */ new Set();
5475
+ let repeated = null;
5476
+ const ids = data.map((row, index) => {
5477
+ const v = row[field];
5478
+ if (typeof v !== "string" && typeof v !== "number") return index;
5479
+ if (!seen.has(String(v))) {
5480
+ seen.add(String(v));
5481
+ return v;
5482
+ }
5483
+ repeated ??= String(v);
5484
+ return `${v}~${index}`;
5485
+ });
5486
+ return { ids, repeated };
5487
+ }
5488
+ function rowMarks(row, index, field, id) {
5489
+ const v = row[field];
5490
+ return typeof v === "string" || typeof v === "number" ? { "data-row-key": String(id) } : { "data-row-index": String(index) };
5491
+ }
5492
+ var idPart = (id) => String(id).replace(/\s+/g, "_");
6240
5493
  function TableHeading({ title, description, actions, titleId, descriptionId, inset }) {
6241
5494
  const heading = /* @__PURE__ */ jsx("h3", { id: titleId, className: TITLE_CLASSES, children: title });
6242
5495
  const actionsBlock = actions ? /* @__PURE__ */ jsx("div", { className: HEADER_LAYOUT.actions, children: actions }) : null;
@@ -6258,8 +5511,10 @@ function TableHeading({ title, description, actions, titleId, descriptionId, ins
6258
5511
  descriptionText(HEADER_LAYOUT.below)
6259
5512
  ] }) });
6260
5513
  }
5514
+ var NO_PINS = { start: [], end: [], nameFits: false, frame: 0 };
5515
+ var samePins = (a, b) => a.nameFits === b.nameFits && a.frame === b.frame && a.start.join() === b.start.join() && a.end.join() === b.end.join();
6261
5516
  function Table({
6262
- columns,
5517
+ columns: columnsProp,
6263
5518
  data,
6264
5519
  className,
6265
5520
  sortable,
@@ -6271,6 +5526,10 @@ function Table({
6271
5526
  onSelectionChange,
6272
5527
  expandable,
6273
5528
  renderExpanded,
5529
+ rowKey = "id",
5530
+ expandedRows: expandedRowsProp,
5531
+ defaultExpandedRows,
5532
+ onExpandedChange,
6274
5533
  density = "default",
6275
5534
  stickyHeader = false,
6276
5535
  striped = false,
@@ -6278,6 +5537,8 @@ function Table({
6278
5537
  emptyState,
6279
5538
  onRowClick,
6280
5539
  mobileBreakpoint: mobileBreakpointProp,
5540
+ cardsBelow = CARDS_BELOW,
5541
+ pager,
6281
5542
  title,
6282
5543
  description,
6283
5544
  actions,
@@ -6289,7 +5550,10 @@ function Table({
6289
5550
  const descriptionId = `${headingId}-description`;
6290
5551
  const [internalSortKey, setInternalSortKey] = useState(null);
6291
5552
  const [internalSortDir, setInternalSortDir] = useState(null);
6292
- const [expandedRows, setExpandedRows] = useState(/* @__PURE__ */ new Set());
5553
+ const columns = useMemo(
5554
+ () => [...columnsProp.filter((c) => c.pin === "start"), ...columnsProp.filter((c) => !c.pin), ...columnsProp.filter((c) => c.pin === "end")],
5555
+ [columnsProp]
5556
+ );
6293
5557
  const isControlled = controlledSortKey !== void 0;
6294
5558
  const sortKey = isControlled ? controlledSortKey : internalSortKey;
6295
5559
  const sortDir = isControlled ? controlledSortDir ?? null : internalSortDir;
@@ -6309,6 +5573,7 @@ function Table({
6309
5573
  nextKey = key;
6310
5574
  nextDir = "asc";
6311
5575
  }
5576
+ pager?.setPage(1);
6312
5577
  if (onSortChange) {
6313
5578
  onSortChange(nextKey, nextDir);
6314
5579
  } else {
@@ -6316,139 +5581,348 @@ function Table({
6316
5581
  setInternalSortDir(nextDir);
6317
5582
  }
6318
5583
  },
6319
- [sortKey, sortDir, onSortChange]
5584
+ [sortKey, sortDir, onSortChange, pager]
5585
+ );
5586
+ const columnKeys = useMemo(() => columns.map((c) => c.key), [columns]);
5587
+ const inferredKinds = useMemo(() => inferColumnKinds(data, columnKeys), [data, columnKeys]);
5588
+ const kindOf = useCallback(
5589
+ (col) => col.kind ?? inferredKinds.get(col.key),
5590
+ [inferredKinds]
5591
+ );
5592
+ const sortColumn = sortKey ? columns.find((c) => c.key === sortKey) : void 0;
5593
+ const sortKind = sortColumn ? kindOf(sortColumn) : void 0;
5594
+ const { sortedAll, unsortable } = useMemo(() => {
5595
+ const indexed = data.map((row, i) => ({ row, originalIdx: i }));
5596
+ if (!sortKey || !sortDir) return { sortedAll: indexed, unsortable: null };
5597
+ const read = (row) => {
5598
+ if (sortColumn?.sortValue) return sortableValue(sortColumn.sortValue(row));
5599
+ const v = sortableValue(row[sortKey]);
5600
+ if (typeof v !== "string") return v;
5601
+ return (sortKind === "number" ? numberOf(v) : sortKind === "date" ? timeOf(v) : void 0) ?? v;
5602
+ };
5603
+ const valued = indexed.map((entry) => ({ ...entry, value: read(entry.row) }));
5604
+ const elements = valued.some((v) => v.value === null);
5605
+ const sorted = elements ? indexed : valued.sort((a, b) => {
5606
+ if (a.value === void 0 || b.value === void 0) return a.value === b.value ? 0 : a.value === void 0 ? 1 : -1;
5607
+ const cmp = compareValues(a.value, b.value);
5608
+ return sortDir === "asc" ? cmp : -cmp;
5609
+ });
5610
+ return { sortedAll: sorted, unsortable: elements ? sortKey : null };
5611
+ }, [data, sortKey, sortDir, sortColumn, sortKind]);
5612
+ const canSort = useMemo(() => {
5613
+ const can = /* @__PURE__ */ new Set();
5614
+ for (const col of columns) if (col.sortValue || data.every((row) => sortableValue(row[col.key]) !== null)) can.add(col.key);
5615
+ return can;
5616
+ }, [columns, data]);
5617
+ useEffect(() => {
5618
+ 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).`);
5619
+ }, [unsortable]);
5620
+ useEffect(() => {
5621
+ 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).`);
5622
+ }, [pager, data.length]);
5623
+ const rangeStart = pager ? pager.range[0] : null;
5624
+ const rangeEnd2 = pager ? pager.range[1] : null;
5625
+ const drawn = useMemo(
5626
+ () => rangeStart === null ? sortedAll : rangeStart === 0 ? [] : sortedAll.slice(rangeStart - 1, rangeEnd2 ?? void 0),
5627
+ [sortedAll, rangeStart, rangeEnd2]
6320
5628
  );
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 }));
5629
+ const drawnStart = rangeStart ? rangeStart - 1 : 0;
6328
5630
  const selectedSet = new Set(selectedRows);
6329
- const allSelected = data.length > 0 && selectedRows.length === data.length;
6330
- const someSelected = selectedRows.length > 0 && !allSelected;
5631
+ const drawnIdx = drawn.map((d) => d.originalIdx);
5632
+ const allSelected = drawnIdx.length > 0 && drawnIdx.every((i) => selectedSet.has(i));
5633
+ const someSelected = !allSelected && drawnIdx.some((i) => selectedSet.has(i));
6331
5634
  const toggleAll = () => {
6332
- if (allSelected) onSelectionChange?.([]);
6333
- else onSelectionChange?.(data.map((_, i) => i));
5635
+ const shown = new Set(drawnIdx);
5636
+ if (allSelected) onSelectionChange?.(selectedRows.filter((r) => !shown.has(r)));
5637
+ else onSelectionChange?.([...selectedRows.filter((r) => !shown.has(r)), ...drawnIdx]);
6334
5638
  };
6335
5639
  const toggleRow = (idx) => {
6336
5640
  if (selectedSet.has(idx)) onSelectionChange?.(selectedRows.filter((r) => r !== idx));
6337
5641
  else onSelectionChange?.([...selectedRows, idx]);
6338
5642
  };
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
- });
5643
+ const expandControlled = expandedRowsProp !== void 0;
5644
+ const [ownExpanded, setOwnExpanded] = useState(defaultExpandedRows ?? []);
5645
+ const expanded = expandControlled ? expandedRowsProp : ownExpanded;
5646
+ const expandedSet = new Set(expanded.map(String));
5647
+ const { ids: rowIds, repeated } = useMemo(() => rowIdsOf(data, rowKey), [data, rowKey]);
5648
+ useEffect(() => {
5649
+ 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.`);
5650
+ }, [repeated, rowKey]);
5651
+ const idOf = (_row, index) => rowIds[index] ?? index;
5652
+ const marksOf = (row, index) => rowMarks(row, index, rowKey, idOf(row, index));
5653
+ const openedHere = useRef(null);
5654
+ const toggleExpand = (id) => {
5655
+ const opening = !expandedSet.has(String(id));
5656
+ const next = opening ? [...expanded, id] : expanded.filter((x) => String(x) !== String(id));
5657
+ openedHere.current = opening ? String(id) : null;
5658
+ if (!expandControlled) setOwnExpanded(next);
5659
+ onExpandedChange?.(next);
6346
5660
  };
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
- );
5661
+ const canOpenByClick = !!expandable && !!renderExpanded && !onRowClick;
6353
5662
  const preferences = useShellPreferences();
6354
5663
  const mobileBreakpoint = mobileBreakpointProp ?? preferences.tableToCardsBreakpoint ?? "md";
6355
- const isMobile = useBreakpoint(mobileBreakpoint);
6356
- const { ref: scrollRef, measure: measureScroll, scrollable, fadeProps } = useScrollEdges({ enabled: !isMobile });
5664
+ const narrowScreen = useBreakpoint(mobileBreakpoint);
5665
+ const { cards: isMobile, rootRef } = useCardsSwitch(narrowScreen, cardsBelow, title);
5666
+ const setRoot = useCallback((node) => {
5667
+ rootRef.current = node;
5668
+ if (typeof ref === "function") ref(node);
5669
+ else if (ref) ref.current = node;
5670
+ }, [ref, rootRef]);
5671
+ const tableRef = useRef(null);
5672
+ const [pins, setPins] = useState(NO_PINS);
5673
+ const lead = (selectable ? 1 : 0) + (expandable ? 1 : 0);
5674
+ const endCount = columns.filter((c) => c.pin === "end").length;
5675
+ const nameIndex = columns.length > endCount ? lead : -1;
5676
+ const { ref: scrollRef, elRef: scrollEl, measure: measureScroll, scrollable, edges, fadeProps } = useScrollEdges({
5677
+ enabled: !isMobile,
5678
+ noFade: { left: lead + (pins.nameFits ? 1 : 0) > 0, right: endCount > 0 }
5679
+ });
6357
5680
  const [regionFocused, setRegionFocused] = useState(false);
5681
+ useLayoutEffect(() => {
5682
+ const table2 = tableRef.current;
5683
+ const scroller = scrollEl.current;
5684
+ if (!table2 || !scroller || isMobile) return;
5685
+ const measure = () => {
5686
+ const cells = Array.from(table2.querySelectorAll(":scope > thead > tr > th"));
5687
+ if (!cells.length) return;
5688
+ const widths = cells.map((c) => c.getBoundingClientRect().width);
5689
+ const start = [];
5690
+ let at = 0;
5691
+ for (let i = 0; i < lead; i++) {
5692
+ start.push(at);
5693
+ at += widths[i] ?? 0;
5694
+ }
5695
+ const nameWidth = nameIndex >= 0 ? widths[nameIndex] ?? 0 : 0;
5696
+ const end = [];
5697
+ let fromRight = 0;
5698
+ for (let i = cells.length - 1; i >= cells.length - endCount; i--) {
5699
+ end.unshift(fromRight);
5700
+ fromRight += widths[i] ?? 0;
5701
+ }
5702
+ const frame = scroller.clientWidth;
5703
+ const nameFits = nameIndex >= 0 && frame > 0 && at + nameWidth + fromRight <= frame / 2;
5704
+ if (nameFits) start.push(at);
5705
+ const next = { start, end, nameFits, frame };
5706
+ setPins((prev) => samePins(prev, next) ? prev : next);
5707
+ };
5708
+ measure();
5709
+ if (typeof ResizeObserver === "undefined") return;
5710
+ const observer = new ResizeObserver(measure);
5711
+ observer.observe(table2);
5712
+ observer.observe(scroller);
5713
+ return () => observer.disconnect();
5714
+ }, [isMobile, lead, endCount, nameIndex, scrollEl, columns, drawn.length]);
5715
+ const pinnedStart = lead + (pins.nameFits ? 1 : 0);
5716
+ const pinStyle = (position, total) => {
5717
+ if (position < pinnedStart && pins.start[position] !== void 0) {
5718
+ const inner = position === pinnedStart - 1;
5719
+ return { position: "sticky", left: pins.start[position], zIndex: 1, background: PINNED_BACKGROUND, boxShadow: inner && edges.left ? `6px 0 6px -6px ${PIN_SHADOW}` : void 0 };
5720
+ }
5721
+ const fromEnd = total - 1 - position;
5722
+ if (fromEnd < endCount && pins.end[endCount - 1 - fromEnd] !== void 0) {
5723
+ const inner = fromEnd === endCount - 1;
5724
+ 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 };
5725
+ }
5726
+ return void 0;
5727
+ };
5728
+ const fade = fadeProps;
5729
+ const [renderedKinds, setRenderedKinds] = useState(() => /* @__PURE__ */ new Map());
5730
+ useLayoutEffect(() => {
5731
+ const table2 = tableRef.current;
5732
+ if (!table2 || isMobile) return;
5733
+ const next = /* @__PURE__ */ new Map();
5734
+ for (const col of columns) {
5735
+ if (kindOf(col)) continue;
5736
+ const texts = Array.from(table2.querySelectorAll(`:scope > tbody > tr > td[data-col="${CSS.escape(col.key)}"]`)).slice(0, 50).map((td) => td.textContent ?? "");
5737
+ const kind = texts.length ? kindOfRenderedText(texts) : void 0;
5738
+ if (kind) next.set(col.key, kind);
5739
+ }
5740
+ setRenderedKinds((prev) => prev.size === next.size && [...next].every(([k, v]) => prev.get(k) === v) ? prev : next);
5741
+ }, [columns, kindOf, drawn, isMobile]);
5742
+ const expandedKey = expanded.map(String).join("");
5743
+ const shownBefore = useRef(/* @__PURE__ */ new Set());
5744
+ const pendingScroll = useRef(null);
5745
+ const scrollToRow = useCallback((key) => {
5746
+ const row = rootRef.current?.querySelector(`[data-row-key="${CSS.escape(key)}"], [data-row-index="${CSS.escape(key)}"]`);
5747
+ if (row && typeof row.scrollIntoView === "function") row.scrollIntoView({ block: "nearest" });
5748
+ }, [rootRef]);
5749
+ useEffect(() => {
5750
+ const now = new Set(expanded.map(String));
5751
+ const added = [...now].filter((k) => !shownBefore.current.has(k) && k !== openedHere.current);
5752
+ shownBefore.current = now;
5753
+ openedHere.current = null;
5754
+ if (!expandControlled) return;
5755
+ const key = added[added.length - 1];
5756
+ if (key === void 0) return;
5757
+ if (pager) {
5758
+ const position = sortedAll.findIndex((r) => String(idOf(r.row, r.originalIdx)) === key);
5759
+ if (position >= 0 && (position < drawnStart || position >= drawnStart + drawn.length)) {
5760
+ pendingScroll.current = key;
5761
+ pager.setPage(Math.floor(position / pager.pageSize) + 1);
5762
+ return;
5763
+ }
5764
+ }
5765
+ scrollToRow(key);
5766
+ }, [expandedKey]);
5767
+ useEffect(() => {
5768
+ const key = pendingScroll.current;
5769
+ if (!key || !drawn.some((r) => String(idOf(r.row, r.originalIdx)) === key)) return;
5770
+ pendingScroll.current = null;
5771
+ scrollToRow(key);
5772
+ }, [drawnStart, drawn.length, scrollToRow]);
6358
5773
  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: [
5774
+ 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
5775
  cardHeading,
6361
5776
  body
6362
5777
  ] });
5778
+ const control = pager && !loading && data.length > 0 ? /* @__PURE__ */ jsx(Pagination, { ...pager.props }) : null;
5779
+ const listRoot = control ? void 0 : setRoot;
5780
+ const listClass = control ? void 0 : className;
5781
+ const withControl = (list) => control ? /* @__PURE__ */ jsxs("div", { ref: setRoot, "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
5782
+ list,
5783
+ control
5784
+ ] }) : list;
5785
+ const focusWas = useRef(null);
5786
+ const noteFocus = (e) => {
5787
+ const target = e.target;
5788
+ const row = target.closest("[data-row-key], [data-row-index]");
5789
+ if (!row) {
5790
+ focusWas.current = null;
5791
+ return;
5792
+ }
5793
+ const control2 = target === row ? null : target.getAttribute("aria-labelledby") ?? target.getAttribute("aria-label") ?? target.textContent;
5794
+ 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 };
5795
+ };
5796
+ const forgetFocus = (e) => {
5797
+ const node = e.target;
5798
+ queueMicrotask(() => {
5799
+ if (node.isConnected && !rootRef.current?.contains(document.activeElement)) focusWas.current = null;
5800
+ });
5801
+ };
5802
+ useLayoutEffect(() => {
5803
+ const was = focusWas.current;
5804
+ const active = document.activeElement;
5805
+ if (!was || active && active !== document.body) return;
5806
+ const row = rootRef.current?.querySelector(was.row);
5807
+ if (!row) return;
5808
+ const controls = Array.from(row.querySelectorAll(FOCUSABLE));
5809
+ const same2 = was.control === null ? null : controls.find((el) => (el.getAttribute("aria-labelledby") ?? el.getAttribute("aria-label") ?? el.textContent) === was.control);
5810
+ (same2 ?? (row.tabIndex >= 0 ? row : controls[0]))?.focus({ preventScroll: true });
5811
+ }, [isMobile, rootRef]);
6363
5812
  if (isMobile) {
6364
5813
  if (loading) {
6365
5814
  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" }) });
5815
+ 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
5816
  }
6368
5817
  if (data.length === 0 && emptyState) {
6369
5818
  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 });
5819
+ 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
5820
  }
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;
5821
+ 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: [
5822
+ selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select`, children: "Select" }),
5823
+ drawn.map(({ row, originalIdx }) => {
5824
+ const isSelected = selectedSet.has(originalIdx);
5825
+ const id = idOf(row, originalIdx);
5826
+ return /* @__PURE__ */ jsxs(
5827
+ "div",
5828
+ {
5829
+ "data-row": "",
5830
+ ...marksOf(row, originalIdx),
5831
+ className: cn(
5832
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
5833
+ "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
5834
+ "p-ds-4 space-y-ds-3",
5835
+ isSelected && ACTIVE_RING,
5836
+ onRowClick && "cursor-pointer hover:bg-[var(--ds-bg-muted)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
5837
+ ),
5838
+ onClick: onRowClick ? (e) => {
5839
+ if (clickIsRows(e)) onRowClick(row, originalIdx);
5840
+ } : void 0,
5841
+ tabIndex: onRowClick ? 0 : void 0,
5842
+ onKeyDown: onRowClick ? (e) => {
5843
+ if (e.target !== e.currentTarget) return;
5844
+ if (e.key === "Enter" || e.key === " ") {
5845
+ e.preventDefault();
5846
+ onRowClick(row, originalIdx);
5847
+ }
5848
+ } : void 0,
5849
+ children: [
5850
+ selectable && // "Select" and the row's name, as in the table (T15)
5851
+ /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => toggleRow(originalIdx), "aria-labelledby": nameIndex >= 0 ? `${headingId}-select ${headingId}-card-name-${idPart(id)}` : `${headingId}-select` }),
5852
+ columns.map((col, c) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-ds-2 text-sm", children: [
5853
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--ds-text-secondary)] font-medium text-xs uppercase tracking-wider", children: col.header }),
5854
+ /* @__PURE__ */ jsx(CardValue, { value: row[col.key], id: lead + c === nameIndex ? `${headingId}-card-name-${idPart(id)}` : void 0 })
5855
+ ] }, col.key)),
5856
+ 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, originalIdx) })
5857
+ ]
5858
+ },
5859
+ String(id)
5860
+ );
5861
+ })
5862
+ ] }) });
5863
+ return withControl(title ? titledCards(cards) : cards);
6407
5864
  }
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(
5865
+ const cellCount = columns.length + lead;
5866
+ 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: [
5867
+ /* @__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: [
5868
+ 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
5869
  Checkbox,
6412
5870
  {
6413
5871
  checked: allSelected,
6414
5872
  indeterminate: someSelected,
6415
- onChange: toggleAll
5873
+ onChange: toggleAll,
5874
+ "aria-labelledby": `${headingId}-select-all`
6416
5875
  }
6417
5876
  ) }),
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;
5877
+ 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) }),
5878
+ columns.map((col, c) => {
5879
+ const isSortable = col.header.trim() !== "" && (col.sortable ?? (!!sortable && canSort.has(col.key)));
5880
+ const isActiveSort = sortKey === col.key && !!sortDir && unsortable !== col.key;
6422
5881
  const headerKind = kindOf(col);
6423
5882
  const headerAlign = col.align ?? (headerKind === "number" ? "right" : "left");
6424
5883
  const shrink = (headerKind === "number" || headerKind === "date") && !col.maxWidth;
5884
+ const style = { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth, ...pinStyle(lead + c, cellCount) };
6425
5885
  return /* @__PURE__ */ jsx(
6426
5886
  "th",
6427
5887
  {
5888
+ "aria-sort": isSortable && isActiveSort ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6428
5889
  className: cn(
6429
5890
  density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6430
5891
  shrink && "w-px whitespace-nowrap",
6431
5892
  "font-[var(--ds-weight-heading)] text-[var(--ds-text-secondary)]",
6432
5893
  "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)]"
5894
+ headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left"
6435
5895
  ),
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,
5896
+ style,
5897
+ children: isSortable ? (
5898
+ // A47: a button with the column's name, reached with Tab and pressed with Enter or Space
5899
+ /* @__PURE__ */ jsxs(
5900
+ "button",
6442
5901
  {
6443
- name: "chevron-right",
6444
- size: 12,
5902
+ type: "button",
5903
+ onClick: () => handleSort(col.key),
6445
5904
  className: cn(
6446
- "transition-transform",
6447
- sortDir === "asc" ? "-rotate-90" : "rotate-90"
6448
- )
5905
+ "inline-flex items-center gap-ds-1 uppercase tracking-wider cursor-pointer select-none",
5906
+ "[font:inherit] text-inherit hover:text-[var(--ds-text-primary)]",
5907
+ "rounded-[var(--ds-radius-sm)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
5908
+ ),
5909
+ children: [
5910
+ col.header,
5911
+ /* @__PURE__ */ jsx(
5912
+ Icon,
5913
+ {
5914
+ name: "chevron-right",
5915
+ size: 12,
5916
+ className: cn(
5917
+ "transition-transform",
5918
+ !isActiveSort ? "invisible" : sortDir === "asc" ? "-rotate-90" : "rotate-90"
5919
+ )
5920
+ }
5921
+ )
5922
+ ]
6449
5923
  }
6450
5924
  )
6451
- ] })
5925
+ ) : /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-ds-1", children: col.header })
6452
5926
  },
6453
5927
  col.key
6454
5928
  );
@@ -6457,31 +5931,44 @@ function Table({
6457
5931
  /* @__PURE__ */ jsx("tbody", { children: loading ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6458
5932
  "td",
6459
5933
  {
6460
- colSpan: columns.length + (selectable ? 1 : 0) + (expandable ? 1 : 0),
5934
+ colSpan: cellCount,
6461
5935
  className: "px-ds-4 py-ds-10 text-center",
6462
5936
  children: /* @__PURE__ */ jsx(Spinner, { label: "Loading table data" })
6463
5937
  }
6464
5938
  ) }) : data.length === 0 && emptyState ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6465
5939
  "td",
6466
5940
  {
6467
- colSpan: columns.length + (selectable ? 1 : 0) + (expandable ? 1 : 0),
5941
+ colSpan: cellCount,
6468
5942
  className: "px-ds-4 py-ds-10 text-center text-sm text-[var(--ds-text-muted)]",
6469
5943
  children: emptyState
6470
5944
  }
6471
- ) }) : sortedWithIndices.map(({ row, originalIdx }, i) => {
5945
+ ) }) : drawn.map(({ row, originalIdx }, i) => {
6472
5946
  const isSelected = selectedSet.has(originalIdx);
6473
- const isExpanded = expandedRows.has(i);
5947
+ const id = idOf(row, originalIdx);
5948
+ const isExpanded = expandedSet.has(String(id));
5949
+ const clickable = !!onRowClick || canOpenByClick;
6474
5950
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6475
5951
  /* @__PURE__ */ jsxs(
6476
5952
  "tr",
6477
5953
  {
5954
+ ...marksOf(row, originalIdx),
6478
5955
  className: cn(
6479
5956
  "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)]"
5957
+ // the row's background and its tint change together, so its pinned cells and the rest fade as one
5958
+ // (roots.css registers --gs-row-tint as a colour so it can)
5959
+ "transition-[background-color,--gs-row-tint]",
5960
+ // the row's tint, as a variable too, so its pinned cells draw it over their solid ground (A35)
5961
+ 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)]",
5962
+ clickable && "cursor-pointer",
5963
+ // an outline inside the row: it shows over the pinned cells, whose solid ground hid the inset
5964
+ // shadow the row drew before (the review of 30 September)
5965
+ onRowClick && "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ds-border-focus)]"
6483
5966
  ),
6484
- onClick: onRowClick ? () => onRowClick(row, originalIdx) : void 0,
5967
+ onClick: clickable ? (e) => {
5968
+ if (!clickIsRows(e)) return;
5969
+ if (onRowClick) onRowClick(row, originalIdx);
5970
+ else toggleExpand(id);
5971
+ } : void 0,
6485
5972
  tabIndex: onRowClick ? 0 : void 0,
6486
5973
  onKeyDown: onRowClick ? (e) => {
6487
5974
  if (e.target !== e.currentTarget) return;
@@ -6491,15 +5978,15 @@ function Table({
6491
5978
  }
6492
5979
  } : void 0,
6493
5980
  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(
5981
+ 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` }) }),
5982
+ 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
5983
  "button",
6497
5984
  {
6498
5985
  type: "button",
6499
- onClick: () => toggleExpand(i),
5986
+ onClick: () => toggleExpand(id),
6500
5987
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] cursor-pointer",
6501
5988
  "aria-expanded": isExpanded,
6502
- "aria-label": "Expand row",
5989
+ ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-details ${headingId}-name-${idPart(id)}` } : { "aria-label": "Details" },
6503
5990
  children: /* @__PURE__ */ jsx(
6504
5991
  Icon,
6505
5992
  {
@@ -6510,12 +5997,13 @@ function Table({
6510
5997
  )
6511
5998
  }
6512
5999
  ) }),
6513
- columns.map((col) => {
6000
+ columns.map((col, c) => {
6514
6001
  const truncLines = col.truncate === true ? 1 : col.truncate ? col.truncate.lines : 0;
6515
6002
  const kind = kindOf(col);
6516
6003
  const alignClass = col.align ? col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "text-left" : kind === "number" ? "text-right" : "text-left";
6517
6004
  const cellContent = row[col.key];
6518
6005
  const shrink = (kind === "number" || kind === "date") && !col.maxWidth;
6006
+ const whole = !kind && renderedKinds.has(col.key);
6519
6007
  let inner;
6520
6008
  if (truncLines > 0) {
6521
6009
  inner = /* @__PURE__ */ jsx(TruncCell, { lines: truncLines, children: cellContent });
@@ -6531,13 +6019,17 @@ function Table({
6531
6019
  return /* @__PURE__ */ jsx(
6532
6020
  "td",
6533
6021
  {
6022
+ id: lead > 0 && lead + c === nameIndex ? `${headingId}-name-${idPart(id)}` : void 0,
6023
+ "data-col": col.key,
6534
6024
  className: cn(
6535
6025
  density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6536
6026
  "text-[var(--ds-text-primary)]",
6537
6027
  alignClass,
6538
- shrink && "w-px whitespace-nowrap"
6028
+ shrink && "w-px whitespace-nowrap",
6029
+ whole && "whitespace-nowrap",
6030
+ kind === "identifier" && "[overflow-wrap:anywhere]"
6539
6031
  ),
6540
- style: col.maxWidth || col.width || col.minWidth ? { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth } : void 0,
6032
+ 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
6033
  children: inner
6542
6034
  },
6543
6035
  col.key
@@ -6549,50 +6041,57 @@ function Table({
6549
6041
  expandable && isExpanded && renderExpanded && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6550
6042
  "td",
6551
6043
  {
6552
- colSpan: columns.length + (selectable ? 1 : 0) + 1,
6044
+ colSpan: cellCount,
6553
6045
  className: "bg-[var(--ds-bg-muted)] px-ds-6 py-ds-4 text-sm text-[var(--ds-text-primary)]",
6554
- children: renderExpanded(row, i)
6046
+ children: /* @__PURE__ */ jsx("div", { "data-row-panel": "", className: "sticky left-ds-6", style: pins.frame ? { maxWidth: `calc(${pins.frame}px - 2 * var(--ds-space-6))` } : void 0, children: renderExpanded(row, originalIdx) })
6555
6047
  }
6556
6048
  ) })
6557
- ] }, i);
6049
+ ] }, String(id));
6558
6050
  }) })
6559
6051
  ] });
6560
6052
  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
- }
6053
+ return withControl(
6054
+ /* @__PURE__ */ jsxs(
6055
+ "div",
6056
+ {
6057
+ ref: listRoot,
6058
+ "data-ds-list": "",
6059
+ "data-gs": "table",
6060
+ 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),
6061
+ onFocus: noteFocus,
6062
+ onBlur: forgetFocus,
6063
+ children: [
6064
+ title && /* @__PURE__ */ jsx(TableHeading, { title, description, actions, titleId, descriptionId, inset: true }),
6065
+ selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select-all`, children: "Select all rows shown" }),
6066
+ selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select`, children: "Select" }),
6067
+ expandable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-details`, children: "Details" }),
6068
+ /* @__PURE__ */ jsx(
6069
+ "div",
6070
+ {
6071
+ ref: scrollRef,
6072
+ "data-table-scroll": "",
6073
+ onScroll: measureScroll,
6074
+ onFocus: (e) => {
6075
+ if (e.target === e.currentTarget) setRegionFocused(true);
6076
+ },
6077
+ onBlur: (e) => {
6078
+ if (e.target === e.currentTarget) setRegionFocused(false);
6079
+ },
6080
+ ...fade,
6081
+ ...region,
6082
+ className: cn("min-h-0 overflow-x-auto focus-visible:outline-none", title ? "rounded-b-[inherit]" : "rounded-[inherit]"),
6083
+ children: table
6084
+ }
6085
+ )
6086
+ ]
6087
+ }
6088
+ )
6590
6089
  );
6591
6090
  }
6592
6091
  function optionsKeyForFit(options) {
6593
6092
  return options.map((o) => `${o.value}:${o.label}`).join("|");
6594
6093
  }
6595
- var sizes3 = {
6094
+ var sizes2 = {
6596
6095
  xs: "text-xs px-ds-2",
6597
6096
  sm: "text-xs px-ds-3",
6598
6097
  md: "text-sm px-ds-3",
@@ -6794,7 +6293,7 @@ function SegmentedControl({
6794
6293
  "rounded-[var(--ds-radius-md)]",
6795
6294
  "transition-colors cursor-pointer",
6796
6295
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
6797
- sizes3[size],
6296
+ sizes2[size],
6798
6297
  opt.value === value ? "text-[var(--ds-text-primary)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]",
6799
6298
  opt.disabled && "opacity-disabled cursor-not-allowed"
6800
6299
  ),
@@ -8701,7 +8200,9 @@ function VerticalLayout({ items, className }) {
8701
8200
  ] })
8702
8201
  ] }, i)) });
8703
8202
  }
8704
- function Timeline({ items, orientation = "vertical", minItemWidth = 200, className, ref }) {
8203
+ function Timeline({ items: allItems, orientation = "vertical", minItemWidth = 200, className, ref, pager }) {
8204
+ useWholeListCheck("Timeline", allItems.length, pager);
8205
+ const items = pagerPart(allItems, pager);
8705
8206
  const measureRef = useRef(null);
8706
8207
  const width = useElementWidth(measureRef);
8707
8208
  const setRefs = useCallback((node) => {
@@ -8711,7 +8212,12 @@ function Timeline({ items, orientation = "vertical", minItemWidth = 200, classNa
8711
8212
  }, [ref]);
8712
8213
  const fits = width === 0 || width >= items.length * minItemWidth;
8713
8214
  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 }) });
8215
+ 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 }) });
8216
+ if (!pager) return list;
8217
+ return /* @__PURE__ */ jsxs("div", { "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
8218
+ list,
8219
+ allItems.length > 0 && /* @__PURE__ */ jsx(Pagination, { ...pager.props })
8220
+ ] });
8715
8221
  }
8716
8222
  var CollapsibleContext = createContext(null);
8717
8223
  function useCollapsible() {
@@ -9966,7 +9472,8 @@ var StackedList = forwardRef(function StackedList2({
9966
9472
  selectedId,
9967
9473
  className,
9968
9474
  flush,
9969
- hover
9475
+ hover,
9476
+ pager
9970
9477
  }, ref) {
9971
9478
  const isPhone = useBreakpoint("sm");
9972
9479
  const inCard = useContext(CardSurfaceCtx);
@@ -9979,20 +9486,22 @@ var StackedList = forwardRef(function StackedList2({
9979
9486
  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
9487
  }, [legacy]);
9981
9488
  const articles = variant === "articles";
9489
+ useWholeListCheck("StackedList", items.length, pager);
9490
+ const shown = pagerPart(items, pager);
9982
9491
  const stackMeta = isPhone && articles;
9983
9492
  const selectable = selectedId !== void 0 || items.some((i) => i.selected !== void 0);
9984
9493
  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(
9494
+ const list = /* @__PURE__ */ jsx(
9986
9495
  "ul",
9987
9496
  {
9988
9497
  ref,
9989
9498
  "data-ds-list": "",
9990
9499
  className: cn(
9991
9500
  divided && "divide-y-[length:var(--ds-border-width)] divide-[var(--ds-border-subtle)]",
9992
- className
9501
+ !pager && className
9993
9502
  ),
9994
9503
  role: "list",
9995
- children: items.map((item) => {
9504
+ children: shown.map((item) => {
9996
9505
  const isSelected = item.selected ?? (selectedId !== void 0 && item.id === selectedId);
9997
9506
  const interactive = !!item.onClick;
9998
9507
  const hasEmphasis = item.unread !== void 0;
@@ -10084,6 +9593,11 @@ var StackedList = forwardRef(function StackedList2({
10084
9593
  })
10085
9594
  }
10086
9595
  );
9596
+ if (!pager) return list;
9597
+ return /* @__PURE__ */ jsxs("div", { "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
9598
+ list,
9599
+ items.length > 0 && /* @__PURE__ */ jsx(Pagination, { ...pager.props })
9600
+ ] });
10087
9601
  });
10088
9602
  var TIME_ZOOMS = ["day", "week", "month", "quarter"];
10089
9603
  var DAY_MS = 864e5;
@@ -11119,7 +10633,7 @@ var SELECTED = "outline-solid outline-2 outline-offset-2 outline-[var(--ds-text-
11119
10633
  var UNDATED_INSET = 16;
11120
10634
  var NO_GROUPS = [];
11121
10635
  var NO_STATUSES = {};
11122
- var warned2 = /* @__PURE__ */ new Set();
10636
+ var warned = /* @__PURE__ */ new Set();
11123
10637
  function warnOnce2(message) {
11124
10638
  let dev = false;
11125
10639
  try {
@@ -11127,8 +10641,8 @@ function warnOnce2(message) {
11127
10641
  } catch {
11128
10642
  dev = false;
11129
10643
  }
11130
- if (!dev || warned2.has(message)) return;
11131
- warned2.add(message);
10644
+ if (!dev || warned.has(message)) return;
10645
+ warned.add(message);
11132
10646
  console.warn(`Gantt: ${message}`);
11133
10647
  }
11134
10648
  function GanttArrows({ arrows, width, height }) {
@@ -12485,281 +11999,6 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
12485
11999
  dropdown
12486
12000
  ] });
12487
12001
  });
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,
12650
- 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)]"
12656
- ),
12657
- children: p
12658
- },
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: [
12731
- /* @__PURE__ */ jsx(
12732
- NumberInput,
12733
- {
12734
- value: jumpPage,
12735
- onChange: setJumpPage,
12736
- min: 1,
12737
- max: totalPages,
12738
- step: 1,
12739
- size: "sm",
12740
- className: "w-[5rem]"
12741
- }
12742
- ),
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
- }
12763
12002
  var HeaderTabRowContext = createContext(null);
12764
12003
  function useHeaderTabRow() {
12765
12004
  return useContext(HeaderTabRowContext);
@@ -17468,67 +16707,6 @@ function getPopoverPosition(rect, placement, padding) {
17468
16707
  top = Math.max(8, Math.min(top, window.innerHeight - popH - 8));
17469
16708
  return { top, left };
17470
16709
  }
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
16710
  function useDestructiveAction(options) {
17533
16711
  const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, doneMessage, undoMessage, restoredMessage, severity } = options;
17534
16712
  const preferences = useShellPreferences();
@@ -17675,7 +16853,7 @@ var PAST_TENSE = {
17675
16853
  reject: "Rejected",
17676
16854
  decline: "Declined"
17677
16855
  };
17678
- var warned3 = /* @__PURE__ */ new Set();
16856
+ var warned2 = /* @__PURE__ */ new Set();
17679
16857
  function pastTenseOf(label) {
17680
16858
  const words = label.trim().toLowerCase().split(/\s+/);
17681
16859
  const found = PAST_TENSE[words.slice(0, 2).join(" ")] ?? PAST_TENSE[words[0] ?? ""];
@@ -17686,8 +16864,8 @@ function pastTenseOf(label) {
17686
16864
  } catch {
17687
16865
  dev = false;
17688
16866
  }
17689
- if (dev && !warned3.has(label)) {
17690
- warned3.add(label);
16867
+ if (dev && !warned2.has(label)) {
16868
+ warned2.add(label);
17691
16869
  console.warn(`useDestructiveAction: "${label}" isn't a verb the hook knows; pass doneMessage so the confirmation says what happened.`);
17692
16870
  }
17693
16871
  return "Done";
@@ -17832,6 +17010,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
17832
17010
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
17833
17011
  });
17834
17012
 
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 };
17013
+ 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, 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
17014
  //# sourceMappingURL=index.js.map
17837
17015
  //# sourceMappingURL=index.js.map