@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
@@ -13,10 +13,11 @@ import {
13
13
  } from "../chunk-N3LMBQYT.js";
14
14
  import {
15
15
  ErrorBox
16
- } from "../chunk-LJKF267O.js";
16
+ } from "../chunk-5Z5GTXKL.js";
17
17
  import {
18
18
  Skeleton
19
19
  } from "../chunk-DRSBLHOF.js";
20
+ import "../chunk-HS6GMHKZ.js";
20
21
  import "../chunk-IMOOQQOO.js";
21
22
  import {
22
23
  cn
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-table/DrillChart.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * THE DRILL CHART — `@ai-matrx/design-system/data-table/drill-chart` (lane DRILL-CHART,\n * DRILL-FINISH decision 5). ONE shared chart, fed by drill answers (`platform.drill_ask`):\n * stacked bars over time; the stack is the top N (`seriesLimit`, the host's knob\n * `drill.chart.top_n`, default 10) of the question's split Dimension ranked by the Measure over the\n * WHOLE window, everything else one Other series — every bar sums to its period's total and the\n * bars to the window total. No split = one series.\n *\n * What it asks is decided by the pure model in `drill.ts` (`drillChartQuestions` → round one,\n * `drillChartCellsQuestion` → round two, `drillChartData` → the bars); this file only draws.\n *\n * click a segment → drill: the value joins the trail, exactly as the answer table's drill\n * click a period → the window narrows to that period (`from..to`)\n * click Other → the period is broken out: the window narrows to it, where the answer table\n * lists every value one by one (said in the tooltip — never a dead click)\n * legend → buttons (keyboard reachable) that hide and show a series\n *\n * Its own entry, so `./data-table` never imports recharts — an OPTIONAL peer only a host that\n * draws this chart installs. Colors are the package's `--matrx-chart-*` tokens (light and dark).\n */\n\nimport { useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from \"recharts\";\n\nimport { formatCount } from \"@ai-matrx/kit/format\";\n\nimport { cn } from \"../cn\";\nimport { ErrorBox } from \"../error-box\";\nimport { Skeleton } from \"../skeleton\";\nimport {\n MATRX_DRILL_CHART_SERIES_LIMIT,\n dimensionRefLabel,\n drillChartData,\n drillChartDrill,\n drillChartNarrowToPeriod,\n drillChartQuestions,\n type MatrxDrillChartAnswers,\n type MatrxDrillChartData,\n type MatrxDrillChartPeriod,\n type MatrxDrillChartSeries,\n type MatrxDrillDimension,\n type MatrxDrillMeasure,\n type MatrxDrillQuestion,\n} from \"./drill\";\n\nexport {\n MATRX_DRILL_CHART_CEILING,\n MATRX_DRILL_CHART_OTHER,\n MATRX_DRILL_CHART_SERIES_LIMIT,\n drillChartCellsQuestion,\n drillChartData,\n drillChartDrill,\n drillChartNarrowToPeriod,\n drillChartQuestions,\n drillChartSeriesKey,\n type MatrxDrillChartAnswers,\n type MatrxDrillChartContext,\n type MatrxDrillChartData,\n type MatrxDrillChartPeriod,\n type MatrxDrillChartPlan,\n type MatrxDrillChartRequest,\n type MatrxDrillChartRequestKey,\n type MatrxDrillChartRow,\n type MatrxDrillChartSeries,\n} from \"./drill\";\n\nexport interface MatrxDrillChartProps {\n dimensions: readonly MatrxDrillDimension[];\n measures: readonly MatrxDrillMeasure[];\n /** The question the answer table answers; the chart asks its own questions from it. */\n question: MatrxDrillQuestion;\n onQuestionChange: (next: MatrxDrillQuestion) => void;\n /** The door's rows (with `kind`) per chart request key: `series`, `periods`, `cells`. */\n answers: MatrxDrillChartAnswers;\n /** Series before Other — the host feeds the knob `drill.chart.top_n`. Default 10. */\n seriesLimit?: number | undefined;\n /** The Measure stacked. Default: the question's first shown Measure. */\n measure?: string | null | undefined;\n /**\n * STACK THESE MEASURES instead (two or more that add up, e.g. input and output tokens): one series\n * each, no split; a segment click narrows to its period (lane DRILL-FLIP-FIXES L3).\n */\n stack?: readonly string[] | null | undefined;\n /** The time reference when the question does not group by time. */\n time?: string | null | undefined;\n /** The store's page ceiling, when the host knows it is not 1000. */\n ceiling?: number | undefined;\n /** Declared drill paths; a segment click follows them like the answer table. */\n paths?: readonly (readonly string[])[] | undefined;\n /** A read that failed, in words. */\n error?: string | null | undefined;\n emptyLabel?: string | undefined;\n /** Plot height in px (default 220). */\n height?: number | undefined;\n /** A fixed plot width in px; default fills its container. */\n width?: number | undefined;\n /** Words above the chart (a title, the freshness). */\n note?: ReactNode | undefined;\n className?: string | undefined;\n}\n\nconst NO_PATHS: readonly (readonly string[])[] = [];\nconst PLOT_MARGIN = { top: 4, right: 4, bottom: 0, left: 0 };\nconst Y_AXIS_WIDTH = 56;\n\nfunction colorOf(series: MatrxDrillChartSeries, index: number): string {\n if (series.color) return series.color;\n return series.other ? \"hsl(var(--matrx-chart-other))\" : `hsl(var(--matrx-chart-${(index % 10) + 1}))`;\n}\n\nfunction formatWith(measure: MatrxDrillMeasure | undefined, value: number | null | undefined): string {\n if (value === null || value === undefined) return \"not measured\";\n if (measure?.format) return measure.format(value);\n return value.toLocaleString(undefined, { maximumFractionDigits: 2 });\n}\n\n/** A short axis number: the Measure's own format when it has one, else 1.2k / 3.4M. */\n/**\n * A tick on the value axis: the measure's own words while they fit the axis (\"$12\", \"40 ms\"), else the\n * number in compact form (\"28M\") — the chart's title already names the unit (\"Cost (points) by day\").\n * A long formatted value (\"28,000,000 points\") was clipped to \",000,000 points\" (lane DRILL-ADOPT walk).\n */\nexport function drillChartAxisNumber(measure: MatrxDrillMeasure | undefined, value: number): string {\n // A fractional tick under a thousand keeps one decimal (0.5); everything else is kit's compact count.\n const compact =\n Math.abs(value) < 1000 && !Number.isInteger(value)\n ? String(Math.round(value * 10) / 10)\n : formatCount(value, { style: \"compact\" });\n if (!measure?.format) return compact;\n const said = measure.format(value);\n if (said.length <= 7) return said;\n // THE TICK SAYS THE NUMBER THE FORMAT SAID, NOT THE RAW VALUE. A Measure's format may convert its unit\n // (the usage explorer stores dollars and prints points at the points rate), so compacting the raw value\n // put \"450\" under \"Cost (points) by day\" for a 9,000,000-point day (localhost walk, 2026-09-30). The\n // number the format printed is compacted, keeping a leading currency sign (\"$1,234.56\" → \"$1.2k\").\n const printed = said.match(/^([^\\d-]*)(-?\\d[\\d,]*(?:\\.\\d+)?)/);\n if (!printed) return compact;\n const numeric = printed[2];\n if (!numeric) return compact;\n const n = Number(numeric.replace(/,/g, \"\"));\n if (!Number.isFinite(n)) return compact;\n const prefix = printed[1]?.trim() ?? \"\";\n const shown = Math.abs(n) < 1000 && !Number.isInteger(n) ? String(Math.round(n * 10) / 10) : formatCount(n, { style: \"compact\" });\n return prefix.length <= 1 ? `${prefix}${shown}` : shown;\n}\nconst axisNumber = drillChartAxisNumber;\n\nexport function MatrxDrillChart({\n dimensions,\n measures,\n question,\n onQuestionChange,\n answers,\n seriesLimit = MATRX_DRILL_CHART_SERIES_LIMIT,\n measure: measureKey,\n stack,\n time,\n ceiling,\n paths = NO_PATHS,\n error,\n emptyLabel = \"No value\",\n height = 220,\n width,\n note,\n className,\n}: MatrxDrillChartProps) {\n const plan = useMemo(\n () => drillChartQuestions(question, seriesLimit, { dimensions, measures, measure: measureKey, stack, time, ceiling }),\n [question, seriesLimit, dimensions, measures, measureKey, stack, time, ceiling],\n );\n const data = useMemo(() => drillChartData(plan, answers, dimensions, measures, emptyLabel), [plan, answers, dimensions, measures, emptyLabel]);\n const measure = measures.find((m) => m.key === plan.measure);\n const [hidden, setHidden] = useState<ReadonlySet<string>>(() => new Set());\n const [hover, setHover] = useState<string | null>(null);\n // A segment's click reaches the chart's own click too (it bubbles): the segment wins.\n const segmentClicked = useRef(false);\n const plotRef = useRef<HTMLDivElement | null>(null);\n\n const measureWords = plan.stack ? plan.stack.map((k) => measures.find((m) => m.key === k)?.label ?? k).join(\" and \") : measure?.label ?? plan.measure;\n const title = plan.time\n ? `${measureWords} by ${dimensionRefLabel(dimensions, plan.time).replace(/^.* by /, \"\")}${\n plan.split ? `, split by ${dimensionRefLabel(dimensions, plan.split)}` : \"\"\n }`\n : measureWords;\n\n const frame = (body: ReactNode) => (\n <section data-matrx-drill-chart aria-label={title} className={cn(\"flex min-w-0 flex-col gap-1.5 px-2 py-2\", className)}>\n {note ? <div data-matrx-drill-chart-note className=\"text-xs text-muted-foreground\">{note}</div> : null}\n {body}\n </section>\n );\n\n if (error) {\n return frame(<ErrorBox className=\"text-sm text-destructive-ink\" message={error} operation=\"Draw the chart\" />);\n }\n if (plan.refused) {\n return frame(\n <p data-matrx-drill-chart-refused className=\"text-xs text-muted-foreground\">\n {plan.refused}\n </p>,\n );\n }\n if (!data) {\n return frame(<Skeleton data-matrx-drill-chart-loading className=\"w-full\" style={{ height }} />);\n }\n if (data.periods.length === 0) {\n return frame(<p data-matrx-drill-chart-empty className=\"text-xs text-muted-foreground\">Nothing was counted in this window.</p>);\n }\n\n const toggle = (key: string) =>\n setHidden((was) => {\n const next = new Set(was);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n const drawn = data.series.map((s, i) => ({ s, i, dataKey: `s${i}` })).filter(({ s }) => !hidden.has(s.key));\n const rows = data.periods.map((p) => {\n const row: Record<string, string | number | null> = { __key: p.key, __label: p.label };\n data.series.forEach((s, i) => {\n row[`s${i}`] = p.values[s.key] ?? null;\n });\n return row;\n });\n const splitLabel = data.split ? dimensionRefLabel(dimensions, data.split) : \"\";\n const onPeriod = (period: MatrxDrillChartPeriod | undefined) => {\n if (period) onQuestionChange(drillChartNarrowToPeriod(question, plan, period.value, dimensions));\n };\n const onSegment = (series: MatrxDrillChartSeries, period: MatrxDrillChartPeriod | undefined) => {\n segmentClicked.current = true;\n if (series.other || !data.split) {\n onPeriod(period);\n return;\n }\n onQuestionChange(drillChartDrill(question, plan, series.value, dimensions, paths));\n };\n const hintFor = (key: string | null): string | null => {\n const series = data.series.find((s) => s.key === key);\n if (!series) return \"Click a bar to narrow the window to its period.\";\n if (series.other) return `Other is everything past the top ${plan.seriesLimit}: click to break this period out, one by one, in the table.`;\n if (!data.split) return \"Click to narrow the window to this period.\";\n return `Click to drill into ${series.label}.`;\n };\n\n const plot = (\n <BarChart\n data={rows}\n margin={PLOT_MARGIN}\n barCategoryGap=\"12%\"\n onClick={(_state, event) => {\n if (segmentClicked.current) {\n segmentClicked.current = false;\n return;\n }\n // The period is read from WHERE the person clicked, never from the tooltip's last index:\n // a click that arrives before the chart has seen the pointer move would otherwise read a\n // stale index (walked: a quick click landed on the first day). Bands are equal-width over\n // the plot, which starts after the value axis and ends at the right margin.\n const box = (plotRef.current?.querySelector(\"svg.recharts-surface\") ?? plotRef.current)?.getBoundingClientRect();\n if (!box || data.periods.length === 0) return;\n const left = box.left + PLOT_MARGIN.left + Y_AXIS_WIDTH;\n const right = box.right - PLOT_MARGIN.right;\n const index = Math.floor(((event.clientX - left) / (right - left)) * data.periods.length);\n if (index >= 0 && index < data.periods.length) onPeriod(data.periods[index]);\n }}\n {...(width ? { width, height } : {})}\n >\n <CartesianGrid vertical={false} stroke=\"hsl(var(--border))\" strokeDasharray=\"0\" />\n <XAxis\n dataKey=\"__label\"\n tickLine={false}\n axisLine={{ stroke: \"hsl(var(--border))\" }}\n tick={{ fill: \"hsl(var(--muted-foreground))\", fontSize: 11 }}\n minTickGap={12}\n interval=\"preserveStartEnd\"\n />\n <YAxis\n tickLine={false}\n axisLine={false}\n width={Y_AXIS_WIDTH}\n tick={{ fill: \"hsl(var(--muted-foreground))\", fontSize: 11 }}\n tickFormatter={(v: number) => axisNumber(measure, v)}\n />\n <Tooltip\n cursor={{ fill: \"hsl(var(--muted))\", opacity: 0.6 }}\n isAnimationActive={false}\n content={({ active, label }) => {\n if (!active) return null;\n const period = data.periods.find((p) => p.label === label);\n if (!period) return null;\n return (\n <DrillChartTooltip\n period={period}\n data={data}\n hidden={hidden}\n measure={measure}\n splitLabel={splitLabel}\n hint={hintFor(hover)}\n />\n );\n }}\n />\n {drawn.map(({ s, i, dataKey }) => (\n <Bar\n key={s.key}\n dataKey={dataKey}\n name={s.label}\n stackId=\"drill\"\n fill={colorOf(s, i)}\n stroke=\"hsl(var(--background))\"\n strokeWidth={1}\n isAnimationActive={false}\n cursor=\"pointer\"\n onMouseEnter={() => setHover(s.key)}\n onMouseLeave={() => setHover(null)}\n onClick={(_item, index) => onSegment(s, data.periods[index])}\n />\n ))}\n </BarChart>\n );\n\n return frame(\n <>\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5\">\n <h3 className=\"text-xs font-medium text-foreground\">{title}</h3>\n <span data-matrx-drill-chart-total className=\"text-xs text-muted-foreground\">\n Total {formatWith(measure, data.total)}\n </span>\n </div>\n <div ref={plotRef} data-matrx-drill-chart-plot className=\"w-full min-w-0\" style={{ height }}>\n {width ? plot : <ResponsiveContainer width=\"100%\" height=\"100%\">{plot}</ResponsiveContainer>}\n </div>\n {data.series.length > 1 || data.split ? (\n <ul data-matrx-drill-chart-legend aria-label=\"Series — press to hide or show\" className=\"flex flex-wrap gap-x-1 gap-y-0.5\">\n {data.series.map((s, i) => {\n const off = hidden.has(s.key);\n return (\n <li key={s.key} className=\"min-w-0\">\n <button\n type=\"button\"\n data-matrx-drill-chart-series={s.key}\n aria-pressed={!off}\n title={`${off ? \"Show\" : \"Hide\"} ${s.label} (${formatWith(measure, s.total)})`}\n onClick={() => toggle(s.key)}\n className={cn(\n \"inline-flex max-w-[16rem] items-center gap-1.5 rounded px-1.5 py-0.5 text-xs text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring\",\n off && \"text-muted-foreground line-through\",\n )}\n >\n <span aria-hidden=\"true\" className=\"h-2.5 w-2.5 shrink-0 rounded-sm\" style={{ background: off ? \"transparent\" : colorOf(s, i), boxShadow: `inset 0 0 0 1.5px ${colorOf(s, i)}` }} />\n <span className=\"truncate\">{s.label}</span>\n <span className=\"shrink-0 tabular-nums text-muted-foreground\">{formatWith(measure, s.total)}</span>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n {data.says.length > 0 ? (\n <p data-matrx-drill-chart-says className=\"text-xs text-muted-foreground\">\n {data.says.join(\" \")}\n </p>\n ) : null}\n </>,\n );\n}\n\n/** Every series' value in the period, each in the Measure's own format, and the period's total. */\nfunction DrillChartTooltip({\n period,\n data,\n hidden,\n measure,\n splitLabel,\n hint,\n}: {\n period: MatrxDrillChartPeriod;\n data: MatrxDrillChartData;\n hidden: ReadonlySet<string>;\n measure: MatrxDrillMeasure | undefined;\n splitLabel: string;\n hint: string | null;\n}) {\n return (\n <div\n data-matrx-drill-chart-tooltip\n className=\"min-w-[12rem] max-w-[18rem] rounded-md border border-border bg-popover px-2.5 py-2 text-xs text-popover-foreground shadow-md\"\n >\n <div className=\"mb-1 font-medium\">{period.label}</div>\n {data.split ? <div className=\"mb-1 text-muted-foreground\">{splitLabel}</div> : null}\n <ul className=\"flex flex-col gap-0.5\">\n {data.series.map((s, i) => (\n <li key={s.key} className={cn(\"flex items-center gap-1.5\", hidden.has(s.key) && \"opacity-50\")}>\n <span aria-hidden=\"true\" className=\"h-2 w-2 shrink-0 rounded-sm\" style={{ background: colorOf(s, i) }} />\n <span className=\"min-w-0 flex-1 truncate\">{s.label}</span>\n <span className=\"tabular-nums\">{formatWith(measure, period.values[s.key] ?? null)}</span>\n </li>\n ))}\n </ul>\n <div className=\"mt-1 flex justify-between border-t border-border pt-1 font-medium\">\n <span>Total</span>\n <span className=\"tabular-nums\">{formatWith(measure, period.total)}</span>\n </div>\n {hint ? <div className=\"mt-1 text-muted-foreground\">{hint}</div> : null}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/data-table/DrillChart.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * THE DRILL CHART — `@ai-matrx/design-system/data-table/drill-chart` (lane DRILL-CHART,\n * DRILL-FINISH decision 5). ONE shared chart, fed by drill answers (`platform.drill_ask`):\n * stacked bars over time; the stack is the top N (`seriesLimit`, the host's knob\n * `drill.chart.top_n`, default 10) of the question's split Dimension ranked by the Measure over the\n * WHOLE window, everything else one Other series — every bar sums to its period's total and the\n * bars to the window total. No split = one series.\n *\n * What it asks is decided by the pure model in `drill.ts` (`drillChartQuestions` → round one,\n * `drillChartCellsQuestion` → round two, `drillChartData` → the bars); this file only draws.\n *\n * click a segment → drill: the value joins the trail, exactly as the answer table's drill\n * click a period → the window narrows to that period (`from..to`)\n * click Other → the period is broken out: the window narrows to it, where the answer table\n * lists every value one by one (said in the tooltip — never a dead click)\n * legend → buttons (keyboard reachable) that hide and show a series\n *\n * Its own entry, so `./data-table` never imports recharts — an OPTIONAL peer only a host that\n * draws this chart installs. Colors are the package's `--matrx-chart-*` tokens (light and dark).\n */\n\nimport { useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from \"recharts\";\n\nimport { formatCount } from \"@ai-matrx/kit/format\";\n\nimport { cn } from \"../cn\";\nimport { ErrorBox } from \"../error-box\";\nimport { Skeleton } from \"../skeleton\";\nimport {\n MATRX_DRILL_CHART_SERIES_LIMIT,\n dimensionRefLabel,\n drillChartData,\n drillChartDrill,\n drillChartNarrowToPeriod,\n drillChartQuestions,\n type MatrxDrillChartAnswers,\n type MatrxDrillChartData,\n type MatrxDrillChartPeriod,\n type MatrxDrillChartSeries,\n type MatrxDrillDimension,\n type MatrxDrillMeasure,\n type MatrxDrillQuestion,\n} from \"./drill\";\n\nexport {\n MATRX_DRILL_CHART_CEILING,\n MATRX_DRILL_CHART_OTHER,\n MATRX_DRILL_CHART_SERIES_LIMIT,\n drillChartCellsQuestion,\n drillChartData,\n drillChartDrill,\n drillChartNarrowToPeriod,\n drillChartQuestions,\n drillChartSeriesKey,\n type MatrxDrillChartAnswers,\n type MatrxDrillChartContext,\n type MatrxDrillChartData,\n type MatrxDrillChartPeriod,\n type MatrxDrillChartPlan,\n type MatrxDrillChartRequest,\n type MatrxDrillChartRequestKey,\n type MatrxDrillChartRow,\n type MatrxDrillChartSeries,\n} from \"./drill\";\n\nexport interface MatrxDrillChartProps {\n dimensions: readonly MatrxDrillDimension[];\n measures: readonly MatrxDrillMeasure[];\n /** The question the answer table answers; the chart asks its own questions from it. */\n question: MatrxDrillQuestion;\n onQuestionChange: (next: MatrxDrillQuestion) => void;\n /** The door's rows (with `kind`) per chart request key: `series`, `periods`, `cells`. */\n answers: MatrxDrillChartAnswers;\n /** Series before Other — the host feeds the knob `drill.chart.top_n`. Default 10. */\n seriesLimit?: number | undefined;\n /** The Measure stacked. Default: the question's first shown Measure. */\n measure?: string | null | undefined;\n /**\n * STACK THESE MEASURES instead (two or more that add up, e.g. input and output tokens): one series\n * each, no split; a segment click narrows to its period (lane DRILL-FLIP-FIXES L3).\n */\n stack?: readonly string[] | null | undefined;\n /** The time reference when the question does not group by time. */\n time?: string | null | undefined;\n /** The store's page ceiling, when the host knows it is not 1000. */\n ceiling?: number | undefined;\n /** Declared drill paths; a segment click follows them like the answer table. */\n paths?: readonly (readonly string[])[] | undefined;\n /** A read that failed, in words. */\n error?: string | null | undefined;\n emptyLabel?: string | undefined;\n /** Plot height in px (default 220). */\n height?: number | undefined;\n /** A fixed plot width in px; default fills its container. */\n width?: number | undefined;\n /** Words above the chart (a title, the freshness). */\n note?: ReactNode | undefined;\n className?: string | undefined;\n}\n\nconst NO_PATHS: readonly (readonly string[])[] = [];\nconst PLOT_MARGIN = { top: 4, right: 4, bottom: 0, left: 0 };\nconst Y_AXIS_WIDTH = 56;\n\nfunction colorOf(series: MatrxDrillChartSeries, index: number): string {\n if (series.color) return series.color;\n return series.other ? \"hsl(var(--matrx-chart-other))\" : `hsl(var(--matrx-chart-${(index % 10) + 1}))`;\n}\n\nfunction formatWith(measure: MatrxDrillMeasure | undefined, value: number | null | undefined): string {\n if (value === null || value === undefined) return \"not measured\";\n if (measure?.format) return measure.format(value);\n return value.toLocaleString(undefined, { maximumFractionDigits: 2 });\n}\n\n/** A short axis number: the Measure's own format when it has one, else 1.2k / 3.4M. */\n/**\n * A tick on the value axis: the measure's own words while they fit the axis (\"$12\", \"40 ms\"), else the\n * number in compact form (\"28M\") — the chart's title already names the unit (\"Cost (points) by day\").\n * A long formatted value (\"28,000,000 points\") was clipped to \",000,000 points\" (lane DRILL-ADOPT walk).\n */\nexport function drillChartAxisNumber(measure: MatrxDrillMeasure | undefined, value: number): string {\n // A fractional tick under a thousand keeps one decimal (0.5); everything else is kit's compact count.\n const compact =\n Math.abs(value) < 1000 && !Number.isInteger(value)\n ? String(Math.round(value * 10) / 10)\n : formatCount(value, { style: \"compact\" });\n if (!measure?.format) return compact;\n const said = measure.format(value);\n if (said.length <= 7) return said;\n // THE TICK SAYS THE NUMBER THE FORMAT SAID, NOT THE RAW VALUE. A Measure's format may convert its unit\n // (the usage explorer stores dollars and prints points at the points rate), so compacting the raw value\n // put \"450\" under \"Cost (points) by day\" for a 9,000,000-point day (localhost walk, 2026-09-30). The\n // number the format printed is compacted, keeping a leading currency sign (\"$1,234.56\" → \"$1.2k\").\n const printed = said.match(/^([^\\d-]*)(-?\\d[\\d,]*(?:\\.\\d+)?)/);\n if (!printed) return compact;\n const numeric = printed[2];\n if (!numeric) return compact;\n const n = Number(numeric.replace(/,/g, \"\"));\n if (!Number.isFinite(n)) return compact;\n const prefix = printed[1]?.trim() ?? \"\";\n const shown = Math.abs(n) < 1000 && !Number.isInteger(n) ? String(Math.round(n * 10) / 10) : formatCount(n, { style: \"compact\" });\n return prefix.length <= 1 ? `${prefix}${shown}` : shown;\n}\nconst axisNumber = drillChartAxisNumber;\n\nexport function MatrxDrillChart({\n dimensions,\n measures,\n question,\n onQuestionChange,\n answers,\n seriesLimit = MATRX_DRILL_CHART_SERIES_LIMIT,\n measure: measureKey,\n stack,\n time,\n ceiling,\n paths = NO_PATHS,\n error,\n emptyLabel = \"No value\",\n height = 220,\n width,\n note,\n className,\n}: MatrxDrillChartProps) {\n const plan = useMemo(\n () => drillChartQuestions(question, seriesLimit, { dimensions, measures, measure: measureKey, stack, time, ceiling }),\n [question, seriesLimit, dimensions, measures, measureKey, stack, time, ceiling],\n );\n const data = useMemo(() => drillChartData(plan, answers, dimensions, measures, emptyLabel), [plan, answers, dimensions, measures, emptyLabel]);\n const measure = measures.find((m) => m.key === plan.measure);\n const [hidden, setHidden] = useState<ReadonlySet<string>>(() => new Set());\n const [hover, setHover] = useState<string | null>(null);\n // A segment's click reaches the chart's own click too (it bubbles): the segment wins.\n const segmentClicked = useRef(false);\n const plotRef = useRef<HTMLDivElement | null>(null);\n\n const measureWords = plan.stack ? plan.stack.map((k) => measures.find((m) => m.key === k)?.label ?? k).join(\" and \") : measure?.label ?? plan.measure;\n const title = plan.time\n ? `${measureWords} by ${dimensionRefLabel(dimensions, plan.time).replace(/^.* by /, \"\")}${\n plan.split ? `, split by ${dimensionRefLabel(dimensions, plan.split)}` : \"\"\n }`\n : measureWords;\n\n const frame = (body: ReactNode) => (\n <section data-matrx-drill-chart aria-label={title} className={cn(\"flex min-w-0 flex-col gap-1.5 px-2 py-2\", className)}>\n {note ? <div data-matrx-drill-chart-note className=\"text-xs text-muted-foreground\">{note}</div> : null}\n {body}\n </section>\n );\n\n if (error) {\n return frame(<ErrorBox className=\"text-sm text-destructive-ink\" message={error} operation=\"Draw the chart\" />);\n }\n if (plan.refused) {\n return frame(\n <p data-matrx-drill-chart-refused className=\"text-xs text-muted-foreground\">\n {plan.refused}\n </p>,\n );\n }\n if (!data) {\n return frame(<Skeleton data-matrx-drill-chart-loading className=\"w-full\" style={{ height }} />);\n }\n if (data.periods.length === 0) {\n return frame(<p data-matrx-drill-chart-empty className=\"text-xs text-muted-foreground\">Nothing was counted in this window.</p>);\n }\n\n const toggle = (key: string) =>\n setHidden((was) => {\n const next = new Set(was);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n const drawn = data.series.map((s, i) => ({ s, i, dataKey: `s${i}` })).filter(({ s }) => !hidden.has(s.key));\n const rows = data.periods.map((p) => {\n const row: Record<string, string | number | null> = { __key: p.key, __label: p.label };\n data.series.forEach((s, i) => {\n row[`s${i}`] = p.values[s.key] ?? null;\n });\n return row;\n });\n const splitLabel = data.split ? dimensionRefLabel(dimensions, data.split) : \"\";\n const onPeriod = (period: MatrxDrillChartPeriod | undefined) => {\n if (period) onQuestionChange(drillChartNarrowToPeriod(question, plan, period.value, dimensions));\n };\n const onSegment = (series: MatrxDrillChartSeries, period: MatrxDrillChartPeriod | undefined) => {\n segmentClicked.current = true;\n if (series.other || !data.split) {\n onPeriod(period);\n return;\n }\n onQuestionChange(drillChartDrill(question, plan, series.value, dimensions, paths));\n };\n const hintFor = (key: string | null): string | null => {\n const series = data.series.find((s) => s.key === key);\n if (!series) return \"Click a bar to narrow the window to its period.\";\n if (series.other) return `Other is everything past the top ${plan.seriesLimit}: click to break this period out, one by one, in the table.`;\n if (!data.split) return \"Click to narrow the window to this period.\";\n return `Click to drill into ${series.label}.`;\n };\n\n const plot = (\n <BarChart\n data={rows}\n margin={PLOT_MARGIN}\n barCategoryGap=\"12%\"\n onClick={(_state, event) => {\n if (segmentClicked.current) {\n segmentClicked.current = false;\n return;\n }\n // The period is read from WHERE the person clicked, never from the tooltip's last index:\n // a click that arrives before the chart has seen the pointer move would otherwise read a\n // stale index (walked: a quick click landed on the first day). Bands are equal-width over\n // the plot, which starts after the value axis and ends at the right margin.\n const box = (plotRef.current?.querySelector(\"svg.recharts-surface\") ?? plotRef.current)?.getBoundingClientRect();\n if (!box || data.periods.length === 0) return;\n const left = box.left + PLOT_MARGIN.left + Y_AXIS_WIDTH;\n const right = box.right - PLOT_MARGIN.right;\n const index = Math.floor(((event.clientX - left) / (right - left)) * data.periods.length);\n if (index >= 0 && index < data.periods.length) onPeriod(data.periods[index]);\n }}\n {...(width ? { width, height } : {})}\n >\n <CartesianGrid vertical={false} stroke=\"hsl(var(--border))\" strokeDasharray=\"0\" />\n <XAxis\n dataKey=\"__label\"\n tickLine={false}\n axisLine={{ stroke: \"hsl(var(--border))\" }}\n tick={{ fill: \"hsl(var(--muted-foreground))\", fontSize: 11 }}\n minTickGap={12}\n interval=\"preserveStartEnd\"\n />\n <YAxis\n tickLine={false}\n axisLine={false}\n width={Y_AXIS_WIDTH}\n tick={{ fill: \"hsl(var(--muted-foreground))\", fontSize: 11 }}\n tickFormatter={(v: number) => axisNumber(measure, v)}\n />\n <Tooltip\n cursor={{ fill: \"hsl(var(--muted))\", opacity: 0.6 }}\n isAnimationActive={false}\n content={({ active, label }) => {\n if (!active) return null;\n const period = data.periods.find((p) => p.label === label);\n if (!period) return null;\n return (\n <DrillChartTooltip\n period={period}\n data={data}\n hidden={hidden}\n measure={measure}\n splitLabel={splitLabel}\n hint={hintFor(hover)}\n />\n );\n }}\n />\n {drawn.map(({ s, i, dataKey }) => (\n <Bar\n key={s.key}\n dataKey={dataKey}\n name={s.label}\n stackId=\"drill\"\n fill={colorOf(s, i)}\n stroke=\"hsl(var(--background))\"\n strokeWidth={1}\n isAnimationActive={false}\n cursor=\"pointer\"\n onMouseEnter={() => setHover(s.key)}\n onMouseLeave={() => setHover(null)}\n onClick={(_item, index) => onSegment(s, data.periods[index])}\n />\n ))}\n </BarChart>\n );\n\n return frame(\n <>\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5\">\n <h3 className=\"text-xs font-medium text-foreground\">{title}</h3>\n <span data-matrx-drill-chart-total className=\"text-xs text-muted-foreground\">\n Total {formatWith(measure, data.total)}\n </span>\n </div>\n <div ref={plotRef} data-matrx-drill-chart-plot className=\"w-full min-w-0\" style={{ height }}>\n {width ? plot : <ResponsiveContainer width=\"100%\" height=\"100%\">{plot}</ResponsiveContainer>}\n </div>\n {data.series.length > 1 || data.split ? (\n <ul data-matrx-drill-chart-legend aria-label=\"Series — press to hide or show\" className=\"flex flex-wrap gap-x-1 gap-y-0.5\">\n {data.series.map((s, i) => {\n const off = hidden.has(s.key);\n return (\n <li key={s.key} className=\"min-w-0\">\n <button\n type=\"button\"\n data-matrx-drill-chart-series={s.key}\n aria-pressed={!off}\n title={`${off ? \"Show\" : \"Hide\"} ${s.label} (${formatWith(measure, s.total)})`}\n onClick={() => toggle(s.key)}\n className={cn(\n \"inline-flex max-w-[16rem] items-center gap-1.5 rounded px-1.5 py-0.5 text-xs text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring\",\n off && \"text-muted-foreground line-through\",\n )}\n >\n <span aria-hidden=\"true\" className=\"h-2.5 w-2.5 shrink-0 rounded-sm\" style={{ background: off ? \"transparent\" : colorOf(s, i), boxShadow: `inset 0 0 0 1.5px ${colorOf(s, i)}` }} />\n <span className=\"truncate\">{s.label}</span>\n <span className=\"shrink-0 tabular-nums text-muted-foreground\">{formatWith(measure, s.total)}</span>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n {data.says.length > 0 ? (\n <p data-matrx-drill-chart-says className=\"text-xs text-muted-foreground\">\n {data.says.join(\" \")}\n </p>\n ) : null}\n </>,\n );\n}\n\n/** Every series' value in the period, each in the Measure's own format, and the period's total. */\nfunction DrillChartTooltip({\n period,\n data,\n hidden,\n measure,\n splitLabel,\n hint,\n}: {\n period: MatrxDrillChartPeriod;\n data: MatrxDrillChartData;\n hidden: ReadonlySet<string>;\n measure: MatrxDrillMeasure | undefined;\n splitLabel: string;\n hint: string | null;\n}) {\n return (\n <div\n data-matrx-drill-chart-tooltip\n className=\"min-w-[12rem] max-w-[18rem] rounded-md border border-border bg-popover px-2.5 py-2 text-xs text-popover-foreground shadow-md\"\n >\n <div className=\"mb-1 font-medium\">{period.label}</div>\n {data.split ? <div className=\"mb-1 text-muted-foreground\">{splitLabel}</div> : null}\n <ul className=\"flex flex-col gap-0.5\">\n {data.series.map((s, i) => (\n <li key={s.key} className={cn(\"flex items-center gap-1.5\", hidden.has(s.key) && \"opacity-50\")}>\n <span aria-hidden=\"true\" className=\"h-2 w-2 shrink-0 rounded-sm\" style={{ background: colorOf(s, i) }} />\n <span className=\"min-w-0 flex-1 truncate\">{s.label}</span>\n <span className=\"tabular-nums\">{formatWith(measure, period.values[s.key] ?? null)}</span>\n </li>\n ))}\n </ul>\n <div className=\"mt-1 flex justify-between border-t border-border pt-1 font-medium\">\n <span>Total</span>\n <span className=\"tabular-nums\">{formatWith(measure, period.total)}</span>\n </div>\n {hint ? <div className=\"mt-1 text-muted-foreground\">{hint}</div> : null}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-table/host.tsx","../../src/table-host/host.tsx","../../src/table-host/row-drawing.ts"],"sourcesContent":["\"use client\";\n\n// The host contract lives in ../table-host so sections that only DRAW a table's row (item, peek) never\n// load the data-table directory. This file keeps the public `@ai-matrx/design-system/data-table/host` path.\nexport * from \"../table-host/host\";\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","/**\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,IAAAA,gBAAoH;;;ACwBpH,mBAAgF;AAsBhF,IAAM,kBAAkB,uBAAO,IAAI,oDAAoD;AAkBhF,SAAS,qBAAuC,WAAc,MAA4B;AAC/F,EAAC,UAA8D,eAAe,IAAI,IAAI,IAAI,IAAI;AAC9F,SAAO;AACT;;;AD4ByH;AAHzH,IAAM,YAAY,uBAAO,IAAI,2CAA2C;AACxE,IAAM,eAAe;AACrB,IAAM,cAAc,aAAa,SAAS,UAAM,6BAAkC,CAAC,CAAC;AAC7E,SAAS,uBAAuB,EAAE,OAAO,SAAS,GAAuD;AAAE,SAAO,4CAAC,YAAY,UAAZ,EAAqB,OAAe,UAAS;AAAyB;AACzL,SAAS,wBAA4C;AAAE,aAAO,0BAAW,WAAW;AAAG;AAa9F,IAAM,mBAAmB,uBAAO,IAAI,kDAAkD;AACtF,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB,mBAAmB,gBAAgB,UAAM,6BAA2C,IAAI;AAE3G,SAAS,0BAA0B,EAAE,OAAO,SAAS,GAAyD;AAAE,SAAO,4CAAC,kBAAkB,UAAlB,EAA2B,OAAe,UAAS;AAA+B;AAE1M,SAAS,0BAAuD;AAAE,aAAO,0BAAW,iBAAiB;AAAG;AAE/G,IAAM,oBAAoB,uBAAO,IAAI,mDAAmD;AACxF,IAAM,sBAAsB;AAC5B,IAAM,qBAAqB,oBAAoB,iBAAiB,UAAM,6BAAmD,IAAI;AACtH,SAAS,2BAA2B,EAAE,OAAO,SAAS,GAAiE;AAAE,SAAO,4CAAC,mBAAmB,UAAnB,EAA4B,OAAe,UAAS;AAAgC;AACrN,SAAS,2BAAgE;AAAE,aAAO,0BAAW,kBAAkB;AAAG;","names":["import_react"]}
1
+ {"version":3,"sources":["../../src/data-table/host.tsx","../../src/table-host/host.tsx","../../src/table-host/row-drawing.ts"],"sourcesContent":["\"use client\";\n\n// The host contract lives in ../table-host so sections that only DRAW a table's row (item, peek) never\n// load the data-table directory. This file keeps the public `@ai-matrx/design-system/data-table/host` path.\nexport * from \"../table-host/host\";\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","/**\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,IAAAA,gBAAoH;;;ACwBpH,mBAAgF;AAsBhF,IAAM,kBAAkB,uBAAO,IAAI,oDAAoD;AAkBhF,SAAS,qBAAuC,WAAc,MAA4B;AAC/F,EAAC,UAA8D,eAAe,IAAI,IAAI,IAAI,IAAI;AAC9F,SAAO;AACT;;;AD4ByH;AAHzH,IAAM,YAAY,uBAAO,IAAI,2CAA2C;AACxE,IAAM,eAAe;AACrB,IAAM,cAAc,aAAa,SAAS,UAAM,6BAAkC,CAAC,CAAC;AAC7E,SAAS,uBAAuB,EAAE,OAAO,SAAS,GAAuD;AAAE,SAAO,4CAAC,YAAY,UAAZ,EAAqB,OAAe,UAAS;AAAyB;AACzL,SAAS,wBAA4C;AAAE,aAAO,0BAAW,WAAW;AAAG;AAa9F,IAAM,mBAAmB,uBAAO,IAAI,kDAAkD;AACtF,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB,mBAAmB,gBAAgB,UAAM,6BAA2C,IAAI;AAE3G,SAAS,0BAA0B,EAAE,OAAO,SAAS,GAAyD;AAAE,SAAO,4CAAC,kBAAkB,UAAlB,EAA2B,OAAe,UAAS;AAA+B;AAE1M,SAAS,0BAAuD;AAAE,aAAO,0BAAW,iBAAiB;AAAG;AAE/G,IAAM,oBAAoB,uBAAO,IAAI,mDAAmD;AACxF,IAAM,sBAAsB;AAC5B,IAAM,qBAAqB,oBAAoB,iBAAiB,UAAM,6BAAmD,IAAI;AACtH,SAAS,2BAA2B,EAAE,OAAO,SAAS,GAAiE;AAAE,SAAO,4CAAC,mBAAmB,UAAnB,EAA4B,OAAe,UAAS;AAAgC;AACrN,SAAS,2BAAgE;AAAE,aAAO,0BAAW,kBAAkB;AAAG;","names":["import_react"]}
@@ -1257,8 +1257,12 @@ interface MatrxDataTableHost {
1257
1257
  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. */
1258
1258
  defaultLook?: MatrxDataTableLook;
1259
1259
  notify?: {
1260
- success(message: string): void;
1261
- error(message: string): void;
1260
+ success(message: string, options?: {
1261
+ description?: string;
1262
+ }): void;
1263
+ error(message: string, options?: {
1264
+ description?: string;
1265
+ }): void;
1262
1266
  }; /** Client-side navigation (the app router), for `rowClick="page"`. Absent: a full page load. */
1263
1267
  navigate?: (href: string) => void;
1264
1268
  }
@@ -1257,8 +1257,12 @@ interface MatrxDataTableHost {
1257
1257
  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. */
1258
1258
  defaultLook?: MatrxDataTableLook;
1259
1259
  notify?: {
1260
- success(message: string): void;
1261
- error(message: string): void;
1260
+ success(message: string, options?: {
1261
+ description?: string;
1262
+ }): void;
1263
+ error(message: string, options?: {
1264
+ description?: string;
1265
+ }): void;
1262
1266
  }; /** Client-side navigation (the app router), for `rowClick="page"`. Absent: a full page load. */
1263
1267
  navigate?: (href: string) => void;
1264
1268
  }
@@ -8,7 +8,7 @@ import {
8
8
  useMatrxDataTableHost,
9
9
  useMatrxTableRowAlchemy,
10
10
  useMatrxTableRowControls
11
- } from "../chunk-POJ3RR3K.js";
11
+ } from "../chunk-B5D3VIMC.js";
12
12
  export {
13
13
  MatrxDataTableProvider,
14
14
  MatrxTableRowAlchemyScope,
@@ -1120,7 +1120,7 @@ var DropdownMenuSubTrigger = React6.forwardRef(({ className, inset, children, ..
1120
1120
  DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
1121
1121
  var DropdownMenuSubContent = React6.forwardRef(({ className, container, surface = "default", sideOffset = 2, alignOffset = -4, collisionPadding, onFocusOutside, children, ...props }, ref) => {
1122
1122
  const portalContainer = usePortalContainer(container);
1123
- const [bottomClearance] = React6.useState(readBottomClearance);
1123
+ const [bottomClearance] = React6.useState(sharedBottomClearance);
1124
1124
  const padding = menuCollisionPadding(collisionPadding ?? 8, 0, bottomClearance);
1125
1125
  const nested = useNestedSubmenuPlacement(ref, padding);
1126
1126
  const keep = useSubmenuSurvivesParentPanelFocus(nested.ref, onFocusOutside);
@@ -1142,18 +1142,27 @@ var DropdownMenuSubContent = React6.forwardRef(({ className, container, surface
1142
1142
  ) });
1143
1143
  });
1144
1144
  DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
1145
- function readTopClearance() {
1146
- return readClearance("var(--matrx-floating-top-clearance, var(--shell-header-h, 0px))");
1147
- }
1148
- function readBottomClearance() {
1149
- return readClearance("var(--matrx-floating-clearance, 0px)");
1150
- }
1151
1145
  function menuCollisionPadding(caller, top, bottom, fallback = 0) {
1152
1146
  const side = (key) => typeof caller === "number" ? caller : caller?.[key] ?? fallback;
1153
1147
  return { top: Math.max(side("top"), top), right: side("right"), bottom: Math.max(side("bottom"), bottom), left: side("left") };
1154
1148
  }
1155
- function readClearance(height) {
1149
+ var CLEARANCE_REUSE_MS = 500;
1150
+ var clearanceCache = /* @__PURE__ */ new Map();
1151
+ var clock = () => typeof performance !== "undefined" ? performance.now() : Date.now();
1152
+ function readClearance(height, shared = false) {
1156
1153
  if (typeof document === "undefined") return 0;
1154
+ const now = clock();
1155
+ const held = shared ? clearanceCache.get(height) : void 0;
1156
+ if (held && now - held.at < CLEARANCE_REUSE_MS) return held.px;
1157
+ const px2 = measureClearance(height);
1158
+ clearanceCache.set(height, { px: px2, at: now });
1159
+ return px2;
1160
+ }
1161
+ var TOP_CLEARANCE = "var(--matrx-floating-top-clearance, var(--shell-header-h, 0px))";
1162
+ var BOTTOM_CLEARANCE = "var(--matrx-floating-clearance, 0px)";
1163
+ var sharedTopClearance = () => readClearance(TOP_CLEARANCE, true);
1164
+ var sharedBottomClearance = () => readClearance(BOTTOM_CLEARANCE, true);
1165
+ function measureClearance(height) {
1157
1166
  const probe2 = document.createElement("div");
1158
1167
  probe2.style.cssText = `position:fixed;top:0;left:0;width:0;visibility:hidden;pointer-events:none;height:${height}`;
1159
1168
  document.body.appendChild(probe2);
@@ -1163,8 +1172,8 @@ function readClearance(height) {
1163
1172
  }
1164
1173
  var DropdownMenuContent = React6.forwardRef(({ className, sideOffset = 4, container, surface = "default", children, onCloseAutoFocus, collisionPadding, ...props }, ref) => {
1165
1174
  const portalContainer = usePortalContainer(container);
1166
- const [topClearance] = React6.useState(readTopClearance);
1167
- const [bottomClearance] = React6.useState(readBottomClearance);
1175
+ const [topClearance] = React6.useState(sharedTopClearance);
1176
+ const [bottomClearance] = React6.useState(sharedBottomClearance);
1168
1177
  const edges = useMenuScrollEdges(ref);
1169
1178
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DropdownMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1170
1179
  DropdownMenuPrimitive.Content,
@@ -3905,6 +3914,16 @@ function errorSentenceOf(error) {
3905
3914
  }
3906
3915
  var TIMED_OUT_SENTENCE = "That took too long. Nothing was lost; try again.";
3907
3916
  var FALLBACK_ERROR_SENTENCE = "Something went wrong. Try again.";
3917
+ var CARD_ERROR_LINE = {
3918
+ OFFLINE: "No connection",
3919
+ TIMEOUT: "Took too long. Nothing was lost.",
3920
+ UNAUTHORIZED: "Signed out. Sign in again.",
3921
+ FORBIDDEN: "Not allowed for this account",
3922
+ NOT_FOUND: "Not found. Refresh and retry.",
3923
+ RATE_LIMIT: "Too many requests. Wait a moment.",
3924
+ SERVER: "Server problem. Try again soon.",
3925
+ FALLBACK: "Something went wrong"
3926
+ };
3908
3927
  function isTimedOutError(error, message, code) {
3909
3928
  if (code === "57014" || code === "timed_out") return true;
3910
3929
  if (error && typeof error === "object") {
@@ -3922,8 +3941,9 @@ var ANY_HOST_CODES = /* @__PURE__ */ new Set(["OFFLINE", "TIMEOUT", "UNAUTHORIZE
3922
3941
  var MACHINE_PREFIX = /^\s*(?:\[[\w./-]+\]\s*|[a-z][\w]*(?:[._][\w]+)+:\s+)/;
3923
3942
  var RAW_TEXT = /\b(?:Type|Reference|Syntax|Range|Internal|Eval|URI)Error\b|\bat\s+[\w$.<>]+\s*\(|\.(?:[cm]?[jt]sx?|py):\d+|(?:^|[\s(])(?:\/[\w.@-]+){2,}|\bundefined is not\b|\bcannot read propert(?:y|ies)\b|\bis not a function\b|\bunexpected token\b/i;
3924
3943
  function peopleErrorSentence(error, message, options = {}) {
3925
- if (isTimedOutError(error, message, options.code)) return TIMED_OUT_SENTENCE;
3926
- const fallback = options.fallback ?? FALLBACK_ERROR_SENTENCE;
3944
+ const card = options.card === true;
3945
+ if (isTimedOutError(error, message, options.code)) return card ? CARD_ERROR_LINE.TIMEOUT : TIMED_OUT_SENTENCE;
3946
+ const fallback = options.fallback ?? (card ? CARD_ERROR_LINE.FALLBACK : FALLBACK_ERROR_SENTENCE);
3927
3947
  const raw = message?.trim() || errorSentenceOf(error).trim();
3928
3948
  if (!raw) return fallback;
3929
3949
  let text = raw;
@@ -3931,7 +3951,8 @@ function peopleErrorSentence(error, message, options = {}) {
3931
3951
  if (!text) return fallback;
3932
3952
  if (offlineText(text) || RAW_TEXT.test(text) || text !== raw) {
3933
3953
  const said = describeError(text, fallback);
3934
- return said.code && ANY_HOST_CODES.has(said.code) ? said.cause : fallback;
3954
+ if (!said.code || !ANY_HOST_CODES.has(said.code)) return fallback;
3955
+ return card ? CARD_ERROR_LINE[said.code] : said.cause;
3935
3956
  }
3936
3957
  return text;
3937
3958
  }
@@ -6430,13 +6451,14 @@ function useTableNotice() {
6430
6451
  const [message, setMessage] = (0, import_react17.useState)(null);
6431
6452
  const notify = (0, import_react17.useMemo)(
6432
6453
  () => ({
6433
- success(text) {
6434
- if (host.notify) host.notify.success(text);
6435
- else setMessage({ text, error: false });
6454
+ // A card is a short title and an optional line (NOTICE_TEXT_BUDGET); the inline fallback joins them.
6455
+ success(text, options) {
6456
+ if (host.notify) host.notify.success(text, options);
6457
+ else setMessage({ text: options?.description ? `${text}. ${options.description}` : text, error: false });
6436
6458
  },
6437
- error(text) {
6438
- if (host.notify) host.notify.error(text);
6439
- else setMessage({ text, error: true });
6459
+ error(text, options) {
6460
+ if (host.notify) host.notify.error(text, options);
6461
+ else setMessage({ text: options?.description ? `${text}. ${options.description}` : text, error: true });
6440
6462
  }
6441
6463
  }),
6442
6464
  [host]
@@ -11876,9 +11898,9 @@ function drillWindowMoment(value) {
11876
11898
  function momentWords(end) {
11877
11899
  const [y, m, d] = end.slice(0, 10).split("-").map(Number);
11878
11900
  const month = new Date(Date.UTC(y, m - 1, d)).toLocaleString("en-US", { month: "short", timeZone: "UTC" });
11879
- const clock = end.length > 10 ? end.slice(11, 16) : null;
11901
+ const clock2 = end.length > 10 ? end.slice(11, 16) : null;
11880
11902
  const zone = end.endsWith("Z") ? "UTC" : end.length > 16 ? `UTC${end.slice(-6)}`.replace("UTC+00:00", "UTC") : "";
11881
- return { day: `${month} ${d}, ${y}`, clock, zone };
11903
+ return { day: `${month} ${d}, ${y}`, clock: clock2, zone };
11882
11904
  }
11883
11905
  function drillWindowLabel(window2) {
11884
11906
  if (!window2) return "All time";
@@ -14078,9 +14100,9 @@ function drillConfigFromColumns(columns, rows = [], options = {}) {
14078
14100
  { key: `max_${id}`, label: `${label} (max)`, additive: false, op: "max", of: id, ...fmt }
14079
14101
  );
14080
14102
  } else if (kind === "time") {
14081
- const clock = sample.some((v) => v instanceof Date || typeof v === "string" && v.length > 10 && !/T00:00(:00(\.0+)?)?(Z|[+-]00:?00)?$/.test(v));
14082
- if (!clock) calendarDates.add(id);
14083
- dimensions.push({ key: id, label, kind: "time", grains: clock ? MATRX_DRILL_GRAINS : MATRX_DRILL_DEFAULT_GRAINS });
14103
+ const clock2 = sample.some((v) => v instanceof Date || typeof v === "string" && v.length > 10 && !/T00:00(:00(\.0+)?)?(Z|[+-]00:?00)?$/.test(v));
14104
+ if (!clock2) calendarDates.add(id);
14105
+ dimensions.push({ key: id, label, kind: "time", grains: clock2 ? MATRX_DRILL_GRAINS : MATRX_DRILL_DEFAULT_GRAINS });
14084
14106
  } else if (kind === "boolean") {
14085
14107
  dimensions.push({ key: id, label, kind: "boolean", cardinality: "low" });
14086
14108
  } else if (kind === "text") {
@@ -18294,7 +18316,7 @@ function RecordId({
18294
18316
  e.preventDefault();
18295
18317
  void (0, import_clipboard2.copyText)(value).then((ok) => {
18296
18318
  if (!ok) {
18297
- notify?.error(`Couldn't copy ${copyLabel}`);
18319
+ notify?.error("Not copied", { description: copyLabel });
18298
18320
  return;
18299
18321
  }
18300
18322
  setCopied(true);
@@ -18310,12 +18332,10 @@ function RecordId({
18310
18332
  try {
18311
18333
  const result = await onOpen(value);
18312
18334
  if (result === "forbidden") {
18313
- notify?.error("You don't have access to open this record");
18335
+ notify?.error("No access", { description: "This account can't open this record" });
18314
18336
  }
18315
18337
  } catch (err) {
18316
- notify?.error(
18317
- `Couldn't open: ${err instanceof Error ? err.message : String(err)}`
18318
- );
18338
+ notify?.error("Not opened", { description: peopleErrorSentence(err, null, { card: true }) });
18319
18339
  } finally {
18320
18340
  setOpening(false);
18321
18341
  }
@@ -23135,9 +23155,7 @@ function MatrxDataTableCore(coreProps) {
23135
23155
  });
23136
23156
  toast.success("Change saved");
23137
23157
  }).catch((error) => {
23138
- toast.error(
23139
- `Couldn't save: ${error instanceof Error ? error.message : String(error)}`
23140
- );
23158
+ toast.error("Not saved", { description: peopleErrorSentence(error, null, { card: true }) });
23141
23159
  }).finally(() => finishSaving(scope));
23142
23160
  }
23143
23161
  };
@@ -23283,9 +23301,7 @@ function MatrxDataTableCore(coreProps) {
23283
23301
  if (!hierarchy) return;
23284
23302
  void Promise.resolve(hierarchy.onMove(row, move)).catch(
23285
23303
  (error) => {
23286
- toast.error(
23287
- `Couldn't move row: ${error instanceof Error ? error.message : String(error)}`
23288
- );
23304
+ toast.error("Row not moved", { description: peopleErrorSentence(error, null, { card: true }) });
23289
23305
  }
23290
23306
  );
23291
23307
  };
@@ -23413,9 +23429,7 @@ function MatrxDataTableCore(coreProps) {
23413
23429
  });
23414
23430
  toast.success("Changes saved");
23415
23431
  } catch (e) {
23416
- toast.error(
23417
- `Couldn't save: ${e instanceof Error ? e.message : String(e)}`
23418
- );
23432
+ toast.error("Not saved", { description: peopleErrorSentence(e, null, { card: true }) });
23419
23433
  } finally {
23420
23434
  savingScopesRef.current.delete(scope);
23421
23435
  finishSaving(scope);
@@ -24992,7 +25006,7 @@ function MatrxDataTableCore(coreProps) {
24992
25006
  void Promise.resolve().then(() => toolbar.refresh?.onRefresh()).catch((cause) => {
24993
25007
  const message = cause instanceof Error ? cause.message : String(cause);
24994
25008
  setToolbarActionError(`Refresh failed: ${message}`);
24995
- toast.error(`Refresh failed: ${message}`);
25009
+ toast.error("Not refreshed", { description: peopleErrorSentence(cause, message, { card: true }) });
24996
25010
  }).finally(() => {
24997
25011
  toolbarActionPendingRef.current.refresh = false;
24998
25012
  setToolbarActionPending((current) => ({ ...current, refresh: false }));
@@ -25017,7 +25031,7 @@ function MatrxDataTableCore(coreProps) {
25017
25031
  void Promise.resolve().then(() => toolbar.add?.onAdd()).catch((cause) => {
25018
25032
  const message = cause instanceof Error ? cause.message : String(cause);
25019
25033
  setToolbarActionError(`Add failed: ${message}`);
25020
- toast.error(`Add failed: ${message}`);
25034
+ toast.error("Not added", { description: peopleErrorSentence(cause, message, { card: true }) });
25021
25035
  }).finally(() => {
25022
25036
  toolbarActionPendingRef.current.add = false;
25023
25037
  setToolbarActionPending((current) => ({ ...current, add: false }));