@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/README.md +933 -0
- package/dist/chunk-IZEGCTZW.js +588 -0
- package/dist/chunk-IZEGCTZW.js.map +1 -0
- package/dist/cli.js +345 -0
- package/dist/controlStyles-Bco2sOHE.d.ts +4 -0
- package/dist/editor.d.ts +43 -0
- package/dist/editor.js +373 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.d.ts +2470 -0
- package/dist/index.js +10582 -0
- package/dist/index.js.map +1 -0
- package/dist/theme.d.ts +78 -0
- package/dist/theme.js +287 -0
- package/dist/theme.js.map +1 -0
- package/package.json +69 -0
- package/theme.css +909 -0
- package/ui.theme.schema.json +462 -0
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"]}
|