@ai-matrx/design-system 0.86.356 → 0.86.358

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 (131) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/auth-gate-dialog.js +2 -2
  3. package/dist/carousel.js +11 -10
  4. package/dist/carousel.js.map +1 -1
  5. package/dist/{chunk-UWTODOMD.js → chunk-2BQAHLU3.js} +4 -4
  6. package/dist/{chunk-T4D7WAV7.js → chunk-42GFQMLA.js} +2 -2
  7. package/dist/chunk-5Z5GTXKL.js +79 -0
  8. package/dist/chunk-5Z5GTXKL.js.map +1 -0
  9. package/dist/{chunk-POJ3RR3K.js → chunk-B5D3VIMC.js} +1 -1
  10. package/dist/{chunk-POJ3RR3K.js.map → chunk-B5D3VIMC.js.map} +1 -1
  11. package/dist/{chunk-4WSXAP24.js → chunk-BB5LA7FQ.js} +3 -1
  12. package/dist/chunk-BB5LA7FQ.js.map +1 -0
  13. package/dist/{chunk-FRJ4PPA2.js → chunk-CZGZV35G.js} +2 -2
  14. package/dist/{chunk-RLM4RSED.js → chunk-GUGBVQ4T.js} +6 -4
  15. package/dist/{chunk-RLM4RSED.js.map → chunk-GUGBVQ4T.js.map} +1 -1
  16. package/dist/{chunk-LJKF267O.js → chunk-HS6GMHKZ.js} +18 -76
  17. package/dist/chunk-HS6GMHKZ.js.map +1 -0
  18. package/dist/{chunk-2PO3N42Q.js → chunk-K3XWUACX.js} +5 -5
  19. package/dist/{chunk-ZMUPFGQO.js → chunk-K5ZQROWN.js} +5 -5
  20. package/dist/{chunk-U46XUQI7.js → chunk-KSCSH7AO.js} +16 -14
  21. package/dist/chunk-KSCSH7AO.js.map +1 -0
  22. package/dist/{chunk-MTE6N7XU.js → chunk-NFJ63LIH.js} +2 -2
  23. package/dist/{chunk-CFUXEGW2.js → chunk-OUQT72UL.js} +4 -4
  24. package/dist/{chunk-WUD23YTG.js → chunk-P56Y43RL.js} +6 -6
  25. package/dist/{chunk-GMNROZQJ.js → chunk-QTPW44IJ.js} +2 -2
  26. package/dist/{chunk-QA7EHFRX.js → chunk-T3FKFPCW.js} +5 -5
  27. package/dist/{chunk-X6ZRHJQP.js → chunk-T57DLONB.js} +20 -11
  28. package/dist/chunk-T57DLONB.js.map +1 -0
  29. package/dist/{chunk-SOCNWQMX.js → chunk-TEMAM5ET.js} +3 -3
  30. package/dist/{chunk-CH2NWSES.js → chunk-TIV6M7Q7.js} +3 -3
  31. package/dist/{chunk-6XNYYROM.js → chunk-WVEDW5VO.js} +2 -2
  32. package/dist/clipboard-fallback-dialog.js +4 -4
  33. package/dist/controls.cjs +34 -13
  34. package/dist/controls.cjs.map +1 -1
  35. package/dist/controls.js +9 -8
  36. package/dist/data-table/data-row-inspector.cjs +99 -11
  37. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  38. package/dist/data-table/data-row-inspector.js +8 -7
  39. package/dist/data-table/data-row-window.cjs +99 -11
  40. package/dist/data-table/data-row-window.cjs.map +1 -1
  41. package/dist/data-table/data-row-window.js +11 -10
  42. package/dist/data-table/drill-chart.cjs +15 -3
  43. package/dist/data-table/drill-chart.cjs.map +1 -1
  44. package/dist/data-table/drill-chart.js +2 -1
  45. package/dist/data-table/drill-chart.js.map +1 -1
  46. package/dist/data-table/host.cjs.map +1 -1
  47. package/dist/data-table/host.d.cts +6 -2
  48. package/dist/data-table/host.d.ts +6 -2
  49. package/dist/data-table/host.js +1 -1
  50. package/dist/data-table/index.cjs +54 -40
  51. package/dist/data-table/index.cjs.map +1 -1
  52. package/dist/data-table/index.d.cts +6 -2
  53. package/dist/data-table/index.d.ts +6 -2
  54. package/dist/data-table/index.js +25 -28
  55. package/dist/data-table/index.js.map +1 -1
  56. package/dist/data-table/pagination.cjs +19 -10
  57. package/dist/data-table/pagination.cjs.map +1 -1
  58. package/dist/data-table/pagination.js +4 -4
  59. package/dist/data-table/uuid-cell.cjs +99 -11
  60. package/dist/data-table/uuid-cell.cjs.map +1 -1
  61. package/dist/data-table/uuid-cell.js +5 -4
  62. package/dist/desktop.cjs +45 -15
  63. package/dist/desktop.cjs.map +1 -1
  64. package/dist/desktop.js +23 -15
  65. package/dist/desktop.js.map +1 -1
  66. package/dist/email-input-dialog.cjs +15 -3
  67. package/dist/email-input-dialog.cjs.map +1 -1
  68. package/dist/email-input-dialog.js +4 -3
  69. package/dist/email-input-dialog.js.map +1 -1
  70. package/dist/error-sentence.cjs +130 -0
  71. package/dist/error-sentence.cjs.map +1 -0
  72. package/dist/error-sentence.d.cts +49 -0
  73. package/dist/error-sentence.d.ts +49 -0
  74. package/dist/error-sentence.js +22 -0
  75. package/dist/error-sentence.js.map +1 -0
  76. package/dist/index.cjs +47 -26
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.d.cts +12 -10
  79. package/dist/index.d.ts +12 -10
  80. package/dist/index.js +30 -28
  81. package/dist/index.js.map +1 -1
  82. package/dist/item.cjs +19 -10
  83. package/dist/item.cjs.map +1 -1
  84. package/dist/item.js +7 -7
  85. package/dist/lite-table.cjs +15 -3
  86. package/dist/lite-table.cjs.map +1 -1
  87. package/dist/lite-table.js +5 -4
  88. package/dist/lite-table.js.map +1 -1
  89. package/dist/menu-look.cjs +19 -10
  90. package/dist/menu-look.cjs.map +1 -1
  91. package/dist/menu-look.js +2 -2
  92. package/dist/notice-stack.cjs +2 -0
  93. package/dist/notice-stack.cjs.map +1 -1
  94. package/dist/notice-stack.d.cts +11 -1
  95. package/dist/notice-stack.d.ts +11 -1
  96. package/dist/notice-stack.js +3 -1
  97. package/dist/record-list.cjs +34 -13
  98. package/dist/record-list.cjs.map +1 -1
  99. package/dist/record-list.js +8 -7
  100. package/dist/record-list.js.map +1 -1
  101. package/dist/sandbox-gate-dialog.js +2 -2
  102. package/dist/scope-mismatch-dialog.js +2 -2
  103. package/dist/settings.js +3 -3
  104. package/dist/swipe-deck.cjs +15 -3
  105. package/dist/swipe-deck.cjs.map +1 -1
  106. package/dist/swipe-deck.js +2 -1
  107. package/dist/swipe-deck.js.map +1 -1
  108. package/dist/text-input-dialog.cjs +15 -3
  109. package/dist/text-input-dialog.cjs.map +1 -1
  110. package/dist/text-input-dialog.js +4 -3
  111. package/dist/text-input-dialog.js.map +1 -1
  112. package/dist/value-prompts-dialog.js +2 -2
  113. package/dist/writing-field.js +5 -5
  114. package/package.json +12 -2
  115. package/dist/chunk-4WSXAP24.js.map +0 -1
  116. package/dist/chunk-LJKF267O.js.map +0 -1
  117. package/dist/chunk-U46XUQI7.js.map +0 -1
  118. package/dist/chunk-X6ZRHJQP.js.map +0 -1
  119. /package/dist/{chunk-UWTODOMD.js.map → chunk-2BQAHLU3.js.map} +0 -0
  120. /package/dist/{chunk-T4D7WAV7.js.map → chunk-42GFQMLA.js.map} +0 -0
  121. /package/dist/{chunk-FRJ4PPA2.js.map → chunk-CZGZV35G.js.map} +0 -0
  122. /package/dist/{chunk-2PO3N42Q.js.map → chunk-K3XWUACX.js.map} +0 -0
  123. /package/dist/{chunk-ZMUPFGQO.js.map → chunk-K5ZQROWN.js.map} +0 -0
  124. /package/dist/{chunk-MTE6N7XU.js.map → chunk-NFJ63LIH.js.map} +0 -0
  125. /package/dist/{chunk-CFUXEGW2.js.map → chunk-OUQT72UL.js.map} +0 -0
  126. /package/dist/{chunk-WUD23YTG.js.map → chunk-P56Y43RL.js.map} +0 -0
  127. /package/dist/{chunk-GMNROZQJ.js.map → chunk-QTPW44IJ.js.map} +0 -0
  128. /package/dist/{chunk-QA7EHFRX.js.map → chunk-T3FKFPCW.js.map} +0 -0
  129. /package/dist/{chunk-SOCNWQMX.js.map → chunk-TEMAM5ET.js.map} +0 -0
  130. /package/dist/{chunk-CH2NWSES.js.map → chunk-TIV6M7Q7.js.map} +0 -0
  131. /package/dist/{chunk-6XNYYROM.js.map → chunk-WVEDW5VO.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.86.358
4
+
5
+ UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
+ Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/design-system/src`.
7
+
8
+ ## 0.86.357
9
+
10
+ SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
11
+ so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
12
+ Diff: `git diff bd77a197ad 3cc6206acf -- apps/shared/design-system/src`
13
+
14
+ Changed shipped source files (3):
15
+ - `apps/shared/design-system/src/desktop/state.tsx`
16
+ - `apps/shared/design-system/src/error-sentence.ts`
17
+ - `apps/shared/design-system/src/notice-stack.ts`
18
+
3
19
  ## 0.86.356
4
20
 
5
21
  - Slider forwards an explicitly supplied `aria-valuetext` to its focusable thumb, so controls such as PageCompare announce their formatted value. Keyboard and numeric value behavior are unchanged. Consumer action: none.
@@ -5,10 +5,10 @@ import {
5
5
  DialogDescription,
6
6
  DialogHeader,
7
7
  DialogTitle
8
- } from "./chunk-UWTODOMD.js";
8
+ } from "./chunk-2BQAHLU3.js";
9
+ import "./chunk-LEPD6TV7.js";
9
10
  import "./chunk-U24MOJWK.js";
10
11
  import "./chunk-NDTDBBMM.js";
11
- import "./chunk-LEPD6TV7.js";
12
12
  import "./chunk-MOZRIHJF.js";
13
13
  import {
14
14
  Button
package/dist/carousel.js CHANGED
@@ -1,41 +1,42 @@
1
1
  "use client";
2
2
  import {
3
3
  IconButton
4
- } from "./chunk-2PO3N42Q.js";
4
+ } from "./chunk-K3XWUACX.js";
5
5
  import "./chunk-6NOLVA4E.js";
6
6
  import "./chunk-5IR3BZOS.js";
7
7
  import "./chunk-AV5C4YLL.js";
8
8
  import "./chunk-XERGQT2M.js";
9
9
  import "./chunk-WQ2QCMMA.js";
10
10
  import "./chunk-YRF22M5S.js";
11
- import "./chunk-GMNROZQJ.js";
11
+ import "./chunk-QTPW44IJ.js";
12
12
  import "./chunk-K4XCB576.js";
13
- import "./chunk-LJKF267O.js";
13
+ import "./chunk-5Z5GTXKL.js";
14
14
  import "./chunk-DRSBLHOF.js";
15
15
  import "./chunk-TH6DDCWF.js";
16
- import "./chunk-UWTODOMD.js";
17
- import "./chunk-IMQ3RX35.js";
18
- import "./chunk-U24MOJWK.js";
19
- import "./chunk-NDTDBBMM.js";
16
+ import "./chunk-2BQAHLU3.js";
20
17
  import {
21
18
  holdDragCursor
22
19
  } from "./chunk-LEPD6TV7.js";
23
- import "./chunk-MTE6N7XU.js";
20
+ import "./chunk-IMQ3RX35.js";
21
+ import "./chunk-U24MOJWK.js";
22
+ import "./chunk-NDTDBBMM.js";
23
+ import "./chunk-NFJ63LIH.js";
24
24
  import "./chunk-WFBPCMPF.js";
25
25
  import {
26
26
  Popover,
27
27
  PopoverContent,
28
28
  PopoverTrigger
29
29
  } from "./chunk-OPF265PU.js";
30
- import "./chunk-6XNYYROM.js";
30
+ import "./chunk-WVEDW5VO.js";
31
31
  import "./chunk-BD77DHUO.js";
32
32
  import "./chunk-BKCJUFD3.js";
33
33
  import {
34
34
  TooltipProvider
35
35
  } from "./chunk-PIQJULQB.js";
36
36
  import "./chunk-MOZRIHJF.js";
37
- import "./chunk-X6ZRHJQP.js";
37
+ import "./chunk-T57DLONB.js";
38
38
  import "./chunk-BNXNIN2S.js";
39
+ import "./chunk-HS6GMHKZ.js";
39
40
  import "./chunk-WFITMVDB.js";
40
41
  import "./chunk-WAT2XW7L.js";
41
42
  import "./chunk-7IOSA55D.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/carousel/Carousel.tsx","../src/carousel/model.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Carousel — THE slide-strip primitive. One component, two modes:\n *\n * viewer swipe (native scroll-snap) · arrows · Left/Right/Home/End · dots +\n * counter · thumbnail strip · aspect 1:1 | 4:5 | 9:16 | 16:9\n * editor viewer + add / duplicate / delete / drag-or-button reorder /\n * edit / version history / export\n *\n * The carousel never draws a slide. The host passes `renderSlide`, so the\n * same primitive carries generated images, text-on-image social slides and\n * presentation slides. It never stores anything either: `slides` is the\n * host's, every edit leaves through `onSlidesChange`, and a slide edit also\n * leaves through `onSlideChange` with the PREVIOUS version so the host can\n * keep history (see `model.ts` — the store contract). Export is a callback\n * for the same reason: downloads go through the host's export door.\n *\n * NO LAYOUT SHIFT. The frame is an aspect-ratio box and every control lives\n * inside it, in a fixed-height row below, or in the thumbnail strip; nothing\n * appears or disappears into the flow when the slide changes.\n */\n\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { holdDragCursor } from \"../drag-cursor\";\nimport { IconButton } from \"../icon-button\";\nimport { ArrowLeftIcon, ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon, CopyIcon, DownloadIcon, HistoryIcon, PencilIcon, PlusIcon, Trash2Icon } from \"../icons\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../popover\";\nimport { TooltipProvider } from \"../tooltip\";\n\nimport {\n DEFAULT_MAX_SLIDES,\n DEFAULT_MIN_SLIDES,\n aspectRatioOf,\n clampIndex,\n duplicateSlide,\n insertSlide,\n moveSlide,\n removeSlide,\n replaceSlide,\n stepIndex,\n type CarouselAspect,\n type CarouselSlideBase,\n type SlideChange,\n type SlideVersion,\n} from \"./model\";\n\nexport type CarouselMode = \"viewer\" | \"editor\";\n\nexport interface CarouselSlideContext<T> {\n readonly index: number;\n readonly count: number;\n readonly active: boolean;\n readonly mode: CarouselMode;\n /** True when drawing the small thumbnail copy — skip interactive chrome. */\n readonly thumbnail: boolean;\n /** Save an edited slide: reports `previous` + `next` to `onSlideChange`. */\n readonly update: (next: T) => void;\n}\n\nexport interface CarouselLabels {\n readonly carousel: string;\n readonly previous: string;\n readonly next: string;\n readonly slide: (index: number, count: number) => string;\n readonly add: string;\n readonly duplicate: string;\n readonly remove: string;\n readonly moveEarlier: string;\n readonly moveLater: string;\n readonly edit: string;\n readonly history: string;\n readonly restore: string;\n readonly noHistory: string;\n readonly export: string;\n}\n\nexport const DEFAULT_CAROUSEL_LABELS: CarouselLabels = {\n carousel: \"Slides\",\n previous: \"Previous slide\",\n next: \"Next slide\",\n slide: (index, count) => `Slide ${index + 1} of ${count}`,\n add: \"Add slide\",\n duplicate: \"Duplicate slide\",\n remove: \"Delete slide\",\n moveEarlier: \"Move earlier\",\n moveLater: \"Move later\",\n edit: \"Edit slide\",\n history: \"Version history\",\n restore: \"Restore\",\n noHistory: \"No earlier versions\",\n export: \"Export\",\n};\n\nexport interface CarouselProps<T extends CarouselSlideBase> {\n slides: readonly T[];\n /** Draws one slide. It fills the frame; the carousel owns the aspect box. */\n renderSlide: (slide: T, ctx: CarouselSlideContext<T>) => React.ReactNode;\n mode?: CarouselMode;\n aspect?: CarouselAspect;\n /** Controlled active index; omit for the carousel to keep its own. */\n index?: number;\n onIndexChange?: (index: number) => void;\n loop?: boolean;\n dots?: boolean;\n counter?: boolean;\n thumbnails?: boolean;\n /** Overrides the visible-slide height cap for tall aspects (CSS length). */\n maxHeight?: string;\n labels?: Partial<CarouselLabels>;\n className?: string;\n\n // Editor\n onSlidesChange?: (slides: T[], index: number) => void;\n /** Called with the previous and next slide whenever a slide is updated or restored. */\n onSlideChange?: (change: SlideChange<T>) => void;\n /** Makes \"Add\" available; returns a brand-new slide with a fresh id. */\n createSlide?: () => T;\n /** Fresh id for a duplicate; defaults to `crypto.randomUUID()`. */\n createId?: () => string;\n /** Deep copy hook for duplicating; defaults to a shallow copy. */\n cloneSlide?: (slide: T) => T;\n /** Shows an Edit button; the host opens its own editor for the slide. */\n onEditSlide?: (slide: T, index: number) => void;\n minSlides?: number;\n maxSlides?: number;\n /** Kept versions per slide id, newest first — the host's store, read-only here. */\n history?: Readonly<Record<string, readonly SlideVersion<T>[]>>;\n /** Shows an Export button. The host runs the download. */\n onExport?: (slides: readonly T[]) => void;\n}\n\nconst MAX_DOTS = 12;\n/** Thumbnail geometry (px): the strip row is 64px with 2px padding top and bottom. */\nconst THUMB_HEIGHT = 60;\nconst THUMB_CANVAS = 360;\n\nfunction prefersReducedMotion(): boolean {\n return typeof window !== \"undefined\" && window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches === true;\n}\n\nexport function Carousel<T extends CarouselSlideBase>({\n slides,\n renderSlide,\n mode = \"viewer\",\n aspect = \"1:1\",\n index: controlledIndex,\n onIndexChange,\n loop = false,\n dots = true,\n counter = true,\n thumbnails = true,\n maxHeight = \"70vh\",\n labels: labelOverrides,\n className,\n onSlidesChange,\n onSlideChange,\n createSlide,\n createId,\n cloneSlide,\n onEditSlide,\n minSlides = DEFAULT_MIN_SLIDES,\n maxSlides = DEFAULT_MAX_SLIDES,\n history,\n onExport,\n}: CarouselProps<T>) {\n const labels = { ...DEFAULT_CAROUSEL_LABELS, ...labelOverrides };\n const count = slides.length;\n const editor = mode === \"editor\";\n const [ownIndex, setOwnIndex] = React.useState(0);\n const index = clampIndex(controlledIndex ?? ownIndex, count);\n\n const trackRef = React.useRef<HTMLDivElement>(null);\n const target = React.useRef<number | null>(null);\n const settle = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const setIndex = React.useCallback(\n (next: number) => {\n if (controlledIndex === undefined) setOwnIndex(next);\n onIndexChange?.(next);\n },\n [controlledIndex, onIndexChange],\n );\n\n const scrollToIndex = React.useCallback((i: number, smooth: boolean) => {\n const track = trackRef.current;\n if (!track) return;\n track.scrollTo?.({ left: i * track.clientWidth, behavior: smooth && !prefersReducedMotion() ? \"smooth\" : \"auto\" });\n }, []);\n\n const goTo = React.useCallback(\n (raw: number) => {\n const next = clampIndex(raw, count);\n target.current = next;\n if (settle.current) clearTimeout(settle.current);\n settle.current = setTimeout(() => {\n target.current = null;\n }, 700);\n setIndex(next);\n scrollToIndex(next, true);\n },\n [count, scrollToIndex, setIndex],\n );\n\n // Keep the scroller on the active slide when the index or the deck changes from outside\n // (add, delete, reorder, a controlled parent) — without fighting a smooth scroll in flight.\n React.useLayoutEffect(() => {\n const track = trackRef.current;\n if (!track || target.current !== null || track.clientWidth === 0) return;\n if (Math.round(track.scrollLeft / track.clientWidth) !== index) scrollToIndex(index, false);\n }, [index, count, scrollToIndex]);\n\n React.useEffect(() => {\n const track = trackRef.current;\n if (!track || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => scrollToIndex(index, false));\n observer.observe(track);\n return () => observer.disconnect();\n }, [index, scrollToIndex]);\n\n React.useEffect(\n () => () => {\n if (settle.current) clearTimeout(settle.current);\n },\n [],\n );\n\n const onScroll = () => {\n const track = trackRef.current;\n if (!track || track.clientWidth === 0) return;\n const seen = clampIndex(Math.round(track.scrollLeft / track.clientWidth), count);\n if (target.current !== null) {\n if (seen === target.current) target.current = null;\n return;\n }\n if (seen !== index) setIndex(seen);\n };\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === \"ArrowLeft\") goTo(stepIndex(index, -1, count, loop));\n else if (event.key === \"ArrowRight\") goTo(stepIndex(index, 1, count, loop));\n else if (event.key === \"Home\") goTo(0);\n else if (event.key === \"End\") goTo(count - 1);\n else return;\n event.preventDefault();\n };\n\n // ---- editor operations -------------------------------------------------\n\n const emit = (edit: { slides: T[]; index: number } | null) => {\n if (!edit) return;\n onSlidesChange?.(edit.slides, edit.index);\n target.current = edit.index;\n setIndex(edit.index);\n // Land the scroller on the new active slide once the host's deck is rendered.\n requestAnimationFrame(() => {\n scrollToIndex(edit.index, false);\n target.current = null;\n });\n };\n\n const commitSlide = (next: T) => {\n const result = replaceSlide(slides, next);\n if (!result) return;\n onSlidesChange?.(result.slides, index);\n onSlideChange?.({ id: next.id, index: result.index, previous: result.previous, next });\n };\n\n const active = slides[index];\n const canAdd = editor && createSlide !== undefined && count < maxSlides;\n const canRemove = editor && count > minSlides;\n const activeHistory = active ? (history?.[active.id] ?? []) : [];\n\n const [w, h] = aspectRatioOf(aspect);\n const frameStyle: React.CSSProperties = {\n aspectRatio: `${w} / ${h}`,\n maxWidth: `min(100%, calc(${maxHeight} * ${w} / ${h}))`,\n };\n\n const contextFor = (_slide: T, i: number, thumbnail: boolean): CarouselSlideContext<T> => ({\n index: i,\n count,\n active: i === index,\n mode,\n thumbnail,\n update: thumbnail ? () => undefined : commitSlide,\n });\n\n return (\n // Its own provider: the control tooltips work in any host root (a portal, a window, a bare test).\n <TooltipProvider>\n <section\n aria-roledescription=\"carousel\"\n aria-label={labels.carousel}\n data-matrx-carousel={mode}\n className={cn(\"flex w-full min-w-0 flex-col gap-2\", className)}\n >\n <div\n className=\"relative mx-auto w-full overflow-hidden rounded-lg border border-border bg-muted\"\n style={frameStyle}\n >\n <div\n ref={trackRef}\n tabIndex={0}\n aria-live=\"polite\"\n aria-label={active ? labels.slide(index, count) : labels.carousel}\n onScroll={onScroll}\n onKeyDown={onKeyDown}\n className=\"flex h-full w-full snap-x snap-mandatory overflow-x-auto overscroll-x-contain outline-none [scrollbar-width:none] focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-scrollbar]:hidden\"\n >\n {slides.map((slide, i) => (\n <div\n key={slide.id}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={labels.slide(i, count)}\n aria-hidden={i !== index}\n inert={i !== index}\n className=\"h-full w-full shrink-0 snap-center snap-always\"\n >\n {renderSlide(slide, contextFor(slide, i, false))}\n </div>\n ))}\n </div>\n\n {count > 1 && (\n <>\n <div className=\"pointer-events-none absolute inset-y-0 left-2 flex items-center\">\n <IconButton\n icon={ChevronLeftIcon}\n variant=\"outline\"\n tooltip={labels.previous}\n disabled={!loop && index === 0}\n onClick={() => goTo(stepIndex(index, -1, count, loop))}\n className=\"pointer-events-auto bg-card\"\n />\n </div>\n <div className=\"pointer-events-none absolute inset-y-0 right-2 flex items-center\">\n <IconButton\n icon={ChevronRightIcon}\n variant=\"outline\"\n tooltip={labels.next}\n disabled={!loop && index === count - 1}\n onClick={() => goTo(stepIndex(index, 1, count, loop))}\n className=\"pointer-events-auto bg-card\"\n />\n </div>\n </>\n )}\n {counter && count > 0 && (\n <span\n data-matrx-carousel-counter\n className=\"pointer-events-none absolute right-2 top-2 rounded-full bg-card px-2 py-0.5 text-xs tabular-nums text-foreground ring-1 ring-border\"\n >\n {index + 1} / {count}\n </span>\n )}\n </div>\n\n {/* One fixed-height row: dots when they fit, so the strip below never jumps. */}\n {dots && count > 1 && count <= MAX_DOTS && (\n <div className=\"flex h-3 items-center justify-center gap-1.5\" role=\"tablist\" aria-label={labels.carousel}>\n {slides.map((slide, i) => (\n <button\n key={slide.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === index}\n aria-label={labels.slide(i, count)}\n onClick={() => goTo(i)}\n className={cn(\n \"h-1.5 rounded-full transition-[width,background-color]\",\n i === index ? \"w-4 bg-primary\" : \"w-1.5 bg-muted-foreground/40\",\n )}\n />\n ))}\n </div>\n )}\n\n {editor && (\n <div className=\"flex flex-wrap items-center justify-center gap-1\" role=\"toolbar\" aria-label={labels.carousel}>\n <IconButton\n icon={ArrowLeftIcon}\n tooltip={labels.moveEarlier}\n disabled={index === 0}\n onClick={() => emit(moveSlide(slides, index, index - 1, index))}\n />\n <IconButton\n icon={ArrowRightIcon}\n tooltip={labels.moveLater}\n disabled={index >= count - 1}\n onClick={() => emit(moveSlide(slides, index, index + 1, index))}\n />\n {onEditSlide && active && (\n <IconButton icon={PencilIcon} tooltip={labels.edit} onClick={() => onEditSlide(active, index)} />\n )}\n <IconButton\n icon={PlusIcon}\n tooltip={labels.add}\n disabled={!canAdd}\n onClick={() => createSlide && emit(insertSlide(slides, createSlide(), index, maxSlides))}\n />\n <IconButton\n icon={CopyIcon}\n tooltip={labels.duplicate}\n disabled={!active || count >= maxSlides}\n onClick={() =>\n emit(duplicateSlide(slides, index, createId ? createId() : crypto.randomUUID(), maxSlides, cloneSlide))\n }\n />\n <IconButton\n icon={Trash2Icon}\n tooltip={labels.remove}\n disabled={!canRemove}\n onClick={() => emit(removeSlide(slides, index, index, minSlides))}\n />\n {history !== undefined && active && (\n <Popover>\n <PopoverTrigger asChild>\n <IconButton icon={HistoryIcon} tooltip={labels.history} />\n </PopoverTrigger>\n <PopoverContent className=\"w-56 p-1\" align=\"center\">\n {activeHistory.length === 0 ? (\n <p className=\"px-2 py-1.5 text-xs text-muted-foreground\">{labels.noHistory}</p>\n ) : (\n <ul className=\"flex flex-col\">\n {activeHistory.map((version) => (\n <li key={version.at} className=\"flex items-center justify-between gap-2 px-2 py-1\">\n <time className=\"text-xs tabular-nums text-muted-foreground\" dateTime={new Date(version.at).toISOString()}>\n {new Date(version.at).toLocaleTimeString([], { hour: \"numeric\", minute: \"2-digit\" })}\n </time>\n <button\n type=\"button\"\n className=\"text-xs text-primary hover:underline\"\n onClick={() => commitSlide({ ...version.slide, id: active.id })}\n >\n {labels.restore}\n </button>\n </li>\n ))}\n </ul>\n )}\n </PopoverContent>\n </Popover>\n )}\n {onExport && (\n <IconButton icon={DownloadIcon} tooltip={labels.export} onClick={() => onExport(slides)} />\n )}\n </div>\n )}\n\n {thumbnails && count > 1 && (\n <Thumbnails\n slides={slides}\n index={index}\n aspect={aspect}\n editor={editor}\n onSelect={goTo}\n onReorder={(from, to) => emit(moveSlide(slides, from, to, index))}\n renderThumb={(slide, i) => renderSlide(slide, contextFor(slide, i, true))}\n label={labels.slide}\n />\n )}\n </section>\n </TooltipProvider>\n );\n}\n\nfunction Thumbnails<T extends CarouselSlideBase>({\n slides,\n index,\n aspect,\n editor,\n onSelect,\n onReorder,\n renderThumb,\n label,\n}: {\n slides: readonly T[];\n index: number;\n aspect: CarouselAspect;\n editor: boolean;\n onSelect: (i: number) => void;\n onReorder: (from: number, to: number) => void;\n renderThumb: (slide: T, i: number) => React.ReactNode;\n label: CarouselLabels[\"slide\"];\n}) {\n const [w, h] = aspectRatioOf(aspect);\n const dragFrom = React.useRef<number | null>(null);\n const release = React.useRef<(() => void) | null>(null);\n const [over, setOver] = React.useState<number | null>(null);\n const activeRef = React.useRef<HTMLButtonElement>(null);\n\n React.useEffect(() => {\n activeRef.current?.scrollIntoView?.({ block: \"nearest\", inline: \"nearest\" });\n }, [index]);\n\n const end = () => {\n dragFrom.current = null;\n release.current?.();\n release.current = null;\n setOver(null);\n };\n\n return (\n <div\n data-matrx-carousel-thumbnails\n className=\"flex h-16 gap-2 overflow-x-auto py-0.5 [scrollbar-width:thin]\"\n >\n {slides.map((slide, i) => (\n <button\n key={slide.id}\n ref={i === index ? activeRef : undefined}\n type=\"button\"\n aria-label={label(i, slides.length)}\n aria-current={i === index}\n draggable={editor}\n data-clickable\n onClick={() => onSelect(i)}\n onDragStart={(event) => {\n dragFrom.current = i;\n release.current = holdDragCursor(\"grabbing\");\n event.dataTransfer.effectAllowed = \"move\";\n event.dataTransfer.setData(\"text/plain\", slide.id);\n }}\n onDragOver={(event) => {\n if (dragFrom.current === null) return;\n event.preventDefault();\n setOver(i);\n }}\n onDrop={(event) => {\n event.preventDefault();\n const from = dragFrom.current;\n end();\n if (from !== null && from !== i) onReorder(from, i);\n }}\n onDragEnd={end}\n style={{ aspectRatio: `${w} / ${h}`, height: \"100%\" }}\n className={cn(\n \"relative shrink-0 overflow-hidden rounded-md border bg-muted\",\n i === index ? \"border-primary ring-2 ring-primary/40\" : \"border-border opacity-80 hover:opacity-100\",\n over === i && dragFrom.current !== i && \"ring-2 ring-ring\",\n )}\n >\n {/* The slide draws at a fixed 360px-wide canvas and is scaled down, so a thumbnail is a true miniature. */}\n <span\n className=\"pointer-events-none block origin-top-left\"\n style={{\n width: THUMB_CANVAS,\n height: (THUMB_CANVAS * h) / w,\n transform: `scale(${(THUMB_HEIGHT * (w / h)) / THUMB_CANVAS})`,\n }}\n >\n {renderThumb(slide, i)}\n </span>\n <span className=\"pointer-events-none absolute bottom-0.5 left-0.5 rounded bg-card px-1 text-[10px] tabular-nums text-foreground\">\n {i + 1}\n </span>\n </button>\n ))}\n </div>\n );\n}\n","/**\n * The carousel's pure slide logic — reorder, add, duplicate, delete, index\n * bookkeeping, and the version-history contract. No React, no DOM: the\n * component only calls these, so the editing rules are proven without a\n * browser (`model.test.ts`).\n *\n * VERSION HISTORY IS A STORE CONTRACT, NOT STORAGE. The carousel never saves\n * anything. When a slide is edited it reports `{ previous, next }`; the host\n * keeps `previous` wherever it keeps history (`pushVersion` is the one\n * bounded, newest-first way to do it) and hands the list back through the\n * `history` prop so the History menu can show it.\n */\n\n/** Every slide carries a stable id — reorder and history are keyed on it. */\nexport interface CarouselSlideBase {\n readonly id: string;\n}\n\n/** The aspect ratios social and presentation slides come in. */\nexport const CAROUSEL_ASPECTS = [\"1:1\", \"4:5\", \"9:16\", \"16:9\"] as const;\nexport type CarouselAspect = (typeof CAROUSEL_ASPECTS)[number];\n\n/** `[width, height]` of an aspect — the one place the ratio is spelled. */\nexport function aspectRatioOf(aspect: CarouselAspect): readonly [number, number] {\n switch (aspect) {\n case \"1:1\":\n return [1, 1];\n case \"4:5\":\n return [4, 5];\n case \"9:16\":\n return [9, 16];\n case \"16:9\":\n return [16, 9];\n }\n}\n\n/** Social carousels run 2-10 slides; a host with other limits passes its own. */\nexport const DEFAULT_MIN_SLIDES = 1;\nexport const DEFAULT_MAX_SLIDES = 10;\nexport const DEFAULT_HISTORY_LIMIT = 20;\n\nexport function clampIndex(index: number, length: number): number {\n if (length <= 0) return 0;\n if (!Number.isFinite(index)) return 0;\n return Math.min(Math.max(Math.trunc(index), 0), length - 1);\n}\n\n/** Step `index` by `delta`; wraps when `loop`, otherwise stops at the ends. */\nexport function stepIndex(index: number, delta: number, length: number, loop: boolean): number {\n if (length <= 0) return 0;\n const next = index + delta;\n if (loop) return ((next % length) + length) % length;\n return clampIndex(next, length);\n}\n\nexport interface SlideEdit<T> {\n readonly slides: T[];\n /** Where the active slide ends up after the edit. */\n readonly index: number;\n}\n\n/** Move the slide at `from` to position `to`. A no-op returns the same array. */\nexport function moveSlide<T>(slides: readonly T[], from: number, to: number, active: number): SlideEdit<T> {\n const last = slides.length - 1;\n if (from < 0 || from > last || to < 0 || to > last || from === to) {\n return { slides: slides as T[], index: active };\n }\n const next = slides.slice();\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved as T);\n // The active slide follows the same item through the move.\n let index = active;\n if (active === from) index = to;\n else if (from < active && to >= active) index = active - 1;\n else if (from > active && to <= active) index = active + 1;\n return { slides: next, index };\n}\n\n/** Insert `slide` after `after` (default: at the end); the new slide becomes active. */\nexport function insertSlide<T>(\n slides: readonly T[],\n slide: T,\n after: number = slides.length - 1,\n max: number = DEFAULT_MAX_SLIDES,\n): SlideEdit<T> | null {\n if (slides.length >= max) return null;\n const at = clampIndex(after, slides.length + 1) + (slides.length === 0 ? 0 : 1);\n const next = slides.slice();\n next.splice(at, 0, slide);\n return { slides: next, index: at };\n}\n\n/** Copy the slide at `index` right after itself with a new id; the copy becomes active. */\nexport function duplicateSlide<T extends CarouselSlideBase>(\n slides: readonly T[],\n index: number,\n newId: string,\n max: number = DEFAULT_MAX_SLIDES,\n clone: (slide: T) => T = (slide) => ({ ...slide }),\n): SlideEdit<T> | null {\n const source = slides[index];\n if (!source || slides.length >= max) return null;\n const copy = { ...clone(source), id: newId } as T;\n const next = slides.slice();\n next.splice(index + 1, 0, copy);\n return { slides: next, index: index + 1 };\n}\n\n/** Remove the slide at `index`; refuses below `min` and keeps the active slide in range. */\nexport function removeSlide<T>(\n slides: readonly T[],\n index: number,\n active: number,\n min: number = DEFAULT_MIN_SLIDES,\n): SlideEdit<T> | null {\n if (index < 0 || index >= slides.length || slides.length <= min) return null;\n const next = slides.slice();\n next.splice(index, 1);\n let nextActive = active;\n if (index < active || (index === active && active === slides.length - 1)) nextActive = active - 1;\n return { slides: next, index: clampIndex(nextActive, next.length) };\n}\n\n/** Replace the slide with `next.id`; reports what it replaced. */\nexport function replaceSlide<T extends CarouselSlideBase>(\n slides: readonly T[],\n next: T,\n): { slides: T[]; previous: T; index: number } | null {\n const index = slides.findIndex((slide) => slide.id === next.id);\n const previous = slides[index];\n if (index < 0 || previous === undefined) return null;\n const copy = slides.slice();\n copy[index] = next;\n return { slides: copy, previous, index };\n}\n\n// ---------------------------------------------------------------------------\n// Version history — the store contract.\n// ---------------------------------------------------------------------------\n\n/** One kept version of a slide; `at` is epoch ms the host stamped when it kept it. */\nexport interface SlideVersion<T> {\n readonly slide: T;\n readonly at: number;\n}\n\n/** What `onSlideChange` hands the host: the slide before, the slide after. */\nexport interface SlideChange<T> {\n readonly id: string;\n readonly index: number;\n readonly previous: T;\n readonly next: T;\n}\n\n/** Keep `previous` at the head of `history`, newest first, bounded to `limit`. */\nexport function pushVersion<T>(\n history: readonly SlideVersion<T>[] | undefined,\n previous: T,\n at: number,\n limit: number = DEFAULT_HISTORY_LIMIT,\n): SlideVersion<T>[] {\n return [{ slide: previous, at }, ...(history ?? [])].slice(0, Math.max(limit, 0));\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/carousel/Carousel.tsx","../src/carousel/model.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Carousel — THE slide-strip primitive. One component, two modes:\n *\n * viewer swipe (native scroll-snap) · arrows · Left/Right/Home/End · dots +\n * counter · thumbnail strip · aspect 1:1 | 4:5 | 9:16 | 16:9\n * editor viewer + add / duplicate / delete / drag-or-button reorder /\n * edit / version history / export\n *\n * The carousel never draws a slide. The host passes `renderSlide`, so the\n * same primitive carries generated images, text-on-image social slides and\n * presentation slides. It never stores anything either: `slides` is the\n * host's, every edit leaves through `onSlidesChange`, and a slide edit also\n * leaves through `onSlideChange` with the PREVIOUS version so the host can\n * keep history (see `model.ts` — the store contract). Export is a callback\n * for the same reason: downloads go through the host's export door.\n *\n * NO LAYOUT SHIFT. The frame is an aspect-ratio box and every control lives\n * inside it, in a fixed-height row below, or in the thumbnail strip; nothing\n * appears or disappears into the flow when the slide changes.\n */\n\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { holdDragCursor } from \"../drag-cursor\";\nimport { IconButton } from \"../icon-button\";\nimport { ArrowLeftIcon, ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon, CopyIcon, DownloadIcon, HistoryIcon, PencilIcon, PlusIcon, Trash2Icon } from \"../icons\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../popover\";\nimport { TooltipProvider } from \"../tooltip\";\n\nimport {\n DEFAULT_MAX_SLIDES,\n DEFAULT_MIN_SLIDES,\n aspectRatioOf,\n clampIndex,\n duplicateSlide,\n insertSlide,\n moveSlide,\n removeSlide,\n replaceSlide,\n stepIndex,\n type CarouselAspect,\n type CarouselSlideBase,\n type SlideChange,\n type SlideVersion,\n} from \"./model\";\n\nexport type CarouselMode = \"viewer\" | \"editor\";\n\nexport interface CarouselSlideContext<T> {\n readonly index: number;\n readonly count: number;\n readonly active: boolean;\n readonly mode: CarouselMode;\n /** True when drawing the small thumbnail copy — skip interactive chrome. */\n readonly thumbnail: boolean;\n /** Save an edited slide: reports `previous` + `next` to `onSlideChange`. */\n readonly update: (next: T) => void;\n}\n\nexport interface CarouselLabels {\n readonly carousel: string;\n readonly previous: string;\n readonly next: string;\n readonly slide: (index: number, count: number) => string;\n readonly add: string;\n readonly duplicate: string;\n readonly remove: string;\n readonly moveEarlier: string;\n readonly moveLater: string;\n readonly edit: string;\n readonly history: string;\n readonly restore: string;\n readonly noHistory: string;\n readonly export: string;\n}\n\nexport const DEFAULT_CAROUSEL_LABELS: CarouselLabels = {\n carousel: \"Slides\",\n previous: \"Previous slide\",\n next: \"Next slide\",\n slide: (index, count) => `Slide ${index + 1} of ${count}`,\n add: \"Add slide\",\n duplicate: \"Duplicate slide\",\n remove: \"Delete slide\",\n moveEarlier: \"Move earlier\",\n moveLater: \"Move later\",\n edit: \"Edit slide\",\n history: \"Version history\",\n restore: \"Restore\",\n noHistory: \"No earlier versions\",\n export: \"Export\",\n};\n\nexport interface CarouselProps<T extends CarouselSlideBase> {\n slides: readonly T[];\n /** Draws one slide. It fills the frame; the carousel owns the aspect box. */\n renderSlide: (slide: T, ctx: CarouselSlideContext<T>) => React.ReactNode;\n mode?: CarouselMode;\n aspect?: CarouselAspect;\n /** Controlled active index; omit for the carousel to keep its own. */\n index?: number;\n onIndexChange?: (index: number) => void;\n loop?: boolean;\n dots?: boolean;\n counter?: boolean;\n thumbnails?: boolean;\n /** Overrides the visible-slide height cap for tall aspects (CSS length). */\n maxHeight?: string;\n labels?: Partial<CarouselLabels>;\n className?: string;\n\n // Editor\n onSlidesChange?: (slides: T[], index: number) => void;\n /** Called with the previous and next slide whenever a slide is updated or restored. */\n onSlideChange?: (change: SlideChange<T>) => void;\n /** Makes \"Add\" available; returns a brand-new slide with a fresh id. */\n createSlide?: () => T;\n /** Fresh id for a duplicate; defaults to `crypto.randomUUID()`. */\n createId?: () => string;\n /** Deep copy hook for duplicating; defaults to a shallow copy. */\n cloneSlide?: (slide: T) => T;\n /** Shows an Edit button; the host opens its own editor for the slide. */\n onEditSlide?: (slide: T, index: number) => void;\n minSlides?: number;\n maxSlides?: number;\n /** Kept versions per slide id, newest first — the host's store, read-only here. */\n history?: Readonly<Record<string, readonly SlideVersion<T>[]>>;\n /** Shows an Export button. The host runs the download. */\n onExport?: (slides: readonly T[]) => void;\n}\n\nconst MAX_DOTS = 12;\n/** Thumbnail geometry (px): the strip row is 64px with 2px padding top and bottom. */\nconst THUMB_HEIGHT = 60;\nconst THUMB_CANVAS = 360;\n\nfunction prefersReducedMotion(): boolean {\n return typeof window !== \"undefined\" && window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches === true;\n}\n\nexport function Carousel<T extends CarouselSlideBase>({\n slides,\n renderSlide,\n mode = \"viewer\",\n aspect = \"1:1\",\n index: controlledIndex,\n onIndexChange,\n loop = false,\n dots = true,\n counter = true,\n thumbnails = true,\n maxHeight = \"70vh\",\n labels: labelOverrides,\n className,\n onSlidesChange,\n onSlideChange,\n createSlide,\n createId,\n cloneSlide,\n onEditSlide,\n minSlides = DEFAULT_MIN_SLIDES,\n maxSlides = DEFAULT_MAX_SLIDES,\n history,\n onExport,\n}: CarouselProps<T>) {\n const labels = { ...DEFAULT_CAROUSEL_LABELS, ...labelOverrides };\n const count = slides.length;\n const editor = mode === \"editor\";\n const [ownIndex, setOwnIndex] = React.useState(0);\n const index = clampIndex(controlledIndex ?? ownIndex, count);\n\n const trackRef = React.useRef<HTMLDivElement>(null);\n const target = React.useRef<number | null>(null);\n const settle = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const setIndex = React.useCallback(\n (next: number) => {\n if (controlledIndex === undefined) setOwnIndex(next);\n onIndexChange?.(next);\n },\n [controlledIndex, onIndexChange],\n );\n\n const scrollToIndex = React.useCallback((i: number, smooth: boolean) => {\n const track = trackRef.current;\n if (!track) return;\n track.scrollTo?.({ left: i * track.clientWidth, behavior: smooth && !prefersReducedMotion() ? \"smooth\" : \"auto\" });\n }, []);\n\n const goTo = React.useCallback(\n (raw: number) => {\n const next = clampIndex(raw, count);\n target.current = next;\n if (settle.current) clearTimeout(settle.current);\n settle.current = setTimeout(() => {\n target.current = null;\n }, 700);\n setIndex(next);\n scrollToIndex(next, true);\n },\n [count, scrollToIndex, setIndex],\n );\n\n // Keep the scroller on the active slide when the index or the deck changes from outside\n // (add, delete, reorder, a controlled parent) — without fighting a smooth scroll in flight.\n React.useLayoutEffect(() => {\n const track = trackRef.current;\n if (!track || target.current !== null || track.clientWidth === 0) return;\n if (Math.round(track.scrollLeft / track.clientWidth) !== index) scrollToIndex(index, false);\n }, [index, count, scrollToIndex]);\n\n React.useEffect(() => {\n const track = trackRef.current;\n if (!track || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => scrollToIndex(index, false));\n observer.observe(track);\n return () => observer.disconnect();\n }, [index, scrollToIndex]);\n\n React.useEffect(\n () => () => {\n if (settle.current) clearTimeout(settle.current);\n },\n [],\n );\n\n const onScroll = () => {\n const track = trackRef.current;\n if (!track || track.clientWidth === 0) return;\n const seen = clampIndex(Math.round(track.scrollLeft / track.clientWidth), count);\n if (target.current !== null) {\n if (seen === target.current) target.current = null;\n return;\n }\n if (seen !== index) setIndex(seen);\n };\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === \"ArrowLeft\") goTo(stepIndex(index, -1, count, loop));\n else if (event.key === \"ArrowRight\") goTo(stepIndex(index, 1, count, loop));\n else if (event.key === \"Home\") goTo(0);\n else if (event.key === \"End\") goTo(count - 1);\n else return;\n event.preventDefault();\n };\n\n // ---- editor operations -------------------------------------------------\n\n const emit = (edit: { slides: T[]; index: number } | null) => {\n if (!edit) return;\n onSlidesChange?.(edit.slides, edit.index);\n target.current = edit.index;\n setIndex(edit.index);\n // Land the scroller on the new active slide once the host's deck is rendered.\n requestAnimationFrame(() => {\n scrollToIndex(edit.index, false);\n target.current = null;\n });\n };\n\n const commitSlide = (next: T) => {\n const result = replaceSlide(slides, next);\n if (!result) return;\n onSlidesChange?.(result.slides, index);\n onSlideChange?.({ id: next.id, index: result.index, previous: result.previous, next });\n };\n\n const active = slides[index];\n const canAdd = editor && createSlide !== undefined && count < maxSlides;\n const canRemove = editor && count > minSlides;\n const activeHistory = active ? (history?.[active.id] ?? []) : [];\n\n const [w, h] = aspectRatioOf(aspect);\n const frameStyle: React.CSSProperties = {\n aspectRatio: `${w} / ${h}`,\n maxWidth: `min(100%, calc(${maxHeight} * ${w} / ${h}))`,\n };\n\n const contextFor = (_slide: T, i: number, thumbnail: boolean): CarouselSlideContext<T> => ({\n index: i,\n count,\n active: i === index,\n mode,\n thumbnail,\n update: thumbnail ? () => undefined : commitSlide,\n });\n\n return (\n // Its own provider: the control tooltips work in any host root (a portal, a window, a bare test).\n <TooltipProvider>\n <section\n aria-roledescription=\"carousel\"\n aria-label={labels.carousel}\n data-matrx-carousel={mode}\n className={cn(\"flex w-full min-w-0 flex-col gap-2\", className)}\n >\n <div\n className=\"relative mx-auto w-full overflow-hidden rounded-lg border border-border bg-muted\"\n style={frameStyle}\n >\n <div\n ref={trackRef}\n tabIndex={0}\n aria-live=\"polite\"\n aria-label={active ? labels.slide(index, count) : labels.carousel}\n onScroll={onScroll}\n onKeyDown={onKeyDown}\n className=\"flex h-full w-full snap-x snap-mandatory overflow-x-auto overscroll-x-contain outline-none [scrollbar-width:none] focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-scrollbar]:hidden\"\n >\n {slides.map((slide, i) => (\n <div\n key={slide.id}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={labels.slide(i, count)}\n aria-hidden={i !== index}\n inert={i !== index}\n className=\"h-full w-full shrink-0 snap-center snap-always\"\n >\n {renderSlide(slide, contextFor(slide, i, false))}\n </div>\n ))}\n </div>\n\n {count > 1 && (\n <>\n <div className=\"pointer-events-none absolute inset-y-0 left-2 flex items-center\">\n <IconButton\n icon={ChevronLeftIcon}\n variant=\"outline\"\n tooltip={labels.previous}\n disabled={!loop && index === 0}\n onClick={() => goTo(stepIndex(index, -1, count, loop))}\n className=\"pointer-events-auto bg-card\"\n />\n </div>\n <div className=\"pointer-events-none absolute inset-y-0 right-2 flex items-center\">\n <IconButton\n icon={ChevronRightIcon}\n variant=\"outline\"\n tooltip={labels.next}\n disabled={!loop && index === count - 1}\n onClick={() => goTo(stepIndex(index, 1, count, loop))}\n className=\"pointer-events-auto bg-card\"\n />\n </div>\n </>\n )}\n {counter && count > 0 && (\n <span\n data-matrx-carousel-counter\n className=\"pointer-events-none absolute right-2 top-2 rounded-full bg-card px-2 py-0.5 text-xs tabular-nums text-foreground ring-1 ring-border\"\n >\n {index + 1} / {count}\n </span>\n )}\n </div>\n\n {/* One fixed-height row: dots when they fit, so the strip below never jumps. */}\n {dots && count > 1 && count <= MAX_DOTS && (\n <div className=\"flex h-3 items-center justify-center gap-1.5\" role=\"tablist\" aria-label={labels.carousel}>\n {slides.map((slide, i) => (\n <button\n key={slide.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === index}\n aria-label={labels.slide(i, count)}\n onClick={() => goTo(i)}\n className={cn(\n \"h-1.5 rounded-full transition-[width,background-color]\",\n i === index ? \"w-4 bg-primary\" : \"w-1.5 bg-muted-foreground/40\",\n )}\n />\n ))}\n </div>\n )}\n\n {editor && (\n <div className=\"flex flex-wrap items-center justify-center gap-1\" role=\"toolbar\" aria-label={labels.carousel}>\n <IconButton\n icon={ArrowLeftIcon}\n tooltip={labels.moveEarlier}\n disabled={index === 0}\n onClick={() => emit(moveSlide(slides, index, index - 1, index))}\n />\n <IconButton\n icon={ArrowRightIcon}\n tooltip={labels.moveLater}\n disabled={index >= count - 1}\n onClick={() => emit(moveSlide(slides, index, index + 1, index))}\n />\n {onEditSlide && active && (\n <IconButton icon={PencilIcon} tooltip={labels.edit} onClick={() => onEditSlide(active, index)} />\n )}\n <IconButton\n icon={PlusIcon}\n tooltip={labels.add}\n disabled={!canAdd}\n onClick={() => createSlide && emit(insertSlide(slides, createSlide(), index, maxSlides))}\n />\n <IconButton\n icon={CopyIcon}\n tooltip={labels.duplicate}\n disabled={!active || count >= maxSlides}\n onClick={() =>\n emit(duplicateSlide(slides, index, createId ? createId() : crypto.randomUUID(), maxSlides, cloneSlide))\n }\n />\n <IconButton\n icon={Trash2Icon}\n tooltip={labels.remove}\n disabled={!canRemove}\n onClick={() => emit(removeSlide(slides, index, index, minSlides))}\n />\n {history !== undefined && active && (\n <Popover>\n <PopoverTrigger asChild>\n <IconButton icon={HistoryIcon} tooltip={labels.history} />\n </PopoverTrigger>\n <PopoverContent className=\"w-56 p-1\" align=\"center\">\n {activeHistory.length === 0 ? (\n <p className=\"px-2 py-1.5 text-xs text-muted-foreground\">{labels.noHistory}</p>\n ) : (\n <ul className=\"flex flex-col\">\n {activeHistory.map((version) => (\n <li key={version.at} className=\"flex items-center justify-between gap-2 px-2 py-1\">\n <time className=\"text-xs tabular-nums text-muted-foreground\" dateTime={new Date(version.at).toISOString()}>\n {new Date(version.at).toLocaleTimeString([], { hour: \"numeric\", minute: \"2-digit\" })}\n </time>\n <button\n type=\"button\"\n className=\"text-xs text-primary hover:underline\"\n onClick={() => commitSlide({ ...version.slide, id: active.id })}\n >\n {labels.restore}\n </button>\n </li>\n ))}\n </ul>\n )}\n </PopoverContent>\n </Popover>\n )}\n {onExport && (\n <IconButton icon={DownloadIcon} tooltip={labels.export} onClick={() => onExport(slides)} />\n )}\n </div>\n )}\n\n {thumbnails && count > 1 && (\n <Thumbnails\n slides={slides}\n index={index}\n aspect={aspect}\n editor={editor}\n onSelect={goTo}\n onReorder={(from, to) => emit(moveSlide(slides, from, to, index))}\n renderThumb={(slide, i) => renderSlide(slide, contextFor(slide, i, true))}\n label={labels.slide}\n />\n )}\n </section>\n </TooltipProvider>\n );\n}\n\nfunction Thumbnails<T extends CarouselSlideBase>({\n slides,\n index,\n aspect,\n editor,\n onSelect,\n onReorder,\n renderThumb,\n label,\n}: {\n slides: readonly T[];\n index: number;\n aspect: CarouselAspect;\n editor: boolean;\n onSelect: (i: number) => void;\n onReorder: (from: number, to: number) => void;\n renderThumb: (slide: T, i: number) => React.ReactNode;\n label: CarouselLabels[\"slide\"];\n}) {\n const [w, h] = aspectRatioOf(aspect);\n const dragFrom = React.useRef<number | null>(null);\n const release = React.useRef<(() => void) | null>(null);\n const [over, setOver] = React.useState<number | null>(null);\n const activeRef = React.useRef<HTMLButtonElement>(null);\n\n React.useEffect(() => {\n activeRef.current?.scrollIntoView?.({ block: \"nearest\", inline: \"nearest\" });\n }, [index]);\n\n const end = () => {\n dragFrom.current = null;\n release.current?.();\n release.current = null;\n setOver(null);\n };\n\n return (\n <div\n data-matrx-carousel-thumbnails\n className=\"flex h-16 gap-2 overflow-x-auto py-0.5 [scrollbar-width:thin]\"\n >\n {slides.map((slide, i) => (\n <button\n key={slide.id}\n ref={i === index ? activeRef : undefined}\n type=\"button\"\n aria-label={label(i, slides.length)}\n aria-current={i === index}\n draggable={editor}\n data-clickable\n onClick={() => onSelect(i)}\n onDragStart={(event) => {\n dragFrom.current = i;\n release.current = holdDragCursor(\"grabbing\");\n event.dataTransfer.effectAllowed = \"move\";\n event.dataTransfer.setData(\"text/plain\", slide.id);\n }}\n onDragOver={(event) => {\n if (dragFrom.current === null) return;\n event.preventDefault();\n setOver(i);\n }}\n onDrop={(event) => {\n event.preventDefault();\n const from = dragFrom.current;\n end();\n if (from !== null && from !== i) onReorder(from, i);\n }}\n onDragEnd={end}\n style={{ aspectRatio: `${w} / ${h}`, height: \"100%\" }}\n className={cn(\n \"relative shrink-0 overflow-hidden rounded-md border bg-muted\",\n i === index ? \"border-primary ring-2 ring-primary/40\" : \"border-border opacity-80 hover:opacity-100\",\n over === i && dragFrom.current !== i && \"ring-2 ring-ring\",\n )}\n >\n {/* The slide draws at a fixed 360px-wide canvas and is scaled down, so a thumbnail is a true miniature. */}\n <span\n className=\"pointer-events-none block origin-top-left\"\n style={{\n width: THUMB_CANVAS,\n height: (THUMB_CANVAS * h) / w,\n transform: `scale(${(THUMB_HEIGHT * (w / h)) / THUMB_CANVAS})`,\n }}\n >\n {renderThumb(slide, i)}\n </span>\n <span className=\"pointer-events-none absolute bottom-0.5 left-0.5 rounded bg-card px-1 text-[10px] tabular-nums text-foreground\">\n {i + 1}\n </span>\n </button>\n ))}\n </div>\n );\n}\n","/**\n * The carousel's pure slide logic — reorder, add, duplicate, delete, index\n * bookkeeping, and the version-history contract. No React, no DOM: the\n * component only calls these, so the editing rules are proven without a\n * browser (`model.test.ts`).\n *\n * VERSION HISTORY IS A STORE CONTRACT, NOT STORAGE. The carousel never saves\n * anything. When a slide is edited it reports `{ previous, next }`; the host\n * keeps `previous` wherever it keeps history (`pushVersion` is the one\n * bounded, newest-first way to do it) and hands the list back through the\n * `history` prop so the History menu can show it.\n */\n\n/** Every slide carries a stable id — reorder and history are keyed on it. */\nexport interface CarouselSlideBase {\n readonly id: string;\n}\n\n/** The aspect ratios social and presentation slides come in. */\nexport const CAROUSEL_ASPECTS = [\"1:1\", \"4:5\", \"9:16\", \"16:9\"] as const;\nexport type CarouselAspect = (typeof CAROUSEL_ASPECTS)[number];\n\n/** `[width, height]` of an aspect — the one place the ratio is spelled. */\nexport function aspectRatioOf(aspect: CarouselAspect): readonly [number, number] {\n switch (aspect) {\n case \"1:1\":\n return [1, 1];\n case \"4:5\":\n return [4, 5];\n case \"9:16\":\n return [9, 16];\n case \"16:9\":\n return [16, 9];\n }\n}\n\n/** Social carousels run 2-10 slides; a host with other limits passes its own. */\nexport const DEFAULT_MIN_SLIDES = 1;\nexport const DEFAULT_MAX_SLIDES = 10;\nexport const DEFAULT_HISTORY_LIMIT = 20;\n\nexport function clampIndex(index: number, length: number): number {\n if (length <= 0) return 0;\n if (!Number.isFinite(index)) return 0;\n return Math.min(Math.max(Math.trunc(index), 0), length - 1);\n}\n\n/** Step `index` by `delta`; wraps when `loop`, otherwise stops at the ends. */\nexport function stepIndex(index: number, delta: number, length: number, loop: boolean): number {\n if (length <= 0) return 0;\n const next = index + delta;\n if (loop) return ((next % length) + length) % length;\n return clampIndex(next, length);\n}\n\nexport interface SlideEdit<T> {\n readonly slides: T[];\n /** Where the active slide ends up after the edit. */\n readonly index: number;\n}\n\n/** Move the slide at `from` to position `to`. A no-op returns the same array. */\nexport function moveSlide<T>(slides: readonly T[], from: number, to: number, active: number): SlideEdit<T> {\n const last = slides.length - 1;\n if (from < 0 || from > last || to < 0 || to > last || from === to) {\n return { slides: slides as T[], index: active };\n }\n const next = slides.slice();\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved as T);\n // The active slide follows the same item through the move.\n let index = active;\n if (active === from) index = to;\n else if (from < active && to >= active) index = active - 1;\n else if (from > active && to <= active) index = active + 1;\n return { slides: next, index };\n}\n\n/** Insert `slide` after `after` (default: at the end); the new slide becomes active. */\nexport function insertSlide<T>(\n slides: readonly T[],\n slide: T,\n after: number = slides.length - 1,\n max: number = DEFAULT_MAX_SLIDES,\n): SlideEdit<T> | null {\n if (slides.length >= max) return null;\n const at = clampIndex(after, slides.length + 1) + (slides.length === 0 ? 0 : 1);\n const next = slides.slice();\n next.splice(at, 0, slide);\n return { slides: next, index: at };\n}\n\n/** Copy the slide at `index` right after itself with a new id; the copy becomes active. */\nexport function duplicateSlide<T extends CarouselSlideBase>(\n slides: readonly T[],\n index: number,\n newId: string,\n max: number = DEFAULT_MAX_SLIDES,\n clone: (slide: T) => T = (slide) => ({ ...slide }),\n): SlideEdit<T> | null {\n const source = slides[index];\n if (!source || slides.length >= max) return null;\n const copy = { ...clone(source), id: newId } as T;\n const next = slides.slice();\n next.splice(index + 1, 0, copy);\n return { slides: next, index: index + 1 };\n}\n\n/** Remove the slide at `index`; refuses below `min` and keeps the active slide in range. */\nexport function removeSlide<T>(\n slides: readonly T[],\n index: number,\n active: number,\n min: number = DEFAULT_MIN_SLIDES,\n): SlideEdit<T> | null {\n if (index < 0 || index >= slides.length || slides.length <= min) return null;\n const next = slides.slice();\n next.splice(index, 1);\n let nextActive = active;\n if (index < active || (index === active && active === slides.length - 1)) nextActive = active - 1;\n return { slides: next, index: clampIndex(nextActive, next.length) };\n}\n\n/** Replace the slide with `next.id`; reports what it replaced. */\nexport function replaceSlide<T extends CarouselSlideBase>(\n slides: readonly T[],\n next: T,\n): { slides: T[]; previous: T; index: number } | null {\n const index = slides.findIndex((slide) => slide.id === next.id);\n const previous = slides[index];\n if (index < 0 || previous === undefined) return null;\n const copy = slides.slice();\n copy[index] = next;\n return { slides: copy, previous, index };\n}\n\n// ---------------------------------------------------------------------------\n// Version history — the store contract.\n// ---------------------------------------------------------------------------\n\n/** One kept version of a slide; `at` is epoch ms the host stamped when it kept it. */\nexport interface SlideVersion<T> {\n readonly slide: T;\n readonly at: number;\n}\n\n/** What `onSlideChange` hands the host: the slide before, the slide after. */\nexport interface SlideChange<T> {\n readonly id: string;\n readonly index: number;\n readonly previous: T;\n readonly next: T;\n}\n\n/** Keep `previous` at the head of `history`, newest first, bounded to `limit`. */\nexport function pushVersion<T>(\n history: readonly SlideVersion<T>[] | undefined,\n previous: T,\n at: number,\n limit: number = DEFAULT_HISTORY_LIMIT,\n): SlideVersion<T>[] {\n return [{ slide: previous, at }, ...(history ?? [])].slice(0, Math.max(limit, 0));\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,7 @@
1
1
  "use client";
2
+ import {
3
+ holdDragCursor
4
+ } from "./chunk-LEPD6TV7.js";
2
5
  import {
3
6
  CloseRow,
4
7
  CloseSlotProvider,
@@ -12,9 +15,6 @@ import {
12
15
  useModalLayer,
13
16
  useRadixDialogModal
14
17
  } from "./chunk-NDTDBBMM.js";
15
- import {
16
- holdDragCursor
17
- } from "./chunk-LEPD6TV7.js";
18
18
  import {
19
19
  keepMovedFocus
20
20
  } from "./chunk-DGNHZBJY.js";
@@ -613,4 +613,4 @@ export {
613
613
  DialogTitle,
614
614
  DialogDescription
615
615
  };
616
- //# sourceMappingURL=chunk-UWTODOMD.js.map
616
+ //# sourceMappingURL=chunk-2BQAHLU3.js.map
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  DropdownMenuContent,
11
11
  DropdownMenuSubContent
12
- } from "./chunk-X6ZRHJQP.js";
12
+ } from "./chunk-T57DLONB.js";
13
13
  import {
14
14
  useScrollFade
15
15
  } from "./chunk-BNXNIN2S.js";
@@ -409,4 +409,4 @@ export {
409
409
  MenuLookPanelSeparator,
410
410
  MenuLookSearch
411
411
  };
412
- //# sourceMappingURL=chunk-T4D7WAV7.js.map
412
+ //# sourceMappingURL=chunk-42GFQMLA.js.map
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import {
3
+ peopleErrorSentence
4
+ } from "./chunk-HS6GMHKZ.js";
5
+
6
+ // src/error-box.tsx
7
+ import * as React from "react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ var CONTEXT_KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/design-system/error-actions");
10
+ var registry = globalThis;
11
+ var ErrorActionsContext = registry[CONTEXT_KEY] ?? (registry[CONTEXT_KEY] = React.createContext(null));
12
+ function ErrorActionsProvider({
13
+ render,
14
+ children
15
+ }) {
16
+ return /* @__PURE__ */ jsx(ErrorActionsContext.Provider, { value: render, children });
17
+ }
18
+ var MENUS_KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/design-system/error-card-menus");
19
+ var menusRegistry = globalThis;
20
+ var ErrorCardMenusContext = menusRegistry[MENUS_KEY] ?? (menusRegistry[MENUS_KEY] = React.createContext(null));
21
+ function ErrorCardMenusProvider({
22
+ menus,
23
+ children
24
+ }) {
25
+ return /* @__PURE__ */ jsx(ErrorCardMenusContext.Provider, { value: menus, children });
26
+ }
27
+ function useErrorCardMenus() {
28
+ return React.useContext(ErrorCardMenusContext);
29
+ }
30
+ function useErrorActions() {
31
+ return React.useContext(ErrorActionsContext);
32
+ }
33
+ function ErrorActions(props) {
34
+ const render = useErrorActions();
35
+ if (!render) return null;
36
+ return /* @__PURE__ */ jsx(Fragment, { children: render(props) });
37
+ }
38
+ function sayable(body, error) {
39
+ if (typeof body === "string") return peopleErrorSentence(error, body);
40
+ if (Array.isArray(body)) return body.map((b) => typeof b === "string" && b.trim() ? peopleErrorSentence(error, b) : b);
41
+ return body;
42
+ }
43
+ var ErrorBox = React.forwardRef(function ErrorBox2({
44
+ as = "p",
45
+ message,
46
+ title,
47
+ error,
48
+ operation,
49
+ records,
50
+ unsavedInput,
51
+ origin,
52
+ children,
53
+ ...rest
54
+ }, ref) {
55
+ const Tag = as;
56
+ const facts = {
57
+ ...message !== void 0 ? { message } : {},
58
+ ...title !== void 0 ? { title } : {},
59
+ ...error !== void 0 ? { error } : {},
60
+ ...operation !== void 0 ? { operation } : {},
61
+ ...records !== void 0 ? { records } : {},
62
+ ...unsavedInput !== void 0 ? { unsavedInput } : {},
63
+ ...origin !== void 0 ? { origin } : {}
64
+ };
65
+ return /* @__PURE__ */ jsxs(Tag, { ref, role: "alert", "data-error-box": "", ...rest, children: [
66
+ sayable(children ?? message, error),
67
+ /* @__PURE__ */ jsx(ErrorActions, { ...facts })
68
+ ] });
69
+ });
70
+
71
+ export {
72
+ ErrorActionsProvider,
73
+ ErrorCardMenusProvider,
74
+ useErrorCardMenus,
75
+ useErrorActions,
76
+ ErrorActions,
77
+ ErrorBox
78
+ };
79
+ //# sourceMappingURL=chunk-5Z5GTXKL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error-box.tsx"],"sourcesContent":["/**\n * ErrorBox — THE one way a package draws an error on screen, and the seam a\n * host uses to put its own actions on every one of them.\n *\n * Every error sentence this package (and every sibling package that depends\n * on it) shows is an `ErrorBox`. The box is a plain `role=\"alert\"` element —\n * its tag and classes are the caller's, so nothing about the look changes — and\n * after its children it renders whatever the host's `ErrorActionsProvider`\n * returns for that error. With no provider it renders nothing extra.\n *\n * Why a slot and not a menu: the package cannot know the host's AI copy, bug\n * report, or support flow (Matrx fills it with its Alchemy copy-for-AI menu at\n * the app root). The package supplies the error's facts — the sentence, the\n * raw error, the operation, the records — and the host decides the actions.\n *\n * Guard: `error-box.census.test.ts` fails when a package file renders\n * `role=\"alert\"` for an error without going through `ErrorBox` (or, for the\n * `Alert` family, `ErrorActions`).\n */\nimport * as React from \"react\";\nimport { peopleErrorSentence } from \"./error-sentence\";\n\nexport interface ErrorActionsRecord {\n type: string;\n id: string;\n label?: string;\n}\n\n/** The facts a package knows about an error it is showing. All optional. */\nexport interface ErrorActionsInput {\n /** The sentence shown. Omit when the words are only in the rendered box. */\n message?: string | undefined;\n title?: string | undefined;\n /** The raw error (code/status/stack stay available to the host). */\n error?: unknown | undefined;\n /** What was being attempted (\"Load saved views\"). */\n operation?: string | undefined;\n records?: readonly ErrorActionsRecord[] | undefined;\n /** The person's input that did not land. */\n unsavedInput?: unknown | undefined;\n /** The package + component that drew it (\"design-system/SavedViewsControl\"). */\n origin?: string | undefined;\n /** Extra structured facts for the AI payload (never shown). */\n details?: Record<string, unknown> | undefined;\n code?: string | number | undefined;\n status?: number | undefined;\n /** The tables / RPCs the failed call used — pins its captured request as the cause. */\n calls?: readonly string[] | undefined;\n /** Where it was drawn: an `ErrorNotice` card/line (\"inline\") or a package alert box (\"alert\", the default). */\n source?: \"inline\" | \"alert\" | undefined;\n}\n\nexport type ErrorActionsRenderer = (input: ErrorActionsInput) => React.ReactNode;\n\n/**\n * ONE context for every copy of this module. tsup builds each entry point\n * (`.`, `./data-table`, …) without code splitting, so this file is inlined into\n * several bundles; a plain `createContext` would give each bundle its own\n * context and the host's provider (imported from `.`) would never reach an\n * error drawn by the data table. The registry key makes them one.\n */\nconst CONTEXT_KEY = Symbol.for(\"@ai-matrx/design-system/error-actions\");\ntype ContextRegistry = Record<symbol, React.Context<ErrorActionsRenderer | null> | undefined>;\nconst registry = globalThis as unknown as ContextRegistry;\nconst ErrorActionsContext: React.Context<ErrorActionsRenderer | null> =\n registry[CONTEXT_KEY] ??\n (registry[CONTEXT_KEY] = React.createContext<ErrorActionsRenderer | null>(null));\n\n/** Mount once at the host's app root; every ErrorBox below renders `render(...)`. */\nexport function ErrorActionsProvider({\n render,\n children,\n}: {\n render: ErrorActionsRenderer;\n children: React.ReactNode;\n}) {\n return (\n <ErrorActionsContext.Provider value={render}>{children}</ErrorActionsContext.Provider>\n );\n}\n\n/**\n * The host's content menu around an error card (`ErrorNotice`): the corner ⋯\n * button and the right-click boundary that show the host's content actions\n * over the error's sentence. Read-only — nothing here may change a record.\n * Without a provider the card draws neither (the actions slot still renders).\n */\nexport interface ErrorCardMenus {\n /** The corner ⋯ button over `{ title, text }`. */\n Button: React.ComponentType<{ title: string; text: string; className?: string }>;\n /** Wraps the whole card so right-click opens the same menu. */\n Boundary: React.ComponentType<{ title: string; text: string; children: React.ReactNode }>;\n}\n\nconst MENUS_KEY = Symbol.for(\"@ai-matrx/design-system/error-card-menus\");\ntype MenusRegistry = Record<symbol, React.Context<ErrorCardMenus | null> | undefined>;\nconst menusRegistry = globalThis as unknown as MenusRegistry;\nconst ErrorCardMenusContext: React.Context<ErrorCardMenus | null> =\n menusRegistry[MENUS_KEY] ??\n (menusRegistry[MENUS_KEY] = React.createContext<ErrorCardMenus | null>(null));\n\n/** Mount once at the host's app root beside `ErrorActionsProvider`. */\nexport function ErrorCardMenusProvider({\n menus,\n children,\n}: {\n menus: ErrorCardMenus;\n children: React.ReactNode;\n}) {\n return <ErrorCardMenusContext.Provider value={menus}>{children}</ErrorCardMenusContext.Provider>;\n}\n\nexport function useErrorCardMenus(): ErrorCardMenus | null {\n return React.useContext(ErrorCardMenusContext);\n}\n\nexport function useErrorActions(): ErrorActionsRenderer | null {\n return React.useContext(ErrorActionsContext);\n}\n\n/**\n * The host's actions for one error, placed wherever the caller puts this\n * element. Renders nothing without a provider. Use inside a box you do not\n * render yourself (the `Alert` family); everything else uses `ErrorBox`.\n */\nexport function ErrorActions(props: ErrorActionsInput) {\n const render = useErrorActions();\n if (!render) return null;\n return <>{render(props)}</>;\n}\n\ntype ErrorBoxTag = \"p\" | \"div\" | \"span\" | \"section\" | \"li\";\n\nexport interface ErrorBoxProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"role\" | \"title\">,\n ErrorActionsInput {\n /** The element drawn — the caller's existing tag, so layout never shifts. */\n as?: ErrorBoxTag;\n}\n\n/** A plain-string body goes through the one funnel (error-sentence.ts); rich children are the caller's. */\nfunction sayable(body: React.ReactNode, error: unknown): React.ReactNode {\n if (typeof body === \"string\") return peopleErrorSentence(error, body);\n if (Array.isArray(body)) return body.map((b) => (typeof b === \"string\" && b.trim() ? peopleErrorSentence(error, b) : b));\n return body;\n}\n\nexport const ErrorBox = React.forwardRef<HTMLElement, ErrorBoxProps>(function ErrorBox(\n {\n as = \"p\",\n message,\n title,\n error,\n operation,\n records,\n unsavedInput,\n origin,\n children,\n ...rest\n },\n ref,\n) {\n // A component type, never a bare ElementType: an app compiling this source may carry global JSX\n // elements (react-three-fiber) that make a bare ElementType's props `never` (2026-10-22).\n const Tag = as as unknown as React.ComponentType<React.HTMLAttributes<HTMLElement> & { ref?: React.Ref<HTMLElement> }>;\n const facts: ErrorActionsInput = {\n ...(message !== undefined ? { message } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(error !== undefined ? { error } : {}),\n ...(operation !== undefined ? { operation } : {}),\n ...(records !== undefined ? { records } : {}),\n ...(unsavedInput !== undefined ? { unsavedInput } : {}),\n ...(origin !== undefined ? { origin } : {}),\n };\n return (\n <Tag ref={ref} role=\"alert\" data-error-box=\"\" {...rest}>\n {sayable(children ?? message, error)}\n <ErrorActions {...facts} />\n </Tag>\n );\n});\n"],"mappings":";;;;;;AAmBA,YAAY,WAAW;AA0DnB,SAmDK,UAnDL,KAkGA,YAlGA;AAhBJ,IAAM,cAAc,uBAAO,IAAI,uCAAuC;AAEtE,IAAM,WAAW;AACjB,IAAM,sBACJ,SAAS,WAAW,MACnB,SAAS,WAAW,IAAU,oBAA2C,IAAI;AAGzE,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,QAAS,UAAS;AAE3D;AAeA,IAAM,YAAY,uBAAO,IAAI,0CAA0C;AAEvE,IAAM,gBAAgB;AACtB,IAAM,wBACJ,cAAc,SAAS,MACtB,cAAc,SAAS,IAAU,oBAAqC,IAAI;AAGtE,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AACF,GAGG;AACD,SAAO,oBAAC,sBAAsB,UAAtB,EAA+B,OAAO,OAAQ,UAAS;AACjE;AAEO,SAAS,oBAA2C;AACzD,SAAa,iBAAW,qBAAqB;AAC/C;AAEO,SAAS,kBAA+C;AAC7D,SAAa,iBAAW,mBAAmB;AAC7C;AAOO,SAAS,aAAa,OAA0B;AACrD,QAAM,SAAS,gBAAgB;AAC/B,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,gCAAG,iBAAO,KAAK,GAAE;AAC1B;AAYA,SAAS,QAAQ,MAAuB,OAAiC;AACvE,MAAI,OAAO,SAAS,SAAU,QAAO,oBAAoB,OAAO,IAAI;AACpE,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,CAAC,MAAO,OAAO,MAAM,YAAY,EAAE,KAAK,IAAI,oBAAoB,OAAO,CAAC,IAAI,CAAE;AACvH,SAAO;AACT;AAEO,IAAM,WAAiB,iBAAuC,SAASA,UAC5E;AAAA,EACE,KAAK;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AAGA,QAAM,MAAM;AACZ,QAAM,QAA2B;AAAA,IAC/B,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACvC,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACvC,GAAI,cAAc,SAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IAC/C,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,IACrD,GAAI,WAAW,SAAY,EAAE,OAAO,IAAI,CAAC;AAAA,EAC3C;AACA,SACE,qBAAC,OAAI,KAAU,MAAK,SAAQ,kBAAe,IAAI,GAAG,MAC/C;AAAA,YAAQ,YAAY,SAAS,KAAK;AAAA,IACnC,oBAAC,gBAAc,GAAG,OAAO;AAAA,KAC3B;AAEJ,CAAC;","names":["ErrorBox"]}
@@ -228,4 +228,4 @@ export {
228
228
  MatrxTableRowControlsScope,
229
229
  useMatrxTableRowControls
230
230
  };
231
- //# sourceMappingURL=chunk-POJ3RR3K.js.map
231
+ //# sourceMappingURL=chunk-B5D3VIMC.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/table-host/row-drawing.ts","../src/table-host/host.tsx"],"sourcesContent":["/**\n * A ROW IS REDRAWN WHEN WHAT IT SHOWS CHANGED — NEVER BECAUSE A FUNCTION WAS BORN AGAIN\n * (lane TABLE-RERENDER, 2026-10-03).\n *\n * Hosts write `cell`, `customActions`, `getRowId`, `rowClassName` and their handlers inline, so every\n * host render hands the table new functions. Identity cannot say whether a new function draws\n * anything new, so the table used to redraw every row: /artifacts (560 rows) spent ~2.6 s redrawing\n * 1,120 rows to open one artifact. Asking every host to stabilise its callbacks fixed one screen of\n * ~85 at a time.\n *\n * The table now ASKS the row instead. When only the host's functions changed, a row builds its\n * elements (cheap — no component runs) and compares them with what it drew last:\n * · equal → it returns the SAME element object it committed, and React does not descend into it;\n * · different → it draws, and only that row pays.\n * Handlers inside a row that was not redrawn still call the host's LATEST function: every event\n * handler (`on[A-Z]…`) and every function the table marks `lazyCallback` is handed to the DOM as a\n * stable trampoline whose target is refreshed on each comparison (the latest-ref pattern, applied\n * to every handler a row holds instead of to a list the table has to keep in sync).\n *\n * Any other function in the tree (a render prop, a formatter a child calls while it renders, a\n * component type) is compared by identity, so a new one redraws the row — exactly as before. That\n * keeps the rule honest: a row is never left showing what an old closure drew. The one exception is\n * a prop its component DECLARED a lazy data getter (`declareLazyDataProps`): it is compared by what\n * it returns.\n */\nimport { cloneElement, isValidElement, type ReactElement, type ReactNode } from \"react\";\n\nconst LAZY = Symbol.for(\"@ai-matrx/design-system/data-table/lazy-callback\");\nconst MAX_DEPTH = 32;\nconst HANDLER_KEY = /^on[A-Z]/;\n\ntype AnyFunction = (...args: never[]) => unknown;\n\n/**\n * Marks a function the table hands into a row as one that runs LATER (on a click, a copy, a menu\n * choice) and never while a child renders — so a row may keep a trampoline to it instead of\n * redrawing when only its identity changed.\n */\nexport function lazyCallback<F extends AnyFunction>(fn: F): F {\n (fn as F & { [LAZY]?: true })[LAZY] = true;\n return fn;\n}\n\nfunction isLazy(fn: unknown): boolean {\n return (fn as { [LAZY]?: true })[LAZY] === true;\n}\n\nconst LAZY_DATA_PROPS = Symbol.for(\"@ai-matrx/design-system/data-table/lazy-data-props\");\n\n/**\n * Declares the props of `component` that take a GETTER of data the component reads only later —\n * a row menu's `config={() => menu}`, resolved when the menu opens. Without it a getter is just an\n * unknown function, compared by identity, and a host that builds it inline (every row menu does)\n * redraws every row on every render of its own: /crm redrew all 25 rows on each checkbox tick.\n *\n * A declared prop is compared by what the getter RETURNS (its handlers by trampoline, everything\n * else exactly as a drawing is): the same data → the row keeps its drawing and the component is\n * handed a stable trampoline to the host's LATEST getter, so it opens with fresh items; different\n * data (a checkbox in an open menu flipped) → that row alone redraws. Declare once, on the\n * component — never per call site.\n *\n * The contract: a declared getter is PURE and CHEAP — the row calls the previous and the new one on\n * every host render to compare them. A getter that fetches, dispatches or builds something heavy\n * must not be declared.\n */\nexport function declareLazyDataProps<C extends object>(component: C, keys: readonly string[]): C {\n (component as C & { [LAZY_DATA_PROPS]?: ReadonlySet<string> })[LAZY_DATA_PROPS] = new Set(keys);\n return component;\n}\n\nfunction lazyDataKeys(type: unknown): ReadonlySet<string> | undefined {\n if ((typeof type !== \"function\" && typeof type !== \"object\") || type === null) return undefined;\n return (type as { [LAZY_DATA_PROPS]?: ReadonlySet<string> })[LAZY_DATA_PROPS];\n}\n\nconst UNRESOLVED = Symbol(\"unresolved\");\n\n/** A declared getter's data — or `UNRESOLVED` when calling it threw (the row then simply redraws). */\nfunction resolveLazyData(value: unknown): unknown {\n if (typeof value !== \"function\") return value;\n try {\n return (value as () => unknown)();\n } catch {\n return UNRESOLVED;\n }\n}\n\nfunction trampolinable(key: string, fn: unknown): boolean {\n return isLazy(fn) || HANDLER_KEY.test(key);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\ninterface Slot {\n fn: (...args: unknown[]) => unknown;\n readonly stable: (...args: unknown[]) => unknown;\n}\n\nfunction makeSlot(fn: (...args: unknown[]) => unknown): Slot {\n const slot: Slot = {\n fn,\n // A Proxy rather than a bare wrapper: a handler that carries its own members (a debounced\n // function's `cancel`) keeps them, read from the latest function.\n stable: new Proxy((() => undefined) as (...args: unknown[]) => unknown, {\n apply: (_target, thisArg, args: unknown[]) => Reflect.apply(slot.fn, thisArg, args),\n get: (_target, key) => Reflect.get(slot.fn, key),\n }),\n };\n return slot;\n}\n\ntype Updates = Array<[path: string, fn: (...args: unknown[]) => unknown]>;\n\nfunction sameEntries(\n a: Record<string, unknown>,\n b: Record<string, unknown>,\n path: string,\n depth: number,\n updates: Updates,\n lazyKeys?: ReadonlySet<string>,\n): boolean {\n const keys = Object.keys(a);\n if (keys.length !== Object.keys(b).length) return false;\n for (const key of keys) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false;\n // A ref is never a handler: a new callback ref must be attached by React.\n if (key === \"ref\") {\n if (!Object.is(a[key], b[key])) return false;\n continue;\n }\n if (lazyKeys?.has(key) && (typeof a[key] === \"function\" || typeof b[key] === \"function\")) {\n if (Object.is(a[key], b[key])) continue;\n // A getter that became a plain value (or the reverse) is a new drawing: the committed element\n // still holds a trampoline to the OLD getter, which nothing would re-point.\n if (typeof a[key] !== typeof b[key]) return false;\n const before = resolveLazyData(a[key]);\n const after = resolveLazyData(b[key]);\n if (before === UNRESOLVED || after === UNRESOLVED) return false;\n // Handlers inside the data are reached through the getter's own trampoline, so the ones this\n // comparison collects are dropped: the component calls the LATEST getter and gets them fresh.\n if (!sameDrawing(before, after, key, `${path}/${key}()`, depth + 1, [])) return false;\n if (typeof b[key] === \"function\") updates.push([`${path}/${key}`, b[key] as (...args: unknown[]) => unknown]);\n continue;\n }\n if (!sameDrawing(a[key], b[key], key, `${path}/${key}`, depth + 1, updates)) return false;\n }\n return true;\n}\n\n/**\n * Whether `next` draws exactly what `previous` drew. Handlers that differ only by identity are\n * collected into `updates` (by path) so the caller can point the committed trampolines at them.\n */\nfunction sameDrawing(previous: unknown, next: unknown, key: string, path: string, depth: number, updates: Updates): boolean {\n if (Object.is(previous, next)) return true;\n if (typeof previous === \"function\" && typeof next === \"function\") {\n if (trampolinable(key, previous) && trampolinable(key, next)) {\n updates.push([path, next as (...args: unknown[]) => unknown]);\n return true;\n }\n return false;\n }\n if (depth > MAX_DEPTH) return false;\n if (isValidElement(previous) && isValidElement(next)) {\n if (previous.type !== next.type || previous.key !== next.key) return false;\n return sameEntries(\n previous.props as Record<string, unknown>,\n next.props as Record<string, unknown>,\n path,\n depth,\n updates,\n lazyDataKeys(previous.type),\n );\n }\n if (Array.isArray(previous) && Array.isArray(next)) {\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!sameDrawing(previous[i], next[i], key, `${path}/${i}`, depth + 1, updates)) return false;\n }\n return true;\n }\n if (isPlainObject(previous) && isPlainObject(next)) return sameEntries(previous, next, path, depth, updates);\n return false;\n}\n\n/** `value` with every trampolinable function replaced by its stable trampoline. */\nfunction stabilize(value: unknown, key: string, path: string, depth: number, slots: Map<string, Slot>, reuse: Map<string, Slot> | undefined, lazyData = false): unknown {\n if (typeof value === \"function\") {\n if (!lazyData && !trampolinable(key, value)) return value;\n const fn = value as (...args: unknown[]) => unknown;\n const kept = reuse?.get(path);\n const slot = kept ?? makeSlot(fn);\n slot.fn = fn;\n slots.set(path, slot);\n return slot.stable;\n }\n if (depth > MAX_DEPTH) return value;\n if (isValidElement(value)) {\n const props = value.props as Record<string, unknown>;\n const lazyKeys = lazyDataKeys(value.type);\n let changed: Record<string, unknown> | null = null;\n for (const propKey of Object.keys(props)) {\n if (propKey === \"ref\") continue;\n const before = props[propKey];\n const after = stabilize(before, propKey, `${path}/${propKey}`, depth + 1, slots, reuse, lazyKeys?.has(propKey) ?? false);\n if (after !== before) (changed ??= {})[propKey] = after;\n }\n if (!changed) return value;\n const clone = cloneElement(value as ReactElement, changed);\n // React (dev) marks JSX's static children as key-checked on the ELEMENT; a clone starts\n // unmarked, and a clone inside a static children array would then warn about a missing key.\n const before = (value as { _store?: { validated?: unknown } })._store;\n const after = (clone as { _store?: { validated?: unknown } })._store;\n if (before && after && \"validated\" in before) {\n try { after.validated = before.validated; } catch { /* frozen in some builds: only a dev warning is at stake */ }\n }\n return clone;\n }\n if (Array.isArray(value)) {\n let out: unknown[] | null = null;\n for (let i = 0; i < value.length; i += 1) {\n const after = stabilize(value[i], key, `${path}/${i}`, depth + 1, slots, reuse);\n if (after !== value[i]) (out ??= value.slice())[i] = after;\n }\n return out ?? value;\n }\n if (isPlainObject(value)) {\n let out: Record<string, unknown> | null = null;\n for (const entryKey of Object.keys(value)) {\n const before = value[entryKey];\n const after = stabilize(before, entryKey, `${path}/${entryKey}`, depth + 1, slots, reuse);\n if (after !== before) (out ??= { ...value })[entryKey] = after;\n }\n return out ?? value;\n }\n return value;\n}\n\ninterface RowDrawing {\n /** What the host and the table built last time — compared, never rendered. */\n raw: unknown;\n /** What React was handed: `raw` with trampolines. Returned unchanged while the drawing holds. */\n out: ReactNode;\n slots: Map<string, Slot>;\n}\n\n/** One table's drawings, by row id. */\nexport class RowDrawings {\n private readonly rows = new Map<string, RowDrawing>();\n\n /**\n * The element to hand React for this row. The SAME object as last time when `raw` draws what the\n * last one drew (its handlers are re-pointed); a new one otherwise.\n */\n draw(rowId: string, raw: ReactNode, fresh = false): ReactNode {\n const previous = this.rows.get(rowId);\n // `fresh`: the row's own facts moved — what it shows may differ even when its elements match.\n if (previous && !fresh) {\n const updates: Updates = [];\n if (sameDrawing(previous.raw, raw, \"\", \"\", 0, updates) && updates.every(([path]) => previous.slots.has(path))) {\n for (const [path, fn] of updates) previous.slots.get(path)!.fn = fn;\n previous.raw = raw;\n return previous.out;\n }\n }\n const slots = new Map<string, Slot>();\n const out = stabilize(raw, \"\", \"\", 0, slots, previous?.slots) as ReactNode;\n this.rows.set(rowId, { raw, out, slots });\n return out;\n }\n\n /** Forget rows that are no longer in the table (bounded by what was ever drawn). */\n prune(liveIds: ReadonlySet<string>): void {\n for (const id of this.rows.keys()) if (!liveIds.has(id)) this.rows.delete(id);\n }\n\n get size(): number {\n return this.rows.size;\n }\n}\n","\"use client\";\nimport { createContext, useContext, type Context, type ComponentType, type ReactNode, type MouseEventHandler } from \"react\";\nimport type { CopyControlProps } from \"../data-table/copy-types\";\nimport type { MatrxColumnDef, MatrxDataTableDensity, MatrxDataTableLook, MatrxDataTableRecordControls, TableSavedViewsProps } from \"../data-table/types\";\nimport type {\n MatrxTableMenuIconName,\n MatrxTableMenuItem,\n MatrxTableMenuPayload,\n MatrxTableMenuSectionsResolver,\n MatrxTableMenuTarget,\n} from \"../data-table/menu-targets\";\n\nexport type {\n MatrxTableMenuIconName,\n MatrxTableMenuItem,\n MatrxTableMenuSection,\n MatrxTableMenuSectionsResolver,\n} from \"../data-table/menu-targets\";\n\nexport type {\n AgentPayloadInput,\n AiCustomSource,\n AiVariant,\n ContentTransferReference,\n CopyControlProps,\n CopyEnvelopeMeta,\n CopyExportConfig,\n TableCopyController,\n} from \"../data-table/copy-types\";\n\nexport interface TableLinkProps { href: string; children: ReactNode; className?: string; onClick?: MouseEventHandler<HTMLAnchorElement>; }\n/**\n * Host-owned toolbar affordance. The table package keeps operation state and\n * labels; an application can supply its canonical icon-button primitive\n * without making the shared package depend on an application UI package.\n */\nexport interface TableToolbarActionProps { ariaLabel: string; tooltip?: string | undefined; disabled?: boolean | undefined; busy?: boolean | undefined; /** Indicates a selected mutually-exclusive action such as the current page. */ active?: boolean | undefined; onClick: () => void; children: ReactNode; }\nexport interface TableSidePanelProps { title: string; titleNode?: ReactNode; description?: ReactNode; onClose: () => void; /** Increments on every open request, including a repeat open of the row already shown — a host whose panel can sit in the background (a tab) brings it forward when this changes. */ openRequest?: number; defaultWidth?: number; headerActions?: ReactNode; children: ReactNode; }\nexport interface TableWindowPanelProps { id: string; title: string; onClose: () => void; width?: number; height?: number; minWidth?: number; minHeight?: number; actionsRight?: ReactNode; bodyClassName?: string; sidebarDefaultSize?: number; sidebarMinSize?: number; sidebar?: ReactNode; children: ReactNode; /** The table look the window opens from (lane mac-blockers): `\"mac\"` asks the host window for the Mac header. Absent: the default look. */ look?: MatrxDataTableLook; }\nexport interface TableEntityRefProps { token: string; id: string; href?: string; children: ReactNode; className?: string; /** Opening it starts a new tab. */ openInNewTab?: boolean; /** Its open / peek buttons stay shown, not only on hover. */ alwaysShowActions?: boolean; }\nexport interface TablePeekProps { kind: string; id: string; href?: string; onClose: () => void; }\nexport interface TableDoors {\n href?: string;\n peekKind?: string;\n canPeek?: boolean;\n /**\n * THE RECORD'S OWN MENU (lane DRILL-PARITY-LAST): what its kind offers wherever a table names it —\n * a person's admin destinations, an organization's pages — as neutral items the table draws in a\n * row's menu and on a right-click. Absent = the record has only its open and preview doors.\n */\n menu?: MatrxTableMenuItem[];\n}\n/**\n * The host's door onto the table's right-click levels.\n *\n * A canonical table registers ONE resolver per instance. The host's own menu\n * reads which of the four levels was clicked (`readMenuTarget`) and asks for\n * that level's descriptor — the package never interprets what comes back, and\n * the host never reaches into the table for the row or the range.\n */\nexport interface TableRowContextRegistry { register(tableId: string, resolve: (target: MatrxTableMenuTarget) => unknown): () => void; }\nexport interface TableContextMenuBoundaryProps {\n label: string;\n children: ReactNode;\n /**\n * The consumer's own sections, asked per right-click. Present only when the\n * table's consumer declared `contextMenu.sections`; the host reads the level\n * off the clicked element (`readMenuTarget`), calls this, and draws what comes\n * back in its own menu. Absent, the boundary behaves exactly as before.\n */\n sections?: MatrxTableMenuSectionsResolver | undefined;\n /** The table asked for the host's designed menu (`MatrxDataTableContextMenuConfig.designedMenu`). */\n designedMenu?: boolean | undefined;\n}\n/**\n * The host's view of a failed read with NO rows to show (RC-B12 round 13):\n * its error display (with the host's actions — matrx-frontend's Alchemy Menu)\n * and a retry. Without a host port the table draws a package `ErrorBox`.\n */\nexport interface TableReadFailureProps { error: unknown; what: string; onRetry?: (() => void) | undefined; }\n/**\n * The host's notice above rows kept from an earlier read when the latest read\n * failed: the rows may be out of date. Without a host port the table draws a\n * package `ErrorBox` in warning colors.\n */\nexport interface TableStaleNoticeProps { error: unknown; what: string; onRetry?: (() => void) | undefined; }\n/** The host's glyph for a menu icon name. A host without one draws labels only. */\nexport interface TableMenuIconProps { name: MatrxTableMenuIconName; className?: string | undefined; }\n/** The host's custom-field columns for a registry token over the given organizations (lane 7 W5). */\nexport interface TableCustomFieldColumns<T> { columns: MatrxColumnDef<T>[]; }\nexport interface MatrxDataTableHost { /** A HOOK, bound once by the host: the custom-field columns of `token` (null = none) for `organizationIds`. */ useCustomFieldColumns?: <T>(token: string | null, organizationIds: readonly string[]) => TableCustomFieldColumns<T> | null; JsonViewer?: ComponentType<{ data: object | string }>; Link?: ComponentType<TableLinkProps>; CopyControls?: ComponentType<CopyControlProps>; ToolbarAction?: ComponentType<TableToolbarActionProps>; SidePanelSurface?: ComponentType<TableSidePanelProps>; WindowPanel?: ComponentType<TableWindowPanelProps>; EntityRef?: ComponentType<TableEntityRefProps>; resolveEntityDoors?: (token: string, id: string, href?: string | null) => TableDoors | null; ResourcePeek?: ComponentType<TablePeekProps>; SavedViews?: ComponentType<TableSavedViewsProps>; rowContextRegistry?: TableRowContextRegistry; createDefaultMenuContext?: (payload: MatrxTableMenuPayload) => unknown; ContextMenuBoundary?: ComponentType<TableContextMenuBoundaryProps>; MenuIcon?: ComponentType<TableMenuIconProps>; /** A failed read with no rows (RC-B12 r13). */ ReadFailure?: ComponentType<TableReadFailureProps>; /** A failed refresh over kept rows (RC-B12 r13). */ StaleNotice?: ComponentType<TableStaleNoticeProps>; /** Host default; a table's explicit density takes precedence. */ defaultDensity?: MatrxDataTableDensity; /** Host default look (`default` | `mac`); a table's explicit `look` takes precedence. One switch turns every table under the host, and every mode built on it, to the Mac look. */ defaultLook?: MatrxDataTableLook; notify?: { success(message: string): void; error(message: string): void }; /** Client-side navigation (the app router), for `rowClick=\"page\"`. Absent: a full page load. */ navigate?: (href: string) => void; }\n// Public entrypoints are independently bundled in ESM and CJS. The context\n// identity must survive those bundle boundaries; values stay provider-scoped.\nconst HOST_SLOT = Symbol.for(\"ai-matrx.design-system.table-host-context\");\nconst hostRegistry = globalThis as typeof globalThis & { [HOST_SLOT]?: Context<MatrxDataTableHost> };\nconst HostContext = hostRegistry[HOST_SLOT] ??= createContext<MatrxDataTableHost>({});\nexport function MatrxDataTableProvider({ value, children }: { value: MatrxDataTableHost; children: ReactNode }) { return <HostContext.Provider value={value}>{children}</HostContext.Provider>; }\nexport function useMatrxDataTableHost(): MatrxDataTableHost { return useContext(HostContext); }\n\nexport interface MatrxTableRowAlchemy {\n copy: CopyControlProps;\n controls?: MatrxDataTableRecordControls;\n placement: \"inline\" | \"menu\";\n ready: boolean;\n activate(): void;\n /** Mount Alchemy's canonical palette in the host row menu. */\n mountMenu(target: HTMLElement | null, onClose?: () => void, restoreFocus?: () => void): void;\n prepare(restoreFocus?: () => void): Promise<void>;\n}\n\nconst ROW_ALCHEMY_SLOT = Symbol.for(\"ai-matrx.design-system.table-row-alchemy-context\");\nconst rowAlchemyRegistry = globalThis as typeof globalThis & { [ROW_ALCHEMY_SLOT]?: Context<MatrxTableRowAlchemy | null> };\nconst RowAlchemyContext = rowAlchemyRegistry[ROW_ALCHEMY_SLOT] ??= createContext<MatrxTableRowAlchemy | null>(null);\n\nexport function MatrxTableRowAlchemyScope({ value, children }: { value: MatrxTableRowAlchemy; children: ReactNode }) { return <RowAlchemyContext.Provider value={value}>{children}</RowAlchemyContext.Provider>; }\n\nexport function useMatrxTableRowAlchemy(): MatrxTableRowAlchemy | null { return useContext(RowAlchemyContext); }\n\nconst ROW_CONTROLS_SLOT = Symbol.for(\"ai-matrx.design-system.table-row-controls-context\");\nconst rowControlsRegistry = globalThis as typeof globalThis & { [ROW_CONTROLS_SLOT]?: Context<MatrxDataTableRecordControls | null> };\nconst RowControlsContext = rowControlsRegistry[ROW_CONTROLS_SLOT] ??= createContext<MatrxDataTableRecordControls | null>(null);\nexport function MatrxTableRowControlsScope({ value, children }: { value: MatrxDataTableRecordControls; children: ReactNode }) { return <RowControlsContext.Provider value={value}>{children}</RowControlsContext.Provider>; }\nexport function useMatrxTableRowControls(): MatrxDataTableRecordControls | null { return useContext(RowControlsContext); }\n\n/** Light door for components a row draws (a row menu): declare a lazy data prop without loading the table. */\nexport { declareLazyDataProps } from \"./row-drawing\";\n"],"mappings":"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","names":["before","after"]}
1
+ {"version":3,"sources":["../src/table-host/row-drawing.ts","../src/table-host/host.tsx"],"sourcesContent":["/**\n * A ROW IS REDRAWN WHEN WHAT IT SHOWS CHANGED — NEVER BECAUSE A FUNCTION WAS BORN AGAIN\n * (lane TABLE-RERENDER, 2026-10-03).\n *\n * Hosts write `cell`, `customActions`, `getRowId`, `rowClassName` and their handlers inline, so every\n * host render hands the table new functions. Identity cannot say whether a new function draws\n * anything new, so the table used to redraw every row: /artifacts (560 rows) spent ~2.6 s redrawing\n * 1,120 rows to open one artifact. Asking every host to stabilise its callbacks fixed one screen of\n * ~85 at a time.\n *\n * The table now ASKS the row instead. When only the host's functions changed, a row builds its\n * elements (cheap — no component runs) and compares them with what it drew last:\n * · equal → it returns the SAME element object it committed, and React does not descend into it;\n * · different → it draws, and only that row pays.\n * Handlers inside a row that was not redrawn still call the host's LATEST function: every event\n * handler (`on[A-Z]…`) and every function the table marks `lazyCallback` is handed to the DOM as a\n * stable trampoline whose target is refreshed on each comparison (the latest-ref pattern, applied\n * to every handler a row holds instead of to a list the table has to keep in sync).\n *\n * Any other function in the tree (a render prop, a formatter a child calls while it renders, a\n * component type) is compared by identity, so a new one redraws the row — exactly as before. That\n * keeps the rule honest: a row is never left showing what an old closure drew. The one exception is\n * a prop its component DECLARED a lazy data getter (`declareLazyDataProps`): it is compared by what\n * it returns.\n */\nimport { cloneElement, isValidElement, type ReactElement, type ReactNode } from \"react\";\n\nconst LAZY = Symbol.for(\"@ai-matrx/design-system/data-table/lazy-callback\");\nconst MAX_DEPTH = 32;\nconst HANDLER_KEY = /^on[A-Z]/;\n\ntype AnyFunction = (...args: never[]) => unknown;\n\n/**\n * Marks a function the table hands into a row as one that runs LATER (on a click, a copy, a menu\n * choice) and never while a child renders — so a row may keep a trampoline to it instead of\n * redrawing when only its identity changed.\n */\nexport function lazyCallback<F extends AnyFunction>(fn: F): F {\n (fn as F & { [LAZY]?: true })[LAZY] = true;\n return fn;\n}\n\nfunction isLazy(fn: unknown): boolean {\n return (fn as { [LAZY]?: true })[LAZY] === true;\n}\n\nconst LAZY_DATA_PROPS = Symbol.for(\"@ai-matrx/design-system/data-table/lazy-data-props\");\n\n/**\n * Declares the props of `component` that take a GETTER of data the component reads only later —\n * a row menu's `config={() => menu}`, resolved when the menu opens. Without it a getter is just an\n * unknown function, compared by identity, and a host that builds it inline (every row menu does)\n * redraws every row on every render of its own: /crm redrew all 25 rows on each checkbox tick.\n *\n * A declared prop is compared by what the getter RETURNS (its handlers by trampoline, everything\n * else exactly as a drawing is): the same data → the row keeps its drawing and the component is\n * handed a stable trampoline to the host's LATEST getter, so it opens with fresh items; different\n * data (a checkbox in an open menu flipped) → that row alone redraws. Declare once, on the\n * component — never per call site.\n *\n * The contract: a declared getter is PURE and CHEAP — the row calls the previous and the new one on\n * every host render to compare them. A getter that fetches, dispatches or builds something heavy\n * must not be declared.\n */\nexport function declareLazyDataProps<C extends object>(component: C, keys: readonly string[]): C {\n (component as C & { [LAZY_DATA_PROPS]?: ReadonlySet<string> })[LAZY_DATA_PROPS] = new Set(keys);\n return component;\n}\n\nfunction lazyDataKeys(type: unknown): ReadonlySet<string> | undefined {\n if ((typeof type !== \"function\" && typeof type !== \"object\") || type === null) return undefined;\n return (type as { [LAZY_DATA_PROPS]?: ReadonlySet<string> })[LAZY_DATA_PROPS];\n}\n\nconst UNRESOLVED = Symbol(\"unresolved\");\n\n/** A declared getter's data — or `UNRESOLVED` when calling it threw (the row then simply redraws). */\nfunction resolveLazyData(value: unknown): unknown {\n if (typeof value !== \"function\") return value;\n try {\n return (value as () => unknown)();\n } catch {\n return UNRESOLVED;\n }\n}\n\nfunction trampolinable(key: string, fn: unknown): boolean {\n return isLazy(fn) || HANDLER_KEY.test(key);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\ninterface Slot {\n fn: (...args: unknown[]) => unknown;\n readonly stable: (...args: unknown[]) => unknown;\n}\n\nfunction makeSlot(fn: (...args: unknown[]) => unknown): Slot {\n const slot: Slot = {\n fn,\n // A Proxy rather than a bare wrapper: a handler that carries its own members (a debounced\n // function's `cancel`) keeps them, read from the latest function.\n stable: new Proxy((() => undefined) as (...args: unknown[]) => unknown, {\n apply: (_target, thisArg, args: unknown[]) => Reflect.apply(slot.fn, thisArg, args),\n get: (_target, key) => Reflect.get(slot.fn, key),\n }),\n };\n return slot;\n}\n\ntype Updates = Array<[path: string, fn: (...args: unknown[]) => unknown]>;\n\nfunction sameEntries(\n a: Record<string, unknown>,\n b: Record<string, unknown>,\n path: string,\n depth: number,\n updates: Updates,\n lazyKeys?: ReadonlySet<string>,\n): boolean {\n const keys = Object.keys(a);\n if (keys.length !== Object.keys(b).length) return false;\n for (const key of keys) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false;\n // A ref is never a handler: a new callback ref must be attached by React.\n if (key === \"ref\") {\n if (!Object.is(a[key], b[key])) return false;\n continue;\n }\n if (lazyKeys?.has(key) && (typeof a[key] === \"function\" || typeof b[key] === \"function\")) {\n if (Object.is(a[key], b[key])) continue;\n // A getter that became a plain value (or the reverse) is a new drawing: the committed element\n // still holds a trampoline to the OLD getter, which nothing would re-point.\n if (typeof a[key] !== typeof b[key]) return false;\n const before = resolveLazyData(a[key]);\n const after = resolveLazyData(b[key]);\n if (before === UNRESOLVED || after === UNRESOLVED) return false;\n // Handlers inside the data are reached through the getter's own trampoline, so the ones this\n // comparison collects are dropped: the component calls the LATEST getter and gets them fresh.\n if (!sameDrawing(before, after, key, `${path}/${key}()`, depth + 1, [])) return false;\n if (typeof b[key] === \"function\") updates.push([`${path}/${key}`, b[key] as (...args: unknown[]) => unknown]);\n continue;\n }\n if (!sameDrawing(a[key], b[key], key, `${path}/${key}`, depth + 1, updates)) return false;\n }\n return true;\n}\n\n/**\n * Whether `next` draws exactly what `previous` drew. Handlers that differ only by identity are\n * collected into `updates` (by path) so the caller can point the committed trampolines at them.\n */\nfunction sameDrawing(previous: unknown, next: unknown, key: string, path: string, depth: number, updates: Updates): boolean {\n if (Object.is(previous, next)) return true;\n if (typeof previous === \"function\" && typeof next === \"function\") {\n if (trampolinable(key, previous) && trampolinable(key, next)) {\n updates.push([path, next as (...args: unknown[]) => unknown]);\n return true;\n }\n return false;\n }\n if (depth > MAX_DEPTH) return false;\n if (isValidElement(previous) && isValidElement(next)) {\n if (previous.type !== next.type || previous.key !== next.key) return false;\n return sameEntries(\n previous.props as Record<string, unknown>,\n next.props as Record<string, unknown>,\n path,\n depth,\n updates,\n lazyDataKeys(previous.type),\n );\n }\n if (Array.isArray(previous) && Array.isArray(next)) {\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!sameDrawing(previous[i], next[i], key, `${path}/${i}`, depth + 1, updates)) return false;\n }\n return true;\n }\n if (isPlainObject(previous) && isPlainObject(next)) return sameEntries(previous, next, path, depth, updates);\n return false;\n}\n\n/** `value` with every trampolinable function replaced by its stable trampoline. */\nfunction stabilize(value: unknown, key: string, path: string, depth: number, slots: Map<string, Slot>, reuse: Map<string, Slot> | undefined, lazyData = false): unknown {\n if (typeof value === \"function\") {\n if (!lazyData && !trampolinable(key, value)) return value;\n const fn = value as (...args: unknown[]) => unknown;\n const kept = reuse?.get(path);\n const slot = kept ?? makeSlot(fn);\n slot.fn = fn;\n slots.set(path, slot);\n return slot.stable;\n }\n if (depth > MAX_DEPTH) return value;\n if (isValidElement(value)) {\n const props = value.props as Record<string, unknown>;\n const lazyKeys = lazyDataKeys(value.type);\n let changed: Record<string, unknown> | null = null;\n for (const propKey of Object.keys(props)) {\n if (propKey === \"ref\") continue;\n const before = props[propKey];\n const after = stabilize(before, propKey, `${path}/${propKey}`, depth + 1, slots, reuse, lazyKeys?.has(propKey) ?? false);\n if (after !== before) (changed ??= {})[propKey] = after;\n }\n if (!changed) return value;\n const clone = cloneElement(value as ReactElement, changed);\n // React (dev) marks JSX's static children as key-checked on the ELEMENT; a clone starts\n // unmarked, and a clone inside a static children array would then warn about a missing key.\n const before = (value as { _store?: { validated?: unknown } })._store;\n const after = (clone as { _store?: { validated?: unknown } })._store;\n if (before && after && \"validated\" in before) {\n try { after.validated = before.validated; } catch { /* frozen in some builds: only a dev warning is at stake */ }\n }\n return clone;\n }\n if (Array.isArray(value)) {\n let out: unknown[] | null = null;\n for (let i = 0; i < value.length; i += 1) {\n const after = stabilize(value[i], key, `${path}/${i}`, depth + 1, slots, reuse);\n if (after !== value[i]) (out ??= value.slice())[i] = after;\n }\n return out ?? value;\n }\n if (isPlainObject(value)) {\n let out: Record<string, unknown> | null = null;\n for (const entryKey of Object.keys(value)) {\n const before = value[entryKey];\n const after = stabilize(before, entryKey, `${path}/${entryKey}`, depth + 1, slots, reuse);\n if (after !== before) (out ??= { ...value })[entryKey] = after;\n }\n return out ?? value;\n }\n return value;\n}\n\ninterface RowDrawing {\n /** What the host and the table built last time — compared, never rendered. */\n raw: unknown;\n /** What React was handed: `raw` with trampolines. Returned unchanged while the drawing holds. */\n out: ReactNode;\n slots: Map<string, Slot>;\n}\n\n/** One table's drawings, by row id. */\nexport class RowDrawings {\n private readonly rows = new Map<string, RowDrawing>();\n\n /**\n * The element to hand React for this row. The SAME object as last time when `raw` draws what the\n * last one drew (its handlers are re-pointed); a new one otherwise.\n */\n draw(rowId: string, raw: ReactNode, fresh = false): ReactNode {\n const previous = this.rows.get(rowId);\n // `fresh`: the row's own facts moved — what it shows may differ even when its elements match.\n if (previous && !fresh) {\n const updates: Updates = [];\n if (sameDrawing(previous.raw, raw, \"\", \"\", 0, updates) && updates.every(([path]) => previous.slots.has(path))) {\n for (const [path, fn] of updates) previous.slots.get(path)!.fn = fn;\n previous.raw = raw;\n return previous.out;\n }\n }\n const slots = new Map<string, Slot>();\n const out = stabilize(raw, \"\", \"\", 0, slots, previous?.slots) as ReactNode;\n this.rows.set(rowId, { raw, out, slots });\n return out;\n }\n\n /** Forget rows that are no longer in the table (bounded by what was ever drawn). */\n prune(liveIds: ReadonlySet<string>): void {\n for (const id of this.rows.keys()) if (!liveIds.has(id)) this.rows.delete(id);\n }\n\n get size(): number {\n return this.rows.size;\n }\n}\n","\"use client\";\nimport { createContext, useContext, type Context, type ComponentType, type ReactNode, type MouseEventHandler } from \"react\";\nimport type { CopyControlProps } from \"../data-table/copy-types\";\nimport type { MatrxColumnDef, MatrxDataTableDensity, MatrxDataTableLook, MatrxDataTableRecordControls, TableSavedViewsProps } from \"../data-table/types\";\nimport type {\n MatrxTableMenuIconName,\n MatrxTableMenuItem,\n MatrxTableMenuPayload,\n MatrxTableMenuSectionsResolver,\n MatrxTableMenuTarget,\n} from \"../data-table/menu-targets\";\n\nexport type {\n MatrxTableMenuIconName,\n MatrxTableMenuItem,\n MatrxTableMenuSection,\n MatrxTableMenuSectionsResolver,\n} from \"../data-table/menu-targets\";\n\nexport type {\n AgentPayloadInput,\n AiCustomSource,\n AiVariant,\n ContentTransferReference,\n CopyControlProps,\n CopyEnvelopeMeta,\n CopyExportConfig,\n TableCopyController,\n} from \"../data-table/copy-types\";\n\nexport interface TableLinkProps { href: string; children: ReactNode; className?: string; onClick?: MouseEventHandler<HTMLAnchorElement>; }\n/**\n * Host-owned toolbar affordance. The table package keeps operation state and\n * labels; an application can supply its canonical icon-button primitive\n * without making the shared package depend on an application UI package.\n */\nexport interface TableToolbarActionProps { ariaLabel: string; tooltip?: string | undefined; disabled?: boolean | undefined; busy?: boolean | undefined; /** Indicates a selected mutually-exclusive action such as the current page. */ active?: boolean | undefined; onClick: () => void; children: ReactNode; }\nexport interface TableSidePanelProps { title: string; titleNode?: ReactNode; description?: ReactNode; onClose: () => void; /** Increments on every open request, including a repeat open of the row already shown — a host whose panel can sit in the background (a tab) brings it forward when this changes. */ openRequest?: number; defaultWidth?: number; headerActions?: ReactNode; children: ReactNode; }\nexport interface TableWindowPanelProps { id: string; title: string; onClose: () => void; width?: number; height?: number; minWidth?: number; minHeight?: number; actionsRight?: ReactNode; bodyClassName?: string; sidebarDefaultSize?: number; sidebarMinSize?: number; sidebar?: ReactNode; children: ReactNode; /** The table look the window opens from (lane mac-blockers): `\"mac\"` asks the host window for the Mac header. Absent: the default look. */ look?: MatrxDataTableLook; }\nexport interface TableEntityRefProps { token: string; id: string; href?: string; children: ReactNode; className?: string; /** Opening it starts a new tab. */ openInNewTab?: boolean; /** Its open / peek buttons stay shown, not only on hover. */ alwaysShowActions?: boolean; }\nexport interface TablePeekProps { kind: string; id: string; href?: string; onClose: () => void; }\nexport interface TableDoors {\n href?: string;\n peekKind?: string;\n canPeek?: boolean;\n /**\n * THE RECORD'S OWN MENU (lane DRILL-PARITY-LAST): what its kind offers wherever a table names it —\n * a person's admin destinations, an organization's pages — as neutral items the table draws in a\n * row's menu and on a right-click. Absent = the record has only its open and preview doors.\n */\n menu?: MatrxTableMenuItem[];\n}\n/**\n * The host's door onto the table's right-click levels.\n *\n * A canonical table registers ONE resolver per instance. The host's own menu\n * reads which of the four levels was clicked (`readMenuTarget`) and asks for\n * that level's descriptor — the package never interprets what comes back, and\n * the host never reaches into the table for the row or the range.\n */\nexport interface TableRowContextRegistry { register(tableId: string, resolve: (target: MatrxTableMenuTarget) => unknown): () => void; }\nexport interface TableContextMenuBoundaryProps {\n label: string;\n children: ReactNode;\n /**\n * The consumer's own sections, asked per right-click. Present only when the\n * table's consumer declared `contextMenu.sections`; the host reads the level\n * off the clicked element (`readMenuTarget`), calls this, and draws what comes\n * back in its own menu. Absent, the boundary behaves exactly as before.\n */\n sections?: MatrxTableMenuSectionsResolver | undefined;\n /** The table asked for the host's designed menu (`MatrxDataTableContextMenuConfig.designedMenu`). */\n designedMenu?: boolean | undefined;\n}\n/**\n * The host's view of a failed read with NO rows to show (RC-B12 round 13):\n * its error display (with the host's actions — matrx-frontend's Alchemy Menu)\n * and a retry. Without a host port the table draws a package `ErrorBox`.\n */\nexport interface TableReadFailureProps { error: unknown; what: string; onRetry?: (() => void) | undefined; }\n/**\n * The host's notice above rows kept from an earlier read when the latest read\n * failed: the rows may be out of date. Without a host port the table draws a\n * package `ErrorBox` in warning colors.\n */\nexport interface TableStaleNoticeProps { error: unknown; what: string; onRetry?: (() => void) | undefined; }\n/** The host's glyph for a menu icon name. A host without one draws labels only. */\nexport interface TableMenuIconProps { name: MatrxTableMenuIconName; className?: string | undefined; }\n/** The host's custom-field columns for a registry token over the given organizations (lane 7 W5). */\nexport interface TableCustomFieldColumns<T> { columns: MatrxColumnDef<T>[]; }\nexport interface MatrxDataTableHost { /** A HOOK, bound once by the host: the custom-field columns of `token` (null = none) for `organizationIds`. */ useCustomFieldColumns?: <T>(token: string | null, organizationIds: readonly string[]) => TableCustomFieldColumns<T> | null; JsonViewer?: ComponentType<{ data: object | string }>; Link?: ComponentType<TableLinkProps>; CopyControls?: ComponentType<CopyControlProps>; ToolbarAction?: ComponentType<TableToolbarActionProps>; SidePanelSurface?: ComponentType<TableSidePanelProps>; WindowPanel?: ComponentType<TableWindowPanelProps>; EntityRef?: ComponentType<TableEntityRefProps>; resolveEntityDoors?: (token: string, id: string, href?: string | null) => TableDoors | null; ResourcePeek?: ComponentType<TablePeekProps>; SavedViews?: ComponentType<TableSavedViewsProps>; rowContextRegistry?: TableRowContextRegistry; createDefaultMenuContext?: (payload: MatrxTableMenuPayload) => unknown; ContextMenuBoundary?: ComponentType<TableContextMenuBoundaryProps>; MenuIcon?: ComponentType<TableMenuIconProps>; /** A failed read with no rows (RC-B12 r13). */ ReadFailure?: ComponentType<TableReadFailureProps>; /** A failed refresh over kept rows (RC-B12 r13). */ StaleNotice?: ComponentType<TableStaleNoticeProps>; /** Host default; a table's explicit density takes precedence. */ defaultDensity?: MatrxDataTableDensity; /** Host default look (`default` | `mac`); a table's explicit `look` takes precedence. One switch turns every table under the host, and every mode built on it, to the Mac look. */ defaultLook?: MatrxDataTableLook; notify?: { success(message: string, options?: { description?: string }): void; error(message: string, options?: { description?: string }): void }; /** Client-side navigation (the app router), for `rowClick=\"page\"`. Absent: a full page load. */ navigate?: (href: string) => void; }\n// Public entrypoints are independently bundled in ESM and CJS. The context\n// identity must survive those bundle boundaries; values stay provider-scoped.\nconst HOST_SLOT = Symbol.for(\"ai-matrx.design-system.table-host-context\");\nconst hostRegistry = globalThis as typeof globalThis & { [HOST_SLOT]?: Context<MatrxDataTableHost> };\nconst HostContext = hostRegistry[HOST_SLOT] ??= createContext<MatrxDataTableHost>({});\nexport function MatrxDataTableProvider({ value, children }: { value: MatrxDataTableHost; children: ReactNode }) { return <HostContext.Provider value={value}>{children}</HostContext.Provider>; }\nexport function useMatrxDataTableHost(): MatrxDataTableHost { return useContext(HostContext); }\n\nexport interface MatrxTableRowAlchemy {\n copy: CopyControlProps;\n controls?: MatrxDataTableRecordControls;\n placement: \"inline\" | \"menu\";\n ready: boolean;\n activate(): void;\n /** Mount Alchemy's canonical palette in the host row menu. */\n mountMenu(target: HTMLElement | null, onClose?: () => void, restoreFocus?: () => void): void;\n prepare(restoreFocus?: () => void): Promise<void>;\n}\n\nconst ROW_ALCHEMY_SLOT = Symbol.for(\"ai-matrx.design-system.table-row-alchemy-context\");\nconst rowAlchemyRegistry = globalThis as typeof globalThis & { [ROW_ALCHEMY_SLOT]?: Context<MatrxTableRowAlchemy | null> };\nconst RowAlchemyContext = rowAlchemyRegistry[ROW_ALCHEMY_SLOT] ??= createContext<MatrxTableRowAlchemy | null>(null);\n\nexport function MatrxTableRowAlchemyScope({ value, children }: { value: MatrxTableRowAlchemy; children: ReactNode }) { return <RowAlchemyContext.Provider value={value}>{children}</RowAlchemyContext.Provider>; }\n\nexport function useMatrxTableRowAlchemy(): MatrxTableRowAlchemy | null { return useContext(RowAlchemyContext); }\n\nconst ROW_CONTROLS_SLOT = Symbol.for(\"ai-matrx.design-system.table-row-controls-context\");\nconst rowControlsRegistry = globalThis as typeof globalThis & { [ROW_CONTROLS_SLOT]?: Context<MatrxDataTableRecordControls | null> };\nconst RowControlsContext = rowControlsRegistry[ROW_CONTROLS_SLOT] ??= createContext<MatrxDataTableRecordControls | null>(null);\nexport function MatrxTableRowControlsScope({ value, children }: { value: MatrxDataTableRecordControls; children: ReactNode }) { return <RowControlsContext.Provider value={value}>{children}</RowControlsContext.Provider>; }\nexport function useMatrxTableRowControls(): MatrxDataTableRecordControls | null { return useContext(RowControlsContext); }\n\n/** Light door for components a row draws (a row menu): declare a lazy data prop without loading the table. */\nexport { declareLazyDataProps } from \"./row-drawing\";\n"],"mappings":"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","names":["before","after"]}
@@ -13,6 +13,7 @@ var NOTICE_STACK = {
13
13
  /** The card's width: small, never the window's. */
14
14
  widthRem: 22
15
15
  };
16
+ var NOTICE_TEXT_BUDGET = { titleChars: 24, lineChars: 40 };
16
17
  function noticeKey(tone, title, message) {
17
18
  const text = (v) => typeof v === "string" || typeof v === "number" ? String(v).trim() : "";
18
19
  return `${tone}\0${text(title)}\0${text(message)}`;
@@ -43,9 +44,10 @@ function noticeLifetimeMs(tone) {
43
44
 
44
45
  export {
45
46
  NOTICE_STACK,
47
+ NOTICE_TEXT_BUDGET,
46
48
  noticeKey,
47
49
  collapseNotices,
48
50
  visibleNotices,
49
51
  noticeLifetimeMs
50
52
  };
51
- //# sourceMappingURL=chunk-4WSXAP24.js.map
53
+ //# sourceMappingURL=chunk-BB5LA7FQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/notice-stack.ts"],"sourcesContent":["/**\n * @ai-matrx/design-system/notice-stack — THE ONE NOTICE LOOK AND STACK RULE, for the web and Matrx 2.\n *\n * Arman, 2026-10-23, looking at two full-width red bars across Matrx 2 Cloud Chat: \"Let's eliminate\n * this terrible pattern of having giant errors that shit the entire ui. If we're going to have a\n * place for errors, it should be a small something that resembles errors from a normal macos\".\n *\n * Champion: the macOS notification banner. A small rounded card of translucent material in the\n * window's TOP-RIGHT corner (where macOS puts every notification, so the eye already looks there;\n * the bottom of our windows holds the composer and the list's foot, which a notice must not sit on),\n * the tone's icon, a one-line title, an optional second line, at most one action, and ×.\n *\n * The stack rule, the same on both hosts:\n * - identical notices (same tone, title and line) collapse into ONE card with a count;\n * - at most 3 cards show; the rest fold into \"N more\";\n * - info and success go after NOTICE_STACK.infoMs; errors and warnings stay until dismissed\n * or until NOTICE_STACK.errorMs (long, so a notice nobody saw does not sit there for ever).\n *\n * The desktop renders it through `Notices` + `Banner` (desktop/layout.tsx, desktop/state.tsx); the\n * web through its one sonner Toaster (matrx-frontend components/ui/sonner.tsx), which reads these\n * constants and keys every toast with `noticeKey` so the same notice never stacks twice.\n */\n\nexport const NOTICE_STACK = {\n /** The corner every notice floats in. */\n corner: \"top-right\",\n /** Cards shown at once; the rest fold into \"N more\". */\n maxVisible: 3,\n /** Info and success: gone after this long. */\n infoMs: 4000,\n /** Errors and warnings: stay until dismissed, or this long at most. */\n errorMs: 30000,\n /** The card's width: small, never the window's. */\n widthRem: 22,\n} as const;\n\n/**\n * How much a card can SAY with nothing cut by \"…\" at the smallest window (900 × 560, Matrx 2 Cloud\n * Chat: the card is 324 px wide over the viewer, and an action such as Retry shares the row, leaving\n * ~180 px of text): the title is one line, the line under it at most two short ones. Measured\n * 2026-10-24 (\"Your change was not saved …\" / \"Check the connection and tr…\"). In-app text is layout.\n */\nexport const NOTICE_TEXT_BUDGET = { titleChars: 24, lineChars: 40 } as const;\n\nexport type NoticeStackTone = \"neutral\" | \"info\" | \"warning\" | \"danger\" | \"success\";\n\n/** The identity two notices share when they say the same thing (tone, title, line). */\nexport function noticeKey(tone: string, title: unknown, message?: unknown): string {\n const text = (v: unknown): string => (typeof v === \"string\" || typeof v === \"number\" ? String(v).trim() : \"\");\n return `${tone}\\u0000${text(title)}\\u0000${text(message)}`;\n}\n\nexport interface CollapsedNotice<T> {\n /** The latest notice of the group (its dismiss and action are the live ones). */\n readonly item: T;\n /** Every notice the group holds, oldest first. */\n readonly members: readonly T[];\n /** How many identical notices it stands for. */\n readonly count: number;\n}\n\n/**\n * Identical notices become one with a count, in the order each first appeared. A notice whose key\n * is null (a rich one that says nothing comparable) stands alone.\n */\nexport function collapseNotices<T>(items: readonly T[], keyOf: (item: T) => string | null): CollapsedNotice<T>[] {\n const groups: { key: string | null; members: T[] }[] = [];\n const byKey = new Map<string, { key: string | null; members: T[] }>();\n for (const item of items) {\n const key = keyOf(item);\n const found = key !== null ? byKey.get(key) : undefined;\n if (found) {\n found.members.push(item);\n continue;\n }\n const group = { key, members: [item] };\n groups.push(group);\n if (key !== null) byKey.set(key, group);\n }\n return groups.map((g) => ({ item: g.members[g.members.length - 1] as T, members: g.members, count: g.members.length }));\n}\n\n/** The cards on screen and how many fold away (the newest show). */\nexport function visibleNotices<T>(groups: readonly T[], max: number = NOTICE_STACK.maxVisible): { shown: T[]; folded: number } {\n if (groups.length <= max) return { shown: [...groups], folded: 0 };\n return { shown: groups.slice(groups.length - max), folded: groups.length - max };\n}\n\n/** How long a notice of this tone stays (ms; Infinity never happens: errors cap at errorMs). */\nexport function noticeLifetimeMs(tone: NoticeStackTone | \"error\"): number {\n return tone === \"danger\" || tone === \"warning\" || tone === \"error\" ? NOTICE_STACK.errorMs : NOTICE_STACK.infoMs;\n}\n"],"mappings":";;;AAuBO,IAAM,eAAe;AAAA;AAAA,EAE1B,QAAQ;AAAA;AAAA,EAER,YAAY;AAAA;AAAA,EAEZ,QAAQ;AAAA;AAAA,EAER,SAAS;AAAA;AAAA,EAET,UAAU;AACZ;AAQO,IAAM,qBAAqB,EAAE,YAAY,IAAI,WAAW,GAAG;AAK3D,SAAS,UAAU,MAAc,OAAgB,SAA2B;AACjF,QAAM,OAAO,CAAC,MAAwB,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW,OAAO,CAAC,EAAE,KAAK,IAAI;AAC1G,SAAO,GAAG,IAAI,KAAS,KAAK,KAAK,CAAC,KAAS,KAAK,OAAO,CAAC;AAC1D;AAeO,SAAS,gBAAmB,OAAqB,OAAyD;AAC/G,QAAM,SAAiD,CAAC;AACxD,QAAM,QAAQ,oBAAI,IAAkD;AACpE,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,MAAM,IAAI;AACtB,UAAM,QAAQ,QAAQ,OAAO,MAAM,IAAI,GAAG,IAAI;AAC9C,QAAI,OAAO;AACT,YAAM,QAAQ,KAAK,IAAI;AACvB;AAAA,IACF;AACA,UAAM,QAAQ,EAAE,KAAK,SAAS,CAAC,IAAI,EAAE;AACrC,WAAO,KAAK,KAAK;AACjB,QAAI,QAAQ,KAAM,OAAM,IAAI,KAAK,KAAK;AAAA,EACxC;AACA,SAAO,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,SAAS,CAAC,GAAQ,SAAS,EAAE,SAAS,OAAO,EAAE,QAAQ,OAAO,EAAE;AACxH;AAGO,SAAS,eAAkB,QAAsB,MAAc,aAAa,YAA4C;AAC7H,MAAI,OAAO,UAAU,IAAK,QAAO,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,QAAQ,EAAE;AACjE,SAAO,EAAE,OAAO,OAAO,MAAM,OAAO,SAAS,GAAG,GAAG,QAAQ,OAAO,SAAS,IAAI;AACjF;AAGO,SAAS,iBAAiB,MAAyC;AACxE,SAAO,SAAS,YAAY,SAAS,aAAa,SAAS,UAAU,aAAa,UAAU,aAAa;AAC3G;","names":[]}