@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.
Files changed (137) hide show
  1. package/README.md +22 -5
  2. package/dist/bulk-summary-CwSd6Yjb.cjs +108 -0
  3. package/dist/bulk-summary-CwSd6Yjb.cjs.map +1 -0
  4. package/dist/bulk-summary-GxMDP4bw.js +108 -0
  5. package/dist/bulk-summary-GxMDP4bw.js.map +1 -0
  6. package/dist/button-BxnnE28H.cjs +55 -0
  7. package/dist/button-BxnnE28H.cjs.map +1 -0
  8. package/dist/button-MWVeZaqj.js +44 -0
  9. package/dist/button-MWVeZaqj.js.map +1 -0
  10. package/dist/candidates-gate-CX2PBu6l.js +63 -0
  11. package/dist/candidates-gate-CX2PBu6l.js.map +1 -0
  12. package/dist/candidates-gate-DiFI52r7.cjs +70 -0
  13. package/dist/candidates-gate-DiFI52r7.cjs.map +1 -0
  14. package/dist/candidates-relation-picker-CBsVcnPR.js +82 -0
  15. package/dist/candidates-relation-picker-CBsVcnPR.js.map +1 -0
  16. package/dist/candidates-relation-picker-Dd_GVLlu.cjs +84 -0
  17. package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +1 -0
  18. package/dist/check-confirm-dialog-BRByTFpg.cjs +1534 -0
  19. package/dist/check-confirm-dialog-BRByTFpg.cjs.map +1 -0
  20. package/dist/check-confirm-dialog-CkaVTPT9.js +1232 -0
  21. package/dist/check-confirm-dialog-CkaVTPT9.js.map +1 -0
  22. package/dist/dialog-B_rt9S1c.js +63 -0
  23. package/dist/dialog-B_rt9S1c.js.map +1 -0
  24. package/dist/dialog-DRNUq9EA.cjs +112 -0
  25. package/dist/dialog-DRNUq9EA.cjs.map +1 -0
  26. package/dist/filter-builder-B3mZFL-K.cjs +1761 -0
  27. package/dist/filter-builder-B3mZFL-K.cjs.map +1 -0
  28. package/dist/filter-builder-DOnaXNfP.js +1605 -0
  29. package/dist/filter-builder-DOnaXNfP.js.map +1 -0
  30. package/dist/flow-runtime-B6zOHYqI.cjs +255 -0
  31. package/dist/flow-runtime-B6zOHYqI.cjs.map +1 -0
  32. package/dist/flow-runtime-D8h99FSg.js +252 -0
  33. package/dist/flow-runtime-D8h99FSg.js.map +1 -0
  34. package/dist/flow-screen-CX05b3G9.cjs +705 -0
  35. package/dist/flow-screen-CX05b3G9.cjs.map +1 -0
  36. package/dist/flow-screen-CmbARSqg.js +701 -0
  37. package/dist/flow-screen-CmbARSqg.js.map +1 -0
  38. package/dist/index.cjs +2825 -11912
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +1612 -1862
  41. package/dist/index.d.ts +1700 -1950
  42. package/dist/index.js +2498 -11605
  43. package/dist/index.js.map +1 -1
  44. package/dist/inline-flow-host-B7U1OJUe.cjs +112 -0
  45. package/dist/inline-flow-host-B7U1OJUe.cjs.map +1 -0
  46. package/dist/inline-flow-host-C3H2UpSA.js +112 -0
  47. package/dist/inline-flow-host-C3H2UpSA.js.map +1 -0
  48. package/dist/input-BI0czGyI.cjs +23 -0
  49. package/dist/input-BI0czGyI.cjs.map +1 -0
  50. package/dist/input-CUPStzZO.js +17 -0
  51. package/dist/input-CUPStzZO.js.map +1 -0
  52. package/dist/label-66W1qbke.js +29 -0
  53. package/dist/label-66W1qbke.js.map +1 -0
  54. package/dist/label-w6JYf2cF.cjs +36 -0
  55. package/dist/label-w6JYf2cF.cjs.map +1 -0
  56. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +14 -0
  57. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +1 -0
  58. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +19 -0
  59. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +1 -0
  60. package/dist/markdown/index.cjs +1 -1
  61. package/dist/markdown/index.js +1 -1
  62. package/dist/page-flow-route-CQ7HsbBm.cjs +277 -0
  63. package/dist/page-flow-route-CQ7HsbBm.cjs.map +1 -0
  64. package/dist/page-flow-route-CyXSvXSE.js +277 -0
  65. package/dist/page-flow-route-CyXSvXSE.js.map +1 -0
  66. package/dist/peek-filter-CnqHwTMU.cjs +51 -0
  67. package/dist/peek-filter-CnqHwTMU.cjs.map +1 -0
  68. package/dist/peek-filter-ErLfO153.js +28 -0
  69. package/dist/peek-filter-ErLfO153.js.map +1 -0
  70. package/dist/place-node--GdsP688.js +7315 -0
  71. package/dist/place-node--GdsP688.js.map +1 -0
  72. package/dist/place-node-SIp4XYQH.cjs +7833 -0
  73. package/dist/place-node-SIp4XYQH.cjs.map +1 -0
  74. package/dist/related-list-cards-B6x9HQQp.js +42 -0
  75. package/dist/related-list-cards-B6x9HQQp.js.map +1 -0
  76. package/dist/related-list-cards-nxuODlIA.cjs +42 -0
  77. package/dist/related-list-cards-nxuODlIA.cjs.map +1 -0
  78. package/dist/related-list-timeline-BYJKEbmA.cjs +142 -0
  79. package/dist/related-list-timeline-BYJKEbmA.cjs.map +1 -0
  80. package/dist/related-list-timeline-DEdfd-rr.js +142 -0
  81. package/dist/related-list-timeline-DEdfd-rr.js.map +1 -0
  82. package/dist/related-rows-B4levHcR.js +186 -0
  83. package/dist/related-rows-B4levHcR.js.map +1 -0
  84. package/dist/related-rows-zh5f_4QR.cjs +209 -0
  85. package/dist/related-rows-zh5f_4QR.cjs.map +1 -0
  86. package/dist/relation-create-BOaUe12G.cjs +362 -0
  87. package/dist/relation-create-BOaUe12G.cjs.map +1 -0
  88. package/dist/relation-create-Bg-eEJLs.js +337 -0
  89. package/dist/relation-create-Bg-eEJLs.js.map +1 -0
  90. package/dist/relation-many-picker-B-NCEUgX.cjs +465 -0
  91. package/dist/relation-many-picker-B-NCEUgX.cjs.map +1 -0
  92. package/dist/relation-many-picker-gdAOD-qK.js +464 -0
  93. package/dist/relation-many-picker-gdAOD-qK.js.map +1 -0
  94. package/dist/relation-picker-BAY77ivO.js +462 -0
  95. package/dist/relation-picker-BAY77ivO.js.map +1 -0
  96. package/dist/relation-picker-Da_uhFvA.cjs +479 -0
  97. package/dist/relation-picker-Da_uhFvA.cjs.map +1 -0
  98. package/dist/relation-picker-dialog-M0pQaWlp.cjs +300 -0
  99. package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +1 -0
  100. package/dist/relation-picker-dialog-hA9rYrZB.js +299 -0
  101. package/dist/relation-picker-dialog-hA9rYrZB.js.map +1 -0
  102. package/dist/resource-page-router-OSQyRA5b.cjs +1779 -0
  103. package/dist/resource-page-router-OSQyRA5b.cjs.map +1 -0
  104. package/dist/resource-page-router-T4vS5gbJ.js +1582 -0
  105. package/dist/resource-page-router-T4vS5gbJ.js.map +1 -0
  106. package/dist/{rich-text-IDFvyvRE.js → rich-text-DFerywgz.js} +7 -4
  107. package/dist/{rich-text-IDFvyvRE.js.map → rich-text-DFerywgz.js.map} +1 -1
  108. package/dist/{rich-text-lndbYqzR.cjs → rich-text-vTyVjDoa.cjs} +25 -22
  109. package/dist/{rich-text-lndbYqzR.cjs.map → rich-text-vTyVjDoa.cjs.map} +1 -1
  110. package/dist/search-access-B7G5oR8n.cjs +812 -0
  111. package/dist/search-access-B7G5oR8n.cjs.map +1 -0
  112. package/dist/search-access-CURsWn-U.js +669 -0
  113. package/dist/search-access-CURsWn-U.js.map +1 -0
  114. package/dist/shell-BPlk8hEa.js +205 -0
  115. package/dist/shell-BPlk8hEa.js.map +1 -0
  116. package/dist/shell-DxhlsPwm.cjs +271 -0
  117. package/dist/shell-DxhlsPwm.cjs.map +1 -0
  118. package/dist/skeleton-U2noefj5.js +13 -0
  119. package/dist/skeleton-U2noefj5.js.map +1 -0
  120. package/dist/skeleton-_1gP7wan.cjs +18 -0
  121. package/dist/skeleton-_1gP7wan.cjs.map +1 -0
  122. package/dist/styles.css +241 -9
  123. package/dist/toaster-C7CiYA4Z.cjs +248 -0
  124. package/dist/toaster-C7CiYA4Z.cjs.map +1 -0
  125. package/dist/toaster-CfCRLgR-.js +164 -0
  126. package/dist/toaster-CfCRLgR-.js.map +1 -0
  127. package/dist/use-dialog-filter-state-BBCC0OEK.cjs +92 -0
  128. package/dist/use-dialog-filter-state-BBCC0OEK.cjs.map +1 -0
  129. package/dist/use-dialog-filter-state-BDcgS5Oz.js +87 -0
  130. package/dist/use-dialog-filter-state-BDcgS5Oz.js.map +1 -0
  131. package/dist/{use-theme-BwPWinB2.cjs → use-theme-DFlpXils.cjs} +12 -1
  132. package/dist/use-theme-DFlpXils.cjs.map +1 -0
  133. package/dist/{use-theme-IHmtrhEt.js → use-theme-dnE3Od3I.js} +7 -2
  134. package/dist/use-theme-dnE3Od3I.js.map +1 -0
  135. package/package.json +16 -14
  136. package/dist/use-theme-BwPWinB2.cjs.map +0 -1
  137. package/dist/use-theme-IHmtrhEt.js.map +0 -1
@@ -1,5 +1,8 @@
1
- const require_use_theme = require("./use-theme-BwPWinB2.cjs");
2
- const require_index = require("./index.cjs");
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)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Dialog, {
280
+ return (0, react_jsx_runtime.jsx)(require_dialog.Dialog, {
278
281
  open,
279
282
  onOpenChange,
280
- children: (0, react_jsx_runtime.jsxs)(require_index.DialogContent, {
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)(require_index.DialogHeader, { children: (0, react_jsx_runtime.jsx)(require_index.DialogTitle, { children: t("richText.addLinkTitle", { defaultValue: "Add link" }) }) }),
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)(require_index.Label, {
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)(require_index.Input, {
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)(require_index.DialogFooter, { children: [
316
- (0, react_jsx_runtime.jsx)(require_index.Button, {
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)(require_index.Button, {
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)(require_index.Button, {
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 ?? {}).toolbar !== false;
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-lndbYqzR.cjs.map
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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"}