@ai-matrx/design-system 0.14.2 → 0.15.1

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