@tarviks/lexical-rich-editor 1.0.11 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,9 +6,9 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
6
6
  import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
7
7
  import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
8
8
  import { createCommand, DecoratorNode, COMMAND_PRIORITY_HIGH, TextNode, ElementNode, $setSelection, $getSelection, $isRangeSelection, SELECTION_CHANGE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, $findMatchingParent, COMMAND_PRIORITY_LOW, KEY_DOWN_COMMAND, $insertNodes, $isRootOrShadowRoot, $createParagraphNode, COMMAND_PRIORITY_EDITOR, PASTE_COMMAND, DRAGSTART_COMMAND, DRAGOVER_COMMAND, DROP_COMMAND, $getNodeByKey, $getRoot, $createTextNode, $isTextNode, $createRangeSelection, $isElementNode, $isNodeSelection, CLICK_COMMAND, $applyNodeReplacement, FORMAT_TEXT_COMMAND, $isDecoratorNode, $getNearestNodeFromDOMNode, COMMAND_PRIORITY_CRITICAL, REDO_COMMAND, UNDO_COMMAND, getDOMSelection, KEY_ESCAPE_COMMAND, isHTMLElement, getDOMSelectionFromTarget, SKIP_SELECTION_FOCUS_TAG, createEditor, KEY_ENTER_COMMAND } from 'lexical';
9
- import { mergeStyleSets, Stack, css, useTheme, Callout, TextField, DefaultButton } from '@fluentui/react';
10
- import { makeStyles, FluentProvider, webLightTheme, Menu, MenuTrigger, MenuPopover, MenuList, MenuGroup, MenuGroupHeader, MenuItem, MenuDivider, Dropdown, Option, ToolbarDivider, Button, Popover, PopoverTrigger, PopoverSurface, Field, Input } from '@fluentui/react-components';
11
- import { ErrorCircleRegular, ChevronDown12Regular, ArrowUpRegular, RowTripleRegular, ArrowDownRegular, ArrowLeftRegular, ColumnTripleRegular, ArrowRightRegular, DeleteRegular, TextCaseUppercaseFilled, TextCaseLowercaseFilled, TextCaseTitleFilled, TextStrikethroughFilled, TextSubscriptFilled, TextSuperscriptFilled, HighlightAccentFilled, TextBulletListLtrFilled, TextNumberListLtrFilled, DocumentPageBreakRegular, CommentQuoteRegular, TextUnderlineFilled, TextItalicFilled, TextBold24Regular, TextAlignLeftFilled, TextAlignCenterFilled, TextAlignRightFilled, TextAlignJustifyFilled, VideoClipRegular, ImageEditRegular, AttachFilled, ImageAddRegular, TableAddRegular, LinkAddRegular, TextColorRegular, PaintBucket16Filled, CodeFilled, LinkFilled } from '@fluentui/react-icons';
9
+ import { mergeStyleSets, Stack, css, useTheme, Callout, TextField } from '@fluentui/react';
10
+ import { makeStyles, FluentProvider, webLightTheme, Menu, MenuTrigger, MenuPopover, MenuList, MenuGroup, MenuGroupHeader, MenuItem, MenuDivider, Dropdown, Option, ToolbarDivider, Button, MenuItemRadio, Popover, PopoverTrigger, PopoverSurface, Field, Input } from '@fluentui/react-components';
11
+ import { ErrorCircleRegular, ChevronDown12Regular, ArrowUpRegular, RowTripleRegular, ArrowDownRegular, ArrowLeftRegular, ColumnTripleRegular, ArrowRightRegular, DeleteRegular, TextCaseUppercaseFilled, TextCaseLowercaseFilled, TextCaseTitleFilled, TextStrikethroughFilled, TextSubscriptFilled, TextSuperscriptFilled, HighlightAccentFilled, TextBulletListLtrFilled, TextNumberListLtrFilled, DocumentPageBreakRegular, CommentQuoteRegular, TextUnderlineFilled, TextItalicFilled, TextBold24Regular, DocumentRegular, TextAlignLeftFilled, TextAlignCenterFilled, TextAlignRightFilled, TextAlignJustifyFilled, VideoClipRegular, ImageEditRegular, AttachFilled, ImageAddRegular, TableAddRegular, LinkAddRegular, TextColorRegular, PaintBucket16Filled, CodeFilled, LinkFilled, Dismiss16Regular } from '@fluentui/react-icons';
12
12
  import { CodeHighlightNode, CodeNode, $isCodeHighlightNode } from '@lexical/code';
13
13
  import { LinkNode, AutoLinkNode, $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND, $createLinkNode } from '@lexical/link';
14
14
  import { ListNode, ListItemNode, $isListNode, REMOVE_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list';
@@ -1228,6 +1228,46 @@ var ContentEditorLevel = /* @__PURE__ */ ((ContentEditorLevel2) => {
1228
1228
  ContentEditorLevel2["Pro"] = "pro";
1229
1229
  return ContentEditorLevel2;
1230
1230
  })(ContentEditorLevel || {});
1231
+
1232
+ // src/Types/PageSetup.ts
1233
+ var DEFAULT_PAGE_SETUP = {
1234
+ size: "pageless",
1235
+ orientation: "portrait",
1236
+ margin: "normal"
1237
+ };
1238
+ var PAGE_SIZE_OPTIONS = [
1239
+ { key: "a4", label: 'A4 (8.27" x 11.69")', widthIn: 8.27, heightIn: 11.69 },
1240
+ { key: "letter", label: 'Letter (8.5" x 11")', widthIn: 8.5, heightIn: 11 },
1241
+ { key: "legal", label: 'Legal (8.5" x 14")', widthIn: 8.5, heightIn: 14 },
1242
+ { key: "tabloid", label: 'Tabloid (11" x 17")', widthIn: 11, heightIn: 17 },
1243
+ { key: "a3", label: 'A3 (11.69" x 16.54")', widthIn: 11.69, heightIn: 16.54 },
1244
+ { key: "a5", label: 'A5 (5.83" x 8.27")', widthIn: 5.83, heightIn: 8.27 },
1245
+ { key: "b4", label: 'B4 (9.84" x 13.90")', widthIn: 9.84, heightIn: 13.9 },
1246
+ { key: "b5", label: 'B5 (6.93" x 9.84")', widthIn: 6.93, heightIn: 9.84 },
1247
+ { key: "statement", label: 'Statement (5.5" x 8.5")', widthIn: 5.5, heightIn: 8.5 },
1248
+ { key: "executive", label: 'Executive (7.25" x 10.5")', widthIn: 7.25, heightIn: 10.5 },
1249
+ { key: "folio", label: 'Folio (8.5" x 13")', widthIn: 8.5, heightIn: 13 }
1250
+ ];
1251
+ var MARGIN_OPTIONS = [
1252
+ { key: "narrow", label: 'Narrow (0.25")', valueIn: 0.25 },
1253
+ { key: "normal", label: 'Normal (0.4")', valueIn: 0.4 },
1254
+ { key: "moderate", label: 'Moderate (0.75")', valueIn: 0.75 },
1255
+ { key: "wide", label: 'Wide (1")', valueIn: 1 }
1256
+ ];
1257
+ var CSS_PX_PER_INCH = 96;
1258
+ function resolvePageCanvasMetrics(value) {
1259
+ const margin = MARGIN_OPTIONS.find((o) => o.key === value.margin) ?? MARGIN_OPTIONS[1];
1260
+ if (value.size === "pageless") {
1261
+ return { widthPx: void 0, paddingPx: 20 };
1262
+ }
1263
+ const size = PAGE_SIZE_OPTIONS.find((o) => o.key === value.size);
1264
+ if (!size) return { widthPx: void 0, paddingPx: 20 };
1265
+ const widthIn = value.orientation === "landscape" ? size.heightIn : size.widthIn;
1266
+ return {
1267
+ widthPx: Math.round(widthIn * CSS_PX_PER_INCH),
1268
+ paddingPx: Math.round(margin.valueIn * CSS_PX_PER_INCH)
1269
+ };
1270
+ }
1231
1271
  var AutocompleteNode = class _AutocompleteNode extends TextNode {
1232
1272
  static getType() {
1233
1273
  return "autocomplete";
@@ -2466,6 +2506,236 @@ function CharacterStylesPopupPlugin(props) {
2466
2506
  const [editor] = useLexicalComposerContext();
2467
2507
  return useCharacterStylesPopup(editor, props);
2468
2508
  }
2509
+
2510
+ // src/Utils/Sanitize.ts
2511
+ var DROP_ENTIRELY = /* @__PURE__ */ new Set([
2512
+ "script",
2513
+ "noscript",
2514
+ "style",
2515
+ "object",
2516
+ "embed",
2517
+ "form",
2518
+ "input",
2519
+ "button",
2520
+ "select",
2521
+ "textarea",
2522
+ "meta",
2523
+ "link",
2524
+ "base"
2525
+ ]);
2526
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
2527
+ // Block
2528
+ "p",
2529
+ "h1",
2530
+ "h2",
2531
+ "h3",
2532
+ "h4",
2533
+ "h5",
2534
+ "h6",
2535
+ "ul",
2536
+ "ol",
2537
+ "li",
2538
+ "blockquote",
2539
+ "pre",
2540
+ "div",
2541
+ "table",
2542
+ "thead",
2543
+ "tbody",
2544
+ "tfoot",
2545
+ "tr",
2546
+ "td",
2547
+ "th",
2548
+ // Inline
2549
+ "span",
2550
+ "a",
2551
+ "strong",
2552
+ "b",
2553
+ "em",
2554
+ "i",
2555
+ "u",
2556
+ "s",
2557
+ "del",
2558
+ "strike",
2559
+ "sub",
2560
+ "sup",
2561
+ "mark",
2562
+ "code",
2563
+ "br",
2564
+ "hr",
2565
+ // Media / embeds
2566
+ "img",
2567
+ "iframe"
2568
+ ]);
2569
+ var ALLOWED_ATTRS = /* @__PURE__ */ new Set([
2570
+ // Presentation
2571
+ "class",
2572
+ "style",
2573
+ "dir",
2574
+ "lang",
2575
+ // Anchors
2576
+ "href",
2577
+ "target",
2578
+ "rel",
2579
+ // Images
2580
+ "src",
2581
+ "alt",
2582
+ "width",
2583
+ "height",
2584
+ // Tables
2585
+ "colspan",
2586
+ "rowspan",
2587
+ // Lists — <ol type="a"> and <ol start="2">
2588
+ "start",
2589
+ "type",
2590
+ // Lexical-internal data markers
2591
+ "data-lex-block",
2592
+ "data-kind",
2593
+ "data-lexical-decorator",
2594
+ // Misc
2595
+ "title",
2596
+ "allowfullscreen"
2597
+ ]);
2598
+ var DANGEROUS_URL = /^\s*(javascript|data\s*:|vbscript)/i;
2599
+ function sanitizeElement(el) {
2600
+ for (const child of Array.from(el.children)) {
2601
+ sanitizeElement(child);
2602
+ }
2603
+ const tag = el.tagName.toLowerCase();
2604
+ if (DROP_ENTIRELY.has(tag)) {
2605
+ el.parentNode?.removeChild(el);
2606
+ return;
2607
+ }
2608
+ if (tag === "iframe") {
2609
+ const src = el.getAttribute("src") ?? "";
2610
+ const isYouTube = /^\s*https:\/\/(www\.)?(youtube\.com|youtube-nocookie\.com)\/embed\/[^?&/]+/i.test(src);
2611
+ if (!isYouTube) {
2612
+ el.parentNode?.removeChild(el);
2613
+ return;
2614
+ }
2615
+ }
2616
+ if (!ALLOWED_TAGS.has(tag)) {
2617
+ while (el.firstChild) {
2618
+ el.parentNode?.insertBefore(el.firstChild, el);
2619
+ }
2620
+ el.parentNode?.removeChild(el);
2621
+ return;
2622
+ }
2623
+ for (const { name, value } of Array.from(el.attributes)) {
2624
+ const lname = name.toLowerCase();
2625
+ if (!ALLOWED_ATTRS.has(lname)) {
2626
+ el.removeAttribute(name);
2627
+ continue;
2628
+ }
2629
+ if (lname === "href" || lname === "src") {
2630
+ if (DANGEROUS_URL.test(value)) {
2631
+ el.removeAttribute(name);
2632
+ }
2633
+ }
2634
+ if (lname === "style") {
2635
+ const safe = value.replace(/expression\s*\(/gi, "(").replace(/url\s*\(\s*['"]?\s*javascript:/gi, "url(");
2636
+ el.setAttribute("style", safe);
2637
+ }
2638
+ }
2639
+ if (tag === "a" && (el.getAttribute("target") || "").toLowerCase() === "_blank") {
2640
+ const rel = (el.getAttribute("rel") || "").trim();
2641
+ const tokens = new Set(
2642
+ rel.split(/\s+/).filter(Boolean).map((t) => t.toLowerCase())
2643
+ );
2644
+ tokens.add("noopener");
2645
+ tokens.add("noreferrer");
2646
+ el.setAttribute("rel", Array.from(tokens).join(" "));
2647
+ }
2648
+ }
2649
+ function sanitizeHtml(html) {
2650
+ if (!html || typeof html !== "string") return "";
2651
+ const doc = new DOMParser().parseFromString(html, "text/html");
2652
+ for (const child of Array.from(doc.body.children)) {
2653
+ sanitizeElement(child);
2654
+ }
2655
+ return doc.body.innerHTML;
2656
+ }
2657
+ function postProcessOutput(html) {
2658
+ if (!html) return html;
2659
+ const doc = new DOMParser().parseFromString(html, "text/html");
2660
+ for (const inner of Array.from(doc.querySelectorAll("b > strong"))) {
2661
+ const outer = inner.parentElement;
2662
+ _mergeAttributes(inner, outer);
2663
+ while (inner.firstChild) outer.insertBefore(inner.firstChild, inner);
2664
+ outer.removeChild(inner);
2665
+ }
2666
+ for (const inner of Array.from(doc.querySelectorAll("i > em"))) {
2667
+ const outer = inner.parentElement;
2668
+ _mergeAttributes(inner, outer);
2669
+ while (inner.firstChild) outer.insertBefore(inner.firstChild, inner);
2670
+ outer.removeChild(inner);
2671
+ }
2672
+ for (const p of Array.from(doc.querySelectorAll("p"))) {
2673
+ const style = p.style;
2674
+ const hasMargin = !!(style.margin || style.marginTop || style.marginRight || style.marginBottom || style.marginLeft);
2675
+ if (!hasMargin) style.margin = "0";
2676
+ if (!style.lineHeight) style.lineHeight = "1.25";
2677
+ }
2678
+ return doc.body.innerHTML;
2679
+ }
2680
+ function _mergeAttributes(src, dst) {
2681
+ for (const { name, value } of Array.from(src.attributes)) {
2682
+ if (name === "style") {
2683
+ const prev = dst.getAttribute("style") || "";
2684
+ dst.setAttribute("style", prev ? `${prev}; ${value}` : value);
2685
+ } else if (name === "class") {
2686
+ const prev = dst.getAttribute("class") || "";
2687
+ dst.setAttribute("class", prev ? `${prev} ${value}` : value);
2688
+ }
2689
+ }
2690
+ }
2691
+ var BLOCK_TAGS = /* @__PURE__ */ new Set([
2692
+ "p",
2693
+ "h1",
2694
+ "h2",
2695
+ "h3",
2696
+ "h4",
2697
+ "h5",
2698
+ "h6",
2699
+ "div",
2700
+ "ul",
2701
+ "ol",
2702
+ "li",
2703
+ "table",
2704
+ "blockquote",
2705
+ "pre",
2706
+ "hr"
2707
+ ]);
2708
+ function normalizeToBlockHtml(html) {
2709
+ if (!html) return "";
2710
+ const doc = new DOMParser().parseFromString(html, "text/html");
2711
+ const body = doc.body;
2712
+ const childNodes = Array.from(body.childNodes);
2713
+ const needsWrap = childNodes.some((node) => {
2714
+ if (node.nodeType === Node.TEXT_NODE) return !!node.nodeValue?.trim();
2715
+ if (node.nodeType === Node.ELEMENT_NODE)
2716
+ return !BLOCK_TAGS.has(node.tagName.toLowerCase());
2717
+ return false;
2718
+ });
2719
+ if (!needsWrap) return html;
2720
+ while (body.firstChild) body.removeChild(body.firstChild);
2721
+ let pendingP = null;
2722
+ for (const node of childNodes) {
2723
+ const isBlock = node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
2724
+ const isWhitespace = node.nodeType === Node.TEXT_NODE && !node.nodeValue?.trim();
2725
+ if (isBlock) {
2726
+ if (pendingP) {
2727
+ body.appendChild(pendingP);
2728
+ pendingP = null;
2729
+ }
2730
+ body.appendChild(node);
2731
+ } else if (!isWhitespace) {
2732
+ if (!pendingP) pendingP = doc.createElement("p");
2733
+ pendingP.appendChild(node);
2734
+ }
2735
+ }
2736
+ if (pendingP) body.appendChild(pendingP);
2737
+ return body.innerHTML;
2738
+ }
2469
2739
  var CustomOnChangePlugin = ({ value, onChange }) => {
2470
2740
  const [editor] = useLexicalComposerContext();
2471
2741
  const initializedRef = useRef(false);
@@ -2486,7 +2756,7 @@ var CustomOnChangePlugin = ({ value, onChange }) => {
2486
2756
  }, [editor, value]);
2487
2757
  const handleChange = useCallback((editorState) => {
2488
2758
  editorState.read(() => {
2489
- onChangeRef.current($generateHtmlFromNodes(editor));
2759
+ onChangeRef.current(postProcessOutput($generateHtmlFromNodes(editor)));
2490
2760
  });
2491
2761
  }, [editor]);
2492
2762
  return /* @__PURE__ */ jsx(
@@ -3468,309 +3738,111 @@ function $onDragStart2(event) {
3468
3738
  const dataTransfer = event.dataTransfer;
3469
3739
  if (!dataTransfer) return false;
3470
3740
  dataTransfer.setData("text/plain", "_");
3471
- dataTransfer.setDragImage(img2, 0, 0);
3472
- dataTransfer.setData(
3473
- "application/x-lexical-drag",
3474
- JSON.stringify({
3475
- data: {
3476
- altText: node.__altText,
3477
- caption: node.__caption,
3478
- height: node.__height,
3479
- key: node.getKey(),
3480
- showCaption: node.__showCaption,
3481
- src: node.__src,
3482
- width: node.__width
3483
- },
3484
- type: "image"
3485
- })
3486
- );
3487
- return true;
3488
- }
3489
- var $onDragover2 = (event) => {
3490
- const node = $getImageNodeInSelection2();
3491
- if (!node) return false;
3492
- if (!canDropImage2(event)) {
3493
- event.preventDefault();
3494
- }
3495
- return true;
3496
- };
3497
- var $onDrop2 = (event, editor) => {
3498
- const node = $getImageNodeInSelection2();
3499
- if (!node) return false;
3500
- const data = getDragImageData2(event);
3501
- if (!data) return false;
3502
- event.preventDefault();
3503
- if (canDropImage2(event)) {
3504
- const range = getDragSelection2(event);
3505
- node.remove();
3506
- const rangeSelection = $createRangeSelection();
3507
- if (range !== null && range !== void 0) {
3508
- rangeSelection.applyDOMRange(range);
3509
- }
3510
- $setSelection(rangeSelection);
3511
- editor.dispatchCommand(INSERT_INLINE_IMAGE_COMMAND, data);
3512
- }
3513
- return true;
3514
- };
3515
- var $getImageNodeInSelection2 = () => {
3516
- const selection = $getSelection();
3517
- if (!$isNodeSelection(selection)) return null;
3518
- const nodes = selection.getNodes();
3519
- const node = nodes[0];
3520
- return $isInlineImageNode(node) ? node : null;
3521
- };
3522
- var getDragImageData2 = (event) => {
3523
- const dragData = event.dataTransfer?.getData("application/x-lexical-drag");
3524
- if (!dragData) return null;
3525
- const { type, data } = JSON.parse(dragData);
3526
- if (type !== "image") return null;
3527
- return data;
3528
- };
3529
- var canDropImage2 = (event) => {
3530
- const target = event.target;
3531
- return !!(isHTMLElement(target) && !target.closest("code, span.editor-image") && isHTMLElement(target.parentElement) && target.parentElement.closest("div.ContentEditable__root"));
3532
- };
3533
- var getDragSelection2 = (event) => {
3534
- let range;
3535
- const domSelection = getDOMSelectionFromTarget(event.target);
3536
- if (document.caretRangeFromPoint) {
3537
- range = document.caretRangeFromPoint(event.clientX, event.clientY);
3538
- } else if (event.rangeParent && domSelection !== null) {
3539
- domSelection.collapse(event.rangeParent, event.rangeOffset || 0);
3540
- range = domSelection.getRangeAt(0);
3541
- } else {
3542
- throw Error("Cannot get the selection when dragging");
3543
- }
3544
- return range;
3545
- };
3546
- var INSERT_PAGE_BREAK = createCommand();
3547
- function PageBreakPlugin() {
3548
- const [editor] = useLexicalComposerContext();
3549
- useEffect(() => {
3550
- if (!editor.hasNodes([PageBreakNode])) {
3551
- throw new Error(
3552
- "PageBreakPlugin: PageBreakNode is not registered on editor"
3553
- );
3554
- }
3555
- return mergeRegister(
3556
- editor.registerCommand(
3557
- INSERT_PAGE_BREAK,
3558
- () => {
3559
- const selection = $getSelection();
3560
- if (!$isRangeSelection(selection)) {
3561
- return false;
3562
- }
3563
- const focusNode = selection.focus.getNode();
3564
- if (focusNode !== null) {
3565
- const pgBreak = $createPageBreakNode();
3566
- $insertNodeToNearestRoot(pgBreak);
3567
- }
3568
- return true;
3569
- },
3570
- COMMAND_PRIORITY_EDITOR
3571
- )
3572
- );
3573
- }, [editor]);
3574
- return null;
3575
- }
3576
-
3577
- // src/Utils/Sanitize.ts
3578
- var DROP_ENTIRELY = /* @__PURE__ */ new Set([
3579
- "script",
3580
- "noscript",
3581
- "style",
3582
- "object",
3583
- "embed",
3584
- "form",
3585
- "input",
3586
- "button",
3587
- "select",
3588
- "textarea",
3589
- "meta",
3590
- "link",
3591
- "base"
3592
- ]);
3593
- var ALLOWED_TAGS = /* @__PURE__ */ new Set([
3594
- // Block
3595
- "p",
3596
- "h1",
3597
- "h2",
3598
- "h3",
3599
- "h4",
3600
- "h5",
3601
- "h6",
3602
- "ul",
3603
- "ol",
3604
- "li",
3605
- "blockquote",
3606
- "pre",
3607
- "div",
3608
- "table",
3609
- "thead",
3610
- "tbody",
3611
- "tfoot",
3612
- "tr",
3613
- "td",
3614
- "th",
3615
- // Inline
3616
- "span",
3617
- "a",
3618
- "strong",
3619
- "b",
3620
- "em",
3621
- "i",
3622
- "u",
3623
- "s",
3624
- "del",
3625
- "strike",
3626
- "sub",
3627
- "sup",
3628
- "mark",
3629
- "code",
3630
- "br",
3631
- "hr",
3632
- // Media / embeds
3633
- "img",
3634
- "iframe"
3635
- ]);
3636
- var ALLOWED_ATTRS = /* @__PURE__ */ new Set([
3637
- // Presentation
3638
- "class",
3639
- "style",
3640
- "dir",
3641
- "lang",
3642
- // Anchors
3643
- "href",
3644
- "target",
3645
- "rel",
3646
- // Images
3647
- "src",
3648
- "alt",
3649
- "width",
3650
- "height",
3651
- // Tables
3652
- "colspan",
3653
- "rowspan",
3654
- // Lists — <ol type="a"> and <ol start="2">
3655
- "start",
3656
- "type",
3657
- // Lexical-internal data markers
3658
- "data-lex-block",
3659
- "data-kind",
3660
- "data-lexical-decorator",
3661
- // Misc
3662
- "title",
3663
- "allowfullscreen"
3664
- ]);
3665
- var DANGEROUS_URL = /^\s*(javascript|data\s*:|vbscript)/i;
3666
- function sanitizeElement(el) {
3667
- for (const child of Array.from(el.children)) {
3668
- sanitizeElement(child);
3669
- }
3670
- const tag = el.tagName.toLowerCase();
3671
- if (DROP_ENTIRELY.has(tag)) {
3672
- el.parentNode?.removeChild(el);
3673
- return;
3674
- }
3675
- if (tag === "iframe") {
3676
- const src = el.getAttribute("src") ?? "";
3677
- const isYouTube = /^\s*https:\/\/(www\.)?(youtube\.com|youtube-nocookie\.com)\/embed\/[^?&/]+/i.test(src);
3678
- if (!isYouTube) {
3679
- el.parentNode?.removeChild(el);
3680
- return;
3681
- }
3682
- }
3683
- if (!ALLOWED_TAGS.has(tag)) {
3684
- while (el.firstChild) {
3685
- el.parentNode?.insertBefore(el.firstChild, el);
3686
- }
3687
- el.parentNode?.removeChild(el);
3688
- return;
3689
- }
3690
- for (const { name, value } of Array.from(el.attributes)) {
3691
- const lname = name.toLowerCase();
3692
- if (!ALLOWED_ATTRS.has(lname)) {
3693
- el.removeAttribute(name);
3694
- continue;
3695
- }
3696
- if (lname === "href" || lname === "src") {
3697
- if (DANGEROUS_URL.test(value)) {
3698
- el.removeAttribute(name);
3699
- }
3700
- }
3701
- if (lname === "style") {
3702
- const safe = value.replace(/expression\s*\(/gi, "(").replace(/url\s*\(\s*['"]?\s*javascript:/gi, "url(");
3703
- el.setAttribute("style", safe);
3704
- }
3705
- }
3706
- if (tag === "a" && (el.getAttribute("target") || "").toLowerCase() === "_blank") {
3707
- const rel = (el.getAttribute("rel") || "").trim();
3708
- const tokens = new Set(
3709
- rel.split(/\s+/).filter(Boolean).map((t) => t.toLowerCase())
3710
- );
3711
- tokens.add("noopener");
3712
- tokens.add("noreferrer");
3713
- el.setAttribute("rel", Array.from(tokens).join(" "));
3714
- }
3715
- }
3716
- function sanitizeHtml(html) {
3717
- if (!html || typeof html !== "string") return "";
3718
- const doc = new DOMParser().parseFromString(html, "text/html");
3719
- for (const child of Array.from(doc.body.children)) {
3720
- sanitizeElement(child);
3721
- }
3722
- return doc.body.innerHTML;
3723
- }
3724
- var BLOCK_TAGS = /* @__PURE__ */ new Set([
3725
- "p",
3726
- "h1",
3727
- "h2",
3728
- "h3",
3729
- "h4",
3730
- "h5",
3731
- "h6",
3732
- "div",
3733
- "ul",
3734
- "ol",
3735
- "li",
3736
- "table",
3737
- "blockquote",
3738
- "pre",
3739
- "hr"
3740
- ]);
3741
- function normalizeToBlockHtml(html) {
3742
- if (!html) return "";
3743
- const doc = new DOMParser().parseFromString(html, "text/html");
3744
- const body = doc.body;
3745
- const childNodes = Array.from(body.childNodes);
3746
- const needsWrap = childNodes.some((node) => {
3747
- if (node.nodeType === Node.TEXT_NODE) return !!node.nodeValue?.trim();
3748
- if (node.nodeType === Node.ELEMENT_NODE)
3749
- return !BLOCK_TAGS.has(node.tagName.toLowerCase());
3750
- return false;
3751
- });
3752
- if (!needsWrap) return html;
3753
- while (body.firstChild) body.removeChild(body.firstChild);
3754
- let pendingP = null;
3755
- for (const node of childNodes) {
3756
- const isBlock = node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
3757
- const isWhitespace = node.nodeType === Node.TEXT_NODE && !node.nodeValue?.trim();
3758
- if (isBlock) {
3759
- if (pendingP) {
3760
- body.appendChild(pendingP);
3761
- pendingP = null;
3762
- }
3763
- body.appendChild(node);
3764
- } else if (!isWhitespace) {
3765
- if (!pendingP) pendingP = doc.createElement("p");
3766
- pendingP.appendChild(node);
3741
+ dataTransfer.setDragImage(img2, 0, 0);
3742
+ dataTransfer.setData(
3743
+ "application/x-lexical-drag",
3744
+ JSON.stringify({
3745
+ data: {
3746
+ altText: node.__altText,
3747
+ caption: node.__caption,
3748
+ height: node.__height,
3749
+ key: node.getKey(),
3750
+ showCaption: node.__showCaption,
3751
+ src: node.__src,
3752
+ width: node.__width
3753
+ },
3754
+ type: "image"
3755
+ })
3756
+ );
3757
+ return true;
3758
+ }
3759
+ var $onDragover2 = (event) => {
3760
+ const node = $getImageNodeInSelection2();
3761
+ if (!node) return false;
3762
+ if (!canDropImage2(event)) {
3763
+ event.preventDefault();
3764
+ }
3765
+ return true;
3766
+ };
3767
+ var $onDrop2 = (event, editor) => {
3768
+ const node = $getImageNodeInSelection2();
3769
+ if (!node) return false;
3770
+ const data = getDragImageData2(event);
3771
+ if (!data) return false;
3772
+ event.preventDefault();
3773
+ if (canDropImage2(event)) {
3774
+ const range = getDragSelection2(event);
3775
+ node.remove();
3776
+ const rangeSelection = $createRangeSelection();
3777
+ if (range !== null && range !== void 0) {
3778
+ rangeSelection.applyDOMRange(range);
3767
3779
  }
3780
+ $setSelection(rangeSelection);
3781
+ editor.dispatchCommand(INSERT_INLINE_IMAGE_COMMAND, data);
3768
3782
  }
3769
- if (pendingP) body.appendChild(pendingP);
3770
- return body.innerHTML;
3783
+ return true;
3784
+ };
3785
+ var $getImageNodeInSelection2 = () => {
3786
+ const selection = $getSelection();
3787
+ if (!$isNodeSelection(selection)) return null;
3788
+ const nodes = selection.getNodes();
3789
+ const node = nodes[0];
3790
+ return $isInlineImageNode(node) ? node : null;
3791
+ };
3792
+ var getDragImageData2 = (event) => {
3793
+ const dragData = event.dataTransfer?.getData("application/x-lexical-drag");
3794
+ if (!dragData) return null;
3795
+ const { type, data } = JSON.parse(dragData);
3796
+ if (type !== "image") return null;
3797
+ return data;
3798
+ };
3799
+ var canDropImage2 = (event) => {
3800
+ const target = event.target;
3801
+ return !!(isHTMLElement(target) && !target.closest("code, span.editor-image") && isHTMLElement(target.parentElement) && target.parentElement.closest("div.ContentEditable__root"));
3802
+ };
3803
+ var getDragSelection2 = (event) => {
3804
+ let range;
3805
+ const domSelection = getDOMSelectionFromTarget(event.target);
3806
+ if (document.caretRangeFromPoint) {
3807
+ range = document.caretRangeFromPoint(event.clientX, event.clientY);
3808
+ } else if (event.rangeParent && domSelection !== null) {
3809
+ domSelection.collapse(event.rangeParent, event.rangeOffset || 0);
3810
+ range = domSelection.getRangeAt(0);
3811
+ } else {
3812
+ throw Error("Cannot get the selection when dragging");
3813
+ }
3814
+ return range;
3815
+ };
3816
+ var INSERT_PAGE_BREAK = createCommand();
3817
+ function PageBreakPlugin() {
3818
+ const [editor] = useLexicalComposerContext();
3819
+ useEffect(() => {
3820
+ if (!editor.hasNodes([PageBreakNode])) {
3821
+ throw new Error(
3822
+ "PageBreakPlugin: PageBreakNode is not registered on editor"
3823
+ );
3824
+ }
3825
+ return mergeRegister(
3826
+ editor.registerCommand(
3827
+ INSERT_PAGE_BREAK,
3828
+ () => {
3829
+ const selection = $getSelection();
3830
+ if (!$isRangeSelection(selection)) {
3831
+ return false;
3832
+ }
3833
+ const focusNode = selection.focus.getNode();
3834
+ if (focusNode !== null) {
3835
+ const pgBreak = $createPageBreakNode();
3836
+ $insertNodeToNearestRoot(pgBreak);
3837
+ }
3838
+ return true;
3839
+ },
3840
+ COMMAND_PRIORITY_EDITOR
3841
+ )
3842
+ );
3843
+ }, [editor]);
3844
+ return null;
3771
3845
  }
3772
-
3773
- // src/Utils/Helper.ts
3774
3846
  function findBlockByKind(kind) {
3775
3847
  const root = $getRoot();
3776
3848
  for (const child of root.getChildren()) {
@@ -3842,7 +3914,7 @@ function RefApiPlugin({
3842
3914
  editor.getEditorState().read(() => {
3843
3915
  html = $generateHtmlFromNodes(editor, null);
3844
3916
  });
3845
- return html;
3917
+ return postProcessOutput(html);
3846
3918
  },
3847
3919
  clear: () => {
3848
3920
  editor.update(() => {
@@ -4991,7 +5063,8 @@ function getToolbarGroupsByLevel(level) {
4991
5063
  ["FontFamily", "|"],
4992
5064
  ["FontSize", "|"],
4993
5065
  ["Decorators", "|"],
4994
- ["Align"]
5066
+ ["Align", "|"],
5067
+ ["PageSetup"]
4995
5068
  ];
4996
5069
  case "pro" /* Pro */:
4997
5070
  default:
@@ -5006,7 +5079,8 @@ function getToolbarGroupsByLevel(level) {
5006
5079
  ["FontFamily", "|"],
5007
5080
  ["FontSize", "|"],
5008
5081
  ["Decorators", "|"],
5009
- ["Align"]
5082
+ ["Align", "|"],
5083
+ ["PageSetup"]
5010
5084
  ];
5011
5085
  }
5012
5086
  }
@@ -5044,6 +5118,7 @@ var normalizeHex = (v) => {
5044
5118
  if (hex.length === 4 || hex.length === 7) return hex.toLowerCase();
5045
5119
  return "#000000";
5046
5120
  };
5121
+ var isCompleteHex = (v) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test((v ?? "").trim());
5047
5122
  var hexToRgb = (hex) => {
5048
5123
  const h = normalizeHex(hex).replace("#", "");
5049
5124
  if (h.length === 3) {
@@ -5091,40 +5166,6 @@ var hsvToRgb = (h, s, v) => {
5091
5166
  b: Math.round((bb + m) * 255)
5092
5167
  };
5093
5168
  };
5094
- function useDrag(onMove, onEnd, interactingRef) {
5095
- const draggingRef = React9.useRef(false);
5096
- const start = React9.useCallback(
5097
- (e) => {
5098
- draggingRef.current = true;
5099
- if (interactingRef) interactingRef.current = true;
5100
- onMove(e.clientX, e.clientY);
5101
- const move = (ev) => {
5102
- if (!draggingRef.current) return;
5103
- onMove(ev.clientX, ev.clientY);
5104
- };
5105
- const up = () => {
5106
- draggingRef.current = false;
5107
- window.removeEventListener("mousemove", move);
5108
- window.removeEventListener("mouseup", up);
5109
- if (interactingRef) {
5110
- const clearFlag = () => {
5111
- interactingRef.current = false;
5112
- };
5113
- window.addEventListener("click", clearFlag, { once: true });
5114
- setTimeout(() => {
5115
- window.removeEventListener("click", clearFlag);
5116
- interactingRef.current = false;
5117
- }, 0);
5118
- }
5119
- onEnd?.();
5120
- };
5121
- window.addEventListener("mousemove", move);
5122
- window.addEventListener("mouseup", up);
5123
- },
5124
- [onMove, onEnd, interactingRef]
5125
- );
5126
- return start;
5127
- }
5128
5169
  var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange }) => {
5129
5170
  const [open, setOpen] = React9.useState(false);
5130
5171
  const btnRef = React9.useRef(null);
@@ -5135,13 +5176,9 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5135
5176
  },
5136
5177
  [onOpenChange]
5137
5178
  );
5138
- const interactingRef = React9.useRef(false);
5139
5179
  const handleDismiss = React9.useCallback(() => setOpenAndNotify(false), [setOpenAndNotify]);
5140
5180
  const preventDismissOnEvent = React9.useCallback(
5141
- (ev) => {
5142
- if (interactingRef.current) return true;
5143
- return ev.type !== "click";
5144
- },
5181
+ (ev) => ev.type !== "click",
5145
5182
  []
5146
5183
  );
5147
5184
  const [, forceReposition] = React9.useState(0);
@@ -5163,100 +5200,119 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5163
5200
  window.removeEventListener("resize", reposition);
5164
5201
  };
5165
5202
  }, [open]);
5166
- const [hex, setHex] = React9.useState(normalizeHex(value || "#000000"));
5203
+ const appliedHex = React9.useMemo(() => normalizeHex(value || "#000000"), [value]);
5204
+ const [hex, setHexState] = React9.useState(appliedHex);
5205
+ const [hexText, setHexText] = React9.useState(appliedHex);
5167
5206
  const { r, g, b } = React9.useMemo(() => hexToRgb(hex), [hex]);
5168
5207
  const hsv = React9.useMemo(() => rgbToHsv(r, g, b), [r, g, b]);
5169
5208
  const [h, setH] = React9.useState(hsv.h);
5170
5209
  const [s, setS] = React9.useState(hsv.s);
5171
5210
  const [v, setV] = React9.useState(hsv.v);
5211
+ const commit = React9.useCallback(
5212
+ (nextHex) => {
5213
+ const rgb = hexToRgb(nextHex);
5214
+ const next = rgbToHsv(rgb.r, rgb.g, rgb.b);
5215
+ setHexState(nextHex);
5216
+ setHexText(nextHex);
5217
+ setH(next.h);
5218
+ setS(next.s);
5219
+ setV(next.v);
5220
+ onChange(nextHex);
5221
+ },
5222
+ [onChange]
5223
+ );
5224
+ const commitFromHsv = React9.useCallback(
5225
+ (hh, ss, vv) => {
5226
+ const rgb = hsvToRgb(hh, ss, vv);
5227
+ const nextHex = rgbToHex(rgb.r, rgb.g, rgb.b);
5228
+ setHexState(nextHex);
5229
+ setHexText(nextHex);
5230
+ setH(hh);
5231
+ setS(ss);
5232
+ setV(vv);
5233
+ onChange(nextHex);
5234
+ },
5235
+ [onChange]
5236
+ );
5172
5237
  const wasOpenRef = React9.useRef(open);
5173
- console.log("[AO-ColorPicker]", title, "render with incoming value prop:", JSON.stringify(value));
5174
5238
  React9.useEffect(() => {
5175
5239
  const justOpened = open && !wasOpenRef.current;
5176
5240
  wasOpenRef.current = open;
5177
- console.log("[AO-ColorPicker]", title, "open-seed effect", {
5178
- open,
5179
- justOpened,
5180
- incomingValue: value
5181
- });
5182
5241
  if (!justOpened) return;
5183
- const n = normalizeHex(value || "#000000");
5184
- console.log("[AO-ColorPicker]", title, "seeding local state from value on open", {
5185
- incomingValue: value,
5186
- normalized: n
5187
- });
5188
- setHex(n);
5189
- const rgb = hexToRgb(n);
5242
+ setHexState(appliedHex);
5243
+ setHexText(appliedHex);
5244
+ const rgb = hexToRgb(appliedHex);
5190
5245
  const next = rgbToHsv(rgb.r, rgb.g, rgb.b);
5191
5246
  setH(next.h);
5192
5247
  setS(next.s);
5193
5248
  setV(next.v);
5194
- }, [value, open]);
5195
- const updateHexFromHsv = React9.useCallback((hh, ss, vv) => {
5196
- const rgb = hsvToRgb(hh, ss, vv);
5197
- const nextHex = rgbToHex(rgb.r, rgb.g, rgb.b);
5198
- setHex(nextHex);
5199
- return nextHex;
5249
+ }, [appliedHex, open]);
5250
+ const svRef = React9.useRef(null);
5251
+ const svPointFromEvent = React9.useCallback((clientX, clientY) => {
5252
+ if (!svRef.current) return null;
5253
+ const rect = svRef.current.getBoundingClientRect();
5254
+ const x = clamp3(clientX - rect.left, 0, rect.width);
5255
+ const y = clamp3(clientY - rect.top, 0, rect.height);
5256
+ const ss = rect.width === 0 ? 0 : x / rect.width;
5257
+ const vv = rect.height === 0 ? 0 : 1 - y / rect.height;
5258
+ return { ss, vv };
5200
5259
  }, []);
5201
- const commitHsv = React9.useCallback(
5202
- (hh, ss, vv, close) => {
5203
- const nextHex = updateHexFromHsv(hh, ss, vv);
5204
- console.log("[AO-ColorPicker]", title, "commitHsv -> onChange", { nextHex, close: !!close });
5205
- onChange(nextHex);
5206
- if (close) setOpenAndNotify(false);
5260
+ const hRef = React9.useRef(h);
5261
+ hRef.current = h;
5262
+ const handleSVPointerDown = React9.useCallback(
5263
+ (e) => {
5264
+ e.currentTarget.setPointerCapture(e.pointerId);
5265
+ const pt = svPointFromEvent(e.clientX, e.clientY);
5266
+ if (pt) commitFromHsv(hRef.current, pt.ss, pt.vv);
5207
5267
  },
5208
- [onChange, title, setOpenAndNotify, updateHexFromHsv]
5268
+ [svPointFromEvent, commitFromHsv]
5209
5269
  );
5210
- const hRef = React9.useRef(h);
5270
+ const handleSVPointerMove = React9.useCallback(
5271
+ (e) => {
5272
+ if (e.buttons !== 1) return;
5273
+ const pt = svPointFromEvent(e.clientX, e.clientY);
5274
+ if (pt) commitFromHsv(hRef.current, pt.ss, pt.vv);
5275
+ },
5276
+ [svPointFromEvent, commitFromHsv]
5277
+ );
5278
+ const hueRef = React9.useRef(null);
5211
5279
  const sRef = React9.useRef(s);
5280
+ sRef.current = s;
5212
5281
  const vRef = React9.useRef(v);
5213
- React9.useEffect(() => {
5214
- hRef.current = h;
5215
- }, [h]);
5216
- React9.useEffect(() => {
5217
- sRef.current = s;
5218
- }, [s]);
5219
- React9.useEffect(() => {
5220
- vRef.current = v;
5221
- }, [v]);
5222
- const svRef = React9.useRef(null);
5223
- const onSVMove = React9.useCallback(
5224
- (clientX, clientY) => {
5225
- if (!svRef.current) return;
5226
- const rect = svRef.current.getBoundingClientRect();
5227
- const x = clamp3(clientX - rect.left, 0, rect.width);
5228
- const y = clamp3(clientY - rect.top, 0, rect.height);
5229
- const ss = rect.width === 0 ? 0 : x / rect.width;
5230
- const vv = rect.height === 0 ? 0 : 1 - y / rect.height;
5231
- setS(ss);
5232
- setV(vv);
5233
- sRef.current = ss;
5234
- vRef.current = vv;
5235
- updateHexFromHsv(hRef.current, ss, vv);
5282
+ vRef.current = v;
5283
+ const huePointFromEvent = React9.useCallback((clientX) => {
5284
+ if (!hueRef.current) return null;
5285
+ const rect = hueRef.current.getBoundingClientRect();
5286
+ const x = clamp3(clientX - rect.left, 0, rect.width);
5287
+ return rect.width === 0 ? 0 : x / rect.width * 360;
5288
+ }, []);
5289
+ const handleHuePointerDown = React9.useCallback(
5290
+ (e) => {
5291
+ e.currentTarget.setPointerCapture(e.pointerId);
5292
+ const hh = huePointFromEvent(e.clientX);
5293
+ if (hh != null) commitFromHsv(hh, sRef.current, vRef.current);
5236
5294
  },
5237
- [updateHexFromHsv]
5295
+ [huePointFromEvent, commitFromHsv]
5238
5296
  );
5239
- const commitSV = React9.useCallback(() => {
5240
- commitHsv(hRef.current, sRef.current, vRef.current);
5241
- }, [commitHsv]);
5242
- const startSV = useDrag(onSVMove, commitSV, interactingRef);
5243
- const hueRef = React9.useRef(null);
5244
- const onHueMove = React9.useCallback(
5245
- (clientX) => {
5246
- if (!hueRef.current) return;
5247
- const rect = hueRef.current.getBoundingClientRect();
5248
- const x = clamp3(clientX - rect.left, 0, rect.width);
5249
- const hh = rect.width === 0 ? 0 : x / rect.width * 360;
5250
- setH(hh);
5251
- hRef.current = hh;
5252
- updateHexFromHsv(hh, sRef.current, vRef.current);
5297
+ const handleHuePointerMove = React9.useCallback(
5298
+ (e) => {
5299
+ if (e.buttons !== 1) return;
5300
+ const hh = huePointFromEvent(e.clientX);
5301
+ if (hh != null) commitFromHsv(hh, sRef.current, vRef.current);
5253
5302
  },
5254
- [updateHexFromHsv]
5303
+ [huePointFromEvent, commitFromHsv]
5255
5304
  );
5256
- const commitHue = React9.useCallback(() => {
5257
- commitHsv(hRef.current, sRef.current, vRef.current);
5258
- }, [commitHsv]);
5259
- const startHue = useDrag((x) => onHueMove(x), commitHue, interactingRef);
5305
+ const handleHexChange = React9.useCallback(
5306
+ (_, val) => {
5307
+ const next = val ?? "";
5308
+ setHexText(next);
5309
+ if (isCompleteHex(next.trim())) commit(normalizeHex(next));
5310
+ },
5311
+ [commit]
5312
+ );
5313
+ const handleHexBlur = React9.useCallback(() => {
5314
+ commit(normalizeHex(hexText));
5315
+ }, [hexText, commit]);
5260
5316
  const svThumb = React9.useMemo(() => ({ left: `${s * 100}%`, top: `${(1 - v) * 100}%` }), [s, v]);
5261
5317
  const hueThumb = React9.useMemo(() => ({ left: `${h / 360 * 100}%` }), [h]);
5262
5318
  const hueColor = React9.useMemo(() => {
@@ -5281,10 +5337,6 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5281
5337
  },
5282
5338
  onClick: () => {
5283
5339
  if (disabled) return;
5284
- console.log("[AO-ColorPicker]", title, "trigger button clicked", {
5285
- wasOpen: open,
5286
- activeElementBeforeToggle: document.activeElement?.tagName
5287
- });
5288
5340
  setOpenAndNotify(!open);
5289
5341
  }
5290
5342
  }
@@ -5299,7 +5351,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5299
5351
  transform: "translateX(-50%)",
5300
5352
  width: 14,
5301
5353
  height: 3,
5302
- background: hex,
5354
+ background: appliedHex,
5303
5355
  borderRadius: 1,
5304
5356
  border: "0.5px solid rgba(0,0,0,0.18)",
5305
5357
  pointerEvents: "none"
@@ -5315,72 +5367,192 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5315
5367
  directionalHint: 4,
5316
5368
  className: "aoColorCallout",
5317
5369
  preventDismissOnEvent,
5318
- children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, styles: { root: { padding: 12, width: 320 } }, children: [
5319
- /* @__PURE__ */ jsxs("div", { className: "aoLexRow", children: [
5320
- /* @__PURE__ */ jsx("div", { className: "aoLexSwatch", style: { background: hex } }),
5321
- /* @__PURE__ */ jsx("div", { className: "aoLexTitle", children: title })
5322
- ] }),
5323
- /* @__PURE__ */ jsxs("div", { className: "aoLexRow", children: [
5324
- /* @__PURE__ */ jsx("div", { className: "aoLexLabel", children: "Hex" }),
5370
+ children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 14 }, styles: { root: { padding: "14px 16px 16px", width: 288 } }, children: [
5371
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
5372
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#242424", letterSpacing: 0.1 }, children: title }),
5325
5373
  /* @__PURE__ */ jsx(
5326
- TextField,
5374
+ "button",
5327
5375
  {
5328
- value: hex,
5329
- onChange: (_, val) => setHex(normalizeHex(val || "")),
5330
- onBlur: () => {
5331
- const n = normalizeHex(hex);
5332
- setHex(n);
5333
- const rgb = hexToRgb(n);
5334
- const next = rgbToHsv(rgb.r, rgb.g, rgb.b);
5335
- setH(next.h);
5336
- setS(next.s);
5337
- setV(next.v);
5338
- console.log("[AO-ColorPicker]", title, "hex field blur -> onChange", { raw: hex, normalized: n });
5339
- onChange(n);
5340
- }
5376
+ type: "button",
5377
+ "aria-label": "Close",
5378
+ onClick: () => setOpenAndNotify(false),
5379
+ style: {
5380
+ display: "flex",
5381
+ alignItems: "center",
5382
+ justifyContent: "center",
5383
+ width: 24,
5384
+ height: 24,
5385
+ padding: 0,
5386
+ border: "none",
5387
+ borderRadius: 4,
5388
+ background: "transparent",
5389
+ color: "#616161",
5390
+ cursor: "pointer"
5391
+ },
5392
+ onMouseEnter: (e) => e.currentTarget.style.background = "#f0f0f0",
5393
+ onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
5394
+ children: /* @__PURE__ */ jsx(Dismiss16Regular, {})
5341
5395
  }
5342
5396
  )
5343
5397
  ] }),
5344
- /* @__PURE__ */ jsx("div", { className: "aoLexSwatches", children: PRESET.map((c) => /* @__PURE__ */ jsx(
5345
- "button",
5398
+ /* @__PURE__ */ jsxs(
5399
+ "div",
5346
5400
  {
5347
- type: "button",
5348
- className: "aoLexSwatchBtn",
5349
- style: { background: c },
5350
- onClick: () => {
5351
- setHex(c);
5352
- const rgb = hexToRgb(c);
5353
- const next = rgbToHsv(rgb.r, rgb.g, rgb.b);
5354
- setH(next.h);
5355
- setS(next.s);
5356
- setV(next.v);
5357
- console.log("[AO-ColorPicker]", title, "preset swatch click -> onChange", { color: c });
5358
- onChange(c);
5401
+ ref: svRef,
5402
+ onPointerDown: handleSVPointerDown,
5403
+ onPointerMove: handleSVPointerMove,
5404
+ style: {
5405
+ position: "relative",
5406
+ width: "100%",
5407
+ height: 150,
5408
+ borderRadius: 8,
5409
+ overflow: "hidden",
5410
+ cursor: "crosshair",
5411
+ touchAction: "none",
5412
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)"
5359
5413
  },
5360
- title: c
5361
- },
5362
- c
5363
- )) }),
5364
- /* @__PURE__ */ jsxs("div", { className: "aoLexSV", ref: svRef, onMouseDown: startSV, children: [
5365
- /* @__PURE__ */ jsx("div", { className: "aoLexSVHue", style: { background: hueColor } }),
5366
- /* @__PURE__ */ jsx("div", { className: "aoLexSVWhite" }),
5367
- /* @__PURE__ */ jsx("div", { className: "aoLexSVBlack" }),
5368
- /* @__PURE__ */ jsx("div", { className: "aoLexSVThumb", style: svThumb })
5369
- ] }),
5370
- /* @__PURE__ */ jsx("div", { className: "aoLexHue", ref: hueRef, onMouseDown: startHue, children: /* @__PURE__ */ jsx("div", { className: "aoLexHueThumb", style: hueThumb }) }),
5371
- /* @__PURE__ */ jsx("div", { className: "aoLexPreview", style: { background: hex } }),
5372
- /* @__PURE__ */ jsxs("div", { className: "aoLexActions", children: [
5414
+ children: [
5415
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: hueColor } }),
5416
+ /* @__PURE__ */ jsx(
5417
+ "div",
5418
+ {
5419
+ style: {
5420
+ position: "absolute",
5421
+ inset: 0,
5422
+ background: "linear-gradient(to right, #fff, rgba(255,255,255,0))"
5423
+ }
5424
+ }
5425
+ ),
5426
+ /* @__PURE__ */ jsx(
5427
+ "div",
5428
+ {
5429
+ style: {
5430
+ position: "absolute",
5431
+ inset: 0,
5432
+ background: "linear-gradient(to top, #000, rgba(0,0,0,0))"
5433
+ }
5434
+ }
5435
+ ),
5436
+ /* @__PURE__ */ jsx(
5437
+ "div",
5438
+ {
5439
+ style: {
5440
+ position: "absolute",
5441
+ width: 16,
5442
+ height: 16,
5443
+ borderRadius: "50%",
5444
+ border: "2px solid #fff",
5445
+ boxShadow: "0 0 0 1px rgba(0,0,0,0.35), 0 1px 3px rgba(0,0,0,0.4)",
5446
+ transform: "translate(-50%, -50%)",
5447
+ pointerEvents: "none",
5448
+ ...svThumb
5449
+ }
5450
+ }
5451
+ )
5452
+ ]
5453
+ }
5454
+ ),
5455
+ /* @__PURE__ */ jsx(
5456
+ "div",
5457
+ {
5458
+ ref: hueRef,
5459
+ onPointerDown: handleHuePointerDown,
5460
+ onPointerMove: handleHuePointerMove,
5461
+ style: {
5462
+ position: "relative",
5463
+ width: "100%",
5464
+ height: 12,
5465
+ borderRadius: 999,
5466
+ cursor: "pointer",
5467
+ touchAction: "none",
5468
+ background: "linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)",
5469
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)"
5470
+ },
5471
+ children: /* @__PURE__ */ jsx(
5472
+ "div",
5473
+ {
5474
+ style: {
5475
+ position: "absolute",
5476
+ top: "50%",
5477
+ width: 16,
5478
+ height: 16,
5479
+ borderRadius: "50%",
5480
+ background: hueColor,
5481
+ border: "2px solid #fff",
5482
+ boxShadow: "0 0 0 1px rgba(0,0,0,0.35), 0 1px 3px rgba(0,0,0,0.4)",
5483
+ transform: "translate(-50%, -50%)",
5484
+ pointerEvents: "none",
5485
+ ...hueThumb
5486
+ }
5487
+ }
5488
+ )
5489
+ }
5490
+ ),
5491
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
5373
5492
  /* @__PURE__ */ jsx(
5374
- DefaultButton,
5493
+ "div",
5375
5494
  {
5376
- type: "button",
5377
- text: "Apply",
5378
- onClick: () => {
5379
- commitHsv(h, s, v, true);
5495
+ style: {
5496
+ width: 32,
5497
+ height: 32,
5498
+ borderRadius: 6,
5499
+ flexShrink: 0,
5500
+ background: hex,
5501
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.12)"
5380
5502
  }
5381
5503
  }
5382
5504
  ),
5383
- /* @__PURE__ */ jsx(DefaultButton, { type: "button", text: "Close", onClick: () => setOpenAndNotify(false) })
5505
+ /* @__PURE__ */ jsx(
5506
+ TextField,
5507
+ {
5508
+ value: hexText,
5509
+ onChange: handleHexChange,
5510
+ onBlur: handleHexBlur,
5511
+ onKeyDown: (e) => {
5512
+ if (e.key === "Enter") commit(normalizeHex(hexText));
5513
+ },
5514
+ styles: { root: { flex: 1 }, fieldGroup: { borderRadius: 6 } }
5515
+ }
5516
+ )
5517
+ ] }),
5518
+ /* @__PURE__ */ jsxs("div", { children: [
5519
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 600, color: "#8a8a8a", marginBottom: 6, letterSpacing: 0.3 }, children: "STANDARD COLORS" }),
5520
+ /* @__PURE__ */ jsx(
5521
+ "div",
5522
+ {
5523
+ style: {
5524
+ display: "grid",
5525
+ gridTemplateColumns: "repeat(9, 1fr)",
5526
+ gap: 6
5527
+ },
5528
+ children: PRESET.map((c) => {
5529
+ const isSelected = c.toLowerCase() === hex.toLowerCase();
5530
+ return /* @__PURE__ */ jsx(
5531
+ "button",
5532
+ {
5533
+ type: "button",
5534
+ onClick: () => commit(c),
5535
+ title: c,
5536
+ "aria-label": c,
5537
+ style: {
5538
+ width: 22,
5539
+ height: 22,
5540
+ padding: 0,
5541
+ borderRadius: 5,
5542
+ background: c,
5543
+ cursor: "pointer",
5544
+ boxShadow: isSelected ? "0 0 0 2px #fff, 0 0 0 3px #4a86e8" : "inset 0 0 0 1px rgba(0,0,0,0.15)",
5545
+ border: "none",
5546
+ transition: "transform 80ms ease"
5547
+ },
5548
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.12)",
5549
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)"
5550
+ },
5551
+ c
5552
+ );
5553
+ })
5554
+ }
5555
+ )
5384
5556
  ] })
5385
5557
  ] })
5386
5558
  }
@@ -5393,20 +5565,10 @@ var ColorPickerPlugin = ({ disabled }) => {
5393
5565
  const lastRangeSelectionRef = React9__default.useRef(null);
5394
5566
  const updateToolbar = () => {
5395
5567
  const selection = $getSelection();
5396
- const isRange = $isRangeSelection(selection);
5397
- console.log("[AO-ColorPicker] updateToolbar", {
5398
- isRangeSelection: isRange,
5399
- isCollapsed: isRange ? selection.isCollapsed() : null
5400
- });
5401
- if (isRange) {
5568
+ if ($isRangeSelection(selection)) {
5402
5569
  lastRangeSelectionRef.current = selection.clone();
5403
5570
  const c = $getSelectionStyleValueForProperty(selection, "color", "#000000");
5404
- const bg = $getSelectionStyleValueForProperty(
5405
- selection,
5406
- "background-color",
5407
- "#ffffff"
5408
- );
5409
- console.log("[AO-ColorPicker] updateToolbar readback", { color: c, bgColor: bg });
5571
+ const bg = $getSelectionStyleValueForProperty(selection, "background-color", "#ffffff");
5410
5572
  setColors({ color: c, bgColor: bg });
5411
5573
  }
5412
5574
  };
@@ -5436,15 +5598,6 @@ var ColorPickerPlugin = ({ disabled }) => {
5436
5598
  };
5437
5599
  const applyStyle = (args) => {
5438
5600
  if (disabled) return;
5439
- console.log("[AO-ColorPicker] applyStyle called", {
5440
- property: args.property,
5441
- color: args.color,
5442
- hasSavedSelection: !!lastRangeSelectionRef.current,
5443
- savedSelectionIsCollapsed: lastRangeSelectionRef.current?.isCollapsed() ?? null,
5444
- wasEditorActiveAtOpen: wasEditorActiveRef.current,
5445
- activeElementTag: document.activeElement?.tagName,
5446
- activeElementClass: document.activeElement?.className
5447
- });
5448
5601
  editor.update(
5449
5602
  () => {
5450
5603
  const saved = lastRangeSelectionRef.current;
@@ -5452,39 +5605,17 @@ var ColorPickerPlugin = ({ disabled }) => {
5452
5605
  $setSelection(saved.clone());
5453
5606
  }
5454
5607
  const selection = $getSelection();
5455
- const isRange = $isRangeSelection(selection);
5456
- console.log("[AO-ColorPicker] applyStyle inside editor.update", {
5457
- hadSavedSelection: !!saved,
5458
- selectionAfterRestoreIsRange: isRange,
5459
- selectionAfterRestoreIsCollapsed: isRange ? selection.isCollapsed() : null
5460
- });
5461
- if (isRange) {
5608
+ if ($isRangeSelection(selection)) {
5462
5609
  $patchStyleText(selection, { [args.property]: args.color });
5463
- const verify = $getSelectionStyleValueForProperty(
5464
- selection,
5465
- args.property,
5466
- "<none>"
5467
- );
5468
- console.log("[AO-ColorPicker] applyStyle after patchStyleText, readback in same update", {
5469
- property: args.property,
5470
- appliedColor: args.color,
5471
- readBack: verify
5472
- });
5473
- } else {
5474
- console.warn(
5475
- "[AO-ColorPicker] applyStyle: no range selection available \u2014 style was NOT applied",
5476
- { property: args.property, color: args.color }
5477
- );
5478
5610
  }
5479
5611
  },
5480
5612
  // Without this tag, Lexical's reconciler force-focuses the editor root
5481
5613
  // whenever this update's selection diff finds the root isn't already
5482
- // focused (see Lexical.dev.mjs ~8112) — which it never is while the
5483
- // color picker's Callout legitimately holds focus. That forced focus
5484
- // then fights Fluent's Callout for focus on every single drag-driven
5485
- // commit, repeatedly bouncing focus (and, via FocusEventsPlugin,
5486
- // nulling and restoring the selection) between the editor and the
5487
- // popover until the drag's tracked color desynced from the cursor.
5614
+ // focused — which it isn't while the color picker's Callout holds
5615
+ // focus. The picker now only calls applyStyle once, on Apply, so this
5616
+ // is no longer fighting for focus on every drag pixel, but there's no
5617
+ // reason to force a focus change here at all — handleOpenChange above
5618
+ // already does that deliberately, once, on close.
5488
5619
  { tag: SKIP_SELECTION_FOCUS_TAG }
5489
5620
  );
5490
5621
  };
@@ -5853,6 +5984,60 @@ var InsertLinkPlugin = ({ disabled }) => {
5853
5984
  }
5854
5985
  );
5855
5986
  };
5987
+ function PageSetupPlugin({ disabled, value, onChange }) {
5988
+ const sizeLabel = value.size === "pageless" ? "Pageless" : PAGE_SIZE_OPTIONS.find((o) => o.key === value.size)?.label ?? "Pageless";
5989
+ const isPaged = value.size !== "pageless";
5990
+ return /* @__PURE__ */ jsxs(
5991
+ Menu,
5992
+ {
5993
+ checkedValues: {
5994
+ size: [value.size],
5995
+ orientation: [value.orientation],
5996
+ margin: [value.margin]
5997
+ },
5998
+ onCheckedValueChange: (_, data) => {
5999
+ const selected = data.checkedItems[0];
6000
+ if (!selected) return;
6001
+ if (data.name === "size") onChange({ ...value, size: selected });
6002
+ else if (data.name === "orientation")
6003
+ onChange({ ...value, orientation: selected });
6004
+ else if (data.name === "margin") onChange({ ...value, margin: selected });
6005
+ },
6006
+ children: [
6007
+ /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
6008
+ Button,
6009
+ {
6010
+ appearance: "subtle",
6011
+ size: "small",
6012
+ disabled,
6013
+ icon: /* @__PURE__ */ jsx(DocumentRegular, {}),
6014
+ title: "Page setup",
6015
+ style: { minWidth: "auto" },
6016
+ children: sizeLabel
6017
+ }
6018
+ ) }),
6019
+ /* @__PURE__ */ jsx(MenuPopover, { style: { minWidth: 220 }, children: /* @__PURE__ */ jsxs(MenuList, { children: [
6020
+ /* @__PURE__ */ jsxs(MenuGroup, { children: [
6021
+ /* @__PURE__ */ jsx(MenuGroupHeader, { children: "Page size" }),
6022
+ /* @__PURE__ */ jsx(MenuItemRadio, { name: "size", value: "pageless", children: "Pageless" }),
6023
+ PAGE_SIZE_OPTIONS.map((opt) => /* @__PURE__ */ jsx(MenuItemRadio, { name: "size", value: opt.key, children: opt.label }, opt.key))
6024
+ ] }),
6025
+ /* @__PURE__ */ jsx(MenuDivider, {}),
6026
+ /* @__PURE__ */ jsxs(MenuGroup, { children: [
6027
+ /* @__PURE__ */ jsx(MenuGroupHeader, { children: "Orientation" }),
6028
+ /* @__PURE__ */ jsx(MenuItemRadio, { name: "orientation", value: "portrait", disabled: !isPaged, children: "Portrait" }),
6029
+ /* @__PURE__ */ jsx(MenuItemRadio, { name: "orientation", value: "landscape", disabled: !isPaged, children: "Landscape" })
6030
+ ] }),
6031
+ /* @__PURE__ */ jsx(MenuDivider, {}),
6032
+ /* @__PURE__ */ jsxs(MenuGroup, { children: [
6033
+ /* @__PURE__ */ jsx(MenuGroupHeader, { children: "Margins" }),
6034
+ MARGIN_OPTIONS.map((opt) => /* @__PURE__ */ jsx(MenuItemRadio, { name: "margin", value: opt.key, disabled: !isPaged, children: opt.label }, opt.key))
6035
+ ] })
6036
+ ] }) })
6037
+ ]
6038
+ }
6039
+ );
6040
+ }
5856
6041
  var TableItemPlugin = ({ disabled }) => {
5857
6042
  const [editor] = useLexicalComposerContext();
5858
6043
  const [columns, setColumns] = useState("");
@@ -6070,7 +6255,8 @@ var ALLOWED_TOKENS = {
6070
6255
  FontSize: true,
6071
6256
  Decorators: true,
6072
6257
  CodeBlock: true,
6073
- Align: true
6258
+ Align: true,
6259
+ PageSetup: true
6074
6260
  };
6075
6261
  function sanitizePluginGroups(groups) {
6076
6262
  if (!groups || groups.length === 0) return [];
@@ -6609,6 +6795,16 @@ var ToolBarPlugins = (props) => {
6609
6795
  key
6610
6796
  );
6611
6797
  }
6798
+ case "PageSetup":
6799
+ return /* @__PURE__ */ jsx(
6800
+ PageSetupPlugin,
6801
+ {
6802
+ disabled: !isEditable || props.readOnly,
6803
+ value: props.pageSetup,
6804
+ onChange: props.onPageSetupChange
6805
+ },
6806
+ key
6807
+ );
6612
6808
  default:
6613
6809
  return null;
6614
6810
  }
@@ -6830,12 +7026,6 @@ function FocusEventsPlugin({
6830
7026
  const next = e.relatedTarget;
6831
7027
  const container = containerRef.current;
6832
7028
  const stillInside = !!next && (container ? container.contains(next) : root.contains(next));
6833
- console.log("[AO-ColorPicker] FocusEventsPlugin focusout", {
6834
- relatedTargetTag: next ? next.tagName : null,
6835
- relatedTargetClass: next ? next.className : null,
6836
- stillInside,
6837
- willClearSelection: !stillInside
6838
- });
6839
7029
  if (stillInside) return;
6840
7030
  editor.update(() => {
6841
7031
  $setSelection(null);
@@ -6966,6 +7156,8 @@ var ContentEditorComponent = forwardRef(
6966
7156
  const [charCount, setCharCount] = useState(0);
6967
7157
  const handleCharCount = useCallback((count) => setCharCount(count), []);
6968
7158
  const [refErrors, setRefErrors] = useState([]);
7159
+ const [pageSetup, setPageSetup] = useState(DEFAULT_PAGE_SETUP);
7160
+ const pageCanvas = resolvePageCanvasMetrics(pageSetup);
6969
7161
  const contentEditableDomRef = useRef(null);
6970
7162
  const previousOverLimitRef = useRef(false);
6971
7163
  const focusedRef = useRef(false);
@@ -7007,8 +7199,8 @@ var ContentEditorComponent = forwardRef(
7007
7199
  color: "var(--colorNeutralForeground3, grey)",
7008
7200
  position: "absolute",
7009
7201
  top: props.level !== "none" /* None */ ? "17px" : "27px",
7010
- left: 20,
7011
- right: 20,
7202
+ left: pageCanvas.paddingPx,
7203
+ right: pageCanvas.paddingPx,
7012
7204
  fontSize: "14px",
7013
7205
  pointerEvents: "none",
7014
7206
  userSelect: "none"
@@ -7019,8 +7211,6 @@ var ContentEditorComponent = forwardRef(
7019
7211
  outline: "none",
7020
7212
  overflow: "auto",
7021
7213
  marginTop: "0px",
7022
- paddingLeft: "20px",
7023
- paddingRight: "20px",
7024
7214
  position: "relative",
7025
7215
  background: "var(--colorNeutralBackground1, #ffffff)",
7026
7216
  justifyContent: "center",
@@ -7120,7 +7310,9 @@ var ContentEditorComponent = forwardRef(
7120
7310
  ToolBarPlugins,
7121
7311
  {
7122
7312
  level: props.level ?? "basic" /* Basic */,
7123
- readOnly: props.readOnly
7313
+ readOnly: props.readOnly,
7314
+ pageSetup,
7315
+ onPageSetupChange: setPageSetup
7124
7316
  }
7125
7317
  )
7126
7318
  }
@@ -7134,7 +7326,8 @@ var ContentEditorComponent = forwardRef(
7134
7326
  padding: "15px 0px",
7135
7327
  overflowY: "scroll",
7136
7328
  overflowX: "auto",
7137
- minWidth: 0
7329
+ minWidth: 0,
7330
+ background: pageCanvas.widthPx !== void 0 ? "#eef0f2" : void 0
7138
7331
  },
7139
7332
  onClickCapture: handleReadOnlyClickCapture,
7140
7333
  children: [
@@ -7153,7 +7346,15 @@ var ContentEditorComponent = forwardRef(
7153
7346
  {
7154
7347
  ref: contentEditableDomRef,
7155
7348
  className: css(EditorStyles.contentEditor),
7156
- style: { paddingTop: props.level !== "none" /* None */ ? 0 : 10 },
7349
+ style: {
7350
+ paddingTop: props.level !== "none" /* None */ ? 0 : 10,
7351
+ paddingLeft: pageCanvas.paddingPx,
7352
+ paddingRight: pageCanvas.paddingPx,
7353
+ maxWidth: pageCanvas.widthPx,
7354
+ marginLeft: pageCanvas.widthPx !== void 0 ? "auto" : void 0,
7355
+ marginRight: pageCanvas.widthPx !== void 0 ? "auto" : void 0,
7356
+ 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
7357
+ },
7157
7358
  spellCheck: !resolvedSpellCheck,
7158
7359
  autoCorrect: resolvedSpellCheck ? "off" : void 0,
7159
7360
  autoCapitalize: resolvedSpellCheck ? "off" : void 0