@tarviks/lexical-rich-editor 1.2.0 → 1.3.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
@@ -5,13 +5,12 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
6
6
  import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
7
7
  import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
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';
8
+ import { createCommand, DecoratorNode, COMMAND_PRIORITY_HIGH, TextNode, ElementNode, SELECTION_CHANGE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, $getSelection, $isRangeSelection, $findMatchingParent, COMMAND_PRIORITY_LOW, KEY_DOWN_COMMAND, $getNodeByKey, $createRangeSelection, $setSelection, $createParagraphNode, $insertNodes, $isRootOrShadowRoot, COMMAND_PRIORITY_EDITOR, PASTE_COMMAND, DRAGSTART_COMMAND, DRAGOVER_COMMAND, DROP_COMMAND, $getRoot, $createTextNode, $isTextNode, $isElementNode, FORMAT_ELEMENT_COMMAND, CLICK_COMMAND, $isNodeSelection, $applyNodeReplacement, FORMAT_TEXT_COMMAND, $isDecoratorNode, $getNearestNodeFromDOMNode, COMMAND_PRIORITY_CRITICAL, REDO_COMMAND, UNDO_COMMAND, getDOMSelection, KEY_ESCAPE_COMMAND, isHTMLElement, getDOMSelectionFromTarget, $isLineBreakNode, SKIP_SELECTION_FOCUS_TAG, createEditor, KEY_ENTER_COMMAND } from 'lexical';
9
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';
10
+ import { makeStyles, FluentProvider, webLightTheme, Menu, MenuTrigger, MenuPopover, MenuList, MenuGroup, MenuGroupHeader, MenuItem, MenuDivider, Dropdown, Option, Button, ToolbarDivider, MenuItemRadio, Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle, DialogContent, Field, Input, DialogActions, MessageBar, MessageBarBody } from '@fluentui/react-components';
12
11
  import { CodeHighlightNode, CodeNode, $isCodeHighlightNode } from '@lexical/code';
13
12
  import { LinkNode, AutoLinkNode, $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND, $createLinkNode } from '@lexical/link';
14
- import { ListNode, ListItemNode, $isListNode, REMOVE_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list';
13
+ import { ListNode, ListItemNode, $isListNode, REMOVE_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, $removeList, $insertList, $isListItemNode } from '@lexical/list';
15
14
  import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';
16
15
  import { createLinkMatcherWithRegExp, AutoLinkPlugin } from '@lexical/react/LexicalAutoLinkPlugin';
17
16
  import { LexicalComposer } from '@lexical/react/LexicalComposer';
@@ -23,9 +22,9 @@ import { ListPlugin } from '@lexical/react/LexicalListPlugin';
23
22
  import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
24
23
  import { TablePlugin } from '@lexical/react/LexicalTablePlugin';
25
24
  import { HeadingNode, QuoteNode, $isHeadingNode, $createQuoteNode, $createHeadingNode } from '@lexical/rich-text';
26
- import { TableNode, TableRowNode, TableCellNode, $isTableSelection, $isTableNode, $isTableCellNode, $insertTableColumnAtSelection, $insertTableRowAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, $getTableNodeFromLexicalNodeOrThrow, getDOMCellFromTarget, getTableObserverFromTableElement, $getTableRowIndexFromTableCellNode, $isTableRowNode, $getTableColumnIndexFromTableCellNode, $createTableNodeWithDimensions } from '@lexical/table';
27
- import { BlockWithAlignableContents } from '@lexical/react/LexicalBlockWithAlignableContents';
25
+ import { TableNode, TableRowNode, TableCellNode, $isTableSelection, $isTableNode, $isTableCellNode, $insertTableColumnAtSelection, $insertTableRowAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, getDOMCellFromTarget, getTableObserverFromTableElement, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $isTableRowNode, $getTableColumnIndexFromTableCellNode, $createTableNodeWithDimensions } from '@lexical/table';
28
26
  import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
27
+ import { 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, DismissRegular, CheckmarkRegular, EditRegular, CodeFilled, LinkFilled, Dismiss16Regular } from '@fluentui/react-icons';
29
28
  import { $setBlocksType, $patchStyleText, $isAtNodeEnd, $getSelectionStyleValueForProperty } from '@lexical/selection';
30
29
  import { createPortal } from 'react-dom';
31
30
  import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
@@ -523,17 +522,6 @@ var init_ImageComponent = __esm({
523
522
  onClick,
524
523
  COMMAND_PRIORITY_LOW
525
524
  ),
526
- editor.registerCommand(
527
- DRAGSTART_COMMAND,
528
- (event) => {
529
- if (event.target === imageRef.current) {
530
- event.preventDefault();
531
- return true;
532
- }
533
- return false;
534
- },
535
- COMMAND_PRIORITY_LOW
536
- ),
537
525
  editor.registerCommand(
538
526
  KEY_DELETE_COMMAND,
539
527
  $onDelete,
@@ -591,37 +579,44 @@ var init_ImageComponent = __esm({
591
579
  const onResizeStart = () => {
592
580
  setIsResizing(true);
593
581
  };
594
- const draggable = isSelected && $isNodeSelection(selection) && !isResizing;
582
+ const draggable = !isResizing;
595
583
  const isFocused = (isSelected || isResizing) && isEditable;
596
- return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs(Fragment, { children: [
597
- /* @__PURE__ */ jsx("div", { draggable, children: isLoadError ? /* @__PURE__ */ jsx(BrokenImage, {}) : /* @__PURE__ */ jsx(
598
- LazyImage,
599
- {
600
- className: isFocused ? `focused ${$isNodeSelection(selection) ? "draggable" : ""}` : null,
601
- src,
602
- altText,
603
- imageRef,
604
- width,
605
- height,
606
- maxWidth,
607
- onError: () => setIsLoadError(true)
608
- }
609
- ) }),
610
- resizable && $isNodeSelection(selection) && isFocused && /* @__PURE__ */ jsx(
611
- ImageResizer_default,
612
- {
613
- showCaption,
614
- setShowCaption,
615
- editor,
616
- buttonRef,
617
- imageRef,
618
- maxWidth,
619
- onResizeStart,
620
- onResizeEnd,
621
- captionsEnabled: !isLoadError && captionsEnabled
622
- }
623
- )
624
- ] }) });
584
+ return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs(
585
+ "div",
586
+ {
587
+ draggable,
588
+ style: { position: "relative", display: "inline-block" },
589
+ children: [
590
+ isLoadError ? /* @__PURE__ */ jsx(BrokenImage, {}) : /* @__PURE__ */ jsx(
591
+ LazyImage,
592
+ {
593
+ className: isFocused ? `focused ${$isNodeSelection(selection) ? "draggable" : ""}` : null,
594
+ src,
595
+ altText,
596
+ imageRef,
597
+ width,
598
+ height,
599
+ maxWidth,
600
+ onError: () => setIsLoadError(true)
601
+ }
602
+ ),
603
+ resizable && $isNodeSelection(selection) && isFocused && /* @__PURE__ */ jsx(
604
+ ImageResizer_default,
605
+ {
606
+ showCaption,
607
+ setShowCaption,
608
+ editor,
609
+ buttonRef,
610
+ imageRef,
611
+ maxWidth,
612
+ onResizeStart,
613
+ onResizeEnd,
614
+ captionsEnabled: !isLoadError && captionsEnabled
615
+ }
616
+ )
617
+ ]
618
+ }
619
+ ) });
625
620
  };
626
621
  ImageComponent_default = ImageComponent;
627
622
  }
@@ -855,6 +850,7 @@ __export(InlineImageComponent_exports, {
855
850
  var imageCache2, useSuspenseImage2, LazyImage2, InlineImageComponent, InlineImageComponent_default;
856
851
  var init_InlineImageComponent = __esm({
857
852
  "src/Nodes/InlineImageComponent.tsx"() {
853
+ init_ImageResizer();
858
854
  init_InlineImage();
859
855
  init_InlineImageNode();
860
856
  imageCache2 = /* @__PURE__ */ new Set();
@@ -891,6 +887,7 @@ var init_InlineImageComponent = __esm({
891
887
  };
892
888
  InlineImageComponent = ({ src, altText, nodeKey, width, height, showCaption, caption, position }) => {
893
889
  const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
890
+ const [isResizing, setIsResizing] = useState(false);
894
891
  const [selection, setSelection] = useState(null);
895
892
  const activeEditorRef = useRef(null);
896
893
  const imageRef = useRef(null);
@@ -952,6 +949,25 @@ var init_InlineImageComponent = __esm({
952
949
  },
953
950
  [caption, editor, setSelected]
954
951
  );
952
+ const onClick = useCallback(
953
+ (payload) => {
954
+ const event = payload;
955
+ if (isResizing) {
956
+ return true;
957
+ }
958
+ if (event.target === imageRef.current) {
959
+ if (event.shiftKey) {
960
+ setSelected(!isSelected);
961
+ } else {
962
+ clearSelection();
963
+ setSelected(true);
964
+ }
965
+ return true;
966
+ }
967
+ return false;
968
+ },
969
+ [isResizing, isSelected, setSelected, clearSelection]
970
+ );
955
971
  useEffect(() => {
956
972
  let isMounted = true;
957
973
  const unregister = mergeRegister(
@@ -970,48 +986,13 @@ var init_InlineImageComponent = __esm({
970
986
  ),
971
987
  editor.registerCommand(
972
988
  CLICK_COMMAND,
973
- (payload) => {
974
- const event = payload;
975
- if (event.target === imageRef.current) {
976
- if (event.shiftKey) {
977
- setSelected(!isSelected);
978
- } else {
979
- clearSelection();
980
- setSelected(true);
981
- }
982
- return true;
983
- }
984
- return false;
985
- },
986
- COMMAND_PRIORITY_LOW
987
- ),
988
- editor.registerCommand(
989
- DRAGSTART_COMMAND,
990
- (event) => {
991
- if (event.target === imageRef.current) {
992
- event.preventDefault();
993
- return true;
994
- }
995
- return false;
996
- },
997
- COMMAND_PRIORITY_LOW
998
- ),
999
- editor.registerCommand(
1000
- KEY_DELETE_COMMAND,
1001
- $onDelete,
1002
- COMMAND_PRIORITY_LOW
1003
- ),
1004
- editor.registerCommand(
1005
- KEY_BACKSPACE_COMMAND,
1006
- $onDelete,
989
+ onClick,
1007
990
  COMMAND_PRIORITY_LOW
1008
991
  ),
992
+ editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW),
993
+ editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW),
1009
994
  editor.registerCommand(KEY_ENTER_COMMAND, $onEnter, COMMAND_PRIORITY_LOW),
1010
- editor.registerCommand(
1011
- KEY_ESCAPE_COMMAND,
1012
- $onEscape,
1013
- COMMAND_PRIORITY_LOW
1014
- )
995
+ editor.registerCommand(KEY_ESCAPE_COMMAND, $onEscape, COMMAND_PRIORITY_LOW)
1015
996
  );
1016
997
  return () => {
1017
998
  isMounted = false;
@@ -1020,27 +1001,59 @@ var init_InlineImageComponent = __esm({
1020
1001
  }, [
1021
1002
  clearSelection,
1022
1003
  editor,
1004
+ isResizing,
1023
1005
  isSelected,
1024
1006
  nodeKey,
1025
1007
  $onDelete,
1026
1008
  $onEnter,
1027
1009
  $onEscape,
1010
+ onClick,
1028
1011
  setSelected
1029
1012
  ]);
1030
- const draggable = isSelected && $isNodeSelection(selection);
1031
- const isFocused = isSelected && isEditable;
1032
- return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx("span", { draggable, children: /* @__PURE__ */ jsx(
1033
- LazyImage2,
1034
- {
1035
- className: isFocused ? `focused ${$isNodeSelection(selection) ? "draggable" : ""}` : null,
1036
- src,
1037
- altText,
1038
- imageRef,
1039
- width,
1040
- height,
1041
- position
1042
- }
1043
- ) }) });
1013
+ const onResizeStart = () => {
1014
+ setIsResizing(true);
1015
+ };
1016
+ const onResizeEnd = (nextWidth, nextHeight) => {
1017
+ setTimeout(() => {
1018
+ setIsResizing(false);
1019
+ }, 200);
1020
+ editor.update(() => {
1021
+ const node = $getNodeByKey(nodeKey);
1022
+ if ($isInlineImageNode(node)) {
1023
+ node.setWidthAndHeight(nextWidth, nextHeight);
1024
+ }
1025
+ });
1026
+ };
1027
+ const draggable = !isResizing;
1028
+ const isFocused = (isSelected || isResizing) && isEditable;
1029
+ return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs("span", { draggable, style: { position: "relative", display: "inline-block" }, children: [
1030
+ /* @__PURE__ */ jsx(
1031
+ LazyImage2,
1032
+ {
1033
+ className: isFocused ? `focused ${$isNodeSelection(selection) ? "draggable" : ""}` : null,
1034
+ src,
1035
+ altText,
1036
+ imageRef,
1037
+ width,
1038
+ height,
1039
+ position
1040
+ }
1041
+ ),
1042
+ $isNodeSelection(selection) && isFocused && /* @__PURE__ */ jsx(
1043
+ ImageResizer_default,
1044
+ {
1045
+ showCaption: false,
1046
+ setShowCaption: () => {
1047
+ },
1048
+ captionsEnabled: false,
1049
+ editor,
1050
+ buttonRef,
1051
+ imageRef,
1052
+ onResizeStart,
1053
+ onResizeEnd
1054
+ }
1055
+ )
1056
+ ] }) });
1044
1057
  };
1045
1058
  InlineImageComponent_default = InlineImageComponent;
1046
1059
  }
@@ -1228,6 +1241,19 @@ var ContentEditorLevel = /* @__PURE__ */ ((ContentEditorLevel2) => {
1228
1241
  ContentEditorLevel2["Pro"] = "pro";
1229
1242
  return ContentEditorLevel2;
1230
1243
  })(ContentEditorLevel || {});
1244
+ var DEFAULT_VALIDATION_MESSAGES = {
1245
+ required: "This field is required.",
1246
+ minWords: (current, min) => `Minimum ${min} word${min === 1 ? "" : "s"} required (${current} entered).`,
1247
+ maxWords: (current, max) => `Word limit exceeded: ${current} / ${max} words.`,
1248
+ minChars: (current, min) => `Minimum ${min} character${min === 1 ? "" : "s"} required (${current} entered).`,
1249
+ maxChars: (current, max) => `Character limit exceeded: ${current} / ${max} characters.`,
1250
+ noImages: "Images are not allowed in this field.",
1251
+ maxImages: (current, max) => `Too many images: ${current} / ${max} allowed.`,
1252
+ noLinks: "Hyperlinks are not allowed in this field.",
1253
+ maxLinks: (current, max) => `Too many links: ${current} / ${max} allowed.`,
1254
+ noTables: "Tables are not allowed in this field.",
1255
+ imageTooLarge: (fileMB, maxMB) => `Image size (${fileMB.toFixed(1)} MB) exceeds the ${maxMB} MB limit.`
1256
+ };
1231
1257
 
1232
1258
  // src/Types/PageSetup.ts
1233
1259
  var DEFAULT_PAGE_SETUP = {
@@ -1268,6 +1294,106 @@ function resolvePageCanvasMetrics(value) {
1268
1294
  paddingPx: Math.round(margin.valueIn * CSS_PX_PER_INCH)
1269
1295
  };
1270
1296
  }
1297
+ createCommand(
1298
+ "INSERT_ALPHA_LIST_COMMAND"
1299
+ );
1300
+ var AlphaListNode = class _AlphaListNode extends ListNode {
1301
+ static getType() {
1302
+ return "alpha-list";
1303
+ }
1304
+ static clone(node) {
1305
+ return new _AlphaListNode(node.getStart(), node.__key);
1306
+ }
1307
+ constructor(start = 1, key) {
1308
+ super("number", start, key);
1309
+ this.__listType = "lower-alpha";
1310
+ }
1311
+ createDOM(config, _editor) {
1312
+ const element = document.createElement("ol");
1313
+ element.setAttribute("type", "a");
1314
+ if (this.getStart() !== 1) element.setAttribute("start", String(this.getStart()));
1315
+ element.style.listStyleType = "lower-alpha";
1316
+ const olClass = config.theme?.list?.ol;
1317
+ if (olClass) element.className = olClass;
1318
+ return element;
1319
+ }
1320
+ updateDOM() {
1321
+ return false;
1322
+ }
1323
+ exportDOM(_editor) {
1324
+ const element = document.createElement("ol");
1325
+ element.setAttribute("type", "a");
1326
+ if (this.getStart() !== 1) element.setAttribute("start", String(this.getStart()));
1327
+ element.style.listStyleType = "lower-alpha";
1328
+ return { element };
1329
+ }
1330
+ static importDOM() {
1331
+ return {
1332
+ ol: (domNode) => {
1333
+ if (domNode instanceof HTMLOListElement && (domNode.getAttribute("type") === "a" || domNode.style.listStyleType === "lower-alpha")) {
1334
+ return { conversion: convertAlphaOL, priority: 1 };
1335
+ }
1336
+ return null;
1337
+ }
1338
+ };
1339
+ }
1340
+ exportJSON() {
1341
+ const base = super.exportJSON();
1342
+ return {
1343
+ ...base,
1344
+ type: "alpha-list",
1345
+ listType: "lower-alpha",
1346
+ tag: "ol",
1347
+ version: 1,
1348
+ start: this.getStart()
1349
+ };
1350
+ }
1351
+ static importJSON(serialized) {
1352
+ return new _AlphaListNode(serialized.start ?? 1);
1353
+ }
1354
+ };
1355
+ function convertAlphaOL(domNode) {
1356
+ const start = domNode instanceof HTMLOListElement ? parseInt(domNode.getAttribute("start") || "1", 10) || 1 : 1;
1357
+ return { node: $createAlphaListNode(start) };
1358
+ }
1359
+ function $createAlphaListNode(start = 1) {
1360
+ return new AlphaListNode(start);
1361
+ }
1362
+ function $isAlphaListNode(node) {
1363
+ return node instanceof AlphaListNode;
1364
+ }
1365
+ function $toggleAlphaList() {
1366
+ const selection = $getSelection();
1367
+ if (!$isRangeSelection(selection)) return;
1368
+ const anchor = selection.anchor.getNode();
1369
+ const currentList = $findMatchingParent$1(anchor, $isListNode);
1370
+ if ($isAlphaListNode(currentList)) {
1371
+ $removeList();
1372
+ return;
1373
+ }
1374
+ $insertList("number");
1375
+ const sel2 = $getSelection();
1376
+ if (!$isRangeSelection(sel2)) return;
1377
+ const newAnchor = sel2.anchor.getNode();
1378
+ const newList = $findMatchingParent$1(newAnchor, (n) => $isListNode(n) && !$isAlphaListNode(n));
1379
+ if (!newList || !$isListNode(newList)) return;
1380
+ const start = newList.getStart();
1381
+ const formatType = typeof newList.getFormatType === "function" ? newList.getFormatType() : void 0;
1382
+ const alpha = $createAlphaListNode(start);
1383
+ for (const child of newList.getChildren()) {
1384
+ alpha.append(child);
1385
+ }
1386
+ newList.replace(alpha);
1387
+ if (formatType && typeof alpha.setFormat === "function") {
1388
+ alpha.setFormat(formatType);
1389
+ }
1390
+ const firstItem = alpha.getFirstChild();
1391
+ if ($isListItemNode(firstItem)) {
1392
+ firstItem.selectStart();
1393
+ } else {
1394
+ alpha.select();
1395
+ }
1396
+ }
1271
1397
  var AutocompleteNode = class _AutocompleteNode extends TextNode {
1272
1398
  static getType() {
1273
1399
  return "autocomplete";
@@ -1278,6 +1404,13 @@ var AutocompleteNode = class _AutocompleteNode extends TextNode {
1278
1404
  static importJSON(serializedNode) {
1279
1405
  return new _AutocompleteNode(serializedNode.text, serializedNode.uuid);
1280
1406
  }
1407
+ // AutocompleteNode is excluded from copy via excludeFromCopy(), so it is
1408
+ // never placed on the clipboard and never needs to be reconstructed from
1409
+ // HTML. Returning null here satisfies Lexical's exportDOM/importDOM
1410
+ // contract check and silences the console warning.
1411
+ static importDOM() {
1412
+ return null;
1413
+ }
1281
1414
  exportJSON() {
1282
1415
  return { ...super.exportJSON(), uuid: this.__uuid };
1283
1416
  }
@@ -1592,13 +1725,20 @@ function VideoResizer({
1592
1725
  const dy = ev.clientY - rs.startY;
1593
1726
  let newW = rs.startW;
1594
1727
  let newH = rs.startH;
1595
- if (dir === "se") {
1728
+ if (dir === "se" || dir === "ne") {
1596
1729
  newW = Math.max(MIN_WIDTH, rs.startW + dx);
1597
1730
  newH = newW / rs.ratio;
1731
+ } else if (dir === "nw" || dir === "sw") {
1732
+ newW = Math.max(MIN_WIDTH, rs.startW - dx);
1733
+ newH = newW / rs.ratio;
1598
1734
  } else if (dir === "e") {
1599
1735
  newW = Math.max(MIN_WIDTH, rs.startW + dx);
1736
+ } else if (dir === "w") {
1737
+ newW = Math.max(MIN_WIDTH, rs.startW - dx);
1600
1738
  } else if (dir === "s") {
1601
1739
  newH = Math.max(MIN_HEIGHT, rs.startH + dy);
1740
+ } else if (dir === "n") {
1741
+ newH = Math.max(MIN_HEIGHT, rs.startH - dy);
1602
1742
  }
1603
1743
  container.style.width = `${newW}px`;
1604
1744
  container.style.height = `${newH}px`;
@@ -1618,13 +1758,23 @@ function VideoResizer({
1618
1758
  /* @__PURE__ */ jsx(
1619
1759
  "div",
1620
1760
  {
1621
- style: {
1622
- ...handleBase,
1623
- top: "50%",
1624
- right: -5,
1625
- transform: "translateY(-50%)",
1626
- cursor: "ew-resize"
1627
- },
1761
+ style: { ...handleBase, top: -5, left: "50%", transform: "translateX(-50%)", cursor: "n-resize" },
1762
+ onPointerDown: (e) => startResize(e, "n"),
1763
+ title: "Resize height"
1764
+ }
1765
+ ),
1766
+ /* @__PURE__ */ jsx(
1767
+ "div",
1768
+ {
1769
+ style: { ...handleBase, top: -5, right: -5, cursor: "ne-resize" },
1770
+ onPointerDown: (e) => startResize(e, "ne"),
1771
+ title: "Resize (proportional)"
1772
+ }
1773
+ ),
1774
+ /* @__PURE__ */ jsx(
1775
+ "div",
1776
+ {
1777
+ style: { ...handleBase, top: "50%", right: -5, transform: "translateY(-50%)", cursor: "ew-resize" },
1628
1778
  onPointerDown: (e) => startResize(e, "e"),
1629
1779
  title: "Resize width"
1630
1780
  }
@@ -1632,13 +1782,15 @@ function VideoResizer({
1632
1782
  /* @__PURE__ */ jsx(
1633
1783
  "div",
1634
1784
  {
1635
- style: {
1636
- ...handleBase,
1637
- bottom: -5,
1638
- left: "50%",
1639
- transform: "translateX(-50%)",
1640
- cursor: "ns-resize"
1641
- },
1785
+ style: { ...handleBase, bottom: -5, right: -5, cursor: "se-resize" },
1786
+ onPointerDown: (e) => startResize(e, "se"),
1787
+ title: "Resize (proportional)"
1788
+ }
1789
+ ),
1790
+ /* @__PURE__ */ jsx(
1791
+ "div",
1792
+ {
1793
+ style: { ...handleBase, bottom: -5, left: "50%", transform: "translateX(-50%)", cursor: "s-resize" },
1642
1794
  onPointerDown: (e) => startResize(e, "s"),
1643
1795
  title: "Resize height"
1644
1796
  }
@@ -1646,15 +1798,30 @@ function VideoResizer({
1646
1798
  /* @__PURE__ */ jsx(
1647
1799
  "div",
1648
1800
  {
1649
- style: { ...handleBase, bottom: -5, right: -5, cursor: "se-resize" },
1650
- onPointerDown: (e) => startResize(e, "se"),
1801
+ style: { ...handleBase, bottom: -5, left: -5, cursor: "sw-resize" },
1802
+ onPointerDown: (e) => startResize(e, "sw"),
1803
+ title: "Resize (proportional)"
1804
+ }
1805
+ ),
1806
+ /* @__PURE__ */ jsx(
1807
+ "div",
1808
+ {
1809
+ style: { ...handleBase, top: "50%", left: -5, transform: "translateY(-50%)", cursor: "ew-resize" },
1810
+ onPointerDown: (e) => startResize(e, "w"),
1811
+ title: "Resize width"
1812
+ }
1813
+ ),
1814
+ /* @__PURE__ */ jsx(
1815
+ "div",
1816
+ {
1817
+ style: { ...handleBase, top: -5, left: -5, cursor: "nw-resize" },
1818
+ onPointerDown: (e) => startResize(e, "nw"),
1651
1819
  title: "Resize (proportional)"
1652
1820
  }
1653
1821
  )
1654
1822
  ] });
1655
1823
  }
1656
1824
  function YouTubeComponent({
1657
- className,
1658
1825
  format,
1659
1826
  nodeKey,
1660
1827
  videoID,
@@ -1662,9 +1829,48 @@ function YouTubeComponent({
1662
1829
  height,
1663
1830
  editor
1664
1831
  }) {
1832
+ const wrapperRef = React9.useRef(null);
1665
1833
  const containerRef = React9.useRef(null);
1834
+ const iframeRef = React9.useRef(null);
1835
+ const isResizingRef = React9.useRef(false);
1836
+ const [isNodeSelected, setNodeSelected, clearNodeSelection] = useLexicalNodeSelection(nodeKey);
1666
1837
  const [isHovered, setIsHovered] = React9.useState(false);
1667
1838
  const [isResizing, setIsResizing] = React9.useState(false);
1839
+ const [isPlaying, setIsPlaying] = React9.useState(false);
1840
+ React9.useEffect(() => {
1841
+ return mergeRegister(
1842
+ editor.registerCommand(
1843
+ FORMAT_ELEMENT_COMMAND,
1844
+ (formatType) => {
1845
+ if (isNodeSelected) {
1846
+ editor.update(() => {
1847
+ const node = $getNodeByKey(nodeKey);
1848
+ if ($isYouTubeNode(node)) node.setFormat(formatType);
1849
+ });
1850
+ return true;
1851
+ }
1852
+ return false;
1853
+ },
1854
+ COMMAND_PRIORITY_LOW
1855
+ ),
1856
+ // Select this node on click. When the thumbnail is shown the <img> is
1857
+ // in the parent DOM so CLICK_COMMAND fires naturally. When the iframe is
1858
+ // live, clicks land in the iframe's browsing context — the node stays
1859
+ // selected via wrapperRef boundary detection on the wrapper div.
1860
+ editor.registerCommand(
1861
+ CLICK_COMMAND,
1862
+ (event) => {
1863
+ if (wrapperRef.current?.contains(event.target)) {
1864
+ if (!event.shiftKey) clearNodeSelection();
1865
+ setNodeSelected(true);
1866
+ return true;
1867
+ }
1868
+ return false;
1869
+ },
1870
+ COMMAND_PRIORITY_LOW
1871
+ )
1872
+ );
1873
+ }, [editor, isNodeSelected, nodeKey, clearNodeSelection, setNodeSelected]);
1668
1874
  const handleDelete = (e) => {
1669
1875
  e.preventDefault();
1670
1876
  e.stopPropagation();
@@ -1673,72 +1879,173 @@ function YouTubeComponent({
1673
1879
  node?.remove();
1674
1880
  });
1675
1881
  };
1882
+ const handleResizeStart = () => {
1883
+ isResizingRef.current = true;
1884
+ setIsResizing(true);
1885
+ if (iframeRef.current) iframeRef.current.style.pointerEvents = "none";
1886
+ };
1676
1887
  const handleResizeEnd = (w, h) => {
1888
+ isResizingRef.current = false;
1677
1889
  setIsResizing(false);
1890
+ if (iframeRef.current) iframeRef.current.style.pointerEvents = "";
1891
+ setIsHovered(true);
1678
1892
  editor.update(() => {
1679
1893
  const node = $getNodeByKey(nodeKey);
1680
- if ($isYouTubeNode(node)) {
1681
- node.setSize(Math.round(w), Math.round(h));
1682
- }
1894
+ if ($isYouTubeNode(node)) node.setSize(Math.round(w), Math.round(h));
1683
1895
  });
1684
1896
  };
1685
- return /* @__PURE__ */ jsx(BlockWithAlignableContents, { className, format, nodeKey, children: /* @__PURE__ */ jsxs(
1897
+ const actionBtnStyle = (side) => ({
1898
+ position: "absolute",
1899
+ top: 8,
1900
+ [side]: 8,
1901
+ width: 28,
1902
+ height: 28,
1903
+ borderRadius: "50%",
1904
+ background: "rgba(0,0,0,0.65)",
1905
+ color: "#fff",
1906
+ border: "none",
1907
+ cursor: "pointer",
1908
+ fontSize: side === "right" ? 18 : 14,
1909
+ lineHeight: 1,
1910
+ padding: 0,
1911
+ zIndex: 10,
1912
+ display: "flex",
1913
+ alignItems: "center",
1914
+ justifyContent: "center"
1915
+ });
1916
+ return /* @__PURE__ */ jsx("div", { ref: wrapperRef, style: { display: "block", textAlign: format || void 0 }, children: /* @__PURE__ */ jsxs(
1686
1917
  "div",
1687
1918
  {
1688
1919
  ref: containerRef,
1689
- style: { position: "relative", display: "inline-block", width, height, lineHeight: 0 },
1920
+ style: {
1921
+ position: "relative",
1922
+ display: "inline-block",
1923
+ width,
1924
+ height,
1925
+ lineHeight: 0,
1926
+ outline: isNodeSelected ? "2px solid #0078d4" : void 0,
1927
+ outlineOffset: 2
1928
+ },
1690
1929
  onMouseEnter: () => setIsHovered(true),
1691
1930
  onMouseLeave: () => {
1692
- if (!isResizing) setIsHovered(false);
1931
+ if (!isResizingRef.current) setIsHovered(false);
1693
1932
  },
1694
1933
  children: [
1695
- /* @__PURE__ */ jsx(
1696
- "iframe",
1697
- {
1698
- width: "100%",
1699
- height: "100%",
1700
- src: `https://www.youtube.com/embed/${videoID}`,
1701
- allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
1702
- allowFullScreen: true,
1703
- title: "YouTube video",
1704
- style: { display: "block", border: "none", pointerEvents: isResizing ? "none" : "auto" }
1705
- }
1706
- ),
1707
- isHovered && /* @__PURE__ */ jsxs(Fragment, { children: [
1708
- /* @__PURE__ */ jsx(
1709
- "button",
1710
- {
1711
- type: "button",
1712
- onClick: handleDelete,
1713
- title: "Remove video",
1714
- style: {
1715
- position: "absolute",
1716
- top: 8,
1717
- right: 8,
1718
- width: 28,
1719
- height: 28,
1720
- borderRadius: "50%",
1721
- background: "rgba(0,0,0,0.65)",
1722
- color: "#fff",
1723
- border: "none",
1724
- cursor: "pointer",
1725
- fontSize: 18,
1726
- lineHeight: 1,
1727
- padding: 0,
1728
- zIndex: 10,
1729
- display: "flex",
1730
- alignItems: "center",
1731
- justifyContent: "center"
1732
- },
1733
- children: "\xD7"
1734
- }
1735
- ),
1934
+ isPlaying ? (
1935
+ /* ── Playing state ─────────────────────────────────────────────────
1936
+ * Show the real YouTube iframe in-place at the same dimensions.
1937
+ * autoplay=1 starts playback immediately.
1938
+ * pointer-events are set to 'none' during resize (handleResizeStart)
1939
+ * so drag events are not lost to the iframe's browsing context. */
1736
1940
  /* @__PURE__ */ jsx(
1737
- VideoResizer,
1941
+ "iframe",
1738
1942
  {
1739
- containerRef,
1740
- onResizeStart: () => setIsResizing(true),
1741
- onResizeEnd: handleResizeEnd
1943
+ ref: iframeRef,
1944
+ width: "100%",
1945
+ height: "100%",
1946
+ src: `https://www.youtube.com/embed/${videoID}?autoplay=1`,
1947
+ sandbox: "allow-same-origin allow-scripts allow-popups allow-presentation",
1948
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
1949
+ allowFullScreen: true,
1950
+ title: "YouTube video",
1951
+ style: { display: "block", border: "none" }
1952
+ }
1953
+ )
1954
+ ) : (
1955
+ /* ── Thumbnail state ───────────────────────────────────────────────
1956
+ * Static <img> keeps all clicks in the parent DOM so Lexical's
1957
+ * CLICK_COMMAND fires correctly and the node can be selected.
1958
+ * Clicking the red ▶ badge switches to playing state. */
1959
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1960
+ /* @__PURE__ */ jsx(
1961
+ "img",
1962
+ {
1963
+ src: `https://img.youtube.com/vi/${videoID}/hqdefault.jpg`,
1964
+ alt: "YouTube video",
1965
+ draggable: false,
1966
+ style: {
1967
+ width: "100%",
1968
+ height: "100%",
1969
+ objectFit: "cover",
1970
+ display: "block",
1971
+ userSelect: "none",
1972
+ cursor: "pointer"
1973
+ }
1974
+ }
1975
+ ),
1976
+ /* @__PURE__ */ jsx(
1977
+ "div",
1978
+ {
1979
+ role: "button",
1980
+ "aria-label": "Play video",
1981
+ onClick: () => setIsPlaying(true),
1982
+ style: {
1983
+ position: "absolute",
1984
+ top: "50%",
1985
+ left: "50%",
1986
+ transform: "translate(-50%, -50%)",
1987
+ width: 56,
1988
+ height: 56,
1989
+ background: "rgba(255, 0, 0, 0.85)",
1990
+ borderRadius: "50%",
1991
+ display: "flex",
1992
+ alignItems: "center",
1993
+ justifyContent: "center",
1994
+ cursor: "pointer"
1995
+ },
1996
+ children: /* @__PURE__ */ jsx("span", { style: { color: "#fff", fontSize: 20, lineHeight: 1, marginLeft: 5 }, children: "\u25B6" })
1997
+ }
1998
+ )
1999
+ ] })
2000
+ ),
2001
+ (isHovered || isResizing) && /* @__PURE__ */ jsxs(Fragment, { children: [
2002
+ /* @__PURE__ */ jsx(
2003
+ "button",
2004
+ {
2005
+ type: "button",
2006
+ onClick: handleDelete,
2007
+ title: "Remove video",
2008
+ style: actionBtnStyle("right"),
2009
+ children: "\xD7"
2010
+ }
2011
+ ),
2012
+ isPlaying && /* @__PURE__ */ jsx(
2013
+ "button",
2014
+ {
2015
+ type: "button",
2016
+ onClick: (e) => {
2017
+ e.stopPropagation();
2018
+ setIsPlaying(false);
2019
+ },
2020
+ title: "Stop video",
2021
+ style: actionBtnStyle("left"),
2022
+ children: "\u23F9"
2023
+ }
2024
+ ),
2025
+ /* @__PURE__ */ jsx(
2026
+ "button",
2027
+ {
2028
+ type: "button",
2029
+ onClick: (e) => {
2030
+ e.stopPropagation();
2031
+ window.open(`https://www.youtube.com/watch?v=${videoID}`, "_blank", "noopener,noreferrer");
2032
+ },
2033
+ title: "Open in browser",
2034
+ style: {
2035
+ ...actionBtnStyle("left"),
2036
+ top: isPlaying ? 44 : 8,
2037
+ // stack below stop button when playing
2038
+ fontSize: 13
2039
+ },
2040
+ children: "\u2197"
2041
+ }
2042
+ ),
2043
+ /* @__PURE__ */ jsx(
2044
+ VideoResizer,
2045
+ {
2046
+ containerRef,
2047
+ onResizeStart: handleResizeStart,
2048
+ onResizeEnd: handleResizeEnd
1742
2049
  }
1743
2050
  )
1744
2051
  ] })
@@ -1798,6 +2105,7 @@ var YouTubeNode = class _YouTubeNode extends DecoratorBlockNode {
1798
2105
  iframe.setAttribute("width", String(this.__width));
1799
2106
  iframe.setAttribute("height", String(this.__height));
1800
2107
  iframe.style.border = "none";
2108
+ iframe.setAttribute("sandbox", "allow-same-origin allow-scripts allow-popups allow-presentation");
1801
2109
  iframe.setAttribute(
1802
2110
  "allow",
1803
2111
  "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
@@ -1825,16 +2133,10 @@ var YouTubeNode = class _YouTubeNode extends DecoratorBlockNode {
1825
2133
  writable.__width = width;
1826
2134
  writable.__height = height;
1827
2135
  }
1828
- decorate(_editor, config) {
1829
- const embedBlockTheme = config.theme.embedBlock || {};
1830
- const className = {
1831
- base: embedBlockTheme.base || "",
1832
- focus: embedBlockTheme.focus || ""
1833
- };
2136
+ decorate(_editor, _config) {
1834
2137
  return /* @__PURE__ */ jsx(
1835
2138
  YouTubeComponent,
1836
2139
  {
1837
- className,
1838
2140
  format: this.__format,
1839
2141
  nodeKey: this.getKey(),
1840
2142
  videoID: this.__id,
@@ -2200,12 +2502,12 @@ function useFloatingPortalContainer(editor) {
2200
2502
  }, [editor]);
2201
2503
  return container;
2202
2504
  }
2203
- function setPopupPositionFixed(popupEl, rect) {
2505
+ function setPopupPositionFixed(popupEl, rect, topBoundary) {
2204
2506
  const GAP = 8;
2205
2507
  const MARGIN = 8;
2206
2508
  let top = rect.top - popupEl.offsetHeight - GAP;
2207
- let left = rect.left + rect.width - popupEl.offsetWidth;
2208
- if (top < MARGIN) top = rect.bottom + GAP;
2509
+ let left = rect.left;
2510
+ if (top < topBoundary) top = rect.bottom + GAP;
2209
2511
  left = clamp2(left, MARGIN, window.innerWidth - popupEl.offsetWidth - MARGIN);
2210
2512
  popupEl.style.top = `${top}px`;
2211
2513
  popupEl.style.left = `${left}px`;
@@ -2247,10 +2549,22 @@ function FloatingCharacterStylesEditor({
2247
2549
  popupEl.classList.remove("is-open");
2248
2550
  return;
2249
2551
  }
2250
- const range = sel.getRangeAt(0);
2251
- const rect = range.getBoundingClientRect();
2552
+ let rect;
2553
+ try {
2554
+ const focusRange = document.createRange();
2555
+ focusRange.setStart(sel.focusNode, sel.focusOffset);
2556
+ focusRange.setEnd(sel.focusNode, sel.focusOffset);
2557
+ rect = focusRange.getBoundingClientRect();
2558
+ if (rect.width === 0 && rect.height === 0 && rect.top === 0 && rect.left === 0) {
2559
+ throw new Error("empty focus rect");
2560
+ }
2561
+ } catch {
2562
+ rect = sel.getRangeAt(0).getBoundingClientRect();
2563
+ }
2564
+ const toolbarEl = root?.closest(".lexical-rich-editor-root")?.querySelector(".editor-toolbar-root");
2565
+ const topBoundary = toolbarEl ? toolbarEl.getBoundingClientRect().bottom + 8 : 8;
2252
2566
  if (!mouseDownRef.current) {
2253
- setPopupPositionFixed(popupEl, rect);
2567
+ setPopupPositionFixed(popupEl, rect, topBoundary);
2254
2568
  }
2255
2569
  popupEl.classList.add("is-open");
2256
2570
  }, [editor]);
@@ -2458,6 +2772,11 @@ function useCharacterStylesPopup(editor, opts) {
2458
2772
  setIsText(false);
2459
2773
  return;
2460
2774
  }
2775
+ const activeElement = document.activeElement;
2776
+ if (activeElement && activeElement !== document.body && rootElement && !rootElement.contains(activeElement)) {
2777
+ setIsText(false);
2778
+ return;
2779
+ }
2461
2780
  if (!$isRangeSelection(selection)) return;
2462
2781
  const node = getSelectedNode(selection);
2463
2782
  setIsBold(selection.hasFormat("bold"));
@@ -2478,7 +2797,11 @@ function useCharacterStylesPopup(editor, opts) {
2478
2797
  }, [editor]);
2479
2798
  useEffect(() => {
2480
2799
  document.addEventListener("selectionchange", updatePopupState);
2481
- return () => document.removeEventListener("selectionchange", updatePopupState);
2800
+ document.addEventListener("focusin", updatePopupState);
2801
+ return () => {
2802
+ document.removeEventListener("selectionchange", updatePopupState);
2803
+ document.removeEventListener("focusin", updatePopupState);
2804
+ };
2482
2805
  }, [updatePopupState]);
2483
2806
  useEffect(() => editor.registerUpdateListener(updatePopupState), [editor, updatePopupState]);
2484
2807
  if (!portalContainer || !isText || isLink) return null;
@@ -2736,34 +3059,57 @@ function normalizeToBlockHtml(html) {
2736
3059
  if (pendingP) body.appendChild(pendingP);
2737
3060
  return body.innerHTML;
2738
3061
  }
3062
+ function splitHeadingsAtBrSequences(html) {
3063
+ const doc = new DOMParser().parseFromString(html, "text/html");
3064
+ const headings = Array.from(doc.querySelectorAll("h1, h2, h3, h4, h5, h6"));
3065
+ headings.forEach((el) => {
3066
+ const inner = el.innerHTML;
3067
+ const SEP = /<br\s*\/?>\s*(?:<br\s*\/?>)+/gi;
3068
+ if (!SEP.test(inner)) return;
3069
+ SEP.lastIndex = 0;
3070
+ const parts = inner.split(SEP).map((p) => p.trim()).filter(Boolean);
3071
+ if (parts.length <= 1) return;
3072
+ const parent = el.parentNode;
3073
+ if (!parent) return;
3074
+ const tagName = el.tagName.toLowerCase();
3075
+ const attrs = Array.from(el.attributes);
3076
+ const fragment = doc.createDocumentFragment();
3077
+ parts.forEach((part) => {
3078
+ const newEl = doc.createElement(tagName);
3079
+ attrs.forEach((a) => newEl.setAttribute(a.name, a.value));
3080
+ newEl.innerHTML = part;
3081
+ fragment.appendChild(newEl);
3082
+ });
3083
+ parent.replaceChild(fragment, el);
3084
+ });
3085
+ return doc.body.innerHTML;
3086
+ }
2739
3087
  var CustomOnChangePlugin = ({ value, onChange }) => {
2740
3088
  const [editor] = useLexicalComposerContext();
2741
3089
  const initializedRef = useRef(false);
2742
- const onChangeRef = useRef(onChange);
2743
- onChangeRef.current = onChange;
2744
3090
  useEffect(() => {
2745
3091
  if (!value || initializedRef.current) return;
2746
3092
  initializedRef.current = true;
2747
3093
  editor.update(() => {
2748
3094
  const root = $getRoot();
2749
3095
  root.clear();
3096
+ const safe = sanitizeHtml(value);
3097
+ const cleaned = normalizeToBlockHtml(splitHeadingsAtBrSequences(safe));
2750
3098
  const parser = new DOMParser();
2751
- const dom = parser.parseFromString(value, "text/html");
3099
+ const dom = parser.parseFromString(cleaned, "text/html");
2752
3100
  const nodes = $generateNodesFromDOM(editor, dom);
2753
3101
  root.append(...nodes);
2754
- $setSelection(null);
2755
3102
  });
2756
3103
  }, [editor, value]);
2757
- const handleChange = useCallback((editorState) => {
2758
- editorState.read(() => {
2759
- onChangeRef.current(postProcessOutput($generateHtmlFromNodes(editor)));
2760
- });
2761
- }, [editor]);
2762
3104
  return /* @__PURE__ */ jsx(
2763
3105
  OnChangePlugin,
2764
3106
  {
2765
- onChange: handleChange,
2766
- ignoreSelectionChange: true
3107
+ onChange: (editorState) => {
3108
+ editorState.read(() => {
3109
+ const raw = $generateHtmlFromNodes(editor);
3110
+ onChange(postProcessOutput(splitHeadingsAtBrSequences(raw)));
3111
+ });
3112
+ }
2767
3113
  }
2768
3114
  );
2769
3115
  };
@@ -2857,7 +3203,7 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2857
3203
  return;
2858
3204
  }
2859
3205
  const rootElement = editor.getRootElement();
2860
- if (selection !== null && nativeSelection !== null && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) && editor.isEditable()) {
3206
+ if (isLink && selection !== null && nativeSelection !== null && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) && editor.isEditable()) {
2861
3207
  const domRect = nativeSelection.focusNode?.parentElement?.getBoundingClientRect();
2862
3208
  if (domRect) {
2863
3209
  domRect.y += 40;
@@ -2873,7 +3219,7 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2873
3219
  setLinkUrl("");
2874
3220
  }
2875
3221
  return true;
2876
- }, [anchorElem, editor, setIsLinkEditMode, isLinkEditMode, linkUrl]);
3222
+ }, [anchorElem, editor, setIsLinkEditMode, isLinkEditMode, isLink, linkUrl]);
2877
3223
  useEffect(() => {
2878
3224
  const scrollerElem = anchorElem.parentElement;
2879
3225
  const update = () => {
@@ -2980,17 +3326,20 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2980
3326
  }
2981
3327
  }
2982
3328
  ),
2983
- /* @__PURE__ */ jsxs("div", { children: [
3329
+ /* @__PURE__ */ jsxs("div", { className: "link-input-actions", children: [
2984
3330
  /* @__PURE__ */ jsx(
2985
3331
  "div",
2986
3332
  {
2987
3333
  className: "link-cancel",
2988
3334
  role: "button",
2989
3335
  tabIndex: 0,
3336
+ title: "Cancel",
3337
+ "aria-label": "Cancel",
2990
3338
  onMouseDown: preventDefault,
2991
3339
  onClick: () => {
2992
3340
  setIsLinkEditMode(false);
2993
- }
3341
+ },
3342
+ children: /* @__PURE__ */ jsx(DismissRegular, { fontSize: 16 })
2994
3343
  }
2995
3344
  ),
2996
3345
  /* @__PURE__ */ jsx(
@@ -2999,8 +3348,11 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2999
3348
  className: "link-confirm",
3000
3349
  role: "button",
3001
3350
  tabIndex: 0,
3351
+ title: "Confirm",
3352
+ "aria-label": "Confirm",
3002
3353
  onMouseDown: preventDefault,
3003
- onClick: handleLinkSubmission
3354
+ onClick: handleLinkSubmission,
3355
+ children: /* @__PURE__ */ jsx(CheckmarkRegular, { fontSize: 16 })
3004
3356
  }
3005
3357
  )
3006
3358
  ] })
@@ -3020,12 +3372,15 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
3020
3372
  className: "link-edit",
3021
3373
  role: "button",
3022
3374
  tabIndex: 0,
3375
+ title: "Edit link",
3376
+ "aria-label": "Edit link",
3023
3377
  onMouseDown: preventDefault,
3024
3378
  onClick: (event) => {
3025
3379
  event.preventDefault();
3026
3380
  setEditedLinkUrl(linkUrl);
3027
3381
  setIsLinkEditMode(true);
3028
- }
3382
+ },
3383
+ children: /* @__PURE__ */ jsx(EditRegular, { fontSize: 16 })
3029
3384
  }
3030
3385
  ),
3031
3386
  /* @__PURE__ */ jsx(
@@ -3034,10 +3389,13 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
3034
3389
  className: "link-trash",
3035
3390
  role: "button",
3036
3391
  tabIndex: 0,
3392
+ title: "Remove link",
3393
+ "aria-label": "Remove link",
3037
3394
  onMouseDown: preventDefault,
3038
3395
  onClick: () => {
3039
3396
  editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
3040
- }
3397
+ },
3398
+ children: /* @__PURE__ */ jsx(DeleteRegular, { fontSize: 16 })
3041
3399
  }
3042
3400
  )
3043
3401
  ] }) });
@@ -3120,8 +3478,6 @@ var FloatingLinkEditorPlugin = ({ anchorElem, isLinkEditMode, setIsLinkEditMode
3120
3478
  setIsLinkEditMode
3121
3479
  );
3122
3480
  };
3123
-
3124
- // src/Plugins/ImagePlugin.tsx
3125
3481
  init_ImageNode();
3126
3482
  var INSERT_IMAGE_COMMAND = createCommand("INSERT_IMAGE_COMMAND");
3127
3483
  var readClipboardImageAsDataURL = async (event) => {
@@ -3149,7 +3505,7 @@ var InsertImageByURL = ({
3149
3505
  const [src, setSrc] = useState("");
3150
3506
  const isDisabled = disabled || src === "";
3151
3507
  return /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 6, padding: "10px 0px 0px 0px" }, children: [
3152
- /* @__PURE__ */ jsx(Field, { label: "Enter URL", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
3508
+ /* @__PURE__ */ jsx(Field, { label: "Enter URL", size: "small", children: /* @__PURE__ */ jsx(
3153
3509
  Input,
3154
3510
  {
3155
3511
  autoFocus: !disabled,
@@ -3160,7 +3516,7 @@ var InsertImageByURL = ({
3160
3516
  value: src
3161
3517
  }
3162
3518
  ) }),
3163
- /* @__PURE__ */ jsx(Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
3519
+ /* @__PURE__ */ jsx(Field, { label: "Alt Text", size: "small", children: /* @__PURE__ */ jsx(
3164
3520
  Input,
3165
3521
  {
3166
3522
  placeholder: "Alt text",
@@ -3198,16 +3554,19 @@ var InsertImageByURL = ({
3198
3554
  };
3199
3555
  var InsertImageDialog = ({
3200
3556
  activeEditor,
3201
- disabled
3557
+ disabled,
3558
+ maxImageSizeMB,
3559
+ validationMessages
3202
3560
  }) => {
3203
3561
  const [src, setSrc] = useState("");
3204
3562
  const [altText, setAltText] = useState("");
3205
3563
  const [isOpen, setIsOpen] = useState(false);
3206
3564
  const [selectedValue, setSelectedValue] = useState("Upload");
3207
3565
  const [fileName, setFileName] = useState("");
3566
+ const [fileSizeError, setFileSizeError] = useState(null);
3208
3567
  const hasModifier = useRef(false);
3209
3568
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
3210
- const isDisabled = disabled || src === "";
3569
+ const isDisabled = disabled || src === "" || !!fileSizeError;
3211
3570
  useEffect(() => {
3212
3571
  hasModifier.current = false;
3213
3572
  const handler = (e) => {
@@ -3223,31 +3582,44 @@ var InsertImageDialog = ({
3223
3582
  setAltText("");
3224
3583
  setSrc("");
3225
3584
  setFileName("");
3585
+ setFileSizeError(null);
3226
3586
  };
3227
3587
  const loadImage = (event) => {
3228
3588
  if (disabled) return;
3229
3589
  const files = event.target.files;
3230
3590
  if (!files || files.length === 0) return;
3591
+ const file = files[0];
3592
+ if (maxImageSizeMB !== void 0) {
3593
+ const fileMB = file.size / (1024 * 1024);
3594
+ if (fileMB > maxImageSizeMB) {
3595
+ const override = validationMessages?.imageTooLarge;
3596
+ const msg = override !== void 0 ? typeof override === "function" ? override(fileMB, maxImageSizeMB) : override : DEFAULT_VALIDATION_MESSAGES.imageTooLarge(fileMB, maxImageSizeMB);
3597
+ setFileSizeError(msg);
3598
+ setSrc("");
3599
+ setFileName("");
3600
+ event.target.value = "";
3601
+ return;
3602
+ }
3603
+ }
3604
+ setFileSizeError(null);
3231
3605
  const reader = new FileReader();
3232
3606
  reader.onload = () => {
3233
3607
  if (typeof reader.result === "string") {
3234
3608
  setSrc(reader.result);
3235
- setFileName(files[0].name);
3609
+ setFileName(file.name);
3236
3610
  }
3237
3611
  };
3238
- reader.readAsDataURL(files[0]);
3612
+ reader.readAsDataURL(file);
3239
3613
  };
3240
3614
  return /* @__PURE__ */ jsxs(
3241
- Popover,
3615
+ Dialog,
3242
3616
  {
3243
- trapFocus: true,
3244
- withArrow: true,
3245
3617
  open: disabled ? false : isOpen,
3246
3618
  onOpenChange: (_, data) => {
3247
3619
  if (!disabled) setIsOpen(data.open);
3248
3620
  },
3249
3621
  children: [
3250
- /* @__PURE__ */ jsx(PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
3622
+ /* @__PURE__ */ jsx(DialogTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
3251
3623
  Button,
3252
3624
  {
3253
3625
  size: "small",
@@ -3271,81 +3643,73 @@ var InsertImageDialog = ({
3271
3643
  },
3272
3644
  "upload-image"
3273
3645
  ) }),
3274
- /* @__PURE__ */ jsxs(
3275
- PopoverSurface,
3276
- {
3277
- style: {
3278
- width: "320px",
3279
- opacity: disabled ? 0.6 : 1,
3280
- pointerEvents: disabled ? "none" : "auto"
3281
- },
3282
- children: [
3283
- /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 6 }, children: [
3284
- /* @__PURE__ */ jsx(Field, { label: "Upload", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxs(
3285
- "label",
3286
- {
3287
- style: {
3288
- cursor: disabled ? "not-allowed" : "pointer",
3289
- display: "flex",
3290
- alignItems: "center",
3291
- gap: 8,
3292
- opacity: disabled ? 0.75 : 1
3646
+ /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: 340 }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
3647
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Insert image" }),
3648
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 8 }, children: [
3649
+ /* @__PURE__ */ jsx(Field, { label: "Upload", size: "small", children: /* @__PURE__ */ jsxs(
3650
+ "label",
3651
+ {
3652
+ style: {
3653
+ cursor: disabled ? "not-allowed" : "pointer",
3654
+ display: "flex",
3655
+ alignItems: "center",
3656
+ gap: 8,
3657
+ opacity: disabled ? 0.75 : 1
3658
+ },
3659
+ children: [
3660
+ /* @__PURE__ */ jsx(
3661
+ "input",
3662
+ {
3663
+ type: "file",
3664
+ accept: "image/*",
3665
+ style: { display: "none" },
3666
+ disabled,
3667
+ onChange: loadImage
3293
3668
  },
3294
- children: [
3295
- /* @__PURE__ */ jsx(
3296
- "input",
3297
- {
3298
- type: "file",
3299
- accept: "image/*",
3300
- style: { display: "none" },
3301
- disabled,
3302
- onChange: loadImage
3303
- },
3304
- "inline-image-upload"
3305
- ),
3306
- /* @__PURE__ */ jsxs(Stack, { horizontal: true, children: [
3307
- /* @__PURE__ */ jsx(
3308
- AttachFilled,
3309
- {
3310
- style: {
3311
- fontSize: "16px",
3312
- color: disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#808080",
3313
- marginTop: 2
3314
- }
3315
- }
3316
- ),
3317
- !fileName && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#808080" }, children: "Upload File" })
3318
- ] }),
3319
- fileName && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#808080" }, children: fileName })
3320
- ]
3321
- }
3322
- ) }),
3323
- /* @__PURE__ */ jsx(Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
3324
- Input,
3325
- {
3326
- placeholder: "Alt text",
3327
- appearance: "underline",
3328
- disabled,
3329
- onChange: (_, d) => setAltText(d.value),
3330
- value: altText
3331
- }
3332
- ) }),
3333
- /* @__PURE__ */ jsxs(Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
3334
- /* @__PURE__ */ jsx(Button, { size: "small", disabled: isDisabled, onClick: () => onClick({ altText, src }), children: "Add" }),
3335
- /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
3336
- ] })
3337
- ] }),
3338
- selectedValue === "URL" && /* @__PURE__ */ jsx(
3339
- InsertImageByURL,
3340
- {
3341
- disabled,
3342
- setIsOpen: (open) => setIsOpen(open),
3343
- onClick: (payload) => onClick(payload)
3344
- }
3345
- )
3346
- ]
3347
- }
3348
- )
3669
+ "inline-image-upload"
3670
+ ),
3671
+ /* @__PURE__ */ jsxs(Stack, { horizontal: true, children: [
3672
+ /* @__PURE__ */ jsx(
3673
+ AttachFilled,
3674
+ {
3675
+ style: {
3676
+ fontSize: "16px",
3677
+ color: disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#808080",
3678
+ marginTop: 2
3679
+ }
3680
+ }
3681
+ ),
3682
+ !fileName && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#808080" }, children: "Upload File" })
3683
+ ] }),
3684
+ fileName && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#808080" }, children: fileName })
3685
+ ]
3686
+ }
3687
+ ) }),
3688
+ fileSizeError && /* @__PURE__ */ jsx(MessageBar, { intent: "error", style: { marginTop: 4 }, children: /* @__PURE__ */ jsx(MessageBarBody, { children: fileSizeError }) }),
3689
+ /* @__PURE__ */ jsx(Field, { label: "Alt Text", size: "small", children: /* @__PURE__ */ jsx(
3690
+ Input,
3691
+ {
3692
+ placeholder: "Alt text",
3693
+ appearance: "underline",
3694
+ disabled,
3695
+ onChange: (_, d) => setAltText(d.value),
3696
+ value: altText
3697
+ }
3698
+ ) }),
3699
+ selectedValue === "URL" && /* @__PURE__ */ jsx(
3700
+ InsertImageByURL,
3701
+ {
3702
+ disabled,
3703
+ setIsOpen: (open) => setIsOpen(open),
3704
+ onClick: (payload) => onClick(payload)
3705
+ }
3706
+ )
3707
+ ] }) }),
3708
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
3709
+ /* @__PURE__ */ jsx(Button, { size: "small", disabled: isDisabled, onClick: () => onClick({ altText, src }), children: "Add" }),
3710
+ /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
3711
+ ] })
3712
+ ] }) })
3349
3713
  ]
3350
3714
  }
3351
3715
  );
@@ -3361,9 +3725,21 @@ var ImagesPlugin = ({ captionsEnabled }) => {
3361
3725
  INSERT_IMAGE_COMMAND,
3362
3726
  (payload) => {
3363
3727
  const imageNode = $createImageNode(payload);
3364
- $insertNodes([imageNode]);
3365
- if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
3366
- $wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();
3728
+ const selection = $getSelection();
3729
+ if ($isRangeSelection(selection)) {
3730
+ const anchorNode = selection.anchor.getNode();
3731
+ const topLevel = anchorNode.getTopLevelElementOrThrow();
3732
+ const imageParagraph = $createParagraphNode();
3733
+ imageParagraph.append(imageNode);
3734
+ topLevel.insertAfter(imageParagraph);
3735
+ const tail = $createParagraphNode();
3736
+ imageParagraph.insertAfter(tail);
3737
+ tail.select();
3738
+ } else {
3739
+ $insertNodes([imageNode]);
3740
+ if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
3741
+ $wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();
3742
+ }
3367
3743
  }
3368
3744
  return true;
3369
3745
  },
@@ -3414,12 +3790,25 @@ var TRANSPARENT_IMAGE = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAA
3414
3790
  var img = document.createElement("img");
3415
3791
  img.src = TRANSPARENT_IMAGE;
3416
3792
  var $onDragStart = (event) => {
3417
- const node = $getImageNodeInSelection();
3793
+ let node = $getImageNodeInSelection();
3794
+ if (!node) {
3795
+ const target = event.target;
3796
+ if (!target) return false;
3797
+ const lexicalNode = $getNearestNodeFromDOMNode(target);
3798
+ if ($isImageNode(lexicalNode)) {
3799
+ node = lexicalNode;
3800
+ }
3801
+ }
3418
3802
  if (!node) return false;
3419
3803
  const dataTransfer = event.dataTransfer;
3420
3804
  if (!dataTransfer) return false;
3805
+ const imgEl = event.target?.closest?.("span.editor-image")?.querySelector?.("img") ?? event.target;
3806
+ if (imgEl instanceof HTMLElement) {
3807
+ dataTransfer.setDragImage(imgEl, 20, 20);
3808
+ } else {
3809
+ dataTransfer.setDragImage(img, 0, 0);
3810
+ }
3421
3811
  dataTransfer.setData("text/plain", "_");
3422
- dataTransfer.setDragImage(img, 0, 0);
3423
3812
  dataTransfer.setData(
3424
3813
  "application/x-lexical-drag",
3425
3814
  JSON.stringify({
@@ -3447,20 +3836,23 @@ var $onDragover = (event) => {
3447
3836
  return true;
3448
3837
  };
3449
3838
  var $onDrop = (event, editor) => {
3450
- const node = $getImageNodeInSelection();
3451
- if (!node) return false;
3452
3839
  const data = getDragImageData(event);
3453
3840
  if (!data) return false;
3454
3841
  event.preventDefault();
3455
3842
  if (canDropImage(event)) {
3843
+ const sourceKey = data.key;
3844
+ if (sourceKey) {
3845
+ const sourceNode = $getNodeByKey(sourceKey);
3846
+ if (sourceNode) sourceNode.remove();
3847
+ }
3456
3848
  const range = getDragSelection(event);
3457
- node.remove();
3458
3849
  const rangeSelection = $createRangeSelection();
3459
3850
  if (range !== null && range !== void 0) {
3460
3851
  rangeSelection.applyDOMRange(range);
3461
3852
  }
3462
3853
  $setSelection(rangeSelection);
3463
- editor.dispatchCommand(INSERT_IMAGE_COMMAND, data);
3854
+ const { key: _key, ...insertPayload } = data;
3855
+ editor.dispatchCommand(INSERT_IMAGE_COMMAND, insertPayload);
3464
3856
  }
3465
3857
  return true;
3466
3858
  };
@@ -3480,7 +3872,7 @@ var getDragImageData = (event) => {
3480
3872
  };
3481
3873
  var canDropImage = (event) => {
3482
3874
  const target = event.target;
3483
- return !!(isHTMLElement(target) && !target.closest("code, span.editor-image") && isHTMLElement(target.parentElement) && target.parentElement.closest("div.ContentEditable__root"));
3875
+ return !!(isHTMLElement(target) && !target.closest("code, span.editor-image") && target.closest('[contenteditable="true"]'));
3484
3876
  };
3485
3877
  var getDragSelection = (event) => {
3486
3878
  let range;
@@ -3495,8 +3887,6 @@ var getDragSelection = (event) => {
3495
3887
  }
3496
3888
  return range;
3497
3889
  };
3498
-
3499
- // src/Plugins/InlineImage.tsx
3500
3890
  init_InlineImage();
3501
3891
  init_InlineImageNode();
3502
3892
  var INSERT_INLINE_IMAGE_COMMAND = createCommand(
@@ -3514,7 +3904,9 @@ var useStyles = makeStyles({
3514
3904
  });
3515
3905
  var InsertInlineImageDialog = ({
3516
3906
  disabled,
3517
- activeEditor
3907
+ activeEditor,
3908
+ maxImageSizeMB,
3909
+ validationMessages
3518
3910
  }) => {
3519
3911
  const hasModifier = useRef(false);
3520
3912
  const [src, setSrc] = useState("");
@@ -3522,21 +3914,36 @@ var InsertInlineImageDialog = ({
3522
3914
  const [altText, setAltText] = useState("");
3523
3915
  const [fileName, setFileName] = useState("");
3524
3916
  const [position, setPosition] = useState("left");
3917
+ const [fileSizeError, setFileSizeError] = useState(null);
3525
3918
  const styles = useStyles();
3526
3919
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
3527
- const isDisabled = disabled || src === "";
3920
+ const isDisabled = disabled || src === "" || !!fileSizeError;
3528
3921
  const loadImage = (event) => {
3529
3922
  if (disabled) return;
3530
3923
  const files = event.target.files;
3531
3924
  if (!files || files.length === 0) return;
3925
+ const file = files[0];
3926
+ if (maxImageSizeMB !== void 0) {
3927
+ const fileMB = file.size / (1024 * 1024);
3928
+ if (fileMB > maxImageSizeMB) {
3929
+ const override = validationMessages?.imageTooLarge;
3930
+ const msg = override !== void 0 ? typeof override === "function" ? override(fileMB, maxImageSizeMB) : override : DEFAULT_VALIDATION_MESSAGES.imageTooLarge(fileMB, maxImageSizeMB);
3931
+ setFileSizeError(msg);
3932
+ setSrc("");
3933
+ setFileName("");
3934
+ event.target.value = "";
3935
+ return;
3936
+ }
3937
+ }
3938
+ setFileSizeError(null);
3532
3939
  const reader = new FileReader();
3533
3940
  reader.onload = () => {
3534
3941
  if (typeof reader.result === "string") {
3535
3942
  setSrc(reader.result);
3536
- setFileName(files[0].name);
3943
+ setFileName(file.name);
3537
3944
  }
3538
3945
  };
3539
- reader.readAsDataURL(files[0]);
3946
+ reader.readAsDataURL(file);
3540
3947
  };
3541
3948
  useEffect(() => {
3542
3949
  const handler = (e) => {
@@ -3553,18 +3960,17 @@ var InsertInlineImageDialog = ({
3553
3960
  setAltText("");
3554
3961
  setSrc("");
3555
3962
  setFileName("");
3963
+ setFileSizeError(null);
3556
3964
  };
3557
3965
  return /* @__PURE__ */ jsxs(
3558
- Popover,
3966
+ Dialog,
3559
3967
  {
3560
- trapFocus: true,
3561
- withArrow: true,
3562
3968
  open: disabled ? false : isOpen,
3563
3969
  onOpenChange: (_, data) => {
3564
3970
  if (!disabled) setIsOpen(data.open);
3565
3971
  },
3566
3972
  children: [
3567
- /* @__PURE__ */ jsx(PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
3973
+ /* @__PURE__ */ jsx(DialogTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
3568
3974
  Button,
3569
3975
  {
3570
3976
  size: "small",
@@ -3588,104 +3994,100 @@ var InsertInlineImageDialog = ({
3588
3994
  },
3589
3995
  "upload-inline-image"
3590
3996
  ) }),
3591
- /* @__PURE__ */ jsx(
3592
- PopoverSurface,
3593
- {
3594
- style: {
3595
- width: "400px",
3596
- opacity: disabled ? 0.6 : 1,
3597
- pointerEvents: disabled ? "none" : "auto"
3598
- },
3599
- children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 6, padding: "10px 0px 0px 0px" }, children: [
3600
- /* @__PURE__ */ jsx(Field, { label: "Upload", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxs(
3601
- "label",
3602
- {
3603
- style: {
3604
- cursor: disabled ? "not-allowed" : "pointer",
3605
- display: "flex",
3606
- alignItems: "center",
3607
- gap: 8,
3608
- opacity: disabled ? 0.75 : 1
3609
- },
3610
- children: [
3997
+ /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: 360 }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
3998
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Insert inline image" }),
3999
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 8 }, children: [
4000
+ /* @__PURE__ */ jsx(Field, { label: "Upload", size: "small", children: /* @__PURE__ */ jsxs(
4001
+ "label",
4002
+ {
4003
+ style: {
4004
+ cursor: disabled ? "not-allowed" : "pointer",
4005
+ display: "flex",
4006
+ alignItems: "center",
4007
+ gap: 8,
4008
+ opacity: disabled ? 0.75 : 1
4009
+ },
4010
+ children: [
4011
+ /* @__PURE__ */ jsx(
4012
+ "input",
4013
+ {
4014
+ type: "file",
4015
+ accept: "image/*",
4016
+ style: { display: "none" },
4017
+ disabled,
4018
+ onChange: loadImage
4019
+ },
4020
+ "inline-image-upload"
4021
+ ),
4022
+ /* @__PURE__ */ jsxs(Stack, { horizontal: true, children: [
3611
4023
  /* @__PURE__ */ jsx(
3612
- "input",
4024
+ AttachFilled,
3613
4025
  {
3614
- type: "file",
3615
- accept: "image/*",
3616
- style: { display: "none" },
3617
- disabled,
3618
- onChange: loadImage
3619
- },
3620
- "inline-image-upload"
3621
- ),
3622
- /* @__PURE__ */ jsxs(Stack, { horizontal: true, children: [
3623
- /* @__PURE__ */ jsx(
3624
- AttachFilled,
3625
- {
3626
- style: {
3627
- fontSize: "16px",
3628
- color: disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#808080",
3629
- marginTop: 2
3630
- }
4026
+ style: {
4027
+ fontSize: "16px",
4028
+ color: disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#808080",
4029
+ marginTop: 2
3631
4030
  }
3632
- ),
3633
- !fileName && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#808080" }, children: "Upload File" })
3634
- ] }),
3635
- fileName && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#808080" }, children: fileName })
3636
- ]
3637
- }
3638
- ) }),
3639
- /* @__PURE__ */ jsx(Field, { label: "Position", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxs(
3640
- Dropdown,
3641
- {
3642
- placeholder: "Left Align",
3643
- className: styles.alignDropdown,
3644
- disabled,
3645
- listbox: { style: { width: "120px" } },
3646
- root: { style: { borderBottom: "1px solid black" } },
3647
- children: [
3648
- /* @__PURE__ */ jsx(Option, { text: "full", onClick: () => setPosition("full"), children: "Full" }, "full"),
3649
- /* @__PURE__ */ jsx(Option, { text: "left", onClick: () => setPosition("left"), children: "Left" }, "left"),
3650
- /* @__PURE__ */ jsx(Option, { text: "right", onClick: () => setPosition("right"), children: "Right" }, "right")
3651
- ]
3652
- }
3653
- ) }),
3654
- /* @__PURE__ */ jsx(Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
3655
- Input,
3656
- {
3657
- placeholder: "Alt text",
3658
- appearance: "underline",
3659
- disabled,
3660
- value: altText,
3661
- onChange: (_, d) => setAltText(d.value)
3662
- }
3663
- ) }),
3664
- /* @__PURE__ */ jsxs(Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
3665
- /* @__PURE__ */ jsx(
3666
- Button,
3667
- {
3668
- size: "small",
3669
- disabled: isDisabled,
3670
- onClick: handleOnClick,
3671
- children: "Add"
3672
- },
3673
- "file-inline-upload-btn"
3674
- ),
3675
- /* @__PURE__ */ jsx(
3676
- Button,
3677
- {
3678
- size: "small",
3679
- disabled,
3680
- onClick: () => setIsOpen(false),
3681
- children: "Cancel"
3682
- },
3683
- "file-inline-upload-cancel"
3684
- )
3685
- ] })
3686
- ] })
3687
- }
3688
- )
4031
+ }
4032
+ ),
4033
+ !fileName && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#808080" }, children: "Upload File" })
4034
+ ] }),
4035
+ fileName && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#808080" }, children: fileName })
4036
+ ]
4037
+ }
4038
+ ) }),
4039
+ /* @__PURE__ */ jsx(Field, { label: "Position", size: "small", children: /* @__PURE__ */ jsxs(
4040
+ Dropdown,
4041
+ {
4042
+ className: styles.alignDropdown,
4043
+ disabled,
4044
+ value: position === "full" ? "Full" : position === "right" ? "Right" : "Left",
4045
+ selectedOptions: [position ?? "left"],
4046
+ listbox: { style: { width: "120px" } },
4047
+ root: { style: { borderBottom: "1px solid black" } },
4048
+ onOptionSelect: (_, data) => setPosition(data.optionValue),
4049
+ children: [
4050
+ /* @__PURE__ */ jsx(Option, { value: "left", children: "Left" }, "left"),
4051
+ /* @__PURE__ */ jsx(Option, { value: "right", children: "Right" }, "right"),
4052
+ /* @__PURE__ */ jsx(Option, { value: "full", children: "Full" }, "full")
4053
+ ]
4054
+ }
4055
+ ) }),
4056
+ fileSizeError && /* @__PURE__ */ jsx(MessageBar, { intent: "error", style: { marginTop: 4 }, children: /* @__PURE__ */ jsx(MessageBarBody, { children: fileSizeError }) }),
4057
+ /* @__PURE__ */ jsx(Field, { label: "Alt Text", size: "small", children: /* @__PURE__ */ jsx(
4058
+ Input,
4059
+ {
4060
+ placeholder: "Alt text",
4061
+ appearance: "underline",
4062
+ disabled,
4063
+ value: altText,
4064
+ onChange: (_, d) => setAltText(d.value)
4065
+ }
4066
+ ) })
4067
+ ] }) }),
4068
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
4069
+ /* @__PURE__ */ jsx(
4070
+ Button,
4071
+ {
4072
+ size: "small",
4073
+ disabled: isDisabled,
4074
+ onClick: handleOnClick,
4075
+ children: "Add"
4076
+ },
4077
+ "file-inline-upload-btn"
4078
+ ),
4079
+ /* @__PURE__ */ jsx(
4080
+ Button,
4081
+ {
4082
+ size: "small",
4083
+ disabled,
4084
+ onClick: () => setIsOpen(false),
4085
+ children: "Cancel"
4086
+ },
4087
+ "file-inline-upload-cancel"
4088
+ )
4089
+ ] })
4090
+ ] }) })
3689
4091
  ]
3690
4092
  }
3691
4093
  );
@@ -3705,6 +4107,11 @@ var InlineImagePlugin = () => {
3705
4107
  if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
3706
4108
  $wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();
3707
4109
  }
4110
+ const parent = imageNode.getParent();
4111
+ if (parent && typeof parent.setFormat === "function") {
4112
+ const fmt = payload.position === "right" ? "right" : payload.position === "full" ? "center" : "left";
4113
+ parent.setFormat(fmt);
4114
+ }
3708
4115
  return true;
3709
4116
  },
3710
4117
  COMMAND_PRIORITY_EDITOR
@@ -3733,12 +4140,23 @@ var TRANSPARENT_IMAGE2 = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEA
3733
4140
  var img2 = document.createElement("img");
3734
4141
  img2.src = TRANSPARENT_IMAGE2;
3735
4142
  function $onDragStart2(event) {
3736
- const node = $getImageNodeInSelection2();
4143
+ let node = $getImageNodeInSelection2();
4144
+ if (!node) {
4145
+ const target = event.target;
4146
+ if (!target) return false;
4147
+ const lexicalNode = $getNearestNodeFromDOMNode(target);
4148
+ if ($isInlineImageNode(lexicalNode)) node = lexicalNode;
4149
+ }
3737
4150
  if (!node) return false;
3738
4151
  const dataTransfer = event.dataTransfer;
3739
4152
  if (!dataTransfer) return false;
4153
+ const imgEl = event.target?.closest?.(".inline-editor-image")?.querySelector?.("img") ?? event.target;
4154
+ if (imgEl instanceof HTMLElement) {
4155
+ dataTransfer.setDragImage(imgEl, 20, 20);
4156
+ } else {
4157
+ dataTransfer.setDragImage(img2, 0, 0);
4158
+ }
3740
4159
  dataTransfer.setData("text/plain", "_");
3741
- dataTransfer.setDragImage(img2, 0, 0);
3742
4160
  dataTransfer.setData(
3743
4161
  "application/x-lexical-drag",
3744
4162
  JSON.stringify({
@@ -3757,28 +4175,31 @@ function $onDragStart2(event) {
3757
4175
  return true;
3758
4176
  }
3759
4177
  var $onDragover2 = (event) => {
3760
- const node = $getImageNodeInSelection2();
3761
- if (!node) return false;
4178
+ const hasDragData = !!event.dataTransfer?.types.includes("application/x-lexical-drag");
4179
+ if (!hasDragData) return false;
3762
4180
  if (!canDropImage2(event)) {
3763
4181
  event.preventDefault();
3764
4182
  }
3765
4183
  return true;
3766
4184
  };
3767
4185
  var $onDrop2 = (event, editor) => {
3768
- const node = $getImageNodeInSelection2();
3769
- if (!node) return false;
3770
4186
  const data = getDragImageData2(event);
3771
4187
  if (!data) return false;
3772
4188
  event.preventDefault();
3773
4189
  if (canDropImage2(event)) {
4190
+ const sourceKey = data.key;
4191
+ if (sourceKey) {
4192
+ const sourceNode = $getNodeByKey(sourceKey);
4193
+ if (sourceNode) sourceNode.remove();
4194
+ }
3774
4195
  const range = getDragSelection2(event);
3775
- node.remove();
3776
4196
  const rangeSelection = $createRangeSelection();
3777
4197
  if (range !== null && range !== void 0) {
3778
4198
  rangeSelection.applyDOMRange(range);
3779
4199
  }
3780
4200
  $setSelection(rangeSelection);
3781
- editor.dispatchCommand(INSERT_INLINE_IMAGE_COMMAND, data);
4201
+ const { key: _key, ...insertPayload } = data;
4202
+ editor.dispatchCommand(INSERT_INLINE_IMAGE_COMMAND, insertPayload);
3782
4203
  }
3783
4204
  return true;
3784
4205
  };
@@ -3798,7 +4219,7 @@ var getDragImageData2 = (event) => {
3798
4219
  };
3799
4220
  var canDropImage2 = (event) => {
3800
4221
  const target = event.target;
3801
- return !!(isHTMLElement(target) && !target.closest("code, span.editor-image") && isHTMLElement(target.parentElement) && target.parentElement.closest("div.ContentEditable__root"));
4222
+ return !!(isHTMLElement(target) && !target.closest("code, span.editor-image, .inline-editor-image") && target.closest('[contenteditable="true"]'));
3802
4223
  };
3803
4224
  var getDragSelection2 = (event) => {
3804
4225
  let range;
@@ -3889,20 +4310,79 @@ function hasBlock(editor, kind) {
3889
4310
  }
3890
4311
 
3891
4312
  // src/Plugins/RefApiPlugin.tsx
4313
+ function getUserContentSignature(editor) {
4314
+ const parts = [];
4315
+ editor.getEditorState().read(() => {
4316
+ const root = $getRoot();
4317
+ const collectNode = (node) => {
4318
+ if ($isHtmlBlockNode(node)) return;
4319
+ if ($isDecoratorNode(node)) {
4320
+ parts.push(`D:${node.getKey()}`);
4321
+ return;
4322
+ }
4323
+ if ($isLineBreakNode(node)) {
4324
+ parts.push("BR");
4325
+ return;
4326
+ }
4327
+ if ($isTextNode(node)) {
4328
+ parts.push(`T${node.getFormat()}:${node.getStyle()}:${node.getTextContent()}`);
4329
+ return;
4330
+ }
4331
+ if ($isElementNode(node)) {
4332
+ const type = node.getType();
4333
+ const tag = typeof node.getTag === "function" ? node.getTag() : "";
4334
+ const list = typeof node.getListType === "function" ? node.getListType() : "";
4335
+ const fmt = typeof node.getFormat === "function" ? node.getFormat() : "";
4336
+ parts.push(`[${type}:${tag}:${list}:${fmt}]`);
4337
+ for (const child of node.getChildren()) {
4338
+ collectNode(child);
4339
+ }
4340
+ parts.push("[/]");
4341
+ }
4342
+ };
4343
+ for (const child of root.getChildren()) {
4344
+ if ($isHtmlBlockNode(child)) continue;
4345
+ collectNode(child);
4346
+ }
4347
+ });
4348
+ return JSON.stringify(parts);
4349
+ }
3892
4350
  function RefApiPlugin({
3893
4351
  forwardedRef,
3894
4352
  contentEditableDomRef,
3895
- focusedRef,
3896
- setRefErrors
4353
+ focusedRef
3897
4354
  }) {
3898
4355
  const [editor] = useLexicalComposerContext();
4356
+ const cleanBaselineRef = React9__default.useRef(null);
4357
+ React9__default.useEffect(() => {
4358
+ const capture = () => {
4359
+ if (cleanBaselineRef.current === null) {
4360
+ cleanBaselineRef.current = getUserContentSignature(editor);
4361
+ }
4362
+ };
4363
+ const unregister = editor.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
4364
+ if (cleanBaselineRef.current !== null) {
4365
+ unregister();
4366
+ return;
4367
+ }
4368
+ if (dirtyElements.size === 0 && dirtyLeaves.size === 0) return;
4369
+ capture();
4370
+ unregister();
4371
+ });
4372
+ const timerId = setTimeout(capture, 0);
4373
+ return () => {
4374
+ clearTimeout(timerId);
4375
+ unregister();
4376
+ };
4377
+ }, [editor]);
3899
4378
  useImperativeHandle(
3900
4379
  forwardedRef,
3901
4380
  () => ({
3902
4381
  setValue: (html) => {
3903
4382
  editor.update(() => {
4383
+ const safe = normalizeToBlockHtml(sanitizeHtml(html || ""));
3904
4384
  const parser = new DOMParser();
3905
- const dom = parser.parseFromString(html || "<p></p>", "text/html");
4385
+ const dom = parser.parseFromString(safe || "<p></p>", "text/html");
3906
4386
  const nodes = $generateNodesFromDOM(editor, dom);
3907
4387
  const root = $getRoot();
3908
4388
  root.clear();
@@ -3912,9 +4392,9 @@ function RefApiPlugin({
3912
4392
  getValue: () => {
3913
4393
  let html = "";
3914
4394
  editor.getEditorState().read(() => {
3915
- html = $generateHtmlFromNodes(editor, null);
4395
+ html = postProcessOutput(splitHeadingsAtBrSequences($generateHtmlFromNodes(editor, null)));
3916
4396
  });
3917
- return postProcessOutput(html);
4397
+ return html;
3918
4398
  },
3919
4399
  clear: () => {
3920
4400
  editor.update(() => {
@@ -3932,14 +4412,20 @@ function RefApiPlugin({
3932
4412
  },
3933
4413
  isFocused: () => focusedRef.current,
3934
4414
  getEditor: () => editor,
3935
- setErrors: (messages) => setRefErrors(messages),
3936
- clearErrors: () => setRefErrors([]),
3937
4415
  // Generic blocks (signature, footer, banner, etc.)
3938
4416
  upsertBlock: (spec) => upsertBlock(editor, spec),
3939
4417
  removeBlock: (kind) => removeBlock(editor, kind),
3940
- hasBlock: (kind) => hasBlock(editor, kind)
4418
+ hasBlock: (kind) => hasBlock(editor, kind),
4419
+ checkDirty: () => {
4420
+ if (cleanBaselineRef.current === null) return false;
4421
+ const current = getUserContentSignature(editor);
4422
+ return current !== cleanBaselineRef.current;
4423
+ },
4424
+ markClean: () => {
4425
+ cleanBaselineRef.current = getUserContentSignature(editor);
4426
+ }
3941
4427
  }),
3942
- [editor, contentEditableDomRef, focusedRef, setRefErrors]
4428
+ [editor, contentEditableDomRef, focusedRef]
3943
4429
  );
3944
4430
  return null;
3945
4431
  }
@@ -4580,8 +5066,22 @@ function TableActionMenuPlugin({ disabled = false }) {
4580
5066
  const [editor] = useLexicalComposerContext();
4581
5067
  const [isInTable, setIsInTable] = React9.useState(false);
4582
5068
  const [anchorRect, setAnchorRect] = React9.useState(null);
5069
+ const [contentRight, setContentRight] = React9.useState(null);
4583
5070
  const [open, setOpen] = React9.useState(false);
5071
+ const openRef = React9.useRef(false);
5072
+ const savedAnchorRef = React9.useRef(null);
5073
+ const measureContentRight = React9.useCallback((cellDom) => {
5074
+ try {
5075
+ const range = document.createRange();
5076
+ range.selectNodeContents(cellDom);
5077
+ const cr = range.getBoundingClientRect();
5078
+ return cr.width > 2 ? cr.right : null;
5079
+ } catch {
5080
+ return null;
5081
+ }
5082
+ }, []);
4584
5083
  const updateFromSelection = React9.useCallback(() => {
5084
+ if (openRef.current) return;
4585
5085
  const root = editor.getRootElement();
4586
5086
  if (!root) return;
4587
5087
  editor.getEditorState().read(() => {
@@ -4593,6 +5093,7 @@ function TableActionMenuPlugin({ disabled = false }) {
4593
5093
  if (dom) {
4594
5094
  setIsInTable(true);
4595
5095
  setAnchorRect(dom.getBoundingClientRect());
5096
+ setContentRight(null);
4596
5097
  return;
4597
5098
  }
4598
5099
  }
@@ -4600,6 +5101,7 @@ function TableActionMenuPlugin({ disabled = false }) {
4600
5101
  if (!$isRangeSelection(selection)) {
4601
5102
  setIsInTable(false);
4602
5103
  setAnchorRect(null);
5104
+ setContentRight(null);
4603
5105
  return;
4604
5106
  }
4605
5107
  const anchorNode = selection.anchor.getNode();
@@ -4607,18 +5109,28 @@ function TableActionMenuPlugin({ disabled = false }) {
4607
5109
  if (!cellNode || !$isTableCellNode(cellNode)) {
4608
5110
  setIsInTable(false);
4609
5111
  setAnchorRect(null);
5112
+ setContentRight(null);
4610
5113
  return;
4611
5114
  }
4612
5115
  const cellDom = editor.getElementByKey(cellNode.getKey());
4613
5116
  if (!cellDom) {
4614
5117
  setIsInTable(false);
4615
5118
  setAnchorRect(null);
5119
+ setContentRight(null);
4616
5120
  return;
4617
5121
  }
4618
5122
  setIsInTable(true);
4619
5123
  setAnchorRect(cellDom.getBoundingClientRect());
5124
+ setContentRight(measureContentRight(cellDom));
5125
+ if ($isRangeSelection(selection)) {
5126
+ savedAnchorRef.current = {
5127
+ key: selection.anchor.key,
5128
+ offset: selection.anchor.offset,
5129
+ type: selection.anchor.type
5130
+ };
5131
+ }
4620
5132
  });
4621
- }, [editor]);
5133
+ }, [editor, measureContentRight]);
4622
5134
  React9.useEffect(() => {
4623
5135
  return mergeRegister(
4624
5136
  editor.registerCommand(
@@ -4675,31 +5187,38 @@ function TableActionMenuPlugin({ disabled = false }) {
4675
5187
  COMMAND_PRIORITY_HIGH
4676
5188
  );
4677
5189
  }, [editor, disabled]);
4678
- React9.useEffect(() => {
4679
- if (!isInTable && open) setOpen(false);
4680
- }, [isInTable, open]);
4681
5190
  const canShow = isInTable && !!anchorRect && !disabled;
4682
5191
  const handleStyle = React9.useMemo(() => {
4683
5192
  if (!anchorRect) return void 0;
4684
5193
  const top = Math.max(8, anchorRect.top + 6);
4685
- const left = Math.max(8, anchorRect.right - 34);
5194
+ const clampedCellRight = Math.min(anchorRect.right, window.innerWidth - 8);
5195
+ const left = contentRight !== null ? Math.max(anchorRect.left + 4, Math.min(contentRight + 4, clampedCellRight - 32)) : Math.max(8, anchorRect.left + 8);
4686
5196
  return {
4687
5197
  position: "fixed",
4688
5198
  top,
4689
5199
  left,
4690
5200
  zIndex: 9999
4691
5201
  };
4692
- }, [anchorRect]);
5202
+ }, [anchorRect, contentRight]);
4693
5203
  const dangerStyle = {
4694
5204
  color: "var(--colorPaletteRedForeground1)"
4695
5205
  };
4696
5206
  const run = React9.useCallback(
4697
5207
  (fn) => {
4698
5208
  if (disabled) return;
4699
- editor.focus();
4700
- editor.update(() => fn());
5209
+ openRef.current = false;
4701
5210
  setOpen(false);
4702
- },
5211
+ editor.update(() => {
5212
+ const saved = savedAnchorRef.current;
5213
+ if (saved && $getNodeByKey(saved.key)) {
5214
+ const sel = $createRangeSelection();
5215
+ sel.anchor.set(saved.key, saved.offset, saved.type);
5216
+ sel.focus.set(saved.key, saved.offset, saved.type);
5217
+ $setSelection(sel);
5218
+ }
5219
+ fn();
5220
+ });
5221
+ },
4703
5222
  [disabled, editor]
4704
5223
  );
4705
5224
  const insertRowBelow = () => run(() => $insertTableRowAtSelection(true));
@@ -4708,32 +5227,31 @@ function TableActionMenuPlugin({ disabled = false }) {
4708
5227
  const insertColLeft = () => run(() => $insertTableColumnAtSelection(false));
4709
5228
  const deleteRow = () => run(() => $deleteTableRowAtSelection());
4710
5229
  const deleteCol = () => run(() => $deleteTableColumnAtSelection());
4711
- const deleteTable = () => run(() => {
4712
- const selection = $getSelection();
4713
- if ($isTableSelection(selection)) {
4714
- const tableNode = selection.getNodes().find((n) => $isTableNode(n));
5230
+ const deleteTable = () => {
5231
+ if (disabled) return;
5232
+ openRef.current = false;
5233
+ setOpen(false);
5234
+ editor.update(() => {
5235
+ const saved = savedAnchorRef.current;
5236
+ if (!saved) return;
5237
+ const anchorNode = $getNodeByKey(saved.key);
5238
+ if (!anchorNode) return;
5239
+ const tableNode = $findMatchingParent(anchorNode, (n) => $isTableNode(n));
4715
5240
  if (tableNode) tableNode.remove();
4716
- return;
4717
- }
4718
- if (!$isRangeSelection(selection)) return;
4719
- const node = selection.anchor.getNode();
4720
- const cell = $findMatchingParent(node, (n) => $isTableCellNode(n)) ?? ($isTableCellNode(node) ? node : null);
4721
- if (!cell) return;
4722
- const table = $getTableNodeFromLexicalNodeOrThrow(cell);
4723
- table.remove();
4724
- });
5241
+ });
5242
+ };
4725
5243
  if (!canShow || !handleStyle) return null;
4726
5244
  return createPortal(
4727
- /* @__PURE__ */ jsx("div", { style: handleStyle, className: "aoTableActionHandleRoot", children: /* @__PURE__ */ jsxs(Menu, { open, onOpenChange: (_, data) => setOpen(data.open), children: [
5245
+ /* @__PURE__ */ jsx("div", { style: handleStyle, className: "aoTableActionHandleRoot", "data-lexical-editor-portal": "true", children: /* @__PURE__ */ jsxs(Menu, { open, onOpenChange: (_, data) => {
5246
+ openRef.current = data.open;
5247
+ setOpen(data.open);
5248
+ }, children: [
4728
5249
  /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
4729
5250
  "button",
4730
5251
  {
4731
5252
  type: "button",
4732
5253
  className: "aoTableActionHandleBtn",
4733
5254
  "aria-label": "Table options",
4734
- onMouseDown: (e) => {
4735
- e.preventDefault();
4736
- },
4737
5255
  children: /* @__PURE__ */ jsx(ChevronDown12Regular, {})
4738
5256
  }
4739
5257
  ) }),
@@ -5084,6 +5602,85 @@ function getToolbarGroupsByLevel(level) {
5084
5602
  ];
5085
5603
  }
5086
5604
  }
5605
+ var DEFAULT_FONT_SIZE = 15;
5606
+ function $splitBlocksAtLineBreaks(selection) {
5607
+ const blocksToSplit = /* @__PURE__ */ new Set();
5608
+ for (const node of selection.getNodes()) {
5609
+ const block = node.getTopLevelElement();
5610
+ if (block && $isElementNode(block)) {
5611
+ const children = block.getChildren();
5612
+ if (children.some($isLineBreakNode)) {
5613
+ blocksToSplit.add(block);
5614
+ }
5615
+ }
5616
+ }
5617
+ for (const block of blocksToSplit) {
5618
+ const children = [...block.getChildren()];
5619
+ const groups = [[]];
5620
+ for (const child of children) {
5621
+ if ($isLineBreakNode(child)) {
5622
+ groups.push([]);
5623
+ } else {
5624
+ groups[groups.length - 1].push(child);
5625
+ }
5626
+ }
5627
+ const nonEmptyCount = groups.filter((g) => g.length > 0).length;
5628
+ if (nonEmptyCount <= 1) continue;
5629
+ for (let i = groups.length - 1; i >= 1; i--) {
5630
+ const group = groups[i];
5631
+ const newBlock = group.length === 0 ? $createParagraphNode() : $isHeadingNode(block) ? $createHeadingNode(block.getTag()) : $createParagraphNode();
5632
+ group.forEach((child) => newBlock.append(child));
5633
+ block.insertAfter(newBlock);
5634
+ }
5635
+ [...block.getChildren()].filter($isLineBreakNode).forEach((br) => br.remove());
5636
+ }
5637
+ }
5638
+ function $splitBlockAtPartialSelection(selection) {
5639
+ if (selection.isCollapsed()) return false;
5640
+ const anchorBlock = selection.anchor.getNode().getTopLevelElement();
5641
+ const focusBlock = selection.focus.getNode().getTopLevelElement();
5642
+ if (!anchorBlock || !focusBlock || !anchorBlock.is(focusBlock)) return false;
5643
+ if (!$isElementNode(anchorBlock)) return false;
5644
+ const block = anchorBlock;
5645
+ const extractedNodes = selection.extract();
5646
+ if (!extractedNodes.length) return false;
5647
+ const allDirect = extractedNodes.every((n) => {
5648
+ const parent = n.getParent();
5649
+ return parent !== null && parent.is(block);
5650
+ });
5651
+ if (!allDirect) return false;
5652
+ const allChildren = [...block.getChildren()];
5653
+ const firstSelected = extractedNodes[0];
5654
+ const lastSelected = extractedNodes[extractedNodes.length - 1];
5655
+ const firstIdx = allChildren.findIndex((n) => n.is(firstSelected));
5656
+ const lastIdx = allChildren.findIndex((n) => n.is(lastSelected));
5657
+ if (firstIdx === -1 || lastIdx === -1) return false;
5658
+ if (firstIdx === 0 && lastIdx === allChildren.length - 1) return false;
5659
+ const selectedNodes = allChildren.slice(firstIdx, lastIdx + 1);
5660
+ const afterNodes = allChildren.slice(lastIdx + 1);
5661
+ if (afterNodes.length > 0) {
5662
+ const afterBlock = $isHeadingNode(block) ? $createHeadingNode(block.getTag()) : $createParagraphNode();
5663
+ afterNodes.forEach((n) => afterBlock.append(n));
5664
+ block.insertAfter(afterBlock);
5665
+ }
5666
+ const selectedBlock = $createParagraphNode();
5667
+ selectedNodes.forEach((n) => selectedBlock.append(n));
5668
+ block.insertAfter(selectedBlock);
5669
+ if (block.getChildrenSize() === 0) {
5670
+ block.remove();
5671
+ }
5672
+ selectedBlock.select();
5673
+ return true;
5674
+ }
5675
+ var formatParagraph = (editor) => {
5676
+ editor.update(() => {
5677
+ const selection = $getSelection();
5678
+ if ($isRangeSelection(selection)) {
5679
+ $splitBlocksAtLineBreaks(selection);
5680
+ }
5681
+ $setBlocksType($getSelection(), () => $createParagraphNode());
5682
+ });
5683
+ };
5087
5684
  var PRESET = [
5088
5685
  "#000000",
5089
5686
  "#434343",
@@ -5367,7 +5964,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5367
5964
  directionalHint: 4,
5368
5965
  className: "aoColorCallout",
5369
5966
  preventDismissOnEvent,
5370
- children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 14 }, styles: { root: { padding: "14px 16px 16px", width: 288 } }, children: [
5967
+ children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 14 }, styles: { root: { padding: "14px 16px 16px", width: 250 } }, children: [
5371
5968
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
5372
5969
  /* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#242424", letterSpacing: 0.1 }, children: title }),
5373
5970
  /* @__PURE__ */ jsx(
@@ -5380,8 +5977,8 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5380
5977
  display: "flex",
5381
5978
  alignItems: "center",
5382
5979
  justifyContent: "center",
5383
- width: 24,
5384
- height: 24,
5980
+ width: 18,
5981
+ height: 18,
5385
5982
  padding: 0,
5386
5983
  border: "none",
5387
5984
  borderRadius: 4,
@@ -5404,7 +6001,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5404
6001
  style: {
5405
6002
  position: "relative",
5406
6003
  width: "100%",
5407
- height: 150,
6004
+ height: 125,
5408
6005
  borderRadius: 8,
5409
6006
  overflow: "hidden",
5410
6007
  cursor: "crosshair",
@@ -5493,8 +6090,8 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5493
6090
  "div",
5494
6091
  {
5495
6092
  style: {
5496
- width: 32,
5497
- height: 32,
6093
+ width: 20,
6094
+ height: 20,
5498
6095
  borderRadius: 6,
5499
6096
  flexShrink: 0,
5500
6097
  background: hex,
@@ -5535,10 +6132,10 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5535
6132
  title: c,
5536
6133
  "aria-label": c,
5537
6134
  style: {
5538
- width: 22,
5539
- height: 22,
6135
+ width: 18,
6136
+ height: 18,
5540
6137
  padding: 0,
5541
- borderRadius: 5,
6138
+ borderRadius: 4,
5542
6139
  background: c,
5543
6140
  cursor: "pointer",
5544
6141
  boxShadow: isSelected ? "0 0 0 2px #fff, 0 0 0 3px #4a86e8" : "inset 0 0 0 1px rgba(0,0,0,0.15)",
@@ -5748,7 +6345,6 @@ var FontFamilyPlugin = ({ disabled = false }) => {
5748
6345
  "font-family"
5749
6346
  );
5750
6347
  };
5751
- var DEFAULT_FONT_SIZE = 15;
5752
6348
  var FONT_SIZE_OPTIONS = [
5753
6349
  "8",
5754
6350
  "9",
@@ -5884,104 +6480,42 @@ var FontSizePlugin = ({ disabled }) => {
5884
6480
  "fontsize"
5885
6481
  ) });
5886
6482
  };
5887
- var InsertLinkPlugin = ({ disabled }) => {
6483
+ var InsertLinkPlugin = ({
6484
+ disabled,
6485
+ setIsLinkEditMode
6486
+ }) => {
5888
6487
  const [editor] = useLexicalComposerContext();
5889
- const [isOpen, setIsOpen] = useState(false);
5890
- const [text, setText] = useState("");
5891
- const [link, setLink] = useState("");
5892
6488
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
5893
- const insertLink = (text2, link2) => {
6489
+ const insertLink = () => {
5894
6490
  if (disabled) return;
5895
6491
  editor.update(() => {
5896
- setText("");
5897
- setLink("");
5898
6492
  const selection = $getSelection();
5899
- if ($isRangeSelection(selection)) {
5900
- const textNode = new TextNode(text2);
5901
- const linkNode = $createLinkNode(link2.startsWith("http") ? link2 : `https://${link2}`);
5902
- linkNode.append(textNode);
5903
- selection.insertNodes([linkNode]);
6493
+ if (!$isRangeSelection(selection)) return;
6494
+ const node = getSelectedNode2(selection);
6495
+ const linkParent = $findMatchingParent$1(node, $isLinkNode);
6496
+ if (!linkParent && !$isLinkNode(node)) {
6497
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, "https://");
5904
6498
  }
5905
- setIsOpen(false);
5906
6499
  });
6500
+ setIsLinkEditMode(true);
5907
6501
  };
5908
- return /* @__PURE__ */ jsxs(
5909
- Popover,
6502
+ return /* @__PURE__ */ jsx(
6503
+ Button,
5910
6504
  {
5911
- trapFocus: true,
5912
- withArrow: true,
5913
- open: disabled ? false : isOpen,
5914
- onOpenChange: (_, data) => {
5915
- if (!disabled) setIsOpen(data.open);
6505
+ size: "small",
6506
+ title: "Add link",
6507
+ disabled,
6508
+ icon: /* @__PURE__ */ jsx(LinkAddRegular, { style: { color: iconColor } }),
6509
+ style: {
6510
+ background: "none",
6511
+ border: "none",
6512
+ margin: 2,
6513
+ opacity: disabled ? 0.55 : 1,
6514
+ cursor: disabled ? "not-allowed" : "pointer"
5916
6515
  },
5917
- children: [
5918
- /* @__PURE__ */ jsx(PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
5919
- Button,
5920
- {
5921
- size: "small",
5922
- title: "Add link",
5923
- disabled,
5924
- icon: /* @__PURE__ */ jsx(LinkAddRegular, { style: { color: iconColor } }),
5925
- style: {
5926
- background: isOpen && !disabled ? "#ebebeb" : "none",
5927
- border: "none",
5928
- margin: 2,
5929
- opacity: disabled ? 0.55 : 1,
5930
- cursor: disabled ? "not-allowed" : "pointer"
5931
- },
5932
- onClick: () => {
5933
- if (!disabled) setIsOpen((prev) => !prev);
5934
- }
5935
- },
5936
- "upload-link"
5937
- ) }),
5938
- /* @__PURE__ */ jsx(
5939
- PopoverSurface,
5940
- {
5941
- style: {
5942
- width: "270px",
5943
- opacity: disabled ? 0.6 : 1,
5944
- pointerEvents: disabled ? "none" : "auto"
5945
- },
5946
- children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, children: [
5947
- /* @__PURE__ */ jsx(Field, { label: "Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
5948
- Input,
5949
- {
5950
- autoFocus: !disabled,
5951
- value: text,
5952
- appearance: "underline",
5953
- placeholder: "Text",
5954
- disabled,
5955
- onChange: (_, v) => setText(v.value)
5956
- }
5957
- ) }),
5958
- /* @__PURE__ */ jsx(Field, { label: "Link", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
5959
- Input,
5960
- {
5961
- value: link,
5962
- appearance: "underline",
5963
- placeholder: "Link",
5964
- disabled,
5965
- onChange: (_, v) => setLink(v.value)
5966
- }
5967
- ) }),
5968
- /* @__PURE__ */ jsxs(Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
5969
- /* @__PURE__ */ jsx(
5970
- Button,
5971
- {
5972
- size: "small",
5973
- disabled: disabled || !text || !link,
5974
- onClick: () => insertLink(text, link),
5975
- children: "Add"
5976
- }
5977
- ),
5978
- /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
5979
- ] })
5980
- ] })
5981
- }
5982
- )
5983
- ]
5984
- }
6516
+ onClick: insertLink
6517
+ },
6518
+ "insert-link"
5985
6519
  );
5986
6520
  };
5987
6521
  function PageSetupPlugin({ disabled, value, onChange }) {
@@ -6058,16 +6592,14 @@ var TableItemPlugin = ({ disabled }) => {
6058
6592
  setIsOpen(false);
6059
6593
  };
6060
6594
  return /* @__PURE__ */ jsxs(
6061
- Popover,
6595
+ Dialog,
6062
6596
  {
6063
- trapFocus: true,
6064
- withArrow: true,
6065
6597
  open: disabled ? false : isOpen,
6066
6598
  onOpenChange: (_, data) => {
6067
6599
  if (!disabled) setIsOpen(data.open);
6068
6600
  },
6069
6601
  children: [
6070
- /* @__PURE__ */ jsx(PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
6602
+ /* @__PURE__ */ jsx(DialogTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
6071
6603
  Button,
6072
6604
  {
6073
6605
  size: "small",
@@ -6090,58 +6622,45 @@ var TableItemPlugin = ({ disabled }) => {
6090
6622
  },
6091
6623
  "insert-table-nodes"
6092
6624
  ) }),
6093
- /* @__PURE__ */ jsx(
6094
- PopoverSurface,
6095
- {
6096
- style: {
6097
- width: "270px",
6098
- opacity: disabled ? 0.6 : 1,
6099
- pointerEvents: disabled ? "none" : "auto"
6100
- },
6101
- children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, children: [
6102
- /* @__PURE__ */ jsx(Field, { label: "Rows", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
6103
- Input,
6104
- {
6105
- autoFocus: !disabled,
6106
- type: "number",
6107
- min: 1,
6108
- value: rows,
6109
- placeholder: "Rows",
6110
- appearance: "underline",
6111
- disabled,
6112
- input: { style: { textAlign: "left" } },
6113
- onChange: (_, v) => setRows(v.value.replace(/\D/g, ""))
6114
- }
6115
- ) }),
6116
- /* @__PURE__ */ jsx(Field, { label: "Columns", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
6117
- Input,
6118
- {
6119
- type: "number",
6120
- min: 1,
6121
- value: columns,
6122
- placeholder: "Columns",
6123
- appearance: "underline",
6124
- disabled,
6125
- input: { style: { textAlign: "left" } },
6126
- onChange: (_, v) => setColumns(v.value.replace(/\D/g, ""))
6127
- }
6128
- ) }),
6129
- /* @__PURE__ */ jsxs(Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
6130
- /* @__PURE__ */ jsx(
6131
- Button,
6132
- {
6133
- size: "small",
6134
- appearance: "primary",
6135
- disabled: disabled || !rows || !columns,
6136
- onClick: onAddTable,
6137
- children: "Add"
6138
- }
6139
- ),
6140
- /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
6141
- ] })
6142
- ] })
6143
- }
6144
- )
6625
+ /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: 300 }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
6626
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Insert table" }),
6627
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 8 }, children: [
6628
+ /* @__PURE__ */ jsx(Field, { label: "Rows", size: "small", children: /* @__PURE__ */ jsx(
6629
+ Input,
6630
+ {
6631
+ autoFocus: !disabled,
6632
+ value: rows,
6633
+ placeholder: "Rows",
6634
+ appearance: "underline",
6635
+ disabled,
6636
+ onChange: (_, v) => setRows(v.value)
6637
+ }
6638
+ ) }),
6639
+ /* @__PURE__ */ jsx(Field, { label: "Columns", size: "small", children: /* @__PURE__ */ jsx(
6640
+ Input,
6641
+ {
6642
+ value: columns,
6643
+ placeholder: "Columns",
6644
+ appearance: "underline",
6645
+ disabled,
6646
+ onChange: (_, v) => setColumns(v.value)
6647
+ }
6648
+ ) })
6649
+ ] }) }),
6650
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
6651
+ /* @__PURE__ */ jsx(
6652
+ Button,
6653
+ {
6654
+ size: "small",
6655
+ appearance: "primary",
6656
+ disabled: disabled || !rows || !columns,
6657
+ onClick: onAddTable,
6658
+ children: "Add"
6659
+ }
6660
+ ),
6661
+ /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
6662
+ ] })
6663
+ ] }) })
6145
6664
  ]
6146
6665
  }
6147
6666
  );
@@ -6165,16 +6684,14 @@ var YoutubeUploadPlugin = ({ disabled }) => {
6165
6684
  setIsOpen(false);
6166
6685
  };
6167
6686
  return /* @__PURE__ */ jsxs(
6168
- Popover,
6687
+ Dialog,
6169
6688
  {
6170
- trapFocus: true,
6171
- withArrow: true,
6172
6689
  open: disabled ? false : isOpen,
6173
6690
  onOpenChange: (_, data) => {
6174
6691
  if (!disabled) setIsOpen(data.open);
6175
6692
  },
6176
6693
  children: [
6177
- /* @__PURE__ */ jsx(PopoverTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
6694
+ /* @__PURE__ */ jsx(DialogTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
6178
6695
  Button,
6179
6696
  {
6180
6697
  title: "Add youtube URL",
@@ -6196,37 +6713,36 @@ var YoutubeUploadPlugin = ({ disabled }) => {
6196
6713
  },
6197
6714
  "upload-video"
6198
6715
  ) }),
6199
- /* @__PURE__ */ jsx(
6200
- PopoverSurface,
6201
- {
6202
- style: {
6203
- width: "270px",
6204
- opacity: disabled ? 0.6 : 1,
6205
- pointerEvents: disabled ? "none" : "auto"
6206
- },
6207
- children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, children: [
6208
- /* @__PURE__ */ jsx(Field, { label: "URL", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
6209
- Input,
6210
- {
6211
- autoFocus: !disabled,
6212
- disabled,
6213
- value: url,
6214
- appearance: "underline",
6215
- placeholder: "Add Youtube video URL",
6216
- onChange: (_, v) => setURL(v.value)
6217
- }
6218
- ) }),
6219
- /* @__PURE__ */ jsxs(Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
6220
- /* @__PURE__ */ jsx(Button, { size: "small", disabled: disabled || !url, onClick: onHandleEmbeded, children: "Add" }),
6221
- /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
6222
- ] })
6223
- ] })
6224
- }
6225
- )
6716
+ /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: 320 }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
6717
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Embed YouTube video" }),
6718
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(Stack, { tokens: { childrenGap: 8 }, children: /* @__PURE__ */ jsx(Field, { label: "URL", size: "small", children: /* @__PURE__ */ jsx(
6719
+ Input,
6720
+ {
6721
+ autoFocus: !disabled,
6722
+ disabled,
6723
+ value: url,
6724
+ appearance: "underline",
6725
+ placeholder: "Add Youtube video URL",
6726
+ onChange: (_, v) => setURL(v.value)
6727
+ }
6728
+ ) }) }) }),
6729
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
6730
+ /* @__PURE__ */ jsx(Button, { size: "small", disabled: disabled || !url, onClick: onHandleEmbeded, children: "Add" }),
6731
+ /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
6732
+ ] })
6733
+ ] }) })
6226
6734
  ]
6227
6735
  }
6228
6736
  );
6229
6737
  };
6738
+ var TextAlphaListLtrFilled = ({ style }) => /* @__PURE__ */ jsxs("svg", { width: "1em", height: "1em", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", style, children: [
6739
+ /* @__PURE__ */ jsx("path", { d: "M8.75 4a.75.75 0 1 0 0 1.5h7.5a.75.75 0 0 0 0-1.5h-7.5Z" }),
6740
+ /* @__PURE__ */ jsx("path", { d: "M8.75 9a.75.75 0 1 0 0 1.5h7.5a.75.75 0 0 0 0-1.5h-7.5Z" }),
6741
+ /* @__PURE__ */ jsx("path", { d: "M8 14.75c0-.41.34-.75.75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1-.75-.75Z" }),
6742
+ /* @__PURE__ */ jsx("text", { x: "3.5", y: "6", fontSize: "5.5", fontWeight: "bold", textAnchor: "middle", children: "a" }),
6743
+ /* @__PURE__ */ jsx("text", { x: "3.5", y: "11", fontSize: "5.5", fontWeight: "bold", textAnchor: "middle", children: "b" }),
6744
+ /* @__PURE__ */ jsx("text", { x: "3.5", y: "16", fontSize: "5.5", fontWeight: "bold", textAnchor: "middle", children: "c" })
6745
+ ] });
6230
6746
  var useStyles4 = makeStyles({
6231
6747
  dropdown: {
6232
6748
  minInlineSize: "90px",
@@ -6244,24 +6760,42 @@ var ALLOWED_TOKENS = {
6244
6760
  Bold: true,
6245
6761
  Italic: true,
6246
6762
  Underline: true,
6763
+ Strikethrough: true,
6764
+ Subscript: true,
6765
+ Superscript: true,
6766
+ Highlight: true,
6767
+ Uppercase: true,
6768
+ Lowercase: true,
6769
+ Capitalize: true,
6770
+ BulletList: true,
6771
+ NumberList: true,
6772
+ AlphabeticalList: true,
6773
+ Quote: true,
6774
+ PageBreak: true,
6775
+ H1: true,
6776
+ H2: true,
6777
+ H3: true,
6778
+ H4: true,
6779
+ H5: true,
6780
+ H6: true,
6247
6781
  ColorPicker: true,
6248
6782
  Link: true,
6249
6783
  Table: true,
6250
6784
  Image: true,
6251
6785
  InlineImage: true,
6252
6786
  Youtube: true,
6253
- Heading: true,
6254
6787
  FontFamily: true,
6255
6788
  FontSize: true,
6789
+ Align: true,
6790
+ Heading: true,
6256
6791
  Decorators: true,
6257
6792
  CodeBlock: true,
6258
- Align: true,
6259
6793
  PageSetup: true
6260
6794
  };
6261
6795
  function sanitizePluginGroups(groups) {
6262
- if (!groups || groups.length === 0) return [];
6796
+ if (!Array.isArray(groups) || groups.length === 0) return [];
6263
6797
  return groups.map(
6264
- (g) => (g || []).map((t) => t.trim()).filter((t) => ALLOWED_TOKENS[t] === true)
6798
+ (g) => (Array.isArray(g) ? g : []).map((t) => typeof t === "string" ? t.trim() : "").filter((t) => ALLOWED_TOKENS[t] === true)
6265
6799
  ).filter((g) => g.length > 0);
6266
6800
  }
6267
6801
  var ToolBarPlugins = (props) => {
@@ -6281,8 +6815,9 @@ var ToolBarPlugins = (props) => {
6281
6815
  const [isLowercase, setIsLowercase] = useState(false);
6282
6816
  const [isCapitalize, setIsCapitalize] = useState(false);
6283
6817
  const [alignment, setAlignment] = useState("left");
6284
- const lastSelectionRef = React9__default.useRef(null);
6285
- const presetGroups = getToolbarGroupsByLevel(props.level);
6818
+ const [decoratorOpen, setDecoratorOpen] = useState(false);
6819
+ const decoratorSelectingRef = React9__default.useRef(false);
6820
+ const presetGroups = props.customToolbar ?? getToolbarGroupsByLevel(props.level);
6286
6821
  const pluginGroups = useMemo(() => sanitizePluginGroups(presetGroups), [presetGroups]);
6287
6822
  const updateToolbarPlugins = () => {
6288
6823
  const selection = $getSelection();
@@ -6324,6 +6859,10 @@ var ToolBarPlugins = (props) => {
6324
6859
  setSelectNodeType("paragraph");
6325
6860
  return;
6326
6861
  }
6862
+ if ($isAlphaListNode(element)) {
6863
+ setSelectNodeType("alpha");
6864
+ return;
6865
+ }
6327
6866
  if ($isListNode(element)) {
6328
6867
  const parentList = $getNearestNodeOfType(anchorNode, ListNode);
6329
6868
  const type2 = parentList ? parentList.getTag() : element.getTag();
@@ -6335,23 +6874,15 @@ var ToolBarPlugins = (props) => {
6335
6874
  ["paragraph", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "quote", "code"].includes(type) ? type : "paragraph"
6336
6875
  );
6337
6876
  };
6338
- const applyToBlock = React9__default.useCallback(
6339
- (fn) => {
6340
- editor.update(() => {
6341
- const saved = lastSelectionRef.current;
6342
- if (saved) $setSelection(saved.clone());
6343
- const sel = $getSelection();
6344
- if ($isRangeSelection(sel)) fn(sel);
6345
- });
6346
- },
6347
- [editor]
6348
- );
6349
6877
  const formatQuote = () => {
6350
- applyToBlock((selection) => {
6878
+ editor.update(() => {
6879
+ const selection = $getSelection();
6880
+ if (!$isRangeSelection(selection)) return;
6351
6881
  if (selectNodeType === "quote") {
6352
- $setBlocksType(selection, () => $createParagraphNode());
6882
+ formatParagraph(editor);
6353
6883
  } else {
6354
- $setBlocksType(selection, () => $createQuoteNode());
6884
+ $splitBlocksAtLineBreaks(selection);
6885
+ $setBlocksType($getSelection(), () => $createQuoteNode());
6355
6886
  }
6356
6887
  });
6357
6888
  };
@@ -6368,8 +6899,6 @@ var ToolBarPlugins = (props) => {
6368
6899
  editor.registerCommand(
6369
6900
  SELECTION_CHANGE_COMMAND,
6370
6901
  () => {
6371
- const sel = $getSelection();
6372
- if ($isRangeSelection(sel)) lastSelectionRef.current = sel.clone();
6373
6902
  updateToolbarPlugins();
6374
6903
  return false;
6375
6904
  },
@@ -6401,52 +6930,16 @@ var ToolBarPlugins = (props) => {
6401
6930
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, "highlight");
6402
6931
  break;
6403
6932
  case "leftAlign" /* LeftAlign */:
6404
- applyToBlock((sel) => {
6405
- const seen = /* @__PURE__ */ new Set();
6406
- sel.getNodes().forEach((n) => {
6407
- const t = n.getTopLevelElementOrThrow();
6408
- if (!seen.has(t.getKey())) {
6409
- seen.add(t.getKey());
6410
- t.setFormat("left");
6411
- }
6412
- });
6413
- });
6933
+ applyAlignmentWithSplit("left");
6414
6934
  break;
6415
6935
  case "rightAlign" /* RightAlign */:
6416
- applyToBlock((sel) => {
6417
- const seen = /* @__PURE__ */ new Set();
6418
- sel.getNodes().forEach((n) => {
6419
- const t = n.getTopLevelElementOrThrow();
6420
- if (!seen.has(t.getKey())) {
6421
- seen.add(t.getKey());
6422
- t.setFormat("right");
6423
- }
6424
- });
6425
- });
6936
+ applyAlignmentWithSplit("right");
6426
6937
  break;
6427
6938
  case "centerAlign" /* CenterAlign */:
6428
- applyToBlock((sel) => {
6429
- const seen = /* @__PURE__ */ new Set();
6430
- sel.getNodes().forEach((n) => {
6431
- const t = n.getTopLevelElementOrThrow();
6432
- if (!seen.has(t.getKey())) {
6433
- seen.add(t.getKey());
6434
- t.setFormat("center");
6435
- }
6436
- });
6437
- });
6939
+ applyAlignmentWithSplit("center");
6438
6940
  break;
6439
6941
  case "justifyAlign" /* JustifyAlign */:
6440
- applyToBlock((sel) => {
6441
- const seen = /* @__PURE__ */ new Set();
6442
- sel.getNodes().forEach((n) => {
6443
- const t = n.getTopLevelElementOrThrow();
6444
- if (!seen.has(t.getKey())) {
6445
- seen.add(t.getKey());
6446
- t.setFormat("justify");
6447
- }
6448
- });
6449
- });
6942
+ applyAlignmentWithSplit("justify");
6450
6943
  break;
6451
6944
  case "undo" /* Undo */:
6452
6945
  editor.dispatchCommand(UNDO_COMMAND, void 0);
@@ -6465,9 +6958,32 @@ var ToolBarPlugins = (props) => {
6465
6958
  break;
6466
6959
  }
6467
6960
  };
6961
+ const applyAlignmentWithSplit = (formatType) => {
6962
+ editor.update(() => {
6963
+ const selection = $getSelection();
6964
+ if (!$isRangeSelection(selection)) return;
6965
+ $splitBlocksAtLineBreaks(selection);
6966
+ const sel = $getSelection();
6967
+ if ($isRangeSelection(sel)) {
6968
+ $splitBlockAtPartialSelection(sel);
6969
+ }
6970
+ });
6971
+ editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, formatType);
6972
+ };
6468
6973
  const updateHeading = (heading) => {
6469
- applyToBlock((selection) => {
6470
- $setBlocksType(selection, () => $createHeadingNode(heading));
6974
+ editor.update(() => {
6975
+ const selection = $getSelection();
6976
+ if ($isRangeSelection(selection)) {
6977
+ $splitBlocksAtLineBreaks(selection);
6978
+ const sel = $getSelection();
6979
+ if ($isRangeSelection(sel)) {
6980
+ $splitBlockAtPartialSelection(sel);
6981
+ }
6982
+ $setBlocksType($getSelection(), () => $createHeadingNode(heading));
6983
+ }
6984
+ });
6985
+ editor.getEditorState().read(() => {
6986
+ updateToolbarPlugins();
6471
6987
  });
6472
6988
  };
6473
6989
  const renderToken = (token, groupIndex, tokenIndex) => {
@@ -6479,7 +6995,6 @@ var ToolBarPlugins = (props) => {
6479
6995
  const brand = palette.themePrimary;
6480
6996
  const brandHover = palette.themeDarkAlt ?? brand;
6481
6997
  const brandPressed = palette.themeDark ?? brand;
6482
- palette.white;
6483
6998
  const bgHover = palette.neutralLighter;
6484
6999
  const bgPressed = palette.neutralLight;
6485
7000
  const bgActive = palette.neutralLighterAlt ?? palette.neutralLighter;
@@ -6532,6 +7047,8 @@ var ToolBarPlugins = (props) => {
6532
7047
  Button,
6533
7048
  {
6534
7049
  size: "small",
7050
+ "aria-label": "Bold",
7051
+ "aria-pressed": isBold,
6535
7052
  disabled: !isEditable || props.readOnly,
6536
7053
  icon: /* @__PURE__ */ jsx(TextBold24Regular, { style: { color: getIconColor(isBold) } }),
6537
7054
  style: getButtonStyle(isBold),
@@ -6544,6 +7061,8 @@ var ToolBarPlugins = (props) => {
6544
7061
  Button,
6545
7062
  {
6546
7063
  size: "small",
7064
+ "aria-label": "Italic",
7065
+ "aria-pressed": isItalic,
6547
7066
  disabled: !isEditable || props.readOnly,
6548
7067
  icon: /* @__PURE__ */ jsx(TextItalicFilled, { style: { color: getIconColor(isItalic) } }),
6549
7068
  style: getButtonStyle(isItalic),
@@ -6556,6 +7075,8 @@ var ToolBarPlugins = (props) => {
6556
7075
  Button,
6557
7076
  {
6558
7077
  size: "small",
7078
+ "aria-label": "Underline",
7079
+ "aria-pressed": isUnderline,
6559
7080
  disabled: !isEditable || props.readOnly,
6560
7081
  icon: /* @__PURE__ */ jsx(TextUnderlineFilled, { style: { color: getIconColor(isUnderline) } }),
6561
7082
  style: getButtonStyle(isUnderline),
@@ -6566,7 +7087,14 @@ var ToolBarPlugins = (props) => {
6566
7087
  case "ColorPicker":
6567
7088
  return /* @__PURE__ */ jsx(ColorPickerPlugin, { disabled: !isEditable || props.readOnly }, key);
6568
7089
  case "Link":
6569
- return /* @__PURE__ */ jsx(InsertLinkPlugin, { disabled: !isEditable || props.readOnly }, key);
7090
+ return /* @__PURE__ */ jsx(
7091
+ InsertLinkPlugin,
7092
+ {
7093
+ disabled: !isEditable || props.readOnly,
7094
+ setIsLinkEditMode: props.setIsLinkEditMode
7095
+ },
7096
+ key
7097
+ );
6570
7098
  case "Table":
6571
7099
  return /* @__PURE__ */ jsx(TableItemPlugin, { disabled: !isEditable || props.readOnly }, key);
6572
7100
  case "Image":
@@ -6574,7 +7102,9 @@ var ToolBarPlugins = (props) => {
6574
7102
  InsertImageDialog,
6575
7103
  {
6576
7104
  activeEditor: editor,
6577
- disabled: !isEditable || props.readOnly
7105
+ disabled: !isEditable || props.readOnly,
7106
+ maxImageSizeMB: props.maxImageSizeMB,
7107
+ validationMessages: props.validationMessages
6578
7108
  },
6579
7109
  key
6580
7110
  );
@@ -6583,7 +7113,9 @@ var ToolBarPlugins = (props) => {
6583
7113
  InsertInlineImageDialog,
6584
7114
  {
6585
7115
  activeEditor: editor,
6586
- disabled: !isEditable || props.readOnly
7116
+ disabled: !isEditable || props.readOnly,
7117
+ maxImageSizeMB: props.maxImageSizeMB,
7118
+ validationMessages: props.validationMessages
6587
7119
  },
6588
7120
  key
6589
7121
  );
@@ -6606,7 +7138,7 @@ var ToolBarPlugins = (props) => {
6606
7138
  const val = data.optionValue;
6607
7139
  if (!val) return;
6608
7140
  if (val === "paragraph") {
6609
- applyToBlock((sel) => $setBlocksType(sel, () => $createParagraphNode()));
7141
+ formatParagraph(editor);
6610
7142
  setSelectNodeType("paragraph");
6611
7143
  } else {
6612
7144
  updateHeading(val);
@@ -6627,78 +7159,451 @@ var ToolBarPlugins = (props) => {
6627
7159
  return /* @__PURE__ */ jsx(FontSizePlugin, { disabled: !isEditable || props.readOnly }, key);
6628
7160
  case "|":
6629
7161
  return /* @__PURE__ */ jsx(ToolbarDivider, {}, key);
6630
- case "Decorators": {
6631
- const activeDecorators = [
6632
- ...isUppercase ? ["uppercase"] : [],
6633
- ...isLowercase ? ["lowercase"] : [],
6634
- ...isCapitalize ? ["capitalize"] : [],
6635
- ...isStrikethrough ? ["strike"] : [],
6636
- ...isSubscript ? ["subscript"] : [],
6637
- ...isSuperscript ? ["superscript"] : [],
6638
- ...isHighlight ? ["highlight"] : [],
6639
- ...selectNodeType === "ul" ? ["ul-list"] : [],
6640
- ...selectNodeType === "ol" ? ["ol-list"] : [],
6641
- ...selectNodeType === "quote" ? ["quote"] : []
6642
- ];
6643
- const DECORATOR_LABEL = {
6644
- uppercase: "Uppercase",
6645
- lowercase: "Lowercase",
6646
- capitalize: "Capitalize",
6647
- strike: "Strikethrough",
6648
- subscript: "Subscript",
6649
- superscript: "Superscript",
6650
- highlight: "Highlight",
6651
- "ul-list": "Bullet list",
6652
- "ol-list": "Number list",
6653
- quote: "Quote"
6654
- };
6655
- const decoratorValue = activeDecorators.length === 0 ? "" : activeDecorators.length === 1 ? DECORATOR_LABEL[activeDecorators[0]] : `${DECORATOR_LABEL[activeDecorators[0]]} +${activeDecorators.length - 1}`;
6656
- return /* @__PURE__ */ jsxs(
6657
- Dropdown,
7162
+ // ── Standalone text-format toggle buttons ─────────────────────────────
7163
+ // These were previously only accessible inside the 'Decorators' dropdown.
7164
+ // Use them directly in customToolbar to show individual buttons instead.
7165
+ case "Strikethrough":
7166
+ return /* @__PURE__ */ jsx(
7167
+ Button,
6658
7168
  {
6659
- multiselect: true,
6660
- id: `${groupIndex}-set-decorators`,
6661
- placeholder: "Text",
6662
- value: decoratorValue,
6663
- selectedOptions: activeDecorators,
6664
- disabled: !isEditable,
6665
- className: styles.alignDropdown,
6666
- button: { style: dropdownButtonStyle },
6667
- expandIcon: { style: dropdownExpandIconStyle },
6668
- listbox: { style: { minInlineSize: "180px" } },
6669
- onOptionSelect: (_, data) => {
6670
- switch (data.optionValue) {
6671
- case "uppercase":
6672
- editor.dispatchCommand(FORMAT_TEXT_COMMAND, "uppercase");
6673
- break;
6674
- case "lowercase":
6675
- onHandleSelectOption("lowercase" /* Lowercase */);
6676
- break;
6677
- case "capitalize":
6678
- onHandleSelectOption("capitalize" /* Capitalize */);
6679
- break;
6680
- case "strike":
6681
- onHandleSelectOption("strike" /* Strikethrough */);
6682
- break;
6683
- case "subscript":
6684
- onHandleSelectOption("subscript" /* Subscript */);
6685
- break;
6686
- case "superscript":
6687
- onHandleSelectOption("superscript" /* Superscript */);
6688
- break;
6689
- case "highlight":
6690
- onHandleSelectOption("highlight" /* Highlight */);
6691
- break;
6692
- case "ul-list":
6693
- editor.dispatchCommand(selectNodeType === "ul" ? REMOVE_LIST_COMMAND : INSERT_UNORDERED_LIST_COMMAND, void 0);
6694
- break;
6695
- case "ol-list":
6696
- editor.dispatchCommand(selectNodeType === "ol" ? REMOVE_LIST_COMMAND : INSERT_ORDERED_LIST_COMMAND, void 0);
6697
- break;
6698
- case "page-break":
6699
- editor.dispatchCommand(INSERT_PAGE_BREAK, void 0);
6700
- break;
6701
- case "quote":
7169
+ size: "small",
7170
+ "aria-label": "Strikethrough",
7171
+ "aria-pressed": isStrikethrough,
7172
+ disabled: !isEditable || props.readOnly,
7173
+ icon: /* @__PURE__ */ jsx(TextStrikethroughFilled, { style: { color: getIconColor(isStrikethrough) } }),
7174
+ style: getButtonStyle(isStrikethrough),
7175
+ onClick: () => onHandleSelectOption("strike" /* Strikethrough */)
7176
+ },
7177
+ key
7178
+ );
7179
+ case "Subscript":
7180
+ return /* @__PURE__ */ jsx(
7181
+ Button,
7182
+ {
7183
+ size: "small",
7184
+ "aria-label": "Subscript",
7185
+ "aria-pressed": isSubscript,
7186
+ disabled: !isEditable || props.readOnly,
7187
+ icon: /* @__PURE__ */ jsx(TextSubscriptFilled, { style: { color: getIconColor(isSubscript) } }),
7188
+ style: getButtonStyle(isSubscript),
7189
+ onClick: () => onHandleSelectOption("subscript" /* Subscript */)
7190
+ },
7191
+ key
7192
+ );
7193
+ case "Superscript":
7194
+ return /* @__PURE__ */ jsx(
7195
+ Button,
7196
+ {
7197
+ size: "small",
7198
+ "aria-label": "Superscript",
7199
+ "aria-pressed": isSuperscript,
7200
+ disabled: !isEditable || props.readOnly,
7201
+ icon: /* @__PURE__ */ jsx(TextSuperscriptFilled, { style: { color: getIconColor(isSuperscript) } }),
7202
+ style: getButtonStyle(isSuperscript),
7203
+ onClick: () => onHandleSelectOption("superscript" /* Superscript */)
7204
+ },
7205
+ key
7206
+ );
7207
+ case "Highlight":
7208
+ return /* @__PURE__ */ jsx(
7209
+ Button,
7210
+ {
7211
+ size: "small",
7212
+ "aria-label": "Highlight",
7213
+ "aria-pressed": isHighlight,
7214
+ disabled: !isEditable || props.readOnly,
7215
+ icon: /* @__PURE__ */ jsx(
7216
+ HighlightAccentFilled,
7217
+ {
7218
+ style: { color: isEditable ? isHighlight ? brand : fg : fgDisabled }
7219
+ }
7220
+ ),
7221
+ style: getButtonStyle(isHighlight),
7222
+ onClick: () => onHandleSelectOption("highlight" /* Highlight */)
7223
+ },
7224
+ key
7225
+ );
7226
+ case "Uppercase":
7227
+ return /* @__PURE__ */ jsx(
7228
+ Button,
7229
+ {
7230
+ size: "small",
7231
+ "aria-label": "Uppercase",
7232
+ "aria-pressed": isUppercase,
7233
+ disabled: !isEditable || props.readOnly,
7234
+ icon: /* @__PURE__ */ jsx(TextCaseUppercaseFilled, { style: { color: getIconColor(isUppercase) } }),
7235
+ style: getButtonStyle(isUppercase),
7236
+ onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "uppercase")
7237
+ },
7238
+ key
7239
+ );
7240
+ case "Lowercase":
7241
+ return /* @__PURE__ */ jsx(
7242
+ Button,
7243
+ {
7244
+ size: "small",
7245
+ "aria-label": "Lowercase",
7246
+ "aria-pressed": isLowercase,
7247
+ disabled: !isEditable || props.readOnly,
7248
+ icon: /* @__PURE__ */ jsx(TextCaseLowercaseFilled, { style: { color: getIconColor(isLowercase) } }),
7249
+ style: getButtonStyle(isLowercase),
7250
+ onClick: () => onHandleSelectOption("lowercase" /* Lowercase */)
7251
+ },
7252
+ key
7253
+ );
7254
+ case "Capitalize":
7255
+ return /* @__PURE__ */ jsx(
7256
+ Button,
7257
+ {
7258
+ size: "small",
7259
+ "aria-label": "Capitalize",
7260
+ "aria-pressed": isCapitalize,
7261
+ disabled: !isEditable || props.readOnly,
7262
+ icon: /* @__PURE__ */ jsx(TextCaseTitleFilled, { style: { color: getIconColor(isCapitalize) } }),
7263
+ style: getButtonStyle(isCapitalize),
7264
+ onClick: () => onHandleSelectOption("capitalize" /* Capitalize */)
7265
+ },
7266
+ key
7267
+ );
7268
+ // ── Standalone list toggle buttons ────────────────────────────────────
7269
+ case "BulletList":
7270
+ return /* @__PURE__ */ jsx(
7271
+ Button,
7272
+ {
7273
+ size: "small",
7274
+ "aria-label": "Bullet list",
7275
+ "aria-pressed": selectNodeType === "ul",
7276
+ disabled: !isEditable || props.readOnly,
7277
+ icon: /* @__PURE__ */ jsx(TextBulletListLtrFilled, { style: { color: getIconColor(selectNodeType === "ul") } }),
7278
+ style: getButtonStyle(selectNodeType === "ul"),
7279
+ onClick: () => editor.dispatchCommand(
7280
+ selectNodeType === "ul" ? REMOVE_LIST_COMMAND : INSERT_UNORDERED_LIST_COMMAND,
7281
+ void 0
7282
+ )
7283
+ },
7284
+ key
7285
+ );
7286
+ case "NumberList":
7287
+ return /* @__PURE__ */ jsx(
7288
+ Button,
7289
+ {
7290
+ size: "small",
7291
+ "aria-label": "Number list",
7292
+ "aria-pressed": selectNodeType === "ol",
7293
+ disabled: !isEditable || props.readOnly,
7294
+ icon: /* @__PURE__ */ jsx(TextNumberListLtrFilled, { style: { color: getIconColor(selectNodeType === "ol") } }),
7295
+ style: getButtonStyle(selectNodeType === "ol"),
7296
+ onClick: () => editor.dispatchCommand(
7297
+ selectNodeType === "ol" ? REMOVE_LIST_COMMAND : INSERT_ORDERED_LIST_COMMAND,
7298
+ void 0
7299
+ )
7300
+ },
7301
+ key
7302
+ );
7303
+ case "AlphabeticalList":
7304
+ return /* @__PURE__ */ jsx(
7305
+ Button,
7306
+ {
7307
+ size: "small",
7308
+ "aria-label": "Alphabetical list",
7309
+ "aria-pressed": selectNodeType === "alpha",
7310
+ disabled: !isEditable || props.readOnly,
7311
+ icon: /* @__PURE__ */ jsx(
7312
+ TextAlphaListLtrFilled,
7313
+ {
7314
+ style: { color: getIconColor(selectNodeType === "alpha") }
7315
+ }
7316
+ ),
7317
+ style: getButtonStyle(selectNodeType === "alpha"),
7318
+ onClick: () => editor.update(() => $toggleAlphaList())
7319
+ },
7320
+ key
7321
+ );
7322
+ // ── Standalone block buttons ──────────────────────────────────────────
7323
+ case "Quote":
7324
+ return /* @__PURE__ */ jsx(
7325
+ Button,
7326
+ {
7327
+ size: "small",
7328
+ "aria-label": "Quote",
7329
+ "aria-pressed": selectNodeType === "quote",
7330
+ disabled: !isEditable || props.readOnly,
7331
+ icon: /* @__PURE__ */ jsx(CommentQuoteRegular, { style: { color: getIconColor(selectNodeType === "quote") } }),
7332
+ style: getButtonStyle(selectNodeType === "quote"),
7333
+ onClick: () => formatQuote()
7334
+ },
7335
+ key
7336
+ );
7337
+ case "PageBreak":
7338
+ return /* @__PURE__ */ jsx(
7339
+ Button,
7340
+ {
7341
+ size: "small",
7342
+ "aria-label": "Page break",
7343
+ disabled: !isEditable || props.readOnly,
7344
+ icon: /* @__PURE__ */ jsx(DocumentPageBreakRegular, { style: { color: getIconColor() } }),
7345
+ style: getButtonStyle(),
7346
+ onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK, void 0)
7347
+ },
7348
+ key
7349
+ );
7350
+ // ── Standalone heading-level toggle buttons ───────────────────────────
7351
+ // Each button sets (or toggles off) that specific heading level.
7352
+ // Clicking an active heading reverts to normal paragraph.
7353
+ case "H1":
7354
+ return /* @__PURE__ */ jsx(
7355
+ Button,
7356
+ {
7357
+ size: "small",
7358
+ "aria-label": "Heading 1",
7359
+ "aria-pressed": selectNodeType === "h1",
7360
+ disabled: !isEditable || props.readOnly,
7361
+ style: {
7362
+ ...getButtonStyle(selectNodeType === "h1"),
7363
+ color: getIconColor(selectNodeType === "h1"),
7364
+ fontSize: 12,
7365
+ fontWeight: 600
7366
+ },
7367
+ onClick: () => {
7368
+ if (selectNodeType === "h1") {
7369
+ formatParagraph(editor);
7370
+ setSelectNodeType("paragraph");
7371
+ } else {
7372
+ updateHeading("h1");
7373
+ setSelectNodeType("h1");
7374
+ }
7375
+ },
7376
+ children: "H1"
7377
+ },
7378
+ key
7379
+ );
7380
+ case "H2":
7381
+ return /* @__PURE__ */ jsx(
7382
+ Button,
7383
+ {
7384
+ size: "small",
7385
+ "aria-label": "Heading 2",
7386
+ "aria-pressed": selectNodeType === "h2",
7387
+ disabled: !isEditable || props.readOnly,
7388
+ style: {
7389
+ ...getButtonStyle(selectNodeType === "h2"),
7390
+ color: getIconColor(selectNodeType === "h2"),
7391
+ fontSize: 12,
7392
+ fontWeight: 600
7393
+ },
7394
+ onClick: () => {
7395
+ if (selectNodeType === "h2") {
7396
+ formatParagraph(editor);
7397
+ setSelectNodeType("paragraph");
7398
+ } else {
7399
+ updateHeading("h2");
7400
+ setSelectNodeType("h2");
7401
+ }
7402
+ },
7403
+ children: "H2"
7404
+ },
7405
+ key
7406
+ );
7407
+ case "H3":
7408
+ return /* @__PURE__ */ jsx(
7409
+ Button,
7410
+ {
7411
+ size: "small",
7412
+ "aria-label": "Heading 3",
7413
+ "aria-pressed": selectNodeType === "h3",
7414
+ disabled: !isEditable || props.readOnly,
7415
+ style: {
7416
+ ...getButtonStyle(selectNodeType === "h3"),
7417
+ color: getIconColor(selectNodeType === "h3"),
7418
+ fontSize: 12,
7419
+ fontWeight: 600
7420
+ },
7421
+ onClick: () => {
7422
+ if (selectNodeType === "h3") {
7423
+ formatParagraph(editor);
7424
+ setSelectNodeType("paragraph");
7425
+ } else {
7426
+ updateHeading("h3");
7427
+ setSelectNodeType("h3");
7428
+ }
7429
+ },
7430
+ children: "H3"
7431
+ },
7432
+ key
7433
+ );
7434
+ case "H4":
7435
+ return /* @__PURE__ */ jsx(
7436
+ Button,
7437
+ {
7438
+ size: "small",
7439
+ "aria-label": "Heading 4",
7440
+ "aria-pressed": selectNodeType === "h4",
7441
+ disabled: !isEditable || props.readOnly,
7442
+ style: {
7443
+ ...getButtonStyle(selectNodeType === "h4"),
7444
+ color: getIconColor(selectNodeType === "h4"),
7445
+ fontSize: 12,
7446
+ fontWeight: 600
7447
+ },
7448
+ onClick: () => {
7449
+ if (selectNodeType === "h4") {
7450
+ formatParagraph(editor);
7451
+ setSelectNodeType("paragraph");
7452
+ } else {
7453
+ updateHeading("h4");
7454
+ setSelectNodeType("h4");
7455
+ }
7456
+ },
7457
+ children: "H4"
7458
+ },
7459
+ key
7460
+ );
7461
+ case "H5":
7462
+ return /* @__PURE__ */ jsx(
7463
+ Button,
7464
+ {
7465
+ size: "small",
7466
+ "aria-label": "Heading 5",
7467
+ "aria-pressed": selectNodeType === "h5",
7468
+ disabled: !isEditable || props.readOnly,
7469
+ style: {
7470
+ ...getButtonStyle(selectNodeType === "h5"),
7471
+ color: getIconColor(selectNodeType === "h5"),
7472
+ fontSize: 12,
7473
+ fontWeight: 600
7474
+ },
7475
+ onClick: () => {
7476
+ if (selectNodeType === "h5") {
7477
+ formatParagraph(editor);
7478
+ setSelectNodeType("paragraph");
7479
+ } else {
7480
+ updateHeading("h5");
7481
+ setSelectNodeType("h5");
7482
+ }
7483
+ },
7484
+ children: "H5"
7485
+ },
7486
+ key
7487
+ );
7488
+ case "H6":
7489
+ return /* @__PURE__ */ jsx(
7490
+ Button,
7491
+ {
7492
+ size: "small",
7493
+ "aria-label": "Heading 6",
7494
+ "aria-pressed": selectNodeType === "h6",
7495
+ disabled: !isEditable || props.readOnly,
7496
+ style: {
7497
+ ...getButtonStyle(selectNodeType === "h6"),
7498
+ color: getIconColor(selectNodeType === "h6"),
7499
+ fontSize: 12,
7500
+ fontWeight: 600
7501
+ },
7502
+ onClick: () => {
7503
+ if (selectNodeType === "h6") {
7504
+ formatParagraph(editor);
7505
+ setSelectNodeType("paragraph");
7506
+ } else {
7507
+ updateHeading("h6");
7508
+ setSelectNodeType("h6");
7509
+ }
7510
+ },
7511
+ children: "H6"
7512
+ },
7513
+ key
7514
+ );
7515
+ case "Decorators": {
7516
+ const activeDecorators = [
7517
+ ...isUppercase ? ["uppercase"] : [],
7518
+ ...isLowercase ? ["lowercase"] : [],
7519
+ ...isCapitalize ? ["capitalize"] : [],
7520
+ ...isStrikethrough ? ["strike"] : [],
7521
+ ...isSubscript ? ["subscript"] : [],
7522
+ ...isSuperscript ? ["superscript"] : [],
7523
+ ...isHighlight ? ["highlight"] : [],
7524
+ ...selectNodeType === "ul" ? ["ul-list"] : [],
7525
+ ...selectNodeType === "ol" ? ["ol-list"] : [],
7526
+ ...selectNodeType === "alpha" ? ["al-list"] : [],
7527
+ ...selectNodeType === "quote" ? ["quote"] : []
7528
+ ];
7529
+ const DECORATOR_LABEL = {
7530
+ uppercase: "Uppercase",
7531
+ lowercase: "Lowercase",
7532
+ capitalize: "Capitalize",
7533
+ strike: "Strikethrough",
7534
+ subscript: "Subscript",
7535
+ superscript: "Superscript",
7536
+ highlight: "Highlight",
7537
+ "ul-list": "Bullet list",
7538
+ "ol-list": "Number list",
7539
+ "al-list": "Alphabetical list",
7540
+ quote: "Quote"
7541
+ };
7542
+ const decoratorValue = activeDecorators.length === 0 ? "" : activeDecorators.length === 1 ? DECORATOR_LABEL[activeDecorators[0]] : `${DECORATOR_LABEL[activeDecorators[0]]} +${activeDecorators.length - 1}`;
7543
+ return /* @__PURE__ */ jsxs(
7544
+ Dropdown,
7545
+ {
7546
+ multiselect: true,
7547
+ id: `${groupIndex}-set-decorators`,
7548
+ placeholder: "Text",
7549
+ value: decoratorValue,
7550
+ selectedOptions: activeDecorators,
7551
+ disabled: !isEditable,
7552
+ className: styles.alignDropdown,
7553
+ button: { style: dropdownButtonStyle },
7554
+ expandIcon: { style: dropdownExpandIconStyle },
7555
+ listbox: { style: { minInlineSize: "180px" } },
7556
+ open: decoratorOpen,
7557
+ onOpenChange: (_, data) => {
7558
+ if (decoratorSelectingRef.current) {
7559
+ decoratorSelectingRef.current = false;
7560
+ return;
7561
+ }
7562
+ setDecoratorOpen(data.open);
7563
+ },
7564
+ onOptionSelect: (_, data) => {
7565
+ decoratorSelectingRef.current = true;
7566
+ switch (data.optionValue) {
7567
+ case "uppercase":
7568
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "uppercase");
7569
+ break;
7570
+ case "lowercase":
7571
+ onHandleSelectOption("lowercase" /* Lowercase */);
7572
+ break;
7573
+ case "capitalize":
7574
+ onHandleSelectOption("capitalize" /* Capitalize */);
7575
+ break;
7576
+ case "strike":
7577
+ onHandleSelectOption("strike" /* Strikethrough */);
7578
+ break;
7579
+ case "subscript":
7580
+ onHandleSelectOption("subscript" /* Subscript */);
7581
+ break;
7582
+ case "superscript":
7583
+ onHandleSelectOption("superscript" /* Superscript */);
7584
+ break;
7585
+ case "highlight":
7586
+ onHandleSelectOption("highlight" /* Highlight */);
7587
+ break;
7588
+ case "ul-list":
7589
+ editor.dispatchCommand(
7590
+ selectNodeType === "ul" ? REMOVE_LIST_COMMAND : INSERT_UNORDERED_LIST_COMMAND,
7591
+ void 0
7592
+ );
7593
+ break;
7594
+ case "ol-list":
7595
+ editor.dispatchCommand(
7596
+ selectNodeType === "ol" ? REMOVE_LIST_COMMAND : INSERT_ORDERED_LIST_COMMAND,
7597
+ void 0
7598
+ );
7599
+ break;
7600
+ case "al-list":
7601
+ editor.update(() => $toggleAlphaList());
7602
+ break;
7603
+ case "page-break":
7604
+ editor.dispatchCommand(INSERT_PAGE_BREAK, void 0);
7605
+ break;
7606
+ case "quote":
6702
7607
  formatQuote();
6703
7608
  break;
6704
7609
  }
@@ -6729,7 +7634,12 @@ var ToolBarPlugins = (props) => {
6729
7634
  "Superscript"
6730
7635
  ] }),
6731
7636
  /* @__PURE__ */ jsxs(Option, { value: "highlight", text: "Highlight", children: [
6732
- /* @__PURE__ */ jsx(HighlightAccentFilled, { style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled } }),
7637
+ /* @__PURE__ */ jsx(
7638
+ HighlightAccentFilled,
7639
+ {
7640
+ style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled }
7641
+ }
7642
+ ),
6733
7643
  "Highlight"
6734
7644
  ] }),
6735
7645
  /* @__PURE__ */ jsxs(Option, { value: "ul-list", text: "Bullet list", children: [
@@ -6740,6 +7650,10 @@ var ToolBarPlugins = (props) => {
6740
7650
  /* @__PURE__ */ jsx(TextNumberListLtrFilled, { style: optionIconStyle }),
6741
7651
  "Number list"
6742
7652
  ] }),
7653
+ /* @__PURE__ */ jsxs(Option, { value: "al-list", text: "Alphabetical list", children: [
7654
+ /* @__PURE__ */ jsx(TextAlphaListLtrFilled, { style: optionIconStyle }),
7655
+ "Alphabetical list"
7656
+ ] }),
6743
7657
  /* @__PURE__ */ jsxs(Option, { value: "page-break", text: "Page Break", children: [
6744
7658
  /* @__PURE__ */ jsx(DocumentPageBreakRegular, { style: optionIconStyle }),
6745
7659
  "Page break"
@@ -6765,10 +7679,30 @@ var ToolBarPlugins = (props) => {
6765
7679
  // );
6766
7680
  case "Align": {
6767
7681
  const ALIGN_OPTIONS = [
6768
- { value: "left", label: "Left Align", icon: /* @__PURE__ */ jsx(TextAlignLeftFilled, { style: optionIconStyle }), action: "leftAlign" /* LeftAlign */ },
6769
- { value: "center", label: "Center Align", icon: /* @__PURE__ */ jsx(TextAlignCenterFilled, { style: optionIconStyle }), action: "centerAlign" /* CenterAlign */ },
6770
- { value: "right", label: "Right Align", icon: /* @__PURE__ */ jsx(TextAlignRightFilled, { style: optionIconStyle }), action: "rightAlign" /* RightAlign */ },
6771
- { value: "justify", label: "Justify Align", icon: /* @__PURE__ */ jsx(TextAlignJustifyFilled, { style: optionIconStyle }), action: "justifyAlign" /* JustifyAlign */ }
7682
+ {
7683
+ value: "left",
7684
+ label: "Left Align",
7685
+ icon: /* @__PURE__ */ jsx(TextAlignLeftFilled, { style: optionIconStyle }),
7686
+ action: "leftAlign" /* LeftAlign */
7687
+ },
7688
+ {
7689
+ value: "center",
7690
+ label: "Center Align",
7691
+ icon: /* @__PURE__ */ jsx(TextAlignCenterFilled, { style: optionIconStyle }),
7692
+ action: "centerAlign" /* CenterAlign */
7693
+ },
7694
+ {
7695
+ value: "right",
7696
+ label: "Right Align",
7697
+ icon: /* @__PURE__ */ jsx(TextAlignRightFilled, { style: optionIconStyle }),
7698
+ action: "rightAlign" /* RightAlign */
7699
+ },
7700
+ {
7701
+ value: "justify",
7702
+ label: "Justify Align",
7703
+ icon: /* @__PURE__ */ jsx(TextAlignJustifyFilled, { style: optionIconStyle }),
7704
+ action: "justifyAlign" /* JustifyAlign */
7705
+ }
6772
7706
  ];
6773
7707
  const alignLabel = ALIGN_OPTIONS.find((o) => o.value === alignment)?.label ?? "Left Align";
6774
7708
  return /* @__PURE__ */ jsx(
@@ -6984,28 +7918,31 @@ function BrowserSpellCheckPlugin({ enabled }) {
6984
7918
  }, [editor, enabled]);
6985
7919
  return null;
6986
7920
  }
6987
- function WordCountPlugin({ onCountChange }) {
6988
- const [editor] = useLexicalComposerContext();
6989
- useEffect(() => {
6990
- return editor.registerUpdateListener(({ editorState }) => {
6991
- editorState.read(() => {
6992
- const text = $getRoot().getTextContent();
6993
- const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
6994
- onCountChange(words);
6995
- });
6996
- });
6997
- }, [editor, onCountChange]);
6998
- return null;
6999
- }
7000
- function CharCountPlugin({ onCountChange }) {
7921
+ function ContentMetricsPlugin({
7922
+ onMetricsChange
7923
+ }) {
7001
7924
  const [editor] = useLexicalComposerContext();
7925
+ const prevRef = useRef({ words: 0, chars: 0, images: 0, links: 0, tables: 0 });
7002
7926
  useEffect(() => {
7003
- return editor.registerUpdateListener(({ editorState }) => {
7004
- editorState.read(() => {
7005
- onCountChange($getRoot().getTextContent().length);
7006
- });
7927
+ return editor.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
7928
+ if (dirtyElements.size === 0 && dirtyLeaves.size === 0) return;
7929
+ const root = editor.getRootElement();
7930
+ if (!root) return;
7931
+ const text = root.innerText ?? "";
7932
+ const trimmed = text.trim();
7933
+ const words = trimmed === "" ? 0 : trimmed.split(/\s+/).length;
7934
+ const chars = trimmed.length;
7935
+ const images = root.querySelectorAll("img").length;
7936
+ const links = root.querySelectorAll("a[href]").length;
7937
+ const tables = root.querySelectorAll("table").length;
7938
+ const prev = prevRef.current;
7939
+ if (prev.words !== words || prev.chars !== chars || prev.images !== images || prev.links !== links || prev.tables !== tables) {
7940
+ const next = { words, chars, images, links, tables };
7941
+ prevRef.current = next;
7942
+ onMetricsChange(next);
7943
+ }
7007
7944
  });
7008
- }, [editor, onCountChange]);
7945
+ }, [editor, onMetricsChange]);
7009
7946
  return null;
7010
7947
  }
7011
7948
  function FocusEventsPlugin({
@@ -7026,7 +7963,8 @@ function FocusEventsPlugin({
7026
7963
  const next = e.relatedTarget;
7027
7964
  const container = containerRef.current;
7028
7965
  const stillInside = !!next && (container ? container.contains(next) : root.contains(next));
7029
- if (stillInside) return;
7966
+ const isEditorPortal = !!next?.closest?.("[data-lexical-editor-portal]");
7967
+ if (stillInside || isEditorPortal) return;
7030
7968
  editor.update(() => {
7031
7969
  $setSelection(null);
7032
7970
  });
@@ -7136,6 +8074,16 @@ function _makeQueryFn(fn) {
7136
8074
  };
7137
8075
  };
7138
8076
  }
8077
+ function EditorReadyPlugin({ onReady }) {
8078
+ const [editor] = useLexicalComposerContext();
8079
+ const calledRef = useRef(false);
8080
+ useEffect(() => {
8081
+ if (!onReady || calledRef.current) return;
8082
+ calledRef.current = true;
8083
+ onReady(editor);
8084
+ }, [editor, onReady]);
8085
+ return null;
8086
+ }
7139
8087
  var ContentEditorComponent = forwardRef(
7140
8088
  (props, ref) => {
7141
8089
  const isReadOnly = !!props.readOnly;
@@ -7151,13 +8099,17 @@ var ContentEditorComponent = forwardRef(
7151
8099
  );
7152
8100
  const [floatingAnchorElem, setFloatingAnchorElem] = useState(null);
7153
8101
  const [isLinkEditMode, setIsLinkEditMode] = useState(false);
7154
- const [wordCount, setWordCount] = useState(0);
7155
- const handleWordCount = useCallback((count) => setWordCount(count), []);
7156
- const [charCount, setCharCount] = useState(0);
7157
- const handleCharCount = useCallback((count) => setCharCount(count), []);
7158
- const [refErrors, setRefErrors] = useState([]);
8102
+ const [metrics, setMetrics] = useState({
8103
+ words: 0,
8104
+ chars: 0,
8105
+ images: 0,
8106
+ links: 0,
8107
+ tables: 0
8108
+ });
8109
+ const handleMetrics = useCallback((m) => setMetrics(m), []);
7159
8110
  const [pageSetup, setPageSetup] = useState(DEFAULT_PAGE_SETUP);
7160
8111
  const pageCanvas = resolvePageCanvasMetrics(pageSetup);
8112
+ const wordCount = metrics.words;
7161
8113
  const contentEditableDomRef = useRef(null);
7162
8114
  const previousOverLimitRef = useRef(false);
7163
8115
  const focusedRef = useRef(false);
@@ -7168,32 +8120,37 @@ var ContentEditorComponent = forwardRef(
7168
8120
  const onAnchorRef = (elem) => {
7169
8121
  if (elem) setFloatingAnchorElem(elem);
7170
8122
  };
7171
- const initialConfig = {
7172
- namespace: props.namespace,
7173
- theme,
7174
- onError: () => {
7175
- },
7176
- nodes: [
7177
- HeadingNode,
7178
- QuoteNode,
7179
- CodeHighlightNode,
7180
- CodeNode,
7181
- ListNode,
7182
- ListItemNode,
7183
- LinkNode,
7184
- AutoLinkNode,
7185
- TableNode,
7186
- TableRowNode,
7187
- TableCellNode,
7188
- ImageNode,
7189
- InlineImageNode,
7190
- YouTubeNode,
7191
- PageBreakNode,
7192
- AutocompleteNode,
7193
- SpellErrorNode,
7194
- HtmlBlockNode
7195
- ]
7196
- };
8123
+ const initialConfig = React9__default.useMemo(() => {
8124
+ const config = {
8125
+ namespace: props.namespace ?? "",
8126
+ theme,
8127
+ onError: () => {
8128
+ },
8129
+ nodes: [
8130
+ HeadingNode,
8131
+ QuoteNode,
8132
+ CodeHighlightNode,
8133
+ CodeNode,
8134
+ ListNode,
8135
+ ListItemNode,
8136
+ AlphaListNode,
8137
+ LinkNode,
8138
+ AutoLinkNode,
8139
+ TableNode,
8140
+ TableRowNode,
8141
+ TableCellNode,
8142
+ ImageNode,
8143
+ InlineImageNode,
8144
+ YouTubeNode,
8145
+ PageBreakNode,
8146
+ AutocompleteNode,
8147
+ SpellErrorNode,
8148
+ HtmlBlockNode
8149
+ ]
8150
+ };
8151
+ props.onBeforeInitialize?.(config);
8152
+ return config;
8153
+ }, []);
7197
8154
  const EditorStyles = mergeStyleSets({
7198
8155
  editorPlaceholder: {
7199
8156
  color: "var(--colorNeutralForeground3, grey)",
@@ -7235,41 +8192,7 @@ var ContentEditorComponent = forwardRef(
7235
8192
  e.stopPropagation();
7236
8193
  }
7237
8194
  };
7238
- const [touched, setTouched] = useState(false);
7239
8195
  const isOverLimit = props.wordLimit !== void 0 && wordCount > props.wordLimit;
7240
- const internalErrors = [];
7241
- if (isOverLimit) {
7242
- const m = props.errorMessages?.wordLimitExceeded;
7243
- internalErrors.push(
7244
- typeof m === "function" ? m(wordCount, props.wordLimit) : m ?? `Word limit exceeded (${wordCount} / ${props.wordLimit} words used)`
7245
- );
7246
- }
7247
- if (props.required && touched && wordCount === 0) {
7248
- internalErrors.push(
7249
- props.errorMessages?.required ?? "This field is required"
7250
- );
7251
- }
7252
- if (props.minWords !== void 0 && touched && wordCount < props.minWords) {
7253
- const m = props.errorMessages?.minWords;
7254
- internalErrors.push(
7255
- typeof m === "function" ? m(wordCount, props.minWords) : m ?? `Minimum ${props.minWords} words required (${wordCount} entered)`
7256
- );
7257
- }
7258
- if (props.maxChars !== void 0 && charCount > props.maxChars) {
7259
- const m = props.errorMessages?.maxCharsExceeded;
7260
- internalErrors.push(
7261
- typeof m === "function" ? m(charCount, props.maxChars) : m ?? `Character limit exceeded (${charCount} / ${props.maxChars} characters used)`
7262
- );
7263
- }
7264
- if (props.minChars !== void 0 && touched && charCount < props.minChars && charCount > 0) {
7265
- const m = props.errorMessages?.minCharsRequired;
7266
- internalErrors.push(
7267
- typeof m === "function" ? m(charCount, props.minChars) : m ?? `Minimum ${props.minChars} characters required (${charCount} entered)`
7268
- );
7269
- }
7270
- const allErrors = [...internalErrors, ...props.errors ?? [], ...refErrors];
7271
- const hasErrors = allErrors.length > 0;
7272
- const hasRedBorder = hasErrors;
7273
8196
  useEffect(() => {
7274
8197
  if (props.wordLimit === void 0 || !props.onWordLimitExceeded) return;
7275
8198
  const wasOverLimit = previousOverLimitRef.current;
@@ -7282,214 +8205,327 @@ var ContentEditorComponent = forwardRef(
7282
8205
  previousOverLimitRef.current = isOverLimit;
7283
8206
  }
7284
8207
  }, [isOverLimit, wordCount, props.wordLimit, props.onWordLimitExceeded]);
7285
- return /* @__PURE__ */ jsx(FluentProvider, { theme: webLightTheme, style: { height: "100%" }, children: /* @__PURE__ */ jsx(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx("div", { ref: containerRef, style: { height: "100%" }, children: /* @__PURE__ */ jsxs(
7286
- Stack,
7287
- {
7288
- style: {
7289
- zIndex: 1e3,
7290
- background: "#fff",
7291
- borderRadius: "2px",
7292
- width: props.width ?? "100%",
7293
- height: props.height ?? "100%",
7294
- margin: props.margin ?? "5px auto",
7295
- border: `1px solid ${hasRedBorder ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
7296
- transition: "border-color 0.2s",
7297
- display: "flex",
7298
- flexDirection: "column"
7299
- },
7300
- children: [
7301
- /* @__PURE__ */ jsx(
7302
- "div",
7303
- {
7304
- style: {
7305
- pointerEvents: isReadOnly ? "none" : "auto",
7306
- position: "sticky",
7307
- opacity: isReadOnly ? 0.85 : 1
7308
- },
7309
- children: /* @__PURE__ */ jsx(
7310
- ToolBarPlugins,
7311
- {
7312
- level: props.level ?? "basic" /* Basic */,
7313
- readOnly: props.readOnly,
7314
- pageSetup,
7315
- onPageSetupChange: setPageSetup
7316
- }
7317
- )
7318
- }
7319
- ),
7320
- /* @__PURE__ */ jsxs(
7321
- "div",
7322
- {
7323
- style: {
7324
- position: "relative",
7325
- flexGrow: 1,
7326
- padding: "15px 0px",
7327
- overflowY: "scroll",
7328
- overflowX: "auto",
7329
- minWidth: 0,
7330
- background: pageCanvas.widthPx !== void 0 ? "#eef0f2" : void 0
7331
- },
7332
- onClickCapture: handleReadOnlyClickCapture,
7333
- children: [
7334
- /* @__PURE__ */ jsx(
7335
- RichTextPlugin,
8208
+ const validationErrors = React9__default.useMemo(() => {
8209
+ const errors = [];
8210
+ const custom = props.validationMessages ?? {};
8211
+ const { words, chars, images, links, tables } = metrics;
8212
+ const msg = (key, ...args) => {
8213
+ const override = custom[key];
8214
+ if (override !== void 0) {
8215
+ return typeof override === "function" ? override(...args) : override;
8216
+ }
8217
+ const def = DEFAULT_VALIDATION_MESSAGES[key];
8218
+ return typeof def === "function" ? def(...args) : def;
8219
+ };
8220
+ const requiredFired = props.required && words === 0;
8221
+ if (requiredFired) {
8222
+ errors.push({ type: "required", message: msg("required") });
8223
+ } else if (props.minWords !== void 0 && words < props.minWords) {
8224
+ errors.push({ type: "minWords", message: msg("minWords", words, props.minWords) });
8225
+ }
8226
+ const effectiveMaxWords = props.maxWords ?? props.wordLimit;
8227
+ if (effectiveMaxWords !== void 0 && words > effectiveMaxWords) {
8228
+ errors.push({ type: "maxWords", message: msg("maxWords", words, effectiveMaxWords) });
8229
+ }
8230
+ if (!requiredFired && props.minChars !== void 0 && chars < props.minChars) {
8231
+ errors.push({ type: "minChars", message: msg("minChars", chars, props.minChars) });
8232
+ }
8233
+ if (props.maxChars !== void 0 && chars > props.maxChars) {
8234
+ errors.push({ type: "maxChars", message: msg("maxChars", chars, props.maxChars) });
8235
+ }
8236
+ if (props.noImages && images > 0) {
8237
+ errors.push({ type: "noImages", message: msg("noImages") });
8238
+ } else if (props.maxImages !== void 0 && images > props.maxImages) {
8239
+ errors.push({ type: "maxImages", message: msg("maxImages", images, props.maxImages) });
8240
+ }
8241
+ if (props.noLinks && links > 0) {
8242
+ errors.push({ type: "noLinks", message: msg("noLinks") });
8243
+ } else if (props.maxLinks !== void 0 && links > props.maxLinks) {
8244
+ errors.push({ type: "maxLinks", message: msg("maxLinks", links, props.maxLinks) });
8245
+ }
8246
+ if (props.noTables && tables > 0) {
8247
+ errors.push({ type: "noTables", message: msg("noTables") });
8248
+ }
8249
+ return errors;
8250
+ }, [
8251
+ metrics,
8252
+ props.required,
8253
+ props.minWords,
8254
+ props.maxWords,
8255
+ props.wordLimit,
8256
+ props.minChars,
8257
+ props.maxChars,
8258
+ props.noImages,
8259
+ props.maxImages,
8260
+ props.noLinks,
8261
+ props.maxLinks,
8262
+ props.noTables,
8263
+ props.validationMessages
8264
+ ]);
8265
+ const previousErrorTypesRef = useRef("");
8266
+ useEffect(() => {
8267
+ if (!props.onValidationChange) return;
8268
+ const key = validationErrors.map((e) => e.type).join(",");
8269
+ if (key !== previousErrorTypesRef.current) {
8270
+ previousErrorTypesRef.current = key;
8271
+ props.onValidationChange(validationErrors);
8272
+ }
8273
+ }, [validationErrors, props.onValidationChange]);
8274
+ const trimmedErrorMessage = props.errorMessage?.trim() || "";
8275
+ const hasValidationError = validationErrors.length > 0 || !!trimmedErrorMessage;
8276
+ return /* @__PURE__ */ jsx(FluentProvider, { theme: webLightTheme, style: { height: "100%" }, children: /* @__PURE__ */ jsxs(LexicalComposer, { initialConfig, children: [
8277
+ /* @__PURE__ */ jsx("div", { ref: containerRef, className: "lexical-rich-editor-root", style: { height: "100%" }, children: /* @__PURE__ */ jsxs(
8278
+ Stack,
8279
+ {
8280
+ style: {
8281
+ zIndex: 1e3,
8282
+ background: "#fff",
8283
+ borderRadius: "2px",
8284
+ width: props.width ?? "100%",
8285
+ height: props.height ?? "100%",
8286
+ margin: props.margin ?? "5px auto",
8287
+ border: `1px solid ${isOverLimit || hasValidationError ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
8288
+ transition: "border-color 0.2s",
8289
+ display: "flex",
8290
+ flexDirection: "column"
8291
+ },
8292
+ children: [
8293
+ /* @__PURE__ */ jsx(
8294
+ "div",
8295
+ {
8296
+ className: "editor-toolbar-root",
8297
+ style: {
8298
+ pointerEvents: isReadOnly ? "none" : "auto",
8299
+ position: "sticky",
8300
+ opacity: isReadOnly ? 0.85 : 1
8301
+ },
8302
+ children: /* @__PURE__ */ jsx(
8303
+ ToolBarPlugins,
7336
8304
  {
7337
- ErrorBoundary: LexicalErrorBoundary,
7338
- contentEditable: /* @__PURE__ */ jsx(
7339
- "div",
7340
- {
7341
- className: "editor",
7342
- style: { height: "100%", position: "relative" },
7343
- ref: onAnchorRef,
7344
- children: /* @__PURE__ */ jsx(
7345
- ContentEditable,
8305
+ level: props.level ?? "basic" /* Basic */,
8306
+ customToolbar: props.customToolbar,
8307
+ readOnly: props.readOnly,
8308
+ pageSetup,
8309
+ onPageSetupChange: setPageSetup,
8310
+ maxImageSizeMB: props.maxImageSizeMB,
8311
+ validationMessages: props.validationMessages,
8312
+ setIsLinkEditMode
8313
+ }
8314
+ )
8315
+ }
8316
+ ),
8317
+ /* @__PURE__ */ jsxs(
8318
+ "div",
8319
+ {
8320
+ style: {
8321
+ position: "relative",
8322
+ flexGrow: 1,
8323
+ padding: "15px 0px",
8324
+ overflowY: "scroll",
8325
+ overflowX: "auto",
8326
+ minWidth: 0,
8327
+ background: pageCanvas.widthPx !== void 0 ? "#eef0f2" : void 0
8328
+ },
8329
+ onClickCapture: handleReadOnlyClickCapture,
8330
+ children: [
8331
+ /* @__PURE__ */ jsx(
8332
+ RichTextPlugin,
8333
+ {
8334
+ ErrorBoundary: LexicalErrorBoundary,
8335
+ contentEditable: /* @__PURE__ */ jsx(
8336
+ "div",
8337
+ {
8338
+ className: "editor",
8339
+ style: { height: "100%", position: "relative" },
8340
+ ref: onAnchorRef,
8341
+ children: /* @__PURE__ */ jsx(
8342
+ ContentEditable,
8343
+ {
8344
+ ref: contentEditableDomRef,
8345
+ className: css(EditorStyles.contentEditor),
8346
+ style: {
8347
+ paddingTop: props.level !== "none" /* None */ ? 0 : 10,
8348
+ paddingLeft: pageCanvas.paddingPx,
8349
+ paddingRight: pageCanvas.paddingPx,
8350
+ maxWidth: pageCanvas.widthPx,
8351
+ marginLeft: pageCanvas.widthPx !== void 0 ? "auto" : void 0,
8352
+ marginRight: pageCanvas.widthPx !== void 0 ? "auto" : void 0,
8353
+ 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
8354
+ },
8355
+ spellCheck: !resolvedSpellCheck,
8356
+ autoCorrect: resolvedSpellCheck ? "off" : void 0,
8357
+ autoCapitalize: resolvedSpellCheck ? "off" : void 0
8358
+ }
8359
+ )
8360
+ }
8361
+ ),
8362
+ placeholder: /* @__PURE__ */ jsx(Stack, { className: css(EditorStyles.editorPlaceholder), children: props.placeholder })
8363
+ }
8364
+ ),
8365
+ (props.wordLimit !== void 0 || props.maxWords !== void 0 || props.minWords !== void 0 || props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsxs(
8366
+ "div",
8367
+ {
8368
+ style: {
8369
+ position: "sticky",
8370
+ bottom: 0,
8371
+ display: "flex",
8372
+ justifyContent: "flex-end",
8373
+ gap: 10,
8374
+ paddingRight: 14,
8375
+ pointerEvents: "none",
8376
+ userSelect: "none"
8377
+ },
8378
+ children: [
8379
+ (props.wordLimit !== void 0 || props.maxWords !== void 0 || props.minWords !== void 0) && /* @__PURE__ */ jsxs(
8380
+ "span",
8381
+ {
8382
+ style: {
8383
+ fontSize: "11px",
8384
+ color: hasValidationError ? "#c4272c" : "var(--colorNeutralForeground3, #aaa)",
8385
+ fontWeight: hasValidationError ? 600 : 400,
8386
+ transition: "color 0.2s, font-weight 0.2s"
8387
+ },
8388
+ children: [
8389
+ wordCount,
8390
+ (props.maxWords ?? props.wordLimit) !== void 0 && ` / ${props.maxWords ?? props.wordLimit}`,
8391
+ props.minWords !== void 0 && props.maxWords === void 0 && props.wordLimit === void 0 && ` (min ${props.minWords})`,
8392
+ " words"
8393
+ ]
8394
+ }
8395
+ ),
8396
+ (props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsxs(
8397
+ "span",
7346
8398
  {
7347
- ref: contentEditableDomRef,
7348
- className: css(EditorStyles.contentEditor),
7349
8399
  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
8400
+ fontSize: "11px",
8401
+ color: hasValidationError ? "#c4272c" : "var(--colorNeutralForeground3, #aaa)",
8402
+ fontWeight: hasValidationError ? 600 : 400,
8403
+ transition: "color 0.2s, font-weight 0.2s"
7357
8404
  },
7358
- spellCheck: !resolvedSpellCheck,
7359
- autoCorrect: resolvedSpellCheck ? "off" : void 0,
7360
- autoCapitalize: resolvedSpellCheck ? "off" : void 0
8405
+ children: [
8406
+ metrics.chars,
8407
+ props.maxChars !== void 0 && ` / ${props.maxChars}`,
8408
+ props.minChars !== void 0 && props.maxChars === void 0 && ` (min ${props.minChars})`,
8409
+ " chars"
8410
+ ]
7361
8411
  }
7362
8412
  )
7363
- }
7364
- ),
7365
- placeholder: /* @__PURE__ */ jsx(Stack, { className: css(EditorStyles.editorPlaceholder), children: props.placeholder })
7366
- }
7367
- ),
7368
- props.wordLimit !== void 0 && /* @__PURE__ */ jsx(
7369
- "div",
7370
- {
7371
- style: {
7372
- position: "sticky",
7373
- bottom: 0,
7374
- display: "flex",
7375
- justifyContent: "flex-end",
7376
- paddingRight: 14,
7377
- pointerEvents: "none",
7378
- userSelect: "none"
7379
- },
7380
- children: /* @__PURE__ */ jsxs(
7381
- "span",
7382
- {
7383
- style: {
7384
- fontSize: "11px",
7385
- color: isOverLimit ? "#c4272c" : "var(--colorNeutralForeground3, #aaa)",
7386
- fontWeight: isOverLimit ? 600 : 400,
7387
- transition: "color 0.2s, font-weight 0.2s"
7388
- },
7389
- children: [
7390
- wordCount,
7391
- " / ",
7392
- props.wordLimit,
7393
- " words"
7394
- ]
7395
- }
7396
- )
7397
- }
7398
- )
7399
- ]
7400
- }
7401
- ),
7402
- hasErrors && /* @__PURE__ */ jsx(
7403
- "div",
7404
- {
7405
- style: {
7406
- borderTop: "1px solid #fbd5d5",
7407
- background: "#fff8f8",
7408
- padding: "6px 12px 8px",
7409
- display: "flex",
7410
- flexDirection: "column",
7411
- gap: 4
7412
- },
7413
- children: allErrors.map((err, i) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
7414
- /* @__PURE__ */ jsx(ErrorCircleRegular, { style: { fontSize: 14, color: "#c4272c", flexShrink: 0 } }),
7415
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#c4272c" }, children: err })
7416
- ] }, i))
7417
- }
7418
- ),
7419
- /* @__PURE__ */ jsx(ReadOnlyPlugin, { readonly: isReadOnly }),
7420
- /* @__PURE__ */ jsx(BrowserSpellCheckPlugin, { enabled: !resolvedSpellCheck }),
7421
- /* @__PURE__ */ jsx(
7422
- FocusEventsPlugin,
7423
- {
7424
- onFocus: props.onFocus,
7425
- onBlur: () => {
7426
- setTouched(true);
7427
- props.onBlur?.();
7428
- },
7429
- setFocused,
7430
- containerRef
7431
- }
7432
- ),
7433
- props.autoFocus && !isReadOnly && /* @__PURE__ */ jsx(AutoFocusPlugin, {}),
7434
- /* @__PURE__ */ jsx(HistoryPlugin, {}),
7435
- /* @__PURE__ */ jsx(ListPlugin, {}),
7436
- /* @__PURE__ */ jsx(LinkPlugin, { validateUrl }),
7437
- /* @__PURE__ */ jsx(AutoLinkPlugin, { matchers: MATCHERS }),
7438
- /* @__PURE__ */ jsx(TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true }),
7439
- !isReadOnly && /* @__PURE__ */ jsx(YoutubeDeletePlugin, {}),
7440
- !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsx(TableActionMenuPlugin, {}),
7441
- !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsx(TableCellResizerPlugin, { anchorElem: floatingAnchorElem }),
7442
- !isReadOnly && /* @__PURE__ */ jsx(
7443
- FloatingLinkEditorPlugin,
7444
- {
7445
- anchorElem: floatingAnchorElem,
7446
- isLinkEditMode,
7447
- setIsLinkEditMode
7448
- }
7449
- ),
7450
- !isReadOnly && /* @__PURE__ */ jsx(ImagePlugin_default, {}),
7451
- !isReadOnly && /* @__PURE__ */ jsx(InlineImage_default, {}),
7452
- !isReadOnly && /* @__PURE__ */ jsx(PageBreakPlugin, {}),
7453
- !!resolvedQuery && !isReadOnly && /* @__PURE__ */ jsx(
7454
- AutocompletePlugin,
7455
- {
7456
- useQuery: resolvedQuery,
7457
- isReadOnly,
7458
- onSuggestionShown: props.onSuggestionShown,
7459
- onSuggestionAccept: props.onSuggestionAccept,
7460
- idleMs: props.suggestIdleMs ?? 300,
7461
- minWords: 4,
7462
- prefixWindow: 300
7463
- }
7464
- ),
7465
- !!resolvedSpellCheck && !isReadOnly && /* @__PURE__ */ jsx(
7466
- SpellCheckPlugin,
7467
- {
7468
- useSpellCheck: resolvedSpellCheck,
7469
- onSpellCheckAccept: props.onSpellCheckAccept,
7470
- idleMs: props.spellCheckIdleMs ?? 1200,
7471
- enabled: props.spellCheckEnabled !== false
7472
- }
7473
- ),
7474
- !isReadOnly && props.showFloatingToolbar && /* @__PURE__ */ jsx(CharacterStylesPopupPlugin, {}),
7475
- /* @__PURE__ */ jsx(CustomOnChangePlugin, { value: props.value, onChange: props.onChange }),
7476
- (props.wordLimit !== void 0 || props.required || props.minWords !== void 0) && /* @__PURE__ */ jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7477
- (props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsx(CharCountPlugin, { onCountChange: handleCharCount }),
7478
- /* @__PURE__ */ jsx(
7479
- RefApiPlugin,
7480
- {
7481
- forwardedRef: ref,
7482
- contentEditableDomRef,
7483
- focusedRef,
7484
- setRefErrors
7485
- }
7486
- )
7487
- ]
7488
- }
7489
- ) }) }) });
8413
+ ]
8414
+ }
8415
+ )
8416
+ ]
8417
+ }
8418
+ ),
8419
+ /* @__PURE__ */ jsx(ReadOnlyPlugin, { readonly: isReadOnly }),
8420
+ /* @__PURE__ */ jsx(BrowserSpellCheckPlugin, { enabled: !resolvedSpellCheck }),
8421
+ /* @__PURE__ */ jsx(
8422
+ FocusEventsPlugin,
8423
+ {
8424
+ onFocus: props.onFocus,
8425
+ onBlur: props.onBlur,
8426
+ setFocused,
8427
+ containerRef
8428
+ }
8429
+ ),
8430
+ props.autoFocus && !isReadOnly && /* @__PURE__ */ jsx(AutoFocusPlugin, {}),
8431
+ /* @__PURE__ */ jsx(HistoryPlugin, {}),
8432
+ /* @__PURE__ */ jsx(ListPlugin, {}),
8433
+ /* @__PURE__ */ jsx(LinkPlugin, { validateUrl }),
8434
+ /* @__PURE__ */ jsx(AutoLinkPlugin, { matchers: MATCHERS }),
8435
+ /* @__PURE__ */ jsx(TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true }),
8436
+ !isReadOnly && /* @__PURE__ */ jsx(YoutubeDeletePlugin, {}),
8437
+ !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsx(TableActionMenuPlugin, {}),
8438
+ !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsx(TableCellResizerPlugin, { anchorElem: floatingAnchorElem }),
8439
+ !isReadOnly && /* @__PURE__ */ jsx(
8440
+ FloatingLinkEditorPlugin,
8441
+ {
8442
+ anchorElem: floatingAnchorElem,
8443
+ isLinkEditMode,
8444
+ setIsLinkEditMode
8445
+ }
8446
+ ),
8447
+ !isReadOnly && /* @__PURE__ */ jsx(ImagePlugin_default, {}),
8448
+ !isReadOnly && /* @__PURE__ */ jsx(InlineImage_default, {}),
8449
+ !isReadOnly && /* @__PURE__ */ jsx(PageBreakPlugin, {}),
8450
+ !!resolvedQuery && !isReadOnly && /* @__PURE__ */ jsx(
8451
+ AutocompletePlugin,
8452
+ {
8453
+ useQuery: resolvedQuery,
8454
+ isReadOnly,
8455
+ onSuggestionShown: props.onSuggestionShown,
8456
+ onSuggestionAccept: props.onSuggestionAccept,
8457
+ idleMs: props.suggestIdleMs ?? 300,
8458
+ minWords: 4,
8459
+ prefixWindow: 300
8460
+ }
8461
+ ),
8462
+ !!resolvedSpellCheck && !isReadOnly && /* @__PURE__ */ jsx(
8463
+ SpellCheckPlugin,
8464
+ {
8465
+ useSpellCheck: resolvedSpellCheck,
8466
+ onSpellCheckAccept: props.onSpellCheckAccept,
8467
+ idleMs: props.spellCheckIdleMs ?? 1200,
8468
+ enabled: props.spellCheckEnabled !== false
8469
+ }
8470
+ ),
8471
+ !isReadOnly && props.showFloatingToolbar && /* @__PURE__ */ jsx(CharacterStylesPopupPlugin, {}),
8472
+ /* @__PURE__ */ jsx(CustomOnChangePlugin, { value: props.value, onChange: props.onChange }),
8473
+ (props.wordLimit !== void 0 || props.required || props.minWords !== void 0 || props.maxWords !== void 0 || props.minChars !== void 0 || props.maxChars !== void 0 || props.noImages || props.maxImages !== void 0 || props.noLinks || props.maxLinks !== void 0 || props.noTables) && /* @__PURE__ */ jsx(ContentMetricsPlugin, { onMetricsChange: handleMetrics }),
8474
+ /* @__PURE__ */ jsx(
8475
+ RefApiPlugin,
8476
+ {
8477
+ forwardedRef: ref,
8478
+ contentEditableDomRef,
8479
+ focusedRef
8480
+ }
8481
+ ),
8482
+ (validationErrors.length > 0 || trimmedErrorMessage) && /* @__PURE__ */ jsxs("div", { style: { flexShrink: 0, padding: "6px 20px 8px" }, children: [
8483
+ validationErrors.map((err) => /* @__PURE__ */ jsxs(
8484
+ "div",
8485
+ {
8486
+ style: {
8487
+ display: "flex",
8488
+ alignItems: "flex-start",
8489
+ gap: 6,
8490
+ marginTop: 2,
8491
+ color: "#c4272c",
8492
+ fontSize: 12,
8493
+ lineHeight: "18px"
8494
+ },
8495
+ children: [
8496
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, flexShrink: 0 }, children: "\u26A0" }),
8497
+ /* @__PURE__ */ jsx("span", { children: err.message })
8498
+ ]
8499
+ },
8500
+ err.type
8501
+ )),
8502
+ trimmedErrorMessage && /* @__PURE__ */ jsxs(
8503
+ "div",
8504
+ {
8505
+ style: {
8506
+ display: "flex",
8507
+ alignItems: "flex-start",
8508
+ gap: 6,
8509
+ marginTop: 2,
8510
+ color: "#c4272c",
8511
+ fontSize: 12,
8512
+ lineHeight: "18px"
8513
+ },
8514
+ children: [
8515
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, flexShrink: 0 }, children: "\u26A0" }),
8516
+ /* @__PURE__ */ jsx("span", { children: trimmedErrorMessage })
8517
+ ]
8518
+ }
8519
+ )
8520
+ ] })
8521
+ ]
8522
+ }
8523
+ ) }),
8524
+ /* @__PURE__ */ jsx(EditorReadyPlugin, { onReady: props.onReady })
8525
+ ] }) });
7490
8526
  }
7491
8527
  );
7492
8528
 
7493
- export { ContentEditorComponent, ContentEditorLevel };
8529
+ export { ContentEditorComponent, ContentEditorLevel, DEFAULT_VALIDATION_MESSAGES };
7494
8530
  //# sourceMappingURL=index.mjs.map
7495
8531
  //# sourceMappingURL=index.mjs.map