@ai-matrx/design-system 0.14.3 → 0.15.2

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 (106) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +32 -0
  3. package/dist/data-table/copy-helpers.cjs +149 -0
  4. package/dist/data-table/copy-helpers.cjs.map +1 -0
  5. package/dist/data-table/copy-helpers.d.cts +31 -0
  6. package/dist/data-table/copy-helpers.d.ts +31 -0
  7. package/dist/data-table/copy-helpers.js +127 -0
  8. package/dist/data-table/copy-helpers.js.map +1 -0
  9. package/dist/data-table/copy-types.cjs +19 -0
  10. package/dist/data-table/copy-types.cjs.map +1 -0
  11. package/dist/data-table/copy-types.d.cts +137 -0
  12. package/dist/data-table/copy-types.d.ts +137 -0
  13. package/dist/data-table/copy-types.js +1 -0
  14. package/dist/data-table/copy-types.js.map +1 -0
  15. package/dist/data-table/data-row-inspector.cjs +1016 -0
  16. package/dist/data-table/data-row-inspector.cjs.map +1 -0
  17. package/dist/data-table/data-row-inspector.d.cts +34 -0
  18. package/dist/data-table/data-row-inspector.d.ts +34 -0
  19. package/dist/data-table/data-row-inspector.js +988 -0
  20. package/dist/data-table/data-row-inspector.js.map +1 -0
  21. package/dist/data-table/data-row-window.cjs +1136 -0
  22. package/dist/data-table/data-row-window.cjs.map +1 -0
  23. package/dist/data-table/data-row-window.d.cts +29 -0
  24. package/dist/data-table/data-row-window.d.ts +29 -0
  25. package/dist/data-table/data-row-window.js +1110 -0
  26. package/dist/data-table/data-row-window.js.map +1 -0
  27. package/dist/data-table/filter-engine.cjs +314 -0
  28. package/dist/data-table/filter-engine.cjs.map +1 -0
  29. package/dist/data-table/filter-engine.d.cts +35 -0
  30. package/dist/data-table/filter-engine.d.ts +35 -0
  31. package/dist/data-table/filter-engine.js +292 -0
  32. package/dist/data-table/filter-engine.js.map +1 -0
  33. package/dist/data-table/host.cjs +40 -0
  34. package/dist/data-table/host.cjs.map +1 -0
  35. package/dist/data-table/host.d.cts +73 -0
  36. package/dist/data-table/host.d.ts +73 -0
  37. package/dist/data-table/host.js +20 -0
  38. package/dist/data-table/host.js.map +1 -0
  39. package/dist/data-table/index.cjs +7198 -0
  40. package/dist/data-table/index.cjs.map +1 -0
  41. package/dist/data-table/index.d.cts +27 -0
  42. package/dist/data-table/index.d.ts +27 -0
  43. package/dist/data-table/index.js +7202 -0
  44. package/dist/data-table/index.js.map +1 -0
  45. package/dist/data-table/infer-filter.cjs +111 -0
  46. package/dist/data-table/infer-filter.cjs.map +1 -0
  47. package/dist/data-table/infer-filter.d.cts +17 -0
  48. package/dist/data-table/infer-filter.d.ts +17 -0
  49. package/dist/data-table/infer-filter.js +89 -0
  50. package/dist/data-table/infer-filter.js.map +1 -0
  51. package/dist/data-table/layered-filters.cjs +249 -0
  52. package/dist/data-table/layered-filters.cjs.map +1 -0
  53. package/dist/data-table/layered-filters.d.cts +3 -0
  54. package/dist/data-table/layered-filters.d.ts +3 -0
  55. package/dist/data-table/layered-filters.js +229 -0
  56. package/dist/data-table/layered-filters.js.map +1 -0
  57. package/dist/data-table/pagination.cjs +595 -0
  58. package/dist/data-table/pagination.cjs.map +1 -0
  59. package/dist/data-table/pagination.d.cts +34 -0
  60. package/dist/data-table/pagination.d.ts +34 -0
  61. package/dist/data-table/pagination.js +563 -0
  62. package/dist/data-table/pagination.js.map +1 -0
  63. package/dist/data-table/query-control.cjs +126 -0
  64. package/dist/data-table/query-control.cjs.map +1 -0
  65. package/dist/data-table/query-control.d.cts +32 -0
  66. package/dist/data-table/query-control.d.ts +32 -0
  67. package/dist/data-table/query-control.js +104 -0
  68. package/dist/data-table/query-control.js.map +1 -0
  69. package/dist/data-table/scroll-pagination.cjs +222 -0
  70. package/dist/data-table/scroll-pagination.cjs.map +1 -0
  71. package/dist/data-table/scroll-pagination.d.cts +37 -0
  72. package/dist/data-table/scroll-pagination.d.ts +37 -0
  73. package/dist/data-table/scroll-pagination.js +202 -0
  74. package/dist/data-table/scroll-pagination.js.map +1 -0
  75. package/dist/data-table/types.cjs +19 -0
  76. package/dist/data-table/types.cjs.map +1 -0
  77. package/dist/data-table/types.d.cts +3 -0
  78. package/dist/data-table/types.d.ts +3 -0
  79. package/dist/data-table/types.js +1 -0
  80. package/dist/data-table/types.js.map +1 -0
  81. package/dist/data-table/url-state.cjs +201 -0
  82. package/dist/data-table/url-state.cjs.map +1 -0
  83. package/dist/data-table/url-state.d.cts +33 -0
  84. package/dist/data-table/url-state.d.ts +33 -0
  85. package/dist/data-table/url-state.js +184 -0
  86. package/dist/data-table/url-state.js.map +1 -0
  87. package/dist/data-table/uuid-cell.cjs +371 -0
  88. package/dist/data-table/uuid-cell.cjs.map +1 -0
  89. package/dist/data-table/uuid-cell.d.cts +31 -0
  90. package/dist/data-table/uuid-cell.d.ts +31 -0
  91. package/dist/data-table/uuid-cell.js +345 -0
  92. package/dist/data-table/uuid-cell.js.map +1 -0
  93. package/dist/data-table/xlsx.cjs +100 -0
  94. package/dist/data-table/xlsx.cjs.map +1 -0
  95. package/dist/data-table/xlsx.d.cts +16 -0
  96. package/dist/data-table/xlsx.d.ts +16 -0
  97. package/dist/data-table/xlsx.js +70 -0
  98. package/dist/data-table/xlsx.js.map +1 -0
  99. package/dist/index.cjs +9 -5
  100. package/dist/index.cjs.map +1 -1
  101. package/dist/index.js +9 -5
  102. package/dist/index.js.map +1 -1
  103. package/dist/styles.css +22 -0
  104. package/dist/types-ClaxTaXq.d.ts +798 -0
  105. package/dist/types-XXbVB1sh.d.cts +798 -0
  106. package/package.json +164 -1
@@ -0,0 +1,1110 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/data-table/DataRowWindow.tsx
5
+ import { useEffect as useEffect3, useState as useState6 } from "react";
6
+
7
+ // src/dialog.tsx
8
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
9
+ import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
10
+ import { treeContainsComponent } from "@ai-matrx/kit/react-tree";
11
+ import * as React4 from "react";
12
+
13
+ // src/cn.ts
14
+ import { clsx } from "clsx";
15
+ import { twMerge } from "tailwind-merge";
16
+ function cn(...inputs) {
17
+ return twMerge(clsx(inputs));
18
+ }
19
+
20
+ // src/motion.ts
21
+ var MOTION_OVERLAY = "matrx-motion-overlay";
22
+ var MOTION_DIALOG = "matrx-motion-dialog";
23
+ var MOTION_BOTTOM_SHEET = "matrx-motion-bottom-sheet";
24
+ var MOTION_POPPER = "matrx-motion-popper";
25
+ var MOTION_SHEET = {
26
+ top: "matrx-motion-sheet-top",
27
+ bottom: "matrx-motion-sheet-bottom",
28
+ left: "matrx-motion-sheet-left",
29
+ right: "matrx-motion-sheet-right",
30
+ center: "matrx-motion-sheet-center"
31
+ };
32
+ var MOTION_PULSE = "matrx-pulse";
33
+ var MOTION_SPIN = "matrx-spin";
34
+ var ALL_MOTION_CLASSES = [
35
+ MOTION_OVERLAY,
36
+ MOTION_DIALOG,
37
+ MOTION_BOTTOM_SHEET,
38
+ MOTION_POPPER,
39
+ ...Object.values(MOTION_SHEET),
40
+ MOTION_PULSE,
41
+ MOTION_SPIN
42
+ ];
43
+
44
+ // src/icons.tsx
45
+ import { jsx, jsxs } from "react/jsx-runtime";
46
+ function lucideProps(props) {
47
+ return {
48
+ xmlns: "http://www.w3.org/2000/svg",
49
+ viewBox: "0 0 24 24",
50
+ fill: "none",
51
+ stroke: "currentColor",
52
+ strokeWidth: 2,
53
+ strokeLinecap: "round",
54
+ strokeLinejoin: "round",
55
+ "aria-hidden": true,
56
+ ...props
57
+ };
58
+ }
59
+ function XIcon(props) {
60
+ return /* @__PURE__ */ jsxs("svg", { ...lucideProps(props), children: [
61
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
62
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
63
+ ] });
64
+ }
65
+
66
+ // src/portal-container.tsx
67
+ import * as React from "react";
68
+ import { jsx as jsx2 } from "react/jsx-runtime";
69
+ var CONTEXT_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.design-system.portal-container-context");
70
+ var contextRegistry = globalThis;
71
+ var PortalContainerContext = contextRegistry[CONTEXT_SLOT] ??= React.createContext(void 0);
72
+ function PortalContainerProvider({
73
+ container,
74
+ children
75
+ }) {
76
+ return /* @__PURE__ */ jsx2(PortalContainerContext.Provider, { value: container ?? void 0, children });
77
+ }
78
+ function usePortalContainer(explicit) {
79
+ const injected = React.useContext(PortalContainerContext);
80
+ if (explicit !== void 0) {
81
+ return explicit ?? void 0;
82
+ }
83
+ return injected ?? void 0;
84
+ }
85
+
86
+ // src/radix-dialog-modal-context.tsx
87
+ import * as React2 from "react";
88
+ import { jsx as jsx3 } from "react/jsx-runtime";
89
+ var CONTEXT_SLOT2 = /* @__PURE__ */ Symbol.for("ai-matrx.design-system.dialog-modal-context");
90
+ var contextRegistry2 = globalThis;
91
+ var RadixDialogModalContext = contextRegistry2[CONTEXT_SLOT2] ??= React2.createContext(true);
92
+ function RadixDialogModalProvider({
93
+ children,
94
+ modal
95
+ }) {
96
+ return /* @__PURE__ */ jsx3(RadixDialogModalContext.Provider, { value: modal, children });
97
+ }
98
+ function useRadixDialogModal() {
99
+ return React2.useContext(RadixDialogModalContext);
100
+ }
101
+
102
+ // src/use-is-mobile.ts
103
+ import * as React3 from "react";
104
+ var MOBILE_BREAKPOINT = 768;
105
+ function useIsMobile() {
106
+ const [isMobile, setIsMobile] = React3.useState(false);
107
+ const [hasMounted, setHasMounted] = React3.useState(false);
108
+ React3.useEffect(() => {
109
+ setHasMounted(true);
110
+ const onChange = () => {
111
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
112
+ };
113
+ onChange();
114
+ if (typeof window.matchMedia !== "function") {
115
+ return void 0;
116
+ }
117
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
118
+ mql.addEventListener("change", onChange);
119
+ return () => mql.removeEventListener("change", onChange);
120
+ }, []);
121
+ if (!hasMounted) {
122
+ return false;
123
+ }
124
+ return isMobile;
125
+ }
126
+
127
+ // src/dialog.tsx
128
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
129
+ var Dialog = ({
130
+ children,
131
+ ...props
132
+ }) => {
133
+ const modal = props.modal ?? true;
134
+ return /* @__PURE__ */ jsx4(RadixDialogModalProvider, { modal, children: /* @__PURE__ */ jsx4(DialogPrimitive.Root, { ...props, modal, children }) });
135
+ };
136
+ Dialog.displayName = "Dialog";
137
+ var DialogPortal = ({
138
+ container,
139
+ ...props
140
+ }) => {
141
+ const resolved = usePortalContainer(container);
142
+ return /* @__PURE__ */ jsx4(DialogPrimitive.Portal, { container: resolved ?? null, ...props });
143
+ };
144
+ DialogPortal.displayName = "DialogPortal";
145
+ var CONTEXT_SLOT3 = /* @__PURE__ */ Symbol.for("ai-matrx.design-system.dialog-container-context");
146
+ var contextRegistry3 = globalThis;
147
+ var DialogContainerContext = contextRegistry3[CONTEXT_SLOT3] ??= React4.createContext(null);
148
+ var DialogOverlay = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
149
+ DialogPrimitive.Overlay,
150
+ {
151
+ ref,
152
+ "data-slot": "dialog-overlay",
153
+ className: cn(
154
+ // Page context behind the modal stays readable; separation comes from a
155
+ // light token scrim, not a blur.
156
+ "fixed inset-0 z-[10000] bg-[var(--matrx-overlay-scrim-soft)]",
157
+ MOTION_OVERLAY,
158
+ className
159
+ ),
160
+ ...props
161
+ }
162
+ ));
163
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
164
+ var DialogContentPrimitive = React4.forwardRef((props, ref) => {
165
+ const isModal = useRadixDialogModal();
166
+ return /* @__PURE__ */ jsx4(
167
+ DialogPrimitive.Content,
168
+ {
169
+ ...props,
170
+ ref,
171
+ "aria-modal": isModal || void 0
172
+ }
173
+ );
174
+ });
175
+ DialogContentPrimitive.displayName = "DialogContentPrimitive";
176
+ var DIALOG_DESKTOP_CLASSES = "matrx-dialog-card fixed inset-0 z-[10000] m-auto grid h-fit min-w-0 w-full max-w-lg gap-4 max-h-[85dvh] overflow-y-auto overscroll-contain [--dialog-pad:1.5rem] overflow-x-clip border bg-background p-6 shadow-lg [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full sm:rounded-lg";
177
+ var DIALOG_MOBILE_SHEET_CLASSES = "matrx-mobile-sheet matrx-dialog-card fixed inset-x-0 bottom-0 left-0 right-0 top-auto z-[10000] flex min-w-0 flex-col w-full max-w-full max-h-[90dvh] gap-4 [--dialog-pad:1rem] overflow-x-clip border-t bg-background p-4 pb-safe shadow-lg [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full rounded-t-2xl rounded-b-none overflow-y-auto overscroll-contain";
178
+ var DIALOG_MOBILE_SHEET_OVERRIDE = "inset-x-0 bottom-0 left-0 right-0 top-auto translate-none w-full max-w-full max-h-[90dvh] rounded-b-none rounded-t-2xl overflow-y-auto";
179
+ var DialogContent = React4.forwardRef(
180
+ ({
181
+ className,
182
+ children,
183
+ mobileSheet = true,
184
+ animated = true,
185
+ showCloseButton = true,
186
+ container,
187
+ ...props
188
+ }, ref) => {
189
+ const isMobile = useIsMobile();
190
+ const isModal = useRadixDialogModal();
191
+ const injectedContainer = usePortalContainer(container);
192
+ const asSheet = mobileSheet && isMobile;
193
+ const [containerEl, setContainerEl] = React4.useState(
194
+ null
195
+ );
196
+ const mergedRef = React4.useCallback(
197
+ (node) => {
198
+ setContainerEl(node);
199
+ if (typeof ref === "function") ref(node);
200
+ else if (ref)
201
+ ref.current = node;
202
+ },
203
+ [ref]
204
+ );
205
+ const hasTitle = treeContainsComponent(children, DialogTitle) || treeContainsComponent(children, DialogPrimitive.Title);
206
+ const hasDescription = treeContainsComponent(children, DialogDescription) || treeContainsComponent(children, DialogPrimitive.Description);
207
+ return /* @__PURE__ */ jsxs2(DialogPortal, { container, children: [
208
+ isModal ? /* @__PURE__ */ jsx4(DialogOverlay, {}) : null,
209
+ /* @__PURE__ */ jsxs2(
210
+ DialogContentPrimitive,
211
+ {
212
+ ref: mergedRef,
213
+ "data-slot": "dialog-content",
214
+ className: cn(
215
+ asSheet ? DIALOG_MOBILE_SHEET_CLASSES : DIALOG_DESKTOP_CLASSES,
216
+ animated && (asSheet ? MOTION_BOTTOM_SHEET : MOTION_DIALOG),
217
+ className,
218
+ asSheet && DIALOG_MOBILE_SHEET_OVERRIDE,
219
+ // A non-modal dialog is the coexistence contract for content that
220
+ // can sit beside a host's floating window manager (which starts at
221
+ // z=1000); staying below that boundary makes a newly focused window
222
+ // usable without coupling the two systems.
223
+ !isModal && "z-[900]"
224
+ ),
225
+ ...hasDescription ? {} : { "aria-describedby": void 0 },
226
+ ...props,
227
+ children: [
228
+ hasTitle ? null : /* @__PURE__ */ jsx4(VisuallyHidden.Root, { children: /* @__PURE__ */ jsx4(DialogPrimitive.Title, { children: "Dialog" }) }),
229
+ /* @__PURE__ */ jsx4(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ jsx4(
230
+ PortalContainerProvider,
231
+ {
232
+ container: containerEl ?? injectedContainer ?? null,
233
+ children
234
+ }
235
+ ) }),
236
+ showCloseButton ? /* @__PURE__ */ jsxs2(
237
+ DialogPrimitive.Close,
238
+ {
239
+ "data-slot": "dialog-close",
240
+ "aria-label": "Close",
241
+ className: "absolute right-2 top-4 flex h-11 w-11 items-center justify-center rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground sm:right-4 lg:h-10 lg:w-10",
242
+ children: [
243
+ /* @__PURE__ */ jsx4(XIcon, { className: "h-4 w-4" }),
244
+ /* @__PURE__ */ jsx4("span", { className: "sr-only", children: "Close" })
245
+ ]
246
+ }
247
+ ) : null
248
+ ]
249
+ }
250
+ )
251
+ ] });
252
+ }
253
+ );
254
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
255
+ var DialogHeader = ({
256
+ className,
257
+ ...props
258
+ }) => /* @__PURE__ */ jsx4(
259
+ "div",
260
+ {
261
+ "data-slot": "dialog-header",
262
+ className: cn(
263
+ // DialogContent owns an absolute close control. Reserve its hit area in
264
+ // every header so trailing actions never render underneath it.
265
+ "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
266
+ className
267
+ ),
268
+ ...props
269
+ }
270
+ );
271
+ DialogHeader.displayName = "DialogHeader";
272
+ var DialogFooter = ({
273
+ className,
274
+ ...props
275
+ }) => /* @__PURE__ */ jsx4(
276
+ "div",
277
+ {
278
+ "data-slot": "dialog-footer",
279
+ className: cn(
280
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
281
+ // Ruling 3/4 — sticky to the card edges so a scrolled body never shows
282
+ // through under the actions, and safe-area padded on a phone.
283
+ //
284
+ // 🚨 THERE IS NO `mb-[…]` HERE, AND THERE MUST NEVER BE ONE AGAIN
285
+ // (0.11.3, feedback bf8fb00b). The bleed over the card's bottom padding
286
+ // used to be a NEGATIVE BLOCK MARGIN, which makes this box taller than
287
+ // the grid area the card allocates it — and `position: sticky` CLAMPS a
288
+ // box to its containing block, so the browser yanked the footer UP by
289
+ // `--dialog-pad` against the card's `gap-4` and painted 8px of opaque
290
+ // footer over the last line of the consequence, at every length. The
291
+ // card now RESERVES this footer's block size instead (`matrx-dialog-card`
292
+ // in styles.css hands over its `padding-bottom`), so the bleed costs no
293
+ // margin at all. Guard: `sticky-footer-geometry.test.tsx`.
294
+ //
295
+ // These stay UTILITIES rather than a shipped rule so a caller's
296
+ // `className` still wins through tailwind-merge — a shipped rule would
297
+ // have to be unlayered to survive Tailwind preflight (§ THE PREFLIGHT
298
+ // RULE in styles.css) and would then override every caller's padding.
299
+ "sticky bottom-0 z-10 bg-background pt-3",
300
+ "mx-[calc(var(--dialog-pad,0px)*-1)] px-[var(--dialog-pad,0px)] pb-[var(--dialog-pad,0px)]",
301
+ className
302
+ ),
303
+ ...props
304
+ }
305
+ );
306
+ DialogFooter.displayName = "DialogFooter";
307
+ var DialogTitle = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
308
+ DialogPrimitive.Title,
309
+ {
310
+ ref,
311
+ "data-slot": "dialog-title",
312
+ className: cn("text-lg font-semibold leading-none tracking-tight", className),
313
+ ...props
314
+ }
315
+ ));
316
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
317
+ var DialogDescription = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
318
+ DialogPrimitive.Description,
319
+ {
320
+ ref,
321
+ "data-slot": "dialog-description",
322
+ className: cn("text-sm text-muted-foreground", className),
323
+ ...props
324
+ }
325
+ ));
326
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
327
+
328
+ // src/data-table/default-host.tsx
329
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
330
+ function DefaultTableWindow({ title, onClose, width = 720, height = 560, actionsRight, sidebar, children }) {
331
+ return /* @__PURE__ */ jsx5(Dialog, { open: true, onOpenChange: (open) => {
332
+ if (!open) onClose();
333
+ }, children: /* @__PURE__ */ jsxs3(DialogContent, { className: "flex flex-col overflow-hidden", style: { width: `min(${width}px, calc(100vw - 2rem))`, height: `min(${height}px, 85dvh)` }, children: [
334
+ /* @__PURE__ */ jsxs3(DialogHeader, { children: [
335
+ /* @__PURE__ */ jsx5(DialogTitle, { children: title }),
336
+ actionsRight
337
+ ] }),
338
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-h-0 flex-1", children: [
339
+ sidebar ? /* @__PURE__ */ jsx5("aside", { className: "shrink-0 border-r pr-2", children: sidebar }) : null,
340
+ /* @__PURE__ */ jsx5("div", { className: "min-h-0 min-w-0 flex-1 overflow-auto", children })
341
+ ] })
342
+ ] }) });
343
+ }
344
+
345
+ // src/data-table/icons.tsx
346
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
347
+ function Icon({ children, ...props }) {
348
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...props, children });
349
+ }
350
+ var Check = (p) => /* @__PURE__ */ jsx6(Icon, { ...p, children: /* @__PURE__ */ jsx6("path", { d: "m5 12 4 4L19 6" }) });
351
+ var SlidersHorizontal = (p) => /* @__PURE__ */ jsx6(Icon, { ...p, children: /* @__PURE__ */ jsx6("path", { d: "M4 7h16M4 17h16M9 7v0M15 17v0" }) });
352
+ var Pencil = (p) => /* @__PURE__ */ jsx6(Icon, { ...p, children: /* @__PURE__ */ jsx6("path", { d: "m4 20 4-1 11-11-3-3L5 16l-1 4z" }) });
353
+ var Eye = (p) => /* @__PURE__ */ jsxs4(Icon, { ...p, children: [
354
+ /* @__PURE__ */ jsx6("path", { d: "M2 12s3-6 10-6 10 6 10 6-3 6-10 6S2 12 2 12z" }),
355
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "2" })
356
+ ] });
357
+ var PanelRight = (p) => /* @__PURE__ */ jsxs4(Icon, { ...p, children: [
358
+ /* @__PURE__ */ jsx6("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
359
+ /* @__PURE__ */ jsx6("path", { d: "M15 4v16" })
360
+ ] });
361
+ var ExternalLink = (p) => /* @__PURE__ */ jsx6(Icon, { ...p, children: /* @__PURE__ */ jsx6("path", { d: "M14 4h6v6M20 4l-9 9M19 13v6H5V5h6" }) });
362
+ var Copy = (p) => /* @__PURE__ */ jsxs4(Icon, { ...p, children: [
363
+ /* @__PURE__ */ jsx6("rect", { x: "9", y: "9", width: "11", height: "11", rx: "2" }),
364
+ /* @__PURE__ */ jsx6("path", { d: "M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3" })
365
+ ] });
366
+ var Lightbulb = (p) => /* @__PURE__ */ jsx6(Icon, { ...p, children: /* @__PURE__ */ jsx6("path", { d: "M9 18h6M10 22h4M8 14a6 6 0 1 1 8 0c-1 1-1 2-1 3H9c0-1 0-2-1-3z" }) });
367
+ var ShieldOff = (p) => /* @__PURE__ */ jsx6(Icon, { ...p, children: /* @__PURE__ */ jsx6("path", { d: "m3 3 18 18M12 3l7 3v5c0 4-3 7-7 9-4-2-7-5-7-9V6l4-2" }) });
368
+
369
+ // src/data-table/host.tsx
370
+ import { createContext as createContext4, useContext as useContext4 } from "react";
371
+ import { jsx as jsx7 } from "react/jsx-runtime";
372
+ var HOST_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.design-system.table-host-context");
373
+ var hostRegistry = globalThis;
374
+ var HostContext = hostRegistry[HOST_SLOT] ??= createContext4({});
375
+ function useMatrxDataTableHost() {
376
+ return useContext4(HostContext);
377
+ }
378
+
379
+ // src/data-table/copy.tsx
380
+ import { useState as useState4 } from "react";
381
+ import { useClipboard } from "@ai-matrx/kit/clipboard";
382
+
383
+ // src/button.tsx
384
+ import { Slot } from "@radix-ui/react-slot";
385
+ import { cva } from "class-variance-authority";
386
+ import * as React5 from "react";
387
+ import { jsx as jsx8 } from "react/jsx-runtime";
388
+ var buttonVariants = cva(
389
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98] [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
390
+ {
391
+ variants: {
392
+ variant: {
393
+ default: "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
394
+ /**
395
+ * An explicit spelling of `default`, identical by construction. It is
396
+ * not decoration: matrx-frontend's `ButtonMine` fork carried it, ~17
397
+ * call sites say `variant="primary"`, and the alternative to keeping
398
+ * the word was a host shim translating a prop — the massage C22 bans.
399
+ */
400
+ primary: "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
401
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
402
+ /** The affirmative twin of `destructive` — confirm, approve, publish. */
403
+ success: "bg-success text-success-foreground shadow-sm hover:bg-success/90",
404
+ outline: "border border-border bg-card shadow-sm hover:bg-accent hover:text-accent-foreground",
405
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
406
+ ghost: "hover:bg-accent hover:text-accent-foreground",
407
+ link: "text-primary underline-offset-4 hover:underline",
408
+ subtle: "border border-transparent bg-muted/50 text-foreground hover:border-border hover:bg-muted"
409
+ },
410
+ /**
411
+ * ONE size scale, xs → 3xl, plus the two icon boxes. The top and bottom
412
+ * of the scale came in from matrx-frontend's `ButtonMine` fork (2026-09-07):
413
+ * a dense toolbar needs `xs`, and a marketing/hero call to action needs
414
+ * `xl`+. A host that has to reach outside this scale forks the button,
415
+ * which is exactly how `ButtonMine` was born.
416
+ */
417
+ size: {
418
+ xs: "h-6 rounded-md px-1 text-xs",
419
+ sm: "h-8 rounded-md px-3 text-xs",
420
+ default: "h-9 px-4 py-2",
421
+ /** An explicit spelling of `default` (the fork's middle rung). */
422
+ md: "h-9 px-4 py-2",
423
+ lg: "h-10 rounded-md px-6",
424
+ xl: "h-12 px-8 py-3 text-base",
425
+ "2xl": "h-14 px-10 py-3 text-base",
426
+ "3xl": "h-16 px-12 py-3 text-base",
427
+ icon: "h-9 w-9",
428
+ "icon-sm": "h-7 w-7",
429
+ /** A circular icon box — an avatar-shaped control, not a square one. */
430
+ roundIcon: "h-8 w-8 rounded-full p-0"
431
+ }
432
+ },
433
+ defaultVariants: {
434
+ variant: "default",
435
+ size: "default"
436
+ }
437
+ }
438
+ );
439
+ var Button = React5.forwardRef(
440
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
441
+ const Component = asChild ? Slot : "button";
442
+ return /* @__PURE__ */ jsx8(
443
+ Component,
444
+ {
445
+ className: cn(buttonVariants({ variant, size, className })),
446
+ ref,
447
+ ...props
448
+ }
449
+ );
450
+ }
451
+ );
452
+ Button.displayName = "Button";
453
+
454
+ // src/popover.tsx
455
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
456
+ import * as React6 from "react";
457
+ import { jsx as jsx9 } from "react/jsx-runtime";
458
+ var Popover = PopoverPrimitive.Root;
459
+ var PopoverTrigger = PopoverPrimitive.Trigger;
460
+ var PopoverContent = React6.forwardRef(
461
+ ({
462
+ className,
463
+ align = "center",
464
+ sideOffset = 4,
465
+ container,
466
+ animated = true,
467
+ ...props
468
+ }, ref) => {
469
+ const portalContainer = usePortalContainer(container);
470
+ return /* @__PURE__ */ jsx9(PopoverPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx9(
471
+ PopoverPrimitive.Content,
472
+ {
473
+ ref,
474
+ align,
475
+ sideOffset,
476
+ className: cn(
477
+ // Cap to the viewport space Radix measured and scroll — a popover taller
478
+ // than the screen must never trap the user with unreachable content.
479
+ // z must EQUAL the dialog layer (z-[10000]), never exceed it: with equal
480
+ // z, DOM portal order decides, so a dialog opened FROM a popover stacks
481
+ // above it, and a popover opened from a dialog still stacks above the
482
+ // dialog. z-[10001] buried dialogs behind fullscreen popovers.
483
+ "z-[10000] w-72 max-h-[var(--radix-popover-content-available-height)] overflow-y-auto overscroll-contain rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
484
+ animated && MOTION_POPPER,
485
+ className
486
+ ),
487
+ ...props
488
+ }
489
+ ) });
490
+ }
491
+ );
492
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
493
+
494
+ // src/data-table/useTableNotice.tsx
495
+ import { useState as useState3 } from "react";
496
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
497
+ function useTableNotice() {
498
+ const host = useMatrxDataTableHost();
499
+ const [message, setMessage] = useState3(null);
500
+ const notify = {
501
+ success(text) {
502
+ if (host.notify) host.notify.success(text);
503
+ else setMessage({ text, error: false });
504
+ },
505
+ error(text) {
506
+ if (host.notify) host.notify.error(text);
507
+ else setMessage({ text, error: true });
508
+ }
509
+ };
510
+ const notice = message ? /* @__PURE__ */ jsxs5("span", { role: message.error ? "alert" : "status", className: message.error ? "text-xs text-destructive" : "text-xs text-muted-foreground", children: [
511
+ message.text,
512
+ /* @__PURE__ */ jsx10("button", { type: "button", "aria-label": "Dismiss notification", className: "ml-2 underline", onClick: () => setMessage(null), children: "Dismiss" })
513
+ ] }) : null;
514
+ return { notify, notice };
515
+ }
516
+
517
+ // src/data-table/copy-payload.ts
518
+ function tableAgentText(value) {
519
+ if (typeof value === "string") return value;
520
+ const attributes = Object.entries(value.attributes ?? {}).filter(([, entry]) => entry !== null && entry !== void 0 && entry !== "").map(([key, entry]) => ` ${key}="${String(entry)}"`).join("");
521
+ const context = Object.entries(value.context ?? {}).filter(([, entry]) => entry !== null && entry !== void 0 && entry !== "").map(([key, entry]) => `<${key}>${String(entry)}</${key}>`).join("\n");
522
+ const url = typeof window === "undefined" ? "" : window.location.href;
523
+ const route = typeof window === "undefined" ? "" : window.location.pathname;
524
+ return `<${value.kind}${attributes}>
525
+ <context>
526
+ <location>${value.location}</location>
527
+ ${url ? `<url>${url}</url>` : ""}
528
+ ${route ? `<route>${route}</route>` : ""}
529
+ <copied>${value.description}</copied>
530
+ <copied-at>${(/* @__PURE__ */ new Date()).toISOString()}</copied-at>
531
+ ${context}
532
+ </context>
533
+ ${value.summary ? `<summary>
534
+ ${value.summary}
535
+ </summary>
536
+ ` : ""}<data format="json">
537
+ \`\`\`json
538
+ ${JSON.stringify(value.data, null, 2)}
539
+ \`\`\`
540
+ </data>
541
+ </${value.kind}>`;
542
+ }
543
+
544
+ // src/data-table/copy.tsx
545
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
546
+ function resolve(value) {
547
+ return typeof value === "function" ? value() : value;
548
+ }
549
+ function download(item) {
550
+ if (!item.build) throw new Error(`No export action is available for ${item.label}.`);
551
+ const file = item.build();
552
+ const url = URL.createObjectURL(new Blob([file.content], { type: file.mime }));
553
+ try {
554
+ const link = document.createElement("a");
555
+ link.href = url;
556
+ link.download = `${item.label.replace(/[^a-z0-9]+/gi, "-")}.${file.extension}`;
557
+ document.body.append(link);
558
+ link.click();
559
+ link.remove();
560
+ } finally {
561
+ URL.revokeObjectURL(url);
562
+ }
563
+ }
564
+ function CopyButtons(props) {
565
+ const { CopyControls } = useMatrxDataTableHost();
566
+ if (CopyControls) return /* @__PURE__ */ jsx11(CopyControls, { ...props });
567
+ return /* @__PURE__ */ jsx11(DefaultCopyButtons, { ...props });
568
+ }
569
+ function DefaultCopyButtons({ human, agent, json, label, size = "icon", appearance = "segmented", disabled = false, stopPropagation = true, className, aiVariants = [], agentVariant, aiCustom, export: exportConfig, hide }) {
570
+ const { notify, notice } = useTableNotice();
571
+ const clipboard = useClipboard({ notify: (message, kind) => notify[kind](message) });
572
+ const [busy, setBusy] = useState4(false);
573
+ const [modal, setModal] = useState4(null);
574
+ const run = async (operation) => {
575
+ if (busy) return;
576
+ setBusy(true);
577
+ try {
578
+ await operation();
579
+ } catch (error) {
580
+ notify.error(error instanceof Error ? error.message : String(error));
581
+ } finally {
582
+ setBusy(false);
583
+ }
584
+ };
585
+ const faithfulAgent = agent === void 0 ? [] : [{
586
+ id: agentVariant?.id ?? "copy-for-ai",
587
+ label: agentVariant?.label ?? "Copy for AI",
588
+ ...agentVariant?.hint === void 0 ? {} : { hint: agentVariant.hint },
589
+ section: "ai",
590
+ build: () => resolve(agent)
591
+ }];
592
+ const orderedAgent = agentVariant?.position === "last" ? [...aiVariants, ...faithfulAgent] : [...faithfulAgent, ...aiVariants];
593
+ const variants = [
594
+ ...json === void 0 ? [] : [{ id: "json", label: "Copy JSON", section: "copy", build: () => JSON.stringify(resolve(json), null, 2) }],
595
+ ...orderedAgent
596
+ ].filter((variant) => !hide?.includes(variant.section ?? "ai"));
597
+ const exports = hide?.includes("export") ? void 0 : exportConfig;
598
+ const showCustom = aiCustom && !hide?.includes("ai");
599
+ const buttonClass = `${size === "xs" ? "h-8 w-8 lg:h-5 lg:w-8" : ""} ${appearance === "bare" ? "border-0 bg-transparent shadow-none" : ""}`;
600
+ return /* @__PURE__ */ jsxs6("span", { className: `inline-flex flex-wrap items-center gap-0.5 ${className ?? ""}`, onClick: stopPropagation ? (event) => event.stopPropagation() : void 0, children: [
601
+ human !== void 0 && !hide?.includes("copy") ? /* @__PURE__ */ jsx11(Button, { type: "button", variant: "ghost", size: "icon", className: buttonClass, disabled: disabled || busy, "aria-label": `Copy ${label}`, onClick: () => void run(() => clipboard.copyText(resolve(human), `${label} copied`)), children: /* @__PURE__ */ jsx11(Copy, { className: "h-3.5 w-3.5" }) }) : null,
602
+ variants.length > 0 || showCustom || exports ? /* @__PURE__ */ jsxs6(Popover, { children: [
603
+ /* @__PURE__ */ jsx11(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx11(Button, { type: "button", variant: "ghost", size: "icon", className: buttonClass, disabled: disabled || busy, "aria-label": `Copy ${label} for AI or export`, children: /* @__PURE__ */ jsx11(SlidersHorizontal, { className: "h-3.5 w-3.5" }) }) }),
604
+ /* @__PURE__ */ jsxs6(PopoverContent, { align: "end", className: "w-64 p-1", children: [
605
+ variants.map((variant) => /* @__PURE__ */ jsx11(Button, { type: "button", variant: "ghost", size: "sm", className: "w-full justify-start", disabled: busy, onClick: () => void run(async () => {
606
+ if (variant.onSelect) variant.onSelect();
607
+ else if (variant.modal) setModal(variant.id);
608
+ else if (variant.build) await clipboard.copyText(tableAgentText(await variant.build()), variant.successMessage ?? `${label} copied`);
609
+ else throw new Error(`No copy action is available for ${variant.label}.`);
610
+ }), children: variant.label }, variant.id)),
611
+ showCustom ? /* @__PURE__ */ jsx11(Button, { type: "button", variant: "ghost", size: "sm", className: "w-full justify-start", onClick: () => setModal("custom"), children: aiCustom.label ?? "Custom copy\u2026" }) : null,
612
+ exports?.items.map((item) => /* @__PURE__ */ jsx11(Button, { type: "button", variant: "ghost", size: "sm", className: "w-full justify-start", disabled: busy, onClick: () => void run(async () => {
613
+ if (item.onSelect) await item.onSelect();
614
+ else download(item);
615
+ }), children: item.label }, item.id)),
616
+ exports?.sheetRows ? /* @__PURE__ */ jsx11(Button, { type: "button", variant: "ghost", size: "sm", className: "w-full justify-start", onClick: () => void run(async () => {
617
+ const rows = exports.sheetRows?.();
618
+ if (!rows) return;
619
+ const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))];
620
+ const value = (input) => String(input ?? "").replace(/[\t\r\n]+/g, " ");
621
+ await clipboard.copyText([keys.map(value).join(" "), ...rows.map((row) => keys.map((key) => value(row[key])).join(" "))].join("\n"), "Copied for pasting into Google Sheets");
622
+ }), children: "Copy for Google Sheets" }) : null
623
+ ] })
624
+ ] }) : null,
625
+ variants.map((variant) => variant.modal && modal === variant.id ? /* @__PURE__ */ jsx11("span", { children: variant.modal({ open: true, onOpenChange: (open) => setModal(open ? variant.id : null) }) }, variant.id) : null),
626
+ modal === "custom" && aiCustom ? /* @__PURE__ */ jsx11(CustomCopy, { source: aiCustom, onClose: () => setModal(null) }) : null,
627
+ notice
628
+ ] });
629
+ }
630
+ function CustomCopy({ source, onClose }) {
631
+ const [values, setValues] = useState4(() => Object.fromEntries(source.options.map((option) => [option.key, option.default])));
632
+ const { notify, notice } = useTableNotice();
633
+ const clipboard = useClipboard({ notify: (message, kind) => notify[kind](message) });
634
+ let text = "";
635
+ let error = null;
636
+ try {
637
+ const built = source.build(values);
638
+ text = tableAgentText(source.wrap(built.text, values, built.meta));
639
+ } catch (raw) {
640
+ error = raw instanceof Error ? raw.message : String(raw);
641
+ }
642
+ return /* @__PURE__ */ jsx11(Dialog, { open: true, onOpenChange: (open) => {
643
+ if (!open) onClose();
644
+ }, children: /* @__PURE__ */ jsxs6(DialogContent, { children: [
645
+ /* @__PURE__ */ jsx11(DialogHeader, { children: /* @__PURE__ */ jsx11(DialogTitle, { children: source.dialogTitle ?? "Custom copy" }) }),
646
+ source.dialogDescription ? /* @__PURE__ */ jsx11("p", { className: "text-sm", children: source.dialogDescription }) : null,
647
+ source.options.map((option) => /* @__PURE__ */ jsxs6("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
648
+ option.label,
649
+ option.kind === "toggle" ? /* @__PURE__ */ jsx11("input", { type: "checkbox", checked: values[option.key] === true, onChange: (e) => setValues({ ...values, [option.key]: e.target.checked }) }) : option.kind === "preset" ? /* @__PURE__ */ jsx11("select", { "aria-label": option.label, value: Number(values[option.key]), onChange: (e) => setValues({ ...values, [option.key]: Number(e.target.value) }), children: option.options.map((choice) => /* @__PURE__ */ jsx11("option", { value: choice.value, children: choice.label }, choice.value)) }) : option.kind === "slider" ? /* @__PURE__ */ jsx11("input", { "aria-label": option.label, type: "range", min: 0, max: option.stops.length - 1, step: 1, value: Math.max(0, option.stops.indexOf(Number(values[option.key]))), onChange: (e) => setValues({ ...values, [option.key]: option.stops[Number(e.target.value)] ?? option.default }) }) : /* @__PURE__ */ jsxs6("span", { className: "flex gap-1", children: [
650
+ option.presets?.map((choice) => /* @__PURE__ */ jsx11(Button, { type: "button", size: "sm", variant: "outline", onClick: () => setValues({ ...values, [option.key]: choice.value }), children: choice.label }, choice.value)),
651
+ /* @__PURE__ */ jsx11("input", { "aria-label": option.label, type: "number", value: Number(values[option.key]), min: option.min, step: option.step, onChange: (e) => {
652
+ if (Number.isFinite(e.target.valueAsNumber)) setValues({ ...values, [option.key]: e.target.valueAsNumber });
653
+ } })
654
+ ] })
655
+ ] }, option.key)),
656
+ error ? /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-destructive", children: error }) : /* @__PURE__ */ jsx11("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap text-xs", children: text }),
657
+ notice,
658
+ /* @__PURE__ */ jsxs6(DialogFooter, { children: [
659
+ /* @__PURE__ */ jsx11(Button, { type: "button", variant: "outline", onClick: onClose, children: "Close" }),
660
+ /* @__PURE__ */ jsx11(Button, { type: "button", disabled: error !== null, onClick: () => void clipboard.copyText(text, "Custom copy copied"), children: "Copy" })
661
+ ] })
662
+ ] }) });
663
+ }
664
+
665
+ // src/data-table/MatrxUuidCell.tsx
666
+ import {
667
+ useEffect as useEffect2,
668
+ useRef,
669
+ useState as useState5
670
+ } from "react";
671
+
672
+ // src/tooltip.tsx
673
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
674
+ import * as React7 from "react";
675
+ import { jsx as jsx12 } from "react/jsx-runtime";
676
+ var TooltipProvider = TooltipPrimitive.Provider;
677
+ var Tooltip = TooltipPrimitive.Root;
678
+ var TooltipTrigger = TooltipPrimitive.Trigger;
679
+ var TooltipContent = React7.forwardRef(({ className, sideOffset = 4, container, animated = true, ...props }, ref) => {
680
+ const resolved = usePortalContainer(container);
681
+ return /* @__PURE__ */ jsx12(TooltipPrimitive.Portal, { container: resolved ?? null, children: /* @__PURE__ */ jsx12(
682
+ TooltipPrimitive.Content,
683
+ {
684
+ ref,
685
+ "data-slot": "tooltip-content",
686
+ sideOffset,
687
+ className: cn(
688
+ "z-[10001] overflow-hidden rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md",
689
+ animated && MOTION_POPPER,
690
+ className
691
+ ),
692
+ ...props
693
+ }
694
+ ) });
695
+ });
696
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
697
+
698
+ // src/data-table/MatrxUuidCell.tsx
699
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
700
+ var SHORT_LEN = 8;
701
+ function shortUuid(id, len = SHORT_LEN) {
702
+ return id.slice(0, len);
703
+ }
704
+ async function copyText(text) {
705
+ try {
706
+ if (navigator.clipboard?.writeText) {
707
+ await navigator.clipboard.writeText(text);
708
+ return true;
709
+ }
710
+ } catch {
711
+ }
712
+ try {
713
+ const ta = document.createElement("textarea");
714
+ ta.value = text;
715
+ ta.style.position = "fixed";
716
+ ta.style.opacity = "0";
717
+ document.body.appendChild(ta);
718
+ ta.select();
719
+ const ok = document.execCommand("copy");
720
+ document.body.removeChild(ta);
721
+ return ok;
722
+ } catch {
723
+ return false;
724
+ }
725
+ }
726
+ function MatrxUuidCell({
727
+ value,
728
+ label,
729
+ onOpen,
730
+ href,
731
+ token,
732
+ forbidden = false,
733
+ className,
734
+ trailing
735
+ }) {
736
+ const { resolveEntityDoors, ResourcePeek } = useMatrxDataTableHost();
737
+ const { notify, notice } = useTableNotice();
738
+ const [copied, setCopied] = useState5(false);
739
+ const [opening, setOpening] = useState5(false);
740
+ const [peekOpen, setPeekOpen] = useState5(false);
741
+ const timer = useRef(null);
742
+ useEffect2(
743
+ () => () => {
744
+ if (timer.current) clearTimeout(timer.current);
745
+ },
746
+ []
747
+ );
748
+ if (!value) {
749
+ return /* @__PURE__ */ jsx13("span", { className: "text-muted-foreground/40", children: "\u2014" });
750
+ }
751
+ const doors = token ? resolveEntityDoors?.(token, value, href) ?? null : null;
752
+ const resolvedHref = doors?.href ?? href ?? null;
753
+ const canPeek = Boolean(doors?.canPeek) && !forbidden;
754
+ const display = shortUuid(value);
755
+ const tip = label ? `${label}: ${value}` : value;
756
+ const stop = (e) => {
757
+ e.stopPropagation();
758
+ };
759
+ const onCopy = (e) => {
760
+ stop(e);
761
+ e.preventDefault();
762
+ void copyText(value).then((ok) => {
763
+ if (!ok) {
764
+ notify?.error("Couldn't copy ID");
765
+ return;
766
+ }
767
+ setCopied(true);
768
+ if (timer.current) clearTimeout(timer.current);
769
+ timer.current = setTimeout(() => setCopied(false), 1500);
770
+ });
771
+ };
772
+ const handleOpen = async (e) => {
773
+ stop(e);
774
+ e.preventDefault();
775
+ if (!onOpen || forbidden || opening) return;
776
+ setOpening(true);
777
+ try {
778
+ const result = await onOpen(value);
779
+ if (result === "forbidden") {
780
+ notify?.error("You don't have access to open this record");
781
+ }
782
+ } catch (err) {
783
+ notify?.error(
784
+ `Couldn't open: ${err instanceof Error ? err.message : String(err)}`
785
+ );
786
+ } finally {
787
+ setOpening(false);
788
+ }
789
+ };
790
+ const idNode = (() => {
791
+ if (forbidden) {
792
+ return /* @__PURE__ */ jsxs7("span", { className: "inline-flex min-w-0 items-center gap-1 font-mono text-xs text-muted-foreground/70 tabular-nums", children: [
793
+ /* @__PURE__ */ jsx13("span", { className: "min-w-0 truncate", children: display }),
794
+ /* @__PURE__ */ jsx13(ShieldOff, { className: "h-3 w-3 shrink-0 opacity-60", "aria-hidden": true })
795
+ ] });
796
+ }
797
+ if (onOpen) {
798
+ return /* @__PURE__ */ jsxs7(
799
+ "button",
800
+ {
801
+ type: "button",
802
+ "aria-label": `Open ${label ?? "record"} ${display}`,
803
+ onClick: (e) => void handleOpen(e),
804
+ disabled: opening,
805
+ className: "inline-flex min-w-0 items-center gap-1 font-mono text-xs text-sky-600 hover:underline dark:text-sky-400 disabled:opacity-60",
806
+ children: [
807
+ /* @__PURE__ */ jsx13("span", { className: "min-w-0 truncate tabular-nums", children: display }),
808
+ /* @__PURE__ */ jsx13(PanelRight, { className: "h-3 w-3 shrink-0 opacity-50" })
809
+ ]
810
+ }
811
+ );
812
+ }
813
+ if (resolvedHref) {
814
+ return /* @__PURE__ */ jsxs7(
815
+ "a",
816
+ {
817
+ href: resolvedHref,
818
+ target: "_blank",
819
+ rel: "noopener noreferrer",
820
+ "aria-label": `Open ${label ?? "record"} ${display} in a new tab`,
821
+ onClick: stop,
822
+ className: "inline-flex min-w-0 items-center gap-1 font-mono text-xs text-sky-600 hover:underline dark:text-sky-400",
823
+ children: [
824
+ /* @__PURE__ */ jsx13("span", { className: "min-w-0 truncate tabular-nums", children: display }),
825
+ /* @__PURE__ */ jsx13(ExternalLink, { className: "h-3 w-3 shrink-0 opacity-50" })
826
+ ]
827
+ }
828
+ );
829
+ }
830
+ return /* @__PURE__ */ jsx13("span", { className: "min-w-0 truncate font-mono text-xs text-muted-foreground tabular-nums select-all", children: display });
831
+ })();
832
+ return /* @__PURE__ */ jsxs7(
833
+ "span",
834
+ {
835
+ "data-matrx-cell-control": true,
836
+ className: cn(
837
+ "inline-flex min-w-0 max-w-full items-center gap-0.5 whitespace-nowrap",
838
+ className
839
+ ),
840
+ onClick: stop,
841
+ children: [
842
+ /* @__PURE__ */ jsx13(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs7(Tooltip, { children: [
843
+ /* @__PURE__ */ jsx13(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx13("span", { className: "inline-flex min-w-0 max-w-full items-center", children: idNode }) }),
844
+ /* @__PURE__ */ jsx13(TooltipContent, { side: "top", className: "max-w-sm break-all font-mono", children: forbidden ? `No access \xB7 ${tip}` : tip })
845
+ ] }) }),
846
+ /* @__PURE__ */ jsx13(
847
+ "button",
848
+ {
849
+ type: "button",
850
+ onClick: onCopy,
851
+ "aria-label": copied ? "Copied" : `Copy ${label ?? "id"}`,
852
+ title: `Copy ${label ?? "id"}: ${value}`,
853
+ className: cn(
854
+ "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground focus:outline-none",
855
+ copied && "text-primary"
856
+ ),
857
+ children: copied ? /* @__PURE__ */ jsx13(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx13(Copy, { className: "h-3 w-3" })
858
+ }
859
+ ),
860
+ canPeek ? /* @__PURE__ */ jsx13(
861
+ "button",
862
+ {
863
+ type: "button",
864
+ onClick: (e) => {
865
+ stop(e);
866
+ setPeekOpen(true);
867
+ },
868
+ "aria-label": `Quick look at ${label ?? "record"}`,
869
+ title: `Quick look at ${label ?? "record"}`,
870
+ className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground focus:outline-none",
871
+ children: /* @__PURE__ */ jsx13(Lightbulb, { className: "h-3 w-3" })
872
+ }
873
+ ) : null,
874
+ onOpen && resolvedHref && !forbidden ? /* @__PURE__ */ jsx13(
875
+ "a",
876
+ {
877
+ href: resolvedHref,
878
+ target: "_blank",
879
+ rel: "noopener noreferrer",
880
+ onClick: stop,
881
+ "aria-label": `Open ${label ?? "record"} in a new tab`,
882
+ title: `Open ${label ?? "record"} in a new tab`,
883
+ className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground focus:outline-none",
884
+ children: /* @__PURE__ */ jsx13(ExternalLink, { className: "h-3 w-3" })
885
+ }
886
+ ) : null,
887
+ trailing,
888
+ notice,
889
+ canPeek && peekOpen && ResourcePeek ? /* @__PURE__ */ jsx13(
890
+ ResourcePeek,
891
+ {
892
+ kind: doors?.peekKind ?? token ?? "record",
893
+ id: value,
894
+ onClose: () => setPeekOpen(false),
895
+ ...href == null ? {} : { href }
896
+ }
897
+ ) : null
898
+ ]
899
+ }
900
+ );
901
+ }
902
+
903
+ // src/data-table/entity.ts
904
+ function isUuidValue(value) {
905
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
906
+ }
907
+
908
+ // src/data-table/DataRowInspector.tsx
909
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
910
+ var noFieldToken = () => null;
911
+ function formatValue(value) {
912
+ if (value == null) return "\u2014";
913
+ if (typeof value === "string") return value || "\u2014";
914
+ if (typeof value === "number" || typeof value === "boolean") {
915
+ return String(value);
916
+ }
917
+ if (value instanceof Date) return value.toISOString();
918
+ try {
919
+ return JSON.stringify(value, null, 2);
920
+ } catch {
921
+ return String(value);
922
+ }
923
+ }
924
+ function isPlainObject(value) {
925
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
926
+ }
927
+ function DataRowInspector({
928
+ row,
929
+ title,
930
+ className,
931
+ fieldCopy = true,
932
+ recordKind = "record",
933
+ recordLabel,
934
+ location,
935
+ tokenForField = noFieldToken
936
+ }) {
937
+ const entries = isPlainObject(row) ? Object.entries(row) : [["value", row]];
938
+ const fieldButtons = (key, value) => fieldCopy ? /* @__PURE__ */ jsx14("div", { className: "absolute right-1 top-1 opacity-0 transition-opacity focus-within:opacity-100 group-hover/field:opacity-100", children: /* @__PURE__ */ jsx14(
939
+ CopyButtons,
940
+ {
941
+ size: "xs",
942
+ label: recordLabel ? `${recordLabel} \xB7 ${key}` : key,
943
+ human: () => formatValue(value),
944
+ agent: () => ({
945
+ kind: `${recordKind}-field`,
946
+ location: location ?? "Data row inspector",
947
+ description: `The "${key}" field of one ${recordLabel ?? recordKind}.`,
948
+ data: { field: key, value, record: row },
949
+ attributes: { field: key }
950
+ })
951
+ }
952
+ ) }) : null;
953
+ return /* @__PURE__ */ jsxs8(
954
+ "div",
955
+ {
956
+ className: cn("flex h-full min-h-0 flex-col overflow-hidden", className),
957
+ children: [
958
+ title ? /* @__PURE__ */ jsx14("div", { className: "shrink-0 border-b border-border px-3 py-2 text-sm font-medium", children: title }) : null,
959
+ /* @__PURE__ */ jsx14("div", { className: "min-h-0 flex-1 overflow-auto p-3", children: /* @__PURE__ */ jsx14("dl", { className: "space-y-2", children: entries.map(([key, value]) => {
960
+ if (isUuidValue(value)) {
961
+ return /* @__PURE__ */ jsxs8(
962
+ "div",
963
+ {
964
+ className: "group/field relative grid grid-cols-[minmax(7rem,9rem)_1fr] gap-2 rounded-md border border-border/60 bg-muted/20 px-2.5 py-2",
965
+ children: [
966
+ /* @__PURE__ */ jsx14("dt", { className: "truncate text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: key }),
967
+ /* @__PURE__ */ jsx14("dd", { className: "min-w-0", children: /* @__PURE__ */ jsx14(
968
+ MatrxUuidCell,
969
+ {
970
+ value,
971
+ label: key,
972
+ token: tokenForField(key, row)
973
+ }
974
+ ) })
975
+ ]
976
+ },
977
+ key
978
+ );
979
+ }
980
+ const formatted = formatValue(value);
981
+ const multiline = formatted.includes("\n") || formatted.length > 80;
982
+ return /* @__PURE__ */ jsxs8(
983
+ "div",
984
+ {
985
+ className: "group/field relative grid grid-cols-[minmax(7rem,9rem)_1fr] gap-2 rounded-md border border-border/60 bg-muted/20 py-2 pl-2.5 pr-12",
986
+ children: [
987
+ /* @__PURE__ */ jsx14("dt", { className: "truncate text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: key }),
988
+ /* @__PURE__ */ jsx14(
989
+ "dd",
990
+ {
991
+ className: cn(
992
+ "min-w-0 text-sm text-foreground break-words",
993
+ multiline && "whitespace-pre-wrap font-mono text-xs leading-relaxed"
994
+ ),
995
+ children: formatted
996
+ }
997
+ ),
998
+ fieldButtons(key, value)
999
+ ]
1000
+ },
1001
+ key
1002
+ );
1003
+ }) }) })
1004
+ ]
1005
+ }
1006
+ );
1007
+ }
1008
+
1009
+ // src/data-table/DataRowWindow.tsx
1010
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1011
+ function DataRowWindow({
1012
+ isOpen,
1013
+ onClose,
1014
+ title = "Row details",
1015
+ row,
1016
+ children,
1017
+ viewContent,
1018
+ editContent,
1019
+ defaultTab,
1020
+ headerActions,
1021
+ width = 720,
1022
+ height = 560,
1023
+ windowId = "matrx-data-row-window"
1024
+ }) {
1025
+ const host = useMatrxDataTableHost();
1026
+ const WindowPanel = host.WindowPanel ?? DefaultTableWindow;
1027
+ const hasEdit = editContent != null;
1028
+ const hasTabs = hasEdit && children == null;
1029
+ const initialTab = defaultTab ?? (hasEdit ? "edit" : "view");
1030
+ const [tab, setTab] = useState6(initialTab);
1031
+ useEffect3(() => {
1032
+ if (!isOpen) return;
1033
+ setTab(defaultTab ?? (hasEdit ? "edit" : "view"));
1034
+ }, [isOpen, windowId, hasEdit, defaultTab]);
1035
+ if (!isOpen) return null;
1036
+ const viewBody = viewContent ?? /* @__PURE__ */ jsx15(DataRowInspector, { row });
1037
+ const body = children ?? /* @__PURE__ */ jsx15("div", { className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden", children: hasTabs ? tab === "edit" ? /* @__PURE__ */ jsx15("div", { className: "min-h-0 flex-1 overflow-y-auto", children: editContent }) : /* @__PURE__ */ jsx15("div", { className: "min-h-0 flex-1 overflow-y-auto", children: viewBody }) : /* @__PURE__ */ jsx15("div", { className: "min-h-0 flex-1 overflow-y-auto", children: viewBody }) });
1038
+ return /* @__PURE__ */ jsx15(
1039
+ WindowPanel,
1040
+ {
1041
+ id: windowId,
1042
+ title,
1043
+ onClose,
1044
+ width,
1045
+ height,
1046
+ minWidth: 420,
1047
+ minHeight: 320,
1048
+ actionsRight: headerActions,
1049
+ bodyClassName: "flex min-h-0 flex-1 flex-col overflow-hidden p-0",
1050
+ sidebarDefaultSize: 140,
1051
+ sidebarMinSize: 110,
1052
+ sidebar: hasTabs ? /* @__PURE__ */ jsxs9(
1053
+ "nav",
1054
+ {
1055
+ "aria-label": "Row window tabs",
1056
+ className: "flex h-full flex-col gap-0.5 overflow-y-auto p-1.5",
1057
+ children: [
1058
+ /* @__PURE__ */ jsx15(
1059
+ TabButton,
1060
+ {
1061
+ active: tab === "view",
1062
+ icon: /* @__PURE__ */ jsx15(Eye, { className: "h-4 w-4 shrink-0" }),
1063
+ label: "View",
1064
+ onClick: () => setTab("view")
1065
+ }
1066
+ ),
1067
+ /* @__PURE__ */ jsx15(
1068
+ TabButton,
1069
+ {
1070
+ active: tab === "edit",
1071
+ icon: /* @__PURE__ */ jsx15(Pencil, { className: "h-4 w-4 shrink-0" }),
1072
+ label: "Edit",
1073
+ onClick: () => setTab("edit")
1074
+ }
1075
+ )
1076
+ ]
1077
+ }
1078
+ ) : void 0,
1079
+ children: body
1080
+ }
1081
+ );
1082
+ }
1083
+ function TabButton({
1084
+ active,
1085
+ icon,
1086
+ label,
1087
+ onClick
1088
+ }) {
1089
+ return /* @__PURE__ */ jsxs9(
1090
+ "button",
1091
+ {
1092
+ type: "button",
1093
+ role: "tab",
1094
+ "aria-selected": active,
1095
+ onClick,
1096
+ className: cn(
1097
+ "flex items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm transition-colors",
1098
+ active ? "bg-accent font-medium text-foreground" : "text-muted-foreground hover:bg-muted/60 hover:text-foreground"
1099
+ ),
1100
+ children: [
1101
+ icon,
1102
+ /* @__PURE__ */ jsx15("span", { className: "min-w-0 flex-1 truncate", children: label })
1103
+ ]
1104
+ }
1105
+ );
1106
+ }
1107
+ export {
1108
+ DataRowWindow
1109
+ };
1110
+ //# sourceMappingURL=data-row-window.js.map