@burojs/ui-shadcn 0.4.0 → 0.5.0
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/README.md +22 -5
- package/dist/bulk-summary-CwSd6Yjb.cjs +108 -0
- package/dist/bulk-summary-CwSd6Yjb.cjs.map +1 -0
- package/dist/bulk-summary-GxMDP4bw.js +108 -0
- package/dist/bulk-summary-GxMDP4bw.js.map +1 -0
- package/dist/button-BxnnE28H.cjs +55 -0
- package/dist/button-BxnnE28H.cjs.map +1 -0
- package/dist/button-MWVeZaqj.js +44 -0
- package/dist/button-MWVeZaqj.js.map +1 -0
- package/dist/candidates-gate-CX2PBu6l.js +63 -0
- package/dist/candidates-gate-CX2PBu6l.js.map +1 -0
- package/dist/candidates-gate-DiFI52r7.cjs +70 -0
- package/dist/candidates-gate-DiFI52r7.cjs.map +1 -0
- package/dist/candidates-relation-picker-CBsVcnPR.js +82 -0
- package/dist/candidates-relation-picker-CBsVcnPR.js.map +1 -0
- package/dist/candidates-relation-picker-Dd_GVLlu.cjs +84 -0
- package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +1 -0
- package/dist/check-confirm-dialog-BRByTFpg.cjs +1534 -0
- package/dist/check-confirm-dialog-BRByTFpg.cjs.map +1 -0
- package/dist/check-confirm-dialog-CkaVTPT9.js +1232 -0
- package/dist/check-confirm-dialog-CkaVTPT9.js.map +1 -0
- package/dist/dialog-B_rt9S1c.js +63 -0
- package/dist/dialog-B_rt9S1c.js.map +1 -0
- package/dist/dialog-DRNUq9EA.cjs +112 -0
- package/dist/dialog-DRNUq9EA.cjs.map +1 -0
- package/dist/filter-builder-B3mZFL-K.cjs +1761 -0
- package/dist/filter-builder-B3mZFL-K.cjs.map +1 -0
- package/dist/filter-builder-DOnaXNfP.js +1605 -0
- package/dist/filter-builder-DOnaXNfP.js.map +1 -0
- package/dist/flow-runtime-B6zOHYqI.cjs +255 -0
- package/dist/flow-runtime-B6zOHYqI.cjs.map +1 -0
- package/dist/flow-runtime-D8h99FSg.js +252 -0
- package/dist/flow-runtime-D8h99FSg.js.map +1 -0
- package/dist/flow-screen-CX05b3G9.cjs +705 -0
- package/dist/flow-screen-CX05b3G9.cjs.map +1 -0
- package/dist/flow-screen-CmbARSqg.js +701 -0
- package/dist/flow-screen-CmbARSqg.js.map +1 -0
- package/dist/index.cjs +2825 -11912
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1612 -1862
- package/dist/index.d.ts +1700 -1950
- package/dist/index.js +2498 -11605
- package/dist/index.js.map +1 -1
- package/dist/inline-flow-host-B7U1OJUe.cjs +112 -0
- package/dist/inline-flow-host-B7U1OJUe.cjs.map +1 -0
- package/dist/inline-flow-host-C3H2UpSA.js +112 -0
- package/dist/inline-flow-host-C3H2UpSA.js.map +1 -0
- package/dist/input-BI0czGyI.cjs +23 -0
- package/dist/input-BI0czGyI.cjs.map +1 -0
- package/dist/input-CUPStzZO.js +17 -0
- package/dist/input-CUPStzZO.js.map +1 -0
- package/dist/label-66W1qbke.js +29 -0
- package/dist/label-66W1qbke.js.map +1 -0
- package/dist/label-w6JYf2cF.cjs +36 -0
- package/dist/label-w6JYf2cF.cjs.map +1 -0
- package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +14 -0
- package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +1 -0
- package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +19 -0
- package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +1 -0
- package/dist/markdown/index.cjs +1 -1
- package/dist/markdown/index.js +1 -1
- package/dist/page-flow-route-CQ7HsbBm.cjs +277 -0
- package/dist/page-flow-route-CQ7HsbBm.cjs.map +1 -0
- package/dist/page-flow-route-CyXSvXSE.js +277 -0
- package/dist/page-flow-route-CyXSvXSE.js.map +1 -0
- package/dist/peek-filter-CnqHwTMU.cjs +51 -0
- package/dist/peek-filter-CnqHwTMU.cjs.map +1 -0
- package/dist/peek-filter-ErLfO153.js +28 -0
- package/dist/peek-filter-ErLfO153.js.map +1 -0
- package/dist/place-node--GdsP688.js +7315 -0
- package/dist/place-node--GdsP688.js.map +1 -0
- package/dist/place-node-SIp4XYQH.cjs +7833 -0
- package/dist/place-node-SIp4XYQH.cjs.map +1 -0
- package/dist/related-list-cards-B6x9HQQp.js +42 -0
- package/dist/related-list-cards-B6x9HQQp.js.map +1 -0
- package/dist/related-list-cards-nxuODlIA.cjs +42 -0
- package/dist/related-list-cards-nxuODlIA.cjs.map +1 -0
- package/dist/related-list-timeline-BYJKEbmA.cjs +142 -0
- package/dist/related-list-timeline-BYJKEbmA.cjs.map +1 -0
- package/dist/related-list-timeline-DEdfd-rr.js +142 -0
- package/dist/related-list-timeline-DEdfd-rr.js.map +1 -0
- package/dist/related-rows-B4levHcR.js +186 -0
- package/dist/related-rows-B4levHcR.js.map +1 -0
- package/dist/related-rows-zh5f_4QR.cjs +209 -0
- package/dist/related-rows-zh5f_4QR.cjs.map +1 -0
- package/dist/relation-create-BOaUe12G.cjs +362 -0
- package/dist/relation-create-BOaUe12G.cjs.map +1 -0
- package/dist/relation-create-Bg-eEJLs.js +337 -0
- package/dist/relation-create-Bg-eEJLs.js.map +1 -0
- package/dist/relation-many-picker-B-NCEUgX.cjs +465 -0
- package/dist/relation-many-picker-B-NCEUgX.cjs.map +1 -0
- package/dist/relation-many-picker-gdAOD-qK.js +464 -0
- package/dist/relation-many-picker-gdAOD-qK.js.map +1 -0
- package/dist/relation-picker-BAY77ivO.js +462 -0
- package/dist/relation-picker-BAY77ivO.js.map +1 -0
- package/dist/relation-picker-Da_uhFvA.cjs +479 -0
- package/dist/relation-picker-Da_uhFvA.cjs.map +1 -0
- package/dist/relation-picker-dialog-M0pQaWlp.cjs +300 -0
- package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +1 -0
- package/dist/relation-picker-dialog-hA9rYrZB.js +299 -0
- package/dist/relation-picker-dialog-hA9rYrZB.js.map +1 -0
- package/dist/resource-page-router-OSQyRA5b.cjs +1779 -0
- package/dist/resource-page-router-OSQyRA5b.cjs.map +1 -0
- package/dist/resource-page-router-T4vS5gbJ.js +1582 -0
- package/dist/resource-page-router-T4vS5gbJ.js.map +1 -0
- package/dist/{rich-text-IDFvyvRE.js → rich-text-DFerywgz.js} +7 -4
- package/dist/{rich-text-IDFvyvRE.js.map → rich-text-DFerywgz.js.map} +1 -1
- package/dist/{rich-text-lndbYqzR.cjs → rich-text-vTyVjDoa.cjs} +25 -22
- package/dist/{rich-text-lndbYqzR.cjs.map → rich-text-vTyVjDoa.cjs.map} +1 -1
- package/dist/search-access-B7G5oR8n.cjs +812 -0
- package/dist/search-access-B7G5oR8n.cjs.map +1 -0
- package/dist/search-access-CURsWn-U.js +669 -0
- package/dist/search-access-CURsWn-U.js.map +1 -0
- package/dist/shell-BPlk8hEa.js +205 -0
- package/dist/shell-BPlk8hEa.js.map +1 -0
- package/dist/shell-DxhlsPwm.cjs +271 -0
- package/dist/shell-DxhlsPwm.cjs.map +1 -0
- package/dist/skeleton-U2noefj5.js +13 -0
- package/dist/skeleton-U2noefj5.js.map +1 -0
- package/dist/skeleton-_1gP7wan.cjs +18 -0
- package/dist/skeleton-_1gP7wan.cjs.map +1 -0
- package/dist/styles.css +241 -9
- package/dist/toaster-C7CiYA4Z.cjs +248 -0
- package/dist/toaster-C7CiYA4Z.cjs.map +1 -0
- package/dist/toaster-CfCRLgR-.js +164 -0
- package/dist/toaster-CfCRLgR-.js.map +1 -0
- package/dist/use-dialog-filter-state-BBCC0OEK.cjs +92 -0
- package/dist/use-dialog-filter-state-BBCC0OEK.cjs.map +1 -0
- package/dist/use-dialog-filter-state-BDcgS5Oz.js +87 -0
- package/dist/use-dialog-filter-state-BDcgS5Oz.js.map +1 -0
- package/dist/{use-theme-BwPWinB2.cjs → use-theme-DFlpXils.cjs} +12 -1
- package/dist/use-theme-DFlpXils.cjs.map +1 -0
- package/dist/{use-theme-IHmtrhEt.js → use-theme-dnE3Od3I.js} +7 -2
- package/dist/use-theme-dnE3Od3I.js.map +1 -0
- package/package.json +16 -14
- package/dist/use-theme-BwPWinB2.cjs.map +0 -1
- package/dist/use-theme-IHmtrhEt.js.map +0 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
const require_use_theme = require("./use-theme-
|
|
2
|
-
const
|
|
1
|
+
const require_use_theme = require("./use-theme-DFlpXils.cjs");
|
|
2
|
+
const require_button = require("./button-BxnnE28H.cjs");
|
|
3
|
+
const require_input = require("./input-BI0czGyI.cjs");
|
|
4
|
+
const require_dialog = require("./dialog-DRNUq9EA.cjs");
|
|
5
|
+
const require_label = require("./label-w6JYf2cF.cjs");
|
|
3
6
|
let _burojs_react = require("@burojs/react");
|
|
4
7
|
let _tabler_icons_react = require("@tabler/icons-react");
|
|
5
8
|
let react = require("react");
|
|
@@ -85,7 +88,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
85
88
|
"aria-label": t("richText.toolbar", { defaultValue: "Rich text formatting" }),
|
|
86
89
|
"data-testid": `${testid}:toolbar`,
|
|
87
90
|
children: [
|
|
88
|
-
(0, react_jsx_runtime.jsx)(
|
|
91
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
89
92
|
type: "button",
|
|
90
93
|
variant: "ghost",
|
|
91
94
|
size: "sm",
|
|
@@ -100,7 +103,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
100
103
|
"aria-hidden": true
|
|
101
104
|
})
|
|
102
105
|
}),
|
|
103
|
-
(0, react_jsx_runtime.jsx)(
|
|
106
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
104
107
|
type: "button",
|
|
105
108
|
variant: "ghost",
|
|
106
109
|
size: "sm",
|
|
@@ -119,7 +122,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
119
122
|
className: "mx-1 h-4 w-px bg-border",
|
|
120
123
|
"aria-hidden": true
|
|
121
124
|
}),
|
|
122
|
-
(0, react_jsx_runtime.jsx)(
|
|
125
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
123
126
|
type: "button",
|
|
124
127
|
variant: "ghost",
|
|
125
128
|
size: "sm",
|
|
@@ -134,7 +137,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
134
137
|
"aria-hidden": true
|
|
135
138
|
})
|
|
136
139
|
}),
|
|
137
|
-
(0, react_jsx_runtime.jsx)(
|
|
140
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
138
141
|
type: "button",
|
|
139
142
|
variant: "ghost",
|
|
140
143
|
size: "sm",
|
|
@@ -149,7 +152,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
149
152
|
"aria-hidden": true
|
|
150
153
|
})
|
|
151
154
|
}),
|
|
152
|
-
(0, react_jsx_runtime.jsx)(
|
|
155
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
153
156
|
type: "button",
|
|
154
157
|
variant: "ghost",
|
|
155
158
|
size: "sm",
|
|
@@ -164,7 +167,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
164
167
|
"aria-hidden": true
|
|
165
168
|
})
|
|
166
169
|
}),
|
|
167
|
-
(0, react_jsx_runtime.jsx)(
|
|
170
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
168
171
|
type: "button",
|
|
169
172
|
variant: "ghost",
|
|
170
173
|
size: "sm",
|
|
@@ -183,7 +186,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
183
186
|
className: "mx-1 h-4 w-px bg-border",
|
|
184
187
|
"aria-hidden": true
|
|
185
188
|
}),
|
|
186
|
-
(0, react_jsx_runtime.jsx)(
|
|
189
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
187
190
|
type: "button",
|
|
188
191
|
variant: "ghost",
|
|
189
192
|
size: "sm",
|
|
@@ -198,7 +201,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
198
201
|
"aria-hidden": true
|
|
199
202
|
})
|
|
200
203
|
}),
|
|
201
|
-
(0, react_jsx_runtime.jsx)(
|
|
204
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
202
205
|
type: "button",
|
|
203
206
|
variant: "ghost",
|
|
204
207
|
size: "sm",
|
|
@@ -217,7 +220,7 @@ function Toolbar({ editor, testid, onOpenLinkDialog }) {
|
|
|
217
220
|
className: "mx-1 h-4 w-px bg-border",
|
|
218
221
|
"aria-hidden": true
|
|
219
222
|
}),
|
|
220
|
-
(0, react_jsx_runtime.jsx)(
|
|
223
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
221
224
|
type: "button",
|
|
222
225
|
variant: "ghost",
|
|
223
226
|
size: "sm",
|
|
@@ -274,21 +277,21 @@ function LinkDialog({ open, initialUrl, onOpenChange, onSubmit, onRemove, testid
|
|
|
274
277
|
onSubmit(url);
|
|
275
278
|
onOpenChange(false);
|
|
276
279
|
}
|
|
277
|
-
return (0, react_jsx_runtime.jsx)(
|
|
280
|
+
return (0, react_jsx_runtime.jsx)(require_dialog.Dialog, {
|
|
278
281
|
open,
|
|
279
282
|
onOpenChange,
|
|
280
|
-
children: (0, react_jsx_runtime.jsxs)(
|
|
283
|
+
children: (0, react_jsx_runtime.jsxs)(require_dialog.DialogContent, {
|
|
281
284
|
"data-testid": `${testid}:link-dialog`,
|
|
282
285
|
children: [
|
|
283
|
-
(0, react_jsx_runtime.jsx)(
|
|
286
|
+
(0, react_jsx_runtime.jsx)(require_dialog.DialogHeader, { children: (0, react_jsx_runtime.jsx)(require_dialog.DialogTitle, { children: t("richText.addLinkTitle", { defaultValue: "Add link" }) }) }),
|
|
284
287
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
285
288
|
className: "space-y-2",
|
|
286
289
|
children: [
|
|
287
|
-
(0, react_jsx_runtime.jsx)(
|
|
290
|
+
(0, react_jsx_runtime.jsx)(require_label.Label, {
|
|
288
291
|
htmlFor: `${testid}:link-url`,
|
|
289
292
|
children: "URL"
|
|
290
293
|
}),
|
|
291
|
-
(0, react_jsx_runtime.jsx)(
|
|
294
|
+
(0, react_jsx_runtime.jsx)(require_input.Input, {
|
|
292
295
|
id: `${testid}:link-url`,
|
|
293
296
|
"data-testid": `${testid}:link-url`,
|
|
294
297
|
type: "url",
|
|
@@ -312,15 +315,15 @@ function LinkDialog({ open, initialUrl, onOpenChange, onSubmit, onRemove, testid
|
|
|
312
315
|
}) : null
|
|
313
316
|
]
|
|
314
317
|
}),
|
|
315
|
-
(0, react_jsx_runtime.jsxs)(
|
|
316
|
-
(0, react_jsx_runtime.jsx)(
|
|
318
|
+
(0, react_jsx_runtime.jsxs)(require_dialog.DialogFooter, { children: [
|
|
319
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
317
320
|
type: "button",
|
|
318
321
|
variant: "ghost",
|
|
319
322
|
onClick: () => onOpenChange(false),
|
|
320
323
|
"data-testid": `${testid}:link-cancel`,
|
|
321
324
|
children: "Cancel"
|
|
322
325
|
}),
|
|
323
|
-
(0, react_jsx_runtime.jsx)(
|
|
326
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
324
327
|
type: "button",
|
|
325
328
|
variant: "ghost",
|
|
326
329
|
onClick: () => {
|
|
@@ -330,7 +333,7 @@ function LinkDialog({ open, initialUrl, onOpenChange, onSubmit, onRemove, testid
|
|
|
330
333
|
"data-testid": `${testid}:link-remove`,
|
|
331
334
|
children: "Remove"
|
|
332
335
|
}),
|
|
333
|
-
(0, react_jsx_runtime.jsx)(
|
|
336
|
+
(0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
334
337
|
type: "button",
|
|
335
338
|
onClick: handleSubmit,
|
|
336
339
|
"data-testid": `${testid}:link-save`,
|
|
@@ -342,7 +345,7 @@ function LinkDialog({ open, initialUrl, onOpenChange, onSubmit, onRemove, testid
|
|
|
342
345
|
});
|
|
343
346
|
}
|
|
344
347
|
function RichTextInput({ name, id, value, onChange, testid, disabled, required, ariaInvalid, ariaDescribedBy, ariaErrorMessage, field, format: _formatProp }) {
|
|
345
|
-
const showToolbar = (field?.ui ?? {}).
|
|
348
|
+
const showToolbar = (field?.ui ?? {}).formatting !== false;
|
|
346
349
|
const valueStr = typeof value === "string" ? value : "";
|
|
347
350
|
const editorTestid = testid ?? `rich-text-input:${name}`;
|
|
348
351
|
const [linkDialogOpen, setLinkDialogOpen] = react.useState(false);
|
|
@@ -454,4 +457,4 @@ function RichTextInput({ name, id, value, onChange, testid, disabled, required,
|
|
|
454
457
|
exports.RichTextDisplay = RichTextDisplay;
|
|
455
458
|
exports.RichTextInput = RichTextInput;
|
|
456
459
|
|
|
457
|
-
//# sourceMappingURL=rich-text-
|
|
460
|
+
//# sourceMappingURL=rich-text-vTyVjDoa.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-lndbYqzR.cjs","names":["StarterKit","generateJSON","generateHTML","useOptionalT","React","Button","cn","IconBold","IconItalic","IconPilcrow","IconH1","IconH2","IconH3","IconList","IconListNumbers","LinkIcon","Dialog","DialogContent","DialogHeader","DialogTitle","Label","Input","DialogFooter","useEditor","EditorContent"],"sources":["../src/fields/rich-text.tsx"],"sourcesContent":["'use client';\nimport * as React from 'react';\nimport {\n IconBold,\n IconH1,\n IconH2,\n IconH3,\n IconItalic,\n IconLink as LinkIcon,\n IconList,\n IconListNumbers,\n IconPilcrow,\n} from '@tabler/icons-react';\nimport { useOptionalT } from '@burojs/react';\nimport { useEditor, EditorContent, type Editor } from '@tiptap/react';\nimport { generateHTML, generateJSON } from '@tiptap/core';\nimport StarterKit from '@tiptap/starter-kit';\n// NB: StarterKit already bundles `@tiptap/extension-link` in v3 — passing\n// `link: { ... }` configures the bundled instance rather than adding a\n// duplicate extension. Listing `@tiptap/extension-link` as a direct\n// runtime dep keeps the package's intent explicit and gives us a stable\n// import path for any future override.\nimport { Button } from '../primitives/button.js';\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../primitives/dialog.js';\nimport { Input } from '../primitives/input.js';\nimport { Label } from '../primitives/label.js';\nimport { cn } from '../lib/cn.js';\nimport type { FieldInputProps, FieldRendererProps } from './registry.js';\n\n/**\n * `<RichTextInput>` — TipTap-backed rich-text editor renderer (B5).\n *\n * Editor: TipTap v3 (`@tiptap/react` + `@tiptap/starter-kit` +\n * `@tiptap/extension-link`). See spec\n * `docs/superpowers/specs/2026-05-07-forms-rich-text.md` for the\n * editor + output-format decision rationale.\n *\n * Output format: HTML (`editor.getHTML()`). v0.2 ships only `'html'`;\n * markdown serialization is deferred to v0.3.\n *\n * Toolbar buttons:\n * - Bold / Italic — toggle marks\n * - Heading dropdown — H1 / H2 / H3 / Paragraph\n * - Bullet list / Ordered list — toggle node\n * - Link — opens an accessible Radix Dialog for URL entry\n *\n * Each toolbar button surfaces `aria-pressed` reflecting the current\n * mark/node active state so screen readers announce toggle state.\n *\n * SSR safety: `useEditor` returns null during the first server render\n * because TipTap's ProseMirror view depends on the DOM. The component\n * renders a placeholder dropzone-style div while editor is null so the\n * layout doesn't shift on hydration.\n *\n * aria-* propagation: `ariaInvalid` / `ariaDescribedBy` /\n * `ariaErrorMessage` land on the contenteditable wrapper via\n * `editorProps.attributes` so the form's error association follows the\n * focused control. The wrapper carries `role=\"textbox\"` +\n * `aria-multiline=\"true\"` so AT users hear the right control type.\n */\n\nexport interface RichTextInputProps extends FieldInputProps {\n /** Output format. v0.2 ships only `'html'`. */\n format?: 'html';\n}\n\ninterface ToolbarProps {\n editor: Editor;\n testid: string;\n onOpenLinkDialog: () => void;\n}\n\n/** Allowed link URL schemes — defends against `javascript:` and `data:`\n * smuggling in user-supplied URLs from the link dialog. */\nfunction isAllowedLinkScheme(url: string): boolean {\n if (typeof url !== 'string') return false;\n const trimmed = url.trim();\n if (trimmed.length === 0) return false;\n // Reject explicitly dangerous schemes regardless of casing/whitespace.\n const lowered = trimmed.toLowerCase();\n if (\n lowered.startsWith('javascript:') ||\n lowered.startsWith('data:') ||\n lowered.startsWith('vbscript:')\n ) {\n return false;\n }\n const allowed = ['http:', 'https:', 'mailto:', 'tel:'];\n try {\n const base =\n typeof window !== 'undefined' && window.location?.origin\n ? window.location.origin\n : 'http://localhost';\n const parsed = new URL(trimmed, base);\n return allowed.includes(parsed.protocol);\n } catch {\n // Allow relative URLs (no protocol) — the URL constructor would\n // have parsed them against the base above; if we land here the\n // string is genuinely unparseable.\n return false;\n }\n}\n\n/** Sanitize stored HTML by round-tripping through TipTap's schema.\n * Drops `<script>`, `<iframe>`, unknown nodes, and event-handler\n * attributes (`onerror=`, `onclick=`, …). Also normalizes any\n * `<a>` element to carry `rel=\"noopener noreferrer\"` and\n * `target=\"_blank\"` via the link extension's HTMLAttributes. */\nexport function sanitizeRichTextHtml(value: string): string {\n if (!value || typeof value !== 'string') return '';\n const extensions = [\n StarterKit.configure({\n link: {\n openOnClick: false,\n HTMLAttributes: {\n rel: 'noopener noreferrer',\n target: '_blank',\n },\n },\n }),\n ];\n try {\n const json = generateJSON(value, extensions);\n let html = generateHTML(json, extensions);\n // Belt-and-braces: TipTap's link extension applies rel/target to\n // links it parses, but we double-check via a regex pass so any\n // `<a>` that slipped through (e.g. inline anchors generated\n // outside the link extension) is still hardened.\n html = ensureSafeAnchorAttrs(html);\n return html;\n } catch {\n // If parse fails (malformed HTML the schema rejects outright),\n // return empty string rather than rendering raw markup.\n return '';\n }\n}\n\n/** Add `rel=\"noopener noreferrer\"` and `target=\"_blank\"` to every\n * `<a>` tag if missing. Idempotent — safe to call on already-safe\n * HTML. */\nfunction ensureSafeAnchorAttrs(html: string): string {\n return html.replace(/<a\\b([^>]*)>/gi, (match, attrs: string) => {\n const hasRel = /\\brel\\s*=/.test(attrs);\n const hasTarget = /\\btarget\\s*=/.test(attrs);\n let next = attrs;\n if (!hasRel) next += ' rel=\"noopener noreferrer\"';\n if (!hasTarget) next += ' target=\"_blank\"';\n return `<a${next}>`;\n });\n}\n\nfunction Toolbar({ editor, testid, onOpenLinkDialog }: ToolbarProps): React.ReactElement {\n const t = useOptionalT();\n // Subscribe to editor selection/transaction events so the toolbar\n // re-renders when the active mark/node state changes (toggles\n // aria-pressed correctly). useEditor already triggers re-render on\n // state change in TipTap v3 — this state mirror is a belt-and-braces\n // for testing-library's snapshot model.\n const [, forceUpdate] = React.useReducer((x: number) => x + 1, 0);\n React.useEffect(() => {\n const handler = () => forceUpdate();\n editor.on('selectionUpdate', handler);\n editor.on('transaction', handler);\n return () => {\n editor.off('selectionUpdate', handler);\n editor.off('transaction', handler);\n };\n }, [editor]);\n\n // Active-state probes. `editor.isActive('bold')` etc. is TipTap's\n // canonical predicate; it works with selection ranges + cursor\n // positions and accounts for marks already applied.\n const isBold = editor.isActive('bold');\n const isItalic = editor.isActive('italic');\n const isBulletList = editor.isActive('bulletList');\n const isOrderedList = editor.isActive('orderedList');\n const isH1 = editor.isActive('heading', { level: 1 });\n const isH2 = editor.isActive('heading', { level: 2 });\n const isH3 = editor.isActive('heading', { level: 3 });\n const isLink = editor.isActive('link');\n const isParagraph = editor.isActive('paragraph') && !isH1 && !isH2 && !isH3;\n\n return (\n <div\n className=\"flex flex-wrap items-center gap-1 border-b bg-muted/30 px-2 py-1.5\"\n role=\"toolbar\"\n aria-label={t('richText.toolbar', { defaultValue: 'Rich text formatting' })}\n data-testid={`${testid}:toolbar`}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isBold}\n aria-label={t('richText.bold', { defaultValue: 'Bold (Ctrl+B)' })}\n data-testid={`${testid}:bold`}\n data-active={isBold || undefined}\n onClick={() => editor.chain().focus().toggleBold().run()}\n className={cn('h-7 w-7 p-0', isBold && 'bg-accent')}\n >\n <IconBold className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isItalic}\n aria-label={t('richText.italic', { defaultValue: 'Italic (Ctrl+I)' })}\n data-testid={`${testid}:italic`}\n data-active={isItalic || undefined}\n onClick={() => editor.chain().focus().toggleItalic().run()}\n className={cn('h-7 w-7 p-0', isItalic && 'bg-accent')}\n >\n <IconItalic className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <span className=\"mx-1 h-4 w-px bg-border\" aria-hidden />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isParagraph}\n aria-label={t('richText.paragraph', { defaultValue: 'Paragraph (Ctrl+Alt+0)' })}\n data-testid={`${testid}:paragraph`}\n data-active={isParagraph || undefined}\n onClick={() => editor.chain().focus().setParagraph().run()}\n className={cn('h-7 w-7 p-0', isParagraph && 'bg-accent')}\n >\n <IconPilcrow className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isH1}\n aria-label={t('richText.heading1', { defaultValue: 'Heading 1 (Ctrl+Alt+1)' })}\n data-testid={`${testid}:h1`}\n data-active={isH1 || undefined}\n onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}\n className={cn('h-7 w-7 p-0', isH1 && 'bg-accent')}\n >\n <IconH1 className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isH2}\n aria-label={t('richText.heading2', { defaultValue: 'Heading 2 (Ctrl+Alt+2)' })}\n data-testid={`${testid}:h2`}\n data-active={isH2 || undefined}\n onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}\n className={cn('h-7 w-7 p-0', isH2 && 'bg-accent')}\n >\n <IconH2 className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isH3}\n aria-label={t('richText.heading3', { defaultValue: 'Heading 3 (Ctrl+Alt+3)' })}\n data-testid={`${testid}:h3`}\n data-active={isH3 || undefined}\n onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}\n className={cn('h-7 w-7 p-0', isH3 && 'bg-accent')}\n >\n <IconH3 className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <span className=\"mx-1 h-4 w-px bg-border\" aria-hidden />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isBulletList}\n aria-label={t('richText.bulletList', { defaultValue: 'Bullet list (Ctrl+Shift+8)' })}\n data-testid={`${testid}:bullet-list`}\n data-active={isBulletList || undefined}\n onClick={() => editor.chain().focus().toggleBulletList().run()}\n className={cn('h-7 w-7 p-0', isBulletList && 'bg-accent')}\n >\n <IconList className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isOrderedList}\n aria-label={t('richText.orderedList', { defaultValue: 'Ordered list (Ctrl+Shift+7)' })}\n data-testid={`${testid}:ordered-list`}\n data-active={isOrderedList || undefined}\n onClick={() => editor.chain().focus().toggleOrderedList().run()}\n className={cn('h-7 w-7 p-0', isOrderedList && 'bg-accent')}\n >\n <IconListNumbers className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <span className=\"mx-1 h-4 w-px bg-border\" aria-hidden />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isLink}\n aria-label={\n isLink\n ? t('richText.editLink', { defaultValue: 'Edit link (Ctrl+K)' })\n : t('richText.insertLink', { defaultValue: 'Insert link (Ctrl+K)' })\n }\n data-testid={`${testid}:link`}\n data-active={isLink || undefined}\n onClick={onOpenLinkDialog}\n className={cn('h-7 w-7 p-0', isLink && 'bg-accent')}\n >\n <LinkIcon className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n </div>\n );\n}\n\n/** Display variant: render the saved HTML through TipTap's sanitize\n * pass before injection. Falls back to em-dash when empty.\n * Wrapped in a `prose` container so saved HTML inherits typographic\n * defaults consistent with other display renderers.\n *\n * Security: legacy / adversarial HTML (e.g. records imported from a\n * spreadsheet, payloads from a compromised collaborator) may carry\n * `<script>` / `<iframe>` / `onerror=` smuggling attempts. The\n * sanitize pass round-trips the value through TipTap's schema,\n * dropping nodes outside the editor's allow-list and stripping\n * event-handler attributes. */\nexport function RichTextDisplay({ value }: FieldRendererProps): React.ReactElement {\n if (value == null || value === '') {\n return <span className=\"text-muted-foreground\">—</span>;\n }\n const html = sanitizeRichTextHtml(String(value));\n if (html === '') {\n return <span className=\"text-muted-foreground\">—</span>;\n }\n return (\n <div\n className=\"prose prose-sm max-w-none dark:prose-invert\"\n // biome-ignore lint/security/noDangerouslySetInnerHtml: sanitized via TipTap schema round-trip\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n\ninterface LinkDialogProps {\n open: boolean;\n initialUrl: string;\n onOpenChange: (open: boolean) => void;\n onSubmit: (url: string) => void;\n onRemove: () => void;\n testid: string;\n}\n\nfunction LinkDialog({\n open,\n initialUrl,\n onOpenChange,\n onSubmit,\n onRemove,\n testid,\n}: LinkDialogProps): React.ReactElement {\n const t = useOptionalT();\n const [url, setUrl] = React.useState(initialUrl);\n const [error, setError] = React.useState<string | null>(null);\n\n // Reset internal state when the dialog opens with a fresh URL.\n React.useEffect(() => {\n if (open) {\n setUrl(initialUrl);\n setError(null);\n }\n }, [open, initialUrl]);\n\n function handleSubmit() {\n if (url.trim() === '') {\n // Empty URL = remove link mark.\n onRemove();\n onOpenChange(false);\n return;\n }\n if (!isAllowedLinkScheme(url)) {\n setError('URL scheme not allowed (use http, https, mailto, or tel).');\n // eslint-disable-next-line no-console\n console.warn('[RichTextInput] Link URL rejected: scheme not in allowlist');\n return;\n }\n onSubmit(url);\n onOpenChange(false);\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent data-testid={`${testid}:link-dialog`}>\n <DialogHeader>\n <DialogTitle>{t('richText.addLinkTitle', { defaultValue: 'Add link' })}</DialogTitle>\n </DialogHeader>\n <div className=\"space-y-2\">\n <Label htmlFor={`${testid}:link-url`}>URL</Label>\n <Input\n id={`${testid}:link-url`}\n data-testid={`${testid}:link-url`}\n type=\"url\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n placeholder=\"https://example.com\"\n aria-label={t('richText.url', { defaultValue: 'URL' })}\n autoFocus\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleSubmit();\n }\n }}\n />\n {error ? (\n <p\n className=\"text-sm text-destructive\"\n data-testid={`${testid}:link-error`}\n role=\"alert\"\n >\n {error}\n </p>\n ) : null}\n </div>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => onOpenChange(false)}\n data-testid={`${testid}:link-cancel`}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => {\n onRemove();\n onOpenChange(false);\n }}\n data-testid={`${testid}:link-remove`}\n >\n Remove\n </Button>\n <Button type=\"button\" onClick={handleSubmit} data-testid={`${testid}:link-save`}>\n Save\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport function RichTextInput({\n name,\n id,\n value,\n onChange,\n testid,\n disabled,\n required,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n field,\n format: _formatProp,\n}: RichTextInputProps): React.ReactElement | null {\n const ui = (field?.ui ?? {}) as { format?: string; toolbar?: false };\n // v0.2 ships HTML output only — `format` is accepted (and may be\n // forwarded as a string e.g. `\"html\"`) but the renderer always\n // returns HTML. `_formatProp` is intentionally unused; kept on the\n // props type for forward-compat with v0.3 markdown.\n const showToolbar = ui.toolbar !== false;\n\n const valueStr = typeof value === 'string' ? value : '';\n const editorTestid = testid ?? `rich-text-input:${name}`;\n\n const [linkDialogOpen, setLinkDialogOpen] = React.useState(false);\n const [linkDialogInitial, setLinkDialogInitial] = React.useState('');\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({\n // StarterKit's bundled link extension. Pass options here instead\n // of registering @tiptap/extension-link separately to avoid the\n // \"Duplicate extension names\" warning.\n link: {\n openOnClick: false,\n autolink: true,\n // HTMLAttributes lands on every <a> rendered by the editor.\n // `rel=noopener noreferrer` is a security-best-practice for\n // user-authored content; `target=_blank` opens in a new tab.\n HTMLAttributes: {\n rel: 'noopener noreferrer',\n target: '_blank',\n },\n },\n }),\n ],\n content: valueStr,\n editable: !disabled,\n // SSR safety: TipTap recommends setting this to false to avoid\n // hydration mismatches when the server-rendered HTML differs from\n // the editor's prosemirror DOM. We render the editor only on\n // client mount via the `if (!editor) return null` branch below.\n immediatelyRender: false,\n editorProps: {\n attributes: {\n role: 'textbox',\n 'aria-multiline': 'true',\n 'aria-required': required ? 'true' : 'false',\n ...(ariaInvalid ? { 'aria-invalid': 'true' } : {}),\n ...(ariaDescribedBy ? { 'aria-describedby': ariaDescribedBy } : {}),\n ...(ariaErrorMessage ? { 'aria-errormessage': ariaErrorMessage } : {}),\n ...(id ? { id } : {}),\n 'data-testid': `${editorTestid}:editor`,\n // Tailwind classes on the contenteditable element for typography.\n // `prose-sm` matches the display variant so the editor and the\n // saved view look consistent.\n class:\n 'prose prose-sm max-w-none dark:prose-invert px-3 py-2 min-h-[6rem] focus:outline-hidden',\n },\n },\n onUpdate: ({ editor: ed }) => {\n // TipTap returns `<p></p>` for an empty editor — normalize that\n // to '' so the form treats \"no content\" identically regardless\n // of whether the user typed-then-deleted or never typed at all.\n const html = ed.getHTML();\n onChange(html === '<p></p>' ? '' : html);\n },\n });\n\n const openLinkDialog = React.useCallback(() => {\n if (!editor) return;\n const existingHref = (editor.getAttributes('link').href as string | undefined) ?? '';\n setLinkDialogInitial(existingHref);\n setLinkDialogOpen(true);\n }, [editor]);\n\n const submitLink = React.useCallback(\n (href: string) => {\n if (!editor) return;\n editor.chain().focus().extendMarkRange('link').setLink({ href }).run();\n },\n [editor],\n );\n\n const removeLink = React.useCallback(() => {\n if (!editor) return;\n editor.chain().focus().unsetLink().run();\n }, [editor]);\n\n // Sync external value changes (e.g. record loaded after first paint,\n // or programmatic clear) into the editor without triggering onUpdate\n // recursion. TipTap exposes `setContent` with `emitUpdate: false`.\n React.useEffect(() => {\n if (!editor) return;\n const current = editor.getHTML();\n const incoming = valueStr === '' ? '<p></p>' : valueStr;\n if (current === incoming) return;\n editor.commands.setContent(valueStr, { emitUpdate: false });\n }, [editor, valueStr]);\n\n // Reflect `disabled` prop changes into TipTap's editable state.\n React.useEffect(() => {\n if (!editor) return;\n editor.setEditable(!disabled);\n }, [editor, disabled]);\n\n // SSR / first-render safety: `useEditor` returns null on initial\n // server-side render (immediatelyRender: false). Render a stable\n // placeholder so the layout doesn't shift on hydration. The\n // placeholder carries the same id/data-testid as the eventual\n // contenteditable so test selectors resolve consistently.\n if (!editor) {\n return (\n <div className=\"rounded-md border bg-background\" data-testid={`${editorTestid}:root`}>\n <div\n className=\"flex h-9 items-center border-b bg-muted/30 px-2 text-xs text-muted-foreground\"\n aria-hidden\n >\n Loading editor…\n </div>\n <div\n id={id}\n data-testid={`${editorTestid}:editor`}\n className=\"min-h-[6rem] px-3 py-2 text-sm text-muted-foreground\"\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-busy=\"true\"\n >\n {valueStr ? '' : 'Rich text editor loading…'}\n </div>\n {/* Hidden form input so the `name` is still submittable in\n uncontrolled-form scenarios; the form reads from state, but\n this keeps the DOM well-formed. */}\n <input type=\"hidden\" name={name} value={valueStr} />\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'rounded-md border bg-background text-foreground focus-within:ring-2 focus-within:ring-ring',\n ariaInvalid && 'border-destructive focus-within:ring-destructive',\n disabled && 'opacity-60',\n )}\n data-testid={`${editorTestid}:root`}\n data-invalid={ariaInvalid || undefined}\n >\n {showToolbar ? (\n <Toolbar editor={editor} testid={editorTestid} onOpenLinkDialog={openLinkDialog} />\n ) : null}\n <EditorContent editor={editor} />\n <LinkDialog\n open={linkDialogOpen}\n initialUrl={linkDialogInitial}\n onOpenChange={setLinkDialogOpen}\n onSubmit={submitLink}\n onRemove={removeLink}\n testid={editorTestid}\n />\n {/* Hidden field carrying the canonical name for forms that read\n via the DOM (rare; the form controller reads from state). */}\n <input type=\"hidden\" name={name} value={valueStr} />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rich-text-vTyVjDoa.cjs","names":["StarterKit","generateJSON","generateHTML","useOptionalT","React","Button","cn","IconBold","IconItalic","IconPilcrow","IconH1","IconH2","IconH3","IconList","IconListNumbers","LinkIcon","Dialog","DialogContent","DialogHeader","DialogTitle","Label","Input","DialogFooter","useEditor","EditorContent"],"sources":["../src/fields/rich-text.tsx"],"sourcesContent":["'use client';\nimport * as React from 'react';\nimport {\n IconBold,\n IconH1,\n IconH2,\n IconH3,\n IconItalic,\n IconLink as LinkIcon,\n IconList,\n IconListNumbers,\n IconPilcrow,\n} from '@tabler/icons-react';\nimport { useOptionalT } from '@burojs/react';\nimport { useEditor, EditorContent, type Editor } from '@tiptap/react';\nimport { generateHTML, generateJSON } from '@tiptap/core';\nimport StarterKit from '@tiptap/starter-kit';\n// NB: StarterKit already bundles `@tiptap/extension-link` in v3 — passing\n// `link: { ... }` configures the bundled instance rather than adding a\n// duplicate extension. Listing `@tiptap/extension-link` as a direct\n// runtime dep keeps the package's intent explicit and gives us a stable\n// import path for any future override.\nimport { Button } from '../primitives/button.js';\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../primitives/dialog.js';\nimport { Input } from '../primitives/input.js';\nimport { Label } from '../primitives/label.js';\nimport { cn } from '../lib/cn.js';\nimport type { FieldInputProps, FieldRendererProps } from './registry.js';\n\n/**\n * `<RichTextInput>` — TipTap-backed rich-text editor renderer (B5).\n *\n * Editor: TipTap v3 (`@tiptap/react` + `@tiptap/starter-kit` +\n * `@tiptap/extension-link`). See spec\n * `docs/superpowers/specs/2026-05-07-forms-rich-text.md` for the\n * editor + output-format decision rationale.\n *\n * Output format: HTML (`editor.getHTML()`). v0.2 ships only `'html'`;\n * markdown serialization is deferred to v0.3.\n *\n * Toolbar buttons:\n * - Bold / Italic — toggle marks\n * - Heading dropdown — H1 / H2 / H3 / Paragraph\n * - Bullet list / Ordered list — toggle node\n * - Link — opens an accessible Radix Dialog for URL entry\n *\n * Each toolbar button surfaces `aria-pressed` reflecting the current\n * mark/node active state so screen readers announce toggle state.\n *\n * SSR safety: `useEditor` returns null during the first server render\n * because TipTap's ProseMirror view depends on the DOM. The component\n * renders a placeholder dropzone-style div while editor is null so the\n * layout doesn't shift on hydration.\n *\n * aria-* propagation: `ariaInvalid` / `ariaDescribedBy` /\n * `ariaErrorMessage` land on the contenteditable wrapper via\n * `editorProps.attributes` so the form's error association follows the\n * focused control. The wrapper carries `role=\"textbox\"` +\n * `aria-multiline=\"true\"` so AT users hear the right control type.\n */\n\nexport interface RichTextInputProps extends FieldInputProps {\n /** Output format. v0.2 ships only `'html'`. */\n format?: 'html';\n}\n\ninterface ToolbarProps {\n editor: Editor;\n testid: string;\n onOpenLinkDialog: () => void;\n}\n\n/** Allowed link URL schemes — defends against `javascript:` and `data:`\n * smuggling in user-supplied URLs from the link dialog. */\nfunction isAllowedLinkScheme(url: string): boolean {\n if (typeof url !== 'string') return false;\n const trimmed = url.trim();\n if (trimmed.length === 0) return false;\n // Reject explicitly dangerous schemes regardless of casing/whitespace.\n const lowered = trimmed.toLowerCase();\n if (\n lowered.startsWith('javascript:') ||\n lowered.startsWith('data:') ||\n lowered.startsWith('vbscript:')\n ) {\n return false;\n }\n const allowed = ['http:', 'https:', 'mailto:', 'tel:'];\n try {\n const base =\n typeof window !== 'undefined' && window.location?.origin\n ? window.location.origin\n : 'http://localhost';\n const parsed = new URL(trimmed, base);\n return allowed.includes(parsed.protocol);\n } catch {\n // Allow relative URLs (no protocol) — the URL constructor would\n // have parsed them against the base above; if we land here the\n // string is genuinely unparseable.\n return false;\n }\n}\n\n/** Sanitize stored HTML by round-tripping through TipTap's schema.\n * Drops `<script>`, `<iframe>`, unknown nodes, and event-handler\n * attributes (`onerror=`, `onclick=`, …). Also normalizes any\n * `<a>` element to carry `rel=\"noopener noreferrer\"` and\n * `target=\"_blank\"` via the link extension's HTMLAttributes. */\nexport function sanitizeRichTextHtml(value: string): string {\n if (!value || typeof value !== 'string') return '';\n const extensions = [\n StarterKit.configure({\n link: {\n openOnClick: false,\n HTMLAttributes: {\n rel: 'noopener noreferrer',\n target: '_blank',\n },\n },\n }),\n ];\n try {\n const json = generateJSON(value, extensions);\n let html = generateHTML(json, extensions);\n // Belt-and-braces: TipTap's link extension applies rel/target to\n // links it parses, but we double-check via a regex pass so any\n // `<a>` that slipped through (e.g. inline anchors generated\n // outside the link extension) is still hardened.\n html = ensureSafeAnchorAttrs(html);\n return html;\n } catch {\n // If parse fails (malformed HTML the schema rejects outright),\n // return empty string rather than rendering raw markup.\n return '';\n }\n}\n\n/** Add `rel=\"noopener noreferrer\"` and `target=\"_blank\"` to every\n * `<a>` tag if missing. Idempotent — safe to call on already-safe\n * HTML. */\nfunction ensureSafeAnchorAttrs(html: string): string {\n return html.replace(/<a\\b([^>]*)>/gi, (match, attrs: string) => {\n const hasRel = /\\brel\\s*=/.test(attrs);\n const hasTarget = /\\btarget\\s*=/.test(attrs);\n let next = attrs;\n if (!hasRel) next += ' rel=\"noopener noreferrer\"';\n if (!hasTarget) next += ' target=\"_blank\"';\n return `<a${next}>`;\n });\n}\n\nfunction Toolbar({ editor, testid, onOpenLinkDialog }: ToolbarProps): React.ReactElement {\n const t = useOptionalT();\n // Subscribe to editor selection/transaction events so the toolbar\n // re-renders when the active mark/node state changes (toggles\n // aria-pressed correctly). useEditor already triggers re-render on\n // state change in TipTap v3 — this state mirror is a belt-and-braces\n // for testing-library's snapshot model.\n const [, forceUpdate] = React.useReducer((x: number) => x + 1, 0);\n React.useEffect(() => {\n const handler = () => forceUpdate();\n editor.on('selectionUpdate', handler);\n editor.on('transaction', handler);\n return () => {\n editor.off('selectionUpdate', handler);\n editor.off('transaction', handler);\n };\n }, [editor]);\n\n // Active-state probes. `editor.isActive('bold')` etc. is TipTap's\n // canonical predicate; it works with selection ranges + cursor\n // positions and accounts for marks already applied.\n const isBold = editor.isActive('bold');\n const isItalic = editor.isActive('italic');\n const isBulletList = editor.isActive('bulletList');\n const isOrderedList = editor.isActive('orderedList');\n const isH1 = editor.isActive('heading', { level: 1 });\n const isH2 = editor.isActive('heading', { level: 2 });\n const isH3 = editor.isActive('heading', { level: 3 });\n const isLink = editor.isActive('link');\n const isParagraph = editor.isActive('paragraph') && !isH1 && !isH2 && !isH3;\n\n return (\n <div\n className=\"flex flex-wrap items-center gap-1 border-b bg-muted/30 px-2 py-1.5\"\n role=\"toolbar\"\n aria-label={t('richText.toolbar', { defaultValue: 'Rich text formatting' })}\n data-testid={`${testid}:toolbar`}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isBold}\n aria-label={t('richText.bold', { defaultValue: 'Bold (Ctrl+B)' })}\n data-testid={`${testid}:bold`}\n data-active={isBold || undefined}\n onClick={() => editor.chain().focus().toggleBold().run()}\n className={cn('h-7 w-7 p-0', isBold && 'bg-accent')}\n >\n <IconBold className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isItalic}\n aria-label={t('richText.italic', { defaultValue: 'Italic (Ctrl+I)' })}\n data-testid={`${testid}:italic`}\n data-active={isItalic || undefined}\n onClick={() => editor.chain().focus().toggleItalic().run()}\n className={cn('h-7 w-7 p-0', isItalic && 'bg-accent')}\n >\n <IconItalic className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <span className=\"mx-1 h-4 w-px bg-border\" aria-hidden />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isParagraph}\n aria-label={t('richText.paragraph', { defaultValue: 'Paragraph (Ctrl+Alt+0)' })}\n data-testid={`${testid}:paragraph`}\n data-active={isParagraph || undefined}\n onClick={() => editor.chain().focus().setParagraph().run()}\n className={cn('h-7 w-7 p-0', isParagraph && 'bg-accent')}\n >\n <IconPilcrow className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isH1}\n aria-label={t('richText.heading1', { defaultValue: 'Heading 1 (Ctrl+Alt+1)' })}\n data-testid={`${testid}:h1`}\n data-active={isH1 || undefined}\n onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}\n className={cn('h-7 w-7 p-0', isH1 && 'bg-accent')}\n >\n <IconH1 className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isH2}\n aria-label={t('richText.heading2', { defaultValue: 'Heading 2 (Ctrl+Alt+2)' })}\n data-testid={`${testid}:h2`}\n data-active={isH2 || undefined}\n onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}\n className={cn('h-7 w-7 p-0', isH2 && 'bg-accent')}\n >\n <IconH2 className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isH3}\n aria-label={t('richText.heading3', { defaultValue: 'Heading 3 (Ctrl+Alt+3)' })}\n data-testid={`${testid}:h3`}\n data-active={isH3 || undefined}\n onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}\n className={cn('h-7 w-7 p-0', isH3 && 'bg-accent')}\n >\n <IconH3 className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <span className=\"mx-1 h-4 w-px bg-border\" aria-hidden />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isBulletList}\n aria-label={t('richText.bulletList', { defaultValue: 'Bullet list (Ctrl+Shift+8)' })}\n data-testid={`${testid}:bullet-list`}\n data-active={isBulletList || undefined}\n onClick={() => editor.chain().focus().toggleBulletList().run()}\n className={cn('h-7 w-7 p-0', isBulletList && 'bg-accent')}\n >\n <IconList className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isOrderedList}\n aria-label={t('richText.orderedList', { defaultValue: 'Ordered list (Ctrl+Shift+7)' })}\n data-testid={`${testid}:ordered-list`}\n data-active={isOrderedList || undefined}\n onClick={() => editor.chain().focus().toggleOrderedList().run()}\n className={cn('h-7 w-7 p-0', isOrderedList && 'bg-accent')}\n >\n <IconListNumbers className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n <span className=\"mx-1 h-4 w-px bg-border\" aria-hidden />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={isLink}\n aria-label={\n isLink\n ? t('richText.editLink', { defaultValue: 'Edit link (Ctrl+K)' })\n : t('richText.insertLink', { defaultValue: 'Insert link (Ctrl+K)' })\n }\n data-testid={`${testid}:link`}\n data-active={isLink || undefined}\n onClick={onOpenLinkDialog}\n className={cn('h-7 w-7 p-0', isLink && 'bg-accent')}\n >\n <LinkIcon className=\"h-3.5 w-3.5\" aria-hidden />\n </Button>\n </div>\n );\n}\n\n/** Display variant: render the saved HTML through TipTap's sanitize\n * pass before injection. Falls back to em-dash when empty.\n * Wrapped in a `prose` container so saved HTML inherits typographic\n * defaults consistent with other display renderers.\n *\n * Security: legacy / adversarial HTML (e.g. records imported from a\n * spreadsheet, payloads from a compromised collaborator) may carry\n * `<script>` / `<iframe>` / `onerror=` smuggling attempts. The\n * sanitize pass round-trips the value through TipTap's schema,\n * dropping nodes outside the editor's allow-list and stripping\n * event-handler attributes. */\nexport function RichTextDisplay({ value }: FieldRendererProps): React.ReactElement {\n if (value == null || value === '') {\n return <span className=\"text-muted-foreground\">—</span>;\n }\n const html = sanitizeRichTextHtml(String(value));\n if (html === '') {\n return <span className=\"text-muted-foreground\">—</span>;\n }\n return (\n <div\n className=\"prose prose-sm max-w-none dark:prose-invert\"\n // biome-ignore lint/security/noDangerouslySetInnerHtml: sanitized via TipTap schema round-trip\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n\ninterface LinkDialogProps {\n open: boolean;\n initialUrl: string;\n onOpenChange: (open: boolean) => void;\n onSubmit: (url: string) => void;\n onRemove: () => void;\n testid: string;\n}\n\nfunction LinkDialog({\n open,\n initialUrl,\n onOpenChange,\n onSubmit,\n onRemove,\n testid,\n}: LinkDialogProps): React.ReactElement {\n const t = useOptionalT();\n const [url, setUrl] = React.useState(initialUrl);\n const [error, setError] = React.useState<string | null>(null);\n\n // Reset internal state when the dialog opens with a fresh URL.\n React.useEffect(() => {\n if (open) {\n setUrl(initialUrl);\n setError(null);\n }\n }, [open, initialUrl]);\n\n function handleSubmit() {\n if (url.trim() === '') {\n // Empty URL = remove link mark.\n onRemove();\n onOpenChange(false);\n return;\n }\n if (!isAllowedLinkScheme(url)) {\n setError('URL scheme not allowed (use http, https, mailto, or tel).');\n // eslint-disable-next-line no-console\n console.warn('[RichTextInput] Link URL rejected: scheme not in allowlist');\n return;\n }\n onSubmit(url);\n onOpenChange(false);\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent data-testid={`${testid}:link-dialog`}>\n <DialogHeader>\n <DialogTitle>{t('richText.addLinkTitle', { defaultValue: 'Add link' })}</DialogTitle>\n </DialogHeader>\n <div className=\"space-y-2\">\n <Label htmlFor={`${testid}:link-url`}>URL</Label>\n <Input\n id={`${testid}:link-url`}\n data-testid={`${testid}:link-url`}\n type=\"url\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n placeholder=\"https://example.com\"\n aria-label={t('richText.url', { defaultValue: 'URL' })}\n autoFocus\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleSubmit();\n }\n }}\n />\n {error ? (\n <p\n className=\"text-sm text-destructive\"\n data-testid={`${testid}:link-error`}\n role=\"alert\"\n >\n {error}\n </p>\n ) : null}\n </div>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => onOpenChange(false)}\n data-testid={`${testid}:link-cancel`}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => {\n onRemove();\n onOpenChange(false);\n }}\n data-testid={`${testid}:link-remove`}\n >\n Remove\n </Button>\n <Button type=\"button\" onClick={handleSubmit} data-testid={`${testid}:link-save`}>\n Save\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport function RichTextInput({\n name,\n id,\n value,\n onChange,\n testid,\n disabled,\n required,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n field,\n format: _formatProp,\n}: RichTextInputProps): React.ReactElement | null {\n const ui = (field?.ui ?? {}) as { format?: string; formatting?: false };\n // v0.2 ships HTML output only — `format` is accepted (and may be\n // forwarded as a string e.g. `\"html\"`) but the renderer always\n // returns HTML. `_formatProp` is intentionally unused; kept on the\n // props type for forward-compat with v0.3 markdown.\n const showToolbar = ui.formatting !== false;\n\n const valueStr = typeof value === 'string' ? value : '';\n const editorTestid = testid ?? `rich-text-input:${name}`;\n\n const [linkDialogOpen, setLinkDialogOpen] = React.useState(false);\n const [linkDialogInitial, setLinkDialogInitial] = React.useState('');\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({\n // StarterKit's bundled link extension. Pass options here instead\n // of registering @tiptap/extension-link separately to avoid the\n // \"Duplicate extension names\" warning.\n link: {\n openOnClick: false,\n autolink: true,\n // HTMLAttributes lands on every <a> rendered by the editor.\n // `rel=noopener noreferrer` is a security-best-practice for\n // user-authored content; `target=_blank` opens in a new tab.\n HTMLAttributes: {\n rel: 'noopener noreferrer',\n target: '_blank',\n },\n },\n }),\n ],\n content: valueStr,\n editable: !disabled,\n // SSR safety: TipTap recommends setting this to false to avoid\n // hydration mismatches when the server-rendered HTML differs from\n // the editor's prosemirror DOM. We render the editor only on\n // client mount via the `if (!editor) return null` branch below.\n immediatelyRender: false,\n editorProps: {\n attributes: {\n role: 'textbox',\n 'aria-multiline': 'true',\n 'aria-required': required ? 'true' : 'false',\n ...(ariaInvalid ? { 'aria-invalid': 'true' } : {}),\n ...(ariaDescribedBy ? { 'aria-describedby': ariaDescribedBy } : {}),\n ...(ariaErrorMessage ? { 'aria-errormessage': ariaErrorMessage } : {}),\n ...(id ? { id } : {}),\n 'data-testid': `${editorTestid}:editor`,\n // Tailwind classes on the contenteditable element for typography.\n // `prose-sm` matches the display variant so the editor and the\n // saved view look consistent.\n class:\n 'prose prose-sm max-w-none dark:prose-invert px-3 py-2 min-h-[6rem] focus:outline-hidden',\n },\n },\n onUpdate: ({ editor: ed }) => {\n // TipTap returns `<p></p>` for an empty editor — normalize that\n // to '' so the form treats \"no content\" identically regardless\n // of whether the user typed-then-deleted or never typed at all.\n const html = ed.getHTML();\n onChange(html === '<p></p>' ? '' : html);\n },\n });\n\n const openLinkDialog = React.useCallback(() => {\n if (!editor) return;\n const existingHref = (editor.getAttributes('link').href as string | undefined) ?? '';\n setLinkDialogInitial(existingHref);\n setLinkDialogOpen(true);\n }, [editor]);\n\n const submitLink = React.useCallback(\n (href: string) => {\n if (!editor) return;\n editor.chain().focus().extendMarkRange('link').setLink({ href }).run();\n },\n [editor],\n );\n\n const removeLink = React.useCallback(() => {\n if (!editor) return;\n editor.chain().focus().unsetLink().run();\n }, [editor]);\n\n // Sync external value changes (e.g. record loaded after first paint,\n // or programmatic clear) into the editor without triggering onUpdate\n // recursion. TipTap exposes `setContent` with `emitUpdate: false`.\n React.useEffect(() => {\n if (!editor) return;\n const current = editor.getHTML();\n const incoming = valueStr === '' ? '<p></p>' : valueStr;\n if (current === incoming) return;\n editor.commands.setContent(valueStr, { emitUpdate: false });\n }, [editor, valueStr]);\n\n // Reflect `disabled` prop changes into TipTap's editable state.\n React.useEffect(() => {\n if (!editor) return;\n editor.setEditable(!disabled);\n }, [editor, disabled]);\n\n // SSR / first-render safety: `useEditor` returns null on initial\n // server-side render (immediatelyRender: false). Render a stable\n // placeholder so the layout doesn't shift on hydration. The\n // placeholder carries the same id/data-testid as the eventual\n // contenteditable so test selectors resolve consistently.\n if (!editor) {\n return (\n <div className=\"rounded-md border bg-background\" data-testid={`${editorTestid}:root`}>\n <div\n className=\"flex h-9 items-center border-b bg-muted/30 px-2 text-xs text-muted-foreground\"\n aria-hidden\n >\n Loading editor…\n </div>\n <div\n id={id}\n data-testid={`${editorTestid}:editor`}\n className=\"min-h-[6rem] px-3 py-2 text-sm text-muted-foreground\"\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-busy=\"true\"\n >\n {valueStr ? '' : 'Rich text editor loading…'}\n </div>\n {/* Hidden form input so the `name` is still submittable in\n uncontrolled-form scenarios; the form reads from state, but\n this keeps the DOM well-formed. */}\n <input type=\"hidden\" name={name} value={valueStr} />\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'rounded-md border bg-background text-foreground focus-within:ring-2 focus-within:ring-ring',\n ariaInvalid && 'border-destructive focus-within:ring-destructive',\n disabled && 'opacity-60',\n )}\n data-testid={`${editorTestid}:root`}\n data-invalid={ariaInvalid || undefined}\n >\n {showToolbar ? (\n <Toolbar editor={editor} testid={editorTestid} onOpenLinkDialog={openLinkDialog} />\n ) : null}\n <EditorContent editor={editor} />\n <LinkDialog\n open={linkDialogOpen}\n initialUrl={linkDialogInitial}\n onOpenChange={setLinkDialogOpen}\n onSubmit={submitLink}\n onRemove={removeLink}\n testid={editorTestid}\n />\n {/* Hidden field carrying the canonical name for forms that read\n via the DOM (rare; the form controller reads from state). */}\n <input type=\"hidden\" name={name} value={valueStr} />\n </div>\n );\n}\n"],"mappings":"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"}
|