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