@ai-matrx/design-system 0.86.356 → 0.86.358
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/auth-gate-dialog.js +2 -2
- package/dist/carousel.js +11 -10
- package/dist/carousel.js.map +1 -1
- package/dist/{chunk-UWTODOMD.js → chunk-2BQAHLU3.js} +4 -4
- package/dist/{chunk-T4D7WAV7.js → chunk-42GFQMLA.js} +2 -2
- package/dist/chunk-5Z5GTXKL.js +79 -0
- package/dist/chunk-5Z5GTXKL.js.map +1 -0
- package/dist/{chunk-POJ3RR3K.js → chunk-B5D3VIMC.js} +1 -1
- package/dist/{chunk-POJ3RR3K.js.map → chunk-B5D3VIMC.js.map} +1 -1
- package/dist/{chunk-4WSXAP24.js → chunk-BB5LA7FQ.js} +3 -1
- package/dist/chunk-BB5LA7FQ.js.map +1 -0
- package/dist/{chunk-FRJ4PPA2.js → chunk-CZGZV35G.js} +2 -2
- package/dist/{chunk-RLM4RSED.js → chunk-GUGBVQ4T.js} +6 -4
- package/dist/{chunk-RLM4RSED.js.map → chunk-GUGBVQ4T.js.map} +1 -1
- package/dist/{chunk-LJKF267O.js → chunk-HS6GMHKZ.js} +18 -76
- package/dist/chunk-HS6GMHKZ.js.map +1 -0
- package/dist/{chunk-2PO3N42Q.js → chunk-K3XWUACX.js} +5 -5
- package/dist/{chunk-ZMUPFGQO.js → chunk-K5ZQROWN.js} +5 -5
- package/dist/{chunk-U46XUQI7.js → chunk-KSCSH7AO.js} +16 -14
- package/dist/chunk-KSCSH7AO.js.map +1 -0
- package/dist/{chunk-MTE6N7XU.js → chunk-NFJ63LIH.js} +2 -2
- package/dist/{chunk-CFUXEGW2.js → chunk-OUQT72UL.js} +4 -4
- package/dist/{chunk-WUD23YTG.js → chunk-P56Y43RL.js} +6 -6
- package/dist/{chunk-GMNROZQJ.js → chunk-QTPW44IJ.js} +2 -2
- package/dist/{chunk-QA7EHFRX.js → chunk-T3FKFPCW.js} +5 -5
- package/dist/{chunk-X6ZRHJQP.js → chunk-T57DLONB.js} +20 -11
- package/dist/chunk-T57DLONB.js.map +1 -0
- package/dist/{chunk-SOCNWQMX.js → chunk-TEMAM5ET.js} +3 -3
- package/dist/{chunk-CH2NWSES.js → chunk-TIV6M7Q7.js} +3 -3
- package/dist/{chunk-6XNYYROM.js → chunk-WVEDW5VO.js} +2 -2
- package/dist/clipboard-fallback-dialog.js +4 -4
- package/dist/controls.cjs +34 -13
- package/dist/controls.cjs.map +1 -1
- package/dist/controls.js +9 -8
- package/dist/data-table/data-row-inspector.cjs +99 -11
- package/dist/data-table/data-row-inspector.cjs.map +1 -1
- package/dist/data-table/data-row-inspector.js +8 -7
- package/dist/data-table/data-row-window.cjs +99 -11
- package/dist/data-table/data-row-window.cjs.map +1 -1
- package/dist/data-table/data-row-window.js +11 -10
- package/dist/data-table/drill-chart.cjs +15 -3
- package/dist/data-table/drill-chart.cjs.map +1 -1
- package/dist/data-table/drill-chart.js +2 -1
- package/dist/data-table/drill-chart.js.map +1 -1
- package/dist/data-table/host.cjs.map +1 -1
- package/dist/data-table/host.d.cts +6 -2
- package/dist/data-table/host.d.ts +6 -2
- package/dist/data-table/host.js +1 -1
- package/dist/data-table/index.cjs +54 -40
- package/dist/data-table/index.cjs.map +1 -1
- package/dist/data-table/index.d.cts +6 -2
- package/dist/data-table/index.d.ts +6 -2
- package/dist/data-table/index.js +25 -28
- package/dist/data-table/index.js.map +1 -1
- package/dist/data-table/pagination.cjs +19 -10
- package/dist/data-table/pagination.cjs.map +1 -1
- package/dist/data-table/pagination.js +4 -4
- package/dist/data-table/uuid-cell.cjs +99 -11
- package/dist/data-table/uuid-cell.cjs.map +1 -1
- package/dist/data-table/uuid-cell.js +5 -4
- package/dist/desktop.cjs +45 -15
- package/dist/desktop.cjs.map +1 -1
- package/dist/desktop.js +23 -15
- package/dist/desktop.js.map +1 -1
- package/dist/email-input-dialog.cjs +15 -3
- package/dist/email-input-dialog.cjs.map +1 -1
- package/dist/email-input-dialog.js +4 -3
- package/dist/email-input-dialog.js.map +1 -1
- package/dist/error-sentence.cjs +130 -0
- package/dist/error-sentence.cjs.map +1 -0
- package/dist/error-sentence.d.cts +49 -0
- package/dist/error-sentence.d.ts +49 -0
- package/dist/error-sentence.js +22 -0
- package/dist/error-sentence.js.map +1 -0
- package/dist/index.cjs +47 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -10
- package/dist/index.d.ts +12 -10
- package/dist/index.js +30 -28
- package/dist/index.js.map +1 -1
- package/dist/item.cjs +19 -10
- package/dist/item.cjs.map +1 -1
- package/dist/item.js +7 -7
- package/dist/lite-table.cjs +15 -3
- package/dist/lite-table.cjs.map +1 -1
- package/dist/lite-table.js +5 -4
- package/dist/lite-table.js.map +1 -1
- package/dist/menu-look.cjs +19 -10
- package/dist/menu-look.cjs.map +1 -1
- package/dist/menu-look.js +2 -2
- package/dist/notice-stack.cjs +2 -0
- package/dist/notice-stack.cjs.map +1 -1
- package/dist/notice-stack.d.cts +11 -1
- package/dist/notice-stack.d.ts +11 -1
- package/dist/notice-stack.js +3 -1
- package/dist/record-list.cjs +34 -13
- package/dist/record-list.cjs.map +1 -1
- package/dist/record-list.js +8 -7
- package/dist/record-list.js.map +1 -1
- package/dist/sandbox-gate-dialog.js +2 -2
- package/dist/scope-mismatch-dialog.js +2 -2
- package/dist/settings.js +3 -3
- package/dist/swipe-deck.cjs +15 -3
- package/dist/swipe-deck.cjs.map +1 -1
- package/dist/swipe-deck.js +2 -1
- package/dist/swipe-deck.js.map +1 -1
- package/dist/text-input-dialog.cjs +15 -3
- package/dist/text-input-dialog.cjs.map +1 -1
- package/dist/text-input-dialog.js +4 -3
- package/dist/text-input-dialog.js.map +1 -1
- package/dist/value-prompts-dialog.js +2 -2
- package/dist/writing-field.js +5 -5
- package/package.json +12 -2
- package/dist/chunk-4WSXAP24.js.map +0 -1
- package/dist/chunk-LJKF267O.js.map +0 -1
- package/dist/chunk-U46XUQI7.js.map +0 -1
- package/dist/chunk-X6ZRHJQP.js.map +0 -1
- /package/dist/{chunk-UWTODOMD.js.map → chunk-2BQAHLU3.js.map} +0 -0
- /package/dist/{chunk-T4D7WAV7.js.map → chunk-42GFQMLA.js.map} +0 -0
- /package/dist/{chunk-FRJ4PPA2.js.map → chunk-CZGZV35G.js.map} +0 -0
- /package/dist/{chunk-2PO3N42Q.js.map → chunk-K3XWUACX.js.map} +0 -0
- /package/dist/{chunk-ZMUPFGQO.js.map → chunk-K5ZQROWN.js.map} +0 -0
- /package/dist/{chunk-MTE6N7XU.js.map → chunk-NFJ63LIH.js.map} +0 -0
- /package/dist/{chunk-CFUXEGW2.js.map → chunk-OUQT72UL.js.map} +0 -0
- /package/dist/{chunk-WUD23YTG.js.map → chunk-P56Y43RL.js.map} +0 -0
- /package/dist/{chunk-GMNROZQJ.js.map → chunk-QTPW44IJ.js.map} +0 -0
- /package/dist/{chunk-QA7EHFRX.js.map → chunk-T3FKFPCW.js.map} +0 -0
- /package/dist/{chunk-SOCNWQMX.js.map → chunk-TEMAM5ET.js.map} +0 -0
- /package/dist/{chunk-CH2NWSES.js.map → chunk-TIV6M7Q7.js.map} +0 -0
- /package/dist/{chunk-6XNYYROM.js.map → chunk-WVEDW5VO.js.map} +0 -0
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./chunk-K4XCB576.js";
|
|
16
16
|
import {
|
|
17
17
|
ErrorActions
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-5Z5GTXKL.js";
|
|
19
19
|
import "./chunk-TDMVH77G.js";
|
|
20
20
|
import {
|
|
21
21
|
Dialog,
|
|
@@ -24,16 +24,17 @@ import {
|
|
|
24
24
|
DialogFooter,
|
|
25
25
|
DialogHeader,
|
|
26
26
|
DialogTitle
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-2BQAHLU3.js";
|
|
28
|
+
import "./chunk-LEPD6TV7.js";
|
|
28
29
|
import "./chunk-IMQ3RX35.js";
|
|
29
30
|
import {
|
|
30
31
|
useIsMobile
|
|
31
32
|
} from "./chunk-U24MOJWK.js";
|
|
32
33
|
import "./chunk-NDTDBBMM.js";
|
|
33
|
-
import "./chunk-LEPD6TV7.js";
|
|
34
34
|
import "./chunk-BD77DHUO.js";
|
|
35
35
|
import "./chunk-BKCJUFD3.js";
|
|
36
36
|
import "./chunk-MOZRIHJF.js";
|
|
37
|
+
import "./chunk-HS6GMHKZ.js";
|
|
37
38
|
import "./chunk-WFITMVDB.js";
|
|
38
39
|
import "./chunk-WAT2XW7L.js";
|
|
39
40
|
import {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/text-input-dialog.tsx","../src/opener-reclick.ts"],"sourcesContent":["/**\n * TextInputDialog — moved from matrx-frontend components/dialogs/text-input\n * (chat host slots get real package defaults, 2026-10-08).\n *\n * Drop-in replacement for `window.prompt`. Captures a single string of\n * input from the user. Renders a `<Drawer>` (bottom sheet) on mobile\n * and a `<Dialog>` on desktop, per the matrx-admin layout standard\n * (see CLAUDE.md \"Mobile Layout\" — never Dialog on mobile).\n *\n * Local-state component — no global host. Use it like any controlled\n * dialog: hold `open` and `value` in your component, render once.\n *\n * @example\n * const [open, setOpen] = useState(false);\n * const [busy, setBusy] = useState(false);\n *\n * <TextInputDialog\n * open={open}\n * onOpenChange={(o) => !busy && setOpen(o)}\n * title=\"New folder\"\n * placeholder=\"Folder name\"\n * confirmLabel=\"Create\"\n * busy={busy}\n * onConfirm={async (name) => {\n * setBusy(true);\n * try { await createFolder(name); setOpen(false); }\n * finally { setBusy(false); }\n * }}\n * />\n */\n\n\"use client\";\n\nimport * as React from \"react\";\nimport { Loader2Icon as Loader2 } from \"./icons\";\n\nimport { cn } from \"./cn\";\nimport { Button } from \"./button\";\nimport { Input } from \"./controls/doors\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"./dialog\";\nimport {\n Drawer,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n} from \"./drawer\";\nimport { useIsMobile } from \"./use-is-mobile\";\nimport { useWritingBox } from \"./writing-box\";\nimport { isPointOnOpener } from \"./opener-reclick\";\nimport { ErrorActions } from \"./error-box\";\n\nexport interface TextInputDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n title: React.ReactNode;\n description?: React.ReactNode;\n placeholder?: string;\n defaultValue?: string;\n /** Use a textarea instead of a single-line input (for longer descriptions). */\n multiline?: boolean;\n /** Visible rows when `multiline` is true. Default 5. */\n rows?: number;\n confirmLabel?: string;\n cancelLabel?: string;\n /** When true, both buttons are disabled and a spinner shows on confirm. */\n busy?: boolean;\n /**\n * Optional sync validator. Return an error message to display, or\n * null/empty to accept. Empty/whitespace-only input is rejected by\n * default (treated as \"Required\").\n */\n validate?: (value: string) => string | null | undefined;\n /**\n * When true, an empty value is a valid answer: Confirm is enabled and submits \"\".\n * For an \"(optional)\" note. Default false (empty is \"Required\").\n */\n allowEmpty?: boolean;\n onConfirm: (value: string) => void | Promise<void>;\n}\n\nexport function TextInputDialog({\n open,\n onOpenChange,\n title,\n description,\n placeholder,\n defaultValue = \"\",\n multiline = false,\n rows = 5,\n confirmLabel = \"Confirm\",\n cancelLabel = \"Cancel\",\n busy = false,\n validate,\n allowEmpty = false,\n onConfirm,\n}: TextInputDialogProps) {\n const isMobile = useIsMobile();\n const ProTextarea = useWritingBox();\n const [value, setValue] = React.useState(defaultValue);\n const [error, setError] = React.useState<string | null>(null);\n\n /**\n * THE DEFECT THIS CLOSES (live on www.aimatrx.com, 2026-09-22). Every\n * caller of this dialog opens it from a toolbar button that stays on\n * screen, dimmed but perfectly readable, UNDER the dialog's own full-screen\n * scrim. Clicking that button again is therefore an \"interact outside\" and\n * DISMISSES the dialog. A person who does not notice the dialog and clicks\n * the button again — the most natural thing in the world — closes it; an\n * even number of clicks leaves the page looking untouched. That is exactly\n * how the battle page's \"Save as…\" was reported dead: four clicks, no\n * dialog, no toast, no overlay (verified click-by-click on the live site —\n * click 2 closed it, click 3 reopened it).\n *\n * So the element that OPENED this dialog is never an outside click. It is\n * remembered at open time (it is the focused element the instant before\n * Radix moves focus into the dialog) and a pointer-down whose point lands\n * on it is swallowed: the dialog stays open and focus returns to the field.\n * Every other outside click still dismisses, as it should.\n */\n const openerRef = React.useRef<HTMLElement | null>(null);\n const lastPointerTargetRef = React.useRef<HTMLElement | null>(null);\n\n // The opener cannot be read as `document.activeElement` once the dialog is\n // open — Radix has already pulled focus into the field — so the last\n // pointer-down target is remembered continuously and frozen the moment the\n // dialog opens.\n React.useEffect(() => {\n const remember = (event: Event) => {\n const target = event.target;\n lastPointerTargetRef.current =\n target instanceof HTMLElement ? target.closest(\"button\") ?? target : null;\n };\n document.addEventListener(\"pointerdown\", remember, true);\n return () => document.removeEventListener(\"pointerdown\", remember, true);\n }, []);\n\n React.useEffect(() => {\n if (open) openerRef.current = lastPointerTargetRef.current;\n }, [open]);\n\n const pointIsOnOpener = React.useCallback(\n (event: Event) =>\n isPointOnOpener(openerRef.current, event as unknown as MouseEvent),\n [],\n );\n\n // Reset state on every open. Keeps the component pure — opening it\n // twice in a row doesn't keep stale text from the prior session.\n React.useEffect(() => {\n if (open) {\n setValue(defaultValue);\n setError(null);\n }\n }, [open, defaultValue]);\n\n const submit = React.useCallback(() => {\n const trimmed = value.trim();\n if (!trimmed && !allowEmpty) {\n setError(\"Required\");\n return;\n }\n if (validate) {\n const err = validate(trimmed);\n if (err) {\n setError(err);\n return;\n }\n }\n void onConfirm(trimmed);\n }, [value, validate, allowEmpty, onConfirm]);\n\n const handleFormSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n if (busy) return;\n submit();\n };\n\n const body = (\n <form onSubmit={handleFormSubmit} className=\"space-y-2\">\n {multiline ? (\n <ProTextarea\n autoFocus\n onFocus={(event) => event.currentTarget.select()}\n value={value}\n onChange={(event) => {\n setValue(event.target.value);\n if (error) setError(null);\n }}\n placeholder={placeholder}\n disabled={busy}\n rows={rows}\n className={cn(\n \"min-h-[8rem] resize-y text-base\",\n error && \"border-destructive\",\n )}\n aria-invalid={!!error}\n aria-describedby={error ? \"text-input-dialog-error\" : undefined}\n />\n ) : (\n <Input\n autoFocus\n onFocus={(event) => event.currentTarget.select()}\n value={value}\n onChange={(event) => {\n setValue(event.target.value);\n if (error) setError(null);\n }}\n placeholder={placeholder}\n disabled={busy}\n // text-base = 16px, prevents iOS Safari zoom-on-focus\n \n aria-invalid={!!error}\n aria-describedby={error ? \"text-input-dialog-error\" : undefined}\n />\n )}\n {error ? (\n <p id=\"text-input-dialog-error\" className=\"text-sm text-destructive\">\n {error}\n <ErrorActions message={error} operation=\"Enter a value\" source=\"inline\" origin=\"design-system/TextInputDialog\" />\n </p>\n ) : null}\n {/* Hidden submit so Enter key submits the form. */}\n <button type=\"submit\" className=\"hidden\" tabIndex={-1} aria-hidden />\n </form>\n );\n\n const buttons = (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={busy}\n >\n {cancelLabel}\n </Button>\n <Button type=\"button\" onClick={submit} disabled={busy || (!allowEmpty && !value.trim())}>\n {busy ? <Loader2 className=\"mr-2 h-4 w-4 matrx-spin\" /> : null}\n {confirmLabel}\n </Button>\n </>\n );\n\n if (isMobile) {\n return (\n <Drawer open={open} onOpenChange={onOpenChange}>\n <DrawerContent>\n <DrawerHeader>\n <DrawerTitle>{title}</DrawerTitle>\n {description ? (\n <DrawerDescription>{description}</DrawerDescription>\n ) : null}\n </DrawerHeader>\n <div className=\"px-4 pb-2\">{body}</div>\n <DrawerFooter className=\"flex-row justify-end gap-2\">\n {buttons}\n </DrawerFooter>\n </DrawerContent>\n </Drawer>\n );\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n className=\"sm:max-w-md\"\n onPointerDownOutside={(event) => {\n if (pointIsOnOpener(event.detail.originalEvent)) {\n event.preventDefault();\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n {description ? (\n <DialogDescription>{description}</DialogDescription>\n ) : null}\n </DialogHeader>\n {body}\n <DialogFooter>{buttons}</DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","/**\n * \"Is this outside-click actually a re-click of the button that opened me?\"\n *\n * THE DEFECT THIS CLOSES (live on www.aimatrx.com, 2026-09-22). A dialog's\n * trigger stays on screen, readable, UNDER the dialog's own full-screen\n * scrim. Clicking it again is an outside interaction, so it DISMISSES what it\n * opened. A person who does not notice the dialog and clicks the button again\n * closes it, and an even number of clicks leaves the page looking untouched:\n * the battle page's \"Save as…\" was reported dead after four clicks — no\n * dialog, no toast, no overlay — and click-by-click on the live site the\n * dialog count went 1 → 0 → 1.\n *\n * WHY GEOMETRY AND NOT A HIT TEST. The scrim swallows the event, so the\n * event's TARGET is the scrim, never the trigger — `contains(target)` is\n * always false. `document.elementsFromPoint` is no better: a modal Radix\n * dialog puts `pointer-events: none` on the body, and a hit test skips\n * elements that cannot be hit, so the trigger drops out of the stack\n * unpredictably (observed live: two re-clicks held, the third dismissed).\n * The opener's own rectangle is the one thing that is always true.\n */\nexport function isPointOnOpener(\n opener: HTMLElement | null,\n event: { clientX?: number; clientY?: number },\n): boolean {\n if (!opener || !opener.isConnected) return false;\n const { clientX, clientY } = event;\n if (typeof clientX !== \"number\" || typeof clientY !== \"number\") return false;\n // A keyboard \"click\" reports 0,0; never treat that as a re-click.\n if (clientX === 0 && clientY === 0) return false;\n const rect = opener.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return (\n clientX >= rect.left &&\n clientX <= rect.right &&\n clientY >= rect.top &&\n clientY <= rect.bottom\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/text-input-dialog.tsx","../src/opener-reclick.ts"],"sourcesContent":["/**\n * TextInputDialog — moved from matrx-frontend components/dialogs/text-input\n * (chat host slots get real package defaults, 2026-10-08).\n *\n * Drop-in replacement for `window.prompt`. Captures a single string of\n * input from the user. Renders a `<Drawer>` (bottom sheet) on mobile\n * and a `<Dialog>` on desktop, per the matrx-admin layout standard\n * (see CLAUDE.md \"Mobile Layout\" — never Dialog on mobile).\n *\n * Local-state component — no global host. Use it like any controlled\n * dialog: hold `open` and `value` in your component, render once.\n *\n * @example\n * const [open, setOpen] = useState(false);\n * const [busy, setBusy] = useState(false);\n *\n * <TextInputDialog\n * open={open}\n * onOpenChange={(o) => !busy && setOpen(o)}\n * title=\"New folder\"\n * placeholder=\"Folder name\"\n * confirmLabel=\"Create\"\n * busy={busy}\n * onConfirm={async (name) => {\n * setBusy(true);\n * try { await createFolder(name); setOpen(false); }\n * finally { setBusy(false); }\n * }}\n * />\n */\n\n\"use client\";\n\nimport * as React from \"react\";\nimport { Loader2Icon as Loader2 } from \"./icons\";\n\nimport { cn } from \"./cn\";\nimport { Button } from \"./button\";\nimport { Input } from \"./controls/doors\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"./dialog\";\nimport {\n Drawer,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n} from \"./drawer\";\nimport { useIsMobile } from \"./use-is-mobile\";\nimport { useWritingBox } from \"./writing-box\";\nimport { isPointOnOpener } from \"./opener-reclick\";\nimport { ErrorActions } from \"./error-box\";\n\nexport interface TextInputDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n title: React.ReactNode;\n description?: React.ReactNode;\n placeholder?: string;\n defaultValue?: string;\n /** Use a textarea instead of a single-line input (for longer descriptions). */\n multiline?: boolean;\n /** Visible rows when `multiline` is true. Default 5. */\n rows?: number;\n confirmLabel?: string;\n cancelLabel?: string;\n /** When true, both buttons are disabled and a spinner shows on confirm. */\n busy?: boolean;\n /**\n * Optional sync validator. Return an error message to display, or\n * null/empty to accept. Empty/whitespace-only input is rejected by\n * default (treated as \"Required\").\n */\n validate?: (value: string) => string | null | undefined;\n /**\n * When true, an empty value is a valid answer: Confirm is enabled and submits \"\".\n * For an \"(optional)\" note. Default false (empty is \"Required\").\n */\n allowEmpty?: boolean;\n onConfirm: (value: string) => void | Promise<void>;\n}\n\nexport function TextInputDialog({\n open,\n onOpenChange,\n title,\n description,\n placeholder,\n defaultValue = \"\",\n multiline = false,\n rows = 5,\n confirmLabel = \"Confirm\",\n cancelLabel = \"Cancel\",\n busy = false,\n validate,\n allowEmpty = false,\n onConfirm,\n}: TextInputDialogProps) {\n const isMobile = useIsMobile();\n const ProTextarea = useWritingBox();\n const [value, setValue] = React.useState(defaultValue);\n const [error, setError] = React.useState<string | null>(null);\n\n /**\n * THE DEFECT THIS CLOSES (live on www.aimatrx.com, 2026-09-22). Every\n * caller of this dialog opens it from a toolbar button that stays on\n * screen, dimmed but perfectly readable, UNDER the dialog's own full-screen\n * scrim. Clicking that button again is therefore an \"interact outside\" and\n * DISMISSES the dialog. A person who does not notice the dialog and clicks\n * the button again — the most natural thing in the world — closes it; an\n * even number of clicks leaves the page looking untouched. That is exactly\n * how the battle page's \"Save as…\" was reported dead: four clicks, no\n * dialog, no toast, no overlay (verified click-by-click on the live site —\n * click 2 closed it, click 3 reopened it).\n *\n * So the element that OPENED this dialog is never an outside click. It is\n * remembered at open time (it is the focused element the instant before\n * Radix moves focus into the dialog) and a pointer-down whose point lands\n * on it is swallowed: the dialog stays open and focus returns to the field.\n * Every other outside click still dismisses, as it should.\n */\n const openerRef = React.useRef<HTMLElement | null>(null);\n const lastPointerTargetRef = React.useRef<HTMLElement | null>(null);\n\n // The opener cannot be read as `document.activeElement` once the dialog is\n // open — Radix has already pulled focus into the field — so the last\n // pointer-down target is remembered continuously and frozen the moment the\n // dialog opens.\n React.useEffect(() => {\n const remember = (event: Event) => {\n const target = event.target;\n lastPointerTargetRef.current =\n target instanceof HTMLElement ? target.closest(\"button\") ?? target : null;\n };\n document.addEventListener(\"pointerdown\", remember, true);\n return () => document.removeEventListener(\"pointerdown\", remember, true);\n }, []);\n\n React.useEffect(() => {\n if (open) openerRef.current = lastPointerTargetRef.current;\n }, [open]);\n\n const pointIsOnOpener = React.useCallback(\n (event: Event) =>\n isPointOnOpener(openerRef.current, event as unknown as MouseEvent),\n [],\n );\n\n // Reset state on every open. Keeps the component pure — opening it\n // twice in a row doesn't keep stale text from the prior session.\n React.useEffect(() => {\n if (open) {\n setValue(defaultValue);\n setError(null);\n }\n }, [open, defaultValue]);\n\n const submit = React.useCallback(() => {\n const trimmed = value.trim();\n if (!trimmed && !allowEmpty) {\n setError(\"Required\");\n return;\n }\n if (validate) {\n const err = validate(trimmed);\n if (err) {\n setError(err);\n return;\n }\n }\n void onConfirm(trimmed);\n }, [value, validate, allowEmpty, onConfirm]);\n\n const handleFormSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n if (busy) return;\n submit();\n };\n\n const body = (\n <form onSubmit={handleFormSubmit} className=\"space-y-2\">\n {multiline ? (\n <ProTextarea\n autoFocus\n onFocus={(event) => event.currentTarget.select()}\n value={value}\n onChange={(event) => {\n setValue(event.target.value);\n if (error) setError(null);\n }}\n placeholder={placeholder}\n disabled={busy}\n rows={rows}\n className={cn(\n \"min-h-[8rem] resize-y text-base\",\n error && \"border-destructive\",\n )}\n aria-invalid={!!error}\n aria-describedby={error ? \"text-input-dialog-error\" : undefined}\n />\n ) : (\n <Input\n autoFocus\n onFocus={(event) => event.currentTarget.select()}\n value={value}\n onChange={(event) => {\n setValue(event.target.value);\n if (error) setError(null);\n }}\n placeholder={placeholder}\n disabled={busy}\n // text-base = 16px, prevents iOS Safari zoom-on-focus\n \n aria-invalid={!!error}\n aria-describedby={error ? \"text-input-dialog-error\" : undefined}\n />\n )}\n {error ? (\n <p id=\"text-input-dialog-error\" className=\"text-sm text-destructive\">\n {error}\n <ErrorActions message={error} operation=\"Enter a value\" source=\"inline\" origin=\"design-system/TextInputDialog\" />\n </p>\n ) : null}\n {/* Hidden submit so Enter key submits the form. */}\n <button type=\"submit\" className=\"hidden\" tabIndex={-1} aria-hidden />\n </form>\n );\n\n const buttons = (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={busy}\n >\n {cancelLabel}\n </Button>\n <Button type=\"button\" onClick={submit} disabled={busy || (!allowEmpty && !value.trim())}>\n {busy ? <Loader2 className=\"mr-2 h-4 w-4 matrx-spin\" /> : null}\n {confirmLabel}\n </Button>\n </>\n );\n\n if (isMobile) {\n return (\n <Drawer open={open} onOpenChange={onOpenChange}>\n <DrawerContent>\n <DrawerHeader>\n <DrawerTitle>{title}</DrawerTitle>\n {description ? (\n <DrawerDescription>{description}</DrawerDescription>\n ) : null}\n </DrawerHeader>\n <div className=\"px-4 pb-2\">{body}</div>\n <DrawerFooter className=\"flex-row justify-end gap-2\">\n {buttons}\n </DrawerFooter>\n </DrawerContent>\n </Drawer>\n );\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n className=\"sm:max-w-md\"\n onPointerDownOutside={(event) => {\n if (pointIsOnOpener(event.detail.originalEvent)) {\n event.preventDefault();\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n {description ? (\n <DialogDescription>{description}</DialogDescription>\n ) : null}\n </DialogHeader>\n {body}\n <DialogFooter>{buttons}</DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","/**\n * \"Is this outside-click actually a re-click of the button that opened me?\"\n *\n * THE DEFECT THIS CLOSES (live on www.aimatrx.com, 2026-09-22). A dialog's\n * trigger stays on screen, readable, UNDER the dialog's own full-screen\n * scrim. Clicking it again is an outside interaction, so it DISMISSES what it\n * opened. A person who does not notice the dialog and clicks the button again\n * closes it, and an even number of clicks leaves the page looking untouched:\n * the battle page's \"Save as…\" was reported dead after four clicks — no\n * dialog, no toast, no overlay — and click-by-click on the live site the\n * dialog count went 1 → 0 → 1.\n *\n * WHY GEOMETRY AND NOT A HIT TEST. The scrim swallows the event, so the\n * event's TARGET is the scrim, never the trigger — `contains(target)` is\n * always false. `document.elementsFromPoint` is no better: a modal Radix\n * dialog puts `pointer-events: none` on the body, and a hit test skips\n * elements that cannot be hit, so the trigger drops out of the stack\n * unpredictably (observed live: two re-clicks held, the third dismissed).\n * The opener's own rectangle is the one thing that is always true.\n */\nexport function isPointOnOpener(\n opener: HTMLElement | null,\n event: { clientX?: number; clientY?: number },\n): boolean {\n if (!opener || !opener.isConnected) return false;\n const { clientX, clientY } = event;\n if (typeof clientX !== \"number\" || typeof clientY !== \"number\") return false;\n // A keyboard \"click\" reports 0,0; never treat that as a re-click.\n if (clientX === 0 && clientY === 0) return false;\n const rect = opener.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return (\n clientX >= rect.left &&\n clientX <= rect.right &&\n clientY >= rect.top &&\n clientY <= rect.bottom\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -14,11 +14,11 @@ import {
|
|
|
14
14
|
DialogFooter,
|
|
15
15
|
DialogHeader,
|
|
16
16
|
DialogTitle
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2BQAHLU3.js";
|
|
18
|
+
import "./chunk-LEPD6TV7.js";
|
|
18
19
|
import "./chunk-IMQ3RX35.js";
|
|
19
20
|
import "./chunk-U24MOJWK.js";
|
|
20
21
|
import "./chunk-NDTDBBMM.js";
|
|
21
|
-
import "./chunk-LEPD6TV7.js";
|
|
22
22
|
import "./chunk-BD77DHUO.js";
|
|
23
23
|
import "./chunk-MOZRIHJF.js";
|
|
24
24
|
import "./chunk-WAT2XW7L.js";
|
package/dist/writing-field.js
CHANGED
|
@@ -20,20 +20,20 @@ import {
|
|
|
20
20
|
AlertDialogHeader,
|
|
21
21
|
AlertDialogTitle,
|
|
22
22
|
Tile
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-QTPW44IJ.js";
|
|
24
24
|
import "./chunk-DRSBLHOF.js";
|
|
25
25
|
import {
|
|
26
26
|
Dialog,
|
|
27
27
|
DialogContent,
|
|
28
28
|
DialogTitle
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-2BQAHLU3.js";
|
|
30
|
+
import "./chunk-LEPD6TV7.js";
|
|
30
31
|
import {
|
|
31
32
|
UNCONFIGURED,
|
|
32
33
|
announceStandIn
|
|
33
34
|
} from "./chunk-IMQ3RX35.js";
|
|
34
35
|
import "./chunk-U24MOJWK.js";
|
|
35
36
|
import "./chunk-NDTDBBMM.js";
|
|
36
|
-
import "./chunk-LEPD6TV7.js";
|
|
37
37
|
import "./chunk-B3LDW3IQ.js";
|
|
38
38
|
import {
|
|
39
39
|
TapTargetButtonSolid
|
|
@@ -48,12 +48,12 @@ import {
|
|
|
48
48
|
import {
|
|
49
49
|
Field,
|
|
50
50
|
Textarea
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-WVEDW5VO.js";
|
|
52
52
|
import "./chunk-BD77DHUO.js";
|
|
53
53
|
import "./chunk-BKCJUFD3.js";
|
|
54
54
|
import "./chunk-PIQJULQB.js";
|
|
55
55
|
import "./chunk-MOZRIHJF.js";
|
|
56
|
-
import "./chunk-
|
|
56
|
+
import "./chunk-T57DLONB.js";
|
|
57
57
|
import "./chunk-BNXNIN2S.js";
|
|
58
58
|
import "./chunk-WFITMVDB.js";
|
|
59
59
|
import "./chunk-WAT2XW7L.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/design-system",
|
|
3
|
-
"version": "0.86.
|
|
3
|
+
"version": "0.86.358",
|
|
4
4
|
"description": "AI Matrx semantic React primitives shared across Vite, Next.js, desktop, and customer-built applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -111,6 +111,16 @@
|
|
|
111
111
|
"default": "./dist/notice-stack.cjs"
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
|
+
"./error-sentence": {
|
|
115
|
+
"import": {
|
|
116
|
+
"types": "./dist/error-sentence.d.ts",
|
|
117
|
+
"default": "./dist/error-sentence.js"
|
|
118
|
+
},
|
|
119
|
+
"require": {
|
|
120
|
+
"types": "./dist/error-sentence.d.cts",
|
|
121
|
+
"default": "./dist/error-sentence.cjs"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
114
124
|
"./menu-look.css": "./dist/menu-look.css",
|
|
115
125
|
"./clipboard-fallback-dialog": {
|
|
116
126
|
"import": {
|
|
@@ -770,5 +780,5 @@
|
|
|
770
780
|
"confirm-dialog",
|
|
771
781
|
"alert-dialog"
|
|
772
782
|
],
|
|
773
|
-
"gitHead": "
|
|
783
|
+
"gitHead": "ba5a7456f2e423d1bbb5f463c2989d627844e733"
|
|
774
784
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/notice-stack.ts"],"sourcesContent":["/**\n * @ai-matrx/design-system/notice-stack — THE ONE NOTICE LOOK AND STACK RULE, for the web and Matrx 2.\n *\n * Arman, 2026-10-23, looking at two full-width red bars across Matrx 2 Cloud Chat: \"Let's eliminate\n * this terrible pattern of having giant errors that shit the entire ui. If we're going to have a\n * place for errors, it should be a small something that resembles errors from a normal macos\".\n *\n * Champion: the macOS notification banner. A small rounded card of translucent material in the\n * window's TOP-RIGHT corner (where macOS puts every notification, so the eye already looks there;\n * the bottom of our windows holds the composer and the list's foot, which a notice must not sit on),\n * the tone's icon, a one-line title, an optional second line, at most one action, and ×.\n *\n * The stack rule, the same on both hosts:\n * - identical notices (same tone, title and line) collapse into ONE card with a count;\n * - at most 3 cards show; the rest fold into \"N more\";\n * - info and success go after NOTICE_STACK.infoMs; errors and warnings stay until dismissed\n * or until NOTICE_STACK.errorMs (long, so a notice nobody saw does not sit there for ever).\n *\n * The desktop renders it through `Notices` + `Banner` (desktop/layout.tsx, desktop/state.tsx); the\n * web through its one sonner Toaster (matrx-frontend components/ui/sonner.tsx), which reads these\n * constants and keys every toast with `noticeKey` so the same notice never stacks twice.\n */\n\nexport const NOTICE_STACK = {\n /** The corner every notice floats in. */\n corner: \"top-right\",\n /** Cards shown at once; the rest fold into \"N more\". */\n maxVisible: 3,\n /** Info and success: gone after this long. */\n infoMs: 4000,\n /** Errors and warnings: stay until dismissed, or this long at most. */\n errorMs: 30000,\n /** The card's width: small, never the window's. */\n widthRem: 22,\n} as const;\n\nexport type NoticeStackTone = \"neutral\" | \"info\" | \"warning\" | \"danger\" | \"success\";\n\n/** The identity two notices share when they say the same thing (tone, title, line). */\nexport function noticeKey(tone: string, title: unknown, message?: unknown): string {\n const text = (v: unknown): string => (typeof v === \"string\" || typeof v === \"number\" ? String(v).trim() : \"\");\n return `${tone}\\u0000${text(title)}\\u0000${text(message)}`;\n}\n\nexport interface CollapsedNotice<T> {\n /** The latest notice of the group (its dismiss and action are the live ones). */\n readonly item: T;\n /** Every notice the group holds, oldest first. */\n readonly members: readonly T[];\n /** How many identical notices it stands for. */\n readonly count: number;\n}\n\n/**\n * Identical notices become one with a count, in the order each first appeared. A notice whose key\n * is null (a rich one that says nothing comparable) stands alone.\n */\nexport function collapseNotices<T>(items: readonly T[], keyOf: (item: T) => string | null): CollapsedNotice<T>[] {\n const groups: { key: string | null; members: T[] }[] = [];\n const byKey = new Map<string, { key: string | null; members: T[] }>();\n for (const item of items) {\n const key = keyOf(item);\n const found = key !== null ? byKey.get(key) : undefined;\n if (found) {\n found.members.push(item);\n continue;\n }\n const group = { key, members: [item] };\n groups.push(group);\n if (key !== null) byKey.set(key, group);\n }\n return groups.map((g) => ({ item: g.members[g.members.length - 1] as T, members: g.members, count: g.members.length }));\n}\n\n/** The cards on screen and how many fold away (the newest show). */\nexport function visibleNotices<T>(groups: readonly T[], max: number = NOTICE_STACK.maxVisible): { shown: T[]; folded: number } {\n if (groups.length <= max) return { shown: [...groups], folded: 0 };\n return { shown: groups.slice(groups.length - max), folded: groups.length - max };\n}\n\n/** How long a notice of this tone stays (ms; Infinity never happens: errors cap at errorMs). */\nexport function noticeLifetimeMs(tone: NoticeStackTone | \"error\"): number {\n return tone === \"danger\" || tone === \"warning\" || tone === \"error\" ? NOTICE_STACK.errorMs : NOTICE_STACK.infoMs;\n}\n"],"mappings":";;;AAuBO,IAAM,eAAe;AAAA;AAAA,EAE1B,QAAQ;AAAA;AAAA,EAER,YAAY;AAAA;AAAA,EAEZ,QAAQ;AAAA;AAAA,EAER,SAAS;AAAA;AAAA,EAET,UAAU;AACZ;AAKO,SAAS,UAAU,MAAc,OAAgB,SAA2B;AACjF,QAAM,OAAO,CAAC,MAAwB,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW,OAAO,CAAC,EAAE,KAAK,IAAI;AAC1G,SAAO,GAAG,IAAI,KAAS,KAAK,KAAK,CAAC,KAAS,KAAK,OAAO,CAAC;AAC1D;AAeO,SAAS,gBAAmB,OAAqB,OAAyD;AAC/G,QAAM,SAAiD,CAAC;AACxD,QAAM,QAAQ,oBAAI,IAAkD;AACpE,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,MAAM,IAAI;AACtB,UAAM,QAAQ,QAAQ,OAAO,MAAM,IAAI,GAAG,IAAI;AAC9C,QAAI,OAAO;AACT,YAAM,QAAQ,KAAK,IAAI;AACvB;AAAA,IACF;AACA,UAAM,QAAQ,EAAE,KAAK,SAAS,CAAC,IAAI,EAAE;AACrC,WAAO,KAAK,KAAK;AACjB,QAAI,QAAQ,KAAM,OAAM,IAAI,KAAK,KAAK;AAAA,EACxC;AACA,SAAO,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,SAAS,CAAC,GAAQ,SAAS,EAAE,SAAS,OAAO,EAAE,QAAQ,OAAO,EAAE;AACxH;AAGO,SAAS,eAAkB,QAAsB,MAAc,aAAa,YAA4C;AAC7H,MAAI,OAAO,UAAU,IAAK,QAAO,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,QAAQ,EAAE;AACjE,SAAO,EAAE,OAAO,OAAO,MAAM,OAAO,SAAS,GAAG,GAAG,QAAQ,OAAO,SAAS,IAAI;AACjF;AAGO,SAAS,iBAAiB,MAAyC;AACxE,SAAO,SAAS,YAAY,SAAS,aAAa,SAAS,UAAU,aAAa,UAAU,aAAa;AAC3G;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/desktop/errors.ts","../src/error-sentence.ts","../src/error-box.tsx"],"sourcesContent":["/**\n * THE error-wording layer. A failure is shown as a cause the person can act on (\"A program it\n * needs isn't installed\"), never as the raw text a process threw (\"spawn uv ENOENT\"). The raw text\n * is kept, whole, as `detail` — the banner offers it behind Copy details and a tooltip, for a bug\n * report or an agent. One table, so every screen and every service says the same thing.\n */\n\nexport interface DescribedError {\n /** One plain sentence: what went wrong and, where it helps, what to do. */\n cause: string;\n /** The raw text, unedited (empty when it would only repeat `cause`). */\n detail: string;\n /** A short machine code when one was recognized (\"ENOENT\", \"TIMEOUT\"). */\n code?: string;\n}\n\ninterface Rule {\n code: string;\n test: RegExp;\n cause: string;\n}\n\n/** Ordered: the first rule whose pattern matches the raw text wins. */\nconst RULES: ReadonlyArray<Rule> = [\n { code: \"ENOENT\", test: /\\b(spawn\\s+\\S+\\s+)?ENOENT\\b|command not found|no such file or directory/i, cause: \"A program or file it needs isn't on this computer. Install it or update Matrx, then try again.\" },\n { code: \"EACCES\", test: /\\bEACCES\\b|\\bEPERM\\b|permission denied|operation not permitted|not authori[sz]ed by the user/i, cause: \"Matrx isn't allowed to do that. Grant access in System Settings › Privacy & Security, then try again.\" },\n { code: \"EADDRINUSE\", test: /\\bEADDRINUSE\\b|address already in use|port .* (is )?(already )?in use/i, cause: \"Another program is already using that port. Stop it, or choose another port.\" },\n { code: \"ECONNREFUSED\", test: /\\bECONNREFUSED\\b|connection refused/i, cause: \"The service isn't answering. It may have stopped; start it again.\" },\n { code: \"OFFLINE\", test: /\\bENOTFOUND\\b|\\bEAI_AGAIN\\b|\\bENETUNREACH\\b|getaddrinfo|network (is )?unreachable|failed to fetch|offline/i, cause: \"Can't reach the internet. Check the connection and try again.\" },\n { code: \"TIMEOUT\", test: /\\bETIMEDOUT\\b|timed? ?out|\\bTIMEOUT\\b|deadline exceeded/i, cause: \"It took too long to answer. Try again in a moment.\" },\n { code: \"ENOSPC\", test: /\\bENOSPC\\b|no space left/i, cause: \"The disk is full. Free some space, then try again.\" },\n { code: \"ENOMEM\", test: /\\bENOMEM\\b|out of memory|cannot allocate memory/i, cause: \"The computer ran out of memory. Close something and try again.\" },\n { code: \"UNAUTHORIZED\", test: /\\b401\\b|unauthori[sz]ed|invalid (api )?key|not signed in|jwt expired|session expired/i, cause: \"You're signed out, or the key was refused. Sign in again.\" },\n { code: \"FORBIDDEN\", test: /\\b403\\b|forbidden/i, cause: \"This account isn't allowed to do that.\" },\n { code: \"NOT_FOUND\", test: /\\b404\\b|not found/i, cause: \"It isn't there any more. Refresh and try again.\" },\n { code: \"RATE_LIMIT\", test: /\\b429\\b|rate.?limit|too many requests/i, cause: \"Too many requests just now. Wait a moment and try again.\" },\n { code: \"SERVER\", test: /\\b5\\d\\d\\b|internal server error|bad gateway|service unavailable/i, cause: \"The server had a problem. Try again in a moment.\" },\n { code: \"EXITED\", test: /exited with code|process exited|crashed|SIGKILL|SIGSEGV|SIGTERM/i, cause: \"The service stopped unexpectedly. Start it again; Copy details if it keeps happening.\" },\n];\n\nconst RAW = /\\b[A-Z]{2,}[A-Z_]*\\b|[\\\\/][\\w.-]+[\\\\/]|\\bat \\S+ \\(|\\{|\\}|errno|stack|undefined|null|\\bspawn\\b/;\n\nfunction rawText(error: unknown): string {\n if (error instanceof Error) return error.message || error.name;\n if (typeof error === \"string\") return error;\n if (error && typeof error === \"object\" && \"message\" in error && typeof (error as { message: unknown }).message === \"string\") {\n return (error as { message: string }).message;\n }\n return \"\";\n}\n\n/**\n * Turn any thrown or returned error into words a person can act on. Already-humane text (a full\n * sentence the app wrote itself, with no codes, paths or stack) passes through unchanged.\n */\nexport function describeError(error: unknown, fallback = \"Something went wrong. Try again.\"): DescribedError {\n const detail = rawText(error).trim();\n if (!detail) return { cause: fallback, detail: \"\" };\n for (const rule of RULES) {\n if (rule.test.test(detail)) return { cause: rule.cause, detail, code: rule.code };\n }\n if (!RAW.test(detail) && detail.length <= 160) return { cause: detail, detail: \"\" };\n return { cause: fallback, detail };\n}\n","/**\n * THE SENTENCE A PERSON READS FOR AN ERROR — the one funnel every error surface draws through\n * (ErrorNotice, ErrorBox, the desktop Banner). Raw error text never reaches the screen: no\n * \"TypeError: Failed to fetch\", no `sidebar_conversations:` RPC prefix, no stack frame or file\n * path (Arman, 2026-10-24: notices are small and plain). The raw error still travels to the\n * Alchemy Menu / AI payload through the `error` prop; only the screen gets the sentence.\n *\n * The wording table is desktop/errors.ts `describeError` (one table for every screen); this file\n * adds what decides WHEN a sentence must go through it, narrow enough that an app's own sentence\n * (\"PDF too large\") passes untouched.\n */\nimport { describeError } from \"./desktop/errors\";\n\n/**\n * The sentence an error carries: its own message (string, Error, or any\n * object with a `message`). Empty when it has none.\n */\nexport function errorSentenceOf(error: unknown): string {\n if (error == null || error === true || error === false) return \"\";\n if (typeof error === \"string\") return error;\n if (typeof error !== \"object\") return String(error);\n const message = (error as { message?: unknown }).message;\n return typeof message === \"string\" ? message : \"\";\n}\n\n/**\n * What a person reads when the store cancelled a call at its statement timeout\n * (SQLSTATE 57014). Postgres' own \"canceling statement due to statement\n * timeout\" is about the database, not the person; the cancelled call rolled\n * back, so nothing was written (lane AT-SLOW, 2026-10-19). The same sentence\n * @ai-matrx/records' `TIMED_OUT_MESSAGE` gives.\n */\nexport const TIMED_OUT_SENTENCE = \"That took too long. Nothing was lost; try again.\";\n\n/** What a person reads when an error says nothing a person can use. */\nexport const FALLBACK_ERROR_SENTENCE = \"Something went wrong. Try again.\";\n\n/** True when an error, its code, or its sentence is the store's statement timeout. */\nexport function isTimedOutError(error: unknown, message?: string | null, code?: string | number): boolean {\n if (code === \"57014\" || code === \"timed_out\") return true;\n if (error && typeof error === \"object\") {\n const o = error as { code?: unknown; sqlstate?: unknown };\n if (o.code === \"57014\" || o.code === \"timed_out\" || o.sqlstate === \"57014\") return true;\n }\n const text = `${message ?? \"\"} ${errorSentenceOf(error)}`;\n return /canceling statement due to statement timeout/i.test(text);\n}\n\n/** True when the call failed because the device is offline or the network never answered. */\nexport function isOfflineError(error: unknown, message?: string | null): boolean {\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n return offlineText(`${message ?? \"\"} ${errorSentenceOf(error)}`);\n}\n\nfunction offlineText(text: string): boolean {\n const t = text.trim();\n return t !== \"\" && (describeError(t).code === \"OFFLINE\" || /networkerror|network request failed|load failed/i.test(t));\n}\n\n/** describeError's causes that hold on every host (its process codes — ENOENT, EACCES, ports — are\n * the desktop's words for a local program and would mislead on a database refusal). */\nconst ANY_HOST_CODES = new Set([\"OFFLINE\", \"TIMEOUT\", \"UNAUTHORIZED\", \"FORBIDDEN\", \"NOT_FOUND\", \"RATE_LIMIT\", \"SERVER\"]);\n\n/** A leading machine label: `sidebar_conversations: …`, `chat.rpc_name: …`, `[chat-sidebar] …`. */\nconst MACHINE_PREFIX = /^\\s*(?:\\[[\\w./-]+\\]\\s*|[a-z][\\w]*(?:[._][\\w]+)+:\\s+)/;\n/** Text no person should read: JS error class names, stack frames, source paths. */\nconst RAW_TEXT =\n /\\b(?:Type|Reference|Syntax|Range|Internal|Eval|URI)Error\\b|\\bat\\s+[\\w$.<>]+\\s*\\(|\\.(?:[cm]?[jt]sx?|py):\\d+|(?:^|[\\s(])(?:\\/[\\w.@-]+){2,}|\\bundefined is not\\b|\\bcannot read propert(?:y|ies)\\b|\\bis not a function\\b|\\bunexpected token\\b/i;\n\n/** True when a sentence carries raw error text or a code path (what the guard refuses on screen). */\nexport function isRawErrorText(text: string): boolean {\n return MACHINE_PREFIX.test(text) || RAW_TEXT.test(text);\n}\n\n/**\n * The sentence for the screen: the timeout sentence for a statement timeout; a caller's plain\n * sentence as is; a machine label stripped from the front; anything raw or offline worded by\n * `describeError` (its table, else `fallback`).\n */\nexport function peopleErrorSentence(\n error: unknown,\n message?: string | null,\n options: { code?: string | number | undefined; fallback?: string | undefined } = {},\n): string {\n if (isTimedOutError(error, message, options.code)) return TIMED_OUT_SENTENCE;\n const fallback = options.fallback ?? FALLBACK_ERROR_SENTENCE;\n const raw = message?.trim() || errorSentenceOf(error).trim();\n if (!raw) return fallback;\n let text = raw;\n for (let i = 0; i < 3 && MACHINE_PREFIX.test(text); i++) text = text.replace(MACHINE_PREFIX, \"\").trim();\n if (!text) return fallback;\n if (offlineText(text) || RAW_TEXT.test(text) || text !== raw) {\n const said = describeError(text, fallback);\n return said.code && ANY_HOST_CODES.has(said.code) ? said.cause : fallback;\n }\n return text;\n}\n","/**\n * ErrorBox — THE one way a package draws an error on screen, and the seam a\n * host uses to put its own actions on every one of them.\n *\n * Every error sentence this package (and every sibling package that depends\n * on it) shows is an `ErrorBox`. The box is a plain `role=\"alert\"` element —\n * its tag and classes are the caller's, so nothing about the look changes — and\n * after its children it renders whatever the host's `ErrorActionsProvider`\n * returns for that error. With no provider it renders nothing extra.\n *\n * Why a slot and not a menu: the package cannot know the host's AI copy, bug\n * report, or support flow (Matrx fills it with its Alchemy copy-for-AI menu at\n * the app root). The package supplies the error's facts — the sentence, the\n * raw error, the operation, the records — and the host decides the actions.\n *\n * Guard: `error-box.census.test.ts` fails when a package file renders\n * `role=\"alert\"` for an error without going through `ErrorBox` (or, for the\n * `Alert` family, `ErrorActions`).\n */\nimport * as React from \"react\";\nimport { peopleErrorSentence } from \"./error-sentence\";\n\nexport interface ErrorActionsRecord {\n type: string;\n id: string;\n label?: string;\n}\n\n/** The facts a package knows about an error it is showing. All optional. */\nexport interface ErrorActionsInput {\n /** The sentence shown. Omit when the words are only in the rendered box. */\n message?: string | undefined;\n title?: string | undefined;\n /** The raw error (code/status/stack stay available to the host). */\n error?: unknown | undefined;\n /** What was being attempted (\"Load saved views\"). */\n operation?: string | undefined;\n records?: readonly ErrorActionsRecord[] | undefined;\n /** The person's input that did not land. */\n unsavedInput?: unknown | undefined;\n /** The package + component that drew it (\"design-system/SavedViewsControl\"). */\n origin?: string | undefined;\n /** Extra structured facts for the AI payload (never shown). */\n details?: Record<string, unknown> | undefined;\n code?: string | number | undefined;\n status?: number | undefined;\n /** The tables / RPCs the failed call used — pins its captured request as the cause. */\n calls?: readonly string[] | undefined;\n /** Where it was drawn: an `ErrorNotice` card/line (\"inline\") or a package alert box (\"alert\", the default). */\n source?: \"inline\" | \"alert\" | undefined;\n}\n\nexport type ErrorActionsRenderer = (input: ErrorActionsInput) => React.ReactNode;\n\n/**\n * ONE context for every copy of this module. tsup builds each entry point\n * (`.`, `./data-table`, …) without code splitting, so this file is inlined into\n * several bundles; a plain `createContext` would give each bundle its own\n * context and the host's provider (imported from `.`) would never reach an\n * error drawn by the data table. The registry key makes them one.\n */\nconst CONTEXT_KEY = Symbol.for(\"@ai-matrx/design-system/error-actions\");\ntype ContextRegistry = Record<symbol, React.Context<ErrorActionsRenderer | null> | undefined>;\nconst registry = globalThis as unknown as ContextRegistry;\nconst ErrorActionsContext: React.Context<ErrorActionsRenderer | null> =\n registry[CONTEXT_KEY] ??\n (registry[CONTEXT_KEY] = React.createContext<ErrorActionsRenderer | null>(null));\n\n/** Mount once at the host's app root; every ErrorBox below renders `render(...)`. */\nexport function ErrorActionsProvider({\n render,\n children,\n}: {\n render: ErrorActionsRenderer;\n children: React.ReactNode;\n}) {\n return (\n <ErrorActionsContext.Provider value={render}>{children}</ErrorActionsContext.Provider>\n );\n}\n\n/**\n * The host's content menu around an error card (`ErrorNotice`): the corner ⋯\n * button and the right-click boundary that show the host's content actions\n * over the error's sentence. Read-only — nothing here may change a record.\n * Without a provider the card draws neither (the actions slot still renders).\n */\nexport interface ErrorCardMenus {\n /** The corner ⋯ button over `{ title, text }`. */\n Button: React.ComponentType<{ title: string; text: string; className?: string }>;\n /** Wraps the whole card so right-click opens the same menu. */\n Boundary: React.ComponentType<{ title: string; text: string; children: React.ReactNode }>;\n}\n\nconst MENUS_KEY = Symbol.for(\"@ai-matrx/design-system/error-card-menus\");\ntype MenusRegistry = Record<symbol, React.Context<ErrorCardMenus | null> | undefined>;\nconst menusRegistry = globalThis as unknown as MenusRegistry;\nconst ErrorCardMenusContext: React.Context<ErrorCardMenus | null> =\n menusRegistry[MENUS_KEY] ??\n (menusRegistry[MENUS_KEY] = React.createContext<ErrorCardMenus | null>(null));\n\n/** Mount once at the host's app root beside `ErrorActionsProvider`. */\nexport function ErrorCardMenusProvider({\n menus,\n children,\n}: {\n menus: ErrorCardMenus;\n children: React.ReactNode;\n}) {\n return <ErrorCardMenusContext.Provider value={menus}>{children}</ErrorCardMenusContext.Provider>;\n}\n\nexport function useErrorCardMenus(): ErrorCardMenus | null {\n return React.useContext(ErrorCardMenusContext);\n}\n\nexport function useErrorActions(): ErrorActionsRenderer | null {\n return React.useContext(ErrorActionsContext);\n}\n\n/**\n * The host's actions for one error, placed wherever the caller puts this\n * element. Renders nothing without a provider. Use inside a box you do not\n * render yourself (the `Alert` family); everything else uses `ErrorBox`.\n */\nexport function ErrorActions(props: ErrorActionsInput) {\n const render = useErrorActions();\n if (!render) return null;\n return <>{render(props)}</>;\n}\n\ntype ErrorBoxTag = \"p\" | \"div\" | \"span\" | \"section\" | \"li\";\n\nexport interface ErrorBoxProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"role\" | \"title\">,\n ErrorActionsInput {\n /** The element drawn — the caller's existing tag, so layout never shifts. */\n as?: ErrorBoxTag;\n}\n\n/** A plain-string body goes through the one funnel (error-sentence.ts); rich children are the caller's. */\nfunction sayable(body: React.ReactNode, error: unknown): React.ReactNode {\n if (typeof body === \"string\") return peopleErrorSentence(error, body);\n if (Array.isArray(body)) return body.map((b) => (typeof b === \"string\" && b.trim() ? peopleErrorSentence(error, b) : b));\n return body;\n}\n\nexport const ErrorBox = React.forwardRef<HTMLElement, ErrorBoxProps>(function ErrorBox(\n {\n as = \"p\",\n message,\n title,\n error,\n operation,\n records,\n unsavedInput,\n origin,\n children,\n ...rest\n },\n ref,\n) {\n // A component type, never a bare ElementType: an app compiling this source may carry global JSX\n // elements (react-three-fiber) that make a bare ElementType's props `never` (2026-10-22).\n const Tag = as as unknown as React.ComponentType<React.HTMLAttributes<HTMLElement> & { ref?: React.Ref<HTMLElement> }>;\n const facts: ErrorActionsInput = {\n ...(message !== undefined ? { message } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(error !== undefined ? { error } : {}),\n ...(operation !== undefined ? { operation } : {}),\n ...(records !== undefined ? { records } : {}),\n ...(unsavedInput !== undefined ? { unsavedInput } : {}),\n ...(origin !== undefined ? { origin } : {}),\n };\n return (\n <Tag ref={ref} role=\"alert\" data-error-box=\"\" {...rest}>\n {sayable(children ?? message, error)}\n <ErrorActions {...facts} />\n </Tag>\n );\n});\n"],"mappings":"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","names":["ErrorBox"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/record-id-format.ts","../src/record-id.tsx","../src/data-table/useTableNotice.tsx"],"sourcesContent":["// THE id's formatting — what RecordId shows for an id — as plain functions with no React, so a\n// Server Component (a page title, metadata) shortens an id exactly as the component does.\n// Server-safe entry: `@ai-matrx/design-system/record-id-format`.\n\nimport { isUuidShape } from \"@ai-matrx/kit/uuid\";\n\nconst SHORT_LEN = 8;\n\nexport function shortUuid(id: string, len = SHORT_LEN): string {\n return id.slice(0, len);\n}\n\n/** Up to this many characters an id reads whole; longer ones show their start. */\nconst WHOLE_MAX = 16;\n\n/** A record id the database minted (a UUID) — what a peek shows on its own. */\nexport function isRecordUuid(id: string | null | undefined): boolean {\n return Boolean(id && isUuidShape(id));\n}\n\n/** What RecordId shows for an id: a UUID's (or any long id's) first 8 characters, a short id whole. */\nexport type RecordIdShow = \"auto\" | \"short\" | \"full\";\n\nexport function displayId(id: string, show: RecordIdShow = \"auto\"): string {\n if (show === \"full\") return id;\n if (show === \"short\" || isUuidShape(id) || id.length > WHOLE_MAX) return shortUuid(id);\n return id;\n}\n","\"use client\";\n\n/**\n * RecordId — THE way to show a record's id, anywhere (Arman, 2026-10-18: \"anything with an id …\n * we need to use a simple canonical id component that automatically handles several things,\n * including allowing for easy copy\"). Never `{row.id.slice(0, 8)}`, never a hand-built copy button.\n *\n * What it handles, once:\n * - display: a UUID shows its first 8 characters, a short id (a model id, a video id) shows whole;\n * the full id is always the tooltip;\n * - copy: one click copies the FULL id, a check confirms it, a failure says so;\n * - doors (THE DOOR LAW, \"never render an id you can't open\"): pass `token` (the entity token the\n * id points at) and the route, new tab and quick look resolve from the host registries; or pass\n * `href` / `onOpen`. `forbidden`: copyable, not openable;\n * - looks: `plain` (dense rows, tables — the default) or `chip` (headers, cards, detail panels);\n * `labelled` puts the kind of id in front (\"Agent ID\").\n * Prefer `EntityRef` when the record's NAME is on hand — this is the id itself.\n *\n * The data table's id column (`MatrxUuidCell`) is this component.\n */\n\nimport {\n useEffect,\n useRef,\n useState,\n type MouseEvent,\n type ReactNode,\n} from \"react\";\nimport { Lightbulb, ShieldOff } from \"./data-table/icons\";\nimport { CheckIcon, CopyIcon, ExternalLinkIcon, PanelRightIcon } from \"./icons\";\nimport { useTableNotice } from \"./data-table/useTableNotice\";\nimport { useMatrxDataTableHost } from \"./data-table/host\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"./tooltip\";\nimport { copyText } from \"@ai-matrx/kit/clipboard\";\nimport { cn } from \"./cn\";\nimport { Button as ControlButton } from \"./controls/controls\";\nimport { OutboundLink } from \"@ai-matrx/kit/outbound-react\";\n\nexport { shortUuid, isRecordUuid, displayId, type RecordIdShow } from \"./record-id-format\";\nimport { displayId, type RecordIdShow } from \"./record-id-format\";\nexport type RecordIdLook = \"plain\" | \"chip\" | \"icon\";\n\n\nexport type RecordIdOpenResult = void | \"forbidden\";\n\nexport interface RecordIdProps {\n value: string | null | undefined;\n /** What kind of id it is (\"Agent\", \"Chat\", a column name): the tooltip, the copy label and, with `labelled`, the visible prefix. */\n label?: string | undefined;\n /** `auto` (default): a UUID or long id shows its first 8 characters, a short id shows whole. */\n show?: RecordIdShow;\n /**\n * `plain` (default): dense mono text. `chip`: a quiet bordered pill for headers, cards and panels.\n * `icon`: the copy button alone, header-sized (a record's own header, where the id is shown elsewhere);\n * the full id is its tooltip.\n */\n look?: RecordIdLook;\n /** Show the kind of id in front of it (\"Agent ID 1a2b3c4d\"). */\n labelled?: boolean;\n /** A short note added to the tooltip under the id (\"Not a member of this organization\"). */\n hint?: string;\n /**\n * Inside another control (a button, a select trigger, a door): the id as text only, full id and\n * hint as its native tooltip, no buttons of its own — the control around it owns the click.\n */\n inert?: boolean;\n /**\n * Open the referenced record in-app (prefer WindowPanel).\n * Return `\"forbidden\"` to block navigation and show a muted state.\n */\n onOpen?: (id: string) => RecordIdOpenResult | Promise<RecordIdOpenResult>;\n /** Optional real href (new tab / cmd-click). Used when navigation is allowed. */\n href?: string | null | undefined;\n /**\n * Canonical entity token this id points at (`agent`, `note`, `task`, …).\n * Supplies the route and the peek automatically — THE DOOR LAW, resolved\n * from the registries instead of wired per callsite. An explicit `href` /\n * `onOpen` still wins.\n */\n token?: string | null | undefined;\n /** Force non-navigable (still copyable). */\n forbidden?: boolean;\n className?: string;\n trailing?: ReactNode;\n}\n\nexport function RecordId({\n value,\n label,\n show = \"auto\",\n look = \"plain\",\n labelled = false,\n hint,\n inert = false,\n onOpen,\n href,\n token,\n forbidden = false,\n className,\n trailing,\n}: RecordIdProps) {\n const { resolveEntityDoors, ResourcePeek } = useMatrxDataTableHost();\n const { notify, notice } = useTableNotice();\n const [copied, setCopied] = useState(false);\n const [opening, setOpening] = useState(false);\n const [peekOpen, setPeekOpen] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(\n () => () => {\n if (timer.current) clearTimeout(timer.current);\n },\n [],\n );\n\n if (!value) {\n return <span className=\"text-disabled-foreground\">—</span>;\n }\n\n const doors = token ? resolveEntityDoors?.(token, value, href) ?? null : null;\n const resolvedHref = doors?.href ?? href ?? null;\n const canPeek = Boolean(doors?.canPeek) && !forbidden;\n const display = displayId(value, show);\n\n // A label that already names the id (\"Execution ID\", \"run_id\", a table's \"… Id\" header) is used as\n // written; any other kind of record gains \" ID\" (\"Agent\" → \"Agent ID\").\n const named = label ? /\\b(id|uuid)$/i.test(label.trim()) || /_id$/i.test(label.trim()) : false;\n const copyLabel = label ? (named ? label.trim() : `${label} ID`) : \"ID\";\n const tip = `${label ? `${copyLabel}: ${value}` : value}${hint ? ` — ${hint}` : \"\"}`;\n if (inert) {\n return (\n <span\n data-matrx-record-id=\"\"\n title={tip}\n className={cn(\"min-w-0 truncate font-mono text-xs tabular-nums\", className)}\n >\n {labelled ? `${copyLabel} ` : \"\"}\n {display}\n </span>\n );\n }\n\n const stop = (e: MouseEvent) => {\n e.stopPropagation();\n };\n\n const onCopy = (e: MouseEvent) => {\n stop(e);\n e.preventDefault();\n void copyText(value).then((ok) => {\n if (!ok) {\n notify?.error(`Couldn't copy ${copyLabel}`);\n return;\n }\n setCopied(true);\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), 1500);\n });\n };\n\n const handleOpen = async (e: MouseEvent) => {\n stop(e);\n e.preventDefault();\n if (!onOpen || forbidden || opening) return;\n setOpening(true);\n try {\n const result = await onOpen(value);\n if (result === \"forbidden\") {\n // access-errors: ok — relays the onOpen contract's explicit verdict: the caller's opener returns \"forbidden\" only after ITS OWN access check; the cell performs no read and never guesses\n notify?.error(\"You don't have access to open this record\");\n }\n } catch (err) {\n notify?.error(\n `Couldn't open: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n setOpening(false);\n }\n };\n\n const idNode = (() => {\n if (forbidden) {\n return (\n <span className=\"inline-flex min-w-0 items-center gap-1 font-mono text-xs text-disabled-foreground tabular-nums\">\n <span className=\"min-w-0 truncate\">{display}</span>\n <ShieldOff className=\"h-3 w-3 shrink-0\" aria-hidden />\n </span>\n );\n }\n\n if (onOpen) {\n return (\n <ControlButton\n type=\"button\"\n variant=\"link\"\n iconEnd={<PanelRightIcon className=\"h-3 w-3 shrink-0 text-muted-foreground\" />}\n aria-label={`Open ${label ?? \"record\"} ${display}`}\n onClick={(e) => void handleOpen(e)}\n disabled={opening}\n className=\"min-w-0 font-mono\"\n >\n <span className=\"min-w-0 truncate tabular-nums\">{display}</span>\n </ControlButton>\n );\n }\n\n if (resolvedHref) {\n return (\n <OutboundLink\n href={resolvedHref}\n aria-label={`Open ${label ?? \"record\"} ${display} in a new tab`}\n onClick={stop}\n className=\"inline-flex min-w-0 items-center gap-1 font-mono text-xs text-primary hover:underline\"\n >\n <span className=\"min-w-0 truncate tabular-nums\">{display}</span>\n <ExternalLinkIcon className=\"h-3 w-3 shrink-0 text-muted-foreground\" />\n </OutboundLink>\n );\n }\n\n return (\n <span className=\"min-w-0 truncate font-mono text-xs text-muted-foreground tabular-nums select-all\">\n {display}\n </span>\n );\n })();\n\n return (\n <TooltipProvider delayDuration={200}>\n <span\n data-matrx-cell-control\n data-matrx-record-id=\"\"\n className={cn(\n \"inline-flex min-w-0 max-w-full items-center gap-0.5 whitespace-nowrap\",\n look === \"chip\" && \"h-5 rounded border border-border bg-muted pl-1.5 pr-0.5\",\n className,\n )}\n onClick={stop}\n >\n {labelled && look !== \"icon\" ? <span className=\"mr-1 shrink-0 text-xs text-muted-foreground\">{copyLabel}</span> : null}\n {look === \"icon\" ? null : (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex min-w-0 max-w-full items-center\">\n {idNode}\n </span>\n </TooltipTrigger>\n <TooltipContent side=\"top\" className=\"max-w-sm break-all font-mono\">\n {forbidden ? `No access · ${tip}` : tip}\n </TooltipContent>\n </Tooltip>\n )}\n <Tooltip>\n <TooltipTrigger asChild>\n <ControlButton\n type=\"button\"\n variant=\"quiet\"\n icon={copied ? <CheckIcon className={look === \"icon\" ? \"h-3.5 w-3.5\" : \"h-3 w-3\"} /> : <CopyIcon className={look === \"icon\" ? \"h-3.5 w-3.5\" : \"h-3 w-3\"} />}\n onClick={onCopy}\n aria-label={copied ? \"Copied\" : `Copy ${copyLabel}`}\n className=\"shrink-0\"\n />\n </TooltipTrigger>\n <TooltipContent side=\"top\">{copied ? \"Copied\" : `Copy ${copyLabel}: ${value}`}</TooltipContent>\n </Tooltip>\n {canPeek && look !== \"icon\" ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <ControlButton\n type=\"button\"\n variant=\"quiet\"\n icon={<Lightbulb className=\"h-3 w-3\" />}\n onClick={(e) => {\n stop(e);\n setPeekOpen(true);\n }}\n aria-label={`Quick look at ${label ?? \"record\"}`}\n className=\"shrink-0\"\n >\n </ControlButton>\n </TooltipTrigger>\n <TooltipContent side=\"top\">Quick look at {label ?? \"record\"}</TooltipContent>\n </Tooltip>\n ) : null}\n {onOpen && resolvedHref && !forbidden && look !== \"icon\" ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <OutboundLink\n href={resolvedHref}\n onClick={stop}\n aria-label={`Open ${label ?? \"record\"} in a new tab`}\n 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\"\n >\n <ExternalLinkIcon className=\"h-3 w-3\" />\n </OutboundLink>\n </TooltipTrigger>\n <TooltipContent side=\"top\">Open {label ?? \"record\"} in a new tab</TooltipContent>\n </Tooltip>\n ) : null}\n {trailing}\n {notice}\n {canPeek && peekOpen && ResourcePeek ? (\n <ResourcePeek\n kind={doors?.peekKind ?? token ?? \"record\"}\n id={value}\n onClose={() => setPeekOpen(false)}\n // Same contract as `EntityDoorControls`: forward the CALLER's raw\n // `href` so the peek's \"Open\" cannot send the user somewhere this\n // cell's own link does not go. `doors.ts` names EntityRef and this\n // cell as the two presentations that must not drift — the peek\n // override shipped on one of them first, which is exactly the drift\n // that file warns about.\n {...(href == null ? {} : { href })}\n />\n ) : null}\n </span>\n </TooltipProvider>\n );\n}\n","\"use client\";\nimport { useMemo, useState } from \"react\";\nimport { useMatrxDataTableHost } from \"./host\";\n\n/** Every portable async action has visible feedback even without a host toast system. */\nexport function useTableNotice() {\n const host = useMatrxDataTableHost();\n const [message, setMessage] = useState<{ text: string; error: boolean } | null>(null);\n // One `notify` per host, so a memoised toolbar that holds it never holds a stale one (RENDER-3).\n const notify = useMemo(\n () => ({\n success(text: string) { if (host.notify) host.notify.success(text); else setMessage({ text, error: false }); },\n error(text: string) { if (host.notify) host.notify.error(text); else setMessage({ text, error: true }); },\n }),\n [host],\n );\n const notice = message ? <span role={message.error ? \"alert\" : \"status\"} className={message.error ? \"text-xs text-destructive-ink\" : \"text-xs text-muted-foreground\"}>{message.text}<button type=\"button\" aria-label=\"Dismiss notification\" className=\"ml-2 underline\" onClick={() => setMessage(null)}>Dismiss</button></span> : null;\n return { notify, notice };\n}\n"],"mappings":"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","names":["useState","jsx","jsxs","useState"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dropdown-menu.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * DropdownMenu — Radix dropdown with two behaviors the stock body lacks.\n *\n * 1. THE ROOT RENDERS UNCONDITIONALLY — no hydration mount gate. A wrapper in\n * matrx-frontend once deferred rendering until after hydration on a false\n * premise (Radix ids come from React's SSR-stable `useId`), which deleted\n * the always-visible trigger — buttons, `…` menus — from SSR and the first\n * client paint.\n *\n * 2. A LONG MENU SCROLLS INSTEAD OF GROWING OFF-SCREEN. Content and SubContent\n * cap at `--radix-dropdown-menu-content-available-height` — the space Radix\n * actually measured between the trigger and the viewport edge — and scroll\n * past it. Without the cap a menu longer than the viewport puts its last\n * items where no pointer can reach them, and on a short window that can be\n * the only exit from the surface.\n *\n * Portalling goes through the package's `usePortalContainer` seam, so a menu\n * opened inside a Dialog mounts INSIDE the dialog (staying in the scroll\n * shard, where its wheel events work) instead of at `document.body`. An\n * explicit `container` prop still wins.\n *\n * Icons are the package's inlined SVGs (C19) — no icon-library dependency.\n */\n\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport * as React from \"react\";\n\nimport { cn } from \"./cn\";\nimport { MOTION_POPPER } from \"./motion\";\nimport { SOLID_SURFACE_CLASS, type PopoverSurface } from \"./solid-surface\";\nimport { RadixCheckIcon, DotFilledIcon, RadixChevronRightIcon } from \"./icons\";\nimport { usePortalContainer } from \"./portal-container\";\nimport { useMenuScrollEdges } from \"./menu-scroll-edges\";\nimport { useSubmenuSurvivesParentPanelFocus } from \"./submenu-focus\";\nimport { useNestedSubmenuPlacement } from \"./submenu-side\";\nimport { keepMovedFocus } from \"./focus-return\";\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\n// The cap is exactly the room Radix measured. What floats at the bottom (the host publishes\n// --matrx-floating-clearance) is kept clear as COLLISION PADDING (see readBottomClearance), never subtracted\n// here: subtracting it crushed any menu opened from inside or just above that floating chrome — the attention\n// dock's snooze menu (the dock is itself bottom chrome, ~240 px) opened as a 0 px sliver.\nconst MENU_SURFACE_CLASS =\n \"z-[10001] min-w-[8rem] max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto overflow-x-hidden overscroll-contain rounded-md border bg-popover p-1 text-popover-foreground\";\n\n\n// gap-2: an item's icon never touches its label (page-pass 2026-09-27, admin pass — every menu).\nconst MENU_ITEM_CLASS =\n \"relative flex select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:text-disabled-foreground\";\n\nconst MENU_INDICATOR_ITEM_CLASS =\n \"relative flex select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:text-disabled-foreground\";\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n data-slot=\"dropdown-menu-sub-trigger\"\n className={cn(\n \"flex select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n >\n {children}\n <RadixChevronRightIcon className=\"ml-auto h-4 w-4\" />\n </DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuSubTrigger.displayName =\n DropdownMenuPrimitive.SubTrigger.displayName;\n\n/**\n * A SUBMENU OPENS BESIDE ITS PARENT, NEVER ON TOP OF IT (merged-grid review 2, C1: \"nested\n * submenus overlap the parent menu\"). The submenu was drawn INSIDE the parent's surface, and that\n * surface's entry animation leaves a `transform` on it — which makes the parent the containing\n * block of the submenu's `position: fixed`. Radix measured the submenu's place in viewport\n * coordinates and the browser applied them from the parent's corner, so it landed over the parent\n * (and was clipped by the parent's scroll box). Portalled like every other surface, through the\n * same container seam, it sits beside its trigger as Radix placed it.\n */\nconst DropdownMenuSubContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> & {\n container?: HTMLElement | null;\n /** `\"solid\"` = opaque surface (see PopoverContent `surface`). */\n surface?: PopoverSurface;\n }\n>(({ className, container, surface = \"default\", sideOffset = 2, alignOffset = -4, collisionPadding, onFocusOutside, children, ...props }, ref) => {\n const portalContainer = usePortalContainer(container);\n const [bottomClearance] = React.useState(readBottomClearance);\n const padding = menuCollisionPadding(collisionPadding ?? 8, 0, bottomClearance);\n // A nested submenu opens on its parent's side (see submenu-side.tsx).\n const nested = useNestedSubmenuPlacement(ref, padding);\n // A flipped submenu survives the pointer leaving its trigger (see submenu-focus.ts).\n const keep = useSubmenuSurvivesParentPanelFocus(nested.ref, onFocusOutside);\n const edges = useMenuScrollEdges(keep.ref);\n return (\n <DropdownMenuPrimitive.Portal container={portalContainer}>\n <DropdownMenuPrimitive.SubContent\n ref={edges.ref}\n onFocusOutside={keep.onFocusOutside}\n data-slot=\"dropdown-menu-sub-content\"\n data-surface={surface === \"solid\" ? \"solid\" : undefined}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n {...(nested.padding === undefined ? {} : { collisionPadding: nested.padding })}\n className={cn(MENU_SURFACE_CLASS, edges.surfaceClassName, \"shadow-lg\", surface === \"solid\" && SOLID_SURFACE_CLASS, MOTION_POPPER, className)}\n {...props}\n >\n {nested.wrap(edges.wrap(children))}\n </DropdownMenuPrimitive.SubContent>\n </DropdownMenuPrimitive.Portal>\n );\n});\nDropdownMenuSubContent.displayName =\n DropdownMenuPrimitive.SubContent.displayName;\n\nexport interface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {\n /** Explicit portal target; wins over the injected container. */\n container?: HTMLElement | null;\n /** `\"solid\"` = opaque surface (see PopoverContent `surface`). */\n surface?: PopoverSurface;\n}\n\n/**\n * The px a menu keeps clear of the TOP edge: what the host publishes as `--matrx-floating-top-clearance`\n * (a page's sticky header row, on top of the app header) or, absent that, the app header\n * (`--shell-header-h`). Radix positions against the raw viewport, so without it a menu opened from the first\n * row under a sticky header flips up and its first items sit underneath it. Read once, when the menu opens.\n * Hosts without either variable lose nothing (0px = Radix's own default).\n */\nexport function readTopClearance(): number {\n return readClearance(\"var(--matrx-floating-top-clearance, var(--shell-header-h, 0px))\");\n}\n\n/**\n * The px a menu keeps clear of the BOTTOM edge: what the host publishes as `--matrx-floating-clearance`\n * (the assists pill, the attention dock, any `data-matrx-floating-bottom` chrome). As collision padding,\n * Radix FLIPS a menu upward when the room left below that chrome is too small, and its available height\n * already excludes it — so the last item never ends under the pill, and a menu opened from INSIDE that\n * chrome opens upward at full height instead of collapsing. Read once, when the menu opens.\n */\nexport function readBottomClearance(): number {\n return readClearance(\"var(--matrx-floating-clearance, 0px)\");\n}\n\ntype MenuCollisionPadding = number | Partial<Record<\"top\" | \"right\" | \"bottom\" | \"left\", number>>;\n\n/** Pure: the caller's collision padding (or `fallback` on sides it leaves unset), with the host's top and bottom\n * clearance as a floor — a caller's own padding never puts a menu under the app header or the assists pill. */\nexport function menuCollisionPadding(\n caller: MenuCollisionPadding | undefined,\n top: number,\n bottom: number,\n fallback = 0,\n): Record<\"top\" | \"right\" | \"bottom\" | \"left\", number> {\n const side = (key: \"top\" | \"right\" | \"bottom\" | \"left\") =>\n typeof caller === \"number\" ? caller : (caller?.[key] ?? fallback);\n return { top: Math.max(side(\"top\"), top), right: side(\"right\"), bottom: Math.max(side(\"bottom\"), bottom), left: side(\"left\") };\n}\n\nfunction readClearance(height: string): number {\n if (typeof document === \"undefined\") return 0;\n const probe = document.createElement(\"div\");\n probe.style.cssText = `position:fixed;top:0;left:0;width:0;visibility:hidden;pointer-events:none;height:${height}`;\n document.body.appendChild(probe);\n const px = probe.getBoundingClientRect().height;\n probe.remove();\n return Number.isFinite(px) ? Math.ceil(px) : 0;\n}\n\nconst DropdownMenuContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(({ className, sideOffset = 4, container, surface = \"default\", children, onCloseAutoFocus, collisionPadding, ...props }, ref) => {\n const portalContainer = usePortalContainer(container);\n const [topClearance] = React.useState(readTopClearance);\n const [bottomClearance] = React.useState(readBottomClearance);\n const edges = useMenuScrollEdges(ref);\n return (\n <DropdownMenuPrimitive.Portal container={portalContainer}>\n <DropdownMenuPrimitive.Content\n ref={edges.ref}\n data-slot=\"dropdown-menu-content\"\n data-surface={surface === \"solid\" ? \"solid\" : undefined}\n sideOffset={sideOffset}\n collisionPadding={menuCollisionPadding(collisionPadding, topClearance, bottomClearance)}\n className={cn(\n MENU_SURFACE_CLASS,\n edges.surfaceClassName,\n \"shadow-md\",\n surface === \"solid\" && SOLID_SURFACE_CLASS,\n MOTION_POPPER,\n className,\n )}\n {...props}\n onCloseAutoFocus={keepMovedFocus(onCloseAutoFocus)}\n >\n {edges.wrap(children)}\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n );\n});\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n /** `danger`: a destructive item (Delete, Remove) — destructive text and icon, destructive soft\n * fill on hover / focus. Default: the ordinary item. */\n tone?: \"danger\";\n /** `sm`: 13px text on the 28px control row height, for dense frames. Default: the menu's 14px. */\n size?: \"sm\";\n }\n>(({ className, inset, tone, size, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n data-slot=\"dropdown-menu-item\"\n data-tone={tone}\n data-size={size}\n className={cn(MENU_ITEM_CLASS, size === \"sm\" && \"h-7 py-0 text-[13px]\", inset && \"pl-8\", className)}\n {...props}\n />\n));\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n data-slot=\"dropdown-menu-checkbox-item\"\n className={cn(MENU_INDICATOR_ITEM_CLASS, className)}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <RadixCheckIcon className=\"h-4 w-4\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n));\nDropdownMenuCheckboxItem.displayName =\n DropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n data-slot=\"dropdown-menu-radio-item\"\n className={cn(MENU_INDICATOR_ITEM_CLASS, className)}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <DotFilledIcon className=\"h-4 w-4 fill-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\nconst DropdownMenuLabel = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n data-slot=\"dropdown-menu-label\"\n className={cn(\"type-label px-2 pb-1 pt-2 font-medium text-foreground\", inset && \"pl-8\", className)}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n data-slot=\"dropdown-menu-separator\"\n className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\nconst DropdownMenuShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n data-slot=\"dropdown-menu-shortcut\"\n className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n {...props}\n />\n);\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\";\n\nexport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n};\n"],"mappings":"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","names":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|