oneuxi-editor 1.0.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/index.mjs ADDED
@@ -0,0 +1,2243 @@
1
+ // src/core/Editor.tsx
2
+ import {
3
+ forwardRef,
4
+ useImperativeHandle,
5
+ useRef as useRef2,
6
+ useState as useState8,
7
+ useEffect as useEffect3,
8
+ useCallback
9
+ } from "react";
10
+ import { LexicalComposer } from "@lexical/react/LexicalComposer";
11
+ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
12
+ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
13
+ import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
14
+ import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
15
+ import { ListPlugin } from "@lexical/react/LexicalListPlugin";
16
+ import { CheckListPlugin } from "@lexical/react/LexicalCheckListPlugin";
17
+ import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
18
+ import { HorizontalRulePlugin } from "@lexical/react/LexicalHorizontalRulePlugin";
19
+ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
20
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
21
+ import { HeadingNode, QuoteNode } from "@lexical/rich-text";
22
+ import { ListNode, ListItemNode } from "@lexical/list";
23
+ import { LinkNode, AutoLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
24
+ import { TableNode as TableNode2, TableRowNode as TableRowNode2, TableCellNode as TableCellNode2, $createTableNodeWithDimensions } from "@lexical/table";
25
+ import { CodeNode as CodeNode2, CodeHighlightNode as CodeHighlightNode2, $createCodeNode } from "@lexical/code";
26
+ import { HorizontalRuleNode, $createHorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
27
+ import {
28
+ $getRoot as $getRoot2,
29
+ $getSelection,
30
+ $isRangeSelection,
31
+ $createParagraphNode,
32
+ FORMAT_TEXT_COMMAND,
33
+ FORMAT_ELEMENT_COMMAND,
34
+ UNDO_COMMAND,
35
+ REDO_COMMAND,
36
+ INDENT_CONTENT_COMMAND,
37
+ OUTDENT_CONTENT_COMMAND,
38
+ CAN_UNDO_COMMAND,
39
+ CAN_REDO_COMMAND,
40
+ COMMAND_PRIORITY_CRITICAL
41
+ } from "lexical";
42
+ import { $setBlocksType } from "@lexical/selection";
43
+ import { $createHeadingNode, $createQuoteNode } from "@lexical/rich-text";
44
+ import {
45
+ INSERT_UNORDERED_LIST_COMMAND,
46
+ INSERT_ORDERED_LIST_COMMAND,
47
+ INSERT_CHECK_LIST_COMMAND
48
+ } from "@lexical/list";
49
+
50
+ // src/core/EditorContext.tsx
51
+ import { createContext, useContext } from "react";
52
+ var EditorContext = createContext(null);
53
+ function useEditor() {
54
+ const context = useContext(EditorContext);
55
+ if (!context) {
56
+ throw new Error("useEditor must be used within an <Editor> component");
57
+ }
58
+ return context;
59
+ }
60
+
61
+ // src/extensions/image/ImageNode.tsx
62
+ import {
63
+ DecoratorNode
64
+ } from "lexical";
65
+ import { jsx, jsxs } from "react/jsx-runtime";
66
+ var ImageNode = class _ImageNode extends DecoratorNode {
67
+ __src;
68
+ __alt;
69
+ __width;
70
+ __height;
71
+ __alignment;
72
+ __caption;
73
+ static getType() {
74
+ return "image";
75
+ }
76
+ static clone(node) {
77
+ return new _ImageNode(
78
+ node.__src,
79
+ node.__alt,
80
+ node.__width,
81
+ node.__height,
82
+ node.__alignment,
83
+ node.__caption,
84
+ node.__key
85
+ );
86
+ }
87
+ constructor(src, alt, width, height, alignment, caption, key) {
88
+ super(key);
89
+ this.__src = src;
90
+ this.__alt = alt || "";
91
+ this.__width = width;
92
+ this.__height = height;
93
+ this.__alignment = alignment || "center";
94
+ this.__caption = caption || "";
95
+ }
96
+ static importJSON(serializedNode) {
97
+ const { src, alt, width, height, alignment, caption } = serializedNode;
98
+ return new _ImageNode(src, alt, width, height, alignment, caption);
99
+ }
100
+ exportJSON() {
101
+ return {
102
+ type: "image",
103
+ version: 1,
104
+ src: this.__src,
105
+ alt: this.__alt,
106
+ width: this.__width,
107
+ height: this.__height,
108
+ alignment: this.__alignment,
109
+ caption: this.__caption
110
+ };
111
+ }
112
+ createDOM() {
113
+ const span = document.createElement("span");
114
+ span.className = `oue-image-container oue-align-${this.__alignment}`;
115
+ return span;
116
+ }
117
+ updateDOM() {
118
+ return false;
119
+ }
120
+ exportDOM() {
121
+ const element = document.createElement("img");
122
+ element.setAttribute("src", this.__src);
123
+ if (this.__alt) element.setAttribute("alt", this.__alt);
124
+ if (this.__width) element.setAttribute("width", String(this.__width));
125
+ if (this.__height) element.setAttribute("height", String(this.__height));
126
+ return { element };
127
+ }
128
+ decorate() {
129
+ return /* @__PURE__ */ jsxs("span", { className: `oue-image-container oue-align-${this.__alignment}`, children: [
130
+ /* @__PURE__ */ jsx(
131
+ "img",
132
+ {
133
+ src: this.__src,
134
+ alt: this.__alt,
135
+ style: {
136
+ width: this.__width ? `${this.__width}px` : "auto",
137
+ height: this.__height ? `${this.__height}px` : "auto"
138
+ }
139
+ }
140
+ ),
141
+ this.__caption && /* @__PURE__ */ jsx("span", { className: "oue-image-caption", children: this.__caption })
142
+ ] });
143
+ }
144
+ };
145
+ function $createImageNode(payload) {
146
+ return new ImageNode(
147
+ payload.src,
148
+ payload.alt,
149
+ payload.width,
150
+ payload.height,
151
+ payload.alignment,
152
+ payload.caption,
153
+ payload.key
154
+ );
155
+ }
156
+
157
+ // src/html/serializer.ts
158
+ import { $generateHtmlFromNodes } from "@lexical/html";
159
+
160
+ // src/security/sanitizer.ts
161
+ import DOMPurify from "dompurify";
162
+ var DEFAULT_ALLOWED_TAGS = [
163
+ "p",
164
+ "h1",
165
+ "h2",
166
+ "h3",
167
+ "h4",
168
+ "h5",
169
+ "h6",
170
+ "blockquote",
171
+ "pre",
172
+ "code",
173
+ "ul",
174
+ "ol",
175
+ "li",
176
+ "hr",
177
+ "br",
178
+ "span",
179
+ "div",
180
+ "strong",
181
+ "b",
182
+ "em",
183
+ "i",
184
+ "u",
185
+ "s",
186
+ "sub",
187
+ "sup",
188
+ "a",
189
+ "img",
190
+ "iframe",
191
+ "video",
192
+ "source",
193
+ "table",
194
+ "thead",
195
+ "tbody",
196
+ "tfoot",
197
+ "tr",
198
+ "th",
199
+ "td",
200
+ "figure",
201
+ "figcaption",
202
+ "input"
203
+ ];
204
+ var DEFAULT_ALLOWED_ATTR = [
205
+ "href",
206
+ "target",
207
+ "rel",
208
+ "title",
209
+ "src",
210
+ "alt",
211
+ "width",
212
+ "height",
213
+ "align",
214
+ "class",
215
+ "style",
216
+ "id",
217
+ "data-*",
218
+ "type",
219
+ "checked",
220
+ "disabled",
221
+ "frameborder",
222
+ "allowfullscreen",
223
+ "allow"
224
+ ];
225
+ function sanitizeHTML(html, policy) {
226
+ if (!html) return "";
227
+ if (typeof window === "undefined") {
228
+ return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/on\w+\s*=\s*(["']).*?\1/gi, "").replace(/on\w+\s*=\s*[^"'\s>]+/gi, "").replace(/href\s*=\s*["']?\s*javascript:[^"'>]*/gi, 'href="#"');
229
+ }
230
+ const allowedTags = policy?.allowedTags || DEFAULT_ALLOWED_TAGS;
231
+ const allowedAttributes = policy?.allowedAttributes ? Object.keys(policy.allowedAttributes).reduce((acc, tag) => {
232
+ return [...acc, ...policy.allowedAttributes[tag] || []];
233
+ }, DEFAULT_ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
234
+ const clean = DOMPurify.sanitize(html, {
235
+ ALLOWED_TAGS: allowedTags,
236
+ ALLOWED_ATTR: allowedAttributes,
237
+ ADD_ATTR: ["target", "rel"],
238
+ FORCE_BODY: true
239
+ });
240
+ return clean.replace(/href=["']?\s*javascript:[^"'>]*/gi, 'href="#"');
241
+ }
242
+
243
+ // src/html/serializer.ts
244
+ function getEditorHTML(editor, policy) {
245
+ let html = "";
246
+ editor.getEditorState().read(() => {
247
+ html = $generateHtmlFromNodes(editor, null);
248
+ });
249
+ return sanitizeHTML(html, policy);
250
+ }
251
+
252
+ // src/html/parser.ts
253
+ import { $generateNodesFromDOM } from "@lexical/html";
254
+ import { $getRoot } from "lexical";
255
+ function setEditorHTML(editor, html, policy) {
256
+ const sanitized = sanitizeHTML(html, policy);
257
+ editor.update(() => {
258
+ const root = $getRoot();
259
+ root.clear();
260
+ if (sanitized.trim().length === 0) return;
261
+ const parser = typeof window !== "undefined" ? new DOMParser() : null;
262
+ if (!parser) return;
263
+ const dom = parser.parseFromString(sanitized, "text/html");
264
+ const nodes = $generateNodesFromDOM(editor, dom);
265
+ $getRoot().append(...nodes);
266
+ });
267
+ }
268
+
269
+ // src/theme/theme.ts
270
+ import { useSyncExternalStore, useEffect, useState } from "react";
271
+ var COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)";
272
+ function getSystemThemeSnapshot() {
273
+ if (typeof window === "undefined") return false;
274
+ return window.matchMedia(COLOR_SCHEME_QUERY).matches;
275
+ }
276
+ function getServerThemeSnapshot() {
277
+ return false;
278
+ }
279
+ function subscribeSystemTheme(callback) {
280
+ if (typeof window === "undefined") return () => {
281
+ };
282
+ const matchMedia = window.matchMedia(COLOR_SCHEME_QUERY);
283
+ matchMedia.addEventListener("change", callback);
284
+ return () => matchMedia.removeEventListener("change", callback);
285
+ }
286
+ function useResolvedTheme(theme = "system") {
287
+ const isSystemDark = useSyncExternalStore(
288
+ subscribeSystemTheme,
289
+ getSystemThemeSnapshot,
290
+ getServerThemeSnapshot
291
+ );
292
+ const [mounted, setMounted] = useState(false);
293
+ useEffect(() => {
294
+ setMounted(true);
295
+ }, []);
296
+ let themeObj = {};
297
+ let mode = "system";
298
+ if (typeof theme === "string") {
299
+ mode = theme;
300
+ } else if (typeof theme === "object" && theme !== null) {
301
+ themeObj = theme;
302
+ mode = theme.mode || "system";
303
+ }
304
+ const resolvedMode = mode === "system" ? mounted ? isSystemDark ? "dark" : "light" : "light" : mode;
305
+ const cssVarMap = {
306
+ mode: "",
307
+ background: "--oue-bg",
308
+ surface: "--oue-surface",
309
+ text: "--oue-text",
310
+ textMuted: "--oue-text-muted",
311
+ border: "--oue-border",
312
+ primary: "--oue-primary",
313
+ primaryForeground: "--oue-primary-foreground",
314
+ hover: "--oue-hover",
315
+ active: "--oue-active",
316
+ selection: "--oue-selection",
317
+ codeBg: "--oue-code-bg",
318
+ toolbarBg: "--oue-toolbar-bg",
319
+ toolbarBorder: "--oue-toolbar-border",
320
+ popupBg: "--oue-popup-bg",
321
+ radius: "--oue-radius",
322
+ shadow: "--oue-shadow",
323
+ fontFamily: "--oue-font-family",
324
+ fontSize: "--oue-font-size"
325
+ };
326
+ const styleObject = {};
327
+ Object.entries(themeObj).forEach(([key, val]) => {
328
+ if (val && cssVarMap[key]) {
329
+ styleObject[cssVarMap[key]] = String(val);
330
+ }
331
+ });
332
+ return {
333
+ mode: resolvedMode,
334
+ styleObject
335
+ };
336
+ }
337
+
338
+ // src/toolbar/Toolbar.tsx
339
+ import { useState as useState6 } from "react";
340
+
341
+ // src/components/Icons.tsx
342
+ import { jsx as jsx2 } from "react/jsx-runtime";
343
+ var createSvg = (path) => {
344
+ return function SvgIcon(props) {
345
+ return /* @__PURE__ */ jsx2(
346
+ "svg",
347
+ {
348
+ width: "16",
349
+ height: "16",
350
+ viewBox: "0 0 24 24",
351
+ fill: "none",
352
+ stroke: "currentColor",
353
+ strokeWidth: "2",
354
+ strokeLinecap: "round",
355
+ strokeLinejoin: "round",
356
+ ...props,
357
+ children: path
358
+ }
359
+ );
360
+ };
361
+ };
362
+ var DefaultIcons = {
363
+ bold: createSvg(/* @__PURE__ */ jsx2("path", { d: "M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" })),
364
+ italic: createSvg(/* @__PURE__ */ jsx2("path", { d: "M19 4h-9M14 20H5M15 4L9 20" })),
365
+ underline: createSvg(/* @__PURE__ */ jsx2("path", { d: "M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3M4 21h16" })),
366
+ strike: createSvg(/* @__PURE__ */ jsx2("path", { d: "M16 4H9a3 3 0 0 0-2.83 4M14 12a4 4 0 0 1 0 8H6M4 12h16" })),
367
+ code: createSvg(/* @__PURE__ */ jsx2("path", { d: "M16 18l6-6-6-6M8 6l-6 6 6 6" })),
368
+ subscript: createSvg(/* @__PURE__ */ jsx2("path", { d: "M4 5l8 8M12 5l-8 8M20 19h-4l3-3a1.5 1.5 0 0 0-2.12-2.12" })),
369
+ superscript: createSvg(/* @__PURE__ */ jsx2("path", { d: "M4 19l8-8M12 19l-8-8M20 9h-4l3-3a1.5 1.5 0 0 0-2.12-2.12" })),
370
+ clearFormatting: createSvg(/* @__PURE__ */ jsx2("path", { d: "M18 6L6 18M6 6l12 12" })),
371
+ heading: createSvg(/* @__PURE__ */ jsx2("path", { d: "M4 12h8M4 18V6M12 18V6M17 12l3-3v9" })),
372
+ paragraph: createSvg(/* @__PURE__ */ jsx2("path", { d: "M13 4v16M17 4v16M19 4H9.5a4.5 4.5 0 0 0 0 9H13" })),
373
+ bulletList: createSvg(/* @__PURE__ */ jsx2("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" })),
374
+ orderedList: createSvg(/* @__PURE__ */ jsx2("path", { d: "M10 6h11M10 12h11M10 18h11M4 6h1v4M4 10h2M4 18h3M4 14h3v4" })),
375
+ checkList: createSvg(/* @__PURE__ */ jsx2("path", { d: "M9 11l3 3L22 4M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" })),
376
+ indent: createSvg(/* @__PURE__ */ jsx2("path", { d: "M3 12h18M3 6h18M3 18h18M13 8l4 4-4 4" })),
377
+ outdent: createSvg(/* @__PURE__ */ jsx2("path", { d: "M3 12h18M3 6h18M3 18h18M11 8l-4 4 4 4" })),
378
+ alignLeft: createSvg(/* @__PURE__ */ jsx2("path", { d: "M17 10H3M21 6H3M21 14H3M17 18H3" })),
379
+ alignCenter: createSvg(/* @__PURE__ */ jsx2("path", { d: "M18 10H6M21 6H3M21 14H3M18 18H6" })),
380
+ alignRight: createSvg(/* @__PURE__ */ jsx2("path", { d: "M21 10H7M21 6H3M21 14H3M21 18H7" })),
381
+ alignJustify: createSvg(/* @__PURE__ */ jsx2("path", { d: "M21 10H3M21 6H3M21 14H3M21 18H3" })),
382
+ link: createSvg(/* @__PURE__ */ jsx2("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })),
383
+ image: createSvg(/* @__PURE__ */ jsx2("path", { d: "M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z M8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z M21 15l-5-5L5 21" })),
384
+ table: createSvg(/* @__PURE__ */ jsx2("path", { d: "M3 3h18v18H3V3z M3 9h18 M3 15h18 M9 3v18 M15 3v18" })),
385
+ media: createSvg(/* @__PURE__ */ jsx2("path", { d: "M23 7l-7 5 7 5V7z M14 5H3a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2z" })),
386
+ file: createSvg(/* @__PURE__ */ jsx2("path", { d: "M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z M13 2v7h7" })),
387
+ codeBlock: createSvg(/* @__PURE__ */ jsx2("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M10 12l-2 2 2 2 M14 12l2 2-2 2" })),
388
+ blockquote: createSvg(/* @__PURE__ */ jsx2("path", { d: "M3 21c3 0 7-1 7-8V5H3v8h4c0 4-2 6-4 7zm11 0c3 0 7-1 7-8V5h-7v8h4c0 4-2 6-4 7z" })),
389
+ hr: createSvg(/* @__PURE__ */ jsx2("path", { d: "M5 12h14" })),
390
+ undo: createSvg(/* @__PURE__ */ jsx2("path", { d: "M3 7v6h6M3 13L9 7c6-6 12 0 12 0" })),
391
+ redo: createSvg(/* @__PURE__ */ jsx2("path", { d: "M21 7v6h-6M21 13L15 7c-6-6-12 0-12 0" })),
392
+ sourceMode: createSvg(/* @__PURE__ */ jsx2("path", { d: "M14 4l-4 16M4 8l-4 4 4 4M20 8l4 4-4 4" })),
393
+ findReplace: createSvg(/* @__PURE__ */ jsx2("path", { d: "M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z M21 21l-4.35-4.35" })),
394
+ fullscreen: createSvg(/* @__PURE__ */ jsx2("path", { d: "M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3" }))
395
+ };
396
+ function getIcon(key, customIcons) {
397
+ if (customIcons && customIcons[key]) {
398
+ const CustomComp = customIcons[key];
399
+ return /* @__PURE__ */ jsx2(CustomComp, {});
400
+ }
401
+ const DefaultSvg = DefaultIcons[key];
402
+ return DefaultSvg ? /* @__PURE__ */ jsx2(DefaultSvg, {}) : null;
403
+ }
404
+
405
+ // src/components/LinkModal.tsx
406
+ import { useState as useState2 } from "react";
407
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
408
+ function LinkModal({
409
+ initialUrl = "",
410
+ initialTarget = "_self",
411
+ initialTitle = "",
412
+ isOpen,
413
+ onClose,
414
+ onSubmit
415
+ }) {
416
+ const [url, setUrl] = useState2(initialUrl);
417
+ const [target, setTarget] = useState2(initialTarget);
418
+ const [title, setTitle] = useState2(initialTitle);
419
+ const [error, setError] = useState2("");
420
+ if (!isOpen) return null;
421
+ const handleSubmit = (e) => {
422
+ e.preventDefault();
423
+ if (!url.trim()) {
424
+ setError("URL is required");
425
+ return;
426
+ }
427
+ if (url.trim().toLowerCase().startsWith("javascript:")) {
428
+ setError("Unsafe URL protocol (javascript:) is not permitted.");
429
+ return;
430
+ }
431
+ let finalUrl = url.trim();
432
+ if (!/^https?:\/\//i.test(finalUrl) && !/^\//.test(finalUrl) && !/^mailto:/i.test(finalUrl) && !/^tel:/i.test(finalUrl)) {
433
+ finalUrl = `https://${finalUrl}`;
434
+ }
435
+ onSubmit({
436
+ url: finalUrl,
437
+ target: target === "_blank" ? "_blank" : void 0,
438
+ title: title.trim() || void 0
439
+ });
440
+ onClose();
441
+ };
442
+ return /* @__PURE__ */ jsx3("div", { className: "oue-dialog-overlay", onClick: onClose, children: /* @__PURE__ */ jsxs2("div", { className: "oue-dialog", onClick: (e) => e.stopPropagation(), children: [
443
+ /* @__PURE__ */ jsx3("div", { className: "oue-dialog-title", children: "Insert / Edit Link" }),
444
+ /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, children: [
445
+ /* @__PURE__ */ jsxs2("div", { className: "oue-dialog-field", children: [
446
+ /* @__PURE__ */ jsx3("label", { className: "oue-dialog-label", children: "URL" }),
447
+ /* @__PURE__ */ jsx3(
448
+ "input",
449
+ {
450
+ type: "text",
451
+ className: "oue-dialog-input",
452
+ placeholder: "https://example.com",
453
+ value: url,
454
+ onChange: (e) => {
455
+ setUrl(e.target.value);
456
+ setError("");
457
+ },
458
+ autoFocus: true
459
+ }
460
+ ),
461
+ error && /* @__PURE__ */ jsx3("span", { style: { color: "#ef4444", fontSize: 12 }, children: error })
462
+ ] }),
463
+ /* @__PURE__ */ jsxs2("div", { className: "oue-dialog-field", children: [
464
+ /* @__PURE__ */ jsx3("label", { className: "oue-dialog-label", children: "Title (optional)" }),
465
+ /* @__PURE__ */ jsx3(
466
+ "input",
467
+ {
468
+ type: "text",
469
+ className: "oue-dialog-input",
470
+ placeholder: "Link title",
471
+ value: title,
472
+ onChange: (e) => setTitle(e.target.value)
473
+ }
474
+ )
475
+ ] }),
476
+ /* @__PURE__ */ jsxs2("div", { className: "oue-dialog-field", style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
477
+ /* @__PURE__ */ jsx3(
478
+ "input",
479
+ {
480
+ type: "checkbox",
481
+ id: "oue-link-target",
482
+ checked: target === "_blank",
483
+ onChange: (e) => setTarget(e.target.checked ? "_blank" : "_self")
484
+ }
485
+ ),
486
+ /* @__PURE__ */ jsx3("label", { htmlFor: "oue-link-target", className: "oue-dialog-label", style: { cursor: "pointer" }, children: 'Open in new tab (target="_blank")' })
487
+ ] }),
488
+ /* @__PURE__ */ jsxs2("div", { className: "oue-dialog-actions", children: [
489
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "oue-toolbar-btn", onClick: onClose, children: "Cancel" }),
490
+ /* @__PURE__ */ jsx3(
491
+ "button",
492
+ {
493
+ type: "submit",
494
+ className: "oue-toolbar-btn",
495
+ style: { backgroundColor: "var(--oue-primary)", color: "var(--oue-primary-foreground)", padding: "0 14px" },
496
+ children: "Save Link"
497
+ }
498
+ )
499
+ ] })
500
+ ] })
501
+ ] }) });
502
+ }
503
+
504
+ // src/components/ImageModal.tsx
505
+ import { useState as useState3 } from "react";
506
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
507
+ function ImageModal({
508
+ isOpen,
509
+ onClose,
510
+ uploader,
511
+ maxSize,
512
+ allowedTypes,
513
+ onSubmit
514
+ }) {
515
+ const [tab, setTab] = useState3("url");
516
+ const [url, setUrl] = useState3("");
517
+ const [alt, setAlt] = useState3("");
518
+ const [isLoading, setIsLoading] = useState3(false);
519
+ const [error, setError] = useState3("");
520
+ if (!isOpen) return null;
521
+ const handleUrlSubmit = (e) => {
522
+ e.preventDefault();
523
+ if (!url.trim()) {
524
+ setError("Image URL is required");
525
+ return;
526
+ }
527
+ onSubmit({ src: url.trim(), alt: alt.trim() || void 0 });
528
+ onClose();
529
+ };
530
+ const handleFileUpload = async (e) => {
531
+ const file = e.target.files?.[0];
532
+ if (!file) return;
533
+ if (maxSize && file.size > maxSize) {
534
+ setError(`File size exceeds maximum allowed limit (${(maxSize / (1024 * 1024)).toFixed(1)} MB)`);
535
+ return;
536
+ }
537
+ if (allowedTypes && allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
538
+ setError(`File type ${file.type} is not allowed.`);
539
+ return;
540
+ }
541
+ setIsLoading(true);
542
+ setError("");
543
+ try {
544
+ if (uploader) {
545
+ const res = await uploader(file);
546
+ onSubmit({
547
+ src: res.src,
548
+ alt: res.alt || alt.trim() || file.name,
549
+ width: res.width,
550
+ height: res.height
551
+ });
552
+ } else {
553
+ const reader = new FileReader();
554
+ reader.onload = () => {
555
+ if (typeof reader.result === "string") {
556
+ onSubmit({ src: reader.result, alt: alt.trim() || file.name });
557
+ }
558
+ };
559
+ reader.readAsDataURL(file);
560
+ }
561
+ onClose();
562
+ } catch (err) {
563
+ setError(err?.message || "Failed to upload image");
564
+ } finally {
565
+ setIsLoading(false);
566
+ }
567
+ };
568
+ return /* @__PURE__ */ jsx4("div", { className: "oue-dialog-overlay", onClick: onClose, children: /* @__PURE__ */ jsxs3("div", { className: "oue-dialog", onClick: (e) => e.stopPropagation(), children: [
569
+ /* @__PURE__ */ jsx4("div", { className: "oue-dialog-title", children: "Insert Image" }),
570
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 12, marginBottom: 16, borderBottom: "1px solid var(--oue-border)" }, children: [
571
+ /* @__PURE__ */ jsx4(
572
+ "button",
573
+ {
574
+ type: "button",
575
+ className: `oue-toolbar-btn ${tab === "url" ? "oue-active" : ""}`,
576
+ onClick: () => setTab("url"),
577
+ children: "Image URL"
578
+ }
579
+ ),
580
+ /* @__PURE__ */ jsx4(
581
+ "button",
582
+ {
583
+ type: "button",
584
+ className: `oue-toolbar-btn ${tab === "upload" ? "oue-active" : ""}`,
585
+ onClick: () => setTab("upload"),
586
+ children: "Upload File"
587
+ }
588
+ )
589
+ ] }),
590
+ tab === "url" ? /* @__PURE__ */ jsxs3("form", { onSubmit: handleUrlSubmit, children: [
591
+ /* @__PURE__ */ jsxs3("div", { className: "oue-dialog-field", children: [
592
+ /* @__PURE__ */ jsx4("label", { className: "oue-dialog-label", children: "Image URL" }),
593
+ /* @__PURE__ */ jsx4(
594
+ "input",
595
+ {
596
+ type: "text",
597
+ className: "oue-dialog-input",
598
+ placeholder: "https://example.com/image.jpg",
599
+ value: url,
600
+ onChange: (e) => {
601
+ setUrl(e.target.value);
602
+ setError("");
603
+ },
604
+ autoFocus: true
605
+ }
606
+ )
607
+ ] }),
608
+ /* @__PURE__ */ jsxs3("div", { className: "oue-dialog-field", children: [
609
+ /* @__PURE__ */ jsx4("label", { className: "oue-dialog-label", children: "Alt Text (description)" }),
610
+ /* @__PURE__ */ jsx4(
611
+ "input",
612
+ {
613
+ type: "text",
614
+ className: "oue-dialog-input",
615
+ placeholder: "Describe image...",
616
+ value: alt,
617
+ onChange: (e) => setAlt(e.target.value)
618
+ }
619
+ )
620
+ ] }),
621
+ error && /* @__PURE__ */ jsx4("div", { style: { color: "#ef4444", fontSize: 12, marginBottom: 12 }, children: error }),
622
+ /* @__PURE__ */ jsxs3("div", { className: "oue-dialog-actions", children: [
623
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "oue-toolbar-btn", onClick: onClose, children: "Cancel" }),
624
+ /* @__PURE__ */ jsx4(
625
+ "button",
626
+ {
627
+ type: "submit",
628
+ className: "oue-toolbar-btn",
629
+ style: { backgroundColor: "var(--oue-primary)", color: "var(--oue-primary-foreground)", padding: "0 14px" },
630
+ children: "Insert Image"
631
+ }
632
+ )
633
+ ] })
634
+ ] }) : /* @__PURE__ */ jsxs3("div", { children: [
635
+ /* @__PURE__ */ jsxs3("div", { className: "oue-dialog-field", children: [
636
+ /* @__PURE__ */ jsx4("label", { className: "oue-dialog-label", children: "Select Image File" }),
637
+ /* @__PURE__ */ jsx4(
638
+ "input",
639
+ {
640
+ type: "file",
641
+ accept: allowedTypes ? allowedTypes.join(",") : "image/*",
642
+ onChange: handleFileUpload,
643
+ disabled: isLoading
644
+ }
645
+ )
646
+ ] }),
647
+ /* @__PURE__ */ jsxs3("div", { className: "oue-dialog-field", children: [
648
+ /* @__PURE__ */ jsx4("label", { className: "oue-dialog-label", children: "Alt Text (optional)" }),
649
+ /* @__PURE__ */ jsx4(
650
+ "input",
651
+ {
652
+ type: "text",
653
+ className: "oue-dialog-input",
654
+ placeholder: "Describe image...",
655
+ value: alt,
656
+ onChange: (e) => setAlt(e.target.value)
657
+ }
658
+ )
659
+ ] }),
660
+ isLoading && /* @__PURE__ */ jsx4("div", { style: { fontSize: 13, color: "var(--oue-text-muted)", margin: "8px 0" }, children: "Uploading..." }),
661
+ error && /* @__PURE__ */ jsx4("div", { style: { color: "#ef4444", fontSize: 12, marginBottom: 12 }, children: error }),
662
+ /* @__PURE__ */ jsx4("div", { className: "oue-dialog-actions", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "oue-toolbar-btn", onClick: onClose, children: "Cancel" }) })
663
+ ] })
664
+ ] }) });
665
+ }
666
+
667
+ // src/components/TableModal.tsx
668
+ import { useState as useState4 } from "react";
669
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
670
+ function TableModal({ isOpen, onClose, onSubmit }) {
671
+ const [rows, setRows] = useState4(3);
672
+ const [cols, setCols] = useState4(3);
673
+ if (!isOpen) return null;
674
+ const handleSubmit = (e) => {
675
+ e.preventDefault();
676
+ onSubmit(Math.max(1, rows), Math.max(1, cols));
677
+ onClose();
678
+ };
679
+ return /* @__PURE__ */ jsx5("div", { className: "oue-dialog-overlay", onClick: onClose, children: /* @__PURE__ */ jsxs4("div", { className: "oue-dialog", onClick: (e) => e.stopPropagation(), children: [
680
+ /* @__PURE__ */ jsx5("div", { className: "oue-dialog-title", children: "Insert Table" }),
681
+ /* @__PURE__ */ jsxs4("form", { onSubmit: handleSubmit, children: [
682
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 16 }, children: [
683
+ /* @__PURE__ */ jsxs4("div", { className: "oue-dialog-field", style: { flex: 1 }, children: [
684
+ /* @__PURE__ */ jsx5("label", { className: "oue-dialog-label", children: "Rows" }),
685
+ /* @__PURE__ */ jsx5(
686
+ "input",
687
+ {
688
+ type: "number",
689
+ min: "1",
690
+ max: "20",
691
+ className: "oue-dialog-input",
692
+ value: rows,
693
+ onChange: (e) => setRows(parseInt(e.target.value) || 1),
694
+ autoFocus: true
695
+ }
696
+ )
697
+ ] }),
698
+ /* @__PURE__ */ jsxs4("div", { className: "oue-dialog-field", style: { flex: 1 }, children: [
699
+ /* @__PURE__ */ jsx5("label", { className: "oue-dialog-label", children: "Columns" }),
700
+ /* @__PURE__ */ jsx5(
701
+ "input",
702
+ {
703
+ type: "number",
704
+ min: "1",
705
+ max: "20",
706
+ className: "oue-dialog-input",
707
+ value: cols,
708
+ onChange: (e) => setCols(parseInt(e.target.value) || 1)
709
+ }
710
+ )
711
+ ] })
712
+ ] }),
713
+ /* @__PURE__ */ jsxs4("div", { className: "oue-dialog-actions", children: [
714
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "oue-toolbar-btn", onClick: onClose, children: "Cancel" }),
715
+ /* @__PURE__ */ jsx5(
716
+ "button",
717
+ {
718
+ type: "submit",
719
+ className: "oue-toolbar-btn",
720
+ style: { backgroundColor: "var(--oue-primary)", color: "var(--oue-primary-foreground)", padding: "0 14px" },
721
+ children: "Insert Table"
722
+ }
723
+ )
724
+ ] })
725
+ ] })
726
+ ] }) });
727
+ }
728
+
729
+ // src/components/FindReplaceDialog.tsx
730
+ import { useState as useState5 } from "react";
731
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
732
+ function FindReplaceDialog({ isOpen, onClose }) {
733
+ const { editor, getHTML, setHTML } = useEditor();
734
+ const [findText, setFindText] = useState5("");
735
+ const [replaceText, setReplaceText] = useState5("");
736
+ const [matchCase, setMatchCase] = useState5(false);
737
+ const [statusMessage, setStatusMessage] = useState5("");
738
+ if (!isOpen) return null;
739
+ const handleReplaceAll = () => {
740
+ if (!findText) return;
741
+ const html = getHTML();
742
+ const flags = matchCase ? "g" : "gi";
743
+ const regex = new RegExp(findText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags);
744
+ const matches = (html.match(regex) || []).length;
745
+ if (matches === 0) {
746
+ setStatusMessage("No matches found");
747
+ return;
748
+ }
749
+ const updatedHtml = html.replace(regex, replaceText);
750
+ setHTML(updatedHtml);
751
+ setStatusMessage(`Replaced ${matches} occurrence(s)`);
752
+ };
753
+ const handleReplaceNext = () => {
754
+ if (!findText) return;
755
+ const html = getHTML();
756
+ const flags = matchCase ? "" : "i";
757
+ const regex = new RegExp(findText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags);
758
+ if (!regex.test(html)) {
759
+ setStatusMessage("No matches found");
760
+ return;
761
+ }
762
+ const updatedHtml = html.replace(regex, replaceText);
763
+ setHTML(updatedHtml);
764
+ setStatusMessage("Replaced 1 occurrence");
765
+ };
766
+ return /* @__PURE__ */ jsx6("div", { className: "oue-dialog-overlay", onClick: onClose, children: /* @__PURE__ */ jsxs5("div", { className: "oue-dialog", onClick: (e) => e.stopPropagation(), children: [
767
+ /* @__PURE__ */ jsx6("div", { className: "oue-dialog-title", children: "Find and Replace" }),
768
+ /* @__PURE__ */ jsxs5("div", { className: "oue-dialog-field", children: [
769
+ /* @__PURE__ */ jsx6("label", { className: "oue-dialog-label", children: "Find" }),
770
+ /* @__PURE__ */ jsx6(
771
+ "input",
772
+ {
773
+ type: "text",
774
+ className: "oue-dialog-input",
775
+ placeholder: "Search text...",
776
+ value: findText,
777
+ onChange: (e) => {
778
+ setFindText(e.target.value);
779
+ setStatusMessage("");
780
+ },
781
+ autoFocus: true
782
+ }
783
+ )
784
+ ] }),
785
+ /* @__PURE__ */ jsxs5("div", { className: "oue-dialog-field", children: [
786
+ /* @__PURE__ */ jsx6("label", { className: "oue-dialog-label", children: "Replace with" }),
787
+ /* @__PURE__ */ jsx6(
788
+ "input",
789
+ {
790
+ type: "text",
791
+ className: "oue-dialog-input",
792
+ placeholder: "Replacement text...",
793
+ value: replaceText,
794
+ onChange: (e) => {
795
+ setReplaceText(e.target.value);
796
+ setStatusMessage("");
797
+ }
798
+ }
799
+ )
800
+ ] }),
801
+ /* @__PURE__ */ jsxs5("div", { className: "oue-dialog-field", style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
802
+ /* @__PURE__ */ jsx6(
803
+ "input",
804
+ {
805
+ type: "checkbox",
806
+ id: "oue-match-case",
807
+ checked: matchCase,
808
+ onChange: (e) => setMatchCase(e.target.checked)
809
+ }
810
+ ),
811
+ /* @__PURE__ */ jsx6("label", { htmlFor: "oue-match-case", className: "oue-dialog-label", style: { cursor: "pointer" }, children: "Match case" })
812
+ ] }),
813
+ statusMessage && /* @__PURE__ */ jsx6("div", { style: { fontSize: 12, color: "var(--oue-primary)", margin: "8px 0" }, children: statusMessage }),
814
+ /* @__PURE__ */ jsxs5("div", { className: "oue-dialog-actions", children: [
815
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "oue-toolbar-btn", onClick: onClose, children: "Close" }),
816
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "oue-toolbar-btn", onClick: handleReplaceNext, children: "Replace" }),
817
+ /* @__PURE__ */ jsx6(
818
+ "button",
819
+ {
820
+ type: "button",
821
+ className: "oue-toolbar-btn",
822
+ style: { backgroundColor: "var(--oue-primary)", color: "var(--oue-primary-foreground)", padding: "0 14px" },
823
+ onClick: handleReplaceAll,
824
+ children: "Replace All"
825
+ }
826
+ )
827
+ ] })
828
+ ] }) });
829
+ }
830
+
831
+ // src/toolbar/Toolbar.tsx
832
+ import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
833
+ function Toolbar({ config, imageUploader }) {
834
+ const editorCtx = useEditor();
835
+ const { activeStates, icons } = editorCtx;
836
+ const [isLinkOpen, setIsLinkOpen] = useState6(false);
837
+ const [isImageOpen, setIsImageOpen] = useState6(false);
838
+ const [isTableOpen, setIsTableOpen] = useState6(false);
839
+ const [isFindOpen, setIsFindOpen] = useState6(false);
840
+ if (config === false) return null;
841
+ const items = config || [
842
+ "undo",
843
+ "redo",
844
+ "|",
845
+ "heading",
846
+ "|",
847
+ "bold",
848
+ "italic",
849
+ "underline",
850
+ "strike",
851
+ "code",
852
+ "|",
853
+ "align",
854
+ "|",
855
+ "bulletList",
856
+ "orderedList",
857
+ "checkList",
858
+ "|",
859
+ "link",
860
+ "image",
861
+ "table",
862
+ "codeBlock",
863
+ "blockquote",
864
+ "hr",
865
+ "|",
866
+ "sourceMode",
867
+ "findReplace",
868
+ "fullscreen"
869
+ ];
870
+ const handleHeadingChange = (e) => {
871
+ const val = e.target.value;
872
+ editorCtx.setHeading(val);
873
+ };
874
+ const handleAlignChange = (e) => {
875
+ const val = e.target.value;
876
+ editorCtx.setAlignment(val);
877
+ };
878
+ const getCurrentHeadingValue = () => {
879
+ if (activeStates.isH1) return "h1";
880
+ if (activeStates.isH2) return "h2";
881
+ if (activeStates.isH3) return "h3";
882
+ if (activeStates.isH4) return "h4";
883
+ if (activeStates.isH5) return "h5";
884
+ if (activeStates.isH6) return "h6";
885
+ return "p";
886
+ };
887
+ const getCurrentAlignValue = () => {
888
+ if (activeStates.isAlignCenter) return "center";
889
+ if (activeStates.isAlignRight) return "right";
890
+ if (activeStates.isAlignJustify) return "justify";
891
+ return "left";
892
+ };
893
+ return /* @__PURE__ */ jsxs6(Fragment, { children: [
894
+ /* @__PURE__ */ jsx7(
895
+ "div",
896
+ {
897
+ className: "oue-toolbar",
898
+ role: "toolbar",
899
+ "aria-label": "Editor formatting toolbar",
900
+ onMouseDown: (e) => {
901
+ if (e.target.closest("select") === null) {
902
+ e.preventDefault();
903
+ }
904
+ },
905
+ children: items.map((item, idx) => {
906
+ if (typeof item === "function" || typeof item === "object") {
907
+ const CustomComp = item;
908
+ return /* @__PURE__ */ jsx7(CustomComp, {}, idx);
909
+ }
910
+ if (item === "|") {
911
+ return /* @__PURE__ */ jsx7("div", { className: "oue-toolbar-separator", "aria-hidden": "true" }, idx);
912
+ }
913
+ switch (item) {
914
+ case "undo":
915
+ return /* @__PURE__ */ jsx7(
916
+ "button",
917
+ {
918
+ type: "button",
919
+ "aria-label": "Undo",
920
+ title: "Undo (Ctrl+Z)",
921
+ className: "oue-toolbar-btn",
922
+ disabled: !activeStates.canUndo,
923
+ onClick: editorCtx.undo,
924
+ children: getIcon("undo", icons)
925
+ },
926
+ idx
927
+ );
928
+ case "redo":
929
+ return /* @__PURE__ */ jsx7(
930
+ "button",
931
+ {
932
+ type: "button",
933
+ "aria-label": "Redo",
934
+ title: "Redo (Ctrl+Y / Cmd+Shift+Z)",
935
+ className: "oue-toolbar-btn",
936
+ disabled: !activeStates.canRedo,
937
+ onClick: editorCtx.redo,
938
+ children: getIcon("redo", icons)
939
+ },
940
+ idx
941
+ );
942
+ case "heading":
943
+ return /* @__PURE__ */ jsxs6(
944
+ "select",
945
+ {
946
+ "aria-label": "Text style heading",
947
+ className: "oue-toolbar-select",
948
+ value: getCurrentHeadingValue(),
949
+ onChange: handleHeadingChange,
950
+ children: [
951
+ /* @__PURE__ */ jsx7("option", { value: "p", children: "Paragraph" }),
952
+ /* @__PURE__ */ jsx7("option", { value: "h1", children: "Heading 1" }),
953
+ /* @__PURE__ */ jsx7("option", { value: "h2", children: "Heading 2" }),
954
+ /* @__PURE__ */ jsx7("option", { value: "h3", children: "Heading 3" }),
955
+ /* @__PURE__ */ jsx7("option", { value: "h4", children: "Heading 4" }),
956
+ /* @__PURE__ */ jsx7("option", { value: "h5", children: "Heading 5" }),
957
+ /* @__PURE__ */ jsx7("option", { value: "h6", children: "Heading 6" })
958
+ ]
959
+ },
960
+ idx
961
+ );
962
+ case "bold":
963
+ return /* @__PURE__ */ jsx7(
964
+ "button",
965
+ {
966
+ type: "button",
967
+ "aria-label": "Bold",
968
+ title: "Bold (Ctrl+B)",
969
+ className: `oue-toolbar-btn ${activeStates.isBold ? "oue-active" : ""}`,
970
+ onClick: editorCtx.toggleBold,
971
+ children: getIcon("bold", icons)
972
+ },
973
+ idx
974
+ );
975
+ case "italic":
976
+ return /* @__PURE__ */ jsx7(
977
+ "button",
978
+ {
979
+ type: "button",
980
+ "aria-label": "Italic",
981
+ title: "Italic (Ctrl+I)",
982
+ className: `oue-toolbar-btn ${activeStates.isItalic ? "oue-active" : ""}`,
983
+ onClick: editorCtx.toggleItalic,
984
+ children: getIcon("italic", icons)
985
+ },
986
+ idx
987
+ );
988
+ case "underline":
989
+ return /* @__PURE__ */ jsx7(
990
+ "button",
991
+ {
992
+ type: "button",
993
+ "aria-label": "Underline",
994
+ title: "Underline (Ctrl+U)",
995
+ className: `oue-toolbar-btn ${activeStates.isUnderline ? "oue-active" : ""}`,
996
+ onClick: editorCtx.toggleUnderline,
997
+ children: getIcon("underline", icons)
998
+ },
999
+ idx
1000
+ );
1001
+ case "strike":
1002
+ return /* @__PURE__ */ jsx7(
1003
+ "button",
1004
+ {
1005
+ type: "button",
1006
+ "aria-label": "Strikethrough",
1007
+ title: "Strikethrough",
1008
+ className: `oue-toolbar-btn ${activeStates.isStrikethrough ? "oue-active" : ""}`,
1009
+ onClick: editorCtx.toggleStrikethrough,
1010
+ children: getIcon("strike", icons)
1011
+ },
1012
+ idx
1013
+ );
1014
+ case "code":
1015
+ return /* @__PURE__ */ jsx7(
1016
+ "button",
1017
+ {
1018
+ type: "button",
1019
+ "aria-label": "Inline Code",
1020
+ title: "Inline Code",
1021
+ className: `oue-toolbar-btn ${activeStates.isCode ? "oue-active" : ""}`,
1022
+ onClick: editorCtx.toggleCode,
1023
+ children: getIcon("code", icons)
1024
+ },
1025
+ idx
1026
+ );
1027
+ case "subscript":
1028
+ return /* @__PURE__ */ jsx7(
1029
+ "button",
1030
+ {
1031
+ type: "button",
1032
+ "aria-label": "Subscript",
1033
+ title: "Subscript",
1034
+ className: `oue-toolbar-btn ${activeStates.isSubscript ? "oue-active" : ""}`,
1035
+ onClick: editorCtx.toggleSubscript,
1036
+ children: getIcon("subscript", icons)
1037
+ },
1038
+ idx
1039
+ );
1040
+ case "superscript":
1041
+ return /* @__PURE__ */ jsx7(
1042
+ "button",
1043
+ {
1044
+ type: "button",
1045
+ "aria-label": "Superscript",
1046
+ title: "Superscript",
1047
+ className: `oue-toolbar-btn ${activeStates.isSuperscript ? "oue-active" : ""}`,
1048
+ onClick: editorCtx.toggleSuperscript,
1049
+ children: getIcon("superscript", icons)
1050
+ },
1051
+ idx
1052
+ );
1053
+ case "clearFormatting":
1054
+ return /* @__PURE__ */ jsx7(
1055
+ "button",
1056
+ {
1057
+ type: "button",
1058
+ "aria-label": "Clear formatting",
1059
+ title: "Clear formatting",
1060
+ className: "oue-toolbar-btn",
1061
+ onClick: editorCtx.clearFormatting,
1062
+ children: getIcon("clearFormatting", icons)
1063
+ },
1064
+ idx
1065
+ );
1066
+ case "align":
1067
+ return /* @__PURE__ */ jsxs6(
1068
+ "select",
1069
+ {
1070
+ "aria-label": "Text Alignment",
1071
+ className: "oue-toolbar-select",
1072
+ value: getCurrentAlignValue(),
1073
+ onChange: handleAlignChange,
1074
+ children: [
1075
+ /* @__PURE__ */ jsx7("option", { value: "left", children: "Align Left" }),
1076
+ /* @__PURE__ */ jsx7("option", { value: "center", children: "Align Center" }),
1077
+ /* @__PURE__ */ jsx7("option", { value: "right", children: "Align Right" }),
1078
+ /* @__PURE__ */ jsx7("option", { value: "justify", children: "Justify" })
1079
+ ]
1080
+ },
1081
+ idx
1082
+ );
1083
+ case "bulletList":
1084
+ return /* @__PURE__ */ jsx7(
1085
+ "button",
1086
+ {
1087
+ type: "button",
1088
+ "aria-label": "Bullet List",
1089
+ title: "Bullet List",
1090
+ className: `oue-toolbar-btn ${activeStates.isBulletList ? "oue-active" : ""}`,
1091
+ onClick: editorCtx.toggleBulletList,
1092
+ children: getIcon("bulletList", icons)
1093
+ },
1094
+ idx
1095
+ );
1096
+ case "orderedList":
1097
+ return /* @__PURE__ */ jsx7(
1098
+ "button",
1099
+ {
1100
+ type: "button",
1101
+ "aria-label": "Numbered List",
1102
+ title: "Numbered List",
1103
+ className: `oue-toolbar-btn ${activeStates.isOrderedList ? "oue-active" : ""}`,
1104
+ onClick: editorCtx.toggleOrderedList,
1105
+ children: getIcon("orderedList", icons)
1106
+ },
1107
+ idx
1108
+ );
1109
+ case "checkList":
1110
+ return /* @__PURE__ */ jsx7(
1111
+ "button",
1112
+ {
1113
+ type: "button",
1114
+ "aria-label": "Task List",
1115
+ title: "Task List",
1116
+ className: `oue-toolbar-btn ${activeStates.isCheckList ? "oue-active" : ""}`,
1117
+ onClick: editorCtx.toggleCheckList,
1118
+ children: getIcon("checkList", icons)
1119
+ },
1120
+ idx
1121
+ );
1122
+ case "indent":
1123
+ return /* @__PURE__ */ jsx7(
1124
+ "button",
1125
+ {
1126
+ type: "button",
1127
+ "aria-label": "Indent",
1128
+ title: "Indent (Tab)",
1129
+ className: "oue-toolbar-btn",
1130
+ onClick: editorCtx.indent,
1131
+ children: getIcon("indent", icons)
1132
+ },
1133
+ idx
1134
+ );
1135
+ case "outdent":
1136
+ return /* @__PURE__ */ jsx7(
1137
+ "button",
1138
+ {
1139
+ type: "button",
1140
+ "aria-label": "Outdent",
1141
+ title: "Outdent (Shift+Tab)",
1142
+ className: "oue-toolbar-btn",
1143
+ onClick: editorCtx.outdent,
1144
+ children: getIcon("outdent", icons)
1145
+ },
1146
+ idx
1147
+ );
1148
+ case "link":
1149
+ return /* @__PURE__ */ jsx7(
1150
+ "button",
1151
+ {
1152
+ type: "button",
1153
+ "aria-label": "Insert Link",
1154
+ title: "Insert Link",
1155
+ className: `oue-toolbar-btn ${activeStates.isLink ? "oue-active" : ""}`,
1156
+ onClick: () => setIsLinkOpen(true),
1157
+ children: getIcon("link", icons)
1158
+ },
1159
+ idx
1160
+ );
1161
+ case "image":
1162
+ return /* @__PURE__ */ jsx7(
1163
+ "button",
1164
+ {
1165
+ type: "button",
1166
+ "aria-label": "Insert Image",
1167
+ title: "Insert Image",
1168
+ className: "oue-toolbar-btn",
1169
+ onClick: () => setIsImageOpen(true),
1170
+ children: getIcon("image", icons)
1171
+ },
1172
+ idx
1173
+ );
1174
+ case "table":
1175
+ return /* @__PURE__ */ jsx7(
1176
+ "button",
1177
+ {
1178
+ type: "button",
1179
+ "aria-label": "Insert Table",
1180
+ title: "Insert Table",
1181
+ className: "oue-toolbar-btn",
1182
+ onClick: () => setIsTableOpen(true),
1183
+ children: getIcon("table", icons)
1184
+ },
1185
+ idx
1186
+ );
1187
+ case "codeBlock":
1188
+ return /* @__PURE__ */ jsx7(
1189
+ "button",
1190
+ {
1191
+ type: "button",
1192
+ "aria-label": "Code Block",
1193
+ title: "Code Block",
1194
+ className: `oue-toolbar-btn ${activeStates.isCodeBlock ? "oue-active" : ""}`,
1195
+ onClick: () => editorCtx.insertCodeBlock(),
1196
+ children: getIcon("codeBlock", icons)
1197
+ },
1198
+ idx
1199
+ );
1200
+ case "blockquote":
1201
+ return /* @__PURE__ */ jsx7(
1202
+ "button",
1203
+ {
1204
+ type: "button",
1205
+ "aria-label": "Blockquote",
1206
+ title: "Blockquote",
1207
+ className: `oue-toolbar-btn ${activeStates.isBlockquote ? "oue-active" : ""}`,
1208
+ onClick: editorCtx.toggleBlockquote,
1209
+ children: getIcon("blockquote", icons)
1210
+ },
1211
+ idx
1212
+ );
1213
+ case "hr":
1214
+ return /* @__PURE__ */ jsx7(
1215
+ "button",
1216
+ {
1217
+ type: "button",
1218
+ "aria-label": "Horizontal Rule",
1219
+ title: "Horizontal Rule",
1220
+ className: "oue-toolbar-btn",
1221
+ onClick: editorCtx.insertHR,
1222
+ children: getIcon("hr", icons)
1223
+ },
1224
+ idx
1225
+ );
1226
+ case "sourceMode":
1227
+ return /* @__PURE__ */ jsx7(
1228
+ "button",
1229
+ {
1230
+ type: "button",
1231
+ "aria-label": "HTML Source Mode",
1232
+ title: "Toggle HTML Source",
1233
+ className: `oue-toolbar-btn ${editorCtx.isSourceMode ? "oue-active" : ""}`,
1234
+ onClick: editorCtx.toggleSourceMode,
1235
+ children: getIcon("sourceMode", icons)
1236
+ },
1237
+ idx
1238
+ );
1239
+ case "findReplace":
1240
+ return /* @__PURE__ */ jsx7(
1241
+ "button",
1242
+ {
1243
+ type: "button",
1244
+ "aria-label": "Find and Replace",
1245
+ title: "Find & Replace (Ctrl+F)",
1246
+ className: "oue-toolbar-btn",
1247
+ onClick: () => setIsFindOpen(true),
1248
+ children: getIcon("findReplace", icons)
1249
+ },
1250
+ idx
1251
+ );
1252
+ case "fullscreen":
1253
+ return /* @__PURE__ */ jsx7(
1254
+ "button",
1255
+ {
1256
+ type: "button",
1257
+ "aria-label": "Fullscreen",
1258
+ title: "Toggle Fullscreen",
1259
+ className: `oue-toolbar-btn ${editorCtx.isFullscreen ? "oue-active" : ""}`,
1260
+ onClick: editorCtx.toggleFullscreen,
1261
+ children: getIcon("fullscreen", icons)
1262
+ },
1263
+ idx
1264
+ );
1265
+ default:
1266
+ return null;
1267
+ }
1268
+ })
1269
+ }
1270
+ ),
1271
+ /* @__PURE__ */ jsx7(
1272
+ LinkModal,
1273
+ {
1274
+ isOpen: isLinkOpen,
1275
+ onClose: () => setIsLinkOpen(false),
1276
+ onSubmit: ({ url, target, title }) => editorCtx.insertLink(url, target, title)
1277
+ }
1278
+ ),
1279
+ /* @__PURE__ */ jsx7(
1280
+ ImageModal,
1281
+ {
1282
+ isOpen: isImageOpen,
1283
+ uploader: imageUploader,
1284
+ onClose: () => setIsImageOpen(false),
1285
+ onSubmit: ({ src, alt, width, height }) => editorCtx.insertImage(src, alt, width, height)
1286
+ }
1287
+ ),
1288
+ /* @__PURE__ */ jsx7(
1289
+ TableModal,
1290
+ {
1291
+ isOpen: isTableOpen,
1292
+ onClose: () => setIsTableOpen(false),
1293
+ onSubmit: (rows, cols) => editorCtx.insertTable(rows, cols)
1294
+ }
1295
+ ),
1296
+ /* @__PURE__ */ jsx7(FindReplaceDialog, { isOpen: isFindOpen, onClose: () => setIsFindOpen(false) })
1297
+ ] });
1298
+ }
1299
+
1300
+ // src/toolbar/BubbleToolbar.tsx
1301
+ import { useEffect as useEffect2, useState as useState7, useRef } from "react";
1302
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1303
+ function BubbleToolbar({ config }) {
1304
+ const editorCtx = useEditor();
1305
+ const { editor, activeStates, icons } = editorCtx;
1306
+ const [position, setPosition] = useState7(null);
1307
+ const [isLinkOpen, setIsLinkOpen] = useState7(false);
1308
+ const toolbarRef = useRef(null);
1309
+ useEffect2(() => {
1310
+ if (!editor || config === false) return;
1311
+ const updatePosition = () => {
1312
+ const selection = window.getSelection();
1313
+ if (!selection || selection.isCollapsed || !selection.rangeCount) {
1314
+ setPosition(null);
1315
+ return;
1316
+ }
1317
+ const range = selection.getRangeAt(0);
1318
+ const rect = range.getBoundingClientRect();
1319
+ if (rect.width === 0 || rect.height === 0) {
1320
+ setPosition(null);
1321
+ return;
1322
+ }
1323
+ const top = rect.top - 48 + window.scrollY;
1324
+ const left = rect.left + rect.width / 2 + window.scrollX;
1325
+ setPosition({ top: Math.max(10, top), left });
1326
+ };
1327
+ document.addEventListener("selectionchange", updatePosition);
1328
+ return () => document.removeEventListener("selectionchange", updatePosition);
1329
+ }, [editor, config]);
1330
+ if (config === false || !position) return null;
1331
+ const items = config || ["bold", "italic", "underline", "strike", "link"];
1332
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
1333
+ /* @__PURE__ */ jsx8(
1334
+ "div",
1335
+ {
1336
+ ref: toolbarRef,
1337
+ className: "oue-bubble-toolbar",
1338
+ style: {
1339
+ top: position.top,
1340
+ left: position.left,
1341
+ transform: "translateX(-50%)"
1342
+ },
1343
+ onMouseDown: (e) => e.preventDefault(),
1344
+ children: items.map((item, idx) => {
1345
+ if (typeof item !== "string") return null;
1346
+ switch (item) {
1347
+ case "bold":
1348
+ return /* @__PURE__ */ jsx8(
1349
+ "button",
1350
+ {
1351
+ type: "button",
1352
+ "aria-label": "Bold",
1353
+ className: `oue-toolbar-btn ${activeStates.isBold ? "oue-active" : ""}`,
1354
+ onClick: editorCtx.toggleBold,
1355
+ children: getIcon("bold", icons)
1356
+ },
1357
+ idx
1358
+ );
1359
+ case "italic":
1360
+ return /* @__PURE__ */ jsx8(
1361
+ "button",
1362
+ {
1363
+ type: "button",
1364
+ "aria-label": "Italic",
1365
+ className: `oue-toolbar-btn ${activeStates.isItalic ? "oue-active" : ""}`,
1366
+ onClick: editorCtx.toggleItalic,
1367
+ children: getIcon("italic", icons)
1368
+ },
1369
+ idx
1370
+ );
1371
+ case "underline":
1372
+ return /* @__PURE__ */ jsx8(
1373
+ "button",
1374
+ {
1375
+ type: "button",
1376
+ "aria-label": "Underline",
1377
+ className: `oue-toolbar-btn ${activeStates.isUnderline ? "oue-active" : ""}`,
1378
+ onClick: editorCtx.toggleUnderline,
1379
+ children: getIcon("underline", icons)
1380
+ },
1381
+ idx
1382
+ );
1383
+ case "strike":
1384
+ return /* @__PURE__ */ jsx8(
1385
+ "button",
1386
+ {
1387
+ type: "button",
1388
+ "aria-label": "Strikethrough",
1389
+ className: `oue-toolbar-btn ${activeStates.isStrikethrough ? "oue-active" : ""}`,
1390
+ onClick: editorCtx.toggleStrikethrough,
1391
+ children: getIcon("strike", icons)
1392
+ },
1393
+ idx
1394
+ );
1395
+ case "link":
1396
+ return /* @__PURE__ */ jsx8(
1397
+ "button",
1398
+ {
1399
+ type: "button",
1400
+ "aria-label": "Link",
1401
+ className: `oue-toolbar-btn ${activeStates.isLink ? "oue-active" : ""}`,
1402
+ onClick: () => setIsLinkOpen(true),
1403
+ children: getIcon("link", icons)
1404
+ },
1405
+ idx
1406
+ );
1407
+ default:
1408
+ return null;
1409
+ }
1410
+ })
1411
+ }
1412
+ ),
1413
+ /* @__PURE__ */ jsx8(
1414
+ LinkModal,
1415
+ {
1416
+ isOpen: isLinkOpen,
1417
+ onClose: () => setIsLinkOpen(false),
1418
+ onSubmit: ({ url, target, title }) => editorCtx.insertLink(url, target, title)
1419
+ }
1420
+ )
1421
+ ] });
1422
+ }
1423
+
1424
+ // src/core/Extension.ts
1425
+ function createExtension(config) {
1426
+ const extension = {
1427
+ name: config.name,
1428
+ nodes: config.nodes || [],
1429
+ plugins: config.plugins || [],
1430
+ commands: config.commands || [],
1431
+ toolbarItems: config.toolbarItems || [],
1432
+ configure: (options) => {
1433
+ if (config.configure) {
1434
+ return config.configure(options);
1435
+ }
1436
+ return extension;
1437
+ }
1438
+ };
1439
+ return extension;
1440
+ }
1441
+
1442
+ // src/extensions/image/ImageExtension.ts
1443
+ var ImageExtension = createExtension({
1444
+ name: "image",
1445
+ nodes: [ImageNode],
1446
+ configure: (options) => {
1447
+ return {
1448
+ name: "image",
1449
+ nodes: [ImageNode],
1450
+ options
1451
+ };
1452
+ }
1453
+ });
1454
+
1455
+ // src/extensions/table/TableExtension.ts
1456
+ import { TableNode, TableRowNode, TableCellNode } from "@lexical/table";
1457
+ var TableExtension = createExtension({
1458
+ name: "table",
1459
+ nodes: [TableNode, TableRowNode, TableCellNode]
1460
+ });
1461
+
1462
+ // src/extensions/code/CodeExtension.ts
1463
+ import { CodeNode, CodeHighlightNode } from "@lexical/code";
1464
+ var CodeExtension = createExtension({
1465
+ name: "code",
1466
+ nodes: [CodeNode, CodeHighlightNode]
1467
+ });
1468
+
1469
+ // src/extensions/media/MediaExtension.ts
1470
+ var MediaExtension = createExtension({
1471
+ name: "media",
1472
+ configure: (options) => {
1473
+ return {
1474
+ name: "media",
1475
+ options: options || { youtube: true, allowedDomains: ["youtube.com", "youtu.be"] }
1476
+ };
1477
+ }
1478
+ });
1479
+
1480
+ // src/extensions/file/FileExtension.ts
1481
+ var FileExtension = createExtension({
1482
+ name: "file",
1483
+ configure: (options) => {
1484
+ return {
1485
+ name: "file",
1486
+ options
1487
+ };
1488
+ }
1489
+ });
1490
+
1491
+ // src/extensions/mention/MentionExtension.ts
1492
+ var MentionExtension = createExtension({
1493
+ name: "mention",
1494
+ configure: (options) => {
1495
+ return {
1496
+ name: "mention",
1497
+ options: options || { trigger: "@" }
1498
+ };
1499
+ }
1500
+ });
1501
+
1502
+ // src/extensions/emoji/EmojiExtension.ts
1503
+ var EmojiExtension = createExtension({
1504
+ name: "emoji"
1505
+ });
1506
+
1507
+ // src/extensions/slash-command/SlashCommandExtension.ts
1508
+ var SlashCommandExtension = createExtension({
1509
+ name: "slashCommand",
1510
+ configure: (options) => {
1511
+ return {
1512
+ name: "slashCommand",
1513
+ options
1514
+ };
1515
+ }
1516
+ });
1517
+
1518
+ // src/presets/index.ts
1519
+ var basicPreset = [];
1520
+ var fullPreset = [
1521
+ ImageExtension,
1522
+ TableExtension,
1523
+ CodeExtension,
1524
+ MediaExtension,
1525
+ FileExtension,
1526
+ MentionExtension,
1527
+ EmojiExtension,
1528
+ SlashCommandExtension
1529
+ ];
1530
+
1531
+ // src/core/Editor.tsx
1532
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1533
+ function EditorInstancePlugin({
1534
+ onMount
1535
+ }) {
1536
+ const [editor] = useLexicalComposerContext();
1537
+ useEffect3(() => {
1538
+ onMount(editor);
1539
+ }, [editor, onMount]);
1540
+ return null;
1541
+ }
1542
+ function EditorStateTracker({
1543
+ onStateChange
1544
+ }) {
1545
+ const [editor] = useLexicalComposerContext();
1546
+ useEffect3(() => {
1547
+ const unregisterUndo = editor.registerCommand(
1548
+ CAN_UNDO_COMMAND,
1549
+ (payload) => {
1550
+ onStateChange((prev) => ({ ...prev, canUndo: payload }));
1551
+ return false;
1552
+ },
1553
+ COMMAND_PRIORITY_CRITICAL
1554
+ );
1555
+ const unregisterRedo = editor.registerCommand(
1556
+ CAN_REDO_COMMAND,
1557
+ (payload) => {
1558
+ onStateChange((prev) => ({ ...prev, canRedo: payload }));
1559
+ return false;
1560
+ },
1561
+ COMMAND_PRIORITY_CRITICAL
1562
+ );
1563
+ const updateStates = () => {
1564
+ editor.getEditorState().read(() => {
1565
+ const selection = $getSelection();
1566
+ if (!$isRangeSelection(selection)) return;
1567
+ const anchorNode = selection.anchor.getNode();
1568
+ const element = anchorNode.getKey() === "root" ? anchorNode : anchorNode.getTopLevelElementOrThrow();
1569
+ const elementType = element.getType();
1570
+ const snapshot = {
1571
+ isBold: selection.hasFormat("bold"),
1572
+ isItalic: selection.hasFormat("italic"),
1573
+ isUnderline: selection.hasFormat("underline"),
1574
+ isStrikethrough: selection.hasFormat("strikethrough"),
1575
+ isCode: selection.hasFormat("code"),
1576
+ isSubscript: selection.hasFormat("subscript"),
1577
+ isSuperscript: selection.hasFormat("superscript"),
1578
+ isLink: anchorNode.getParent()?.getType() === "link",
1579
+ isH1: elementType === "heading" && element.getTag() === "h1",
1580
+ isH2: elementType === "heading" && element.getTag() === "h2",
1581
+ isH3: elementType === "heading" && element.getTag() === "h3",
1582
+ isH4: elementType === "heading" && element.getTag() === "h4",
1583
+ isH5: elementType === "heading" && element.getTag() === "h5",
1584
+ isH6: elementType === "heading" && element.getTag() === "h6",
1585
+ isParagraph: elementType === "paragraph",
1586
+ isBulletList: elementType === "list" && element.getListType() === "bullet",
1587
+ isOrderedList: elementType === "list" && element.getListType() === "number",
1588
+ isCheckList: elementType === "list" && element.getListType() === "check",
1589
+ isBlockquote: elementType === "quote",
1590
+ isCodeBlock: elementType === "code",
1591
+ isAlignLeft: false,
1592
+ isAlignCenter: false,
1593
+ isAlignRight: false,
1594
+ isAlignJustify: false
1595
+ };
1596
+ onStateChange((prev) => ({ ...prev, ...snapshot }));
1597
+ });
1598
+ };
1599
+ const unregisterUpdate = editor.registerUpdateListener(updateStates);
1600
+ return () => {
1601
+ unregisterUndo();
1602
+ unregisterRedo();
1603
+ unregisterUpdate();
1604
+ };
1605
+ }, [editor, onStateChange]);
1606
+ return null;
1607
+ }
1608
+ function InitialContentPlugin({ html }) {
1609
+ const [editor] = useLexicalComposerContext();
1610
+ const isInitialized = useRef2(false);
1611
+ useEffect3(() => {
1612
+ if (isInitialized.current || !html) return;
1613
+ isInitialized.current = true;
1614
+ setEditorHTML(editor, html);
1615
+ }, [editor, html]);
1616
+ return null;
1617
+ }
1618
+ var Editor = forwardRef((props, ref) => {
1619
+ const {
1620
+ id,
1621
+ className = "",
1622
+ style,
1623
+ "data-testid": testId,
1624
+ value,
1625
+ defaultValue,
1626
+ output = "html",
1627
+ placeholder = "Start writing...",
1628
+ readOnly = false,
1629
+ disabled = false,
1630
+ theme = "system",
1631
+ toolbar,
1632
+ bubbleToolbar,
1633
+ icons,
1634
+ extensions,
1635
+ preset,
1636
+ sourceMode: initialSourceMode = false,
1637
+ fullscreen: initialFullscreen = false,
1638
+ characterCount,
1639
+ wordCount,
1640
+ maxLength,
1641
+ html: htmlPolicy,
1642
+ autosave,
1643
+ onChange,
1644
+ onUpdate,
1645
+ onError,
1646
+ onDirtyChange,
1647
+ onCharacterCountChange
1648
+ } = props;
1649
+ const [editorInstance, setEditorInstance] = useState8(null);
1650
+ const [isFullscreen, setIsFullscreen] = useState8(initialFullscreen);
1651
+ const [isSourceMode, setIsSourceMode] = useState8(initialSourceMode);
1652
+ const [sourceHtml, setSourceHtml] = useState8("");
1653
+ const [isDirtyState, setIsDirtyState] = useState8(false);
1654
+ const [charCount, setCharCount] = useState8(0);
1655
+ const [wCount, setWCount] = useState8(0);
1656
+ const initialHtmlRef = useRef2(value !== void 0 ? value : defaultValue || "");
1657
+ const lastHtmlRef = useRef2(initialHtmlRef.current);
1658
+ const { mode: resolvedMode, styleObject: themeStyles } = useResolvedTheme(theme);
1659
+ const [activeStates, setActiveStates] = useState8({
1660
+ isBold: false,
1661
+ isItalic: false,
1662
+ isUnderline: false,
1663
+ isStrikethrough: false,
1664
+ isCode: false,
1665
+ isSubscript: false,
1666
+ isSuperscript: false,
1667
+ isLink: false,
1668
+ isH1: false,
1669
+ isH2: false,
1670
+ isH3: false,
1671
+ isH4: false,
1672
+ isH5: false,
1673
+ isH6: false,
1674
+ isParagraph: true,
1675
+ isBulletList: false,
1676
+ isOrderedList: false,
1677
+ isCheckList: false,
1678
+ isBlockquote: false,
1679
+ isCodeBlock: false,
1680
+ isAlignLeft: true,
1681
+ isAlignCenter: false,
1682
+ isAlignRight: false,
1683
+ isAlignJustify: false,
1684
+ canUndo: false,
1685
+ canRedo: false
1686
+ });
1687
+ const activeExtensions = extensions || (preset === "full" ? fullPreset : preset === "basic" ? basicPreset : []);
1688
+ const extensionNodes = activeExtensions.flatMap((ext) => ext.nodes || []);
1689
+ const initialConfig = {
1690
+ namespace: "OneUXIEditor",
1691
+ editable: !readOnly && !disabled,
1692
+ theme: {
1693
+ paragraph: "oue-paragraph",
1694
+ quote: "oue-quote",
1695
+ heading: {
1696
+ h1: "oue-h1",
1697
+ h2: "oue-h2",
1698
+ h3: "oue-h3",
1699
+ h4: "oue-h4",
1700
+ h5: "oue-h5",
1701
+ h6: "oue-h6"
1702
+ },
1703
+ list: {
1704
+ nested: {
1705
+ listitem: "oue-nested-listitem"
1706
+ },
1707
+ ol: "oue-ol",
1708
+ ul: "oue-ul",
1709
+ listitem: "oue-listitem",
1710
+ checklist: "oue-checklist",
1711
+ listitemChecked: "oue-checklist-item-checked",
1712
+ listitemUnchecked: "oue-checklist-item-unchecked"
1713
+ },
1714
+ image: "oue-image",
1715
+ link: "oue-link",
1716
+ text: {
1717
+ bold: "oue-bold",
1718
+ italic: "oue-italic",
1719
+ underline: "oue-underline",
1720
+ strikethrough: "oue-strike",
1721
+ code: "oue-code",
1722
+ subscript: "oue-subscript",
1723
+ superscript: "oue-superscript"
1724
+ },
1725
+ code: "oue-code-block"
1726
+ },
1727
+ onError: (error) => {
1728
+ if (onError) onError(error);
1729
+ },
1730
+ nodes: [
1731
+ HeadingNode,
1732
+ QuoteNode,
1733
+ ListNode,
1734
+ ListItemNode,
1735
+ LinkNode,
1736
+ AutoLinkNode,
1737
+ TableNode2,
1738
+ TableRowNode2,
1739
+ TableCellNode2,
1740
+ CodeNode2,
1741
+ CodeHighlightNode2,
1742
+ HorizontalRuleNode,
1743
+ ImageNode,
1744
+ ...extensionNodes
1745
+ ]
1746
+ };
1747
+ useEffect3(() => {
1748
+ if (!editorInstance || value === void 0) return;
1749
+ if (value !== lastHtmlRef.current) {
1750
+ setEditorHTML(editorInstance, value, htmlPolicy);
1751
+ lastHtmlRef.current = value;
1752
+ }
1753
+ }, [editorInstance, value, htmlPolicy]);
1754
+ const toggleBold = useCallback(() => {
1755
+ if (!editorInstance) return;
1756
+ editorInstance.focus();
1757
+ editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
1758
+ }, [editorInstance]);
1759
+ const toggleItalic = useCallback(() => {
1760
+ if (!editorInstance) return;
1761
+ editorInstance.focus();
1762
+ editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
1763
+ }, [editorInstance]);
1764
+ const toggleUnderline = useCallback(() => {
1765
+ if (!editorInstance) return;
1766
+ editorInstance.focus();
1767
+ editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, "underline");
1768
+ }, [editorInstance]);
1769
+ const toggleStrikethrough = useCallback(() => {
1770
+ if (!editorInstance) return;
1771
+ editorInstance.focus();
1772
+ editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough");
1773
+ }, [editorInstance]);
1774
+ const toggleCode = useCallback(() => {
1775
+ if (!editorInstance) return;
1776
+ editorInstance.focus();
1777
+ editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, "code");
1778
+ }, [editorInstance]);
1779
+ const toggleSubscript = useCallback(() => {
1780
+ if (!editorInstance) return;
1781
+ editorInstance.focus();
1782
+ editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, "subscript");
1783
+ }, [editorInstance]);
1784
+ const toggleSuperscript = useCallback(() => {
1785
+ if (!editorInstance) return;
1786
+ editorInstance.focus();
1787
+ editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, "superscript");
1788
+ }, [editorInstance]);
1789
+ const clearFormatting = useCallback(() => {
1790
+ if (!editorInstance) return;
1791
+ editorInstance.focus();
1792
+ editorInstance.update(() => {
1793
+ const selection = $getSelection();
1794
+ if ($isRangeSelection(selection)) {
1795
+ selection.getNodes().forEach((node) => {
1796
+ if ("setFormat" in node && typeof node.setFormat === "function") {
1797
+ node.setFormat(0);
1798
+ }
1799
+ });
1800
+ }
1801
+ });
1802
+ }, [editorInstance]);
1803
+ const setHeading = useCallback(
1804
+ (tag) => {
1805
+ if (!editorInstance) return;
1806
+ editorInstance.focus();
1807
+ editorInstance.update(() => {
1808
+ const selection = $getSelection();
1809
+ if ($isRangeSelection(selection)) {
1810
+ if (tag === "p") {
1811
+ $setBlocksType(selection, () => $createParagraphNode());
1812
+ } else {
1813
+ $setBlocksType(selection, () => $createHeadingNode(tag));
1814
+ }
1815
+ }
1816
+ });
1817
+ },
1818
+ [editorInstance]
1819
+ );
1820
+ const toggleBlockquote = useCallback(() => {
1821
+ if (!editorInstance) return;
1822
+ editorInstance.focus();
1823
+ editorInstance.update(() => {
1824
+ const selection = $getSelection();
1825
+ if ($isRangeSelection(selection)) {
1826
+ $setBlocksType(selection, () => $createQuoteNode());
1827
+ }
1828
+ });
1829
+ }, [editorInstance]);
1830
+ const insertHR = useCallback(() => {
1831
+ if (!editorInstance) return;
1832
+ editorInstance.focus();
1833
+ editorInstance.update(() => {
1834
+ const selection = $getSelection();
1835
+ if ($isRangeSelection(selection)) {
1836
+ const hr = $createHorizontalRuleNode();
1837
+ selection.insertNodes([hr]);
1838
+ }
1839
+ });
1840
+ }, [editorInstance]);
1841
+ const toggleBulletList = useCallback(() => {
1842
+ if (!editorInstance) return;
1843
+ editorInstance.focus();
1844
+ editorInstance.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, void 0);
1845
+ }, [editorInstance]);
1846
+ const toggleOrderedList = useCallback(() => {
1847
+ if (!editorInstance) return;
1848
+ editorInstance.focus();
1849
+ editorInstance.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, void 0);
1850
+ }, [editorInstance]);
1851
+ const toggleCheckList = useCallback(() => {
1852
+ if (!editorInstance) return;
1853
+ editorInstance.focus();
1854
+ editorInstance.dispatchCommand(INSERT_CHECK_LIST_COMMAND, void 0);
1855
+ }, [editorInstance]);
1856
+ const indent = useCallback(() => {
1857
+ if (!editorInstance) return;
1858
+ editorInstance.focus();
1859
+ editorInstance.dispatchCommand(INDENT_CONTENT_COMMAND, void 0);
1860
+ }, [editorInstance]);
1861
+ const outdent = useCallback(() => {
1862
+ if (!editorInstance) return;
1863
+ editorInstance.focus();
1864
+ editorInstance.dispatchCommand(OUTDENT_CONTENT_COMMAND, void 0);
1865
+ }, [editorInstance]);
1866
+ const setAlignment = useCallback(
1867
+ (align) => {
1868
+ if (!editorInstance) return;
1869
+ editorInstance.focus();
1870
+ editorInstance.dispatchCommand(FORMAT_ELEMENT_COMMAND, align);
1871
+ },
1872
+ [editorInstance]
1873
+ );
1874
+ const insertLink = useCallback(
1875
+ (url, target, title) => {
1876
+ if (!editorInstance) return;
1877
+ editorInstance.focus();
1878
+ editorInstance.dispatchCommand(TOGGLE_LINK_COMMAND, { url, target, title });
1879
+ },
1880
+ [editorInstance]
1881
+ );
1882
+ const removeLink = useCallback(() => {
1883
+ if (!editorInstance) return;
1884
+ editorInstance.focus();
1885
+ editorInstance.dispatchCommand(TOGGLE_LINK_COMMAND, null);
1886
+ }, [editorInstance]);
1887
+ const insertTable = useCallback(
1888
+ (rows = 3, cols = 3) => {
1889
+ if (!editorInstance) return;
1890
+ editorInstance.update(() => {
1891
+ const selection = $getSelection();
1892
+ if ($isRangeSelection(selection)) {
1893
+ const tableNode = $createTableNodeWithDimensions(rows, cols, false);
1894
+ selection.insertNodes([tableNode]);
1895
+ }
1896
+ });
1897
+ },
1898
+ [editorInstance]
1899
+ );
1900
+ const insertImage = useCallback(
1901
+ (src, alt, width, height) => {
1902
+ if (!editorInstance) return;
1903
+ editorInstance.update(() => {
1904
+ const selection = $getSelection();
1905
+ if ($isRangeSelection(selection)) {
1906
+ const imageNode = $createImageNode({ src, alt, width, height });
1907
+ selection.insertNodes([imageNode]);
1908
+ }
1909
+ });
1910
+ },
1911
+ [editorInstance]
1912
+ );
1913
+ const insertCodeBlock = useCallback(
1914
+ (language = "javascript") => {
1915
+ if (!editorInstance) return;
1916
+ editorInstance.update(() => {
1917
+ const selection = $getSelection();
1918
+ if ($isRangeSelection(selection)) {
1919
+ const codeNode = $createCodeNode(language);
1920
+ selection.insertNodes([codeNode]);
1921
+ }
1922
+ });
1923
+ },
1924
+ [editorInstance]
1925
+ );
1926
+ const undo = useCallback(() => {
1927
+ if (!editorInstance) return;
1928
+ editorInstance.focus();
1929
+ editorInstance.dispatchCommand(UNDO_COMMAND, void 0);
1930
+ }, [editorInstance]);
1931
+ const redo = useCallback(() => {
1932
+ if (!editorInstance) return;
1933
+ editorInstance.focus();
1934
+ editorInstance.dispatchCommand(REDO_COMMAND, void 0);
1935
+ }, [editorInstance]);
1936
+ const focus = useCallback(() => editorInstance?.focus(), [editorInstance]);
1937
+ const blur = useCallback(() => editorInstance?.blur(), [editorInstance]);
1938
+ const clear = useCallback(() => {
1939
+ if (!editorInstance) return;
1940
+ editorInstance.update(() => {
1941
+ const root = $getRoot2();
1942
+ root.clear();
1943
+ const p = $createParagraphNode();
1944
+ root.append(p);
1945
+ p.select();
1946
+ });
1947
+ }, [editorInstance]);
1948
+ const getHTML = useCallback(() => {
1949
+ if (!editorInstance) return "";
1950
+ return getEditorHTML(editorInstance, htmlPolicy);
1951
+ }, [editorInstance, htmlPolicy]);
1952
+ const setHTML = useCallback(
1953
+ (html) => {
1954
+ if (!editorInstance) return;
1955
+ setEditorHTML(editorInstance, html, htmlPolicy);
1956
+ lastHtmlRef.current = html;
1957
+ },
1958
+ [editorInstance, htmlPolicy]
1959
+ );
1960
+ const getText = useCallback(() => {
1961
+ if (!editorInstance) return "";
1962
+ let text = "";
1963
+ editorInstance.getEditorState().read(() => {
1964
+ text = $getRoot2().getTextContent();
1965
+ });
1966
+ return text;
1967
+ }, [editorInstance]);
1968
+ const getJSON = useCallback(() => {
1969
+ if (!editorInstance) return { root: {} };
1970
+ return editorInstance.getEditorState().toJSON();
1971
+ }, [editorInstance]);
1972
+ const setJSON = useCallback(
1973
+ (json) => {
1974
+ if (!editorInstance) return;
1975
+ const state = editorInstance.parseEditorState(JSON.stringify(json));
1976
+ editorInstance.setEditorState(state);
1977
+ },
1978
+ [editorInstance]
1979
+ );
1980
+ const isEmpty = useCallback(() => {
1981
+ if (!editorInstance) return true;
1982
+ let empty = true;
1983
+ editorInstance.getEditorState().read(() => {
1984
+ const root = $getRoot2();
1985
+ const children = root.getChildren();
1986
+ if (children.length === 0) {
1987
+ empty = true;
1988
+ return;
1989
+ }
1990
+ const text = root.getTextContent().trim();
1991
+ empty = text.length === 0;
1992
+ });
1993
+ return empty;
1994
+ }, [editorInstance]);
1995
+ const isDirty = useCallback(() => isDirtyState, [isDirtyState]);
1996
+ const markClean = useCallback(() => {
1997
+ setIsDirtyState(false);
1998
+ if (onDirtyChange) onDirtyChange(false);
1999
+ }, [onDirtyChange]);
2000
+ const toggleFullscreen = useCallback(() => setIsFullscreen((prev) => !prev), []);
2001
+ const toggleSourceMode = useCallback(() => {
2002
+ setIsSourceMode((prev) => {
2003
+ const next = !prev;
2004
+ if (next && editorInstance) {
2005
+ setSourceHtml(getEditorHTML(editorInstance, htmlPolicy));
2006
+ } else if (!next && editorInstance) {
2007
+ setEditorHTML(editorInstance, sourceHtml, htmlPolicy);
2008
+ }
2009
+ return next;
2010
+ });
2011
+ }, [editorInstance, htmlPolicy, sourceHtml]);
2012
+ useImperativeHandle(
2013
+ ref,
2014
+ () => ({
2015
+ focus,
2016
+ blur,
2017
+ clear,
2018
+ getHTML,
2019
+ setHTML,
2020
+ getText,
2021
+ getJSON,
2022
+ setJSON,
2023
+ isEmpty,
2024
+ isDirty,
2025
+ markClean,
2026
+ undo,
2027
+ redo
2028
+ }),
2029
+ [focus, blur, clear, getHTML, setHTML, getText, getJSON, setJSON, isEmpty, isDirty, markClean, undo, redo]
2030
+ );
2031
+ const handleEditorChange = useCallback(() => {
2032
+ if (!editorInstance) return;
2033
+ const currentText = editorInstance.getEditorState().read(() => $getRoot2().getTextContent());
2034
+ const currentLength = currentText.length;
2035
+ const currentWords = currentText.trim() ? currentText.trim().split(/\s+/).length : 0;
2036
+ setCharCount(currentLength);
2037
+ setWCount(currentWords);
2038
+ if (onCharacterCountChange) onCharacterCountChange(currentLength);
2039
+ if (maxLength && currentLength > maxLength) {
2040
+ editorInstance.update(() => {
2041
+ const root = $getRoot2();
2042
+ const text = root.getTextContent().slice(0, maxLength);
2043
+ root.clear();
2044
+ const p = $createParagraphNode();
2045
+ p.append(document.createTextNode(text));
2046
+ root.append(p);
2047
+ });
2048
+ return;
2049
+ }
2050
+ const htmlOutput = getEditorHTML(editorInstance, htmlPolicy);
2051
+ const jsonOutput = editorInstance.getEditorState().toJSON();
2052
+ const isCurrentlyEmpty = isEmpty();
2053
+ if (htmlOutput !== lastHtmlRef.current) {
2054
+ lastHtmlRef.current = htmlOutput;
2055
+ if (!isDirtyState) {
2056
+ setIsDirtyState(true);
2057
+ if (onDirtyChange) onDirtyChange(true);
2058
+ }
2059
+ if (onChange) {
2060
+ const result = output === "json" ? JSON.stringify(jsonOutput) : output === "text" ? currentText : htmlOutput;
2061
+ onChange(result);
2062
+ }
2063
+ if (onUpdate) {
2064
+ onUpdate({
2065
+ html: htmlOutput,
2066
+ text: currentText,
2067
+ json: jsonOutput,
2068
+ isEmpty: isCurrentlyEmpty
2069
+ });
2070
+ }
2071
+ if (autosave?.onSave) {
2072
+ autosave.onSave({ html: htmlOutput, json: jsonOutput, text: currentText });
2073
+ }
2074
+ }
2075
+ }, [
2076
+ editorInstance,
2077
+ onCharacterCountChange,
2078
+ maxLength,
2079
+ htmlPolicy,
2080
+ isEmpty,
2081
+ isDirtyState,
2082
+ onDirtyChange,
2083
+ onChange,
2084
+ output,
2085
+ onUpdate,
2086
+ autosave
2087
+ ]);
2088
+ const contextValue = {
2089
+ editor: editorInstance,
2090
+ activeStates,
2091
+ icons,
2092
+ toggleBold,
2093
+ toggleItalic,
2094
+ toggleUnderline,
2095
+ toggleStrikethrough,
2096
+ toggleCode,
2097
+ toggleSubscript,
2098
+ toggleSuperscript,
2099
+ clearFormatting,
2100
+ setHeading,
2101
+ toggleBlockquote,
2102
+ insertHR,
2103
+ toggleBulletList,
2104
+ toggleOrderedList,
2105
+ toggleCheckList,
2106
+ indent,
2107
+ outdent,
2108
+ setAlignment,
2109
+ insertLink,
2110
+ removeLink,
2111
+ insertTable,
2112
+ insertImage,
2113
+ insertCodeBlock,
2114
+ undo,
2115
+ redo,
2116
+ focus,
2117
+ blur,
2118
+ clear,
2119
+ getHTML,
2120
+ setHTML,
2121
+ getText,
2122
+ getJSON,
2123
+ setJSON,
2124
+ isEmpty,
2125
+ isDirty,
2126
+ markClean,
2127
+ isFullscreen,
2128
+ toggleFullscreen,
2129
+ isSourceMode,
2130
+ toggleSourceMode
2131
+ };
2132
+ const imageExt = activeExtensions.find((e) => e.name === "image");
2133
+ const imageUploader = imageExt?.options?.upload;
2134
+ return /* @__PURE__ */ jsx9(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx9(EditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs8(
2135
+ "div",
2136
+ {
2137
+ id,
2138
+ "data-testid": testId,
2139
+ "data-theme": resolvedMode,
2140
+ className: `oue-editor-container oue-editor-wrapper ${isFullscreen ? "oue-fullscreen" : ""} ${disabled ? "oue-disabled" : ""} ${className}`,
2141
+ style: { ...themeStyles, ...style },
2142
+ children: [
2143
+ /* @__PURE__ */ jsx9(EditorInstancePlugin, { onMount: setEditorInstance }),
2144
+ /* @__PURE__ */ jsx9(EditorStateTracker, { onStateChange: setActiveStates }),
2145
+ /* @__PURE__ */ jsx9(InitialContentPlugin, { html: initialHtmlRef.current }),
2146
+ /* @__PURE__ */ jsx9(Toolbar, { config: toolbar, imageUploader }),
2147
+ /* @__PURE__ */ jsx9(BubbleToolbar, { config: bubbleToolbar }),
2148
+ isSourceMode ? /* @__PURE__ */ jsx9(
2149
+ "textarea",
2150
+ {
2151
+ className: "oue-source-textarea",
2152
+ value: sourceHtml,
2153
+ onChange: (e) => {
2154
+ setSourceHtml(e.target.value);
2155
+ if (editorInstance) {
2156
+ setEditorHTML(editorInstance, e.target.value, htmlPolicy);
2157
+ }
2158
+ }
2159
+ }
2160
+ ) : /* @__PURE__ */ jsx9("div", { style: { position: "relative" }, children: /* @__PURE__ */ jsx9(
2161
+ RichTextPlugin,
2162
+ {
2163
+ contentEditable: /* @__PURE__ */ jsx9(
2164
+ ContentEditable,
2165
+ {
2166
+ className: "oue-content-editable",
2167
+ "aria-placeholder": placeholder,
2168
+ placeholder: /* @__PURE__ */ jsx9("div", { className: "oue-placeholder", children: placeholder })
2169
+ }
2170
+ ),
2171
+ ErrorBoundary: LexicalErrorBoundary
2172
+ }
2173
+ ) }),
2174
+ /* @__PURE__ */ jsx9(HistoryPlugin, {}),
2175
+ /* @__PURE__ */ jsx9(ListPlugin, {}),
2176
+ /* @__PURE__ */ jsx9(CheckListPlugin, {}),
2177
+ /* @__PURE__ */ jsx9(LinkPlugin, {}),
2178
+ /* @__PURE__ */ jsx9(HorizontalRulePlugin, {}),
2179
+ /* @__PURE__ */ jsx9(OnChangePlugin, { onChange: handleEditorChange }),
2180
+ (characterCount || wordCount) && /* @__PURE__ */ jsxs8("div", { className: "oue-footer", children: [
2181
+ /* @__PURE__ */ jsxs8("div", { children: [
2182
+ wordCount && /* @__PURE__ */ jsxs8("span", { children: [
2183
+ wCount,
2184
+ " words "
2185
+ ] }),
2186
+ characterCount && /* @__PURE__ */ jsxs8("span", { children: [
2187
+ "(",
2188
+ charCount,
2189
+ " ",
2190
+ maxLength ? `/ ${maxLength}` : "",
2191
+ " chars)"
2192
+ ] })
2193
+ ] }),
2194
+ isDirtyState && /* @__PURE__ */ jsx9("span", { children: "Unsaved changes" })
2195
+ ] })
2196
+ ]
2197
+ }
2198
+ ) }) });
2199
+ });
2200
+ Editor.displayName = "Editor";
2201
+
2202
+ // src/paste/cleaner.ts
2203
+ function cleanPastedHTML(rawHtml) {
2204
+ if (!rawHtml) return "";
2205
+ let cleaned = rawHtml;
2206
+ cleaned = cleaned.replace(/<!--[\s\S]*?-->/g, "");
2207
+ cleaned = cleaned.replace(/<\/?(?:xml|meta|link|style)\b[^>]*>/gi, "");
2208
+ cleaned = cleaned.replace(/class=["']?\bMso[a-zA-Z0-9_-]+\b["']?/gi, "");
2209
+ cleaned = cleaned.replace(/style=["']([^"']*)["']/gi, (match, styleContent) => {
2210
+ const cleanStyles = styleContent.split(";").filter((style) => {
2211
+ const trimmed = style.trim().toLowerCase();
2212
+ return trimmed && !trimmed.startsWith("mso-") && !trimmed.startsWith("font-family") && !trimmed.startsWith("line-height") && !trimmed.startsWith("tab-stops");
2213
+ }).join(";");
2214
+ return cleanStyles ? `style="${cleanStyles}"` : "";
2215
+ });
2216
+ cleaned = cleaned.replace(/<span\s*>(.*?)<\/span>/gi, "$1");
2217
+ cleaned = cleaned.replace(/<span\s+style=["']\s*["']>(.*?)<\/span>/gi, "$1");
2218
+ cleaned = cleaned.replace(/<b\s+style=["']font-weight:\s*normal;?["']>(.*?)<\/b>/gi, "$1");
2219
+ cleaned = cleaned.replace(/<span\s+style=["'][^"']*font-weight:\s*700;?[^"']*["']>(.*?)<\/span>/gi, "<strong>$1</strong>");
2220
+ cleaned = cleaned.replace(/<span\s+style=["'][^"']*font-style:\s*italic;?[^"']*["']>(.*?)<\/span>/gi, "<em>$1</em>");
2221
+ return sanitizeHTML(cleaned);
2222
+ }
2223
+ export {
2224
+ BubbleToolbar,
2225
+ CodeExtension,
2226
+ DefaultIcons,
2227
+ Editor,
2228
+ EmojiExtension,
2229
+ FileExtension,
2230
+ ImageExtension,
2231
+ MediaExtension,
2232
+ MentionExtension,
2233
+ SlashCommandExtension,
2234
+ TableExtension,
2235
+ Toolbar,
2236
+ basicPreset,
2237
+ cleanPastedHTML,
2238
+ createExtension,
2239
+ fullPreset,
2240
+ sanitizeHTML,
2241
+ useEditor
2242
+ };
2243
+ //# sourceMappingURL=index.mjs.map