@astherix/ui 0.40.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/dist/editor.js ADDED
@@ -0,0 +1,373 @@
1
+ "use client";
2
+ import {
3
+ Button,
4
+ Input,
5
+ cn,
6
+ controlFrame,
7
+ useField,
8
+ useFieldControlProps
9
+ } from "./chunk-IZEGCTZW.js";
10
+
11
+ // src/components/editor/Editor.tsx
12
+ import * as React from "react";
13
+ import { useEditor, useEditorState, EditorContent } from "@tiptap/react";
14
+ import StarterKit from "@tiptap/starter-kit";
15
+ import { Placeholder, CharacterCount } from "@tiptap/extensions";
16
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
17
+ var defaultTools = [
18
+ "paragraph",
19
+ "heading2",
20
+ "heading3",
21
+ "|",
22
+ "bold",
23
+ "italic",
24
+ "underline",
25
+ "strike",
26
+ "code",
27
+ "|",
28
+ "link",
29
+ "bulletList",
30
+ "orderedList",
31
+ "blockquote",
32
+ "|",
33
+ "undo",
34
+ "redo"
35
+ ];
36
+ var svg = (children) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
37
+ var icons = {
38
+ link: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
39
+ /* @__PURE__ */ jsx("path", { d: "M8.5 11.5a3 3 0 0 0 4.2 0l2.6-2.6a3 3 0 0 0-4.2-4.2l-.9.9" }),
40
+ /* @__PURE__ */ jsx("path", { d: "M11.5 8.5a3 3 0 0 0-4.2 0l-2.6 2.6a3 3 0 0 0 4.2 4.2l.9-.9" })
41
+ ] })),
42
+ bulletList: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
43
+ /* @__PURE__ */ jsx("path", { d: "M8 5.5h8M8 10h8M8 14.5h8" }),
44
+ /* @__PURE__ */ jsx("circle", { cx: "4.2", cy: "5.5", r: ".9", fill: "currentColor" }),
45
+ /* @__PURE__ */ jsx("circle", { cx: "4.2", cy: "10", r: ".9", fill: "currentColor" }),
46
+ /* @__PURE__ */ jsx("circle", { cx: "4.2", cy: "14.5", r: ".9", fill: "currentColor" })
47
+ ] })),
48
+ orderedList: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
49
+ /* @__PURE__ */ jsx("path", { d: "M8.5 5.5h7.5M8.5 10h7.5M8.5 14.5h7.5" }),
50
+ /* @__PURE__ */ jsx("path", { d: "M3.5 4.5l1-.5v3", strokeWidth: 1.3 }),
51
+ /* @__PURE__ */ jsx("path", { d: "M3.3 11.9c.1-.6.6-.9 1.1-.9.6 0 1 .4 1 .9 0 .8-2.1 1.5-2.1 2.6h2.2", strokeWidth: 1.3 })
52
+ ] })),
53
+ blockquote: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
54
+ /* @__PURE__ */ jsx("path", { d: "M4 5.5v9", strokeWidth: 2 }),
55
+ /* @__PURE__ */ jsx("path", { d: "M8 7h8M8 10h8M8 13h5" })
56
+ ] })),
57
+ horizontalRule: svg(/* @__PURE__ */ jsx("path", { d: "M3.5 10h13" })),
58
+ undo: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
59
+ /* @__PURE__ */ jsx("path", { d: "M7.5 5L4 8.5 7.5 12" }),
60
+ /* @__PURE__ */ jsx("path", { d: "M4.5 8.5H12a4 4 0 0 1 0 8H9" })
61
+ ] })),
62
+ redo: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
63
+ /* @__PURE__ */ jsx("path", { d: "M12.5 5L16 8.5 12.5 12" }),
64
+ /* @__PURE__ */ jsx("path", { d: "M15.5 8.5H8a4 4 0 0 0 0 8h3" })
65
+ ] })),
66
+ code: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx("path", { d: "M7 6.5L3.5 10 7 13.5" }),
68
+ /* @__PURE__ */ jsx("path", { d: "M13 6.5l3.5 3.5-3.5 3.5" })
69
+ ] }))
70
+ };
71
+ var Glyph = ({ children, className }) => /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("text-[0.95rem] leading-none", className), children });
72
+ var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
73
+ var mod = isMac ? "\u2318" : "Ctrl+";
74
+ function normalizeUrl(raw) {
75
+ const url = raw.trim();
76
+ if (!url) return "";
77
+ if (/^(javascript|data|vbscript):/i.test(url)) return "";
78
+ if (/^(https?:|mailto:|tel:|\/|#)/i.test(url)) return url;
79
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(url)) return `mailto:${url}`;
80
+ return `https://${url}`;
81
+ }
82
+ var Editor = React.forwardRef(function Editor2({
83
+ value,
84
+ defaultValue = "",
85
+ onChange,
86
+ placeholder = "Start writing\u2026",
87
+ toolbar = defaultTools,
88
+ maxLength,
89
+ showCount = false,
90
+ minHeight = "9rem",
91
+ maxHeight,
92
+ rounded = "md",
93
+ invalid: invalidProp,
94
+ disabled: disabledProp,
95
+ readOnly = false,
96
+ name,
97
+ id,
98
+ autoFocus = false,
99
+ onReady,
100
+ className,
101
+ "aria-label": ariaLabel,
102
+ "aria-describedby": describedBy
103
+ }, ref) {
104
+ const field = useField();
105
+ const countId = React.useId();
106
+ const { invalid, ...control } = useFieldControlProps({
107
+ id,
108
+ invalid: invalidProp,
109
+ disabled: disabledProp,
110
+ "aria-describedby": [describedBy, showCount || maxLength ? countId : void 0].filter(Boolean).join(" ") || void 0
111
+ });
112
+ const disabled = !!control.disabled;
113
+ const editable = !disabled && !readOnly;
114
+ const [html, setHtml] = React.useState(value ?? defaultValue);
115
+ const onChangeRef = React.useRef(onChange);
116
+ onChangeRef.current = onChange;
117
+ const editor = useEditor({
118
+ // Render on the client only; avoids hydration mismatches in Next.js.
119
+ immediatelyRender: false,
120
+ editable,
121
+ autofocus: autoFocus ? "end" : false,
122
+ content: value ?? defaultValue,
123
+ extensions: [
124
+ StarterKit.configure({
125
+ heading: { levels: [2, 3] },
126
+ codeBlock: false,
127
+ // Keeps the HTML clean (no empty <p> added after headings, quotes and lists).
128
+ trailingNode: false,
129
+ link: {
130
+ openOnClick: false,
131
+ autolink: true,
132
+ defaultProtocol: "https",
133
+ HTMLAttributes: { rel: "noopener noreferrer nofollow", target: "_blank" }
134
+ }
135
+ }),
136
+ Placeholder.configure({ placeholder }),
137
+ CharacterCount.configure({ limit: maxLength ?? null })
138
+ ],
139
+ editorProps: {
140
+ // useEditor re-applies these options on every render, so ARIA stays current.
141
+ attributes: {
142
+ role: "textbox",
143
+ "aria-multiline": "true",
144
+ class: "ui-prose ui-editor-content outline-none",
145
+ ...control.id ? { id: control.id } : {},
146
+ ...ariaLabel ? { "aria-label": ariaLabel } : field?.labelId ? { "aria-labelledby": field.labelId } : {},
147
+ ...control["aria-describedby"] ? { "aria-describedby": control["aria-describedby"] } : {},
148
+ ...invalid ? { "aria-invalid": "true" } : {},
149
+ ...readOnly ? { "aria-readonly": "true" } : {},
150
+ ...disabled ? { "aria-disabled": "true" } : {}
151
+ }
152
+ },
153
+ onUpdate: ({ editor: editor2 }) => {
154
+ const next = editor2.isEmpty ? "" : editor2.getHTML();
155
+ setHtml(next);
156
+ onChangeRef.current?.(next);
157
+ }
158
+ });
159
+ React.useEffect(() => {
160
+ editor?.setEditable(editable);
161
+ }, [editor, editable]);
162
+ React.useEffect(() => {
163
+ if (!editor || value === void 0) return;
164
+ const current = editor.isEmpty ? "" : editor.getHTML();
165
+ if (value !== current) {
166
+ editor.commands.setContent(value || "", { emitUpdate: false });
167
+ setHtml(value);
168
+ }
169
+ }, [editor, value]);
170
+ React.useEffect(() => {
171
+ if (editor) onReady?.(editor);
172
+ }, [editor]);
173
+ const state = useEditorState({
174
+ editor,
175
+ selector: ({ editor: e }) => e ? {
176
+ paragraph: e.isActive("paragraph"),
177
+ heading2: e.isActive("heading", { level: 2 }),
178
+ heading3: e.isActive("heading", { level: 3 }),
179
+ bold: e.isActive("bold"),
180
+ italic: e.isActive("italic"),
181
+ underline: e.isActive("underline"),
182
+ strike: e.isActive("strike"),
183
+ code: e.isActive("code"),
184
+ link: e.isActive("link"),
185
+ bulletList: e.isActive("bulletList"),
186
+ orderedList: e.isActive("orderedList"),
187
+ blockquote: e.isActive("blockquote"),
188
+ canUndo: e.can().undo(),
189
+ canRedo: e.can().redo(),
190
+ characters: e.storage.characterCount?.characters?.() ?? 0
191
+ } : null
192
+ });
193
+ const [linkOpen, setLinkOpen] = React.useState(false);
194
+ const [linkUrl, setLinkUrl] = React.useState("");
195
+ const openLink = React.useCallback(() => {
196
+ if (!editor) return;
197
+ setLinkUrl(editor.getAttributes("link").href ?? "");
198
+ setLinkOpen(true);
199
+ }, [editor]);
200
+ const applyLink = () => {
201
+ if (!editor) return;
202
+ const href = normalizeUrl(linkUrl);
203
+ const chain = editor.chain().focus().extendMarkRange("link");
204
+ if (href) chain.setLink({ href }).run();
205
+ else chain.unsetLink().run();
206
+ setLinkOpen(false);
207
+ };
208
+ const removeLink = () => {
209
+ editor?.chain().focus().extendMarkRange("link").unsetLink().run();
210
+ setLinkOpen(false);
211
+ };
212
+ React.useEffect(() => {
213
+ if (!editor || !editable) return;
214
+ const dom = editor.view.dom;
215
+ const onKey = (e) => {
216
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
217
+ e.preventDefault();
218
+ openLink();
219
+ }
220
+ };
221
+ dom.addEventListener("keydown", onKey);
222
+ return () => dom.removeEventListener("keydown", onKey);
223
+ }, [editor, editable, openLink]);
224
+ const tools = {
225
+ paragraph: { label: "Normal text", content: /* @__PURE__ */ jsx(Glyph, { children: "\xB6" }), active: state?.paragraph, run: () => editor?.chain().focus().setParagraph().run() },
226
+ heading2: { label: "Heading", content: /* @__PURE__ */ jsx(Glyph, { className: "text-[0.8125rem] font-semibold", children: "H2" }), active: state?.heading2, run: () => editor?.chain().focus().toggleHeading({ level: 2 }).run() },
227
+ heading3: { label: "Subheading", content: /* @__PURE__ */ jsx(Glyph, { className: "text-[0.8125rem] font-semibold", children: "H3" }), active: state?.heading3, run: () => editor?.chain().focus().toggleHeading({ level: 3 }).run() },
228
+ bold: { label: "Bold", shortcut: `${mod}B`, content: /* @__PURE__ */ jsx(Glyph, { className: "font-bold", children: "B" }), active: state?.bold, run: () => editor?.chain().focus().toggleBold().run() },
229
+ italic: { label: "Italic", shortcut: `${mod}I`, content: /* @__PURE__ */ jsx(Glyph, { className: "font-serif italic", children: "I" }), active: state?.italic, run: () => editor?.chain().focus().toggleItalic().run() },
230
+ underline: { label: "Underline", shortcut: `${mod}U`, content: /* @__PURE__ */ jsx(Glyph, { className: "underline underline-offset-2", children: "U" }), active: state?.underline, run: () => editor?.chain().focus().toggleUnderline().run() },
231
+ strike: { label: "Strikethrough", content: /* @__PURE__ */ jsx(Glyph, { className: "line-through", children: "S" }), active: state?.strike, run: () => editor?.chain().focus().toggleStrike().run() },
232
+ code: { label: "Inline code", shortcut: `${mod}E`, content: icons.code, active: state?.code, run: () => editor?.chain().focus().toggleCode().run() },
233
+ link: { label: state?.link ? "Edit link" : "Add link", shortcut: `${mod}K`, content: icons.link, active: state?.link || linkOpen, run: openLink },
234
+ bulletList: { label: "Bulleted list", content: icons.bulletList, active: state?.bulletList, run: () => editor?.chain().focus().toggleBulletList().run() },
235
+ orderedList: { label: "Numbered list", content: icons.orderedList, active: state?.orderedList, run: () => editor?.chain().focus().toggleOrderedList().run() },
236
+ blockquote: { label: "Quote", content: icons.blockquote, active: state?.blockquote, run: () => editor?.chain().focus().toggleBlockquote().run() },
237
+ horizontalRule: { label: "Divider line", content: icons.horizontalRule, run: () => editor?.chain().focus().setHorizontalRule().run() },
238
+ undo: { label: "Undo", shortcut: `${mod}Z`, content: icons.undo, disabled: !state?.canUndo, run: () => editor?.chain().focus().undo().run() },
239
+ redo: { label: "Redo", shortcut: isMac ? "\u21E7\u2318Z" : "Ctrl+Y", content: icons.redo, disabled: !state?.canRedo, run: () => editor?.chain().focus().redo().run() }
240
+ };
241
+ const buttonRefs = React.useRef([]);
242
+ const [focusIndex, setFocusIndex] = React.useState(0);
243
+ const buttons = toolbar.filter((t) => t !== "|");
244
+ const onToolbarKey = (e) => {
245
+ const last = buttons.length - 1;
246
+ let next = focusIndex;
247
+ if (e.key === "ArrowRight") next = focusIndex >= last ? 0 : focusIndex + 1;
248
+ else if (e.key === "ArrowLeft") next = focusIndex <= 0 ? last : focusIndex - 1;
249
+ else if (e.key === "Home") next = 0;
250
+ else if (e.key === "End") next = last;
251
+ else return;
252
+ e.preventDefault();
253
+ setFocusIndex(next);
254
+ buttonRefs.current[next]?.focus();
255
+ };
256
+ const count = state?.characters ?? 0;
257
+ let buttonIndex = -1;
258
+ return /* @__PURE__ */ jsxs("div", { ref, className: cn("grid w-full gap-1", className), children: [
259
+ /* @__PURE__ */ jsxs(
260
+ "div",
261
+ {
262
+ "data-invalid": invalid ? "" : void 0,
263
+ "data-disabled": disabled ? "" : void 0,
264
+ "data-readonly": readOnly ? "" : void 0,
265
+ className: cn(controlFrame({ rounded }), "flex-col"),
266
+ children: [
267
+ !readOnly && buttons.length > 0 && /* @__PURE__ */ jsx(
268
+ "div",
269
+ {
270
+ role: "toolbar",
271
+ "aria-label": "Formatting",
272
+ "aria-controls": control.id,
273
+ onKeyDown: onToolbarKey,
274
+ className: "flex flex-wrap items-center gap-0.5 border-b border-border px-1.5 py-1",
275
+ children: toolbar.map((tool, i) => {
276
+ if (tool === "|") return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "mx-1 h-5 w-px bg-border" }, `sep-${i}`);
277
+ const def = tools[tool];
278
+ const index = ++buttonIndex;
279
+ return /* @__PURE__ */ jsx(
280
+ "button",
281
+ {
282
+ ref: (el) => {
283
+ buttonRefs.current[index] = el;
284
+ },
285
+ type: "button",
286
+ tabIndex: index === focusIndex ? 0 : -1,
287
+ "aria-label": def.label,
288
+ "aria-pressed": def.active === void 0 ? void 0 : !!def.active,
289
+ title: def.shortcut ? `${def.label} (${def.shortcut})` : def.label,
290
+ disabled: disabled || !editor || def.disabled,
291
+ onMouseDown: (e) => e.preventDefault(),
292
+ onFocus: () => setFocusIndex(index),
293
+ onClick: def.run,
294
+ className: cn(
295
+ "grid size-8 cursor-pointer place-items-center rounded-control-sm text-fg-muted transition-colors duration-75",
296
+ "hover:bg-secondary-hover hover:text-fg focus-visible:outline-2 focus-visible:outline-ring",
297
+ "disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent",
298
+ "[&_svg]:size-[1.125rem]",
299
+ def.active && "bg-primary/12 text-primary hover:bg-primary/18 hover:text-primary"
300
+ ),
301
+ children: def.content
302
+ },
303
+ tool
304
+ );
305
+ })
306
+ }
307
+ ),
308
+ linkOpen && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border bg-secondary-hover/40 px-2.5 py-2", children: [
309
+ /* @__PURE__ */ jsx(
310
+ Input,
311
+ {
312
+ size: "sm",
313
+ autoFocus: true,
314
+ "aria-label": "Link address",
315
+ placeholder: "example.com or https://\u2026",
316
+ leadingIcon: icons.link,
317
+ value: linkUrl,
318
+ onChange: (e) => setLinkUrl(e.target.value),
319
+ onKeyDown: (e) => {
320
+ if (e.key === "Enter") {
321
+ e.preventDefault();
322
+ applyLink();
323
+ } else if (e.key === "Escape") {
324
+ e.preventDefault();
325
+ e.stopPropagation();
326
+ setLinkOpen(false);
327
+ editor?.commands.focus();
328
+ }
329
+ },
330
+ frameClassName: "flex-1 min-w-48"
331
+ }
332
+ ),
333
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
334
+ state?.link && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: removeLink, children: "Remove" }),
335
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
336
+ setLinkOpen(false);
337
+ editor?.commands.focus();
338
+ }, children: "Cancel" }),
339
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: applyLink, children: state?.link ? "Update link" : "Add link" })
340
+ ] })
341
+ ] }),
342
+ /* @__PURE__ */ jsx(
343
+ EditorContent,
344
+ {
345
+ editor,
346
+ onClick: () => editor && !editor.isFocused && editable && editor.commands.focus(),
347
+ className: cn("cursor-text overflow-y-auto px-3.5 py-3", disabled && "cursor-not-allowed", readOnly && "cursor-auto"),
348
+ style: { minHeight, maxHeight }
349
+ }
350
+ )
351
+ ]
352
+ }
353
+ ),
354
+ (showCount || maxLength !== void 0) && /* @__PURE__ */ jsx(
355
+ "p",
356
+ {
357
+ id: countId,
358
+ className: cn(
359
+ "justify-self-end text-xs tabular-nums text-fg-muted",
360
+ maxLength !== void 0 && count >= maxLength * 0.9 && "text-fg",
361
+ maxLength !== void 0 && count >= maxLength && "text-danger"
362
+ ),
363
+ children: maxLength !== void 0 ? `${count} / ${maxLength}` : `${count} characters`
364
+ }
365
+ ),
366
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: html })
367
+ ] });
368
+ });
369
+ export {
370
+ Editor,
371
+ defaultTools
372
+ };
373
+ //# sourceMappingURL=editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/editor/Editor.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useEditor, useEditorState, EditorContent, type Editor as TiptapEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { Placeholder, CharacterCount } from \"@tiptap/extensions\";\nimport { cn } from \"../../lib/cn\";\nimport { Button } from \"../button/Button\";\nimport { Input } from \"../input/Input\";\nimport { useField, useFieldControlProps } from \"../input/Field\";\nimport { controlFrame, type ControlRounded } from \"../input/controlStyles\";\n\n/*\n * Editor — a basic rich-text (WYSIWYG) editor built on Tiptap (ProseMirror).\n * It edits and returns HTML. Formatting shortcuts work as expected (Ctrl/⌘+B, I, U, K…),\n * and Markdown-style typing too: \"## \" starts a heading, \"- \" a list, \"> \" a quote.\n *\n * Imported from \"@astherix/ui/editor\" so apps that don't use it don't load Tiptap.\n */\n\nexport type EditorTool =\n | \"paragraph\"\n | \"heading2\"\n | \"heading3\"\n | \"bold\"\n | \"italic\"\n | \"underline\"\n | \"strike\"\n | \"code\"\n | \"link\"\n | \"bulletList\"\n | \"orderedList\"\n | \"blockquote\"\n | \"horizontalRule\"\n | \"undo\"\n | \"redo\"\n | \"|\";\n\nexport const defaultTools: EditorTool[] = [\n \"paragraph\",\n \"heading2\",\n \"heading3\",\n \"|\",\n \"bold\",\n \"italic\",\n \"underline\",\n \"strike\",\n \"code\",\n \"|\",\n \"link\",\n \"bulletList\",\n \"orderedList\",\n \"blockquote\",\n \"|\",\n \"undo\",\n \"redo\",\n];\n\nexport interface EditorProps {\n /** Controlled HTML value. */\n value?: string;\n /** Starting HTML when uncontrolled. */\n defaultValue?: string;\n /** Called with the new HTML on every change. An empty editor gives \"\". */\n onChange?: (html: string) => void;\n placeholder?: string;\n /** Which toolbar buttons to show, in order. \"|\" adds a divider. */\n toolbar?: EditorTool[];\n /** Hard limit on characters. Shows a counter. */\n maxLength?: number;\n /** Show the character count (always shown with maxLength). */\n showCount?: boolean;\n /** Minimum height of the writing area, e.g. \"8rem\". Default \"9rem\". */\n minHeight?: string;\n /** Maximum height before the writing area scrolls, e.g. \"24rem\". */\n maxHeight?: string;\n rounded?: Exclude<ControlRounded, \"full\">;\n invalid?: boolean;\n disabled?: boolean;\n /** Read-only: formatted text, no toolbar, no editing. */\n readOnly?: boolean;\n /** Adds a hidden input with this name holding the HTML, for normal form posts. */\n name?: string;\n id?: string;\n autoFocus?: boolean;\n /** Get the underlying Tiptap editor, e.g. to run commands or read JSON. */\n onReady?: (editor: TiptapEditor) => void;\n className?: string;\n \"aria-label\"?: string;\n \"aria-describedby\"?: string;\n}\n\n/* ---------- icons ---------- */\n\nconst svg = (children: React.ReactNode) => (\n <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.6} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n {children}\n </svg>\n);\nconst icons = {\n link: svg(<><path d=\"M8.5 11.5a3 3 0 0 0 4.2 0l2.6-2.6a3 3 0 0 0-4.2-4.2l-.9.9\" /><path d=\"M11.5 8.5a3 3 0 0 0-4.2 0l-2.6 2.6a3 3 0 0 0 4.2 4.2l.9-.9\" /></>),\n bulletList: svg(<><path d=\"M8 5.5h8M8 10h8M8 14.5h8\" /><circle cx=\"4.2\" cy=\"5.5\" r=\".9\" fill=\"currentColor\" /><circle cx=\"4.2\" cy=\"10\" r=\".9\" fill=\"currentColor\" /><circle cx=\"4.2\" cy=\"14.5\" r=\".9\" fill=\"currentColor\" /></>),\n orderedList: svg(<><path d=\"M8.5 5.5h7.5M8.5 10h7.5M8.5 14.5h7.5\" /><path d=\"M3.5 4.5l1-.5v3\" strokeWidth={1.3} /><path d=\"M3.3 11.9c.1-.6.6-.9 1.1-.9.6 0 1 .4 1 .9 0 .8-2.1 1.5-2.1 2.6h2.2\" strokeWidth={1.3} /></>),\n blockquote: svg(<><path d=\"M4 5.5v9\" strokeWidth={2} /><path d=\"M8 7h8M8 10h8M8 13h5\" /></>),\n horizontalRule: svg(<path d=\"M3.5 10h13\" />),\n undo: svg(<><path d=\"M7.5 5L4 8.5 7.5 12\" /><path d=\"M4.5 8.5H12a4 4 0 0 1 0 8H9\" /></>),\n redo: svg(<><path d=\"M12.5 5L16 8.5 12.5 12\" /><path d=\"M15.5 8.5H8a4 4 0 0 0 0 8h3\" /></>),\n code: svg(<><path d=\"M7 6.5L3.5 10 7 13.5\" /><path d=\"M13 6.5l3.5 3.5-3.5 3.5\" /></>),\n};\nconst Glyph = ({ children, className }: { children: React.ReactNode; className?: string }) => (\n <span aria-hidden=\"true\" className={cn(\"text-[0.95rem] leading-none\", className)}>\n {children}\n </span>\n);\n\nconst isMac = typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform);\nconst mod = isMac ? \"⌘\" : \"Ctrl+\";\n\n/* ---------- link helpers ---------- */\n\nfunction normalizeUrl(raw: string) {\n const url = raw.trim();\n if (!url) return \"\";\n if (/^(javascript|data|vbscript):/i.test(url)) return \"\"; // never allow script URLs\n if (/^(https?:|mailto:|tel:|\\/|#)/i.test(url)) return url;\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(url)) return `mailto:${url}`;\n return `https://${url}`;\n}\n\n/* ---------- component ---------- */\n\nexport const Editor = React.forwardRef<HTMLDivElement, EditorProps>(function Editor(\n {\n value,\n defaultValue = \"\",\n onChange,\n placeholder = \"Start writing…\",\n toolbar = defaultTools,\n maxLength,\n showCount = false,\n minHeight = \"9rem\",\n maxHeight,\n rounded = \"md\",\n invalid: invalidProp,\n disabled: disabledProp,\n readOnly = false,\n name,\n id,\n autoFocus = false,\n onReady,\n className,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": describedBy,\n },\n ref\n) {\n const field = useField();\n const countId = React.useId();\n const { invalid, ...control } = useFieldControlProps({\n id,\n invalid: invalidProp,\n disabled: disabledProp,\n \"aria-describedby\": [describedBy, showCount || maxLength ? countId : undefined].filter(Boolean).join(\" \") || undefined,\n });\n const disabled = !!control.disabled;\n const editable = !disabled && !readOnly;\n\n const [html, setHtml] = React.useState(value ?? defaultValue);\n const onChangeRef = React.useRef(onChange);\n onChangeRef.current = onChange;\n\n const editor = useEditor({\n // Render on the client only; avoids hydration mismatches in Next.js.\n immediatelyRender: false,\n editable,\n autofocus: autoFocus ? \"end\" : false,\n content: value ?? defaultValue,\n extensions: [\n StarterKit.configure({\n heading: { levels: [2, 3] },\n codeBlock: false,\n // Keeps the HTML clean (no empty <p> added after headings, quotes and lists).\n trailingNode: false,\n link: {\n openOnClick: false,\n autolink: true,\n defaultProtocol: \"https\",\n HTMLAttributes: { rel: \"noopener noreferrer nofollow\", target: \"_blank\" },\n },\n }),\n Placeholder.configure({ placeholder }),\n CharacterCount.configure({ limit: maxLength ?? null }),\n ],\n editorProps: {\n // useEditor re-applies these options on every render, so ARIA stays current.\n attributes: {\n role: \"textbox\",\n \"aria-multiline\": \"true\",\n class: \"ui-prose ui-editor-content outline-none\",\n ...(control.id ? { id: control.id } : {}),\n ...(ariaLabel ? { \"aria-label\": ariaLabel } : field?.labelId ? { \"aria-labelledby\": field.labelId } : {}),\n ...(control[\"aria-describedby\"] ? { \"aria-describedby\": control[\"aria-describedby\"] } : {}),\n ...(invalid ? { \"aria-invalid\": \"true\" } : {}),\n ...(readOnly ? { \"aria-readonly\": \"true\" } : {}),\n ...(disabled ? { \"aria-disabled\": \"true\" } : {}),\n },\n },\n onUpdate: ({ editor }) => {\n const next = editor.isEmpty ? \"\" : editor.getHTML();\n setHtml(next);\n onChangeRef.current?.(next);\n },\n });\n\n React.useEffect(() => {\n editor?.setEditable(editable);\n }, [editor, editable]);\n\n // Controlled: apply outside changes without losing the cursor on our own updates.\n React.useEffect(() => {\n if (!editor || value === undefined) return;\n const current = editor.isEmpty ? \"\" : editor.getHTML();\n if (value !== current) {\n editor.commands.setContent(value || \"\", { emitUpdate: false });\n setHtml(value);\n }\n }, [editor, value]);\n\n React.useEffect(() => {\n if (editor) onReady?.(editor);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor]);\n\n const state = useEditorState({\n editor,\n selector: ({ editor: e }) =>\n e\n ? {\n paragraph: e.isActive(\"paragraph\"),\n heading2: e.isActive(\"heading\", { level: 2 }),\n heading3: e.isActive(\"heading\", { level: 3 }),\n bold: e.isActive(\"bold\"),\n italic: e.isActive(\"italic\"),\n underline: e.isActive(\"underline\"),\n strike: e.isActive(\"strike\"),\n code: e.isActive(\"code\"),\n link: e.isActive(\"link\"),\n bulletList: e.isActive(\"bulletList\"),\n orderedList: e.isActive(\"orderedList\"),\n blockquote: e.isActive(\"blockquote\"),\n canUndo: e.can().undo(),\n canRedo: e.can().redo(),\n characters: e.storage.characterCount?.characters?.() ?? 0,\n }\n : null,\n });\n\n /* ----- link editing ----- */\n const [linkOpen, setLinkOpen] = React.useState(false);\n const [linkUrl, setLinkUrl] = React.useState(\"\");\n const openLink = React.useCallback(() => {\n if (!editor) return;\n setLinkUrl(editor.getAttributes(\"link\").href ?? \"\");\n setLinkOpen(true);\n }, [editor]);\n const applyLink = () => {\n if (!editor) return;\n const href = normalizeUrl(linkUrl);\n const chain = editor.chain().focus().extendMarkRange(\"link\");\n if (href) chain.setLink({ href }).run();\n else chain.unsetLink().run();\n setLinkOpen(false);\n };\n const removeLink = () => {\n editor?.chain().focus().extendMarkRange(\"link\").unsetLink().run();\n setLinkOpen(false);\n };\n\n // Ctrl/⌘+K opens the link box.\n React.useEffect(() => {\n if (!editor || !editable) return;\n const dom = editor.view.dom as HTMLElement;\n const onKey = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === \"k\") {\n e.preventDefault();\n openLink();\n }\n };\n dom.addEventListener(\"keydown\", onKey);\n return () => dom.removeEventListener(\"keydown\", onKey);\n }, [editor, editable, openLink]);\n\n /* ----- toolbar ----- */\n type ToolDef = { label: string; shortcut?: string; content: React.ReactNode; active?: boolean; disabled?: boolean; run: () => void };\n const tools: Record<Exclude<EditorTool, \"|\">, ToolDef> = {\n paragraph: { label: \"Normal text\", content: <Glyph>¶</Glyph>, active: state?.paragraph, run: () => editor?.chain().focus().setParagraph().run() },\n heading2: { label: \"Heading\", content: <Glyph className=\"text-[0.8125rem] font-semibold\">H2</Glyph>, active: state?.heading2, run: () => editor?.chain().focus().toggleHeading({ level: 2 }).run() },\n heading3: { label: \"Subheading\", content: <Glyph className=\"text-[0.8125rem] font-semibold\">H3</Glyph>, active: state?.heading3, run: () => editor?.chain().focus().toggleHeading({ level: 3 }).run() },\n bold: { label: \"Bold\", shortcut: `${mod}B`, content: <Glyph className=\"font-bold\">B</Glyph>, active: state?.bold, run: () => editor?.chain().focus().toggleBold().run() },\n italic: { label: \"Italic\", shortcut: `${mod}I`, content: <Glyph className=\"font-serif italic\">I</Glyph>, active: state?.italic, run: () => editor?.chain().focus().toggleItalic().run() },\n underline: { label: \"Underline\", shortcut: `${mod}U`, content: <Glyph className=\"underline underline-offset-2\">U</Glyph>, active: state?.underline, run: () => editor?.chain().focus().toggleUnderline().run() },\n strike: { label: \"Strikethrough\", content: <Glyph className=\"line-through\">S</Glyph>, active: state?.strike, run: () => editor?.chain().focus().toggleStrike().run() },\n code: { label: \"Inline code\", shortcut: `${mod}E`, content: icons.code, active: state?.code, run: () => editor?.chain().focus().toggleCode().run() },\n link: { label: state?.link ? \"Edit link\" : \"Add link\", shortcut: `${mod}K`, content: icons.link, active: state?.link || linkOpen, run: openLink },\n bulletList: { label: \"Bulleted list\", content: icons.bulletList, active: state?.bulletList, run: () => editor?.chain().focus().toggleBulletList().run() },\n orderedList: { label: \"Numbered list\", content: icons.orderedList, active: state?.orderedList, run: () => editor?.chain().focus().toggleOrderedList().run() },\n blockquote: { label: \"Quote\", content: icons.blockquote, active: state?.blockquote, run: () => editor?.chain().focus().toggleBlockquote().run() },\n horizontalRule: { label: \"Divider line\", content: icons.horizontalRule, run: () => editor?.chain().focus().setHorizontalRule().run() },\n undo: { label: \"Undo\", shortcut: `${mod}Z`, content: icons.undo, disabled: !state?.canUndo, run: () => editor?.chain().focus().undo().run() },\n redo: { label: \"Redo\", shortcut: isMac ? \"⇧⌘Z\" : \"Ctrl+Y\", content: icons.redo, disabled: !state?.canRedo, run: () => editor?.chain().focus().redo().run() },\n };\n\n // Roving focus: one Tab stop for the toolbar, arrow keys move between buttons.\n const buttonRefs = React.useRef<Array<HTMLButtonElement | null>>([]);\n const [focusIndex, setFocusIndex] = React.useState(0);\n const buttons = toolbar.filter((t): t is Exclude<EditorTool, \"|\"> => t !== \"|\");\n const onToolbarKey = (e: React.KeyboardEvent) => {\n const last = buttons.length - 1;\n let next = focusIndex;\n if (e.key === \"ArrowRight\") next = focusIndex >= last ? 0 : focusIndex + 1;\n else if (e.key === \"ArrowLeft\") next = focusIndex <= 0 ? last : focusIndex - 1;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = last;\n else return;\n e.preventDefault();\n setFocusIndex(next);\n buttonRefs.current[next]?.focus();\n };\n\n const count = state?.characters ?? 0;\n let buttonIndex = -1;\n\n return (\n <div ref={ref} className={cn(\"grid w-full gap-1\", className)}>\n <div\n data-invalid={invalid ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n className={cn(controlFrame({ rounded }), \"flex-col\")}\n >\n {!readOnly && buttons.length > 0 && (\n <div\n role=\"toolbar\"\n aria-label=\"Formatting\"\n aria-controls={control.id}\n onKeyDown={onToolbarKey}\n className=\"flex flex-wrap items-center gap-0.5 border-b border-border px-1.5 py-1\"\n >\n {toolbar.map((tool, i) => {\n if (tool === \"|\") return <span key={`sep-${i}`} aria-hidden=\"true\" className=\"mx-1 h-5 w-px bg-border\" />;\n const def = tools[tool];\n const index = ++buttonIndex;\n return (\n <button\n key={tool}\n ref={(el) => {\n buttonRefs.current[index] = el;\n }}\n type=\"button\"\n tabIndex={index === focusIndex ? 0 : -1}\n aria-label={def.label}\n aria-pressed={def.active === undefined ? undefined : !!def.active}\n title={def.shortcut ? `${def.label} (${def.shortcut})` : def.label}\n disabled={disabled || !editor || def.disabled}\n onMouseDown={(e) => e.preventDefault() /* keep the text selection */}\n onFocus={() => setFocusIndex(index)}\n onClick={def.run}\n className={cn(\n \"grid size-8 cursor-pointer place-items-center rounded-control-sm text-fg-muted transition-colors duration-75\",\n \"hover:bg-secondary-hover hover:text-fg focus-visible:outline-2 focus-visible:outline-ring\",\n \"disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent\",\n \"[&_svg]:size-[1.125rem]\",\n def.active && \"bg-primary/12 text-primary hover:bg-primary/18 hover:text-primary\"\n )}\n >\n {def.content}\n </button>\n );\n })}\n </div>\n )}\n\n {linkOpen && (\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border bg-secondary-hover/40 px-2.5 py-2\">\n <Input\n size=\"sm\"\n autoFocus\n aria-label=\"Link address\"\n placeholder=\"example.com or https://…\"\n leadingIcon={icons.link}\n value={linkUrl}\n onChange={(e) => setLinkUrl(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n applyLink();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n setLinkOpen(false);\n editor?.commands.focus();\n }\n }}\n frameClassName=\"flex-1 min-w-48\"\n />\n <div className=\"flex gap-1.5\">\n {state?.link && (\n <Button size=\"sm\" variant=\"ghost\" onClick={removeLink}>\n Remove\n </Button>\n )}\n <Button size=\"sm\" variant=\"ghost\" onClick={() => { setLinkOpen(false); editor?.commands.focus(); }}>\n Cancel\n </Button>\n <Button size=\"sm\" onClick={applyLink}>\n {state?.link ? \"Update link\" : \"Add link\"}\n </Button>\n </div>\n </div>\n )}\n\n <EditorContent\n editor={editor}\n onClick={() => editor && !editor.isFocused && editable && editor.commands.focus()}\n className={cn(\"cursor-text overflow-y-auto px-3.5 py-3\", disabled && \"cursor-not-allowed\", readOnly && \"cursor-auto\")}\n style={{ minHeight, maxHeight }}\n />\n </div>\n\n {(showCount || maxLength !== undefined) && (\n <p\n id={countId}\n className={cn(\n \"justify-self-end text-xs tabular-nums text-fg-muted\",\n maxLength !== undefined && count >= maxLength * 0.9 && \"text-fg\",\n maxLength !== undefined && count >= maxLength && \"text-danger\"\n )}\n >\n {maxLength !== undefined ? `${count} / ${maxLength}` : `${count} characters`}\n </p>\n )}\n\n {name && <input type=\"hidden\" name={name} value={html} />}\n </div>\n );\n});\n\nexport type { TiptapEditor };\n"],"mappings":"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","names":["Editor","editor"]}