trance-richtext-editor 0.2.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.js ADDED
@@ -0,0 +1,2199 @@
1
+ import { createContext, forwardRef, useRef, useImperativeHandle, useState, useCallback, useEffect, useLayoutEffect, useContext } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { LexicalComposer } from '@lexical/react/LexicalComposer';
4
+ import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
5
+ import { ContentEditable } from '@lexical/react/LexicalContentEditable';
6
+ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
7
+ import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';
8
+ import { ListPlugin } from '@lexical/react/LexicalListPlugin';
9
+ import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
10
+ import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
11
+ import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin';
12
+ import { TablePlugin } from '@lexical/react/LexicalTablePlugin';
13
+ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
14
+ import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';
15
+ import { TRANSFORMERS } from '@lexical/markdown';
16
+ import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
17
+ import { createCommand, $getRoot, $insertNodes, DecoratorNode, $getSelection, $isRangeSelection, $isRootOrShadowRoot, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_CRITICAL, CAN_UNDO_COMMAND, CAN_REDO_COMMAND, $createParagraphNode, UNDO_COMMAND, REDO_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, COMMAND_PRIORITY_EDITOR, RootNode, TextNode, CLICK_COMMAND, COMMAND_PRIORITY_LOW, $getNodeByKey, createEditor } from 'lexical';
18
+ import { HeadingNode, QuoteNode, $isHeadingNode, $isQuoteNode, $createHeadingNode, $createQuoteNode } from '@lexical/rich-text';
19
+ import { ListNode, ListItemNode, $isListNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_CHECK_LIST_COMMAND } from '@lexical/list';
20
+ import { LinkNode, AutoLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
21
+ import { CodeNode, CodeHighlightNode, $isCodeNode, $createCodeNode } from '@lexical/code';
22
+ import { TableNode, TableCellNode, TableRowNode, INSERT_TABLE_COMMAND } from '@lexical/table';
23
+ import { createPortal } from 'react-dom';
24
+ import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
25
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
26
+ import { $findMatchingParent, $getNearestNodeOfType } from '@lexical/utils';
27
+ import { $setBlocksType } from '@lexical/selection';
28
+
29
+ // src/editor/TranceEditor.tsx
30
+ var ImageBackgroundContext = createContext(
31
+ null
32
+ );
33
+ function useImageBackgroundLayer() {
34
+ return useContext(ImageBackgroundContext);
35
+ }
36
+ function $convertImageElement(domNode) {
37
+ const element = domNode;
38
+ if (element.tagName === "IMG") {
39
+ const img = element;
40
+ const node = $createImageNode({
41
+ src: img.src,
42
+ altText: img.alt || "",
43
+ width: img.width || void 0,
44
+ height: img.height || void 0
45
+ });
46
+ return { node };
47
+ }
48
+ if (element.tagName === "FIGURE") {
49
+ const img = element.querySelector("img");
50
+ const figcaption = element.querySelector("figcaption");
51
+ if (img) {
52
+ const node = $createImageNode({
53
+ src: img.src,
54
+ altText: img.alt || "",
55
+ width: img.width || void 0,
56
+ height: img.height || void 0,
57
+ caption: figcaption?.textContent || void 0,
58
+ alignment: element.dataset.align || void 0,
59
+ mode: element.dataset.mode || (element.classList.contains("trance-image-background") ? "background" : void 0)
60
+ });
61
+ return { node };
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+ var ImageNode = class _ImageNode extends DecoratorNode {
67
+ static getType() {
68
+ return "image";
69
+ }
70
+ static clone(node) {
71
+ return new _ImageNode(
72
+ node.__src,
73
+ node.__altText,
74
+ node.__width,
75
+ node.__height,
76
+ node.__caption,
77
+ node.__alignment,
78
+ node.__mode,
79
+ node.__key
80
+ );
81
+ }
82
+ static importJSON(serializedNode) {
83
+ return $createImageNode({
84
+ src: serializedNode.src,
85
+ altText: serializedNode.altText,
86
+ width: serializedNode.width,
87
+ height: serializedNode.height,
88
+ caption: serializedNode.caption,
89
+ alignment: serializedNode.alignment,
90
+ mode: serializedNode.mode
91
+ });
92
+ }
93
+ static importDOM() {
94
+ return {
95
+ img: () => ({
96
+ conversion: $convertImageElement,
97
+ priority: 0
98
+ }),
99
+ figure: () => ({
100
+ conversion: $convertImageElement,
101
+ priority: 0
102
+ })
103
+ };
104
+ }
105
+ constructor(src, altText, width, height, caption, alignment = "center", mode = "inline", key) {
106
+ super(key);
107
+ this.__src = src;
108
+ this.__altText = altText;
109
+ this.__width = width;
110
+ this.__height = height;
111
+ this.__caption = caption;
112
+ this.__alignment = alignment;
113
+ this.__mode = mode;
114
+ }
115
+ exportJSON() {
116
+ return {
117
+ type: "image",
118
+ version: 1,
119
+ src: this.__src,
120
+ altText: this.__altText,
121
+ width: this.__width,
122
+ height: this.__height,
123
+ caption: this.__caption,
124
+ alignment: this.__alignment,
125
+ mode: this.__mode
126
+ };
127
+ }
128
+ exportDOM() {
129
+ const figure = document.createElement("figure");
130
+ figure.style.margin = "0";
131
+ figure.dataset.align = this.__alignment;
132
+ figure.dataset.mode = this.__mode;
133
+ this.applyAlignmentStyles(figure);
134
+ if (this.__mode === "background") {
135
+ figure.classList.add("trance-image-background");
136
+ }
137
+ const img = document.createElement("img");
138
+ img.setAttribute("src", this.__src);
139
+ img.setAttribute("alt", this.__altText);
140
+ if (this.__width) {
141
+ img.setAttribute("width", String(this.__width));
142
+ }
143
+ if (this.__height) {
144
+ img.setAttribute("height", String(this.__height));
145
+ }
146
+ img.style.maxWidth = "100%";
147
+ img.style.height = "auto";
148
+ img.style.borderRadius = "8px";
149
+ img.style.display = "block";
150
+ figure.appendChild(img);
151
+ if (this.__caption && this.__mode !== "background") {
152
+ const figcaption = document.createElement("figcaption");
153
+ figcaption.textContent = this.__caption;
154
+ figcaption.style.textAlign = "center";
155
+ figcaption.style.fontSize = "0.875rem";
156
+ figcaption.style.color = "#6b7280";
157
+ figcaption.style.marginTop = "4px";
158
+ figcaption.style.fontStyle = "italic";
159
+ figure.appendChild(figcaption);
160
+ }
161
+ if (this.__mode === "background") {
162
+ figure.classList.add("trance-image-background");
163
+ return { element: figure };
164
+ }
165
+ return { element: figure };
166
+ }
167
+ applyAlignmentStyles(element) {
168
+ element.style.display = "block";
169
+ element.style.width = "fit-content";
170
+ element.style.maxWidth = "100%";
171
+ switch (this.__alignment) {
172
+ case "left":
173
+ element.style.margin = "0 auto 0 0";
174
+ break;
175
+ case "right":
176
+ element.style.margin = "0 0 0 auto";
177
+ break;
178
+ case "center":
179
+ default:
180
+ element.style.margin = "0 auto";
181
+ break;
182
+ }
183
+ }
184
+ createDOM() {
185
+ const div = document.createElement("div");
186
+ div.className = `trance-image-wrapper align-${this.__alignment} mode-${this.__mode}`;
187
+ return div;
188
+ }
189
+ updateDOM(prevNode, dom) {
190
+ if (prevNode.__alignment !== this.__alignment || prevNode.__mode !== this.__mode) {
191
+ dom.className = `trance-image-wrapper align-${this.__alignment} mode-${this.__mode}`;
192
+ }
193
+ return false;
194
+ }
195
+ getSrc() {
196
+ return this.__src;
197
+ }
198
+ getAltText() {
199
+ return this.__altText;
200
+ }
201
+ setWidthAndHeight(width, height) {
202
+ const writable = this.getWritable();
203
+ writable.__width = width;
204
+ writable.__height = height;
205
+ }
206
+ getAlignment() {
207
+ return this.__alignment;
208
+ }
209
+ setAlignment(alignment) {
210
+ const writable = this.getWritable();
211
+ writable.__alignment = alignment;
212
+ }
213
+ getMode() {
214
+ return this.__mode;
215
+ }
216
+ setMode(mode) {
217
+ const writable = this.getWritable();
218
+ writable.__mode = mode;
219
+ }
220
+ decorate() {
221
+ return /* @__PURE__ */ jsx(
222
+ ImageComponent,
223
+ {
224
+ nodeKey: this.__key,
225
+ src: this.__src,
226
+ altText: this.__altText,
227
+ width: this.__width,
228
+ height: this.__height,
229
+ caption: this.__caption,
230
+ alignment: this.__alignment,
231
+ mode: this.__mode
232
+ }
233
+ );
234
+ }
235
+ isInline() {
236
+ return false;
237
+ }
238
+ };
239
+ function ImageComponent({
240
+ nodeKey,
241
+ src,
242
+ altText,
243
+ width,
244
+ height,
245
+ caption,
246
+ alignment,
247
+ mode
248
+ }) {
249
+ return /* @__PURE__ */ jsx(
250
+ ImageComponentInner,
251
+ {
252
+ nodeKey,
253
+ src,
254
+ altText,
255
+ width,
256
+ height,
257
+ caption,
258
+ alignment,
259
+ mode
260
+ }
261
+ );
262
+ }
263
+ function ImageComponentInner({
264
+ nodeKey,
265
+ src,
266
+ altText,
267
+ width,
268
+ height,
269
+ caption,
270
+ alignment,
271
+ mode
272
+ }) {
273
+ const [editor] = useLexicalComposerContext();
274
+ const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
275
+ const imageRef = useRef(null);
276
+ const wrapperRef = useRef(null);
277
+ const [isResizing, setIsResizing] = useState(false);
278
+ const [currentWidth, setCurrentWidth] = useState(width);
279
+ const backgroundLayer = useImageBackgroundLayer();
280
+ const isBackground = mode === "background";
281
+ useEffect(() => {
282
+ setCurrentWidth(width);
283
+ }, [width]);
284
+ const handleClick = useCallback(
285
+ (event) => {
286
+ event.preventDefault();
287
+ event.stopPropagation();
288
+ setSelected(true);
289
+ },
290
+ [setSelected]
291
+ );
292
+ useEffect(() => {
293
+ return editor.registerCommand(
294
+ CLICK_COMMAND,
295
+ (payload) => {
296
+ const clickedElement = payload.target;
297
+ if (wrapperRef.current && !wrapperRef.current.contains(clickedElement)) {
298
+ clearSelection();
299
+ return false;
300
+ }
301
+ return false;
302
+ },
303
+ COMMAND_PRIORITY_LOW
304
+ );
305
+ }, [editor, clearSelection]);
306
+ const startResize = useCallback(
307
+ (event) => {
308
+ event.preventDefault();
309
+ event.stopPropagation();
310
+ setIsResizing(true);
311
+ setSelected(true);
312
+ const image = imageRef.current;
313
+ if (!image) return;
314
+ const startX = event.clientX;
315
+ const startWidth = image.offsetWidth;
316
+ const aspectRatio = image.naturalWidth / image.naturalHeight;
317
+ const handleMouseMove = (moveEvent) => {
318
+ const deltaX = moveEvent.clientX - startX;
319
+ const newWidth = Math.max(80, startWidth + deltaX);
320
+ const newHeight = aspectRatio > 0 ? newWidth / aspectRatio : void 0;
321
+ setCurrentWidth(newWidth);
322
+ editor.update(() => {
323
+ const node = $getNodeByKey(nodeKey);
324
+ if ($isImageNode(node)) {
325
+ node.setWidthAndHeight(newWidth, newHeight);
326
+ }
327
+ });
328
+ };
329
+ const handleMouseUp = () => {
330
+ setIsResizing(false);
331
+ document.removeEventListener("mousemove", handleMouseMove);
332
+ document.removeEventListener("mouseup", handleMouseUp);
333
+ };
334
+ document.addEventListener("mousemove", handleMouseMove);
335
+ document.addEventListener("mouseup", handleMouseUp);
336
+ },
337
+ [editor, nodeKey, setSelected]
338
+ );
339
+ const setAlignment = useCallback(
340
+ (newAlignment) => {
341
+ editor.update(() => {
342
+ const node = $getNodeByKey(nodeKey);
343
+ if ($isImageNode(node)) {
344
+ node.setAlignment(newAlignment);
345
+ }
346
+ });
347
+ },
348
+ [editor, nodeKey]
349
+ );
350
+ const setMode = useCallback(
351
+ (newMode) => {
352
+ editor.update(() => {
353
+ const node = $getNodeByKey(nodeKey);
354
+ if ($isImageNode(node)) {
355
+ node.setMode(newMode);
356
+ }
357
+ });
358
+ },
359
+ [editor, nodeKey]
360
+ );
361
+ const innerClassName = ["trance-image-inner", isSelected ? "selected" : ""].filter(Boolean).join(" ");
362
+ return /* @__PURE__ */ jsxs(
363
+ "div",
364
+ {
365
+ ref: wrapperRef,
366
+ className: innerClassName,
367
+ onClick: handleClick,
368
+ "data-alignment": alignment,
369
+ children: [
370
+ (isSelected || isResizing) && /* @__PURE__ */ jsxs(
371
+ "div",
372
+ {
373
+ className: "trance-image-toolbar",
374
+ onMouseDown: (e) => e.preventDefault(),
375
+ children: [
376
+ /* @__PURE__ */ jsx(
377
+ "button",
378
+ {
379
+ type: "button",
380
+ className: alignment === "left" ? "active" : "",
381
+ onClick: () => setAlignment("left"),
382
+ "aria-label": "Align left",
383
+ title: "Align left",
384
+ children: /* @__PURE__ */ jsx(AlignLeftIcon, {})
385
+ }
386
+ ),
387
+ /* @__PURE__ */ jsx(
388
+ "button",
389
+ {
390
+ type: "button",
391
+ className: alignment === "center" ? "active" : "",
392
+ onClick: () => setAlignment("center"),
393
+ "aria-label": "Align center",
394
+ title: "Align center",
395
+ children: /* @__PURE__ */ jsx(AlignCenterIcon, {})
396
+ }
397
+ ),
398
+ /* @__PURE__ */ jsx(
399
+ "button",
400
+ {
401
+ type: "button",
402
+ className: alignment === "right" ? "active" : "",
403
+ onClick: () => setAlignment("right"),
404
+ "aria-label": "Align right",
405
+ title: "Align right",
406
+ children: /* @__PURE__ */ jsx(AlignRightIcon, {})
407
+ }
408
+ ),
409
+ /* @__PURE__ */ jsx("div", { className: "trance-image-toolbar-divider" }),
410
+ /* @__PURE__ */ jsx(
411
+ "button",
412
+ {
413
+ type: "button",
414
+ className: isBackground ? "active" : "",
415
+ onClick: () => setMode(isBackground ? "inline" : "background"),
416
+ "aria-label": "Toggle background image",
417
+ title: "Toggle background image",
418
+ children: "BG"
419
+ }
420
+ )
421
+ ]
422
+ }
423
+ ),
424
+ isBackground ? /* @__PURE__ */ jsx("div", { className: "trance-image-background-placeholder", children: /* @__PURE__ */ jsx("span", { children: "Background Image" }) }) : /* @__PURE__ */ jsxs("figure", { style: { margin: 0 }, children: [
425
+ /* @__PURE__ */ jsx(
426
+ "img",
427
+ {
428
+ ref: imageRef,
429
+ src,
430
+ alt: altText,
431
+ width: currentWidth,
432
+ height,
433
+ draggable: false,
434
+ style: {
435
+ maxWidth: "100%",
436
+ height: "auto",
437
+ borderRadius: "8px",
438
+ display: "block",
439
+ cursor: isSelected ? "default" : "pointer",
440
+ userSelect: "none"
441
+ }
442
+ }
443
+ ),
444
+ caption && /* @__PURE__ */ jsx("figcaption", { className: "trance-image-caption", children: caption })
445
+ ] }),
446
+ isBackground && backgroundLayer?.current && createPortal(
447
+ /* @__PURE__ */ jsx(
448
+ "img",
449
+ {
450
+ src,
451
+ alt: altText,
452
+ className: "trance-image-background-img",
453
+ draggable: false
454
+ }
455
+ ),
456
+ backgroundLayer.current
457
+ ),
458
+ (isSelected || isResizing) && !isBackground && /* @__PURE__ */ jsx(
459
+ "div",
460
+ {
461
+ className: "trance-image-resize-handle",
462
+ onMouseDown: startResize,
463
+ "aria-label": "Resize image",
464
+ title: "Resize image"
465
+ }
466
+ )
467
+ ]
468
+ }
469
+ );
470
+ }
471
+ function AlignLeftIcon() {
472
+ return /* @__PURE__ */ jsxs(
473
+ "svg",
474
+ {
475
+ viewBox: "0 0 24 24",
476
+ fill: "none",
477
+ stroke: "currentColor",
478
+ strokeWidth: "2",
479
+ strokeLinecap: "round",
480
+ strokeLinejoin: "round",
481
+ children: [
482
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
483
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "13", y2: "12" }),
484
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "18", y2: "18" })
485
+ ]
486
+ }
487
+ );
488
+ }
489
+ function AlignCenterIcon() {
490
+ return /* @__PURE__ */ jsxs(
491
+ "svg",
492
+ {
493
+ viewBox: "0 0 24 24",
494
+ fill: "none",
495
+ stroke: "currentColor",
496
+ strokeWidth: "2",
497
+ strokeLinecap: "round",
498
+ strokeLinejoin: "round",
499
+ children: [
500
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
501
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "12", x2: "18", y2: "12" }),
502
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18" })
503
+ ]
504
+ }
505
+ );
506
+ }
507
+ function AlignRightIcon() {
508
+ return /* @__PURE__ */ jsxs(
509
+ "svg",
510
+ {
511
+ viewBox: "0 0 24 24",
512
+ fill: "none",
513
+ stroke: "currentColor",
514
+ strokeWidth: "2",
515
+ strokeLinecap: "round",
516
+ strokeLinejoin: "round",
517
+ children: [
518
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
519
+ /* @__PURE__ */ jsx("line", { x1: "11", y1: "12", x2: "21", y2: "12" }),
520
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "18", x2: "21", y2: "18" })
521
+ ]
522
+ }
523
+ );
524
+ }
525
+ function $createImageNode(payload) {
526
+ return new ImageNode(
527
+ payload.src,
528
+ payload.altText,
529
+ payload.width,
530
+ payload.height,
531
+ payload.caption,
532
+ payload.alignment || "center",
533
+ payload.mode || "inline",
534
+ payload.key
535
+ );
536
+ }
537
+ function $isImageNode(node) {
538
+ return node instanceof ImageNode;
539
+ }
540
+ function $convertHorizontalRuleElement() {
541
+ return { node: $createHorizontalRuleNode() };
542
+ }
543
+ var HorizontalRuleNode = class _HorizontalRuleNode extends DecoratorNode {
544
+ static getType() {
545
+ return "horizontalrule";
546
+ }
547
+ static clone(node) {
548
+ return new _HorizontalRuleNode(node.__key);
549
+ }
550
+ static importJSON() {
551
+ return $createHorizontalRuleNode();
552
+ }
553
+ static importDOM() {
554
+ return {
555
+ hr: () => ({
556
+ conversion: $convertHorizontalRuleElement,
557
+ priority: 0
558
+ })
559
+ };
560
+ }
561
+ constructor(key) {
562
+ super(key);
563
+ }
564
+ exportJSON() {
565
+ return {
566
+ type: "horizontalrule",
567
+ version: 1
568
+ };
569
+ }
570
+ exportDOM() {
571
+ const hr = document.createElement("hr");
572
+ hr.style.border = "none";
573
+ hr.style.height = "2px";
574
+ hr.style.background = "linear-gradient(90deg, transparent, #d1d5db, transparent)";
575
+ hr.style.margin = "24px 0";
576
+ return { element: hr };
577
+ }
578
+ createDOM() {
579
+ const hr = document.createElement("hr");
580
+ hr.className = "trance-hr";
581
+ return hr;
582
+ }
583
+ updateDOM() {
584
+ return false;
585
+ }
586
+ getTextContent() {
587
+ return "\n";
588
+ }
589
+ isInline() {
590
+ return false;
591
+ }
592
+ decorate() {
593
+ return null;
594
+ }
595
+ };
596
+ function $createHorizontalRuleNode() {
597
+ return new HorizontalRuleNode();
598
+ }
599
+ function $isHorizontalRuleNode(node) {
600
+ return node instanceof HorizontalRuleNode;
601
+ }
602
+
603
+ // src/editor/nodes/index.ts
604
+ var TRANCE_NODES = [
605
+ HeadingNode,
606
+ QuoteNode,
607
+ ListNode,
608
+ ListItemNode,
609
+ LinkNode,
610
+ AutoLinkNode,
611
+ CodeNode,
612
+ CodeHighlightNode,
613
+ TableNode,
614
+ TableCellNode,
615
+ TableRowNode,
616
+ ImageNode,
617
+ HorizontalRuleNode
618
+ ];
619
+
620
+ // src/styles/lexical-theme.ts
621
+ var tranceLexicalTheme = {
622
+ paragraph: "trance-paragraph",
623
+ heading: {
624
+ h1: "trance-h1",
625
+ h2: "trance-h2",
626
+ h3: "trance-h3",
627
+ h4: "trance-h4",
628
+ h5: "trance-h5",
629
+ h6: "trance-h6"
630
+ },
631
+ text: {
632
+ bold: "trance-bold",
633
+ italic: "trance-italic",
634
+ underline: "trance-underline",
635
+ strikethrough: "trance-strikethrough",
636
+ underlineStrikethrough: "trance-underline trance-strikethrough",
637
+ code: "trance-inline-code",
638
+ highlight: "trance-highlight",
639
+ subscript: "trance-subscript",
640
+ superscript: "trance-superscript"
641
+ },
642
+ list: {
643
+ ul: "trance-ul",
644
+ ol: "trance-ol",
645
+ listitem: "trance-li",
646
+ listitemChecked: "trance-li-checked",
647
+ listitemUnchecked: "trance-li-unchecked",
648
+ nested: {
649
+ listitem: "trance-li-nested"
650
+ }
651
+ },
652
+ link: "trance-link",
653
+ quote: "trance-quote",
654
+ code: "trance-code-block",
655
+ codeHighlight: {
656
+ atrule: "trance-token-atrule",
657
+ attr: "trance-token-attr",
658
+ boolean: "trance-token-boolean",
659
+ builtin: "trance-token-builtin",
660
+ cdata: "trance-token-cdata",
661
+ char: "trance-token-char",
662
+ class: "trance-token-class",
663
+ "class-name": "trance-token-class-name",
664
+ comment: "trance-token-comment",
665
+ constant: "trance-token-constant",
666
+ deleted: "trance-token-deleted",
667
+ doctype: "trance-token-doctype",
668
+ entity: "trance-token-entity",
669
+ function: "trance-token-function",
670
+ important: "trance-token-important",
671
+ inserted: "trance-token-inserted",
672
+ keyword: "trance-token-keyword",
673
+ namespace: "trance-token-namespace",
674
+ number: "trance-token-number",
675
+ operator: "trance-token-operator",
676
+ prolog: "trance-token-prolog",
677
+ property: "trance-token-property",
678
+ punctuation: "trance-token-punctuation",
679
+ regex: "trance-token-regex",
680
+ selector: "trance-token-selector",
681
+ string: "trance-token-string",
682
+ symbol: "trance-token-symbol",
683
+ tag: "trance-token-tag",
684
+ url: "trance-token-url",
685
+ variable: "trance-token-variable"
686
+ },
687
+ table: "trance-table",
688
+ tableCell: "trance-table-cell",
689
+ tableCellHeader: "trance-table-cell-header",
690
+ tableRow: "trance-table-row",
691
+ tableRowStriped: "trance-table-row-striped",
692
+ horizontalRule: "trance-hr",
693
+ image: "trance-image-wrapper",
694
+ indent: "trance-indent",
695
+ ltr: "trance-text-left",
696
+ rtl: "trance-text-right"
697
+ };
698
+ function ToolbarButton({
699
+ icon,
700
+ label,
701
+ active = false,
702
+ disabled = false,
703
+ onClick,
704
+ shortcut
705
+ }) {
706
+ return /* @__PURE__ */ jsxs(
707
+ "button",
708
+ {
709
+ type: "button",
710
+ className: `trance-toolbar-btn${active ? " active" : ""}`,
711
+ disabled,
712
+ onClick,
713
+ "aria-label": label,
714
+ title: shortcut ? `${label} (${shortcut})` : label,
715
+ children: [
716
+ icon,
717
+ /* @__PURE__ */ jsxs("span", { className: "trance-tooltip", children: [
718
+ label,
719
+ shortcut && /* @__PURE__ */ jsx("span", { style: { opacity: 0.7, marginLeft: 4 }, children: shortcut })
720
+ ] })
721
+ ]
722
+ }
723
+ );
724
+ }
725
+ function ToolbarSeparator() {
726
+ return /* @__PURE__ */ jsx("div", { className: "trance-toolbar-separator", "aria-hidden": "true" });
727
+ }
728
+ var BoldIcon = () => /* @__PURE__ */ jsxs(
729
+ "svg",
730
+ {
731
+ width: "16",
732
+ height: "16",
733
+ viewBox: "0 0 24 24",
734
+ fill: "none",
735
+ stroke: "currentColor",
736
+ strokeWidth: "2.5",
737
+ strokeLinecap: "round",
738
+ strokeLinejoin: "round",
739
+ children: [
740
+ /* @__PURE__ */ jsx("path", { d: "M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" }),
741
+ /* @__PURE__ */ jsx("path", { d: "M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" })
742
+ ]
743
+ }
744
+ );
745
+ var ItalicIcon = () => /* @__PURE__ */ jsxs(
746
+ "svg",
747
+ {
748
+ width: "16",
749
+ height: "16",
750
+ viewBox: "0 0 24 24",
751
+ fill: "none",
752
+ stroke: "currentColor",
753
+ strokeWidth: "2",
754
+ strokeLinecap: "round",
755
+ strokeLinejoin: "round",
756
+ children: [
757
+ /* @__PURE__ */ jsx("line", { x1: "19", y1: "4", x2: "10", y2: "4" }),
758
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "20", x2: "5", y2: "20" }),
759
+ /* @__PURE__ */ jsx("line", { x1: "15", y1: "4", x2: "9", y2: "20" })
760
+ ]
761
+ }
762
+ );
763
+ var UnderlineIcon = () => /* @__PURE__ */ jsxs(
764
+ "svg",
765
+ {
766
+ width: "16",
767
+ height: "16",
768
+ viewBox: "0 0 24 24",
769
+ fill: "none",
770
+ stroke: "currentColor",
771
+ strokeWidth: "2",
772
+ strokeLinecap: "round",
773
+ strokeLinejoin: "round",
774
+ children: [
775
+ /* @__PURE__ */ jsx("path", { d: "M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3" }),
776
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "21", x2: "20", y2: "21" })
777
+ ]
778
+ }
779
+ );
780
+ var StrikethroughIcon = () => /* @__PURE__ */ jsxs(
781
+ "svg",
782
+ {
783
+ width: "16",
784
+ height: "16",
785
+ viewBox: "0 0 24 24",
786
+ fill: "none",
787
+ stroke: "currentColor",
788
+ strokeWidth: "2",
789
+ strokeLinecap: "round",
790
+ strokeLinejoin: "round",
791
+ children: [
792
+ /* @__PURE__ */ jsx("path", { d: "M16 4H9a3 3 0 0 0-3 3c0 2 1.5 3 3 3" }),
793
+ /* @__PURE__ */ jsx("path", { d: "M12 12h3c2 0 3 1 3 3a3 3 0 0 1-3 3H8" }),
794
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" })
795
+ ]
796
+ }
797
+ );
798
+ var CodeIcon = () => /* @__PURE__ */ jsxs(
799
+ "svg",
800
+ {
801
+ width: "16",
802
+ height: "16",
803
+ viewBox: "0 0 24 24",
804
+ fill: "none",
805
+ stroke: "currentColor",
806
+ strokeWidth: "2",
807
+ strokeLinecap: "round",
808
+ strokeLinejoin: "round",
809
+ children: [
810
+ /* @__PURE__ */ jsx("polyline", { points: "16 18 22 12 16 6" }),
811
+ /* @__PURE__ */ jsx("polyline", { points: "8 6 2 12 8 18" })
812
+ ]
813
+ }
814
+ );
815
+ var LinkIcon = () => /* @__PURE__ */ jsxs(
816
+ "svg",
817
+ {
818
+ width: "16",
819
+ height: "16",
820
+ viewBox: "0 0 24 24",
821
+ fill: "none",
822
+ stroke: "currentColor",
823
+ strokeWidth: "2",
824
+ strokeLinecap: "round",
825
+ strokeLinejoin: "round",
826
+ children: [
827
+ /* @__PURE__ */ jsx("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
828
+ /* @__PURE__ */ jsx("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
829
+ ]
830
+ }
831
+ );
832
+ var ImageIcon = () => /* @__PURE__ */ jsxs(
833
+ "svg",
834
+ {
835
+ width: "16",
836
+ height: "16",
837
+ viewBox: "0 0 24 24",
838
+ fill: "none",
839
+ stroke: "currentColor",
840
+ strokeWidth: "2",
841
+ strokeLinecap: "round",
842
+ strokeLinejoin: "round",
843
+ children: [
844
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
845
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
846
+ /* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
847
+ ]
848
+ }
849
+ );
850
+ var TableIcon = () => /* @__PURE__ */ jsxs(
851
+ "svg",
852
+ {
853
+ width: "16",
854
+ height: "16",
855
+ viewBox: "0 0 24 24",
856
+ fill: "none",
857
+ stroke: "currentColor",
858
+ strokeWidth: "2",
859
+ strokeLinecap: "round",
860
+ strokeLinejoin: "round",
861
+ children: [
862
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
863
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }),
864
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "15", x2: "21", y2: "15" }),
865
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "21" })
866
+ ]
867
+ }
868
+ );
869
+ var HorizontalRuleIcon = () => /* @__PURE__ */ jsx(
870
+ "svg",
871
+ {
872
+ width: "16",
873
+ height: "16",
874
+ viewBox: "0 0 24 24",
875
+ fill: "none",
876
+ stroke: "currentColor",
877
+ strokeWidth: "2",
878
+ strokeLinecap: "round",
879
+ strokeLinejoin: "round",
880
+ children: /* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
881
+ }
882
+ );
883
+ var ListBulletIcon = () => /* @__PURE__ */ jsxs(
884
+ "svg",
885
+ {
886
+ width: "16",
887
+ height: "16",
888
+ viewBox: "0 0 24 24",
889
+ fill: "none",
890
+ stroke: "currentColor",
891
+ strokeWidth: "2",
892
+ strokeLinecap: "round",
893
+ strokeLinejoin: "round",
894
+ children: [
895
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "6", x2: "20", y2: "6" }),
896
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "12", x2: "20", y2: "12" }),
897
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "18", x2: "20", y2: "18" }),
898
+ /* @__PURE__ */ jsx("circle", { cx: "4", cy: "6", r: "2", fill: "currentColor", stroke: "none" }),
899
+ /* @__PURE__ */ jsx("circle", { cx: "4", cy: "12", r: "2", fill: "currentColor", stroke: "none" }),
900
+ /* @__PURE__ */ jsx("circle", { cx: "4", cy: "18", r: "2", fill: "currentColor", stroke: "none" })
901
+ ]
902
+ }
903
+ );
904
+ var ListNumberedIcon = () => /* @__PURE__ */ jsxs(
905
+ "svg",
906
+ {
907
+ width: "16",
908
+ height: "16",
909
+ viewBox: "0 0 24 24",
910
+ fill: "none",
911
+ stroke: "currentColor",
912
+ strokeWidth: "2",
913
+ strokeLinecap: "round",
914
+ strokeLinejoin: "round",
915
+ children: [
916
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "6", x2: "21", y2: "6" }),
917
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "12", x2: "21", y2: "12" }),
918
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "18", x2: "21", y2: "18" }),
919
+ /* @__PURE__ */ jsx("path", { d: "M4 6h1V4H3v2h1v4H3v2h3V6H4z", fill: "currentColor", stroke: "none" }),
920
+ /* @__PURE__ */ jsx(
921
+ "path",
922
+ {
923
+ d: "M3 12v6h4v-2H5v-1h2v-2H5v-1h2v-2H3z",
924
+ fill: "currentColor",
925
+ stroke: "none"
926
+ }
927
+ )
928
+ ]
929
+ }
930
+ );
931
+ var ListCheckIcon = () => /* @__PURE__ */ jsxs(
932
+ "svg",
933
+ {
934
+ width: "16",
935
+ height: "16",
936
+ viewBox: "0 0 24 24",
937
+ fill: "none",
938
+ stroke: "currentColor",
939
+ strokeWidth: "2",
940
+ strokeLinecap: "round",
941
+ strokeLinejoin: "round",
942
+ children: [
943
+ /* @__PURE__ */ jsx("polyline", { points: "9 11 12 14 22 4" }),
944
+ /* @__PURE__ */ jsx("path", { d: "M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" })
945
+ ]
946
+ }
947
+ );
948
+ var AlignLeftIcon2 = () => /* @__PURE__ */ jsxs(
949
+ "svg",
950
+ {
951
+ width: "16",
952
+ height: "16",
953
+ viewBox: "0 0 24 24",
954
+ fill: "none",
955
+ stroke: "currentColor",
956
+ strokeWidth: "2",
957
+ strokeLinecap: "round",
958
+ strokeLinejoin: "round",
959
+ children: [
960
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
961
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "13", y2: "12" }),
962
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "18", y2: "18" })
963
+ ]
964
+ }
965
+ );
966
+ var AlignCenterIcon2 = () => /* @__PURE__ */ jsxs(
967
+ "svg",
968
+ {
969
+ width: "16",
970
+ height: "16",
971
+ viewBox: "0 0 24 24",
972
+ fill: "none",
973
+ stroke: "currentColor",
974
+ strokeWidth: "2",
975
+ strokeLinecap: "round",
976
+ strokeLinejoin: "round",
977
+ children: [
978
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
979
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "12", x2: "18", y2: "12" }),
980
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18" })
981
+ ]
982
+ }
983
+ );
984
+ var AlignRightIcon2 = () => /* @__PURE__ */ jsxs(
985
+ "svg",
986
+ {
987
+ width: "16",
988
+ height: "16",
989
+ viewBox: "0 0 24 24",
990
+ fill: "none",
991
+ stroke: "currentColor",
992
+ strokeWidth: "2",
993
+ strokeLinecap: "round",
994
+ strokeLinejoin: "round",
995
+ children: [
996
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
997
+ /* @__PURE__ */ jsx("line", { x1: "11", y1: "12", x2: "21", y2: "12" }),
998
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "18", x2: "21", y2: "18" })
999
+ ]
1000
+ }
1001
+ );
1002
+ var AlignJustifyIcon = () => /* @__PURE__ */ jsxs(
1003
+ "svg",
1004
+ {
1005
+ width: "16",
1006
+ height: "16",
1007
+ viewBox: "0 0 24 24",
1008
+ fill: "none",
1009
+ stroke: "currentColor",
1010
+ strokeWidth: "2",
1011
+ strokeLinecap: "round",
1012
+ strokeLinejoin: "round",
1013
+ children: [
1014
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
1015
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12" }),
1016
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18" })
1017
+ ]
1018
+ }
1019
+ );
1020
+ var UndoIcon = () => /* @__PURE__ */ jsxs(
1021
+ "svg",
1022
+ {
1023
+ width: "16",
1024
+ height: "16",
1025
+ viewBox: "0 0 24 24",
1026
+ fill: "none",
1027
+ stroke: "currentColor",
1028
+ strokeWidth: "2",
1029
+ strokeLinecap: "round",
1030
+ strokeLinejoin: "round",
1031
+ children: [
1032
+ /* @__PURE__ */ jsx("path", { d: "M3 7v6h6" }),
1033
+ /* @__PURE__ */ jsx("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" })
1034
+ ]
1035
+ }
1036
+ );
1037
+ var RedoIcon = () => /* @__PURE__ */ jsxs(
1038
+ "svg",
1039
+ {
1040
+ width: "16",
1041
+ height: "16",
1042
+ viewBox: "0 0 24 24",
1043
+ fill: "none",
1044
+ stroke: "currentColor",
1045
+ strokeWidth: "2",
1046
+ strokeLinecap: "round",
1047
+ strokeLinejoin: "round",
1048
+ children: [
1049
+ /* @__PURE__ */ jsx("path", { d: "M21 7v6h-6" }),
1050
+ /* @__PURE__ */ jsx("path", { d: "M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13" })
1051
+ ]
1052
+ }
1053
+ );
1054
+ var HeadingIcon = () => /* @__PURE__ */ jsxs(
1055
+ "svg",
1056
+ {
1057
+ width: "16",
1058
+ height: "16",
1059
+ viewBox: "0 0 24 24",
1060
+ fill: "none",
1061
+ stroke: "currentColor",
1062
+ strokeWidth: "2",
1063
+ strokeLinecap: "round",
1064
+ strokeLinejoin: "round",
1065
+ children: [
1066
+ /* @__PURE__ */ jsx("path", { d: "M6 12h12" }),
1067
+ /* @__PURE__ */ jsx("path", { d: "M6 4v16" }),
1068
+ /* @__PURE__ */ jsx("path", { d: "M18 4v16" })
1069
+ ]
1070
+ }
1071
+ );
1072
+ var QuoteIcon = () => /* @__PURE__ */ jsxs(
1073
+ "svg",
1074
+ {
1075
+ width: "16",
1076
+ height: "16",
1077
+ viewBox: "0 0 24 24",
1078
+ fill: "none",
1079
+ stroke: "currentColor",
1080
+ strokeWidth: "2",
1081
+ strokeLinecap: "round",
1082
+ strokeLinejoin: "round",
1083
+ children: [
1084
+ /* @__PURE__ */ jsx("path", { d: "M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21c0 1 0 1 1 1z" }),
1085
+ /* @__PURE__ */ jsx("path", { d: "M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z" })
1086
+ ]
1087
+ }
1088
+ );
1089
+ var CodeBlockIcon = () => /* @__PURE__ */ jsxs(
1090
+ "svg",
1091
+ {
1092
+ width: "16",
1093
+ height: "16",
1094
+ viewBox: "0 0 24 24",
1095
+ fill: "none",
1096
+ stroke: "currentColor",
1097
+ strokeWidth: "2",
1098
+ strokeLinecap: "round",
1099
+ strokeLinejoin: "round",
1100
+ children: [
1101
+ /* @__PURE__ */ jsx("polyline", { points: "16 18 22 12 16 6" }),
1102
+ /* @__PURE__ */ jsx("polyline", { points: "8 6 2 12 8 18" })
1103
+ ]
1104
+ }
1105
+ );
1106
+ var SuperscriptIcon = () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: [
1107
+ /* @__PURE__ */ jsx(
1108
+ "path",
1109
+ {
1110
+ d: "M5 7l6 8.5L5 24h3l4.5-6.3L17 24h3l-6-8.5L20 7h-3l-4.5 6.3L8 7H5z",
1111
+ transform: "scale(0.7) translate(2, 4)"
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsx("text", { x: "16", y: "10", fontSize: "10", fontWeight: "bold", fontFamily: "sans-serif", children: "2" })
1115
+ ] });
1116
+ var SubscriptIcon = () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: [
1117
+ /* @__PURE__ */ jsx(
1118
+ "path",
1119
+ {
1120
+ d: "M5 3l6 8.5L5 20h3l4.5-6.3L17 20h3l-6-8.5L20 3h-3l-4.5 6.3L8 3H5z",
1121
+ transform: "scale(0.7) translate(2, 0)"
1122
+ }
1123
+ ),
1124
+ /* @__PURE__ */ jsx("text", { x: "16", y: "22", fontSize: "10", fontWeight: "bold", fontFamily: "sans-serif", children: "2" })
1125
+ ] });
1126
+ var HighlightIcon = () => /* @__PURE__ */ jsxs(
1127
+ "svg",
1128
+ {
1129
+ width: "16",
1130
+ height: "16",
1131
+ viewBox: "0 0 24 24",
1132
+ fill: "none",
1133
+ stroke: "currentColor",
1134
+ strokeWidth: "2",
1135
+ strokeLinecap: "round",
1136
+ strokeLinejoin: "round",
1137
+ children: [
1138
+ /* @__PURE__ */ jsx("path", { d: "M12 20h9" }),
1139
+ /* @__PURE__ */ jsx("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })
1140
+ ]
1141
+ }
1142
+ );
1143
+ var ImportIcon = () => /* @__PURE__ */ jsxs(
1144
+ "svg",
1145
+ {
1146
+ width: "16",
1147
+ height: "16",
1148
+ viewBox: "0 0 24 24",
1149
+ fill: "none",
1150
+ stroke: "currentColor",
1151
+ strokeWidth: "2",
1152
+ strokeLinecap: "round",
1153
+ strokeLinejoin: "round",
1154
+ children: [
1155
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1156
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" }),
1157
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "18", x2: "12", y2: "12" }),
1158
+ /* @__PURE__ */ jsx("polyline", { points: "9 15 12 18 15 15" })
1159
+ ]
1160
+ }
1161
+ );
1162
+ var ChevronDownIcon = () => /* @__PURE__ */ jsx(
1163
+ "svg",
1164
+ {
1165
+ width: "12",
1166
+ height: "12",
1167
+ viewBox: "0 0 24 24",
1168
+ fill: "none",
1169
+ stroke: "currentColor",
1170
+ strokeWidth: "2",
1171
+ strokeLinecap: "round",
1172
+ strokeLinejoin: "round",
1173
+ className: "chevron",
1174
+ children: /* @__PURE__ */ jsx("polyline", { points: "6 9 12 15 18 9" })
1175
+ }
1176
+ );
1177
+ var BLOCK_TYPES = [
1178
+ { value: "paragraph", label: "Paragraph", icon: null },
1179
+ { value: "h1", label: "Heading 1", icon: /* @__PURE__ */ jsx(HeadingIcon, {}) },
1180
+ { value: "h2", label: "Heading 2", icon: /* @__PURE__ */ jsx(HeadingIcon, {}) },
1181
+ { value: "h3", label: "Heading 3", icon: /* @__PURE__ */ jsx(HeadingIcon, {}) },
1182
+ { value: "h4", label: "Heading 4", icon: /* @__PURE__ */ jsx(HeadingIcon, {}) },
1183
+ { value: "h5", label: "Heading 5", icon: /* @__PURE__ */ jsx(HeadingIcon, {}) },
1184
+ { value: "h6", label: "Heading 6", icon: /* @__PURE__ */ jsx(HeadingIcon, {}) },
1185
+ { value: "quote", label: "Blockquote", icon: /* @__PURE__ */ jsx(QuoteIcon, {}) },
1186
+ { value: "code", label: "Code Block", icon: /* @__PURE__ */ jsx(CodeBlockIcon, {}) }
1187
+ ];
1188
+ function BlockTypeDropdown({
1189
+ currentBlockType,
1190
+ onSelect
1191
+ }) {
1192
+ const [isOpen, setIsOpen] = useState(false);
1193
+ const [menuPosition, setMenuPosition] = useState({
1194
+ top: 0,
1195
+ left: 0
1196
+ });
1197
+ const dropdownRef = useRef(null);
1198
+ const triggerRef = useRef(null);
1199
+ const currentLabel = BLOCK_TYPES.find((bt) => bt.value === currentBlockType)?.label || "Paragraph";
1200
+ const updateMenuPosition = useCallback(() => {
1201
+ if (!triggerRef.current) return;
1202
+ const rect = triggerRef.current.getBoundingClientRect();
1203
+ setMenuPosition({
1204
+ top: rect.bottom + 4,
1205
+ left: rect.left
1206
+ });
1207
+ }, []);
1208
+ const handleSelect = useCallback(
1209
+ (value) => {
1210
+ onSelect(value);
1211
+ setIsOpen(false);
1212
+ },
1213
+ [onSelect]
1214
+ );
1215
+ useLayoutEffect(() => {
1216
+ if (isOpen) {
1217
+ updateMenuPosition();
1218
+ }
1219
+ }, [isOpen, updateMenuPosition]);
1220
+ useEffect(() => {
1221
+ if (!isOpen) return;
1222
+ const handleClickOutside = (event) => {
1223
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
1224
+ setIsOpen(false);
1225
+ }
1226
+ };
1227
+ const handleKeyDown = (event) => {
1228
+ if (event.key === "Escape") {
1229
+ setIsOpen(false);
1230
+ }
1231
+ };
1232
+ const handleResizeOrScroll = () => {
1233
+ updateMenuPosition();
1234
+ };
1235
+ document.addEventListener("mousedown", handleClickOutside);
1236
+ document.addEventListener("keydown", handleKeyDown);
1237
+ window.addEventListener("resize", handleResizeOrScroll);
1238
+ window.addEventListener("scroll", handleResizeOrScroll, true);
1239
+ return () => {
1240
+ document.removeEventListener("mousedown", handleClickOutside);
1241
+ document.removeEventListener("keydown", handleKeyDown);
1242
+ window.removeEventListener("resize", handleResizeOrScroll);
1243
+ window.removeEventListener("scroll", handleResizeOrScroll, true);
1244
+ };
1245
+ }, [isOpen, updateMenuPosition]);
1246
+ return /* @__PURE__ */ jsxs("div", { className: "trance-block-dropdown-wrapper", ref: dropdownRef, children: [
1247
+ /* @__PURE__ */ jsxs(
1248
+ "button",
1249
+ {
1250
+ ref: triggerRef,
1251
+ type: "button",
1252
+ className: "trance-block-dropdown-trigger",
1253
+ onClick: () => setIsOpen(!isOpen),
1254
+ "aria-expanded": isOpen,
1255
+ "aria-haspopup": "listbox",
1256
+ "aria-label": "Select block type",
1257
+ children: [
1258
+ currentLabel,
1259
+ /* @__PURE__ */ jsx(ChevronDownIcon, {})
1260
+ ]
1261
+ }
1262
+ ),
1263
+ isOpen && /* @__PURE__ */ jsx(
1264
+ "div",
1265
+ {
1266
+ className: "trance-block-dropdown-menu",
1267
+ role: "listbox",
1268
+ style: {
1269
+ top: menuPosition.top,
1270
+ left: menuPosition.left
1271
+ },
1272
+ children: BLOCK_TYPES.map(({ value, label, icon }) => /* @__PURE__ */ jsxs(
1273
+ "button",
1274
+ {
1275
+ type: "button",
1276
+ role: "option",
1277
+ className: `trance-block-dropdown-item${value === currentBlockType ? " active" : ""}`,
1278
+ "aria-selected": value === currentBlockType,
1279
+ onClick: () => handleSelect(value),
1280
+ children: [
1281
+ icon,
1282
+ label
1283
+ ]
1284
+ },
1285
+ value
1286
+ ))
1287
+ }
1288
+ )
1289
+ ] });
1290
+ }
1291
+ var MAX_ROWS = 10;
1292
+ var MAX_COLS = 10;
1293
+ function TableSizePicker({
1294
+ triggerRef,
1295
+ onSelect,
1296
+ onCancel
1297
+ }) {
1298
+ const [hovered, setHovered] = useState({ rows: 1, cols: 1 });
1299
+ const [position, setPosition] = useState({ top: 0, left: 0 });
1300
+ const wrapperRef = useRef(null);
1301
+ useLayoutEffect(() => {
1302
+ const triggerRect = triggerRef.current?.getBoundingClientRect();
1303
+ const pickerRect = wrapperRef.current?.getBoundingClientRect();
1304
+ if (triggerRect) {
1305
+ const pickerWidth = pickerRect?.width ?? 180;
1306
+ const left = Math.min(
1307
+ triggerRect.left,
1308
+ window.innerWidth - pickerWidth - 8
1309
+ );
1310
+ setPosition({
1311
+ top: triggerRect.bottom + 4,
1312
+ left: Math.max(8, left)
1313
+ });
1314
+ }
1315
+ }, [triggerRef]);
1316
+ useEffect(() => {
1317
+ const handleClickOutside = (event) => {
1318
+ if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
1319
+ onCancel();
1320
+ }
1321
+ };
1322
+ const handleKeyDown = (event) => {
1323
+ if (event.key === "Escape") {
1324
+ onCancel();
1325
+ }
1326
+ };
1327
+ document.addEventListener("mousedown", handleClickOutside);
1328
+ document.addEventListener("keydown", handleKeyDown);
1329
+ return () => {
1330
+ document.removeEventListener("mousedown", handleClickOutside);
1331
+ document.removeEventListener("keydown", handleKeyDown);
1332
+ };
1333
+ }, [onCancel]);
1334
+ const handleCellHover = useCallback((row, col) => {
1335
+ setHovered({ rows: row + 1, cols: col + 1 });
1336
+ }, []);
1337
+ const handleCellClick = useCallback(
1338
+ (row, col) => {
1339
+ onSelect(row + 1, col + 1);
1340
+ },
1341
+ [onSelect]
1342
+ );
1343
+ return /* @__PURE__ */ jsxs(
1344
+ "div",
1345
+ {
1346
+ ref: wrapperRef,
1347
+ className: "trance-table-size-picker",
1348
+ role: "dialog",
1349
+ "aria-label": "Select table size",
1350
+ style: {
1351
+ top: position.top,
1352
+ left: position.left
1353
+ },
1354
+ children: [
1355
+ /* @__PURE__ */ jsxs("div", { className: "trance-table-size-picker-label", children: [
1356
+ hovered.rows,
1357
+ " \xD7 ",
1358
+ hovered.cols,
1359
+ " Table"
1360
+ ] }),
1361
+ /* @__PURE__ */ jsx("div", { className: "trance-table-size-picker-grid", children: Array.from({ length: MAX_ROWS }, (_, row) => /* @__PURE__ */ jsx("div", { className: "trance-table-size-picker-row", children: Array.from({ length: MAX_COLS }, (_2, col) => {
1362
+ const isActive = row < hovered.rows && col < hovered.cols;
1363
+ return /* @__PURE__ */ jsx(
1364
+ "button",
1365
+ {
1366
+ type: "button",
1367
+ className: isActive ? "trance-table-size-picker-cell active" : "trance-table-size-picker-cell",
1368
+ onMouseEnter: () => handleCellHover(row, col),
1369
+ onClick: () => handleCellClick(row, col),
1370
+ "aria-label": `Insert ${row + 1} by ${col + 1} table`
1371
+ },
1372
+ `${row}-${col}`
1373
+ );
1374
+ }) }, row)) })
1375
+ ]
1376
+ }
1377
+ );
1378
+ }
1379
+ var INSERT_IMAGE_COMMAND = createCommand("INSERT_IMAGE_COMMAND");
1380
+ function ImagesPlugin({ onImageUpload }) {
1381
+ const [editor] = useLexicalComposerContext();
1382
+ useEffect(() => {
1383
+ return editor.registerCommand(
1384
+ INSERT_IMAGE_COMMAND,
1385
+ (payload) => {
1386
+ const imageNode = $createImageNode(payload);
1387
+ $insertNodes([imageNode]);
1388
+ return true;
1389
+ },
1390
+ COMMAND_PRIORITY_EDITOR
1391
+ );
1392
+ }, [editor]);
1393
+ useEffect(() => {
1394
+ const rootElement = editor.getRootElement();
1395
+ if (!rootElement) return;
1396
+ const handleDrop = async (event) => {
1397
+ const files = event.dataTransfer?.files;
1398
+ if (!files || files.length === 0) return;
1399
+ const file = files[0];
1400
+ if (!file.type.startsWith("image/")) return;
1401
+ event.preventDefault();
1402
+ event.stopPropagation();
1403
+ try {
1404
+ let url;
1405
+ let alt = file.name;
1406
+ if (onImageUpload) {
1407
+ const result = await onImageUpload(file);
1408
+ url = result.url;
1409
+ alt = result.alt || file.name;
1410
+ } else {
1411
+ url = await fileToBase64(file);
1412
+ }
1413
+ editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
1414
+ src: url,
1415
+ altText: alt
1416
+ });
1417
+ } catch (error) {
1418
+ console.error("Image upload failed:", error);
1419
+ }
1420
+ };
1421
+ const handleDragOver = (event) => {
1422
+ const files = event.dataTransfer?.types;
1423
+ if (files?.includes("Files")) {
1424
+ event.preventDefault();
1425
+ }
1426
+ };
1427
+ rootElement.addEventListener("drop", handleDrop);
1428
+ rootElement.addEventListener("dragover", handleDragOver);
1429
+ return () => {
1430
+ rootElement.removeEventListener("drop", handleDrop);
1431
+ rootElement.removeEventListener("dragover", handleDragOver);
1432
+ };
1433
+ }, [editor, onImageUpload]);
1434
+ return null;
1435
+ }
1436
+ function fileToBase64(file) {
1437
+ return new Promise((resolve, reject) => {
1438
+ const reader = new FileReader();
1439
+ reader.onload = () => resolve(reader.result);
1440
+ reader.onerror = reject;
1441
+ reader.readAsDataURL(file);
1442
+ });
1443
+ }
1444
+ var INSERT_HORIZONTAL_RULE_COMMAND = createCommand("INSERT_HORIZONTAL_RULE_COMMAND");
1445
+ function HorizontalRulePlugin() {
1446
+ const [editor] = useLexicalComposerContext();
1447
+ useEffect(() => {
1448
+ return editor.registerCommand(
1449
+ INSERT_HORIZONTAL_RULE_COMMAND,
1450
+ () => {
1451
+ const selection = $getSelection();
1452
+ if ($isRangeSelection(selection)) {
1453
+ const hrNode = $createHorizontalRuleNode();
1454
+ $insertNodes([hrNode]);
1455
+ }
1456
+ return true;
1457
+ },
1458
+ COMMAND_PRIORITY_EDITOR
1459
+ );
1460
+ }, [editor]);
1461
+ return null;
1462
+ }
1463
+ function getFileExtension(filename) {
1464
+ const lastDot = filename.lastIndexOf(".");
1465
+ return lastDot === -1 ? "" : filename.slice(lastDot + 1).toLowerCase();
1466
+ }
1467
+ function fileToArrayBuffer(file) {
1468
+ return new Promise((resolve, reject) => {
1469
+ const reader = new FileReader();
1470
+ reader.onload = () => resolve(reader.result);
1471
+ reader.onerror = reject;
1472
+ reader.readAsArrayBuffer(file);
1473
+ });
1474
+ }
1475
+ async function importDocx(editor, file) {
1476
+ const mammoth = await import('mammoth');
1477
+ const arrayBuffer = await fileToArrayBuffer(file);
1478
+ const result = await mammoth.convertToHtml({ arrayBuffer });
1479
+ editor.update(() => {
1480
+ const parser = new DOMParser();
1481
+ const dom = parser.parseFromString(result.value, "text/html");
1482
+ const nodes = $generateNodesFromDOM(editor, dom);
1483
+ $insertNodes(nodes);
1484
+ });
1485
+ }
1486
+ async function importDocument(editor, file) {
1487
+ const extension = getFileExtension(file.name);
1488
+ if (extension === "docx") {
1489
+ await importDocx(editor, file);
1490
+ return { type: "docx", title: file.name };
1491
+ }
1492
+ throw new Error(
1493
+ `Unsupported file type: ${extension}. Only .docx is supported.`
1494
+ );
1495
+ }
1496
+ function Toolbar({ features, onImageUpload }) {
1497
+ const [editor] = useLexicalComposerContext();
1498
+ const [activeEditor, setActiveEditor] = useState(editor);
1499
+ const [isBold, setIsBold] = useState(false);
1500
+ const [isItalic, setIsItalic] = useState(false);
1501
+ const [isUnderline, setIsUnderline] = useState(false);
1502
+ const [isStrikethrough, setIsStrikethrough] = useState(false);
1503
+ const [isCode, setIsCode] = useState(false);
1504
+ const [isSuperscript, setIsSuperscript] = useState(false);
1505
+ const [isSubscript, setIsSubscript] = useState(false);
1506
+ const [isHighlight, setIsHighlight] = useState(false);
1507
+ const [isLink, setIsLink] = useState(false);
1508
+ const [blockType, setBlockType] = useState("paragraph");
1509
+ const [canUndo, setCanUndo] = useState(false);
1510
+ const [canRedo, setCanRedo] = useState(false);
1511
+ const [showTablePicker, setShowTablePicker] = useState(false);
1512
+ const tableButtonRef = useRef(null);
1513
+ const $updateToolbar = useCallback(() => {
1514
+ const selection = $getSelection();
1515
+ if ($isRangeSelection(selection)) {
1516
+ setIsBold(selection.hasFormat("bold"));
1517
+ setIsItalic(selection.hasFormat("italic"));
1518
+ setIsUnderline(selection.hasFormat("underline"));
1519
+ setIsStrikethrough(selection.hasFormat("strikethrough"));
1520
+ setIsCode(selection.hasFormat("code"));
1521
+ setIsSuperscript(selection.hasFormat("superscript"));
1522
+ setIsSubscript(selection.hasFormat("subscript"));
1523
+ setIsHighlight(selection.hasFormat("highlight"));
1524
+ const anchorNode = selection.anchor.getNode();
1525
+ let element = anchorNode.getKey() === "root" ? anchorNode : $findMatchingParent(anchorNode, (e) => {
1526
+ const parent2 = e.getParent();
1527
+ return parent2 !== null && $isRootOrShadowRoot(parent2);
1528
+ });
1529
+ if (element === null) {
1530
+ element = anchorNode.getTopLevelElementOrThrow();
1531
+ }
1532
+ activeEditor.getElementByKey(element.getKey());
1533
+ if ($isListNode(element)) {
1534
+ const parentList = $getNearestNodeOfType(
1535
+ anchorNode,
1536
+ ListNode
1537
+ );
1538
+ const type = parentList ? parentList.getListType() : element.getListType();
1539
+ if (type === "bullet") setBlockType("bullet");
1540
+ else if (type === "number") setBlockType("number");
1541
+ else if (type === "check") setBlockType("check");
1542
+ } else {
1543
+ const type = $isHeadingNode(element) ? element.getTag() : $isCodeNode(element) ? "code" : $isQuoteNode(element) ? "quote" : "paragraph";
1544
+ setBlockType(type);
1545
+ }
1546
+ const node = selection.anchor.getNode();
1547
+ const parent = node.getParent();
1548
+ setIsLink($isLinkNode(parent) || $isLinkNode(node));
1549
+ }
1550
+ }, [activeEditor]);
1551
+ useEffect(() => {
1552
+ return editor.registerCommand(
1553
+ SELECTION_CHANGE_COMMAND,
1554
+ () => {
1555
+ $updateToolbar();
1556
+ return false;
1557
+ },
1558
+ COMMAND_PRIORITY_CRITICAL
1559
+ );
1560
+ }, [editor, $updateToolbar]);
1561
+ useEffect(() => {
1562
+ return editor.registerUpdateListener(({ editorState }) => {
1563
+ editorState.read(() => {
1564
+ $updateToolbar();
1565
+ });
1566
+ });
1567
+ }, [editor, $updateToolbar]);
1568
+ useEffect(() => {
1569
+ return editor.registerCommand(
1570
+ CAN_UNDO_COMMAND,
1571
+ (payload) => {
1572
+ setCanUndo(payload);
1573
+ return false;
1574
+ },
1575
+ COMMAND_PRIORITY_CRITICAL
1576
+ );
1577
+ }, [editor]);
1578
+ useEffect(() => {
1579
+ return editor.registerCommand(
1580
+ CAN_REDO_COMMAND,
1581
+ (payload) => {
1582
+ setCanRedo(payload);
1583
+ return false;
1584
+ },
1585
+ COMMAND_PRIORITY_CRITICAL
1586
+ );
1587
+ }, [editor]);
1588
+ const handleBlockTypeChange = useCallback(
1589
+ (type) => {
1590
+ editor.update(() => {
1591
+ const selection = $getSelection();
1592
+ if (!$isRangeSelection(selection)) return;
1593
+ if (type === "paragraph") {
1594
+ $setBlocksType(selection, () => $createParagraphNode());
1595
+ } else if (type.match(/^h[1-6]$/)) {
1596
+ $setBlocksType(
1597
+ selection,
1598
+ () => $createHeadingNode(type)
1599
+ );
1600
+ } else if (type === "quote") {
1601
+ $setBlocksType(selection, () => $createQuoteNode());
1602
+ } else if (type === "code") {
1603
+ $setBlocksType(selection, () => $createCodeNode());
1604
+ }
1605
+ });
1606
+ },
1607
+ [editor]
1608
+ );
1609
+ const handleImageInsert = useCallback(async () => {
1610
+ const input = document.createElement("input");
1611
+ input.type = "file";
1612
+ input.accept = "image/*";
1613
+ input.onchange = async () => {
1614
+ const file = input.files?.[0];
1615
+ if (!file) return;
1616
+ try {
1617
+ let url;
1618
+ let alt = file.name;
1619
+ if (onImageUpload) {
1620
+ const result = await onImageUpload(file);
1621
+ url = result.url;
1622
+ alt = result.alt || file.name;
1623
+ } else {
1624
+ url = await new Promise((resolve, reject) => {
1625
+ const reader = new FileReader();
1626
+ reader.onload = () => resolve(reader.result);
1627
+ reader.onerror = reject;
1628
+ reader.readAsDataURL(file);
1629
+ });
1630
+ }
1631
+ editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
1632
+ src: url,
1633
+ altText: alt
1634
+ });
1635
+ } catch (err) {
1636
+ console.error("Image insert failed:", err);
1637
+ }
1638
+ };
1639
+ input.click();
1640
+ }, [editor, onImageUpload]);
1641
+ const normalizeUrl = useCallback((url) => {
1642
+ const trimmed = url.trim();
1643
+ if (!trimmed) return "";
1644
+ if (/^(https?:|mailto:|tel:|#)/i.test(trimmed)) {
1645
+ return trimmed;
1646
+ }
1647
+ if (trimmed.includes("@") && !trimmed.includes("/")) {
1648
+ return `mailto:${trimmed}`;
1649
+ }
1650
+ return `https://${trimmed}`;
1651
+ }, []);
1652
+ const handleLinkToggle = useCallback(() => {
1653
+ if (isLink) {
1654
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
1655
+ } else {
1656
+ const url = prompt("Enter URL:");
1657
+ if (url) {
1658
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, normalizeUrl(url));
1659
+ }
1660
+ }
1661
+ }, [editor, isLink, normalizeUrl]);
1662
+ const handleTableInsert = useCallback(
1663
+ (rows, columns) => {
1664
+ editor.dispatchCommand(INSERT_TABLE_COMMAND, {
1665
+ rows: String(rows),
1666
+ columns: String(columns),
1667
+ includeHeaders: true
1668
+ });
1669
+ setShowTablePicker(false);
1670
+ },
1671
+ [editor]
1672
+ );
1673
+ const handleImport = useCallback(async () => {
1674
+ const input = document.createElement("input");
1675
+ input.type = "file";
1676
+ input.accept = ".docx";
1677
+ input.onchange = async () => {
1678
+ const file = input.files?.[0];
1679
+ if (!file) return;
1680
+ try {
1681
+ await importDocument(editor, file);
1682
+ } catch (error) {
1683
+ console.error("Document import failed:", error);
1684
+ alert("Failed to import document. Please try a .docx file.");
1685
+ }
1686
+ };
1687
+ input.click();
1688
+ }, [editor]);
1689
+ return /* @__PURE__ */ jsxs("div", { className: "trance-toolbar", role: "toolbar", "aria-label": "Text formatting", children: [
1690
+ /* @__PURE__ */ jsx(
1691
+ ToolbarButton,
1692
+ {
1693
+ icon: /* @__PURE__ */ jsx(UndoIcon, {}),
1694
+ label: "Undo",
1695
+ shortcut: "\u2318Z",
1696
+ disabled: !canUndo,
1697
+ onClick: () => editor.dispatchCommand(UNDO_COMMAND, void 0)
1698
+ }
1699
+ ),
1700
+ /* @__PURE__ */ jsx(
1701
+ ToolbarButton,
1702
+ {
1703
+ icon: /* @__PURE__ */ jsx(RedoIcon, {}),
1704
+ label: "Redo",
1705
+ shortcut: "\u2318\u21E7Z",
1706
+ disabled: !canRedo,
1707
+ onClick: () => editor.dispatchCommand(REDO_COMMAND, void 0)
1708
+ }
1709
+ ),
1710
+ /* @__PURE__ */ jsx(ToolbarSeparator, {}),
1711
+ features.heading !== false && /* @__PURE__ */ jsx(
1712
+ BlockTypeDropdown,
1713
+ {
1714
+ currentBlockType: blockType,
1715
+ onSelect: handleBlockTypeChange
1716
+ }
1717
+ ),
1718
+ /* @__PURE__ */ jsx(ToolbarSeparator, {}),
1719
+ features.bold !== false && /* @__PURE__ */ jsx(
1720
+ ToolbarButton,
1721
+ {
1722
+ icon: /* @__PURE__ */ jsx(BoldIcon, {}),
1723
+ label: "Bold",
1724
+ shortcut: "\u2318B",
1725
+ active: isBold,
1726
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold")
1727
+ }
1728
+ ),
1729
+ features.italic !== false && /* @__PURE__ */ jsx(
1730
+ ToolbarButton,
1731
+ {
1732
+ icon: /* @__PURE__ */ jsx(ItalicIcon, {}),
1733
+ label: "Italic",
1734
+ shortcut: "\u2318I",
1735
+ active: isItalic,
1736
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic")
1737
+ }
1738
+ ),
1739
+ features.underline !== false && /* @__PURE__ */ jsx(
1740
+ ToolbarButton,
1741
+ {
1742
+ icon: /* @__PURE__ */ jsx(UnderlineIcon, {}),
1743
+ label: "Underline",
1744
+ shortcut: "\u2318U",
1745
+ active: isUnderline,
1746
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline")
1747
+ }
1748
+ ),
1749
+ features.strikethrough !== false && /* @__PURE__ */ jsx(
1750
+ ToolbarButton,
1751
+ {
1752
+ icon: /* @__PURE__ */ jsx(StrikethroughIcon, {}),
1753
+ label: "Strikethrough",
1754
+ active: isStrikethrough,
1755
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough")
1756
+ }
1757
+ ),
1758
+ features.code !== false && /* @__PURE__ */ jsx(
1759
+ ToolbarButton,
1760
+ {
1761
+ icon: /* @__PURE__ */ jsx(CodeIcon, {}),
1762
+ label: "Inline Code",
1763
+ active: isCode,
1764
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "code")
1765
+ }
1766
+ ),
1767
+ features.highlight !== false && /* @__PURE__ */ jsx(
1768
+ ToolbarButton,
1769
+ {
1770
+ icon: /* @__PURE__ */ jsx(HighlightIcon, {}),
1771
+ label: "Highlight",
1772
+ active: isHighlight,
1773
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "highlight")
1774
+ }
1775
+ ),
1776
+ features.superscript !== false && /* @__PURE__ */ jsx(
1777
+ ToolbarButton,
1778
+ {
1779
+ icon: /* @__PURE__ */ jsx(SuperscriptIcon, {}),
1780
+ label: "Superscript",
1781
+ active: isSuperscript,
1782
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "superscript")
1783
+ }
1784
+ ),
1785
+ features.subscript !== false && /* @__PURE__ */ jsx(
1786
+ ToolbarButton,
1787
+ {
1788
+ icon: /* @__PURE__ */ jsx(SubscriptIcon, {}),
1789
+ label: "Subscript",
1790
+ active: isSubscript,
1791
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "subscript")
1792
+ }
1793
+ ),
1794
+ /* @__PURE__ */ jsx(ToolbarSeparator, {}),
1795
+ features.unorderedList !== false && /* @__PURE__ */ jsx(
1796
+ ToolbarButton,
1797
+ {
1798
+ icon: /* @__PURE__ */ jsx(ListBulletIcon, {}),
1799
+ label: "Bullet List",
1800
+ active: blockType === "bullet",
1801
+ onClick: () => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, void 0)
1802
+ }
1803
+ ),
1804
+ features.orderedList !== false && /* @__PURE__ */ jsx(
1805
+ ToolbarButton,
1806
+ {
1807
+ icon: /* @__PURE__ */ jsx(ListNumberedIcon, {}),
1808
+ label: "Numbered List",
1809
+ active: blockType === "number",
1810
+ onClick: () => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, void 0)
1811
+ }
1812
+ ),
1813
+ features.checkList !== false && /* @__PURE__ */ jsx(
1814
+ ToolbarButton,
1815
+ {
1816
+ icon: /* @__PURE__ */ jsx(ListCheckIcon, {}),
1817
+ label: "Check List",
1818
+ active: blockType === "check",
1819
+ onClick: () => editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, void 0)
1820
+ }
1821
+ ),
1822
+ /* @__PURE__ */ jsx(ToolbarSeparator, {}),
1823
+ features.link !== false && /* @__PURE__ */ jsx(
1824
+ ToolbarButton,
1825
+ {
1826
+ icon: /* @__PURE__ */ jsx(LinkIcon, {}),
1827
+ label: "Link",
1828
+ shortcut: "\u2318K",
1829
+ active: isLink,
1830
+ onClick: handleLinkToggle
1831
+ }
1832
+ ),
1833
+ features.image !== false && /* @__PURE__ */ jsx(
1834
+ ToolbarButton,
1835
+ {
1836
+ icon: /* @__PURE__ */ jsx(ImageIcon, {}),
1837
+ label: "Insert Image",
1838
+ onClick: handleImageInsert
1839
+ }
1840
+ ),
1841
+ features.table !== false && /* @__PURE__ */ jsxs("div", { ref: tableButtonRef, className: "trance-table-dropdown-wrapper", children: [
1842
+ /* @__PURE__ */ jsx(
1843
+ ToolbarButton,
1844
+ {
1845
+ icon: /* @__PURE__ */ jsx(TableIcon, {}),
1846
+ label: "Insert Table",
1847
+ onClick: () => setShowTablePicker(!showTablePicker),
1848
+ active: showTablePicker
1849
+ }
1850
+ ),
1851
+ showTablePicker && /* @__PURE__ */ jsx(
1852
+ TableSizePicker,
1853
+ {
1854
+ triggerRef: tableButtonRef,
1855
+ onSelect: handleTableInsert,
1856
+ onCancel: () => setShowTablePicker(false)
1857
+ }
1858
+ )
1859
+ ] }),
1860
+ features.horizontalRule !== false && /* @__PURE__ */ jsx(
1861
+ ToolbarButton,
1862
+ {
1863
+ icon: /* @__PURE__ */ jsx(HorizontalRuleIcon, {}),
1864
+ label: "Horizontal Rule",
1865
+ onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, void 0)
1866
+ }
1867
+ ),
1868
+ features.import !== false && /* @__PURE__ */ jsx(
1869
+ ToolbarButton,
1870
+ {
1871
+ icon: /* @__PURE__ */ jsx(ImportIcon, {}),
1872
+ label: "Import Document",
1873
+ onClick: handleImport
1874
+ }
1875
+ ),
1876
+ /* @__PURE__ */ jsx(ToolbarSeparator, {}),
1877
+ features.textAlign !== false && /* @__PURE__ */ jsxs(Fragment, { children: [
1878
+ /* @__PURE__ */ jsx(
1879
+ ToolbarButton,
1880
+ {
1881
+ icon: /* @__PURE__ */ jsx(AlignLeftIcon2, {}),
1882
+ label: "Align Left",
1883
+ onClick: () => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "left")
1884
+ }
1885
+ ),
1886
+ /* @__PURE__ */ jsx(
1887
+ ToolbarButton,
1888
+ {
1889
+ icon: /* @__PURE__ */ jsx(AlignCenterIcon2, {}),
1890
+ label: "Align Center",
1891
+ onClick: () => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "center")
1892
+ }
1893
+ ),
1894
+ /* @__PURE__ */ jsx(
1895
+ ToolbarButton,
1896
+ {
1897
+ icon: /* @__PURE__ */ jsx(AlignRightIcon2, {}),
1898
+ label: "Align Right",
1899
+ onClick: () => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "right")
1900
+ }
1901
+ ),
1902
+ /* @__PURE__ */ jsx(
1903
+ ToolbarButton,
1904
+ {
1905
+ icon: /* @__PURE__ */ jsx(AlignJustifyIcon, {}),
1906
+ label: "Justify",
1907
+ onClick: () => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "justify")
1908
+ }
1909
+ )
1910
+ ] })
1911
+ ] });
1912
+ }
1913
+ function HtmlSerializationPlugin({
1914
+ onChange,
1915
+ debounceMs = 300
1916
+ }) {
1917
+ const [editor] = useLexicalComposerContext();
1918
+ const timerRef = useRef(null);
1919
+ const handleChange = useCallback(
1920
+ (editorState) => {
1921
+ if (!onChange) return;
1922
+ if (timerRef.current) {
1923
+ clearTimeout(timerRef.current);
1924
+ }
1925
+ timerRef.current = setTimeout(() => {
1926
+ editorState.read(() => {
1927
+ const html = $generateHtmlFromNodes(editor, null);
1928
+ const json = editorState.toJSON();
1929
+ onChange({ html, json });
1930
+ });
1931
+ }, debounceMs);
1932
+ },
1933
+ [editor, onChange, debounceMs]
1934
+ );
1935
+ useEffect(() => {
1936
+ return editor.registerUpdateListener(({ editorState }) => {
1937
+ handleChange(editorState);
1938
+ });
1939
+ }, [editor, handleChange]);
1940
+ useEffect(() => {
1941
+ return () => {
1942
+ if (timerRef.current) {
1943
+ clearTimeout(timerRef.current);
1944
+ }
1945
+ };
1946
+ }, []);
1947
+ return null;
1948
+ }
1949
+ function MaxLengthPlugin({ maxLength }) {
1950
+ const [editor] = useLexicalComposerContext();
1951
+ useEffect(() => {
1952
+ return editor.registerNodeTransform(RootNode, (rootNode) => {
1953
+ const text = rootNode.getTextContent();
1954
+ if (text.length <= maxLength) return;
1955
+ editor.update(() => {
1956
+ text.slice(0, maxLength);
1957
+ const root = $getRoot();
1958
+ const allTextContent = root.getTextContent();
1959
+ if (allTextContent.length > maxLength) ;
1960
+ });
1961
+ });
1962
+ }, [editor, maxLength]);
1963
+ useEffect(() => {
1964
+ return editor.registerNodeTransform(TextNode, (textNode) => {
1965
+ const root = $getRoot();
1966
+ const totalLength = root.getTextContent().length;
1967
+ if (totalLength > maxLength) {
1968
+ const excess = totalLength - maxLength;
1969
+ const currentText = textNode.getTextContent();
1970
+ if (currentText.length > excess) {
1971
+ textNode.setTextContent(currentText.slice(0, currentText.length - excess));
1972
+ }
1973
+ }
1974
+ });
1975
+ }, [editor, maxLength]);
1976
+ return null;
1977
+ }
1978
+ function EditorInner({
1979
+ placeholder,
1980
+ onChange,
1981
+ onBlur,
1982
+ onFocus,
1983
+ features,
1984
+ onImageUpload,
1985
+ autoFocus,
1986
+ maxLength,
1987
+ debounceMs,
1988
+ editorRef
1989
+ }) {
1990
+ const [editor] = useLexicalComposerContext();
1991
+ const resolvedFeatures = {
1992
+ bold: true,
1993
+ italic: true,
1994
+ underline: true,
1995
+ strikethrough: true,
1996
+ code: true,
1997
+ link: true,
1998
+ orderedList: true,
1999
+ unorderedList: true,
2000
+ checkList: true,
2001
+ blockquote: true,
2002
+ codeBlock: true,
2003
+ image: true,
2004
+ table: true,
2005
+ horizontalRule: true,
2006
+ heading: true,
2007
+ textAlign: true,
2008
+ superscript: true,
2009
+ subscript: true,
2010
+ highlight: true,
2011
+ import: true,
2012
+ ...features
2013
+ };
2014
+ const backgroundLayerRef = useRef(null);
2015
+ useImperativeHandle(
2016
+ editorRef,
2017
+ () => ({
2018
+ getHtml: () => {
2019
+ let html = "";
2020
+ editor.read(() => {
2021
+ html = $generateHtmlFromNodes(editor, null);
2022
+ });
2023
+ return html;
2024
+ },
2025
+ getJson: () => {
2026
+ return editor.getEditorState().toJSON();
2027
+ },
2028
+ setHtml: (html) => {
2029
+ editor.update(() => {
2030
+ const parser = new DOMParser();
2031
+ const dom = parser.parseFromString(html, "text/html");
2032
+ const nodes = $generateNodesFromDOM(editor, dom);
2033
+ const root = $getRoot();
2034
+ root.clear();
2035
+ root.append(...nodes);
2036
+ });
2037
+ },
2038
+ setJson: (json) => {
2039
+ const editorState = editor.parseEditorState(json);
2040
+ editor.setEditorState(editorState);
2041
+ },
2042
+ focus: () => {
2043
+ editor.focus();
2044
+ },
2045
+ clear: () => {
2046
+ editor.update(() => {
2047
+ const root = $getRoot();
2048
+ root.clear();
2049
+ });
2050
+ },
2051
+ getLexicalEditor: () => editor
2052
+ }),
2053
+ [editor]
2054
+ );
2055
+ return /* @__PURE__ */ jsxs(ImageBackgroundContext.Provider, { value: backgroundLayerRef, children: [
2056
+ /* @__PURE__ */ jsx(
2057
+ "div",
2058
+ {
2059
+ ref: backgroundLayerRef,
2060
+ className: "trance-editor-background-layer",
2061
+ "aria-hidden": "true"
2062
+ }
2063
+ ),
2064
+ /* @__PURE__ */ jsx(Toolbar, { features: resolvedFeatures, onImageUpload }),
2065
+ /* @__PURE__ */ jsx("div", { className: "trance-editor-content", children: /* @__PURE__ */ jsx(
2066
+ RichTextPlugin,
2067
+ {
2068
+ contentEditable: /* @__PURE__ */ jsx(
2069
+ ContentEditable,
2070
+ {
2071
+ className: "trance-content-editable",
2072
+ "aria-placeholder": placeholder || "Start writing...",
2073
+ placeholder: /* @__PURE__ */ jsx("div", { className: "trance-editor-placeholder", children: placeholder || "Start writing..." }),
2074
+ onBlur,
2075
+ onFocus
2076
+ }
2077
+ ),
2078
+ ErrorBoundary: LexicalErrorBoundary
2079
+ }
2080
+ ) }),
2081
+ /* @__PURE__ */ jsx(HistoryPlugin, {}),
2082
+ autoFocus && /* @__PURE__ */ jsx(AutoFocusPlugin, {}),
2083
+ /* @__PURE__ */ jsx(ListPlugin, {}),
2084
+ /* @__PURE__ */ jsx(LinkPlugin, {}),
2085
+ /* @__PURE__ */ jsx(CheckListPlugin, {}),
2086
+ /* @__PURE__ */ jsx(TabIndentationPlugin, {}),
2087
+ /* @__PURE__ */ jsx(TablePlugin, {}),
2088
+ /* @__PURE__ */ jsx(MarkdownShortcutPlugin, { transformers: TRANSFORMERS }),
2089
+ /* @__PURE__ */ jsx(HtmlSerializationPlugin, { onChange, debounceMs }),
2090
+ /* @__PURE__ */ jsx(ImagesPlugin, { onImageUpload }),
2091
+ /* @__PURE__ */ jsx(HorizontalRulePlugin, {}),
2092
+ maxLength !== void 0 && /* @__PURE__ */ jsx(MaxLengthPlugin, { maxLength })
2093
+ ] });
2094
+ }
2095
+ var TranceEditor = forwardRef(
2096
+ function TranceEditor2(props, ref) {
2097
+ const {
2098
+ initialHtml,
2099
+ initialJson,
2100
+ theme = "light",
2101
+ className = "",
2102
+ editable = true,
2103
+ ...rest
2104
+ } = props;
2105
+ const initialConfig = {
2106
+ namespace: "TranceEditor",
2107
+ theme: tranceLexicalTheme,
2108
+ nodes: TRANCE_NODES,
2109
+ editable,
2110
+ onError: (error) => {
2111
+ console.error("[TranceEditor]", error);
2112
+ },
2113
+ editorState: initialJson ? JSON.stringify(initialJson) : initialHtml ? (editor) => {
2114
+ editor.update(() => {
2115
+ const parser = new DOMParser();
2116
+ const dom = parser.parseFromString(initialHtml, "text/html");
2117
+ const nodes = $generateNodesFromDOM(editor, dom);
2118
+ const root = $getRoot();
2119
+ root.select();
2120
+ $insertNodes(nodes);
2121
+ });
2122
+ } : void 0
2123
+ };
2124
+ return /* @__PURE__ */ jsx(
2125
+ "div",
2126
+ {
2127
+ className: `trance-editor-wrapper ${className}`.trim(),
2128
+ "data-trance-theme": theme,
2129
+ children: /* @__PURE__ */ jsx(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx(EditorInner, { ...rest, editorRef: ref }) })
2130
+ }
2131
+ );
2132
+ }
2133
+ );
2134
+ function serializeToHtml(editor) {
2135
+ let html = "";
2136
+ editor.read(() => {
2137
+ html = $generateHtmlFromNodes(editor, null);
2138
+ });
2139
+ return html;
2140
+ }
2141
+ function deserializeFromHtml(editor, html) {
2142
+ editor.update(() => {
2143
+ const parser = new DOMParser();
2144
+ const dom = parser.parseFromString(html, "text/html");
2145
+ const nodes = $generateNodesFromDOM(editor, dom);
2146
+ const root = $getRoot();
2147
+ root.clear();
2148
+ $insertNodes(nodes);
2149
+ });
2150
+ }
2151
+ function serializeToJson(editor) {
2152
+ return editor.getEditorState().toJSON();
2153
+ }
2154
+ function deserializeFromJson(editor, json) {
2155
+ const editorState = editor.parseEditorState(json);
2156
+ editor.setEditorState(editorState);
2157
+ }
2158
+ function convertJsonToHtml(json) {
2159
+ const editor = createEditor({
2160
+ namespace: "TranceHeadless",
2161
+ nodes: TRANCE_NODES,
2162
+ theme: tranceLexicalTheme,
2163
+ onError: (error) => {
2164
+ throw error;
2165
+ }
2166
+ });
2167
+ const editorState = editor.parseEditorState(json);
2168
+ let html = "";
2169
+ editorState.read(() => {
2170
+ html = $generateHtmlFromNodes(editor, null);
2171
+ });
2172
+ return html;
2173
+ }
2174
+ function convertHtmlToJson(html) {
2175
+ const editor = createEditor({
2176
+ namespace: "TranceHeadless",
2177
+ nodes: TRANCE_NODES,
2178
+ theme: tranceLexicalTheme,
2179
+ onError: (error) => {
2180
+ throw error;
2181
+ }
2182
+ });
2183
+ editor.update(
2184
+ () => {
2185
+ const parser = new DOMParser();
2186
+ const dom = parser.parseFromString(html, "text/html");
2187
+ const nodes = $generateNodesFromDOM(editor, dom);
2188
+ const root = $getRoot();
2189
+ root.clear();
2190
+ $insertNodes(nodes);
2191
+ },
2192
+ { discrete: true }
2193
+ );
2194
+ return editor.getEditorState().toJSON();
2195
+ }
2196
+
2197
+ export { $createHorizontalRuleNode, $createImageNode, $isHorizontalRuleNode, $isImageNode, HorizontalRuleNode, INSERT_HORIZONTAL_RULE_COMMAND, INSERT_IMAGE_COMMAND, ImageNode, TranceEditor, convertHtmlToJson, convertJsonToHtml, deserializeFromHtml, deserializeFromJson, serializeToHtml, serializeToJson };
2198
+ //# sourceMappingURL=index.js.map
2199
+ //# sourceMappingURL=index.js.map