@ai-matrx/design-system 0.86.355 → 0.86.357

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 (88) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/auth-gate-dialog.js +2 -2
  3. package/dist/carousel.js +8 -7
  4. package/dist/carousel.js.map +1 -1
  5. package/dist/{chunk-UWTODOMD.js → chunk-2BQAHLU3.js} +4 -4
  6. package/dist/chunk-5Z5GTXKL.js +79 -0
  7. package/dist/chunk-5Z5GTXKL.js.map +1 -0
  8. package/dist/{chunk-XXZA2JUP.js → chunk-ACZALXD6.js} +4 -4
  9. package/dist/{chunk-ZRHVA3DL.js → chunk-AV5C4YLL.js} +2 -1
  10. package/dist/{chunk-ZRHVA3DL.js.map → chunk-AV5C4YLL.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-RLM4RSED.js → chunk-GUGBVQ4T.js} +6 -4
  14. package/dist/{chunk-RLM4RSED.js.map → chunk-GUGBVQ4T.js.map} +1 -1
  15. package/dist/{chunk-LJKF267O.js → chunk-HS6GMHKZ.js} +18 -76
  16. package/dist/chunk-HS6GMHKZ.js.map +1 -0
  17. package/dist/{chunk-ZMUPFGQO.js → chunk-TV2IDHPZ.js} +2 -2
  18. package/dist/{chunk-CFUXEGW2.js → chunk-XZGYKKCE.js} +2 -2
  19. package/dist/clipboard-fallback-dialog.js +2 -2
  20. package/dist/controls.cjs +16 -3
  21. package/dist/controls.cjs.map +1 -1
  22. package/dist/controls.js +6 -5
  23. package/dist/data-table/data-row-window.js +3 -3
  24. package/dist/data-table/drill-chart.cjs +15 -3
  25. package/dist/data-table/drill-chart.cjs.map +1 -1
  26. package/dist/data-table/drill-chart.js +2 -1
  27. package/dist/data-table/drill-chart.js.map +1 -1
  28. package/dist/data-table/index.cjs +15 -3
  29. package/dist/data-table/index.cjs.map +1 -1
  30. package/dist/data-table/index.js +8 -7
  31. package/dist/data-table/index.js.map +1 -1
  32. package/dist/desktop.cjs +26 -5
  33. package/dist/desktop.cjs.map +1 -1
  34. package/dist/desktop.js +15 -7
  35. package/dist/desktop.js.map +1 -1
  36. package/dist/email-input-dialog.cjs +15 -3
  37. package/dist/email-input-dialog.cjs.map +1 -1
  38. package/dist/email-input-dialog.js +4 -3
  39. package/dist/email-input-dialog.js.map +1 -1
  40. package/dist/error-sentence.cjs +130 -0
  41. package/dist/error-sentence.cjs.map +1 -0
  42. package/dist/error-sentence.d.cts +49 -0
  43. package/dist/error-sentence.d.ts +49 -0
  44. package/dist/error-sentence.js +22 -0
  45. package/dist/error-sentence.js.map +1 -0
  46. package/dist/index.cjs +17 -4
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +3 -1
  49. package/dist/index.d.ts +3 -1
  50. package/dist/index.js +18 -16
  51. package/dist/index.js.map +1 -1
  52. package/dist/item.js +2 -2
  53. package/dist/lite-table.cjs +15 -3
  54. package/dist/lite-table.cjs.map +1 -1
  55. package/dist/lite-table.js +3 -2
  56. package/dist/lite-table.js.map +1 -1
  57. package/dist/notice-stack.cjs +2 -0
  58. package/dist/notice-stack.cjs.map +1 -1
  59. package/dist/notice-stack.d.cts +11 -1
  60. package/dist/notice-stack.d.ts +11 -1
  61. package/dist/notice-stack.js +3 -1
  62. package/dist/record-list.cjs +15 -3
  63. package/dist/record-list.cjs.map +1 -1
  64. package/dist/record-list.js +3 -2
  65. package/dist/record-list.js.map +1 -1
  66. package/dist/sandbox-gate-dialog.js +2 -2
  67. package/dist/scope-mismatch-dialog.js +2 -2
  68. package/dist/settings.cjs +1 -0
  69. package/dist/settings.cjs.map +1 -1
  70. package/dist/settings.js +1 -1
  71. package/dist/swipe-deck.cjs +25 -4
  72. package/dist/swipe-deck.cjs.map +1 -1
  73. package/dist/swipe-deck.css +2 -0
  74. package/dist/swipe-deck.js +12 -2
  75. package/dist/swipe-deck.js.map +1 -1
  76. package/dist/text-input-dialog.cjs +15 -3
  77. package/dist/text-input-dialog.cjs.map +1 -1
  78. package/dist/text-input-dialog.js +4 -3
  79. package/dist/text-input-dialog.js.map +1 -1
  80. package/dist/value-prompts-dialog.js +2 -2
  81. package/dist/writing-field.js +2 -2
  82. package/package.json +12 -2
  83. package/dist/chunk-4WSXAP24.js.map +0 -1
  84. package/dist/chunk-LJKF267O.js.map +0 -1
  85. /package/dist/{chunk-UWTODOMD.js.map → chunk-2BQAHLU3.js.map} +0 -0
  86. /package/dist/{chunk-XXZA2JUP.js.map → chunk-ACZALXD6.js.map} +0 -0
  87. /package/dist/{chunk-ZMUPFGQO.js.map → chunk-TV2IDHPZ.js.map} +0 -0
  88. /package/dist/{chunk-CFUXEGW2.js.map → chunk-XZGYKKCE.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,10 +1,35 @@
1
1
  # Changelog
2
2
 
3
- ## 0.86.355
3
+ ## 0.86.357
4
4
 
5
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
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
7
 
8
+ ## 0.86.356
9
+
10
+ - 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.
11
+
12
+ - SwipeDeck: a full deck (`fill` / `fullscreen`) takes keyboard focus back when the card changes, so the arrows keep
13
+ answering after an answer left focus outside the deck (a menu portal, a host control). Never from a field being
14
+ typed in, an open dialog or menu. The deck root carries `tabIndex={-1}` (no ring). Consumer action: none.
15
+
16
+ ## 0.86.355
17
+
18
+ SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
19
+ so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
20
+ Diff: `git diff bd26bcae0e 83b6419fe3 -- apps/shared/design-system/src`
21
+
22
+ Changed shipped source files (9):
23
+ - `apps/shared/design-system/src/desktop/desktop.css`
24
+ - `apps/shared/design-system/src/desktop/split-view.tsx`
25
+ - `apps/shared/design-system/src/desktop/state.tsx`
26
+ - `apps/shared/design-system/src/error-box.tsx`
27
+ - `apps/shared/design-system/src/error-notice.tsx`
28
+ - `apps/shared/design-system/src/error-sentence.ts`
29
+ - `apps/shared/design-system/src/index.ts`
30
+ - `apps/shared/design-system/src/notice-material.css`
31
+ - `apps/shared/design-system/src/styles.css`
32
+
8
33
  ## 0.86.354
9
34
 
10
35
  SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
@@ -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,25 +1,26 @@
1
1
  "use client";
2
2
  import {
3
3
  IconButton
4
- } from "./chunk-XXZA2JUP.js";
4
+ } from "./chunk-ACZALXD6.js";
5
5
  import "./chunk-6NOLVA4E.js";
6
6
  import "./chunk-5IR3BZOS.js";
7
- import "./chunk-ZRHVA3DL.js";
7
+ import "./chunk-AV5C4YLL.js";
8
8
  import "./chunk-XERGQT2M.js";
9
9
  import "./chunk-WQ2QCMMA.js";
10
10
  import "./chunk-YRF22M5S.js";
11
11
  import "./chunk-GMNROZQJ.js";
12
12
  import "./chunk-K4XCB576.js";
13
- import "./chunk-LJKF267O.js";
13
+ import "./chunk-5Z5GTXKL.js";
14
+ import "./chunk-HS6GMHKZ.js";
14
15
  import "./chunk-DRSBLHOF.js";
15
16
  import "./chunk-TH6DDCWF.js";
16
- import "./chunk-UWTODOMD.js";
17
- import "./chunk-IMQ3RX35.js";
18
- import "./chunk-U24MOJWK.js";
19
- import "./chunk-NDTDBBMM.js";
17
+ import "./chunk-2BQAHLU3.js";
20
18
  import {
21
19
  holdDragCursor
22
20
  } from "./chunk-LEPD6TV7.js";
21
+ import "./chunk-IMQ3RX35.js";
22
+ import "./chunk-U24MOJWK.js";
23
+ import "./chunk-NDTDBBMM.js";
23
24
  import "./chunk-MTE6N7XU.js";
24
25
  import "./chunk-WFBPCMPF.js";
25
26
  import {
@@ -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
@@ -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"]}
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-5IR3BZOS.js";
10
10
  import {
11
11
  Slider
12
- } from "./chunk-ZRHVA3DL.js";
12
+ } from "./chunk-AV5C4YLL.js";
13
13
  import {
14
14
  Label
15
15
  } from "./chunk-XERGQT2M.js";
@@ -26,7 +26,7 @@ import {
26
26
  } from "./chunk-YRF22M5S.js";
27
27
  import {
28
28
  ErrorActions
29
- } from "./chunk-LJKF267O.js";
29
+ } from "./chunk-5Z5GTXKL.js";
30
30
  import {
31
31
  HostLink
32
32
  } from "./chunk-TH6DDCWF.js";
@@ -36,7 +36,7 @@ import {
36
36
  DialogDescription,
37
37
  DialogHeader,
38
38
  DialogTitle
39
- } from "./chunk-UWTODOMD.js";
39
+ } from "./chunk-2BQAHLU3.js";
40
40
  import {
41
41
  useIsMobile
42
42
  } from "./chunk-U24MOJWK.js";
@@ -4007,4 +4007,4 @@ export {
4007
4007
  TileCard,
4008
4008
  TileCardGrid
4009
4009
  };
4010
- //# sourceMappingURL=chunk-XXZA2JUP.js.map
4010
+ //# sourceMappingURL=chunk-ACZALXD6.js.map
@@ -57,6 +57,7 @@ var Slider = React.forwardRef(({ className, size = "md", ...props }, ref) => {
57
57
  {
58
58
  "data-slot": "slider-thumb",
59
59
  ...props["aria-label"] ? { "aria-label": props["aria-label"] } : {},
60
+ ...props["aria-valuetext"] !== void 0 ? { "aria-valuetext": props["aria-valuetext"] } : {},
60
61
  className: cn(
61
62
  "relative block cursor-grab rounded-full border border-primary/50 bg-background shadow transition-colors active:cursor-grabbing",
62
63
  "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
@@ -79,4 +80,4 @@ Slider.displayName = SliderPrimitive.Root.displayName;
79
80
  export {
80
81
  Slider
81
82
  };
82
- //# sourceMappingURL=chunk-ZRHVA3DL.js.map
83
+ //# sourceMappingURL=chunk-AV5C4YLL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slider.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Slider — the range control (46 host import sites).\n *\n * Ported from matrx-frontend `components/ui/slider.tsx`. Two things the host\n * copy could not do, both fixed here rather than in a fork:\n *\n * 1. A 16px THUMB IS NOT A TOUCH TARGET. The stock thumb is `h-4 w-4`, well\n * under the 44px Apple HIG minimum, so a slider that worked with a mouse\n * was a coin-flip with a thumb. The visible thumb keeps its size (`size`\n * controls it), and an invisible `::after` pseudo-target expands the hit\n * area to 44px on coarse pointers without changing the layout — the same\n * trick `ResizableHandle` uses for mouse tolerance.\n *\n * 2. DENSITY IS A PROP, NOT A FORK (C18). `size=\"sm|md|lg\"` picks the track\n * height and thumb diameter together, so a dense admin panel and a\n * touch-first settings screen use ONE component.\n *\n * Multi-thumb ranges work: a `Thumb` is rendered per value, which is what\n * Radix requires and what the host copy (a single hardcoded Thumb) got wrong\n * for any `value` array longer than one.\n */\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\n\nimport { cn } from \"./cn\";\n\nexport type SliderSize = \"sm\" | \"md\" | \"lg\";\n\nconst TRACK_CLASSES: Record<SliderSize, string> = {\n sm: \"h-1\",\n md: \"h-1.5\",\n lg: \"h-2.5\",\n};\n\nconst THUMB_CLASSES: Record<SliderSize, string> = {\n sm: \"h-3.5 w-3.5\",\n md: \"h-4 w-4\",\n lg: \"h-5 w-5\",\n};\n\nexport interface SliderProps\n extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {\n /** Track + thumb scale. Default `md`. */\n size?: SliderSize;\n}\n\nconst Slider = React.forwardRef<\n React.ComponentRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(({ className, size = \"md\", ...props }, ref) => {\n // One Thumb per value — controlled `value` wins, then `defaultValue`, then\n // the single-thumb default. Radix renders nothing sensible if the count is\n // wrong, which is how the host copy silently dropped a range's upper thumb.\n const thumbCount = Math.max(\n 1,\n (props.value ?? props.defaultValue ?? [0]).length,\n );\n\n return (\n <SliderPrimitive.Root\n ref={ref}\n data-slot=\"slider\"\n className={cn(\n \"relative flex w-full touch-none select-none items-center data-[disabled]:opacity-50\",\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(\n \"relative w-full grow overflow-hidden rounded-full bg-primary/20\",\n TRACK_CLASSES[size],\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\",\n )}\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"absolute h-full bg-primary data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n {Array.from({ length: thumbCount }, (_, index) => (\n <SliderPrimitive.Thumb\n key={index}\n data-slot=\"slider-thumb\"\n // The thumb is the focusable slider: it carries the control's name (a label on the root alone leaves it unnamed).\n {...(props[\"aria-label\"] ? { \"aria-label\": props[\"aria-label\"] } : {})}\n className={cn(\n \"relative block cursor-grab rounded-full border border-primary/50 bg-background shadow transition-colors active:cursor-grabbing\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n // Ruling 1 — invisible 44px target, coarse pointers only, so a\n // dense desktop row keeps its exact hit behavior.\n \"after:absolute after:left-1/2 after:top-1/2 after:-translate-x-1/2 after:-translate-y-1/2 after:content-['']\",\n \"pointer-coarse:after:h-11 pointer-coarse:after:w-11\",\n THUMB_CLASSES[size],\n )}\n />\n ))}\n </SliderPrimitive.Root>\n );\n});\nSlider.displayName = SliderPrimitive.Root.displayName;\n\nexport { Slider };\n"],"mappings":";;;;;;AAwBA,YAAY,qBAAqB;AACjC,YAAY,WAAW;AAqCnB,SAkBI,KAlBJ;AA/BJ,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAQA,IAAM,SAAe,iBAGnB,CAAC,EAAE,WAAW,OAAO,MAAM,GAAG,MAAM,GAAG,QAAQ;AAI/C,QAAM,aAAa,KAAK;AAAA,IACtB;AAAA,KACC,MAAM,SAAS,MAAM,gBAAgB,CAAC,CAAC,GAAG;AAAA,EAC7C;AAEA,SACE;AAAA,IAAiB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAiB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,cAAc,IAAI;AAAA,cAClB;AAAA,YACF;AAAA,YAEA;AAAA,cAAiB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,WAAW,GAAG,CAAC,GAAG,UACtC;AAAA,UAAiB;AAAA,UAAhB;AAAA,YAEC,aAAU;AAAA,YAET,GAAI,MAAM,YAAY,IAAI,EAAE,cAAc,MAAM,YAAY,EAAE,IAAI,CAAC;AAAA,YACpE,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA;AAAA;AAAA,cAGA;AAAA,cACA;AAAA,cACA,cAAc,IAAI;AAAA,YACpB;AAAA;AAAA,UAbK;AAAA,QAcP,CACD;AAAA;AAAA;AAAA,EACH;AAEJ,CAAC;AACD,OAAO,cAA8B,qBAAK;","names":[]}
1
+ {"version":3,"sources":["../src/slider.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Slider — the range control (46 host import sites).\n *\n * Ported from matrx-frontend `components/ui/slider.tsx`. Two things the host\n * copy could not do, both fixed here rather than in a fork:\n *\n * 1. A 16px THUMB IS NOT A TOUCH TARGET. The stock thumb is `h-4 w-4`, well\n * under the 44px Apple HIG minimum, so a slider that worked with a mouse\n * was a coin-flip with a thumb. The visible thumb keeps its size (`size`\n * controls it), and an invisible `::after` pseudo-target expands the hit\n * area to 44px on coarse pointers without changing the layout — the same\n * trick `ResizableHandle` uses for mouse tolerance.\n *\n * 2. DENSITY IS A PROP, NOT A FORK (C18). `size=\"sm|md|lg\"` picks the track\n * height and thumb diameter together, so a dense admin panel and a\n * touch-first settings screen use ONE component.\n *\n * Multi-thumb ranges work: a `Thumb` is rendered per value, which is what\n * Radix requires and what the host copy (a single hardcoded Thumb) got wrong\n * for any `value` array longer than one.\n */\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\n\nimport { cn } from \"./cn\";\n\nexport type SliderSize = \"sm\" | \"md\" | \"lg\";\n\nconst TRACK_CLASSES: Record<SliderSize, string> = {\n sm: \"h-1\",\n md: \"h-1.5\",\n lg: \"h-2.5\",\n};\n\nconst THUMB_CLASSES: Record<SliderSize, string> = {\n sm: \"h-3.5 w-3.5\",\n md: \"h-4 w-4\",\n lg: \"h-5 w-5\",\n};\n\nexport interface SliderProps\n extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {\n /** Track + thumb scale. Default `md`. */\n size?: SliderSize;\n}\n\nconst Slider = React.forwardRef<\n React.ComponentRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(({ className, size = \"md\", ...props }, ref) => {\n // One Thumb per value — controlled `value` wins, then `defaultValue`, then\n // the single-thumb default. Radix renders nothing sensible if the count is\n // wrong, which is how the host copy silently dropped a range's upper thumb.\n const thumbCount = Math.max(\n 1,\n (props.value ?? props.defaultValue ?? [0]).length,\n );\n\n return (\n <SliderPrimitive.Root\n ref={ref}\n data-slot=\"slider\"\n className={cn(\n \"relative flex w-full touch-none select-none items-center data-[disabled]:opacity-50\",\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(\n \"relative w-full grow overflow-hidden rounded-full bg-primary/20\",\n TRACK_CLASSES[size],\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\",\n )}\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"absolute h-full bg-primary data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n {Array.from({ length: thumbCount }, (_, index) => (\n <SliderPrimitive.Thumb\n key={index}\n data-slot=\"slider-thumb\"\n // The thumb is the focusable slider: it carries the control's name (a label on the root alone leaves it unnamed).\n {...(props[\"aria-label\"] ? { \"aria-label\": props[\"aria-label\"] } : {})}\n {...(props[\"aria-valuetext\"] !== undefined ? { \"aria-valuetext\": props[\"aria-valuetext\"] } : {})}\n className={cn(\n \"relative block cursor-grab rounded-full border border-primary/50 bg-background shadow transition-colors active:cursor-grabbing\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n // Ruling 1 — invisible 44px target, coarse pointers only, so a\n // dense desktop row keeps its exact hit behavior.\n \"after:absolute after:left-1/2 after:top-1/2 after:-translate-x-1/2 after:-translate-y-1/2 after:content-['']\",\n \"pointer-coarse:after:h-11 pointer-coarse:after:w-11\",\n THUMB_CLASSES[size],\n )}\n />\n ))}\n </SliderPrimitive.Root>\n );\n});\nSlider.displayName = SliderPrimitive.Root.displayName;\n\nexport { Slider };\n"],"mappings":";;;;;;AAwBA,YAAY,qBAAqB;AACjC,YAAY,WAAW;AAqCnB,SAkBI,KAlBJ;AA/BJ,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAQA,IAAM,SAAe,iBAGnB,CAAC,EAAE,WAAW,OAAO,MAAM,GAAG,MAAM,GAAG,QAAQ;AAI/C,QAAM,aAAa,KAAK;AAAA,IACtB;AAAA,KACC,MAAM,SAAS,MAAM,gBAAgB,CAAC,CAAC,GAAG;AAAA,EAC7C;AAEA,SACE;AAAA,IAAiB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAiB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,cAAc,IAAI;AAAA,cAClB;AAAA,YACF;AAAA,YAEA;AAAA,cAAiB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,WAAW,GAAG,CAAC,GAAG,UACtC;AAAA,UAAiB;AAAA,UAAhB;AAAA,YAEC,aAAU;AAAA,YAET,GAAI,MAAM,YAAY,IAAI,EAAE,cAAc,MAAM,YAAY,EAAE,IAAI,CAAC;AAAA,YACnE,GAAI,MAAM,gBAAgB,MAAM,SAAY,EAAE,kBAAkB,MAAM,gBAAgB,EAAE,IAAI,CAAC;AAAA,YAC9F,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA;AAAA;AAAA,cAGA;AAAA,cACA;AAAA,cACA,cAAc,IAAI;AAAA,YACpB;AAAA;AAAA,UAdK;AAAA,QAeP,CACD;AAAA;AAAA;AAAA,EACH;AAEJ,CAAC;AACD,OAAO,cAA8B,qBAAK;","names":[]}
@@ -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":[]}
@@ -1,10 +1,12 @@
1
1
  "use client";
2
2
  import {
3
3
  ErrorActions,
4
- isTimedOutError,
5
- peopleErrorSentence,
6
4
  useErrorCardMenus
7
- } from "./chunk-LJKF267O.js";
5
+ } from "./chunk-5Z5GTXKL.js";
6
+ import {
7
+ isTimedOutError,
8
+ peopleErrorSentence
9
+ } from "./chunk-HS6GMHKZ.js";
8
10
  import {
9
11
  Button
10
12
  } from "./chunk-7IOSA55D.js";
@@ -333,4 +335,4 @@ export {
333
335
  ReadEmpty,
334
336
  ReadStaleNotice
335
337
  };
336
- //# sourceMappingURL=chunk-RLM4RSED.js.map
338
+ //# sourceMappingURL=chunk-GUGBVQ4T.js.map