@tarviks/lexical-rich-editor 1.1.0 → 1.2.1

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 CHANGED
@@ -636,7 +636,6 @@ var init_ImageComponent = __esm({
636
636
  editor,
637
637
  buttonRef,
638
638
  imageRef,
639
- maxWidth,
640
639
  onResizeStart,
641
640
  onResizeEnd,
642
641
  captionsEnabled: !isLoadError && captionsEnabled
@@ -787,6 +786,9 @@ var init_ImageNode = __esm({
787
786
  const writable = this.getWritable();
788
787
  writable.__width = width;
789
788
  writable.__height = height;
789
+ if (typeof width === "number" && width > writable.__maxWidth) {
790
+ writable.__maxWidth = width;
791
+ }
790
792
  }
791
793
  setShowCaption(showCaption) {
792
794
  const writable = this.getWritable();
@@ -876,6 +878,7 @@ __export(InlineImageComponent_exports, {
876
878
  var imageCache2, useSuspenseImage2, LazyImage2, InlineImageComponent, InlineImageComponent_default;
877
879
  var init_InlineImageComponent = __esm({
878
880
  "src/Nodes/InlineImageComponent.tsx"() {
881
+ init_ImageResizer();
879
882
  init_InlineImage();
880
883
  init_InlineImageNode();
881
884
  imageCache2 = /* @__PURE__ */ new Set();
@@ -918,6 +921,29 @@ var init_InlineImageComponent = __esm({
918
921
  const buttonRef = React9.useRef(null);
919
922
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
920
923
  const isEditable = useLexicalEditable.useLexicalEditable();
924
+ const [isResizing, setIsResizing] = React9.useState(false);
925
+ const onResizeEnd = (nextWidth, nextHeight) => {
926
+ setTimeout(() => {
927
+ setIsResizing(false);
928
+ }, 200);
929
+ editor.update(() => {
930
+ const node = lexical.$getNodeByKey(nodeKey);
931
+ if ($isInlineImageNode(node)) {
932
+ node.setWidthAndHeight(nextWidth, nextHeight);
933
+ }
934
+ });
935
+ };
936
+ const onResizeStart = () => {
937
+ setIsResizing(true);
938
+ };
939
+ const setShowCaption = (show) => {
940
+ editor.update(() => {
941
+ const node = lexical.$getNodeByKey(nodeKey);
942
+ if ($isInlineImageNode(node)) {
943
+ node.setShowCaption(show);
944
+ }
945
+ });
946
+ };
921
947
  const $onDelete = React9.useCallback(
922
948
  (payload) => {
923
949
  const deleteSelection = lexical.$getSelection();
@@ -993,6 +1019,9 @@ var init_InlineImageComponent = __esm({
993
1019
  lexical.CLICK_COMMAND,
994
1020
  (payload) => {
995
1021
  const event = payload;
1022
+ if (isResizing) {
1023
+ return true;
1024
+ }
996
1025
  if (event.target === imageRef.current) {
997
1026
  if (event.shiftKey) {
998
1027
  setSelected(!isSelected);
@@ -1041,6 +1070,7 @@ var init_InlineImageComponent = __esm({
1041
1070
  }, [
1042
1071
  clearSelection,
1043
1072
  editor,
1073
+ isResizing,
1044
1074
  isSelected,
1045
1075
  nodeKey,
1046
1076
  $onDelete,
@@ -1048,20 +1078,35 @@ var init_InlineImageComponent = __esm({
1048
1078
  $onEscape,
1049
1079
  setSelected
1050
1080
  ]);
1051
- const draggable = isSelected && lexical.$isNodeSelection(selection);
1052
- const isFocused = isSelected && isEditable;
1053
- return /* @__PURE__ */ jsxRuntime.jsx(React9.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx("span", { draggable, children: /* @__PURE__ */ jsxRuntime.jsx(
1054
- LazyImage2,
1055
- {
1056
- className: isFocused ? `focused ${lexical.$isNodeSelection(selection) ? "draggable" : ""}` : null,
1057
- src,
1058
- altText,
1059
- imageRef,
1060
- width,
1061
- height,
1062
- position
1063
- }
1064
- ) }) });
1081
+ const draggable = isSelected && lexical.$isNodeSelection(selection) && !isResizing;
1082
+ const isFocused = (isSelected || isResizing) && isEditable;
1083
+ return /* @__PURE__ */ jsxRuntime.jsx(React9.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1084
+ /* @__PURE__ */ jsxRuntime.jsx("span", { draggable, children: /* @__PURE__ */ jsxRuntime.jsx(
1085
+ LazyImage2,
1086
+ {
1087
+ className: isFocused ? `focused ${lexical.$isNodeSelection(selection) ? "draggable" : ""}` : null,
1088
+ src,
1089
+ altText,
1090
+ imageRef,
1091
+ width,
1092
+ height,
1093
+ position
1094
+ }
1095
+ ) }),
1096
+ isFocused && lexical.$isNodeSelection(selection) && /* @__PURE__ */ jsxRuntime.jsx(
1097
+ ImageResizer_default,
1098
+ {
1099
+ showCaption,
1100
+ setShowCaption,
1101
+ editor,
1102
+ buttonRef,
1103
+ imageRef,
1104
+ onResizeStart,
1105
+ onResizeEnd,
1106
+ captionsEnabled: false
1107
+ }
1108
+ )
1109
+ ] }) });
1065
1110
  };
1066
1111
  InlineImageComponent_default = InlineImageComponent;
1067
1112
  }
@@ -1249,6 +1294,46 @@ var ContentEditorLevel = /* @__PURE__ */ ((ContentEditorLevel2) => {
1249
1294
  ContentEditorLevel2["Pro"] = "pro";
1250
1295
  return ContentEditorLevel2;
1251
1296
  })(ContentEditorLevel || {});
1297
+
1298
+ // src/Types/PageSetup.ts
1299
+ var DEFAULT_PAGE_SETUP = {
1300
+ size: "pageless",
1301
+ orientation: "portrait",
1302
+ margin: "normal"
1303
+ };
1304
+ var PAGE_SIZE_OPTIONS = [
1305
+ { key: "a4", label: 'A4 (8.27" x 11.69")', widthIn: 8.27, heightIn: 11.69 },
1306
+ { key: "letter", label: 'Letter (8.5" x 11")', widthIn: 8.5, heightIn: 11 },
1307
+ { key: "legal", label: 'Legal (8.5" x 14")', widthIn: 8.5, heightIn: 14 },
1308
+ { key: "tabloid", label: 'Tabloid (11" x 17")', widthIn: 11, heightIn: 17 },
1309
+ { key: "a3", label: 'A3 (11.69" x 16.54")', widthIn: 11.69, heightIn: 16.54 },
1310
+ { key: "a5", label: 'A5 (5.83" x 8.27")', widthIn: 5.83, heightIn: 8.27 },
1311
+ { key: "b4", label: 'B4 (9.84" x 13.90")', widthIn: 9.84, heightIn: 13.9 },
1312
+ { key: "b5", label: 'B5 (6.93" x 9.84")', widthIn: 6.93, heightIn: 9.84 },
1313
+ { key: "statement", label: 'Statement (5.5" x 8.5")', widthIn: 5.5, heightIn: 8.5 },
1314
+ { key: "executive", label: 'Executive (7.25" x 10.5")', widthIn: 7.25, heightIn: 10.5 },
1315
+ { key: "folio", label: 'Folio (8.5" x 13")', widthIn: 8.5, heightIn: 13 }
1316
+ ];
1317
+ var MARGIN_OPTIONS = [
1318
+ { key: "narrow", label: 'Narrow (0.25")', valueIn: 0.25 },
1319
+ { key: "normal", label: 'Normal (0.4")', valueIn: 0.4 },
1320
+ { key: "moderate", label: 'Moderate (0.75")', valueIn: 0.75 },
1321
+ { key: "wide", label: 'Wide (1")', valueIn: 1 }
1322
+ ];
1323
+ var CSS_PX_PER_INCH = 96;
1324
+ function resolvePageCanvasMetrics(value) {
1325
+ const margin = MARGIN_OPTIONS.find((o) => o.key === value.margin) ?? MARGIN_OPTIONS[1];
1326
+ if (value.size === "pageless") {
1327
+ return { widthPx: void 0, paddingPx: 20 };
1328
+ }
1329
+ const size = PAGE_SIZE_OPTIONS.find((o) => o.key === value.size);
1330
+ if (!size) return { widthPx: void 0, paddingPx: 20 };
1331
+ const widthIn = value.orientation === "landscape" ? size.heightIn : size.widthIn;
1332
+ return {
1333
+ widthPx: Math.round(widthIn * CSS_PX_PER_INCH),
1334
+ paddingPx: Math.round(margin.valueIn * CSS_PX_PER_INCH)
1335
+ };
1336
+ }
1252
1337
  var AutocompleteNode = class _AutocompleteNode extends lexical.TextNode {
1253
1338
  static getType() {
1254
1339
  return "autocomplete";
@@ -2487,99 +2572,329 @@ function CharacterStylesPopupPlugin(props) {
2487
2572
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
2488
2573
  return useCharacterStylesPopup(editor, props);
2489
2574
  }
2490
- var CustomOnChangePlugin = ({ value, onChange }) => {
2491
- const [editor] = LexicalComposerContext.useLexicalComposerContext();
2492
- const initializedRef = React9.useRef(false);
2493
- const onChangeRef = React9.useRef(onChange);
2494
- onChangeRef.current = onChange;
2495
- React9.useEffect(() => {
2496
- if (!value || initializedRef.current) return;
2497
- initializedRef.current = true;
2498
- editor.update(() => {
2499
- const root = lexical.$getRoot();
2500
- root.clear();
2501
- const parser = new DOMParser();
2502
- const dom = parser.parseFromString(value, "text/html");
2503
- const nodes = html.$generateNodesFromDOM(editor, dom);
2504
- root.append(...nodes);
2505
- lexical.$setSelection(null);
2506
- });
2507
- }, [editor, value]);
2508
- const handleChange = React9.useCallback((editorState) => {
2509
- editorState.read(() => {
2510
- onChangeRef.current(html.$generateHtmlFromNodes(editor));
2511
- });
2512
- }, [editor]);
2513
- return /* @__PURE__ */ jsxRuntime.jsx(
2514
- LexicalOnChangePlugin.OnChangePlugin,
2515
- {
2516
- onChange: handleChange,
2517
- ignoreSelectionChange: true
2518
- }
2519
- );
2520
- };
2521
- var getSelectedNode2 = (selection$1) => {
2522
- const anchor = selection$1.anchor;
2523
- const anchorNode = selection$1.anchor.getNode();
2524
- const focus = selection$1.focus;
2525
- const focusNode = selection$1.focus.getNode();
2526
- if (anchorNode === focusNode) {
2527
- return anchorNode;
2528
- }
2529
- const isBackward = selection$1.isBackward();
2530
- if (isBackward) {
2531
- return selection.$isAtNodeEnd(focus) ? anchorNode : focusNode;
2532
- } else {
2533
- return selection.$isAtNodeEnd(anchor) ? anchorNode : focusNode;
2575
+
2576
+ // src/Utils/Sanitize.ts
2577
+ var DROP_ENTIRELY = /* @__PURE__ */ new Set([
2578
+ "script",
2579
+ "noscript",
2580
+ "style",
2581
+ "object",
2582
+ "embed",
2583
+ "form",
2584
+ "input",
2585
+ "button",
2586
+ "select",
2587
+ "textarea",
2588
+ "meta",
2589
+ "link",
2590
+ "base"
2591
+ ]);
2592
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
2593
+ // Block
2594
+ "p",
2595
+ "h1",
2596
+ "h2",
2597
+ "h3",
2598
+ "h4",
2599
+ "h5",
2600
+ "h6",
2601
+ "ul",
2602
+ "ol",
2603
+ "li",
2604
+ "blockquote",
2605
+ "pre",
2606
+ "div",
2607
+ "table",
2608
+ "thead",
2609
+ "tbody",
2610
+ "tfoot",
2611
+ "tr",
2612
+ "td",
2613
+ "th",
2614
+ // Inline
2615
+ "span",
2616
+ "a",
2617
+ "strong",
2618
+ "b",
2619
+ "em",
2620
+ "i",
2621
+ "u",
2622
+ "s",
2623
+ "del",
2624
+ "strike",
2625
+ "sub",
2626
+ "sup",
2627
+ "mark",
2628
+ "code",
2629
+ "br",
2630
+ "hr",
2631
+ // Media / embeds
2632
+ "img",
2633
+ "iframe"
2634
+ ]);
2635
+ var ALLOWED_ATTRS = /* @__PURE__ */ new Set([
2636
+ // Presentation
2637
+ "class",
2638
+ "style",
2639
+ "dir",
2640
+ "lang",
2641
+ // Anchors
2642
+ "href",
2643
+ "target",
2644
+ "rel",
2645
+ // Images
2646
+ "src",
2647
+ "alt",
2648
+ "width",
2649
+ "height",
2650
+ // Tables
2651
+ "colspan",
2652
+ "rowspan",
2653
+ // Lists — <ol type="a"> and <ol start="2">
2654
+ "start",
2655
+ "type",
2656
+ // Lexical-internal data markers
2657
+ "data-lex-block",
2658
+ "data-kind",
2659
+ "data-lexical-decorator",
2660
+ // Misc
2661
+ "title",
2662
+ "allowfullscreen"
2663
+ ]);
2664
+ var DANGEROUS_URL = /^\s*(javascript|data\s*:|vbscript)/i;
2665
+ function sanitizeElement(el) {
2666
+ for (const child of Array.from(el.children)) {
2667
+ sanitizeElement(child);
2534
2668
  }
2535
- };
2536
- var VERTICAL_GAP = 10;
2537
- var HORIZONTAL_OFFSET = 5;
2538
- var setFloatingElemPositionForLinkEditor = (targetRect, floatingElem, anchorElem, verticalGap = VERTICAL_GAP, horizontalOffset = HORIZONTAL_OFFSET) => {
2539
- const scrollerElem = anchorElem.parentElement;
2540
- if (targetRect === null || !scrollerElem) {
2541
- floatingElem.style.opacity = "0";
2542
- floatingElem.style.transform = "translate(-10000px, -10000px)";
2669
+ const tag = el.tagName.toLowerCase();
2670
+ if (DROP_ENTIRELY.has(tag)) {
2671
+ el.parentNode?.removeChild(el);
2543
2672
  return;
2544
2673
  }
2545
- const floatingElemRect = floatingElem.getBoundingClientRect();
2546
- const anchorElementRect = anchorElem.getBoundingClientRect();
2547
- const editorScrollerRect = scrollerElem.getBoundingClientRect();
2548
- let top = targetRect.top - verticalGap;
2549
- let left = targetRect.left - horizontalOffset;
2550
- if (top < editorScrollerRect.top) {
2551
- top += floatingElemRect.height + targetRect.height + verticalGap * 2;
2552
- }
2553
- if (left + floatingElemRect.width > editorScrollerRect.right) {
2554
- left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;
2674
+ if (tag === "iframe") {
2675
+ const src = el.getAttribute("src") ?? "";
2676
+ const isYouTube = /^\s*https:\/\/(www\.)?(youtube\.com|youtube-nocookie\.com)\/embed\/[^?&/]+/i.test(src);
2677
+ if (!isYouTube) {
2678
+ el.parentNode?.removeChild(el);
2679
+ return;
2680
+ }
2555
2681
  }
2556
- top -= anchorElementRect.top;
2557
- left -= anchorElementRect.left;
2558
- floatingElem.style.opacity = "1";
2559
- floatingElem.style.transform = `translate(${left}px, ${top}px)`;
2560
- };
2561
- var SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set([
2562
- "http:",
2563
- "https:",
2564
- "mailto:",
2565
- "sms:",
2566
- "tel:"
2567
- ]);
2568
- var sanitizeUrl = (url) => {
2569
- try {
2570
- const parsedUrl = new URL(url);
2571
- if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) {
2572
- return "about:blank";
2682
+ if (!ALLOWED_TAGS.has(tag)) {
2683
+ while (el.firstChild) {
2684
+ el.parentNode?.insertBefore(el.firstChild, el);
2573
2685
  }
2574
- } catch {
2575
- return url;
2686
+ el.parentNode?.removeChild(el);
2687
+ return;
2576
2688
  }
2577
- return url;
2578
- };
2579
- var preventDefault = (event) => {
2580
- event.preventDefault();
2581
- };
2582
- var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMode, setIsLinkEditMode }) => {
2689
+ for (const { name, value } of Array.from(el.attributes)) {
2690
+ const lname = name.toLowerCase();
2691
+ if (!ALLOWED_ATTRS.has(lname)) {
2692
+ el.removeAttribute(name);
2693
+ continue;
2694
+ }
2695
+ if (lname === "href" || lname === "src") {
2696
+ if (DANGEROUS_URL.test(value)) {
2697
+ el.removeAttribute(name);
2698
+ }
2699
+ }
2700
+ if (lname === "style") {
2701
+ const safe = value.replace(/expression\s*\(/gi, "(").replace(/url\s*\(\s*['"]?\s*javascript:/gi, "url(");
2702
+ el.setAttribute("style", safe);
2703
+ }
2704
+ }
2705
+ if (tag === "a" && (el.getAttribute("target") || "").toLowerCase() === "_blank") {
2706
+ const rel = (el.getAttribute("rel") || "").trim();
2707
+ const tokens = new Set(
2708
+ rel.split(/\s+/).filter(Boolean).map((t) => t.toLowerCase())
2709
+ );
2710
+ tokens.add("noopener");
2711
+ tokens.add("noreferrer");
2712
+ el.setAttribute("rel", Array.from(tokens).join(" "));
2713
+ }
2714
+ }
2715
+ function sanitizeHtml(html) {
2716
+ if (!html || typeof html !== "string") return "";
2717
+ const doc = new DOMParser().parseFromString(html, "text/html");
2718
+ for (const child of Array.from(doc.body.children)) {
2719
+ sanitizeElement(child);
2720
+ }
2721
+ return doc.body.innerHTML;
2722
+ }
2723
+ function postProcessOutput(html) {
2724
+ if (!html) return html;
2725
+ const doc = new DOMParser().parseFromString(html, "text/html");
2726
+ for (const inner of Array.from(doc.querySelectorAll("b > strong"))) {
2727
+ const outer = inner.parentElement;
2728
+ _mergeAttributes(inner, outer);
2729
+ while (inner.firstChild) outer.insertBefore(inner.firstChild, inner);
2730
+ outer.removeChild(inner);
2731
+ }
2732
+ for (const inner of Array.from(doc.querySelectorAll("i > em"))) {
2733
+ const outer = inner.parentElement;
2734
+ _mergeAttributes(inner, outer);
2735
+ while (inner.firstChild) outer.insertBefore(inner.firstChild, inner);
2736
+ outer.removeChild(inner);
2737
+ }
2738
+ for (const p of Array.from(doc.querySelectorAll("p"))) {
2739
+ const style = p.style;
2740
+ const hasMargin = !!(style.margin || style.marginTop || style.marginRight || style.marginBottom || style.marginLeft);
2741
+ if (!hasMargin) style.margin = "0";
2742
+ if (!style.lineHeight) style.lineHeight = "1.25";
2743
+ }
2744
+ return doc.body.innerHTML;
2745
+ }
2746
+ function _mergeAttributes(src, dst) {
2747
+ for (const { name, value } of Array.from(src.attributes)) {
2748
+ if (name === "style") {
2749
+ const prev = dst.getAttribute("style") || "";
2750
+ dst.setAttribute("style", prev ? `${prev}; ${value}` : value);
2751
+ } else if (name === "class") {
2752
+ const prev = dst.getAttribute("class") || "";
2753
+ dst.setAttribute("class", prev ? `${prev} ${value}` : value);
2754
+ }
2755
+ }
2756
+ }
2757
+ var BLOCK_TAGS = /* @__PURE__ */ new Set([
2758
+ "p",
2759
+ "h1",
2760
+ "h2",
2761
+ "h3",
2762
+ "h4",
2763
+ "h5",
2764
+ "h6",
2765
+ "div",
2766
+ "ul",
2767
+ "ol",
2768
+ "li",
2769
+ "table",
2770
+ "blockquote",
2771
+ "pre",
2772
+ "hr"
2773
+ ]);
2774
+ function normalizeToBlockHtml(html) {
2775
+ if (!html) return "";
2776
+ const doc = new DOMParser().parseFromString(html, "text/html");
2777
+ const body = doc.body;
2778
+ const childNodes = Array.from(body.childNodes);
2779
+ const needsWrap = childNodes.some((node) => {
2780
+ if (node.nodeType === Node.TEXT_NODE) return !!node.nodeValue?.trim();
2781
+ if (node.nodeType === Node.ELEMENT_NODE)
2782
+ return !BLOCK_TAGS.has(node.tagName.toLowerCase());
2783
+ return false;
2784
+ });
2785
+ if (!needsWrap) return html;
2786
+ while (body.firstChild) body.removeChild(body.firstChild);
2787
+ let pendingP = null;
2788
+ for (const node of childNodes) {
2789
+ const isBlock = node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
2790
+ const isWhitespace = node.nodeType === Node.TEXT_NODE && !node.nodeValue?.trim();
2791
+ if (isBlock) {
2792
+ if (pendingP) {
2793
+ body.appendChild(pendingP);
2794
+ pendingP = null;
2795
+ }
2796
+ body.appendChild(node);
2797
+ } else if (!isWhitespace) {
2798
+ if (!pendingP) pendingP = doc.createElement("p");
2799
+ pendingP.appendChild(node);
2800
+ }
2801
+ }
2802
+ if (pendingP) body.appendChild(pendingP);
2803
+ return body.innerHTML;
2804
+ }
2805
+ var CustomOnChangePlugin = ({ value, onChange }) => {
2806
+ const [editor] = LexicalComposerContext.useLexicalComposerContext();
2807
+ const initializedRef = React9.useRef(false);
2808
+ const onChangeRef = React9.useRef(onChange);
2809
+ onChangeRef.current = onChange;
2810
+ React9.useEffect(() => {
2811
+ if (!value || initializedRef.current) return;
2812
+ initializedRef.current = true;
2813
+ editor.update(() => {
2814
+ const root = lexical.$getRoot();
2815
+ root.clear();
2816
+ const parser = new DOMParser();
2817
+ const dom = parser.parseFromString(value, "text/html");
2818
+ const nodes = html.$generateNodesFromDOM(editor, dom);
2819
+ root.append(...nodes);
2820
+ lexical.$setSelection(null);
2821
+ });
2822
+ }, [editor, value]);
2823
+ const handleChange = React9.useCallback((editorState) => {
2824
+ editorState.read(() => {
2825
+ onChangeRef.current(postProcessOutput(html.$generateHtmlFromNodes(editor)));
2826
+ });
2827
+ }, [editor]);
2828
+ return /* @__PURE__ */ jsxRuntime.jsx(
2829
+ LexicalOnChangePlugin.OnChangePlugin,
2830
+ {
2831
+ onChange: handleChange,
2832
+ ignoreSelectionChange: true
2833
+ }
2834
+ );
2835
+ };
2836
+ var getSelectedNode2 = (selection$1) => {
2837
+ const anchor = selection$1.anchor;
2838
+ const anchorNode = selection$1.anchor.getNode();
2839
+ const focus = selection$1.focus;
2840
+ const focusNode = selection$1.focus.getNode();
2841
+ if (anchorNode === focusNode) {
2842
+ return anchorNode;
2843
+ }
2844
+ const isBackward = selection$1.isBackward();
2845
+ if (isBackward) {
2846
+ return selection.$isAtNodeEnd(focus) ? anchorNode : focusNode;
2847
+ } else {
2848
+ return selection.$isAtNodeEnd(anchor) ? anchorNode : focusNode;
2849
+ }
2850
+ };
2851
+ var VERTICAL_GAP = 10;
2852
+ var HORIZONTAL_OFFSET = 5;
2853
+ var setFloatingElemPositionForLinkEditor = (targetRect, floatingElem, anchorElem, verticalGap = VERTICAL_GAP, horizontalOffset = HORIZONTAL_OFFSET) => {
2854
+ const scrollerElem = anchorElem.parentElement;
2855
+ if (targetRect === null || !scrollerElem) {
2856
+ floatingElem.style.opacity = "0";
2857
+ floatingElem.style.transform = "translate(-10000px, -10000px)";
2858
+ return;
2859
+ }
2860
+ const floatingElemRect = floatingElem.getBoundingClientRect();
2861
+ const anchorElementRect = anchorElem.getBoundingClientRect();
2862
+ const editorScrollerRect = scrollerElem.getBoundingClientRect();
2863
+ let top = targetRect.top - verticalGap;
2864
+ let left = targetRect.left - horizontalOffset;
2865
+ if (top < editorScrollerRect.top) {
2866
+ top += floatingElemRect.height + targetRect.height + verticalGap * 2;
2867
+ }
2868
+ if (left + floatingElemRect.width > editorScrollerRect.right) {
2869
+ left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;
2870
+ }
2871
+ top -= anchorElementRect.top;
2872
+ left -= anchorElementRect.left;
2873
+ floatingElem.style.opacity = "1";
2874
+ floatingElem.style.transform = `translate(${left}px, ${top}px)`;
2875
+ };
2876
+ var SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set([
2877
+ "http:",
2878
+ "https:",
2879
+ "mailto:",
2880
+ "sms:",
2881
+ "tel:"
2882
+ ]);
2883
+ var sanitizeUrl = (url) => {
2884
+ try {
2885
+ const parsedUrl = new URL(url);
2886
+ if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) {
2887
+ return "about:blank";
2888
+ }
2889
+ } catch {
2890
+ return url;
2891
+ }
2892
+ return url;
2893
+ };
2894
+ var preventDefault = (event) => {
2895
+ event.preventDefault();
2896
+ };
2897
+ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMode, setIsLinkEditMode }) => {
2583
2898
  const [editedLinkUrl, setEditedLinkUrl] = React9.useState("https://");
2584
2899
  const [lastSelection, setLastSelection] = React9.useState(null);
2585
2900
  const [linkUrl, setLinkUrl] = React9.useState("");
@@ -2608,7 +2923,7 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2608
2923
  return;
2609
2924
  }
2610
2925
  const rootElement = editor.getRootElement();
2611
- if (selection !== null && nativeSelection !== null && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) && editor.isEditable()) {
2926
+ if (isLink && selection !== null && nativeSelection !== null && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) && editor.isEditable()) {
2612
2927
  const domRect = nativeSelection.focusNode?.parentElement?.getBoundingClientRect();
2613
2928
  if (domRect) {
2614
2929
  domRect.y += 40;
@@ -2624,7 +2939,7 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2624
2939
  setLinkUrl("");
2625
2940
  }
2626
2941
  return true;
2627
- }, [anchorElem, editor, setIsLinkEditMode, isLinkEditMode, linkUrl]);
2942
+ }, [anchorElem, editor, isLink, setIsLinkEditMode, isLinkEditMode, linkUrl]);
2628
2943
  React9.useEffect(() => {
2629
2944
  const scrollerElem = anchorElem.parentElement;
2630
2945
  const update = () => {
@@ -2722,6 +3037,7 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2722
3037
  {
2723
3038
  ref: inputRef,
2724
3039
  className: "link-input",
3040
+ placeholder: "https://",
2725
3041
  value: editedLinkUrl,
2726
3042
  onChange: (event) => {
2727
3043
  setEditedLinkUrl(event.target.value);
@@ -2731,27 +3047,31 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2731
3047
  }
2732
3048
  }
2733
3049
  ),
2734
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3050
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "link-actions", children: [
2735
3051
  /* @__PURE__ */ jsxRuntime.jsx(
2736
- "div",
3052
+ "button",
2737
3053
  {
2738
- className: "link-cancel",
2739
- role: "button",
2740
- tabIndex: 0,
3054
+ type: "button",
3055
+ className: "link-icon-btn link-cancel",
3056
+ title: "Cancel",
3057
+ "aria-label": "Cancel",
2741
3058
  onMouseDown: preventDefault,
2742
3059
  onClick: () => {
2743
3060
  setIsLinkEditMode(false);
2744
- }
3061
+ },
3062
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Dismiss16Regular, {})
2745
3063
  }
2746
3064
  ),
2747
3065
  /* @__PURE__ */ jsxRuntime.jsx(
2748
- "div",
3066
+ "button",
2749
3067
  {
2750
- className: "link-confirm",
2751
- role: "button",
2752
- tabIndex: 0,
3068
+ type: "button",
3069
+ className: "link-icon-btn link-confirm",
3070
+ title: "Confirm",
3071
+ "aria-label": "Confirm",
2753
3072
  onMouseDown: preventDefault,
2754
- onClick: handleLinkSubmission
3073
+ onClick: handleLinkSubmission,
3074
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Checkmark16Regular, {})
2755
3075
  }
2756
3076
  )
2757
3077
  ] })
@@ -2765,32 +3085,38 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2765
3085
  children: linkUrl
2766
3086
  }
2767
3087
  ),
2768
- /* @__PURE__ */ jsxRuntime.jsx(
2769
- "div",
2770
- {
2771
- className: "link-edit",
2772
- role: "button",
2773
- tabIndex: 0,
2774
- onMouseDown: preventDefault,
2775
- onClick: (event) => {
2776
- event.preventDefault();
2777
- setEditedLinkUrl(linkUrl);
2778
- setIsLinkEditMode(true);
3088
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "link-actions", children: [
3089
+ /* @__PURE__ */ jsxRuntime.jsx(
3090
+ "button",
3091
+ {
3092
+ type: "button",
3093
+ className: "link-icon-btn link-edit",
3094
+ title: "Edit link",
3095
+ "aria-label": "Edit link",
3096
+ onMouseDown: preventDefault,
3097
+ onClick: (event) => {
3098
+ event.preventDefault();
3099
+ setEditedLinkUrl(linkUrl);
3100
+ setIsLinkEditMode(true);
3101
+ },
3102
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Edit16Regular, {})
2779
3103
  }
2780
- }
2781
- ),
2782
- /* @__PURE__ */ jsxRuntime.jsx(
2783
- "div",
2784
- {
2785
- className: "link-trash",
2786
- role: "button",
2787
- tabIndex: 0,
2788
- onMouseDown: preventDefault,
2789
- onClick: () => {
2790
- editor.dispatchCommand(link.TOGGLE_LINK_COMMAND, null);
3104
+ ),
3105
+ /* @__PURE__ */ jsxRuntime.jsx(
3106
+ "button",
3107
+ {
3108
+ type: "button",
3109
+ className: "link-icon-btn link-trash",
3110
+ title: "Remove link",
3111
+ "aria-label": "Remove link",
3112
+ onMouseDown: preventDefault,
3113
+ onClick: () => {
3114
+ editor.dispatchCommand(link.TOGGLE_LINK_COMMAND, null);
3115
+ },
3116
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Delete16Regular, {})
2791
3117
  }
2792
- }
2793
- )
3118
+ )
3119
+ ] })
2794
3120
  ] }) });
2795
3121
  };
2796
3122
  var useFloatingLinkEditorToolbar = (editor, anchorElem, isLinkEditMode, setIsLinkEditMode) => {
@@ -2891,74 +3217,28 @@ var readClipboardImageAsDataURL = async (event) => {
2891
3217
  }
2892
3218
  return null;
2893
3219
  };
2894
- var InsertImageByURL = ({
2895
- setIsOpen,
2896
- onClick,
2897
- disabled
2898
- }) => {
2899
- const [altText, setAltText] = React9.useState("");
2900
- const [src, setSrc] = React9.useState("");
2901
- const isDisabled = disabled || src === "";
2902
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 6, padding: "10px 0px 0px 0px" }, children: [
2903
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Enter URL", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
2904
- reactComponents.Input,
2905
- {
2906
- autoFocus: !disabled,
2907
- appearance: "underline",
2908
- placeholder: "Add URL",
2909
- disabled,
2910
- onChange: (_, v) => setSrc(v.value),
2911
- value: src
2912
- }
2913
- ) }),
2914
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
2915
- reactComponents.Input,
2916
- {
2917
- placeholder: "Alt text",
2918
- disabled,
2919
- onChange: (_, v) => setAltText(v.value),
2920
- value: altText
2921
- },
2922
- "alt-text-url"
2923
- ) }),
2924
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
2925
- /* @__PURE__ */ jsxRuntime.jsx(
2926
- reactComponents.Button,
2927
- {
2928
- style: { width: "150px" },
2929
- onClick: () => !disabled && onClick({ altText, src }),
2930
- disabled: isDisabled,
2931
- size: "small",
2932
- children: "Confirm"
2933
- },
2934
- "url-confirm-btn"
2935
- ),
2936
- /* @__PURE__ */ jsxRuntime.jsx(
2937
- reactComponents.Button,
2938
- {
2939
- style: { width: "150px" },
2940
- onClick: () => setIsOpen(false),
2941
- disabled,
2942
- size: "small",
2943
- children: "Cancel"
2944
- },
2945
- "file-url-cancel"
2946
- )
2947
- ] })
2948
- ] });
2949
- };
2950
3220
  var InsertImageDialog = ({
2951
3221
  activeEditor,
2952
- disabled
3222
+ disabled,
3223
+ open: externalOpen,
3224
+ onClose
2953
3225
  }) => {
2954
3226
  const [src, setSrc] = React9.useState("");
2955
3227
  const [altText, setAltText] = React9.useState("");
2956
- const [isOpen, setIsOpen] = React9.useState(false);
2957
- const [selectedValue, setSelectedValue] = React9.useState("Upload");
3228
+ const [internalOpen, setInternalOpen] = React9.useState(false);
2958
3229
  const [fileName, setFileName] = React9.useState("");
2959
3230
  const hasModifier = React9.useRef(false);
2960
3231
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
2961
- const isDisabled = disabled || src === "";
3232
+ const isControlled = externalOpen !== void 0;
3233
+ const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
3234
+ const isAddDisabled = disabled || src === "";
3235
+ const handleClose = () => {
3236
+ setSrc("");
3237
+ setAltText("");
3238
+ setFileName("");
3239
+ if (isControlled) onClose?.();
3240
+ else setInternalOpen(false);
3241
+ };
2962
3242
  React9.useEffect(() => {
2963
3243
  hasModifier.current = false;
2964
3244
  const handler = (e) => {
@@ -2970,10 +3250,7 @@ var InsertImageDialog = ({
2970
3250
  const onClick = (payload) => {
2971
3251
  if (disabled) return;
2972
3252
  activeEditor.dispatchCommand(INSERT_IMAGE_COMMAND, payload);
2973
- setIsOpen(false);
2974
- setAltText("");
2975
- setSrc("");
2976
- setFileName("");
3253
+ handleClose();
2977
3254
  };
2978
3255
  const loadImage = (event) => {
2979
3256
  if (disabled) return;
@@ -2988,118 +3265,108 @@ var InsertImageDialog = ({
2988
3265
  };
2989
3266
  reader.readAsDataURL(files[0]);
2990
3267
  };
2991
- return /* @__PURE__ */ jsxRuntime.jsxs(
2992
- reactComponents.Popover,
2993
- {
2994
- trapFocus: true,
2995
- withArrow: true,
2996
- open: disabled ? false : isOpen,
2997
- onOpenChange: (_, data) => {
2998
- if (!disabled) setIsOpen(data.open);
3268
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3269
+ !isControlled && /* @__PURE__ */ jsxRuntime.jsx(
3270
+ reactComponents.Button,
3271
+ {
3272
+ size: "small",
3273
+ title: "Add Image",
3274
+ disabled,
3275
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ImageAddRegular, { style: { color: iconColor } }),
3276
+ style: {
3277
+ background: isOpen && !disabled ? "#ebebeb" : "none",
3278
+ border: "none",
3279
+ margin: 2,
3280
+ opacity: disabled ? 0.55 : 1,
3281
+ cursor: disabled ? "not-allowed" : "pointer"
3282
+ },
3283
+ onClick: () => {
3284
+ if (disabled) return;
3285
+ setSrc("");
3286
+ setAltText("");
3287
+ setFileName("");
3288
+ setInternalOpen(true);
3289
+ }
2999
3290
  },
3000
- children: [
3001
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3002
- reactComponents.Button,
3003
- {
3004
- size: "small",
3005
- title: "Add Image",
3006
- disabled,
3007
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ImageAddRegular, { style: { color: iconColor } }),
3008
- style: {
3009
- background: isOpen && !disabled ? "#ebebeb" : "none",
3010
- border: "none",
3011
- margin: 2,
3012
- opacity: disabled ? 0.55 : 1,
3013
- cursor: disabled ? "not-allowed" : "pointer"
3014
- },
3015
- onClick: () => {
3016
- if (disabled) return;
3017
- setIsOpen((prev) => !prev);
3018
- setSrc("");
3019
- setAltText("");
3020
- setFileName("");
3021
- }
3022
- },
3023
- "upload-image"
3024
- ) }),
3025
- /* @__PURE__ */ jsxRuntime.jsxs(
3026
- reactComponents.PopoverSurface,
3027
- {
3028
- style: {
3029
- width: "320px",
3030
- opacity: disabled ? 0.6 : 1,
3031
- pointerEvents: disabled ? "none" : "auto"
3032
- },
3033
- children: [
3034
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 6 }, children: [
3035
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Upload", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsxs(
3036
- "label",
3037
- {
3038
- style: {
3039
- cursor: disabled ? "not-allowed" : "pointer",
3040
- display: "flex",
3041
- alignItems: "center",
3042
- gap: 8,
3043
- opacity: disabled ? 0.75 : 1
3291
+ "upload-image"
3292
+ ),
3293
+ /* @__PURE__ */ jsxRuntime.jsx(
3294
+ reactComponents.Dialog,
3295
+ {
3296
+ open: isOpen,
3297
+ onOpenChange: (_, data) => {
3298
+ if (!data.open) handleClose();
3299
+ },
3300
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogSurface, { style: { maxWidth: "400px" }, children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogBody, { children: [
3301
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogTitle, { children: "Insert Image" }),
3302
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
3303
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Upload", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsxs(
3304
+ "label",
3305
+ {
3306
+ style: {
3307
+ cursor: disabled ? "not-allowed" : "pointer",
3308
+ display: "flex",
3309
+ alignItems: "center",
3310
+ gap: 8,
3311
+ opacity: disabled ? 0.75 : 1
3312
+ },
3313
+ children: [
3314
+ /* @__PURE__ */ jsxRuntime.jsx(
3315
+ "input",
3316
+ {
3317
+ type: "file",
3318
+ accept: "image/*",
3319
+ style: { display: "none" },
3320
+ disabled,
3321
+ onChange: loadImage
3044
3322
  },
3045
- children: [
3046
- /* @__PURE__ */ jsxRuntime.jsx(
3047
- "input",
3048
- {
3049
- type: "file",
3050
- accept: "image/*",
3051
- style: { display: "none" },
3052
- disabled,
3053
- onChange: loadImage
3054
- },
3055
- "inline-image-upload"
3056
- ),
3057
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, children: [
3058
- /* @__PURE__ */ jsxRuntime.jsx(
3059
- reactIcons.AttachFilled,
3060
- {
3061
- style: {
3062
- fontSize: "16px",
3063
- color: disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#808080",
3064
- marginTop: 2
3065
- }
3066
- }
3067
- ),
3068
- !fileName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#808080" }, children: "Upload File" })
3069
- ] }),
3070
- fileName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#808080" }, children: fileName })
3071
- ]
3072
- }
3073
- ) }),
3074
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
3075
- reactComponents.Input,
3076
- {
3077
- placeholder: "Alt text",
3078
- appearance: "underline",
3079
- disabled,
3080
- onChange: (_, d) => setAltText(d.value),
3081
- value: altText
3082
- }
3083
- ) }),
3084
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
3085
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled: isDisabled, onClick: () => onClick({ altText, src }), children: "Add" }),
3086
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
3087
- ] })
3088
- ] }),
3089
- selectedValue === "URL" && /* @__PURE__ */ jsxRuntime.jsx(
3090
- InsertImageByURL,
3091
- {
3092
- disabled,
3093
- setIsOpen: (open) => setIsOpen(open),
3094
- onClick: (payload) => onClick(payload)
3095
- }
3096
- )
3097
- ]
3098
- }
3099
- )
3100
- ]
3101
- }
3102
- );
3323
+ "inline-image-upload"
3324
+ ),
3325
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, children: [
3326
+ /* @__PURE__ */ jsxRuntime.jsx(
3327
+ reactIcons.AttachFilled,
3328
+ {
3329
+ style: {
3330
+ fontSize: "16px",
3331
+ color: disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#808080",
3332
+ marginTop: 2
3333
+ }
3334
+ }
3335
+ ),
3336
+ !fileName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#808080" }, children: "Upload File" })
3337
+ ] }),
3338
+ fileName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#808080" }, children: fileName })
3339
+ ]
3340
+ }
3341
+ ) }),
3342
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
3343
+ reactComponents.Input,
3344
+ {
3345
+ placeholder: "Alt text",
3346
+ appearance: "underline",
3347
+ disabled,
3348
+ onChange: (_, d) => setAltText(d.value),
3349
+ value: altText
3350
+ }
3351
+ ) })
3352
+ ] }) }),
3353
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogActions, { children: [
3354
+ /* @__PURE__ */ jsxRuntime.jsx(
3355
+ reactComponents.Button,
3356
+ {
3357
+ appearance: "primary",
3358
+ size: "small",
3359
+ disabled: isAddDisabled,
3360
+ onClick: () => onClick({ altText, src }),
3361
+ children: "Add"
3362
+ }
3363
+ ),
3364
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled, onClick: handleClose, children: "Cancel" })
3365
+ ] })
3366
+ ] }) })
3367
+ }
3368
+ )
3369
+ ] });
3103
3370
  };
3104
3371
  var ImagesPlugin = ({ captionsEnabled }) => {
3105
3372
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
@@ -3265,17 +3532,28 @@ var useStyles = reactComponents.makeStyles({
3265
3532
  });
3266
3533
  var InsertInlineImageDialog = ({
3267
3534
  disabled,
3268
- activeEditor
3535
+ activeEditor,
3536
+ open: externalOpen,
3537
+ onClose
3269
3538
  }) => {
3270
3539
  const hasModifier = React9.useRef(false);
3271
3540
  const [src, setSrc] = React9.useState("");
3272
- const [isOpen, setIsOpen] = React9.useState(false);
3541
+ const [internalOpen, setInternalOpen] = React9.useState(false);
3273
3542
  const [altText, setAltText] = React9.useState("");
3274
3543
  const [fileName, setFileName] = React9.useState("");
3275
3544
  const [position, setPosition] = React9.useState("left");
3276
3545
  const styles = useStyles();
3277
3546
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
3278
- const isDisabled = disabled || src === "";
3547
+ const isControlled = externalOpen !== void 0;
3548
+ const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
3549
+ const isAddDisabled = disabled || src === "";
3550
+ const handleClose = () => {
3551
+ setSrc("");
3552
+ setAltText("");
3553
+ setFileName("");
3554
+ if (isControlled) onClose?.();
3555
+ else setInternalOpen(false);
3556
+ };
3279
3557
  const loadImage = (event) => {
3280
3558
  if (disabled) return;
3281
3559
  const files = event.target.files;
@@ -3300,146 +3578,135 @@ var InsertInlineImageDialog = ({
3300
3578
  if (disabled) return;
3301
3579
  const payload = { altText, position, src };
3302
3580
  activeEditor.dispatchCommand(INSERT_INLINE_IMAGE_COMMAND, payload);
3303
- setIsOpen(false);
3304
- setAltText("");
3305
- setSrc("");
3306
- setFileName("");
3581
+ handleClose();
3307
3582
  };
3308
- return /* @__PURE__ */ jsxRuntime.jsxs(
3309
- reactComponents.Popover,
3310
- {
3311
- trapFocus: true,
3312
- withArrow: true,
3313
- open: disabled ? false : isOpen,
3314
- onOpenChange: (_, data) => {
3315
- if (!disabled) setIsOpen(data.open);
3583
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3584
+ !isControlled && /* @__PURE__ */ jsxRuntime.jsx(
3585
+ reactComponents.Button,
3586
+ {
3587
+ size: "small",
3588
+ title: "Add Inline Image",
3589
+ disabled,
3590
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ImageEditRegular, { style: { color: iconColor } }),
3591
+ style: {
3592
+ background: isOpen && !disabled ? "#ebebeb" : "none",
3593
+ border: "none",
3594
+ margin: 2,
3595
+ opacity: disabled ? 0.55 : 1,
3596
+ cursor: disabled ? "not-allowed" : "pointer"
3597
+ },
3598
+ onClick: () => {
3599
+ if (disabled) return;
3600
+ setSrc("");
3601
+ setAltText("");
3602
+ setFileName("");
3603
+ setInternalOpen(true);
3604
+ }
3316
3605
  },
3317
- children: [
3318
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3319
- reactComponents.Button,
3320
- {
3321
- size: "small",
3322
- title: "Add Inline Image",
3323
- disabled,
3324
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ImageEditRegular, { style: { color: iconColor } }),
3325
- style: {
3326
- background: isOpen && !disabled ? "#ebebeb" : "none",
3327
- border: "none",
3328
- margin: 2,
3329
- opacity: disabled ? 0.55 : 1,
3330
- cursor: disabled ? "not-allowed" : "pointer"
3331
- },
3332
- onClick: () => {
3333
- if (disabled) return;
3334
- setIsOpen((prev) => !prev);
3335
- setAltText("");
3336
- setSrc("");
3337
- setFileName("");
3338
- }
3339
- },
3340
- "upload-inline-image"
3341
- ) }),
3342
- /* @__PURE__ */ jsxRuntime.jsx(
3343
- reactComponents.PopoverSurface,
3344
- {
3345
- style: {
3346
- width: "400px",
3347
- opacity: disabled ? 0.6 : 1,
3348
- pointerEvents: disabled ? "none" : "auto"
3349
- },
3350
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 6, padding: "10px 0px 0px 0px" }, children: [
3351
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Upload", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsxs(
3352
- "label",
3353
- {
3354
- style: {
3355
- cursor: disabled ? "not-allowed" : "pointer",
3356
- display: "flex",
3357
- alignItems: "center",
3358
- gap: 8,
3359
- opacity: disabled ? 0.75 : 1
3360
- },
3361
- children: [
3606
+ "upload-inline-image"
3607
+ ),
3608
+ /* @__PURE__ */ jsxRuntime.jsx(
3609
+ reactComponents.Dialog,
3610
+ {
3611
+ open: isOpen,
3612
+ onOpenChange: (_, data) => {
3613
+ if (!data.open) handleClose();
3614
+ },
3615
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogSurface, { style: { maxWidth: "440px" }, children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogBody, { children: [
3616
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogTitle, { children: "Insert Inline Image" }),
3617
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
3618
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Upload", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsxs(
3619
+ "label",
3620
+ {
3621
+ style: {
3622
+ cursor: disabled ? "not-allowed" : "pointer",
3623
+ display: "flex",
3624
+ alignItems: "center",
3625
+ gap: 8,
3626
+ opacity: disabled ? 0.75 : 1
3627
+ },
3628
+ children: [
3629
+ /* @__PURE__ */ jsxRuntime.jsx(
3630
+ "input",
3631
+ {
3632
+ type: "file",
3633
+ accept: "image/*",
3634
+ style: { display: "none" },
3635
+ disabled,
3636
+ onChange: loadImage
3637
+ },
3638
+ "inline-image-upload"
3639
+ ),
3640
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, children: [
3362
3641
  /* @__PURE__ */ jsxRuntime.jsx(
3363
- "input",
3642
+ reactIcons.AttachFilled,
3364
3643
  {
3365
- type: "file",
3366
- accept: "image/*",
3367
- style: { display: "none" },
3368
- disabled,
3369
- onChange: loadImage
3370
- },
3371
- "inline-image-upload"
3372
- ),
3373
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, children: [
3374
- /* @__PURE__ */ jsxRuntime.jsx(
3375
- reactIcons.AttachFilled,
3376
- {
3377
- style: {
3378
- fontSize: "16px",
3379
- color: disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#808080",
3380
- marginTop: 2
3381
- }
3382
- }
3383
- ),
3384
- !fileName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#808080" }, children: "Upload File" })
3385
- ] }),
3386
- fileName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#808080" }, children: fileName })
3387
- ]
3388
- }
3389
- ) }),
3390
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Position", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsxs(
3391
- reactComponents.Dropdown,
3392
- {
3393
- placeholder: "Left Align",
3394
- className: styles.alignDropdown,
3395
- disabled,
3396
- listbox: { style: { width: "120px" } },
3397
- root: { style: { borderBottom: "1px solid black" } },
3398
- children: [
3399
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { text: "full", onClick: () => setPosition("full"), children: "Full" }, "full"),
3400
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { text: "left", onClick: () => setPosition("left"), children: "Left" }, "left"),
3401
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { text: "right", onClick: () => setPosition("right"), children: "Right" }, "right")
3402
- ]
3403
- }
3404
- ) }),
3405
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
3406
- reactComponents.Input,
3407
- {
3408
- placeholder: "Alt text",
3409
- appearance: "underline",
3410
- disabled,
3411
- value: altText,
3412
- onChange: (_, d) => setAltText(d.value)
3413
- }
3414
- ) }),
3415
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
3416
- /* @__PURE__ */ jsxRuntime.jsx(
3417
- reactComponents.Button,
3418
- {
3419
- size: "small",
3420
- disabled: isDisabled,
3421
- onClick: handleOnClick,
3422
- children: "Add"
3423
- },
3424
- "file-inline-upload-btn"
3425
- ),
3426
- /* @__PURE__ */ jsxRuntime.jsx(
3427
- reactComponents.Button,
3428
- {
3429
- size: "small",
3430
- disabled,
3431
- onClick: () => setIsOpen(false),
3432
- children: "Cancel"
3433
- },
3434
- "file-inline-upload-cancel"
3435
- )
3436
- ] })
3437
- ] })
3438
- }
3439
- )
3440
- ]
3441
- }
3442
- );
3644
+ style: {
3645
+ fontSize: "16px",
3646
+ color: disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#808080",
3647
+ marginTop: 2
3648
+ }
3649
+ }
3650
+ ),
3651
+ !fileName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#808080" }, children: "Upload File" })
3652
+ ] }),
3653
+ fileName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#808080" }, children: fileName })
3654
+ ]
3655
+ }
3656
+ ) }),
3657
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Position", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsxs(
3658
+ reactComponents.Dropdown,
3659
+ {
3660
+ placeholder: "Left Align",
3661
+ className: styles.alignDropdown,
3662
+ disabled,
3663
+ listbox: { style: { width: "120px" } },
3664
+ root: { style: { borderBottom: "1px solid black" } },
3665
+ children: [
3666
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { text: "full", onClick: () => setPosition("full"), children: "Full" }, "full"),
3667
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { text: "left", onClick: () => setPosition("left"), children: "Left" }, "left"),
3668
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { text: "right", onClick: () => setPosition("right"), children: "Right" }, "right")
3669
+ ]
3670
+ }
3671
+ ) }),
3672
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
3673
+ reactComponents.Input,
3674
+ {
3675
+ placeholder: "Alt text",
3676
+ appearance: "underline",
3677
+ disabled,
3678
+ value: altText,
3679
+ onChange: (_, d) => setAltText(d.value)
3680
+ }
3681
+ ) })
3682
+ ] }) }),
3683
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogActions, { children: [
3684
+ /* @__PURE__ */ jsxRuntime.jsx(
3685
+ reactComponents.Button,
3686
+ {
3687
+ appearance: "primary",
3688
+ size: "small",
3689
+ disabled: isAddDisabled,
3690
+ onClick: handleOnClick,
3691
+ children: "Add"
3692
+ },
3693
+ "file-inline-upload-btn"
3694
+ ),
3695
+ /* @__PURE__ */ jsxRuntime.jsx(
3696
+ reactComponents.Button,
3697
+ {
3698
+ size: "small",
3699
+ disabled,
3700
+ onClick: handleClose,
3701
+ children: "Cancel"
3702
+ },
3703
+ "file-inline-upload-cancel"
3704
+ )
3705
+ ] })
3706
+ ] }) })
3707
+ }
3708
+ )
3709
+ ] });
3443
3710
  };
3444
3711
  var InlineImagePlugin = () => {
3445
3712
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
@@ -3594,204 +3861,6 @@ function PageBreakPlugin() {
3594
3861
  }, [editor]);
3595
3862
  return null;
3596
3863
  }
3597
-
3598
- // src/Utils/Sanitize.ts
3599
- var DROP_ENTIRELY = /* @__PURE__ */ new Set([
3600
- "script",
3601
- "noscript",
3602
- "style",
3603
- "object",
3604
- "embed",
3605
- "form",
3606
- "input",
3607
- "button",
3608
- "select",
3609
- "textarea",
3610
- "meta",
3611
- "link",
3612
- "base"
3613
- ]);
3614
- var ALLOWED_TAGS = /* @__PURE__ */ new Set([
3615
- // Block
3616
- "p",
3617
- "h1",
3618
- "h2",
3619
- "h3",
3620
- "h4",
3621
- "h5",
3622
- "h6",
3623
- "ul",
3624
- "ol",
3625
- "li",
3626
- "blockquote",
3627
- "pre",
3628
- "div",
3629
- "table",
3630
- "thead",
3631
- "tbody",
3632
- "tfoot",
3633
- "tr",
3634
- "td",
3635
- "th",
3636
- // Inline
3637
- "span",
3638
- "a",
3639
- "strong",
3640
- "b",
3641
- "em",
3642
- "i",
3643
- "u",
3644
- "s",
3645
- "del",
3646
- "strike",
3647
- "sub",
3648
- "sup",
3649
- "mark",
3650
- "code",
3651
- "br",
3652
- "hr",
3653
- // Media / embeds
3654
- "img",
3655
- "iframe"
3656
- ]);
3657
- var ALLOWED_ATTRS = /* @__PURE__ */ new Set([
3658
- // Presentation
3659
- "class",
3660
- "style",
3661
- "dir",
3662
- "lang",
3663
- // Anchors
3664
- "href",
3665
- "target",
3666
- "rel",
3667
- // Images
3668
- "src",
3669
- "alt",
3670
- "width",
3671
- "height",
3672
- // Tables
3673
- "colspan",
3674
- "rowspan",
3675
- // Lists — <ol type="a"> and <ol start="2">
3676
- "start",
3677
- "type",
3678
- // Lexical-internal data markers
3679
- "data-lex-block",
3680
- "data-kind",
3681
- "data-lexical-decorator",
3682
- // Misc
3683
- "title",
3684
- "allowfullscreen"
3685
- ]);
3686
- var DANGEROUS_URL = /^\s*(javascript|data\s*:|vbscript)/i;
3687
- function sanitizeElement(el) {
3688
- for (const child of Array.from(el.children)) {
3689
- sanitizeElement(child);
3690
- }
3691
- const tag = el.tagName.toLowerCase();
3692
- if (DROP_ENTIRELY.has(tag)) {
3693
- el.parentNode?.removeChild(el);
3694
- return;
3695
- }
3696
- if (tag === "iframe") {
3697
- const src = el.getAttribute("src") ?? "";
3698
- const isYouTube = /^\s*https:\/\/(www\.)?(youtube\.com|youtube-nocookie\.com)\/embed\/[^?&/]+/i.test(src);
3699
- if (!isYouTube) {
3700
- el.parentNode?.removeChild(el);
3701
- return;
3702
- }
3703
- }
3704
- if (!ALLOWED_TAGS.has(tag)) {
3705
- while (el.firstChild) {
3706
- el.parentNode?.insertBefore(el.firstChild, el);
3707
- }
3708
- el.parentNode?.removeChild(el);
3709
- return;
3710
- }
3711
- for (const { name, value } of Array.from(el.attributes)) {
3712
- const lname = name.toLowerCase();
3713
- if (!ALLOWED_ATTRS.has(lname)) {
3714
- el.removeAttribute(name);
3715
- continue;
3716
- }
3717
- if (lname === "href" || lname === "src") {
3718
- if (DANGEROUS_URL.test(value)) {
3719
- el.removeAttribute(name);
3720
- }
3721
- }
3722
- if (lname === "style") {
3723
- const safe = value.replace(/expression\s*\(/gi, "(").replace(/url\s*\(\s*['"]?\s*javascript:/gi, "url(");
3724
- el.setAttribute("style", safe);
3725
- }
3726
- }
3727
- if (tag === "a" && (el.getAttribute("target") || "").toLowerCase() === "_blank") {
3728
- const rel = (el.getAttribute("rel") || "").trim();
3729
- const tokens = new Set(
3730
- rel.split(/\s+/).filter(Boolean).map((t) => t.toLowerCase())
3731
- );
3732
- tokens.add("noopener");
3733
- tokens.add("noreferrer");
3734
- el.setAttribute("rel", Array.from(tokens).join(" "));
3735
- }
3736
- }
3737
- function sanitizeHtml(html) {
3738
- if (!html || typeof html !== "string") return "";
3739
- const doc = new DOMParser().parseFromString(html, "text/html");
3740
- for (const child of Array.from(doc.body.children)) {
3741
- sanitizeElement(child);
3742
- }
3743
- return doc.body.innerHTML;
3744
- }
3745
- var BLOCK_TAGS = /* @__PURE__ */ new Set([
3746
- "p",
3747
- "h1",
3748
- "h2",
3749
- "h3",
3750
- "h4",
3751
- "h5",
3752
- "h6",
3753
- "div",
3754
- "ul",
3755
- "ol",
3756
- "li",
3757
- "table",
3758
- "blockquote",
3759
- "pre",
3760
- "hr"
3761
- ]);
3762
- function normalizeToBlockHtml(html) {
3763
- if (!html) return "";
3764
- const doc = new DOMParser().parseFromString(html, "text/html");
3765
- const body = doc.body;
3766
- const childNodes = Array.from(body.childNodes);
3767
- const needsWrap = childNodes.some((node) => {
3768
- if (node.nodeType === Node.TEXT_NODE) return !!node.nodeValue?.trim();
3769
- if (node.nodeType === Node.ELEMENT_NODE)
3770
- return !BLOCK_TAGS.has(node.tagName.toLowerCase());
3771
- return false;
3772
- });
3773
- if (!needsWrap) return html;
3774
- while (body.firstChild) body.removeChild(body.firstChild);
3775
- let pendingP = null;
3776
- for (const node of childNodes) {
3777
- const isBlock = node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
3778
- const isWhitespace = node.nodeType === Node.TEXT_NODE && !node.nodeValue?.trim();
3779
- if (isBlock) {
3780
- if (pendingP) {
3781
- body.appendChild(pendingP);
3782
- pendingP = null;
3783
- }
3784
- body.appendChild(node);
3785
- } else if (!isWhitespace) {
3786
- if (!pendingP) pendingP = doc.createElement("p");
3787
- pendingP.appendChild(node);
3788
- }
3789
- }
3790
- if (pendingP) body.appendChild(pendingP);
3791
- return body.innerHTML;
3792
- }
3793
-
3794
- // src/Utils/Helper.ts
3795
3864
  function findBlockByKind(kind) {
3796
3865
  const root = lexical.$getRoot();
3797
3866
  for (const child of root.getChildren()) {
@@ -3863,7 +3932,7 @@ function RefApiPlugin({
3863
3932
  editor.getEditorState().read(() => {
3864
3933
  html$1 = html.$generateHtmlFromNodes(editor, null);
3865
3934
  });
3866
- return html$1;
3935
+ return postProcessOutput(html$1);
3867
3936
  },
3868
3937
  clear: () => {
3869
3938
  editor.update(() => {
@@ -5006,13 +5075,14 @@ function getToolbarGroupsByLevel(level) {
5006
5075
  // ['undo', 'redo', '|'],
5007
5076
  ["Bold", "Italic", "Underline", "|"],
5008
5077
  ["ColorPicker", "|"],
5009
- ["Link", "Table", "|"],
5010
- // ['Image', 'Youtube', 'InlineImage', '|'],
5078
+ ["Link", "|"],
5079
+ ["Insert", "|"],
5011
5080
  ["Heading", "|"],
5012
5081
  ["FontFamily", "|"],
5013
5082
  ["FontSize", "|"],
5014
5083
  ["Decorators", "|"],
5015
- ["Align"]
5084
+ ["Align", "|"],
5085
+ ["PageSetup"]
5016
5086
  ];
5017
5087
  case "pro" /* Pro */:
5018
5088
  default:
@@ -5020,14 +5090,14 @@ function getToolbarGroupsByLevel(level) {
5020
5090
  // ['undo', 'redo', '|'],
5021
5091
  ["Bold", "Italic", "Underline", "|"],
5022
5092
  ["ColorPicker", "|"],
5023
- ["Link", "Table", "|"],
5024
- // ['CodeBlock', '|'],
5025
- ["Image", "Youtube", "InlineImage", "|"],
5093
+ ["Link", "|"],
5094
+ ["Insert", "|"],
5026
5095
  ["Heading", "|"],
5027
5096
  ["FontFamily", "|"],
5028
5097
  ["FontSize", "|"],
5029
5098
  ["Decorators", "|"],
5030
- ["Align"]
5099
+ ["Align", "|"],
5100
+ ["PageSetup"]
5031
5101
  ];
5032
5102
  }
5033
5103
  }
@@ -5065,6 +5135,7 @@ var normalizeHex = (v) => {
5065
5135
  if (hex.length === 4 || hex.length === 7) return hex.toLowerCase();
5066
5136
  return "#000000";
5067
5137
  };
5138
+ var isCompleteHex = (v) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test((v ?? "").trim());
5068
5139
  var hexToRgb = (hex) => {
5069
5140
  const h = normalizeHex(hex).replace("#", "");
5070
5141
  if (h.length === 3) {
@@ -5147,62 +5218,118 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5147
5218
  };
5148
5219
  }, [open]);
5149
5220
  const appliedHex = React9__namespace.useMemo(() => normalizeHex(value || "#000000"), [value]);
5150
- const [hex, setHex] = React9__namespace.useState(appliedHex);
5221
+ const [hex, setHexState] = React9__namespace.useState(appliedHex);
5222
+ const [hexText, setHexText] = React9__namespace.useState(appliedHex);
5151
5223
  const { r, g, b } = React9__namespace.useMemo(() => hexToRgb(hex), [hex]);
5152
5224
  const hsv = React9__namespace.useMemo(() => rgbToHsv(r, g, b), [r, g, b]);
5153
5225
  const [h, setH] = React9__namespace.useState(hsv.h);
5154
5226
  const [s, setS] = React9__namespace.useState(hsv.s);
5155
5227
  const [v, setV] = React9__namespace.useState(hsv.v);
5156
- const setDraft = React9__namespace.useCallback((nextHex) => {
5157
- setHex(nextHex);
5158
- const rgb = hexToRgb(nextHex);
5159
- const next = rgbToHsv(rgb.r, rgb.g, rgb.b);
5160
- setH(next.h);
5161
- setS(next.s);
5162
- setV(next.v);
5163
- }, []);
5164
- const setDraftFromHsv = React9__namespace.useCallback((hh, ss, vv) => {
5165
- const rgb = hsvToRgb(hh, ss, vv);
5166
- setHex(rgbToHex(rgb.r, rgb.g, rgb.b));
5167
- setH(hh);
5168
- setS(ss);
5169
- setV(vv);
5170
- }, []);
5228
+ const commit = React9__namespace.useCallback(
5229
+ (nextHex) => {
5230
+ const rgb = hexToRgb(nextHex);
5231
+ const next = rgbToHsv(rgb.r, rgb.g, rgb.b);
5232
+ setHexState(nextHex);
5233
+ setHexText(nextHex);
5234
+ setH(next.h);
5235
+ setS(next.s);
5236
+ setV(next.v);
5237
+ onChange(nextHex);
5238
+ },
5239
+ [onChange]
5240
+ );
5241
+ const commitFromHsv = React9__namespace.useCallback(
5242
+ (hh, ss, vv) => {
5243
+ const rgb = hsvToRgb(hh, ss, vv);
5244
+ const nextHex = rgbToHex(rgb.r, rgb.g, rgb.b);
5245
+ setHexState(nextHex);
5246
+ setHexText(nextHex);
5247
+ setH(hh);
5248
+ setS(ss);
5249
+ setV(vv);
5250
+ onChange(nextHex);
5251
+ },
5252
+ [onChange]
5253
+ );
5171
5254
  const wasOpenRef = React9__namespace.useRef(open);
5172
5255
  React9__namespace.useEffect(() => {
5173
5256
  const justOpened = open && !wasOpenRef.current;
5174
5257
  wasOpenRef.current = open;
5175
5258
  if (!justOpened) return;
5176
- setDraft(appliedHex);
5177
- }, [appliedHex, open, setDraft]);
5259
+ setHexState(appliedHex);
5260
+ setHexText(appliedHex);
5261
+ const rgb = hexToRgb(appliedHex);
5262
+ const next = rgbToHsv(rgb.r, rgb.g, rgb.b);
5263
+ setH(next.h);
5264
+ setS(next.s);
5265
+ setV(next.v);
5266
+ }, [appliedHex, open]);
5178
5267
  const svRef = React9__namespace.useRef(null);
5179
- const handleSVClick = React9__namespace.useCallback(
5268
+ const svPointFromEvent = React9__namespace.useCallback((clientX, clientY) => {
5269
+ if (!svRef.current) return null;
5270
+ const rect = svRef.current.getBoundingClientRect();
5271
+ const x = clamp3(clientX - rect.left, 0, rect.width);
5272
+ const y = clamp3(clientY - rect.top, 0, rect.height);
5273
+ const ss = rect.width === 0 ? 0 : x / rect.width;
5274
+ const vv = rect.height === 0 ? 0 : 1 - y / rect.height;
5275
+ return { ss, vv };
5276
+ }, []);
5277
+ const hRef = React9__namespace.useRef(h);
5278
+ hRef.current = h;
5279
+ const handleSVPointerDown = React9__namespace.useCallback(
5280
+ (e) => {
5281
+ e.currentTarget.setPointerCapture(e.pointerId);
5282
+ const pt = svPointFromEvent(e.clientX, e.clientY);
5283
+ if (pt) commitFromHsv(hRef.current, pt.ss, pt.vv);
5284
+ },
5285
+ [svPointFromEvent, commitFromHsv]
5286
+ );
5287
+ const handleSVPointerMove = React9__namespace.useCallback(
5180
5288
  (e) => {
5181
- if (!svRef.current) return;
5182
- const rect = svRef.current.getBoundingClientRect();
5183
- const x = clamp3(e.clientX - rect.left, 0, rect.width);
5184
- const y = clamp3(e.clientY - rect.top, 0, rect.height);
5185
- const ss = rect.width === 0 ? 0 : x / rect.width;
5186
- const vv = rect.height === 0 ? 0 : 1 - y / rect.height;
5187
- setDraftFromHsv(h, ss, vv);
5289
+ if (e.buttons !== 1) return;
5290
+ const pt = svPointFromEvent(e.clientX, e.clientY);
5291
+ if (pt) commitFromHsv(hRef.current, pt.ss, pt.vv);
5188
5292
  },
5189
- [h, setDraftFromHsv]
5293
+ [svPointFromEvent, commitFromHsv]
5190
5294
  );
5191
5295
  const hueRef = React9__namespace.useRef(null);
5192
- const handleHueClick = React9__namespace.useCallback(
5296
+ const sRef = React9__namespace.useRef(s);
5297
+ sRef.current = s;
5298
+ const vRef = React9__namespace.useRef(v);
5299
+ vRef.current = v;
5300
+ const huePointFromEvent = React9__namespace.useCallback((clientX) => {
5301
+ if (!hueRef.current) return null;
5302
+ const rect = hueRef.current.getBoundingClientRect();
5303
+ const x = clamp3(clientX - rect.left, 0, rect.width);
5304
+ return rect.width === 0 ? 0 : x / rect.width * 360;
5305
+ }, []);
5306
+ const handleHuePointerDown = React9__namespace.useCallback(
5307
+ (e) => {
5308
+ e.currentTarget.setPointerCapture(e.pointerId);
5309
+ const hh = huePointFromEvent(e.clientX);
5310
+ if (hh != null) commitFromHsv(hh, sRef.current, vRef.current);
5311
+ },
5312
+ [huePointFromEvent, commitFromHsv]
5313
+ );
5314
+ const handleHuePointerMove = React9__namespace.useCallback(
5193
5315
  (e) => {
5194
- if (!hueRef.current) return;
5195
- const rect = hueRef.current.getBoundingClientRect();
5196
- const x = clamp3(e.clientX - rect.left, 0, rect.width);
5197
- const hh = rect.width === 0 ? 0 : x / rect.width * 360;
5198
- setDraftFromHsv(hh, s, v);
5316
+ if (e.buttons !== 1) return;
5317
+ const hh = huePointFromEvent(e.clientX);
5318
+ if (hh != null) commitFromHsv(hh, sRef.current, vRef.current);
5199
5319
  },
5200
- [s, v, setDraftFromHsv]
5320
+ [huePointFromEvent, commitFromHsv]
5201
5321
  );
5202
- const handleApply = React9__namespace.useCallback(() => {
5203
- onChange(hex);
5204
- setOpenAndNotify(false);
5205
- }, [onChange, hex, setOpenAndNotify]);
5322
+ const handleHexChange = React9__namespace.useCallback(
5323
+ (_, val) => {
5324
+ const next = val ?? "";
5325
+ setHexText(next);
5326
+ if (isCompleteHex(next.trim())) commit(normalizeHex(next));
5327
+ },
5328
+ [commit]
5329
+ );
5330
+ const handleHexBlur = React9__namespace.useCallback(() => {
5331
+ commit(normalizeHex(hexText));
5332
+ }, [hexText, commit]);
5206
5333
  const svThumb = React9__namespace.useMemo(() => ({ left: `${s * 100}%`, top: `${(1 - v) * 100}%` }), [s, v]);
5207
5334
  const hueThumb = React9__namespace.useMemo(() => ({ left: `${h / 360 * 100}%` }), [h]);
5208
5335
  const hueColor = React9__namespace.useMemo(() => {
@@ -5257,44 +5384,192 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5257
5384
  directionalHint: 4,
5258
5385
  className: "aoColorCallout",
5259
5386
  preventDismissOnEvent,
5260
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, styles: { root: { padding: 12, width: 320 } }, children: [
5261
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aoLexRow", children: [
5262
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexSwatch", style: { background: hex } }),
5263
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexTitle", children: title })
5387
+ children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 14 }, styles: { root: { padding: "14px 16px 16px", width: 288 } }, children: [
5388
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
5389
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#242424", letterSpacing: 0.1 }, children: title }),
5390
+ /* @__PURE__ */ jsxRuntime.jsx(
5391
+ "button",
5392
+ {
5393
+ type: "button",
5394
+ "aria-label": "Close",
5395
+ onClick: () => setOpenAndNotify(false),
5396
+ style: {
5397
+ display: "flex",
5398
+ alignItems: "center",
5399
+ justifyContent: "center",
5400
+ width: 24,
5401
+ height: 24,
5402
+ padding: 0,
5403
+ border: "none",
5404
+ borderRadius: 4,
5405
+ background: "transparent",
5406
+ color: "#616161",
5407
+ cursor: "pointer"
5408
+ },
5409
+ onMouseEnter: (e) => e.currentTarget.style.background = "#f0f0f0",
5410
+ onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
5411
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Dismiss16Regular, {})
5412
+ }
5413
+ )
5264
5414
  ] }),
5265
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aoLexRow", children: [
5266
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexLabel", children: "Hex" }),
5415
+ /* @__PURE__ */ jsxRuntime.jsxs(
5416
+ "div",
5417
+ {
5418
+ ref: svRef,
5419
+ onPointerDown: handleSVPointerDown,
5420
+ onPointerMove: handleSVPointerMove,
5421
+ style: {
5422
+ position: "relative",
5423
+ width: "100%",
5424
+ height: 150,
5425
+ borderRadius: 8,
5426
+ overflow: "hidden",
5427
+ cursor: "crosshair",
5428
+ touchAction: "none",
5429
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)"
5430
+ },
5431
+ children: [
5432
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: hueColor } }),
5433
+ /* @__PURE__ */ jsxRuntime.jsx(
5434
+ "div",
5435
+ {
5436
+ style: {
5437
+ position: "absolute",
5438
+ inset: 0,
5439
+ background: "linear-gradient(to right, #fff, rgba(255,255,255,0))"
5440
+ }
5441
+ }
5442
+ ),
5443
+ /* @__PURE__ */ jsxRuntime.jsx(
5444
+ "div",
5445
+ {
5446
+ style: {
5447
+ position: "absolute",
5448
+ inset: 0,
5449
+ background: "linear-gradient(to top, #000, rgba(0,0,0,0))"
5450
+ }
5451
+ }
5452
+ ),
5453
+ /* @__PURE__ */ jsxRuntime.jsx(
5454
+ "div",
5455
+ {
5456
+ style: {
5457
+ position: "absolute",
5458
+ width: 16,
5459
+ height: 16,
5460
+ borderRadius: "50%",
5461
+ border: "2px solid #fff",
5462
+ boxShadow: "0 0 0 1px rgba(0,0,0,0.35), 0 1px 3px rgba(0,0,0,0.4)",
5463
+ transform: "translate(-50%, -50%)",
5464
+ pointerEvents: "none",
5465
+ ...svThumb
5466
+ }
5467
+ }
5468
+ )
5469
+ ]
5470
+ }
5471
+ ),
5472
+ /* @__PURE__ */ jsxRuntime.jsx(
5473
+ "div",
5474
+ {
5475
+ ref: hueRef,
5476
+ onPointerDown: handleHuePointerDown,
5477
+ onPointerMove: handleHuePointerMove,
5478
+ style: {
5479
+ position: "relative",
5480
+ width: "100%",
5481
+ height: 12,
5482
+ borderRadius: 999,
5483
+ cursor: "pointer",
5484
+ touchAction: "none",
5485
+ background: "linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)",
5486
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)"
5487
+ },
5488
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5489
+ "div",
5490
+ {
5491
+ style: {
5492
+ position: "absolute",
5493
+ top: "50%",
5494
+ width: 16,
5495
+ height: 16,
5496
+ borderRadius: "50%",
5497
+ background: hueColor,
5498
+ border: "2px solid #fff",
5499
+ boxShadow: "0 0 0 1px rgba(0,0,0,0.35), 0 1px 3px rgba(0,0,0,0.4)",
5500
+ transform: "translate(-50%, -50%)",
5501
+ pointerEvents: "none",
5502
+ ...hueThumb
5503
+ }
5504
+ }
5505
+ )
5506
+ }
5507
+ ),
5508
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
5509
+ /* @__PURE__ */ jsxRuntime.jsx(
5510
+ "div",
5511
+ {
5512
+ style: {
5513
+ width: 32,
5514
+ height: 32,
5515
+ borderRadius: 6,
5516
+ flexShrink: 0,
5517
+ background: hex,
5518
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.12)"
5519
+ }
5520
+ }
5521
+ ),
5267
5522
  /* @__PURE__ */ jsxRuntime.jsx(
5268
5523
  react.TextField,
5269
5524
  {
5270
- value: hex,
5271
- onChange: (_, val) => setHex(normalizeHex(val || "")),
5272
- onBlur: () => setDraft(normalizeHex(hex))
5525
+ value: hexText,
5526
+ onChange: handleHexChange,
5527
+ onBlur: handleHexBlur,
5528
+ onKeyDown: (e) => {
5529
+ if (e.key === "Enter") commit(normalizeHex(hexText));
5530
+ },
5531
+ styles: { root: { flex: 1 }, fieldGroup: { borderRadius: 6 } }
5273
5532
  }
5274
5533
  )
5275
5534
  ] }),
5276
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexSwatches", children: PRESET.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
5277
- "button",
5278
- {
5279
- type: "button",
5280
- className: "aoLexSwatchBtn",
5281
- style: { background: c },
5282
- onClick: () => setDraft(c),
5283
- title: c
5284
- },
5285
- c
5286
- )) }),
5287
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aoLexSV", ref: svRef, onClick: handleSVClick, children: [
5288
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexSVHue", style: { background: hueColor } }),
5289
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexSVWhite" }),
5290
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexSVBlack" }),
5291
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexSVThumb", style: svThumb })
5292
- ] }),
5293
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexHue", ref: hueRef, onClick: handleHueClick, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexHueThumb", style: hueThumb }) }),
5294
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexPreview", style: { background: hex } }),
5295
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aoLexActions", children: [
5296
- /* @__PURE__ */ jsxRuntime.jsx(react.DefaultButton, { type: "button", text: "Apply", onClick: handleApply }),
5297
- /* @__PURE__ */ jsxRuntime.jsx(react.DefaultButton, { type: "button", text: "Close", onClick: () => setOpenAndNotify(false) })
5535
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5536
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 600, color: "#8a8a8a", marginBottom: 6, letterSpacing: 0.3 }, children: "STANDARD COLORS" }),
5537
+ /* @__PURE__ */ jsxRuntime.jsx(
5538
+ "div",
5539
+ {
5540
+ style: {
5541
+ display: "grid",
5542
+ gridTemplateColumns: "repeat(9, 1fr)",
5543
+ gap: 6
5544
+ },
5545
+ children: PRESET.map((c) => {
5546
+ const isSelected = c.toLowerCase() === hex.toLowerCase();
5547
+ return /* @__PURE__ */ jsxRuntime.jsx(
5548
+ "button",
5549
+ {
5550
+ type: "button",
5551
+ onClick: () => commit(c),
5552
+ title: c,
5553
+ "aria-label": c,
5554
+ style: {
5555
+ width: 22,
5556
+ height: 22,
5557
+ padding: 0,
5558
+ borderRadius: 5,
5559
+ background: c,
5560
+ cursor: "pointer",
5561
+ boxShadow: isSelected ? "0 0 0 2px #fff, 0 0 0 3px #4a86e8" : "inset 0 0 0 1px rgba(0,0,0,0.15)",
5562
+ border: "none",
5563
+ transition: "transform 80ms ease"
5564
+ },
5565
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.12)",
5566
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)"
5567
+ },
5568
+ c
5569
+ );
5570
+ })
5571
+ }
5572
+ )
5298
5573
  ] })
5299
5574
  ] })
5300
5575
  }
@@ -5626,294 +5901,464 @@ var FontSizePlugin = ({ disabled }) => {
5626
5901
  "fontsize"
5627
5902
  ) });
5628
5903
  };
5629
- var InsertLinkPlugin = ({ disabled }) => {
5904
+ var VERBATIM_LINK_RE = /^https?:\/\/|^mailto:|^tel:|^#|^\//i;
5905
+ function getLinkValidationMessage(raw) {
5906
+ const trimmed = raw.trim();
5907
+ if (!trimmed) return void 0;
5908
+ if (/\s/.test(trimmed)) return "URL cannot contain spaces";
5909
+ if (!VERBATIM_LINK_RE.test(trimmed) && !trimmed.includes(".")) {
5910
+ return "Enter a valid URL (e.g. example.com)";
5911
+ }
5912
+ return void 0;
5913
+ }
5914
+ var InsertLinkPlugin = ({
5915
+ disabled,
5916
+ open: externalOpen,
5917
+ onClose
5918
+ }) => {
5630
5919
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
5631
- const [isOpen, setIsOpen] = React9.useState(false);
5920
+ const [internalOpen, setInternalOpen] = React9.useState(false);
5632
5921
  const [text, setText] = React9.useState("");
5633
5922
  const [link$1, setLink] = React9.useState("");
5634
5923
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
5924
+ const isControlled = externalOpen !== void 0;
5925
+ const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
5926
+ const linkError = getLinkValidationMessage(link$1);
5927
+ const handleClose = () => {
5928
+ setText("");
5929
+ setLink("");
5930
+ if (isControlled) onClose?.();
5931
+ else setInternalOpen(false);
5932
+ };
5635
5933
  const insertLink = (text2, link2) => {
5636
5934
  if (disabled) return;
5935
+ if (getLinkValidationMessage(link2)) return;
5936
+ const trimmedLink = link2.trim();
5937
+ const href = VERBATIM_LINK_RE.test(trimmedLink) ? trimmedLink : `https://${trimmedLink}`;
5637
5938
  editor.update(() => {
5638
- setText("");
5639
- setLink("");
5640
5939
  const selection = lexical.$getSelection();
5641
5940
  if (lexical.$isRangeSelection(selection)) {
5642
5941
  const textNode = new lexical.TextNode(text2);
5643
- const linkNode = link.$createLinkNode(link2.startsWith("http") ? link2 : `https://${link2}`);
5942
+ const linkNode = link.$createLinkNode(href);
5644
5943
  linkNode.append(textNode);
5645
5944
  selection.insertNodes([linkNode]);
5646
5945
  }
5647
- setIsOpen(false);
5648
5946
  });
5947
+ handleClose();
5649
5948
  };
5949
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5950
+ !isControlled && /* @__PURE__ */ jsxRuntime.jsx(
5951
+ reactComponents.Button,
5952
+ {
5953
+ size: "small",
5954
+ title: "Add link",
5955
+ disabled,
5956
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.LinkAddRegular, { style: { color: iconColor } }),
5957
+ style: {
5958
+ background: isOpen && !disabled ? "#ebebeb" : "none",
5959
+ border: "none",
5960
+ margin: 2,
5961
+ opacity: disabled ? 0.55 : 1,
5962
+ cursor: disabled ? "not-allowed" : "pointer"
5963
+ },
5964
+ onClick: () => {
5965
+ if (disabled) return;
5966
+ setInternalOpen((prev) => !prev);
5967
+ }
5968
+ },
5969
+ "upload-link"
5970
+ ),
5971
+ /* @__PURE__ */ jsxRuntime.jsx(
5972
+ reactComponents.Dialog,
5973
+ {
5974
+ open: isOpen,
5975
+ onOpenChange: (_, data) => {
5976
+ if (!data.open) handleClose();
5977
+ },
5978
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogSurface, { style: { maxWidth: "380px" }, children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogBody, { children: [
5979
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogTitle, { children: "Insert Link" }),
5980
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
5981
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
5982
+ reactComponents.Input,
5983
+ {
5984
+ autoFocus: !disabled,
5985
+ value: text,
5986
+ appearance: "underline",
5987
+ placeholder: "Text",
5988
+ disabled,
5989
+ onChange: (_, v) => setText(v.value)
5990
+ }
5991
+ ) }),
5992
+ /* @__PURE__ */ jsxRuntime.jsx(
5993
+ reactComponents.Field,
5994
+ {
5995
+ label: "Link",
5996
+ orientation: "horizontal",
5997
+ size: "small",
5998
+ validationState: linkError ? "error" : "none",
5999
+ validationMessage: linkError,
6000
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6001
+ reactComponents.Input,
6002
+ {
6003
+ value: link$1,
6004
+ appearance: "underline",
6005
+ placeholder: "Link",
6006
+ disabled,
6007
+ onChange: (_, v) => setLink(v.value),
6008
+ onKeyDown: (e) => {
6009
+ if (e.key === "Enter" && text && link$1 && !linkError) insertLink(text, link$1);
6010
+ }
6011
+ }
6012
+ )
6013
+ }
6014
+ )
6015
+ ] }) }),
6016
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogActions, { children: [
6017
+ /* @__PURE__ */ jsxRuntime.jsx(
6018
+ reactComponents.Button,
6019
+ {
6020
+ appearance: "primary",
6021
+ size: "small",
6022
+ disabled: disabled || !text || !link$1 || !!linkError,
6023
+ onClick: () => insertLink(text, link$1),
6024
+ children: "Add"
6025
+ }
6026
+ ),
6027
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled, onClick: handleClose, children: "Cancel" })
6028
+ ] })
6029
+ ] }) })
6030
+ }
6031
+ )
6032
+ ] });
6033
+ };
6034
+ function PageSetupPlugin({ disabled, value, onChange }) {
6035
+ const sizeLabel = value.size === "pageless" ? "Pageless" : PAGE_SIZE_OPTIONS.find((o) => o.key === value.size)?.label ?? "Pageless";
6036
+ const isPaged = value.size !== "pageless";
5650
6037
  return /* @__PURE__ */ jsxRuntime.jsxs(
5651
- reactComponents.Popover,
6038
+ reactComponents.Menu,
5652
6039
  {
5653
- trapFocus: true,
5654
- withArrow: true,
5655
- open: disabled ? false : isOpen,
5656
- onOpenChange: (_, data) => {
5657
- if (!disabled) setIsOpen(data.open);
6040
+ checkedValues: {
6041
+ size: [value.size],
6042
+ orientation: [value.orientation],
6043
+ margin: [value.margin]
6044
+ },
6045
+ onCheckedValueChange: (_, data) => {
6046
+ const selected = data.checkedItems[0];
6047
+ if (!selected) return;
6048
+ if (data.name === "size") onChange({ ...value, size: selected });
6049
+ else if (data.name === "orientation")
6050
+ onChange({ ...value, orientation: selected });
6051
+ else if (data.name === "margin") onChange({ ...value, margin: selected });
5658
6052
  },
5659
6053
  children: [
5660
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
6054
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5661
6055
  reactComponents.Button,
5662
6056
  {
6057
+ appearance: "subtle",
5663
6058
  size: "small",
5664
- title: "Add link",
5665
6059
  disabled,
5666
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.LinkAddRegular, { style: { color: iconColor } }),
5667
- style: {
5668
- background: isOpen && !disabled ? "#ebebeb" : "none",
5669
- border: "none",
5670
- margin: 2,
5671
- opacity: disabled ? 0.55 : 1,
5672
- cursor: disabled ? "not-allowed" : "pointer"
5673
- },
5674
- onClick: () => {
5675
- if (!disabled) setIsOpen((prev) => !prev);
5676
- }
5677
- },
5678
- "upload-link"
5679
- ) }),
5680
- /* @__PURE__ */ jsxRuntime.jsx(
5681
- reactComponents.PopoverSurface,
5682
- {
5683
- style: {
5684
- width: "270px",
5685
- opacity: disabled ? 0.6 : 1,
5686
- pointerEvents: disabled ? "none" : "auto"
5687
- },
5688
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, children: [
5689
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
5690
- reactComponents.Input,
5691
- {
5692
- autoFocus: !disabled,
5693
- value: text,
5694
- appearance: "underline",
5695
- placeholder: "Text",
5696
- disabled,
5697
- onChange: (_, v) => setText(v.value)
5698
- }
5699
- ) }),
5700
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Link", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
5701
- reactComponents.Input,
5702
- {
5703
- value: link$1,
5704
- appearance: "underline",
5705
- placeholder: "Link",
5706
- disabled,
5707
- onChange: (_, v) => setLink(v.value)
5708
- }
5709
- ) }),
5710
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
5711
- /* @__PURE__ */ jsxRuntime.jsx(
5712
- reactComponents.Button,
5713
- {
5714
- size: "small",
5715
- disabled: disabled || !text || !link$1,
5716
- onClick: () => insertLink(text, link$1),
5717
- children: "Add"
5718
- }
5719
- ),
5720
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
5721
- ] })
5722
- ] })
6060
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DocumentRegular, {}),
6061
+ title: "Page setup",
6062
+ style: { minWidth: "auto" },
6063
+ children: sizeLabel
5723
6064
  }
5724
- )
6065
+ ) }),
6066
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuPopover, { style: { minWidth: 220 }, children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuList, { children: [
6067
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuGroup, { children: [
6068
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuGroupHeader, { children: "Page size" }),
6069
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItemRadio, { name: "size", value: "pageless", children: "Pageless" }),
6070
+ PAGE_SIZE_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItemRadio, { name: "size", value: opt.key, children: opt.label }, opt.key))
6071
+ ] }),
6072
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuDivider, {}),
6073
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuGroup, { children: [
6074
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuGroupHeader, { children: "Orientation" }),
6075
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItemRadio, { name: "orientation", value: "portrait", disabled: !isPaged, children: "Portrait" }),
6076
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItemRadio, { name: "orientation", value: "landscape", disabled: !isPaged, children: "Landscape" })
6077
+ ] }),
6078
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuDivider, {}),
6079
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuGroup, { children: [
6080
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuGroupHeader, { children: "Margins" }),
6081
+ MARGIN_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItemRadio, { name: "margin", value: opt.key, disabled: !isPaged, children: opt.label }, opt.key))
6082
+ ] })
6083
+ ] }) })
5725
6084
  ]
5726
6085
  }
5727
6086
  );
5728
- };
5729
- var TableItemPlugin = ({ disabled }) => {
6087
+ }
6088
+ var MAX_ROWS = 50;
6089
+ var MAX_COLS = 50;
6090
+ var TableItemPlugin = ({
6091
+ disabled,
6092
+ open: externalOpen,
6093
+ onClose
6094
+ }) => {
5730
6095
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
5731
6096
  const [columns, setColumns] = React9.useState("");
5732
6097
  const [rows, setRows] = React9.useState("");
5733
- const [isOpen, setIsOpen] = React9.useState(false);
6098
+ const [internalOpen, setInternalOpen] = React9.useState(false);
6099
+ const [rowError, setRowError] = React9.useState("");
6100
+ const [colError, setColError] = React9.useState("");
6101
+ const isControlled = externalOpen !== void 0;
6102
+ const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
5734
6103
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
6104
+ const handleClose = () => {
6105
+ setRows("");
6106
+ setColumns("");
6107
+ setRowError("");
6108
+ setColError("");
6109
+ if (isControlled) onClose?.();
6110
+ else setInternalOpen(false);
6111
+ };
6112
+ const onRowsChange = (val) => {
6113
+ const clean = val.replace(/\D/g, "");
6114
+ setRows(clean);
6115
+ const n = Number(clean);
6116
+ if (clean && n > MAX_ROWS) setRowError(`Maximum ${MAX_ROWS} rows allowed`);
6117
+ else setRowError("");
6118
+ };
6119
+ const onColsChange = (val) => {
6120
+ const clean = val.replace(/\D/g, "");
6121
+ setColumns(clean);
6122
+ const n = Number(clean);
6123
+ if (clean && n > MAX_COLS) setColError(`Maximum ${MAX_COLS} columns allowed`);
6124
+ else setColError("");
6125
+ };
5735
6126
  const onAddTable = () => {
5736
6127
  if (disabled) return;
5737
6128
  const row = Number(rows);
5738
6129
  const col = Number(columns);
5739
6130
  if (!row || !col) return;
6131
+ if (row > MAX_ROWS || col > MAX_COLS) return;
5740
6132
  editor.update(() => {
5741
6133
  const tableNode = table.$createTableNodeWithDimensions(row, col, true);
5742
6134
  utils.$insertNodeToNearestRoot(tableNode);
5743
6135
  });
5744
- setRows("");
5745
- setColumns("");
5746
- setIsOpen(false);
6136
+ handleClose();
5747
6137
  };
5748
- return /* @__PURE__ */ jsxRuntime.jsxs(
5749
- reactComponents.Popover,
5750
- {
5751
- trapFocus: true,
5752
- withArrow: true,
5753
- open: disabled ? false : isOpen,
5754
- onOpenChange: (_, data) => {
5755
- if (!disabled) setIsOpen(data.open);
6138
+ const isAddDisabled = disabled || !rows || !columns || !!rowError || !!colError;
6139
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6140
+ !isControlled && /* @__PURE__ */ jsxRuntime.jsx(
6141
+ reactComponents.Button,
6142
+ {
6143
+ size: "small",
6144
+ title: "Add table",
6145
+ disabled,
6146
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TableAddRegular, { style: { color: iconColor } }),
6147
+ style: {
6148
+ background: isOpen && !disabled ? "#ebebeb" : "none",
6149
+ border: "none",
6150
+ margin: 2,
6151
+ opacity: disabled ? 0.55 : 1,
6152
+ cursor: disabled ? "not-allowed" : "pointer"
6153
+ },
6154
+ onClick: () => {
6155
+ if (disabled) return;
6156
+ setRows("");
6157
+ setColumns("");
6158
+ setRowError("");
6159
+ setColError("");
6160
+ setInternalOpen(true);
6161
+ }
5756
6162
  },
5757
- children: [
5758
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5759
- reactComponents.Button,
5760
- {
5761
- size: "small",
5762
- title: "Add table",
5763
- disabled,
5764
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TableAddRegular, { style: { color: iconColor } }),
5765
- style: {
5766
- background: isOpen && !disabled ? "#ebebeb" : "none",
5767
- border: "none",
5768
- margin: 2,
5769
- opacity: disabled ? 0.55 : 1,
5770
- cursor: disabled ? "not-allowed" : "pointer"
5771
- },
5772
- onClick: () => {
5773
- if (disabled) return;
5774
- setIsOpen((prev) => !prev);
5775
- setRows("");
5776
- setColumns("");
5777
- }
5778
- },
5779
- "insert-table-nodes"
5780
- ) }),
5781
- /* @__PURE__ */ jsxRuntime.jsx(
5782
- reactComponents.PopoverSurface,
5783
- {
5784
- style: {
5785
- width: "270px",
5786
- opacity: disabled ? 0.6 : 1,
5787
- pointerEvents: disabled ? "none" : "auto"
5788
- },
5789
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, children: [
5790
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Rows", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
5791
- reactComponents.Input,
5792
- {
5793
- autoFocus: !disabled,
5794
- type: "number",
5795
- min: 1,
5796
- value: rows,
5797
- placeholder: "Rows",
5798
- appearance: "underline",
5799
- disabled,
5800
- input: { style: { textAlign: "left" } },
5801
- onChange: (_, v) => setRows(v.value.replace(/\D/g, ""))
5802
- }
5803
- ) }),
5804
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Columns", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
5805
- reactComponents.Input,
5806
- {
5807
- type: "number",
5808
- min: 1,
5809
- value: columns,
5810
- placeholder: "Columns",
5811
- appearance: "underline",
5812
- disabled,
5813
- input: { style: { textAlign: "left" } },
5814
- onChange: (_, v) => setColumns(v.value.replace(/\D/g, ""))
5815
- }
5816
- ) }),
5817
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
5818
- /* @__PURE__ */ jsxRuntime.jsx(
5819
- reactComponents.Button,
6163
+ "insert-table-nodes"
6164
+ ),
6165
+ /* @__PURE__ */ jsxRuntime.jsx(
6166
+ reactComponents.Dialog,
6167
+ {
6168
+ open: isOpen,
6169
+ onOpenChange: (_, data) => {
6170
+ if (!data.open) handleClose();
6171
+ },
6172
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogSurface, { style: { maxWidth: "380px" }, children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogBody, { children: [
6173
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogTitle, { children: "Insert Table" }),
6174
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
6175
+ /* @__PURE__ */ jsxRuntime.jsx(
6176
+ reactComponents.Field,
6177
+ {
6178
+ label: "Rows",
6179
+ orientation: "horizontal",
6180
+ size: "small",
6181
+ validationMessage: rowError || void 0,
6182
+ validationState: rowError ? "error" : "none",
6183
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6184
+ reactComponents.Input,
5820
6185
  {
5821
- size: "small",
5822
- appearance: "primary",
5823
- disabled: disabled || !rows || !columns,
5824
- onClick: onAddTable,
5825
- children: "Add"
6186
+ autoFocus: !disabled,
6187
+ type: "number",
6188
+ min: 1,
6189
+ max: MAX_ROWS,
6190
+ value: rows,
6191
+ placeholder: "Rows",
6192
+ appearance: "underline",
6193
+ disabled,
6194
+ input: { style: { textAlign: "left" } },
6195
+ onChange: (_, v) => onRowsChange(v.value)
5826
6196
  }
5827
- ),
5828
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
5829
- ] })
5830
- ] })
5831
- }
5832
- )
5833
- ]
5834
- }
5835
- );
6197
+ )
6198
+ }
6199
+ ),
6200
+ /* @__PURE__ */ jsxRuntime.jsx(
6201
+ reactComponents.Field,
6202
+ {
6203
+ label: "Columns",
6204
+ orientation: "horizontal",
6205
+ size: "small",
6206
+ validationMessage: colError || void 0,
6207
+ validationState: colError ? "error" : "none",
6208
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6209
+ reactComponents.Input,
6210
+ {
6211
+ type: "number",
6212
+ min: 1,
6213
+ max: MAX_COLS,
6214
+ value: columns,
6215
+ placeholder: "Columns",
6216
+ appearance: "underline",
6217
+ disabled,
6218
+ input: { style: { textAlign: "left" } },
6219
+ onChange: (_, v) => onColsChange(v.value)
6220
+ }
6221
+ )
6222
+ }
6223
+ )
6224
+ ] }) }),
6225
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogActions, { children: [
6226
+ /* @__PURE__ */ jsxRuntime.jsx(
6227
+ reactComponents.Button,
6228
+ {
6229
+ appearance: "primary",
6230
+ size: "small",
6231
+ disabled: isAddDisabled,
6232
+ onClick: onAddTable,
6233
+ children: "Add"
6234
+ }
6235
+ ),
6236
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled, onClick: handleClose, children: "Cancel" })
6237
+ ] })
6238
+ ] }) })
6239
+ }
6240
+ )
6241
+ ] });
5836
6242
  };
5837
- var YoutubeUploadPlugin = ({ disabled }) => {
6243
+ function extractYouTubeId(url) {
6244
+ const trimmed = url.trim();
6245
+ if (/^[\w-]{11}$/.test(trimmed)) return trimmed;
6246
+ const match = /(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/|live\/|u\/\w\/))([^#&?]{11})/.exec(trimmed);
6247
+ return match ? match[1] : null;
6248
+ }
6249
+ var YoutubeUploadPlugin = ({
6250
+ disabled,
6251
+ open: externalOpen,
6252
+ onClose
6253
+ }) => {
5838
6254
  const [url, setURL] = React9.useState("");
5839
- const [isOpen, setIsOpen] = React9.useState(false);
6255
+ const [urlError, setUrlError] = React9.useState("");
6256
+ const [internalOpen, setInternalOpen] = React9.useState(false);
5840
6257
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
5841
6258
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#424242";
6259
+ const isControlled = externalOpen !== void 0;
6260
+ const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
6261
+ const handleClose = () => {
6262
+ setURL("");
6263
+ setUrlError("");
6264
+ if (isControlled) onClose?.();
6265
+ else setInternalOpen(false);
6266
+ };
5842
6267
  const onHandleEmbeded = () => {
5843
6268
  if (disabled) return;
5844
6269
  if (!url) return;
5845
- const match = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/.exec(url);
5846
- const id = match && match[2]?.length === 11 ? match[2] : null;
5847
- if (!id) return;
6270
+ const id = extractYouTubeId(url);
6271
+ if (!id) {
6272
+ setUrlError("Invalid YouTube URL. Supported: watch?v=, youtu.be/, /shorts/, /live/");
6273
+ return;
6274
+ }
6275
+ setUrlError("");
5848
6276
  editor.update(() => {
5849
6277
  const node = $createYouTubeNode(id);
5850
6278
  lexical.$insertNodes([node]);
5851
6279
  });
5852
- setURL("");
5853
- setIsOpen(false);
6280
+ handleClose();
5854
6281
  };
5855
- return /* @__PURE__ */ jsxRuntime.jsxs(
5856
- reactComponents.Popover,
5857
- {
5858
- trapFocus: true,
5859
- withArrow: true,
5860
- open: disabled ? false : isOpen,
5861
- onOpenChange: (_, data) => {
5862
- if (!disabled) setIsOpen(data.open);
5863
- },
5864
- children: [
5865
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5866
- reactComponents.Button,
5867
- {
5868
- title: "Add youtube URL",
5869
- size: "small",
5870
- disabled,
5871
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.VideoClipRegular, { style: { color: iconColor } }),
5872
- style: {
5873
- background: isOpen && !disabled ? "#ebebeb" : "none",
5874
- border: "none",
5875
- margin: 2,
5876
- opacity: disabled ? 0.55 : 1,
5877
- cursor: disabled ? "not-allowed" : "pointer"
5878
- },
5879
- onClick: () => {
5880
- if (disabled) return;
5881
- setIsOpen((prev) => !prev);
5882
- setURL("");
5883
- }
5884
- },
5885
- "upload-video"
5886
- ) }),
5887
- /* @__PURE__ */ jsxRuntime.jsx(
5888
- reactComponents.PopoverSurface,
5889
- {
5890
- style: {
5891
- width: "270px",
5892
- opacity: disabled ? 0.6 : 1,
5893
- pointerEvents: disabled ? "none" : "auto"
5894
- },
5895
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, children: [
5896
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "URL", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
5897
- reactComponents.Input,
5898
- {
5899
- autoFocus: !disabled,
5900
- disabled,
5901
- value: url,
5902
- appearance: "underline",
5903
- placeholder: "Add Youtube video URL",
5904
- onChange: (_, v) => setURL(v.value)
5905
- }
5906
- ) }),
5907
- /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
5908
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled: disabled || !url, onClick: onHandleEmbeded, children: "Add" }),
5909
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
5910
- ] })
5911
- ] })
5912
- }
5913
- )
5914
- ]
5915
- }
5916
- );
6282
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6283
+ !isControlled && /* @__PURE__ */ jsxRuntime.jsx(
6284
+ reactComponents.Button,
6285
+ {
6286
+ title: "Add youtube URL",
6287
+ size: "small",
6288
+ disabled,
6289
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.VideoClipRegular, { style: { color: iconColor } }),
6290
+ style: {
6291
+ background: isOpen && !disabled ? "#ebebeb" : "none",
6292
+ border: "none",
6293
+ margin: 2,
6294
+ opacity: disabled ? 0.55 : 1,
6295
+ cursor: disabled ? "not-allowed" : "pointer"
6296
+ },
6297
+ onClick: () => {
6298
+ if (disabled) return;
6299
+ setURL("");
6300
+ setUrlError("");
6301
+ setInternalOpen(true);
6302
+ }
6303
+ },
6304
+ "upload-video"
6305
+ ),
6306
+ /* @__PURE__ */ jsxRuntime.jsx(
6307
+ reactComponents.Dialog,
6308
+ {
6309
+ open: isOpen,
6310
+ onOpenChange: (_, data) => {
6311
+ if (!data.open) handleClose();
6312
+ },
6313
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogSurface, { style: { maxWidth: "420px" }, children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogBody, { children: [
6314
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogTitle, { children: "Insert YouTube Video" }),
6315
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
6316
+ /* @__PURE__ */ jsxRuntime.jsx(
6317
+ reactComponents.Field,
6318
+ {
6319
+ label: "URL",
6320
+ orientation: "horizontal",
6321
+ size: "small",
6322
+ validationState: urlError ? "error" : "none",
6323
+ validationMessage: urlError || void 0,
6324
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6325
+ reactComponents.Input,
6326
+ {
6327
+ autoFocus: !disabled,
6328
+ disabled,
6329
+ value: url,
6330
+ appearance: "underline",
6331
+ placeholder: "Paste YouTube URL or video ID\u2026",
6332
+ onChange: (_, v) => {
6333
+ setURL(v.value);
6334
+ if (urlError) setUrlError("");
6335
+ },
6336
+ onKeyDown: (e) => {
6337
+ if (e.key === "Enter") onHandleEmbeded();
6338
+ }
6339
+ }
6340
+ )
6341
+ }
6342
+ ),
6343
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#94a3b8", lineHeight: 1.5 }, children: "Supports: youtube.com/watch?v=\u2026, youtu.be/\u2026, /shorts/\u2026, /live/\u2026" })
6344
+ ] }) }),
6345
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogActions, { children: [
6346
+ /* @__PURE__ */ jsxRuntime.jsx(
6347
+ reactComponents.Button,
6348
+ {
6349
+ appearance: "primary",
6350
+ size: "small",
6351
+ disabled: disabled || !url,
6352
+ onClick: onHandleEmbeded,
6353
+ children: "Add"
6354
+ }
6355
+ ),
6356
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", disabled, onClick: handleClose, children: "Cancel" })
6357
+ ] })
6358
+ ] }) })
6359
+ }
6360
+ )
6361
+ ] });
5917
6362
  };
5918
6363
  var useStyles4 = reactComponents.makeStyles({
5919
6364
  dropdown: {
@@ -5938,12 +6383,14 @@ var ALLOWED_TOKENS = {
5938
6383
  Image: true,
5939
6384
  InlineImage: true,
5940
6385
  Youtube: true,
6386
+ Insert: true,
5941
6387
  Heading: true,
5942
6388
  FontFamily: true,
5943
6389
  FontSize: true,
5944
6390
  Decorators: true,
5945
6391
  CodeBlock: true,
5946
- Align: true
6392
+ Align: true,
6393
+ PageSetup: true
5947
6394
  };
5948
6395
  function sanitizePluginGroups(groups) {
5949
6396
  if (!groups || groups.length === 0) return [];
@@ -5968,6 +6415,7 @@ var ToolBarPlugins = (props) => {
5968
6415
  const [isLowercase, setIsLowercase] = React9.useState(false);
5969
6416
  const [isCapitalize, setIsCapitalize] = React9.useState(false);
5970
6417
  const [alignment, setAlignment] = React9.useState("left");
6418
+ const [activeInsertDialog, setActiveInsertDialog] = React9.useState(null);
5971
6419
  const lastSelectionRef = React9__namespace.default.useRef(null);
5972
6420
  const presetGroups = getToolbarGroupsByLevel(props.level);
5973
6421
  const pluginGroups = React9.useMemo(() => sanitizePluginGroups(presetGroups), [presetGroups]);
@@ -6213,6 +6661,7 @@ var ToolBarPlugins = (props) => {
6213
6661
  marginRight: 8,
6214
6662
  verticalAlign: "middle"
6215
6663
  };
6664
+ const isDisabled = !isEditable || !!props.readOnly;
6216
6665
  switch (token) {
6217
6666
  case "Bold":
6218
6667
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -6276,6 +6725,95 @@ var ToolBarPlugins = (props) => {
6276
6725
  );
6277
6726
  case "Youtube":
6278
6727
  return /* @__PURE__ */ jsxRuntime.jsx(YoutubeUploadPlugin, { disabled: !isEditable || props.readOnly }, key);
6728
+ case "Insert": {
6729
+ const menuIconColor = isDisabled ? fgDisabled : fg;
6730
+ return /* @__PURE__ */ jsxRuntime.jsxs(React9__namespace.default.Fragment, { children: [
6731
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Menu, { children: [
6732
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
6733
+ reactComponents.Button,
6734
+ {
6735
+ size: "small",
6736
+ disabled: isDisabled,
6737
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.AddRegular, { style: { color: menuIconColor } }),
6738
+ style: {
6739
+ ...getButtonStyle(false),
6740
+ gap: 4,
6741
+ paddingInline: 8
6742
+ },
6743
+ children: "Insert"
6744
+ }
6745
+ ) }),
6746
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuPopover, { children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuList, { children: [
6747
+ /* @__PURE__ */ jsxRuntime.jsx(
6748
+ reactComponents.MenuItem,
6749
+ {
6750
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TableAddRegular, { style: { color: menuIconColor } }),
6751
+ onClick: () => !isDisabled && setActiveInsertDialog("table"),
6752
+ children: "Table"
6753
+ }
6754
+ ),
6755
+ /* @__PURE__ */ jsxRuntime.jsx(
6756
+ reactComponents.MenuItem,
6757
+ {
6758
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ImageAddRegular, { style: { color: menuIconColor } }),
6759
+ onClick: () => !isDisabled && setActiveInsertDialog("image"),
6760
+ children: "Image"
6761
+ }
6762
+ ),
6763
+ /* @__PURE__ */ jsxRuntime.jsx(
6764
+ reactComponents.MenuItem,
6765
+ {
6766
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ImageEditRegular, { style: { color: menuIconColor } }),
6767
+ onClick: () => !isDisabled && setActiveInsertDialog("inlineImage"),
6768
+ children: "Inline Image"
6769
+ }
6770
+ ),
6771
+ /* @__PURE__ */ jsxRuntime.jsx(
6772
+ reactComponents.MenuItem,
6773
+ {
6774
+ icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.VideoClipRegular, { style: { color: menuIconColor } }),
6775
+ onClick: () => !isDisabled && setActiveInsertDialog("youtube"),
6776
+ children: "YouTube"
6777
+ }
6778
+ )
6779
+ ] }) })
6780
+ ] }),
6781
+ /* @__PURE__ */ jsxRuntime.jsx(
6782
+ TableItemPlugin,
6783
+ {
6784
+ disabled: isDisabled,
6785
+ open: activeInsertDialog === "table",
6786
+ onClose: () => setActiveInsertDialog(null)
6787
+ }
6788
+ ),
6789
+ /* @__PURE__ */ jsxRuntime.jsx(
6790
+ InsertImageDialog,
6791
+ {
6792
+ activeEditor: editor,
6793
+ disabled: isDisabled,
6794
+ open: activeInsertDialog === "image",
6795
+ onClose: () => setActiveInsertDialog(null)
6796
+ }
6797
+ ),
6798
+ /* @__PURE__ */ jsxRuntime.jsx(
6799
+ InsertInlineImageDialog,
6800
+ {
6801
+ activeEditor: editor,
6802
+ disabled: isDisabled,
6803
+ open: activeInsertDialog === "inlineImage",
6804
+ onClose: () => setActiveInsertDialog(null)
6805
+ }
6806
+ ),
6807
+ /* @__PURE__ */ jsxRuntime.jsx(
6808
+ YoutubeUploadPlugin,
6809
+ {
6810
+ disabled: isDisabled,
6811
+ open: activeInsertDialog === "youtube",
6812
+ onClose: () => setActiveInsertDialog(null)
6813
+ }
6814
+ )
6815
+ ] }, key);
6816
+ }
6279
6817
  case "Heading": {
6280
6818
  const headingLabel = selectNodeType === "paragraph" ? "Normal" : HEADING_OPTION.find((h) => h.key === selectNodeType)?.text ?? selectNodeType;
6281
6819
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -6482,6 +7020,16 @@ var ToolBarPlugins = (props) => {
6482
7020
  key
6483
7021
  );
6484
7022
  }
7023
+ case "PageSetup":
7024
+ return /* @__PURE__ */ jsxRuntime.jsx(
7025
+ PageSetupPlugin,
7026
+ {
7027
+ disabled: !isEditable || props.readOnly,
7028
+ value: props.pageSetup,
7029
+ onChange: props.onPageSetupChange
7030
+ },
7031
+ key
7032
+ );
6485
7033
  default:
6486
7034
  return null;
6487
7035
  }
@@ -6661,7 +7209,9 @@ function BrowserSpellCheckPlugin({ enabled }) {
6661
7209
  }, [editor, enabled]);
6662
7210
  return null;
6663
7211
  }
6664
- function WordCountPlugin({ onCountChange }) {
7212
+ function WordCountPlugin({
7213
+ onCountChange
7214
+ }) {
6665
7215
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
6666
7216
  React9.useEffect(() => {
6667
7217
  return editor.registerUpdateListener(({ editorState }) => {
@@ -6674,7 +7224,9 @@ function WordCountPlugin({ onCountChange }) {
6674
7224
  }, [editor, onCountChange]);
6675
7225
  return null;
6676
7226
  }
6677
- function CharCountPlugin({ onCountChange }) {
7227
+ function CharCountPlugin({
7228
+ onCountChange
7229
+ }) {
6678
7230
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
6679
7231
  React9.useEffect(() => {
6680
7232
  return editor.registerUpdateListener(({ editorState }) => {
@@ -6758,7 +7310,10 @@ function _adaptRawSpell(data, text) {
6758
7310
  }
6759
7311
  const rawGrammar = data.grammar_correction ?? data.improved_text;
6760
7312
  const grammarCorrection = rawGrammar && rawGrammar.trim() !== text.trim() ? rawGrammar.trim() : void 0;
6761
- return { issues: issues.sort((a, b) => a.offset - b.offset), grammarCorrection };
7313
+ return {
7314
+ issues: issues.sort((a, b) => a.offset - b.offset),
7315
+ grammarCorrection
7316
+ };
6762
7317
  }
6763
7318
  function _adaptRawSuggest(data) {
6764
7319
  if (!data) return null;
@@ -6813,348 +7368,385 @@ function _makeQueryFn(fn) {
6813
7368
  };
6814
7369
  };
6815
7370
  }
6816
- var ContentEditorComponent = React9.forwardRef(
6817
- (props, ref) => {
6818
- const isReadOnly = !!props.readOnly;
6819
- const resolvedSpellCheck = React9__namespace.default.useMemo(
6820
- () => props.spellCheckFn ? _makeSpellCheckFn(props.spellCheckFn) : props.useSpellCheck,
6821
- // eslint-disable-next-line react-hooks/exhaustive-deps
6822
- [props.spellCheckFn, props.useSpellCheck]
6823
- );
6824
- const resolvedQuery = React9__namespace.default.useMemo(
6825
- () => props.suggestFn ? _makeQueryFn(props.suggestFn) : props.useQuery,
6826
- // eslint-disable-next-line react-hooks/exhaustive-deps
6827
- [props.suggestFn, props.useQuery]
6828
- );
6829
- const [floatingAnchorElem, setFloatingAnchorElem] = React9.useState(null);
6830
- const [isLinkEditMode, setIsLinkEditMode] = React9.useState(false);
6831
- const [wordCount, setWordCount] = React9.useState(0);
6832
- const handleWordCount = React9.useCallback((count) => setWordCount(count), []);
6833
- const [charCount, setCharCount] = React9.useState(0);
6834
- const handleCharCount = React9.useCallback((count) => setCharCount(count), []);
6835
- const [refErrors, setRefErrors] = React9.useState([]);
6836
- const contentEditableDomRef = React9.useRef(null);
6837
- const previousOverLimitRef = React9.useRef(false);
6838
- const focusedRef = React9.useRef(false);
6839
- const setFocused = (focused) => {
6840
- focusedRef.current = focused;
6841
- };
6842
- const containerRef = React9.useRef(null);
6843
- const onAnchorRef = (elem) => {
6844
- if (elem) setFloatingAnchorElem(elem);
6845
- };
6846
- const initialConfig = {
6847
- namespace: props.namespace,
6848
- theme,
6849
- onError: () => {
6850
- },
6851
- nodes: [
6852
- richText.HeadingNode,
6853
- richText.QuoteNode,
6854
- code.CodeHighlightNode,
6855
- code.CodeNode,
6856
- list.ListNode,
6857
- list.ListItemNode,
6858
- link.LinkNode,
6859
- link.AutoLinkNode,
6860
- table.TableNode,
6861
- table.TableRowNode,
6862
- table.TableCellNode,
6863
- ImageNode,
6864
- InlineImageNode,
6865
- YouTubeNode,
6866
- PageBreakNode,
6867
- AutocompleteNode,
6868
- SpellErrorNode,
6869
- HtmlBlockNode
6870
- ]
6871
- };
6872
- const EditorStyles = react.mergeStyleSets({
6873
- editorPlaceholder: {
6874
- color: "var(--colorNeutralForeground3, grey)",
6875
- position: "absolute",
6876
- top: props.level !== "none" /* None */ ? "17px" : "27px",
6877
- left: 20,
6878
- right: 20,
6879
- fontSize: "14px",
6880
- pointerEvents: "none",
6881
- userSelect: "none"
6882
- },
6883
- contentEditor: {
6884
- zIndex: 0,
6885
- flex: "auto",
6886
- outline: "none",
6887
- overflow: "auto",
6888
- marginTop: "0px",
6889
- paddingLeft: "20px",
6890
- paddingRight: "20px",
6891
- position: "relative",
6892
- background: "var(--colorNeutralBackground1, #ffffff)",
6893
- justifyContent: "center",
6894
- height: props.contentHeight ?? "100%",
6895
- ...isReadOnly && {
6896
- cursor: "not-allowed",
6897
- opacity: 0.75,
6898
- userSelect: "text"
6899
- }
6900
- }
6901
- });
6902
- const urlRegExp = new RegExp(
6903
- /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/
6904
- );
6905
- const validateUrl = (url) => url === "https://" || urlRegExp.test(url);
6906
- const handleReadOnlyClickCapture = (e) => {
6907
- if (!isReadOnly) return;
6908
- const target = e.target;
6909
- const anchor = target?.closest?.("a");
6910
- if (anchor) {
6911
- e.preventDefault();
6912
- e.stopPropagation();
7371
+ var ContentEditorComponent = React9.forwardRef((props, ref) => {
7372
+ const isReadOnly = !!props.readOnly;
7373
+ const resolvedSpellCheck = React9__namespace.default.useMemo(
7374
+ () => props.spellCheckFn ? _makeSpellCheckFn(props.spellCheckFn) : props.useSpellCheck,
7375
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7376
+ [props.spellCheckFn, props.useSpellCheck]
7377
+ );
7378
+ const resolvedQuery = React9__namespace.default.useMemo(
7379
+ () => props.suggestFn ? _makeQueryFn(props.suggestFn) : props.useQuery,
7380
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7381
+ [props.suggestFn, props.useQuery]
7382
+ );
7383
+ const [floatingAnchorElem, setFloatingAnchorElem] = React9.useState(null);
7384
+ const [isLinkEditMode, setIsLinkEditMode] = React9.useState(false);
7385
+ const [wordCount, setWordCount] = React9.useState(0);
7386
+ const handleWordCount = React9.useCallback(
7387
+ (count) => setWordCount(count),
7388
+ []
7389
+ );
7390
+ const [charCount, setCharCount] = React9.useState(0);
7391
+ const handleCharCount = React9.useCallback(
7392
+ (count) => setCharCount(count),
7393
+ []
7394
+ );
7395
+ const [refErrors, setRefErrors] = React9.useState([]);
7396
+ const [pageSetup, setPageSetup] = React9.useState(DEFAULT_PAGE_SETUP);
7397
+ const pageCanvas = resolvePageCanvasMetrics(pageSetup);
7398
+ const contentEditableDomRef = React9.useRef(null);
7399
+ const previousOverLimitRef = React9.useRef(false);
7400
+ const focusedRef = React9.useRef(false);
7401
+ const setFocused = (focused) => {
7402
+ focusedRef.current = focused;
7403
+ };
7404
+ const containerRef = React9.useRef(null);
7405
+ const onAnchorRef = (elem) => {
7406
+ if (elem) setFloatingAnchorElem(elem);
7407
+ };
7408
+ const initialConfig = {
7409
+ namespace: props.namespace,
7410
+ theme,
7411
+ onError: () => {
7412
+ },
7413
+ nodes: [
7414
+ richText.HeadingNode,
7415
+ richText.QuoteNode,
7416
+ code.CodeHighlightNode,
7417
+ code.CodeNode,
7418
+ list.ListNode,
7419
+ list.ListItemNode,
7420
+ link.LinkNode,
7421
+ link.AutoLinkNode,
7422
+ table.TableNode,
7423
+ table.TableRowNode,
7424
+ table.TableCellNode,
7425
+ ImageNode,
7426
+ InlineImageNode,
7427
+ YouTubeNode,
7428
+ PageBreakNode,
7429
+ AutocompleteNode,
7430
+ SpellErrorNode,
7431
+ HtmlBlockNode
7432
+ ]
7433
+ };
7434
+ const EditorStyles = react.mergeStyleSets({
7435
+ editorPlaceholder: {
7436
+ color: "var(--colorNeutralForeground3, grey)",
7437
+ position: "absolute",
7438
+ top: props.level !== "none" /* None */ ? "17px" : "27px",
7439
+ left: pageCanvas.paddingPx,
7440
+ right: pageCanvas.paddingPx,
7441
+ fontSize: "14px",
7442
+ pointerEvents: "none",
7443
+ userSelect: "none"
7444
+ },
7445
+ contentEditor: {
7446
+ zIndex: 0,
7447
+ flex: "auto",
7448
+ outline: "none",
7449
+ overflow: "auto",
7450
+ marginTop: "0px",
7451
+ position: "relative",
7452
+ background: "var(--colorNeutralBackground1, #ffffff)",
7453
+ justifyContent: "center",
7454
+ height: props.contentHeight ?? "100%",
7455
+ ...isReadOnly && {
7456
+ cursor: "not-allowed",
7457
+ opacity: 0.75,
7458
+ userSelect: "text"
6913
7459
  }
6914
- };
6915
- const [touched, setTouched] = React9.useState(false);
6916
- const isOverLimit = props.wordLimit !== void 0 && wordCount > props.wordLimit;
6917
- const internalErrors = [];
6918
- if (isOverLimit) {
6919
- const m = props.errorMessages?.wordLimitExceeded;
6920
- internalErrors.push(
6921
- typeof m === "function" ? m(wordCount, props.wordLimit) : m ?? `Word limit exceeded (${wordCount} / ${props.wordLimit} words used)`
6922
- );
6923
- }
6924
- if (props.required && touched && wordCount === 0) {
6925
- internalErrors.push(
6926
- props.errorMessages?.required ?? "This field is required"
6927
- );
6928
7460
  }
6929
- if (props.minWords !== void 0 && touched && wordCount < props.minWords) {
6930
- const m = props.errorMessages?.minWords;
6931
- internalErrors.push(
6932
- typeof m === "function" ? m(wordCount, props.minWords) : m ?? `Minimum ${props.minWords} words required (${wordCount} entered)`
6933
- );
6934
- }
6935
- if (props.maxChars !== void 0 && charCount > props.maxChars) {
6936
- const m = props.errorMessages?.maxCharsExceeded;
6937
- internalErrors.push(
6938
- typeof m === "function" ? m(charCount, props.maxChars) : m ?? `Character limit exceeded (${charCount} / ${props.maxChars} characters used)`
6939
- );
7461
+ });
7462
+ const urlRegExp = new RegExp(
7463
+ /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/
7464
+ );
7465
+ const validateUrl = (url) => url === "https://" || urlRegExp.test(url);
7466
+ const handleReadOnlyClickCapture = (e) => {
7467
+ if (!isReadOnly) return;
7468
+ const target = e.target;
7469
+ const anchor = target?.closest?.("a");
7470
+ if (anchor) {
7471
+ e.preventDefault();
7472
+ e.stopPropagation();
6940
7473
  }
6941
- if (props.minChars !== void 0 && touched && charCount < props.minChars && charCount > 0) {
6942
- const m = props.errorMessages?.minCharsRequired;
6943
- internalErrors.push(
6944
- typeof m === "function" ? m(charCount, props.minChars) : m ?? `Minimum ${props.minChars} characters required (${charCount} entered)`
6945
- );
7474
+ };
7475
+ const [touched, setTouched] = React9.useState(false);
7476
+ const isOverLimit = props.wordLimit !== void 0 && wordCount > props.wordLimit;
7477
+ const internalErrors = [];
7478
+ if (isOverLimit) {
7479
+ const m = props.errorMessages?.wordLimitExceeded;
7480
+ internalErrors.push(
7481
+ typeof m === "function" ? m(wordCount, props.wordLimit) : m ?? `Word limit exceeded (${wordCount} / ${props.wordLimit} words used)`
7482
+ );
7483
+ }
7484
+ if (props.required && touched && wordCount === 0) {
7485
+ internalErrors.push(
7486
+ props.errorMessages?.required ?? "This field is required"
7487
+ );
7488
+ }
7489
+ if (props.minWords !== void 0 && touched && wordCount < props.minWords) {
7490
+ const m = props.errorMessages?.minWords;
7491
+ internalErrors.push(
7492
+ typeof m === "function" ? m(wordCount, props.minWords) : m ?? `Minimum ${props.minWords} words required (${wordCount} entered)`
7493
+ );
7494
+ }
7495
+ if (props.maxChars !== void 0 && charCount > props.maxChars) {
7496
+ const m = props.errorMessages?.maxCharsExceeded;
7497
+ internalErrors.push(
7498
+ typeof m === "function" ? m(charCount, props.maxChars) : m ?? `Character limit exceeded (${charCount} / ${props.maxChars} characters used)`
7499
+ );
7500
+ }
7501
+ if (props.minChars !== void 0 && touched && charCount < props.minChars && charCount > 0) {
7502
+ const m = props.errorMessages?.minCharsRequired;
7503
+ internalErrors.push(
7504
+ typeof m === "function" ? m(charCount, props.minChars) : m ?? `Minimum ${props.minChars} characters required (${charCount} entered)`
7505
+ );
7506
+ }
7507
+ const allErrors = [
7508
+ ...internalErrors,
7509
+ ...props.errors ?? [],
7510
+ ...refErrors
7511
+ ];
7512
+ const hasErrors = allErrors.length > 0;
7513
+ const hasRedBorder = hasErrors;
7514
+ React9.useEffect(() => {
7515
+ if (props.wordLimit === void 0 || !props.onWordLimitExceeded) return;
7516
+ const wasOverLimit = previousOverLimitRef.current;
7517
+ if (isOverLimit !== wasOverLimit) {
7518
+ props.onWordLimitExceeded({
7519
+ wordCount,
7520
+ wordLimit: props.wordLimit,
7521
+ exceeded: isOverLimit
7522
+ });
7523
+ previousOverLimitRef.current = isOverLimit;
6946
7524
  }
6947
- const allErrors = [...internalErrors, ...props.errors ?? [], ...refErrors];
6948
- const hasErrors = allErrors.length > 0;
6949
- const hasRedBorder = hasErrors;
6950
- React9.useEffect(() => {
6951
- if (props.wordLimit === void 0 || !props.onWordLimitExceeded) return;
6952
- const wasOverLimit = previousOverLimitRef.current;
6953
- if (isOverLimit !== wasOverLimit) {
6954
- props.onWordLimitExceeded({
6955
- wordCount,
6956
- wordLimit: props.wordLimit,
6957
- exceeded: isOverLimit
6958
- });
6959
- previousOverLimitRef.current = isOverLimit;
6960
- }
6961
- }, [isOverLimit, wordCount, props.wordLimit, props.onWordLimitExceeded]);
6962
- return /* @__PURE__ */ jsxRuntime.jsx(reactComponents.FluentProvider, { theme: reactComponents.webLightTheme, style: { height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(LexicalComposer.LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, style: { height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsxs(
6963
- react.Stack,
6964
- {
6965
- style: {
6966
- zIndex: 1e3,
6967
- background: "#fff",
6968
- borderRadius: "2px",
6969
- width: props.width ?? "100%",
6970
- height: props.height ?? "100%",
6971
- margin: props.margin ?? "5px auto",
6972
- border: `1px solid ${hasRedBorder ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
6973
- transition: "border-color 0.2s",
6974
- display: "flex",
6975
- flexDirection: "column"
6976
- },
6977
- children: [
6978
- /* @__PURE__ */ jsxRuntime.jsx(
6979
- "div",
6980
- {
6981
- style: {
6982
- pointerEvents: isReadOnly ? "none" : "auto",
6983
- position: "sticky",
6984
- opacity: isReadOnly ? 0.85 : 1
6985
- },
6986
- children: /* @__PURE__ */ jsxRuntime.jsx(
6987
- ToolBarPlugins,
7525
+ }, [isOverLimit, wordCount, props.wordLimit, props.onWordLimitExceeded]);
7526
+ return /* @__PURE__ */ jsxRuntime.jsx(reactComponents.FluentProvider, { theme: reactComponents.webLightTheme, style: { height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(LexicalComposer.LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, style: { height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsxs(
7527
+ react.Stack,
7528
+ {
7529
+ style: {
7530
+ zIndex: 1e3,
7531
+ background: "#fff",
7532
+ borderRadius: "2px",
7533
+ width: props.width ?? "100%",
7534
+ height: props.height ?? "100%",
7535
+ margin: props.margin ?? "5px auto",
7536
+ border: `1px solid ${hasRedBorder ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
7537
+ transition: "border-color 0.2s",
7538
+ display: "flex",
7539
+ flexDirection: "column"
7540
+ },
7541
+ children: [
7542
+ /* @__PURE__ */ jsxRuntime.jsx(
7543
+ "div",
7544
+ {
7545
+ style: {
7546
+ pointerEvents: isReadOnly ? "none" : "auto",
7547
+ position: "sticky",
7548
+ opacity: isReadOnly ? 0.85 : 1
7549
+ },
7550
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7551
+ ToolBarPlugins,
7552
+ {
7553
+ level: props.level ?? "basic" /* Basic */,
7554
+ readOnly: props.readOnly,
7555
+ pageSetup,
7556
+ onPageSetupChange: setPageSetup
7557
+ }
7558
+ )
7559
+ }
7560
+ ),
7561
+ /* @__PURE__ */ jsxRuntime.jsxs(
7562
+ "div",
7563
+ {
7564
+ style: {
7565
+ position: "relative",
7566
+ flexGrow: 1,
7567
+ padding: "15px 0px",
7568
+ overflowY: "scroll",
7569
+ overflowX: "auto",
7570
+ minWidth: 0,
7571
+ background: pageCanvas.widthPx !== void 0 ? "#eef0f2" : void 0
7572
+ },
7573
+ onClickCapture: handleReadOnlyClickCapture,
7574
+ children: [
7575
+ /* @__PURE__ */ jsxRuntime.jsx(
7576
+ LexicalRichTextPlugin.RichTextPlugin,
7577
+ {
7578
+ ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary,
7579
+ contentEditable: /* @__PURE__ */ jsxRuntime.jsx(
7580
+ "div",
7581
+ {
7582
+ className: "editor",
7583
+ style: { height: "100%", position: "relative" },
7584
+ ref: onAnchorRef,
7585
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7586
+ LexicalContentEditable.ContentEditable,
7587
+ {
7588
+ ref: contentEditableDomRef,
7589
+ className: react.css(EditorStyles.contentEditor),
7590
+ style: {
7591
+ paddingTop: props.level !== "none" /* None */ ? 0 : 10,
7592
+ paddingLeft: pageCanvas.paddingPx,
7593
+ paddingRight: pageCanvas.paddingPx,
7594
+ maxWidth: pageCanvas.widthPx,
7595
+ marginLeft: pageCanvas.widthPx !== void 0 ? "auto" : void 0,
7596
+ marginRight: pageCanvas.widthPx !== void 0 ? "auto" : void 0,
7597
+ boxShadow: pageCanvas.widthPx !== void 0 ? "0 0 0 1px rgba(0,0,0,0.08), 0 2px 8px rgba(0,0,0,0.08)" : void 0
7598
+ },
7599
+ spellCheck: !resolvedSpellCheck,
7600
+ autoCorrect: resolvedSpellCheck ? "off" : void 0,
7601
+ autoCapitalize: resolvedSpellCheck ? "off" : void 0
7602
+ }
7603
+ )
7604
+ }
7605
+ ),
7606
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx(react.Stack, { className: react.css(EditorStyles.editorPlaceholder), children: props.placeholder })
7607
+ }
7608
+ ),
7609
+ props.wordLimit !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
7610
+ "div",
6988
7611
  {
6989
- level: props.level ?? "basic" /* Basic */,
6990
- readOnly: props.readOnly
7612
+ style: {
7613
+ position: "sticky",
7614
+ bottom: 0,
7615
+ display: "flex",
7616
+ justifyContent: "flex-end",
7617
+ paddingRight: 14,
7618
+ pointerEvents: "none",
7619
+ userSelect: "none"
7620
+ },
7621
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
7622
+ "span",
7623
+ {
7624
+ style: {
7625
+ fontSize: "11px",
7626
+ color: isOverLimit ? "#c4272c" : "var(--colorNeutralForeground3, #aaa)",
7627
+ fontWeight: isOverLimit ? 600 : 400,
7628
+ transition: "color 0.2s, font-weight 0.2s"
7629
+ },
7630
+ children: [
7631
+ wordCount,
7632
+ " / ",
7633
+ props.wordLimit,
7634
+ " words"
7635
+ ]
7636
+ }
7637
+ )
6991
7638
  }
6992
7639
  )
6993
- }
6994
- ),
6995
- /* @__PURE__ */ jsxRuntime.jsxs(
6996
- "div",
6997
- {
6998
- style: {
6999
- position: "relative",
7000
- flexGrow: 1,
7001
- padding: "15px 0px",
7002
- overflowY: "scroll",
7003
- overflowX: "auto",
7004
- minWidth: 0
7005
- },
7006
- onClickCapture: handleReadOnlyClickCapture,
7007
- children: [
7008
- /* @__PURE__ */ jsxRuntime.jsx(
7009
- LexicalRichTextPlugin.RichTextPlugin,
7010
- {
7011
- ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary,
7012
- contentEditable: /* @__PURE__ */ jsxRuntime.jsx(
7013
- "div",
7014
- {
7015
- className: "editor",
7016
- style: { height: "100%", position: "relative" },
7017
- ref: onAnchorRef,
7018
- children: /* @__PURE__ */ jsxRuntime.jsx(
7019
- LexicalContentEditable.ContentEditable,
7020
- {
7021
- ref: contentEditableDomRef,
7022
- className: react.css(EditorStyles.contentEditor),
7023
- style: { paddingTop: props.level !== "none" /* None */ ? 0 : 10 },
7024
- spellCheck: !resolvedSpellCheck,
7025
- autoCorrect: resolvedSpellCheck ? "off" : void 0,
7026
- autoCapitalize: resolvedSpellCheck ? "off" : void 0
7027
- }
7028
- )
7029
- }
7030
- ),
7031
- placeholder: /* @__PURE__ */ jsxRuntime.jsx(react.Stack, { className: react.css(EditorStyles.editorPlaceholder), children: props.placeholder })
7032
- }
7033
- ),
7034
- props.wordLimit !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
7035
- "div",
7036
- {
7037
- style: {
7038
- position: "sticky",
7039
- bottom: 0,
7040
- display: "flex",
7041
- justifyContent: "flex-end",
7042
- paddingRight: 14,
7043
- pointerEvents: "none",
7044
- userSelect: "none"
7045
- },
7046
- children: /* @__PURE__ */ jsxRuntime.jsxs(
7047
- "span",
7048
- {
7049
- style: {
7050
- fontSize: "11px",
7051
- color: isOverLimit ? "#c4272c" : "var(--colorNeutralForeground3, #aaa)",
7052
- fontWeight: isOverLimit ? 600 : 400,
7053
- transition: "color 0.2s, font-weight 0.2s"
7054
- },
7055
- children: [
7056
- wordCount,
7057
- " / ",
7058
- props.wordLimit,
7059
- " words"
7060
- ]
7061
- }
7062
- )
7063
- }
7064
- )
7065
- ]
7066
- }
7067
- ),
7068
- hasErrors && /* @__PURE__ */ jsxRuntime.jsx(
7069
- "div",
7070
- {
7071
- style: {
7072
- borderTop: "1px solid #fbd5d5",
7073
- background: "#fff8f8",
7074
- padding: "6px 12px 8px",
7075
- display: "flex",
7076
- flexDirection: "column",
7077
- gap: 4
7078
- },
7079
- children: allErrors.map((err, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
7080
- /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ErrorCircleRegular, { style: { fontSize: 14, color: "#c4272c", flexShrink: 0 } }),
7081
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#c4272c" }, children: err })
7082
- ] }, i))
7083
- }
7084
- ),
7085
- /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyPlugin, { readonly: isReadOnly }),
7086
- /* @__PURE__ */ jsxRuntime.jsx(BrowserSpellCheckPlugin, { enabled: !resolvedSpellCheck }),
7087
- /* @__PURE__ */ jsxRuntime.jsx(
7088
- FocusEventsPlugin,
7089
- {
7090
- onFocus: props.onFocus,
7091
- onBlur: () => {
7092
- setTouched(true);
7093
- props.onBlur?.();
7640
+ ]
7641
+ }
7642
+ ),
7643
+ hasErrors && /* @__PURE__ */ jsxRuntime.jsx(
7644
+ "div",
7645
+ {
7646
+ style: {
7647
+ borderTop: "1px solid #fbd5d5",
7648
+ background: "#fff8f8",
7649
+ padding: "6px 12px 8px",
7650
+ display: "flex",
7651
+ flexDirection: "column",
7652
+ gap: 4
7653
+ },
7654
+ children: allErrors.map((err, i) => /* @__PURE__ */ jsxRuntime.jsxs(
7655
+ "div",
7656
+ {
7657
+ style: { display: "flex", alignItems: "center", gap: 6 },
7658
+ children: [
7659
+ /* @__PURE__ */ jsxRuntime.jsx(
7660
+ reactIcons.ErrorCircleRegular,
7661
+ {
7662
+ style: { fontSize: 14, color: "#c4272c", flexShrink: 0 }
7663
+ }
7664
+ ),
7665
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#c4272c" }, children: err })
7666
+ ]
7094
7667
  },
7095
- setFocused,
7096
- containerRef
7097
- }
7098
- ),
7099
- props.autoFocus && !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(LexicalAutoFocusPlugin.AutoFocusPlugin, {}),
7100
- /* @__PURE__ */ jsxRuntime.jsx(LexicalHistoryPlugin.HistoryPlugin, {}),
7101
- /* @__PURE__ */ jsxRuntime.jsx(LexicalListPlugin.ListPlugin, {}),
7102
- /* @__PURE__ */ jsxRuntime.jsx(LexicalLinkPlugin.LinkPlugin, { validateUrl }),
7103
- /* @__PURE__ */ jsxRuntime.jsx(LexicalAutoLinkPlugin.AutoLinkPlugin, { matchers: MATCHERS }),
7104
- /* @__PURE__ */ jsxRuntime.jsx(LexicalTablePlugin.TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true }),
7105
- !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(YoutubeDeletePlugin, {}),
7106
- !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsxRuntime.jsx(TableActionMenuPlugin, {}),
7107
- !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsxRuntime.jsx(TableCellResizerPlugin, { anchorElem: floatingAnchorElem }),
7108
- !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
7109
- FloatingLinkEditorPlugin,
7110
- {
7111
- anchorElem: floatingAnchorElem,
7112
- isLinkEditMode,
7113
- setIsLinkEditMode
7114
- }
7115
- ),
7116
- !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(ImagePlugin_default, {}),
7117
- !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(InlineImage_default, {}),
7118
- !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(PageBreakPlugin, {}),
7119
- !!resolvedQuery && !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
7120
- AutocompletePlugin,
7121
- {
7122
- useQuery: resolvedQuery,
7123
- isReadOnly,
7124
- onSuggestionShown: props.onSuggestionShown,
7125
- onSuggestionAccept: props.onSuggestionAccept,
7126
- idleMs: props.suggestIdleMs ?? 300,
7127
- minWords: 4,
7128
- prefixWindow: 300
7129
- }
7130
- ),
7131
- !!resolvedSpellCheck && !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
7132
- SpellCheckPlugin,
7133
- {
7134
- useSpellCheck: resolvedSpellCheck,
7135
- onSpellCheckAccept: props.onSpellCheckAccept,
7136
- idleMs: props.spellCheckIdleMs ?? 1200,
7137
- enabled: props.spellCheckEnabled !== false
7138
- }
7139
- ),
7140
- !isReadOnly && props.showFloatingToolbar && /* @__PURE__ */ jsxRuntime.jsx(CharacterStylesPopupPlugin, {}),
7141
- /* @__PURE__ */ jsxRuntime.jsx(CustomOnChangePlugin, { value: props.value, onChange: props.onChange }),
7142
- (props.wordLimit !== void 0 || props.required || props.minWords !== void 0) && /* @__PURE__ */ jsxRuntime.jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7143
- (props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsxRuntime.jsx(CharCountPlugin, { onCountChange: handleCharCount }),
7144
- /* @__PURE__ */ jsxRuntime.jsx(
7145
- RefApiPlugin,
7146
- {
7147
- forwardedRef: ref,
7148
- contentEditableDomRef,
7149
- focusedRef,
7150
- setRefErrors
7151
- }
7152
- )
7153
- ]
7154
- }
7155
- ) }) }) });
7156
- }
7157
- );
7668
+ i
7669
+ ))
7670
+ }
7671
+ ),
7672
+ /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyPlugin, { readonly: isReadOnly }),
7673
+ /* @__PURE__ */ jsxRuntime.jsx(BrowserSpellCheckPlugin, { enabled: !resolvedSpellCheck }),
7674
+ /* @__PURE__ */ jsxRuntime.jsx(
7675
+ FocusEventsPlugin,
7676
+ {
7677
+ onFocus: props.onFocus,
7678
+ onBlur: () => {
7679
+ setTouched(true);
7680
+ props.onBlur?.();
7681
+ },
7682
+ setFocused,
7683
+ containerRef
7684
+ }
7685
+ ),
7686
+ props.autoFocus && !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(LexicalAutoFocusPlugin.AutoFocusPlugin, {}),
7687
+ /* @__PURE__ */ jsxRuntime.jsx(LexicalHistoryPlugin.HistoryPlugin, {}),
7688
+ /* @__PURE__ */ jsxRuntime.jsx(LexicalListPlugin.ListPlugin, {}),
7689
+ /* @__PURE__ */ jsxRuntime.jsx(LexicalLinkPlugin.LinkPlugin, { validateUrl }),
7690
+ /* @__PURE__ */ jsxRuntime.jsx(LexicalAutoLinkPlugin.AutoLinkPlugin, { matchers: MATCHERS }),
7691
+ /* @__PURE__ */ jsxRuntime.jsx(LexicalTablePlugin.TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true }),
7692
+ !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(YoutubeDeletePlugin, {}),
7693
+ !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsxRuntime.jsx(TableActionMenuPlugin, {}),
7694
+ !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsxRuntime.jsx(TableCellResizerPlugin, { anchorElem: floatingAnchorElem }),
7695
+ !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
7696
+ FloatingLinkEditorPlugin,
7697
+ {
7698
+ anchorElem: floatingAnchorElem,
7699
+ isLinkEditMode,
7700
+ setIsLinkEditMode
7701
+ }
7702
+ ),
7703
+ !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(ImagePlugin_default, {}),
7704
+ !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(InlineImage_default, {}),
7705
+ !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(PageBreakPlugin, {}),
7706
+ !!resolvedQuery && !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
7707
+ AutocompletePlugin,
7708
+ {
7709
+ useQuery: resolvedQuery,
7710
+ isReadOnly,
7711
+ onSuggestionShown: props.onSuggestionShown,
7712
+ onSuggestionAccept: props.onSuggestionAccept,
7713
+ idleMs: props.suggestIdleMs ?? 300,
7714
+ minWords: 4,
7715
+ prefixWindow: 300
7716
+ }
7717
+ ),
7718
+ !!resolvedSpellCheck && !isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
7719
+ SpellCheckPlugin,
7720
+ {
7721
+ useSpellCheck: resolvedSpellCheck,
7722
+ onSpellCheckAccept: props.onSpellCheckAccept,
7723
+ idleMs: props.spellCheckIdleMs ?? 1200,
7724
+ enabled: props.spellCheckEnabled !== false
7725
+ }
7726
+ ),
7727
+ !isReadOnly && props.showFloatingToolbar && /* @__PURE__ */ jsxRuntime.jsx(CharacterStylesPopupPlugin, {}),
7728
+ /* @__PURE__ */ jsxRuntime.jsx(
7729
+ CustomOnChangePlugin,
7730
+ {
7731
+ value: props.value,
7732
+ onChange: props.onChange
7733
+ }
7734
+ ),
7735
+ (props.wordLimit !== void 0 || props.required || props.minWords !== void 0) && /* @__PURE__ */ jsxRuntime.jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7736
+ (props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsxRuntime.jsx(CharCountPlugin, { onCountChange: handleCharCount }),
7737
+ /* @__PURE__ */ jsxRuntime.jsx(
7738
+ RefApiPlugin,
7739
+ {
7740
+ forwardedRef: ref,
7741
+ contentEditableDomRef,
7742
+ focusedRef,
7743
+ setRefErrors
7744
+ }
7745
+ )
7746
+ ]
7747
+ }
7748
+ ) }) }) });
7749
+ });
7158
7750
 
7159
7751
  exports.ContentEditorComponent = ContentEditorComponent;
7160
7752
  exports.ContentEditorLevel = ContentEditorLevel;