@tarviks/lexical-rich-editor 1.2.1 → 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, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, Field, Input, DialogActions } 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, AddRegular, TableAddRegular, ImageAddRegular, ImageEditRegular, VideoClipRegular, TextUnderlineFilled, TextItalicFilled, TextBold24Regular, DocumentRegular, TextAlignLeftFilled, TextAlignCenterFilled, TextAlignRightFilled, TextAlignJustifyFilled, AttachFilled, LinkAddRegular, TextColorRegular, PaintBucket16Filled, Dismiss16Regular, Checkmark16Regular, Edit16Regular, Delete16Regular, CodeFilled, LinkFilled } 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,36 +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
- onResizeStart,
619
- onResizeEnd,
620
- captionsEnabled: !isLoadError && captionsEnabled
621
- }
622
- )
623
- ] }) });
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
+ ) });
624
620
  };
625
621
  ImageComponent_default = ImageComponent;
626
622
  }
@@ -765,9 +761,6 @@ var init_ImageNode = __esm({
765
761
  const writable = this.getWritable();
766
762
  writable.__width = width;
767
763
  writable.__height = height;
768
- if (typeof width === "number" && width > writable.__maxWidth) {
769
- writable.__maxWidth = width;
770
- }
771
764
  }
772
765
  setShowCaption(showCaption) {
773
766
  const writable = this.getWritable();
@@ -894,35 +887,13 @@ var init_InlineImageComponent = __esm({
894
887
  };
895
888
  InlineImageComponent = ({ src, altText, nodeKey, width, height, showCaption, caption, position }) => {
896
889
  const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
890
+ const [isResizing, setIsResizing] = useState(false);
897
891
  const [selection, setSelection] = useState(null);
898
892
  const activeEditorRef = useRef(null);
899
893
  const imageRef = useRef(null);
900
894
  const buttonRef = useRef(null);
901
895
  const [editor] = useLexicalComposerContext();
902
896
  const isEditable = useLexicalEditable();
903
- const [isResizing, setIsResizing] = useState(false);
904
- const onResizeEnd = (nextWidth, nextHeight) => {
905
- setTimeout(() => {
906
- setIsResizing(false);
907
- }, 200);
908
- editor.update(() => {
909
- const node = $getNodeByKey(nodeKey);
910
- if ($isInlineImageNode(node)) {
911
- node.setWidthAndHeight(nextWidth, nextHeight);
912
- }
913
- });
914
- };
915
- const onResizeStart = () => {
916
- setIsResizing(true);
917
- };
918
- const setShowCaption = (show) => {
919
- editor.update(() => {
920
- const node = $getNodeByKey(nodeKey);
921
- if ($isInlineImageNode(node)) {
922
- node.setShowCaption(show);
923
- }
924
- });
925
- };
926
897
  const $onDelete = useCallback(
927
898
  (payload) => {
928
899
  const deleteSelection = $getSelection();
@@ -978,6 +949,25 @@ var init_InlineImageComponent = __esm({
978
949
  },
979
950
  [caption, editor, setSelected]
980
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
+ );
981
971
  useEffect(() => {
982
972
  let isMounted = true;
983
973
  const unregister = mergeRegister(
@@ -996,51 +986,13 @@ var init_InlineImageComponent = __esm({
996
986
  ),
997
987
  editor.registerCommand(
998
988
  CLICK_COMMAND,
999
- (payload) => {
1000
- const event = payload;
1001
- if (isResizing) {
1002
- return true;
1003
- }
1004
- if (event.target === imageRef.current) {
1005
- if (event.shiftKey) {
1006
- setSelected(!isSelected);
1007
- } else {
1008
- clearSelection();
1009
- setSelected(true);
1010
- }
1011
- return true;
1012
- }
1013
- return false;
1014
- },
1015
- COMMAND_PRIORITY_LOW
1016
- ),
1017
- editor.registerCommand(
1018
- DRAGSTART_COMMAND,
1019
- (event) => {
1020
- if (event.target === imageRef.current) {
1021
- event.preventDefault();
1022
- return true;
1023
- }
1024
- return false;
1025
- },
1026
- COMMAND_PRIORITY_LOW
1027
- ),
1028
- editor.registerCommand(
1029
- KEY_DELETE_COMMAND,
1030
- $onDelete,
1031
- COMMAND_PRIORITY_LOW
1032
- ),
1033
- editor.registerCommand(
1034
- KEY_BACKSPACE_COMMAND,
1035
- $onDelete,
989
+ onClick,
1036
990
  COMMAND_PRIORITY_LOW
1037
991
  ),
992
+ editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW),
993
+ editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW),
1038
994
  editor.registerCommand(KEY_ENTER_COMMAND, $onEnter, COMMAND_PRIORITY_LOW),
1039
- editor.registerCommand(
1040
- KEY_ESCAPE_COMMAND,
1041
- $onEscape,
1042
- COMMAND_PRIORITY_LOW
1043
- )
995
+ editor.registerCommand(KEY_ESCAPE_COMMAND, $onEscape, COMMAND_PRIORITY_LOW)
1044
996
  );
1045
997
  return () => {
1046
998
  isMounted = false;
@@ -1055,12 +1007,27 @@ var init_InlineImageComponent = __esm({
1055
1007
  $onDelete,
1056
1008
  $onEnter,
1057
1009
  $onEscape,
1010
+ onClick,
1058
1011
  setSelected
1059
1012
  ]);
1060
- const draggable = isSelected && $isNodeSelection(selection) && !isResizing;
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;
1061
1028
  const isFocused = (isSelected || isResizing) && isEditable;
1062
- return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs(Fragment, { children: [
1063
- /* @__PURE__ */ jsx("span", { draggable, children: /* @__PURE__ */ jsx(
1029
+ return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs("span", { draggable, style: { position: "relative", display: "inline-block" }, children: [
1030
+ /* @__PURE__ */ jsx(
1064
1031
  LazyImage2,
1065
1032
  {
1066
1033
  className: isFocused ? `focused ${$isNodeSelection(selection) ? "draggable" : ""}` : null,
@@ -1071,18 +1038,19 @@ var init_InlineImageComponent = __esm({
1071
1038
  height,
1072
1039
  position
1073
1040
  }
1074
- ) }),
1075
- isFocused && $isNodeSelection(selection) && /* @__PURE__ */ jsx(
1041
+ ),
1042
+ $isNodeSelection(selection) && isFocused && /* @__PURE__ */ jsx(
1076
1043
  ImageResizer_default,
1077
1044
  {
1078
- showCaption,
1079
- setShowCaption,
1045
+ showCaption: false,
1046
+ setShowCaption: () => {
1047
+ },
1048
+ captionsEnabled: false,
1080
1049
  editor,
1081
1050
  buttonRef,
1082
1051
  imageRef,
1083
1052
  onResizeStart,
1084
- onResizeEnd,
1085
- captionsEnabled: false
1053
+ onResizeEnd
1086
1054
  }
1087
1055
  )
1088
1056
  ] }) });
@@ -1273,6 +1241,19 @@ var ContentEditorLevel = /* @__PURE__ */ ((ContentEditorLevel2) => {
1273
1241
  ContentEditorLevel2["Pro"] = "pro";
1274
1242
  return ContentEditorLevel2;
1275
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
+ };
1276
1257
 
1277
1258
  // src/Types/PageSetup.ts
1278
1259
  var DEFAULT_PAGE_SETUP = {
@@ -1313,6 +1294,106 @@ function resolvePageCanvasMetrics(value) {
1313
1294
  paddingPx: Math.round(margin.valueIn * CSS_PX_PER_INCH)
1314
1295
  };
1315
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
+ }
1316
1397
  var AutocompleteNode = class _AutocompleteNode extends TextNode {
1317
1398
  static getType() {
1318
1399
  return "autocomplete";
@@ -1323,6 +1404,13 @@ var AutocompleteNode = class _AutocompleteNode extends TextNode {
1323
1404
  static importJSON(serializedNode) {
1324
1405
  return new _AutocompleteNode(serializedNode.text, serializedNode.uuid);
1325
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
+ }
1326
1414
  exportJSON() {
1327
1415
  return { ...super.exportJSON(), uuid: this.__uuid };
1328
1416
  }
@@ -1637,13 +1725,20 @@ function VideoResizer({
1637
1725
  const dy = ev.clientY - rs.startY;
1638
1726
  let newW = rs.startW;
1639
1727
  let newH = rs.startH;
1640
- if (dir === "se") {
1728
+ if (dir === "se" || dir === "ne") {
1641
1729
  newW = Math.max(MIN_WIDTH, rs.startW + dx);
1642
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;
1643
1734
  } else if (dir === "e") {
1644
1735
  newW = Math.max(MIN_WIDTH, rs.startW + dx);
1736
+ } else if (dir === "w") {
1737
+ newW = Math.max(MIN_WIDTH, rs.startW - dx);
1645
1738
  } else if (dir === "s") {
1646
1739
  newH = Math.max(MIN_HEIGHT, rs.startH + dy);
1740
+ } else if (dir === "n") {
1741
+ newH = Math.max(MIN_HEIGHT, rs.startH - dy);
1647
1742
  }
1648
1743
  container.style.width = `${newW}px`;
1649
1744
  container.style.height = `${newH}px`;
@@ -1663,13 +1758,23 @@ function VideoResizer({
1663
1758
  /* @__PURE__ */ jsx(
1664
1759
  "div",
1665
1760
  {
1666
- style: {
1667
- ...handleBase,
1668
- top: "50%",
1669
- right: -5,
1670
- transform: "translateY(-50%)",
1671
- cursor: "ew-resize"
1672
- },
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" },
1673
1778
  onPointerDown: (e) => startResize(e, "e"),
1674
1779
  title: "Resize width"
1675
1780
  }
@@ -1677,13 +1782,15 @@ function VideoResizer({
1677
1782
  /* @__PURE__ */ jsx(
1678
1783
  "div",
1679
1784
  {
1680
- style: {
1681
- ...handleBase,
1682
- bottom: -5,
1683
- left: "50%",
1684
- transform: "translateX(-50%)",
1685
- cursor: "ns-resize"
1686
- },
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" },
1687
1794
  onPointerDown: (e) => startResize(e, "s"),
1688
1795
  title: "Resize height"
1689
1796
  }
@@ -1691,15 +1798,30 @@ function VideoResizer({
1691
1798
  /* @__PURE__ */ jsx(
1692
1799
  "div",
1693
1800
  {
1694
- style: { ...handleBase, bottom: -5, right: -5, cursor: "se-resize" },
1695
- 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"),
1696
1819
  title: "Resize (proportional)"
1697
1820
  }
1698
1821
  )
1699
1822
  ] });
1700
1823
  }
1701
1824
  function YouTubeComponent({
1702
- className,
1703
1825
  format,
1704
1826
  nodeKey,
1705
1827
  videoID,
@@ -1707,9 +1829,48 @@ function YouTubeComponent({
1707
1829
  height,
1708
1830
  editor
1709
1831
  }) {
1832
+ const wrapperRef = React9.useRef(null);
1710
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);
1711
1837
  const [isHovered, setIsHovered] = React9.useState(false);
1712
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]);
1713
1874
  const handleDelete = (e) => {
1714
1875
  e.preventDefault();
1715
1876
  e.stopPropagation();
@@ -1718,76 +1879,177 @@ function YouTubeComponent({
1718
1879
  node?.remove();
1719
1880
  });
1720
1881
  };
1882
+ const handleResizeStart = () => {
1883
+ isResizingRef.current = true;
1884
+ setIsResizing(true);
1885
+ if (iframeRef.current) iframeRef.current.style.pointerEvents = "none";
1886
+ };
1721
1887
  const handleResizeEnd = (w, h) => {
1888
+ isResizingRef.current = false;
1722
1889
  setIsResizing(false);
1890
+ if (iframeRef.current) iframeRef.current.style.pointerEvents = "";
1891
+ setIsHovered(true);
1723
1892
  editor.update(() => {
1724
1893
  const node = $getNodeByKey(nodeKey);
1725
- if ($isYouTubeNode(node)) {
1726
- node.setSize(Math.round(w), Math.round(h));
1727
- }
1894
+ if ($isYouTubeNode(node)) node.setSize(Math.round(w), Math.round(h));
1728
1895
  });
1729
1896
  };
1730
- 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(
1731
1917
  "div",
1732
1918
  {
1733
1919
  ref: containerRef,
1734
- 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
+ },
1735
1929
  onMouseEnter: () => setIsHovered(true),
1736
1930
  onMouseLeave: () => {
1737
- if (!isResizing) setIsHovered(false);
1931
+ if (!isResizingRef.current) setIsHovered(false);
1738
1932
  },
1739
1933
  children: [
1740
- /* @__PURE__ */ jsx(
1741
- "iframe",
1742
- {
1743
- width: "100%",
1744
- height: "100%",
1745
- src: `https://www.youtube.com/embed/${videoID}`,
1746
- allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
1747
- allowFullScreen: true,
1748
- title: "YouTube video",
1749
- style: { display: "block", border: "none", pointerEvents: isResizing ? "none" : "auto" }
1750
- }
1751
- ),
1752
- isHovered && /* @__PURE__ */ jsxs(Fragment, { children: [
1753
- /* @__PURE__ */ jsx(
1754
- "button",
1755
- {
1756
- type: "button",
1757
- onClick: handleDelete,
1758
- title: "Remove video",
1759
- style: {
1760
- position: "absolute",
1761
- top: 8,
1762
- right: 8,
1763
- width: 28,
1764
- height: 28,
1765
- borderRadius: "50%",
1766
- background: "rgba(0,0,0,0.65)",
1767
- color: "#fff",
1768
- border: "none",
1769
- cursor: "pointer",
1770
- fontSize: 18,
1771
- lineHeight: 1,
1772
- padding: 0,
1773
- zIndex: 10,
1774
- display: "flex",
1775
- alignItems: "center",
1776
- justifyContent: "center"
1777
- },
1778
- children: "\xD7"
1779
- }
1780
- ),
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. */
1781
1940
  /* @__PURE__ */ jsx(
1782
- VideoResizer,
1941
+ "iframe",
1783
1942
  {
1784
- containerRef,
1785
- onResizeStart: () => setIsResizing(true),
1786
- 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" }
1787
1952
  }
1788
1953
  )
1789
- ] })
1790
- ]
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
2049
+ }
2050
+ )
2051
+ ] })
2052
+ ]
1791
2053
  }
1792
2054
  ) });
1793
2055
  }
@@ -1843,6 +2105,7 @@ var YouTubeNode = class _YouTubeNode extends DecoratorBlockNode {
1843
2105
  iframe.setAttribute("width", String(this.__width));
1844
2106
  iframe.setAttribute("height", String(this.__height));
1845
2107
  iframe.style.border = "none";
2108
+ iframe.setAttribute("sandbox", "allow-same-origin allow-scripts allow-popups allow-presentation");
1846
2109
  iframe.setAttribute(
1847
2110
  "allow",
1848
2111
  "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
@@ -1870,16 +2133,10 @@ var YouTubeNode = class _YouTubeNode extends DecoratorBlockNode {
1870
2133
  writable.__width = width;
1871
2134
  writable.__height = height;
1872
2135
  }
1873
- decorate(_editor, config) {
1874
- const embedBlockTheme = config.theme.embedBlock || {};
1875
- const className = {
1876
- base: embedBlockTheme.base || "",
1877
- focus: embedBlockTheme.focus || ""
1878
- };
2136
+ decorate(_editor, _config) {
1879
2137
  return /* @__PURE__ */ jsx(
1880
2138
  YouTubeComponent,
1881
2139
  {
1882
- className,
1883
2140
  format: this.__format,
1884
2141
  nodeKey: this.getKey(),
1885
2142
  videoID: this.__id,
@@ -2245,12 +2502,12 @@ function useFloatingPortalContainer(editor) {
2245
2502
  }, [editor]);
2246
2503
  return container;
2247
2504
  }
2248
- function setPopupPositionFixed(popupEl, rect) {
2505
+ function setPopupPositionFixed(popupEl, rect, topBoundary) {
2249
2506
  const GAP = 8;
2250
2507
  const MARGIN = 8;
2251
2508
  let top = rect.top - popupEl.offsetHeight - GAP;
2252
- let left = rect.left + rect.width - popupEl.offsetWidth;
2253
- if (top < MARGIN) top = rect.bottom + GAP;
2509
+ let left = rect.left;
2510
+ if (top < topBoundary) top = rect.bottom + GAP;
2254
2511
  left = clamp2(left, MARGIN, window.innerWidth - popupEl.offsetWidth - MARGIN);
2255
2512
  popupEl.style.top = `${top}px`;
2256
2513
  popupEl.style.left = `${left}px`;
@@ -2292,10 +2549,22 @@ function FloatingCharacterStylesEditor({
2292
2549
  popupEl.classList.remove("is-open");
2293
2550
  return;
2294
2551
  }
2295
- const range = sel.getRangeAt(0);
2296
- 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;
2297
2566
  if (!mouseDownRef.current) {
2298
- setPopupPositionFixed(popupEl, rect);
2567
+ setPopupPositionFixed(popupEl, rect, topBoundary);
2299
2568
  }
2300
2569
  popupEl.classList.add("is-open");
2301
2570
  }, [editor]);
@@ -2503,6 +2772,11 @@ function useCharacterStylesPopup(editor, opts) {
2503
2772
  setIsText(false);
2504
2773
  return;
2505
2774
  }
2775
+ const activeElement = document.activeElement;
2776
+ if (activeElement && activeElement !== document.body && rootElement && !rootElement.contains(activeElement)) {
2777
+ setIsText(false);
2778
+ return;
2779
+ }
2506
2780
  if (!$isRangeSelection(selection)) return;
2507
2781
  const node = getSelectedNode(selection);
2508
2782
  setIsBold(selection.hasFormat("bold"));
@@ -2523,7 +2797,11 @@ function useCharacterStylesPopup(editor, opts) {
2523
2797
  }, [editor]);
2524
2798
  useEffect(() => {
2525
2799
  document.addEventListener("selectionchange", updatePopupState);
2526
- return () => document.removeEventListener("selectionchange", updatePopupState);
2800
+ document.addEventListener("focusin", updatePopupState);
2801
+ return () => {
2802
+ document.removeEventListener("selectionchange", updatePopupState);
2803
+ document.removeEventListener("focusin", updatePopupState);
2804
+ };
2527
2805
  }, [updatePopupState]);
2528
2806
  useEffect(() => editor.registerUpdateListener(updatePopupState), [editor, updatePopupState]);
2529
2807
  if (!portalContainer || !isText || isLink) return null;
@@ -2781,34 +3059,57 @@ function normalizeToBlockHtml(html) {
2781
3059
  if (pendingP) body.appendChild(pendingP);
2782
3060
  return body.innerHTML;
2783
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
+ }
2784
3087
  var CustomOnChangePlugin = ({ value, onChange }) => {
2785
3088
  const [editor] = useLexicalComposerContext();
2786
3089
  const initializedRef = useRef(false);
2787
- const onChangeRef = useRef(onChange);
2788
- onChangeRef.current = onChange;
2789
3090
  useEffect(() => {
2790
3091
  if (!value || initializedRef.current) return;
2791
3092
  initializedRef.current = true;
2792
3093
  editor.update(() => {
2793
3094
  const root = $getRoot();
2794
3095
  root.clear();
3096
+ const safe = sanitizeHtml(value);
3097
+ const cleaned = normalizeToBlockHtml(splitHeadingsAtBrSequences(safe));
2795
3098
  const parser = new DOMParser();
2796
- const dom = parser.parseFromString(value, "text/html");
3099
+ const dom = parser.parseFromString(cleaned, "text/html");
2797
3100
  const nodes = $generateNodesFromDOM(editor, dom);
2798
3101
  root.append(...nodes);
2799
- $setSelection(null);
2800
3102
  });
2801
3103
  }, [editor, value]);
2802
- const handleChange = useCallback((editorState) => {
2803
- editorState.read(() => {
2804
- onChangeRef.current(postProcessOutput($generateHtmlFromNodes(editor)));
2805
- });
2806
- }, [editor]);
2807
3104
  return /* @__PURE__ */ jsx(
2808
3105
  OnChangePlugin,
2809
3106
  {
2810
- onChange: handleChange,
2811
- ignoreSelectionChange: true
3107
+ onChange: (editorState) => {
3108
+ editorState.read(() => {
3109
+ const raw = $generateHtmlFromNodes(editor);
3110
+ onChange(postProcessOutput(splitHeadingsAtBrSequences(raw)));
3111
+ });
3112
+ }
2812
3113
  }
2813
3114
  );
2814
3115
  };
@@ -2918,7 +3219,7 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
2918
3219
  setLinkUrl("");
2919
3220
  }
2920
3221
  return true;
2921
- }, [anchorElem, editor, isLink, setIsLinkEditMode, isLinkEditMode, linkUrl]);
3222
+ }, [anchorElem, editor, setIsLinkEditMode, isLinkEditMode, isLink, linkUrl]);
2922
3223
  useEffect(() => {
2923
3224
  const scrollerElem = anchorElem.parentElement;
2924
3225
  const update = () => {
@@ -3016,7 +3317,6 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
3016
3317
  {
3017
3318
  ref: inputRef,
3018
3319
  className: "link-input",
3019
- placeholder: "https://",
3020
3320
  value: editedLinkUrl,
3021
3321
  onChange: (event) => {
3022
3322
  setEditedLinkUrl(event.target.value);
@@ -3026,31 +3326,33 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
3026
3326
  }
3027
3327
  }
3028
3328
  ),
3029
- /* @__PURE__ */ jsxs("div", { className: "link-actions", children: [
3329
+ /* @__PURE__ */ jsxs("div", { className: "link-input-actions", children: [
3030
3330
  /* @__PURE__ */ jsx(
3031
- "button",
3331
+ "div",
3032
3332
  {
3033
- type: "button",
3034
- className: "link-icon-btn link-cancel",
3333
+ className: "link-cancel",
3334
+ role: "button",
3335
+ tabIndex: 0,
3035
3336
  title: "Cancel",
3036
3337
  "aria-label": "Cancel",
3037
3338
  onMouseDown: preventDefault,
3038
3339
  onClick: () => {
3039
3340
  setIsLinkEditMode(false);
3040
3341
  },
3041
- children: /* @__PURE__ */ jsx(Dismiss16Regular, {})
3342
+ children: /* @__PURE__ */ jsx(DismissRegular, { fontSize: 16 })
3042
3343
  }
3043
3344
  ),
3044
3345
  /* @__PURE__ */ jsx(
3045
- "button",
3346
+ "div",
3046
3347
  {
3047
- type: "button",
3048
- className: "link-icon-btn link-confirm",
3348
+ className: "link-confirm",
3349
+ role: "button",
3350
+ tabIndex: 0,
3049
3351
  title: "Confirm",
3050
3352
  "aria-label": "Confirm",
3051
3353
  onMouseDown: preventDefault,
3052
3354
  onClick: handleLinkSubmission,
3053
- children: /* @__PURE__ */ jsx(Checkmark16Regular, {})
3355
+ children: /* @__PURE__ */ jsx(CheckmarkRegular, { fontSize: 16 })
3054
3356
  }
3055
3357
  )
3056
3358
  ] })
@@ -3064,38 +3366,38 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, anchorElem, isLinkEditMod
3064
3366
  children: linkUrl
3065
3367
  }
3066
3368
  ),
3067
- /* @__PURE__ */ jsxs("div", { className: "link-actions", children: [
3068
- /* @__PURE__ */ jsx(
3069
- "button",
3070
- {
3071
- type: "button",
3072
- className: "link-icon-btn link-edit",
3073
- title: "Edit link",
3074
- "aria-label": "Edit link",
3075
- onMouseDown: preventDefault,
3076
- onClick: (event) => {
3077
- event.preventDefault();
3078
- setEditedLinkUrl(linkUrl);
3079
- setIsLinkEditMode(true);
3080
- },
3081
- children: /* @__PURE__ */ jsx(Edit16Regular, {})
3082
- }
3083
- ),
3084
- /* @__PURE__ */ jsx(
3085
- "button",
3086
- {
3087
- type: "button",
3088
- className: "link-icon-btn link-trash",
3089
- title: "Remove link",
3090
- "aria-label": "Remove link",
3091
- onMouseDown: preventDefault,
3092
- onClick: () => {
3093
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
3094
- },
3095
- children: /* @__PURE__ */ jsx(Delete16Regular, {})
3096
- }
3097
- )
3098
- ] })
3369
+ /* @__PURE__ */ jsx(
3370
+ "div",
3371
+ {
3372
+ className: "link-edit",
3373
+ role: "button",
3374
+ tabIndex: 0,
3375
+ title: "Edit link",
3376
+ "aria-label": "Edit link",
3377
+ onMouseDown: preventDefault,
3378
+ onClick: (event) => {
3379
+ event.preventDefault();
3380
+ setEditedLinkUrl(linkUrl);
3381
+ setIsLinkEditMode(true);
3382
+ },
3383
+ children: /* @__PURE__ */ jsx(EditRegular, { fontSize: 16 })
3384
+ }
3385
+ ),
3386
+ /* @__PURE__ */ jsx(
3387
+ "div",
3388
+ {
3389
+ className: "link-trash",
3390
+ role: "button",
3391
+ tabIndex: 0,
3392
+ title: "Remove link",
3393
+ "aria-label": "Remove link",
3394
+ onMouseDown: preventDefault,
3395
+ onClick: () => {
3396
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
3397
+ },
3398
+ children: /* @__PURE__ */ jsx(DeleteRegular, { fontSize: 16 })
3399
+ }
3400
+ )
3099
3401
  ] }) });
3100
3402
  };
3101
3403
  var useFloatingLinkEditorToolbar = (editor, anchorElem, isLinkEditMode, setIsLinkEditMode) => {
@@ -3176,8 +3478,6 @@ var FloatingLinkEditorPlugin = ({ anchorElem, isLinkEditMode, setIsLinkEditMode
3176
3478
  setIsLinkEditMode
3177
3479
  );
3178
3480
  };
3179
-
3180
- // src/Plugins/ImagePlugin.tsx
3181
3481
  init_ImageNode();
3182
3482
  var INSERT_IMAGE_COMMAND = createCommand("INSERT_IMAGE_COMMAND");
3183
3483
  var readClipboardImageAsDataURL = async (event) => {
@@ -3196,28 +3496,77 @@ var readClipboardImageAsDataURL = async (event) => {
3196
3496
  }
3197
3497
  return null;
3198
3498
  };
3499
+ var InsertImageByURL = ({
3500
+ setIsOpen,
3501
+ onClick,
3502
+ disabled
3503
+ }) => {
3504
+ const [altText, setAltText] = useState("");
3505
+ const [src, setSrc] = useState("");
3506
+ const isDisabled = disabled || src === "";
3507
+ return /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 6, padding: "10px 0px 0px 0px" }, children: [
3508
+ /* @__PURE__ */ jsx(Field, { label: "Enter URL", size: "small", children: /* @__PURE__ */ jsx(
3509
+ Input,
3510
+ {
3511
+ autoFocus: !disabled,
3512
+ appearance: "underline",
3513
+ placeholder: "Add URL",
3514
+ disabled,
3515
+ onChange: (_, v) => setSrc(v.value),
3516
+ value: src
3517
+ }
3518
+ ) }),
3519
+ /* @__PURE__ */ jsx(Field, { label: "Alt Text", size: "small", children: /* @__PURE__ */ jsx(
3520
+ Input,
3521
+ {
3522
+ placeholder: "Alt text",
3523
+ disabled,
3524
+ onChange: (_, v) => setAltText(v.value),
3525
+ value: altText
3526
+ },
3527
+ "alt-text-url"
3528
+ ) }),
3529
+ /* @__PURE__ */ jsxs(Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
3530
+ /* @__PURE__ */ jsx(
3531
+ Button,
3532
+ {
3533
+ style: { width: "150px" },
3534
+ onClick: () => !disabled && onClick({ altText, src }),
3535
+ disabled: isDisabled,
3536
+ size: "small",
3537
+ children: "Confirm"
3538
+ },
3539
+ "url-confirm-btn"
3540
+ ),
3541
+ /* @__PURE__ */ jsx(
3542
+ Button,
3543
+ {
3544
+ style: { width: "150px" },
3545
+ onClick: () => setIsOpen(false),
3546
+ disabled,
3547
+ size: "small",
3548
+ children: "Cancel"
3549
+ },
3550
+ "file-url-cancel"
3551
+ )
3552
+ ] })
3553
+ ] });
3554
+ };
3199
3555
  var InsertImageDialog = ({
3200
3556
  activeEditor,
3201
3557
  disabled,
3202
- open: externalOpen,
3203
- onClose
3558
+ maxImageSizeMB,
3559
+ validationMessages
3204
3560
  }) => {
3205
3561
  const [src, setSrc] = useState("");
3206
3562
  const [altText, setAltText] = useState("");
3207
- const [internalOpen, setInternalOpen] = useState(false);
3563
+ const [isOpen, setIsOpen] = useState(false);
3564
+ const [selectedValue, setSelectedValue] = useState("Upload");
3208
3565
  const [fileName, setFileName] = useState("");
3566
+ const [fileSizeError, setFileSizeError] = useState(null);
3209
3567
  const hasModifier = useRef(false);
3210
3568
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
3211
- const isControlled = externalOpen !== void 0;
3212
- const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
3213
- const isAddDisabled = disabled || src === "";
3214
- const handleClose = () => {
3215
- setSrc("");
3216
- setAltText("");
3217
- setFileName("");
3218
- if (isControlled) onClose?.();
3219
- else setInternalOpen(false);
3220
- };
3569
+ const isDisabled = disabled || src === "" || !!fileSizeError;
3221
3570
  useEffect(() => {
3222
3571
  hasModifier.current = false;
3223
3572
  const handler = (e) => {
@@ -3229,57 +3578,75 @@ var InsertImageDialog = ({
3229
3578
  const onClick = (payload) => {
3230
3579
  if (disabled) return;
3231
3580
  activeEditor.dispatchCommand(INSERT_IMAGE_COMMAND, payload);
3232
- handleClose();
3581
+ setIsOpen(false);
3582
+ setAltText("");
3583
+ setSrc("");
3584
+ setFileName("");
3585
+ setFileSizeError(null);
3233
3586
  };
3234
3587
  const loadImage = (event) => {
3235
3588
  if (disabled) return;
3236
3589
  const files = event.target.files;
3237
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);
3238
3605
  const reader = new FileReader();
3239
3606
  reader.onload = () => {
3240
3607
  if (typeof reader.result === "string") {
3241
3608
  setSrc(reader.result);
3242
- setFileName(files[0].name);
3609
+ setFileName(file.name);
3243
3610
  }
3244
3611
  };
3245
- reader.readAsDataURL(files[0]);
3612
+ reader.readAsDataURL(file);
3246
3613
  };
3247
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3248
- !isControlled && /* @__PURE__ */ jsx(
3249
- Button,
3250
- {
3251
- size: "small",
3252
- title: "Add Image",
3253
- disabled,
3254
- icon: /* @__PURE__ */ jsx(ImageAddRegular, { style: { color: iconColor } }),
3255
- style: {
3256
- background: isOpen && !disabled ? "#ebebeb" : "none",
3257
- border: "none",
3258
- margin: 2,
3259
- opacity: disabled ? 0.55 : 1,
3260
- cursor: disabled ? "not-allowed" : "pointer"
3261
- },
3262
- onClick: () => {
3263
- if (disabled) return;
3264
- setSrc("");
3265
- setAltText("");
3266
- setFileName("");
3267
- setInternalOpen(true);
3268
- }
3614
+ return /* @__PURE__ */ jsxs(
3615
+ Dialog,
3616
+ {
3617
+ open: disabled ? false : isOpen,
3618
+ onOpenChange: (_, data) => {
3619
+ if (!disabled) setIsOpen(data.open);
3269
3620
  },
3270
- "upload-image"
3271
- ),
3272
- /* @__PURE__ */ jsx(
3273
- Dialog,
3274
- {
3275
- open: isOpen,
3276
- onOpenChange: (_, data) => {
3277
- if (!data.open) handleClose();
3278
- },
3279
- children: /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: "400px" }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
3280
- /* @__PURE__ */ jsx(DialogTitle, { children: "Insert Image" }),
3281
- /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
3282
- /* @__PURE__ */ jsx(Field, { label: "Upload", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxs(
3621
+ children: [
3622
+ /* @__PURE__ */ jsx(DialogTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
3623
+ Button,
3624
+ {
3625
+ size: "small",
3626
+ title: "Add Image",
3627
+ disabled,
3628
+ icon: /* @__PURE__ */ jsx(ImageAddRegular, { style: { color: iconColor } }),
3629
+ style: {
3630
+ background: isOpen && !disabled ? "#ebebeb" : "none",
3631
+ border: "none",
3632
+ margin: 2,
3633
+ opacity: disabled ? 0.55 : 1,
3634
+ cursor: disabled ? "not-allowed" : "pointer"
3635
+ },
3636
+ onClick: () => {
3637
+ if (disabled) return;
3638
+ setIsOpen((prev) => !prev);
3639
+ setSrc("");
3640
+ setAltText("");
3641
+ setFileName("");
3642
+ }
3643
+ },
3644
+ "upload-image"
3645
+ ) }),
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(
3283
3650
  "label",
3284
3651
  {
3285
3652
  style: {
@@ -3318,7 +3685,8 @@ var InsertImageDialog = ({
3318
3685
  ]
3319
3686
  }
3320
3687
  ) }),
3321
- /* @__PURE__ */ jsx(Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
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(
3322
3690
  Input,
3323
3691
  {
3324
3692
  placeholder: "Alt text",
@@ -3327,25 +3695,24 @@ var InsertImageDialog = ({
3327
3695
  onChange: (_, d) => setAltText(d.value),
3328
3696
  value: altText
3329
3697
  }
3330
- ) })
3331
- ] }) }),
3332
- /* @__PURE__ */ jsxs(DialogActions, { children: [
3333
- /* @__PURE__ */ jsx(
3334
- Button,
3698
+ ) }),
3699
+ selectedValue === "URL" && /* @__PURE__ */ jsx(
3700
+ InsertImageByURL,
3335
3701
  {
3336
- appearance: "primary",
3337
- size: "small",
3338
- disabled: isAddDisabled,
3339
- onClick: () => onClick({ altText, src }),
3340
- children: "Add"
3702
+ disabled,
3703
+ setIsOpen: (open) => setIsOpen(open),
3704
+ onClick: (payload) => onClick(payload)
3341
3705
  }
3342
- ),
3343
- /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: handleClose, children: "Cancel" })
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" })
3344
3711
  ] })
3345
3712
  ] }) })
3346
- }
3347
- )
3348
- ] });
3713
+ ]
3714
+ }
3715
+ );
3349
3716
  };
3350
3717
  var ImagesPlugin = ({ captionsEnabled }) => {
3351
3718
  const [editor] = useLexicalComposerContext();
@@ -3358,9 +3725,21 @@ var ImagesPlugin = ({ captionsEnabled }) => {
3358
3725
  INSERT_IMAGE_COMMAND,
3359
3726
  (payload) => {
3360
3727
  const imageNode = $createImageNode(payload);
3361
- $insertNodes([imageNode]);
3362
- if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
3363
- $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
+ }
3364
3743
  }
3365
3744
  return true;
3366
3745
  },
@@ -3411,12 +3790,25 @@ var TRANSPARENT_IMAGE = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAA
3411
3790
  var img = document.createElement("img");
3412
3791
  img.src = TRANSPARENT_IMAGE;
3413
3792
  var $onDragStart = (event) => {
3414
- 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
+ }
3415
3802
  if (!node) return false;
3416
3803
  const dataTransfer = event.dataTransfer;
3417
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
+ }
3418
3811
  dataTransfer.setData("text/plain", "_");
3419
- dataTransfer.setDragImage(img, 0, 0);
3420
3812
  dataTransfer.setData(
3421
3813
  "application/x-lexical-drag",
3422
3814
  JSON.stringify({
@@ -3444,20 +3836,23 @@ var $onDragover = (event) => {
3444
3836
  return true;
3445
3837
  };
3446
3838
  var $onDrop = (event, editor) => {
3447
- const node = $getImageNodeInSelection();
3448
- if (!node) return false;
3449
3839
  const data = getDragImageData(event);
3450
3840
  if (!data) return false;
3451
3841
  event.preventDefault();
3452
3842
  if (canDropImage(event)) {
3843
+ const sourceKey = data.key;
3844
+ if (sourceKey) {
3845
+ const sourceNode = $getNodeByKey(sourceKey);
3846
+ if (sourceNode) sourceNode.remove();
3847
+ }
3453
3848
  const range = getDragSelection(event);
3454
- node.remove();
3455
3849
  const rangeSelection = $createRangeSelection();
3456
3850
  if (range !== null && range !== void 0) {
3457
3851
  rangeSelection.applyDOMRange(range);
3458
3852
  }
3459
3853
  $setSelection(rangeSelection);
3460
- editor.dispatchCommand(INSERT_IMAGE_COMMAND, data);
3854
+ const { key: _key, ...insertPayload } = data;
3855
+ editor.dispatchCommand(INSERT_IMAGE_COMMAND, insertPayload);
3461
3856
  }
3462
3857
  return true;
3463
3858
  };
@@ -3477,7 +3872,7 @@ var getDragImageData = (event) => {
3477
3872
  };
3478
3873
  var canDropImage = (event) => {
3479
3874
  const target = event.target;
3480
- 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"]'));
3481
3876
  };
3482
3877
  var getDragSelection = (event) => {
3483
3878
  let range;
@@ -3492,8 +3887,6 @@ var getDragSelection = (event) => {
3492
3887
  }
3493
3888
  return range;
3494
3889
  };
3495
-
3496
- // src/Plugins/InlineImage.tsx
3497
3890
  init_InlineImage();
3498
3891
  init_InlineImageNode();
3499
3892
  var INSERT_INLINE_IMAGE_COMMAND = createCommand(
@@ -3512,39 +3905,45 @@ var useStyles = makeStyles({
3512
3905
  var InsertInlineImageDialog = ({
3513
3906
  disabled,
3514
3907
  activeEditor,
3515
- open: externalOpen,
3516
- onClose
3908
+ maxImageSizeMB,
3909
+ validationMessages
3517
3910
  }) => {
3518
3911
  const hasModifier = useRef(false);
3519
3912
  const [src, setSrc] = useState("");
3520
- const [internalOpen, setInternalOpen] = useState(false);
3913
+ const [isOpen, setIsOpen] = useState(false);
3521
3914
  const [altText, setAltText] = useState("");
3522
3915
  const [fileName, setFileName] = useState("");
3523
3916
  const [position, setPosition] = useState("left");
3917
+ const [fileSizeError, setFileSizeError] = useState(null);
3524
3918
  const styles = useStyles();
3525
3919
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
3526
- const isControlled = externalOpen !== void 0;
3527
- const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
3528
- const isAddDisabled = disabled || src === "";
3529
- const handleClose = () => {
3530
- setSrc("");
3531
- setAltText("");
3532
- setFileName("");
3533
- if (isControlled) onClose?.();
3534
- else setInternalOpen(false);
3535
- };
3920
+ const isDisabled = disabled || src === "" || !!fileSizeError;
3536
3921
  const loadImage = (event) => {
3537
3922
  if (disabled) return;
3538
3923
  const files = event.target.files;
3539
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);
3540
3939
  const reader = new FileReader();
3541
3940
  reader.onload = () => {
3542
3941
  if (typeof reader.result === "string") {
3543
3942
  setSrc(reader.result);
3544
- setFileName(files[0].name);
3943
+ setFileName(file.name);
3545
3944
  }
3546
3945
  };
3547
- reader.readAsDataURL(files[0]);
3946
+ reader.readAsDataURL(file);
3548
3947
  };
3549
3948
  useEffect(() => {
3550
3949
  const handler = (e) => {
@@ -3557,44 +3956,48 @@ var InsertInlineImageDialog = ({
3557
3956
  if (disabled) return;
3558
3957
  const payload = { altText, position, src };
3559
3958
  activeEditor.dispatchCommand(INSERT_INLINE_IMAGE_COMMAND, payload);
3560
- handleClose();
3959
+ setIsOpen(false);
3960
+ setAltText("");
3961
+ setSrc("");
3962
+ setFileName("");
3963
+ setFileSizeError(null);
3561
3964
  };
3562
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3563
- !isControlled && /* @__PURE__ */ jsx(
3564
- Button,
3565
- {
3566
- size: "small",
3567
- title: "Add Inline Image",
3568
- disabled,
3569
- icon: /* @__PURE__ */ jsx(ImageEditRegular, { style: { color: iconColor } }),
3570
- style: {
3571
- background: isOpen && !disabled ? "#ebebeb" : "none",
3572
- border: "none",
3573
- margin: 2,
3574
- opacity: disabled ? 0.55 : 1,
3575
- cursor: disabled ? "not-allowed" : "pointer"
3576
- },
3577
- onClick: () => {
3578
- if (disabled) return;
3579
- setSrc("");
3580
- setAltText("");
3581
- setFileName("");
3582
- setInternalOpen(true);
3583
- }
3965
+ return /* @__PURE__ */ jsxs(
3966
+ Dialog,
3967
+ {
3968
+ open: disabled ? false : isOpen,
3969
+ onOpenChange: (_, data) => {
3970
+ if (!disabled) setIsOpen(data.open);
3584
3971
  },
3585
- "upload-inline-image"
3586
- ),
3587
- /* @__PURE__ */ jsx(
3588
- Dialog,
3589
- {
3590
- open: isOpen,
3591
- onOpenChange: (_, data) => {
3592
- if (!data.open) handleClose();
3593
- },
3594
- children: /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: "440px" }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
3595
- /* @__PURE__ */ jsx(DialogTitle, { children: "Insert Inline Image" }),
3596
- /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
3597
- /* @__PURE__ */ jsx(Field, { label: "Upload", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxs(
3972
+ children: [
3973
+ /* @__PURE__ */ jsx(DialogTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
3974
+ Button,
3975
+ {
3976
+ size: "small",
3977
+ title: "Add Inline Image",
3978
+ disabled,
3979
+ icon: /* @__PURE__ */ jsx(ImageEditRegular, { style: { color: iconColor } }),
3980
+ style: {
3981
+ background: isOpen && !disabled ? "#ebebeb" : "none",
3982
+ border: "none",
3983
+ margin: 2,
3984
+ opacity: disabled ? 0.55 : 1,
3985
+ cursor: disabled ? "not-allowed" : "pointer"
3986
+ },
3987
+ onClick: () => {
3988
+ if (disabled) return;
3989
+ setIsOpen((prev) => !prev);
3990
+ setAltText("");
3991
+ setSrc("");
3992
+ setFileName("");
3993
+ }
3994
+ },
3995
+ "upload-inline-image"
3996
+ ) }),
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(
3598
4001
  "label",
3599
4002
  {
3600
4003
  style: {
@@ -3633,22 +4036,25 @@ var InsertInlineImageDialog = ({
3633
4036
  ]
3634
4037
  }
3635
4038
  ) }),
3636
- /* @__PURE__ */ jsx(Field, { label: "Position", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxs(
4039
+ /* @__PURE__ */ jsx(Field, { label: "Position", size: "small", children: /* @__PURE__ */ jsxs(
3637
4040
  Dropdown,
3638
4041
  {
3639
- placeholder: "Left Align",
3640
4042
  className: styles.alignDropdown,
3641
4043
  disabled,
4044
+ value: position === "full" ? "Full" : position === "right" ? "Right" : "Left",
4045
+ selectedOptions: [position ?? "left"],
3642
4046
  listbox: { style: { width: "120px" } },
3643
4047
  root: { style: { borderBottom: "1px solid black" } },
4048
+ onOptionSelect: (_, data) => setPosition(data.optionValue),
3644
4049
  children: [
3645
- /* @__PURE__ */ jsx(Option, { text: "full", onClick: () => setPosition("full"), children: "Full" }, "full"),
3646
- /* @__PURE__ */ jsx(Option, { text: "left", onClick: () => setPosition("left"), children: "Left" }, "left"),
3647
- /* @__PURE__ */ jsx(Option, { text: "right", onClick: () => setPosition("right"), children: "Right" }, "right")
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")
3648
4053
  ]
3649
4054
  }
3650
4055
  ) }),
3651
- /* @__PURE__ */ jsx(Field, { label: "Alt Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
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(
3652
4058
  Input,
3653
4059
  {
3654
4060
  placeholder: "Alt text",
@@ -3663,9 +4069,8 @@ var InsertInlineImageDialog = ({
3663
4069
  /* @__PURE__ */ jsx(
3664
4070
  Button,
3665
4071
  {
3666
- appearance: "primary",
3667
4072
  size: "small",
3668
- disabled: isAddDisabled,
4073
+ disabled: isDisabled,
3669
4074
  onClick: handleOnClick,
3670
4075
  children: "Add"
3671
4076
  },
@@ -3676,16 +4081,16 @@ var InsertInlineImageDialog = ({
3676
4081
  {
3677
4082
  size: "small",
3678
4083
  disabled,
3679
- onClick: handleClose,
4084
+ onClick: () => setIsOpen(false),
3680
4085
  children: "Cancel"
3681
4086
  },
3682
4087
  "file-inline-upload-cancel"
3683
4088
  )
3684
4089
  ] })
3685
4090
  ] }) })
3686
- }
3687
- )
3688
- ] });
4091
+ ]
4092
+ }
4093
+ );
3689
4094
  };
3690
4095
  var InlineImagePlugin = () => {
3691
4096
  const [editor] = useLexicalComposerContext();
@@ -3702,6 +4107,11 @@ var InlineImagePlugin = () => {
3702
4107
  if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
3703
4108
  $wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();
3704
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
+ }
3705
4115
  return true;
3706
4116
  },
3707
4117
  COMMAND_PRIORITY_EDITOR
@@ -3730,12 +4140,23 @@ var TRANSPARENT_IMAGE2 = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEA
3730
4140
  var img2 = document.createElement("img");
3731
4141
  img2.src = TRANSPARENT_IMAGE2;
3732
4142
  function $onDragStart2(event) {
3733
- 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
+ }
3734
4150
  if (!node) return false;
3735
4151
  const dataTransfer = event.dataTransfer;
3736
4152
  if (!dataTransfer) return false;
3737
- dataTransfer.setData("text/plain", "_");
3738
- dataTransfer.setDragImage(img2, 0, 0);
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
+ }
4159
+ dataTransfer.setData("text/plain", "_");
3739
4160
  dataTransfer.setData(
3740
4161
  "application/x-lexical-drag",
3741
4162
  JSON.stringify({
@@ -3754,28 +4175,31 @@ function $onDragStart2(event) {
3754
4175
  return true;
3755
4176
  }
3756
4177
  var $onDragover2 = (event) => {
3757
- const node = $getImageNodeInSelection2();
3758
- if (!node) return false;
4178
+ const hasDragData = !!event.dataTransfer?.types.includes("application/x-lexical-drag");
4179
+ if (!hasDragData) return false;
3759
4180
  if (!canDropImage2(event)) {
3760
4181
  event.preventDefault();
3761
4182
  }
3762
4183
  return true;
3763
4184
  };
3764
4185
  var $onDrop2 = (event, editor) => {
3765
- const node = $getImageNodeInSelection2();
3766
- if (!node) return false;
3767
4186
  const data = getDragImageData2(event);
3768
4187
  if (!data) return false;
3769
4188
  event.preventDefault();
3770
4189
  if (canDropImage2(event)) {
4190
+ const sourceKey = data.key;
4191
+ if (sourceKey) {
4192
+ const sourceNode = $getNodeByKey(sourceKey);
4193
+ if (sourceNode) sourceNode.remove();
4194
+ }
3771
4195
  const range = getDragSelection2(event);
3772
- node.remove();
3773
4196
  const rangeSelection = $createRangeSelection();
3774
4197
  if (range !== null && range !== void 0) {
3775
4198
  rangeSelection.applyDOMRange(range);
3776
4199
  }
3777
4200
  $setSelection(rangeSelection);
3778
- editor.dispatchCommand(INSERT_INLINE_IMAGE_COMMAND, data);
4201
+ const { key: _key, ...insertPayload } = data;
4202
+ editor.dispatchCommand(INSERT_INLINE_IMAGE_COMMAND, insertPayload);
3779
4203
  }
3780
4204
  return true;
3781
4205
  };
@@ -3795,7 +4219,7 @@ var getDragImageData2 = (event) => {
3795
4219
  };
3796
4220
  var canDropImage2 = (event) => {
3797
4221
  const target = event.target;
3798
- 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"]'));
3799
4223
  };
3800
4224
  var getDragSelection2 = (event) => {
3801
4225
  let range;
@@ -3886,20 +4310,79 @@ function hasBlock(editor, kind) {
3886
4310
  }
3887
4311
 
3888
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
+ }
3889
4350
  function RefApiPlugin({
3890
4351
  forwardedRef,
3891
4352
  contentEditableDomRef,
3892
- focusedRef,
3893
- setRefErrors
4353
+ focusedRef
3894
4354
  }) {
3895
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]);
3896
4378
  useImperativeHandle(
3897
4379
  forwardedRef,
3898
4380
  () => ({
3899
4381
  setValue: (html) => {
3900
4382
  editor.update(() => {
4383
+ const safe = normalizeToBlockHtml(sanitizeHtml(html || ""));
3901
4384
  const parser = new DOMParser();
3902
- const dom = parser.parseFromString(html || "<p></p>", "text/html");
4385
+ const dom = parser.parseFromString(safe || "<p></p>", "text/html");
3903
4386
  const nodes = $generateNodesFromDOM(editor, dom);
3904
4387
  const root = $getRoot();
3905
4388
  root.clear();
@@ -3909,9 +4392,9 @@ function RefApiPlugin({
3909
4392
  getValue: () => {
3910
4393
  let html = "";
3911
4394
  editor.getEditorState().read(() => {
3912
- html = $generateHtmlFromNodes(editor, null);
4395
+ html = postProcessOutput(splitHeadingsAtBrSequences($generateHtmlFromNodes(editor, null)));
3913
4396
  });
3914
- return postProcessOutput(html);
4397
+ return html;
3915
4398
  },
3916
4399
  clear: () => {
3917
4400
  editor.update(() => {
@@ -3929,14 +4412,20 @@ function RefApiPlugin({
3929
4412
  },
3930
4413
  isFocused: () => focusedRef.current,
3931
4414
  getEditor: () => editor,
3932
- setErrors: (messages) => setRefErrors(messages),
3933
- clearErrors: () => setRefErrors([]),
3934
4415
  // Generic blocks (signature, footer, banner, etc.)
3935
4416
  upsertBlock: (spec) => upsertBlock(editor, spec),
3936
4417
  removeBlock: (kind) => removeBlock(editor, kind),
3937
- 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
+ }
3938
4427
  }),
3939
- [editor, contentEditableDomRef, focusedRef, setRefErrors]
4428
+ [editor, contentEditableDomRef, focusedRef]
3940
4429
  );
3941
4430
  return null;
3942
4431
  }
@@ -4577,8 +5066,22 @@ function TableActionMenuPlugin({ disabled = false }) {
4577
5066
  const [editor] = useLexicalComposerContext();
4578
5067
  const [isInTable, setIsInTable] = React9.useState(false);
4579
5068
  const [anchorRect, setAnchorRect] = React9.useState(null);
5069
+ const [contentRight, setContentRight] = React9.useState(null);
4580
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
+ }, []);
4581
5083
  const updateFromSelection = React9.useCallback(() => {
5084
+ if (openRef.current) return;
4582
5085
  const root = editor.getRootElement();
4583
5086
  if (!root) return;
4584
5087
  editor.getEditorState().read(() => {
@@ -4590,6 +5093,7 @@ function TableActionMenuPlugin({ disabled = false }) {
4590
5093
  if (dom) {
4591
5094
  setIsInTable(true);
4592
5095
  setAnchorRect(dom.getBoundingClientRect());
5096
+ setContentRight(null);
4593
5097
  return;
4594
5098
  }
4595
5099
  }
@@ -4597,6 +5101,7 @@ function TableActionMenuPlugin({ disabled = false }) {
4597
5101
  if (!$isRangeSelection(selection)) {
4598
5102
  setIsInTable(false);
4599
5103
  setAnchorRect(null);
5104
+ setContentRight(null);
4600
5105
  return;
4601
5106
  }
4602
5107
  const anchorNode = selection.anchor.getNode();
@@ -4604,18 +5109,28 @@ function TableActionMenuPlugin({ disabled = false }) {
4604
5109
  if (!cellNode || !$isTableCellNode(cellNode)) {
4605
5110
  setIsInTable(false);
4606
5111
  setAnchorRect(null);
5112
+ setContentRight(null);
4607
5113
  return;
4608
5114
  }
4609
5115
  const cellDom = editor.getElementByKey(cellNode.getKey());
4610
5116
  if (!cellDom) {
4611
5117
  setIsInTable(false);
4612
5118
  setAnchorRect(null);
5119
+ setContentRight(null);
4613
5120
  return;
4614
5121
  }
4615
5122
  setIsInTable(true);
4616
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
+ }
4617
5132
  });
4618
- }, [editor]);
5133
+ }, [editor, measureContentRight]);
4619
5134
  React9.useEffect(() => {
4620
5135
  return mergeRegister(
4621
5136
  editor.registerCommand(
@@ -4672,30 +5187,37 @@ function TableActionMenuPlugin({ disabled = false }) {
4672
5187
  COMMAND_PRIORITY_HIGH
4673
5188
  );
4674
5189
  }, [editor, disabled]);
4675
- React9.useEffect(() => {
4676
- if (!isInTable && open) setOpen(false);
4677
- }, [isInTable, open]);
4678
5190
  const canShow = isInTable && !!anchorRect && !disabled;
4679
5191
  const handleStyle = React9.useMemo(() => {
4680
5192
  if (!anchorRect) return void 0;
4681
5193
  const top = Math.max(8, anchorRect.top + 6);
4682
- 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);
4683
5196
  return {
4684
5197
  position: "fixed",
4685
5198
  top,
4686
5199
  left,
4687
5200
  zIndex: 9999
4688
5201
  };
4689
- }, [anchorRect]);
5202
+ }, [anchorRect, contentRight]);
4690
5203
  const dangerStyle = {
4691
5204
  color: "var(--colorPaletteRedForeground1)"
4692
5205
  };
4693
5206
  const run = React9.useCallback(
4694
5207
  (fn) => {
4695
5208
  if (disabled) return;
4696
- editor.focus();
4697
- editor.update(() => fn());
5209
+ openRef.current = false;
4698
5210
  setOpen(false);
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
+ });
4699
5221
  },
4700
5222
  [disabled, editor]
4701
5223
  );
@@ -4705,32 +5227,31 @@ function TableActionMenuPlugin({ disabled = false }) {
4705
5227
  const insertColLeft = () => run(() => $insertTableColumnAtSelection(false));
4706
5228
  const deleteRow = () => run(() => $deleteTableRowAtSelection());
4707
5229
  const deleteCol = () => run(() => $deleteTableColumnAtSelection());
4708
- const deleteTable = () => run(() => {
4709
- const selection = $getSelection();
4710
- if ($isTableSelection(selection)) {
4711
- 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));
4712
5240
  if (tableNode) tableNode.remove();
4713
- return;
4714
- }
4715
- if (!$isRangeSelection(selection)) return;
4716
- const node = selection.anchor.getNode();
4717
- const cell = $findMatchingParent(node, (n) => $isTableCellNode(n)) ?? ($isTableCellNode(node) ? node : null);
4718
- if (!cell) return;
4719
- const table = $getTableNodeFromLexicalNodeOrThrow(cell);
4720
- table.remove();
4721
- });
5241
+ });
5242
+ };
4722
5243
  if (!canShow || !handleStyle) return null;
4723
5244
  return createPortal(
4724
- /* @__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: [
4725
5249
  /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
4726
5250
  "button",
4727
5251
  {
4728
5252
  type: "button",
4729
5253
  className: "aoTableActionHandleBtn",
4730
5254
  "aria-label": "Table options",
4731
- onMouseDown: (e) => {
4732
- e.preventDefault();
4733
- },
4734
5255
  children: /* @__PURE__ */ jsx(ChevronDown12Regular, {})
4735
5256
  }
4736
5257
  ) }),
@@ -5054,8 +5575,8 @@ function getToolbarGroupsByLevel(level) {
5054
5575
  // ['undo', 'redo', '|'],
5055
5576
  ["Bold", "Italic", "Underline", "|"],
5056
5577
  ["ColorPicker", "|"],
5057
- ["Link", "|"],
5058
- ["Insert", "|"],
5578
+ ["Link", "Table", "|"],
5579
+ // ['Image', 'Youtube', 'InlineImage', '|'],
5059
5580
  ["Heading", "|"],
5060
5581
  ["FontFamily", "|"],
5061
5582
  ["FontSize", "|"],
@@ -5069,8 +5590,9 @@ function getToolbarGroupsByLevel(level) {
5069
5590
  // ['undo', 'redo', '|'],
5070
5591
  ["Bold", "Italic", "Underline", "|"],
5071
5592
  ["ColorPicker", "|"],
5072
- ["Link", "|"],
5073
- ["Insert", "|"],
5593
+ ["Link", "Table", "|"],
5594
+ // ['CodeBlock', '|'],
5595
+ ["Image", "Youtube", "InlineImage", "|"],
5074
5596
  ["Heading", "|"],
5075
5597
  ["FontFamily", "|"],
5076
5598
  ["FontSize", "|"],
@@ -5080,6 +5602,85 @@ function getToolbarGroupsByLevel(level) {
5080
5602
  ];
5081
5603
  }
5082
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
+ };
5083
5684
  var PRESET = [
5084
5685
  "#000000",
5085
5686
  "#434343",
@@ -5363,7 +5964,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5363
5964
  directionalHint: 4,
5364
5965
  className: "aoColorCallout",
5365
5966
  preventDismissOnEvent,
5366
- 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: [
5367
5968
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
5368
5969
  /* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#242424", letterSpacing: 0.1 }, children: title }),
5369
5970
  /* @__PURE__ */ jsx(
@@ -5376,8 +5977,8 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5376
5977
  display: "flex",
5377
5978
  alignItems: "center",
5378
5979
  justifyContent: "center",
5379
- width: 24,
5380
- height: 24,
5980
+ width: 18,
5981
+ height: 18,
5381
5982
  padding: 0,
5382
5983
  border: "none",
5383
5984
  borderRadius: 4,
@@ -5400,7 +6001,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5400
6001
  style: {
5401
6002
  position: "relative",
5402
6003
  width: "100%",
5403
- height: 150,
6004
+ height: 125,
5404
6005
  borderRadius: 8,
5405
6006
  overflow: "hidden",
5406
6007
  cursor: "crosshair",
@@ -5489,8 +6090,8 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5489
6090
  "div",
5490
6091
  {
5491
6092
  style: {
5492
- width: 32,
5493
- height: 32,
6093
+ width: 20,
6094
+ height: 20,
5494
6095
  borderRadius: 6,
5495
6096
  flexShrink: 0,
5496
6097
  background: hex,
@@ -5531,10 +6132,10 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon, onOpenChange
5531
6132
  title: c,
5532
6133
  "aria-label": c,
5533
6134
  style: {
5534
- width: 22,
5535
- height: 22,
6135
+ width: 18,
6136
+ height: 18,
5536
6137
  padding: 0,
5537
- borderRadius: 5,
6138
+ borderRadius: 4,
5538
6139
  background: c,
5539
6140
  cursor: "pointer",
5540
6141
  boxShadow: isSelected ? "0 0 0 2px #fff, 0 0 0 3px #4a86e8" : "inset 0 0 0 1px rgba(0,0,0,0.15)",
@@ -5744,7 +6345,6 @@ var FontFamilyPlugin = ({ disabled = false }) => {
5744
6345
  "font-family"
5745
6346
  );
5746
6347
  };
5747
- var DEFAULT_FONT_SIZE = 15;
5748
6348
  var FONT_SIZE_OPTIONS = [
5749
6349
  "8",
5750
6350
  "9",
@@ -5880,135 +6480,43 @@ var FontSizePlugin = ({ disabled }) => {
5880
6480
  "fontsize"
5881
6481
  ) });
5882
6482
  };
5883
- var VERBATIM_LINK_RE = /^https?:\/\/|^mailto:|^tel:|^#|^\//i;
5884
- function getLinkValidationMessage(raw) {
5885
- const trimmed = raw.trim();
5886
- if (!trimmed) return void 0;
5887
- if (/\s/.test(trimmed)) return "URL cannot contain spaces";
5888
- if (!VERBATIM_LINK_RE.test(trimmed) && !trimmed.includes(".")) {
5889
- return "Enter a valid URL (e.g. example.com)";
5890
- }
5891
- return void 0;
5892
- }
5893
6483
  var InsertLinkPlugin = ({
5894
6484
  disabled,
5895
- open: externalOpen,
5896
- onClose
6485
+ setIsLinkEditMode
5897
6486
  }) => {
5898
6487
  const [editor] = useLexicalComposerContext();
5899
- const [internalOpen, setInternalOpen] = useState(false);
5900
- const [text, setText] = useState("");
5901
- const [link, setLink] = useState("");
5902
6488
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
5903
- const isControlled = externalOpen !== void 0;
5904
- const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
5905
- const linkError = getLinkValidationMessage(link);
5906
- const handleClose = () => {
5907
- setText("");
5908
- setLink("");
5909
- if (isControlled) onClose?.();
5910
- else setInternalOpen(false);
5911
- };
5912
- const insertLink = (text2, link2) => {
6489
+ const insertLink = () => {
5913
6490
  if (disabled) return;
5914
- if (getLinkValidationMessage(link2)) return;
5915
- const trimmedLink = link2.trim();
5916
- const href = VERBATIM_LINK_RE.test(trimmedLink) ? trimmedLink : `https://${trimmedLink}`;
5917
6491
  editor.update(() => {
5918
6492
  const selection = $getSelection();
5919
- if ($isRangeSelection(selection)) {
5920
- const textNode = new TextNode(text2);
5921
- const linkNode = $createLinkNode(href);
5922
- linkNode.append(textNode);
5923
- 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://");
5924
6498
  }
5925
6499
  });
5926
- handleClose();
6500
+ setIsLinkEditMode(true);
5927
6501
  };
5928
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5929
- !isControlled && /* @__PURE__ */ jsx(
5930
- Button,
5931
- {
5932
- size: "small",
5933
- title: "Add link",
5934
- disabled,
5935
- icon: /* @__PURE__ */ jsx(LinkAddRegular, { style: { color: iconColor } }),
5936
- style: {
5937
- background: isOpen && !disabled ? "#ebebeb" : "none",
5938
- border: "none",
5939
- margin: 2,
5940
- opacity: disabled ? 0.55 : 1,
5941
- cursor: disabled ? "not-allowed" : "pointer"
5942
- },
5943
- onClick: () => {
5944
- if (disabled) return;
5945
- setInternalOpen((prev) => !prev);
5946
- }
6502
+ return /* @__PURE__ */ jsx(
6503
+ Button,
6504
+ {
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"
5947
6515
  },
5948
- "upload-link"
5949
- ),
5950
- /* @__PURE__ */ jsx(
5951
- Dialog,
5952
- {
5953
- open: isOpen,
5954
- onOpenChange: (_, data) => {
5955
- if (!data.open) handleClose();
5956
- },
5957
- children: /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: "380px" }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
5958
- /* @__PURE__ */ jsx(DialogTitle, { children: "Insert Link" }),
5959
- /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
5960
- /* @__PURE__ */ jsx(Field, { label: "Text", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
5961
- Input,
5962
- {
5963
- autoFocus: !disabled,
5964
- value: text,
5965
- appearance: "underline",
5966
- placeholder: "Text",
5967
- disabled,
5968
- onChange: (_, v) => setText(v.value)
5969
- }
5970
- ) }),
5971
- /* @__PURE__ */ jsx(
5972
- Field,
5973
- {
5974
- label: "Link",
5975
- orientation: "horizontal",
5976
- size: "small",
5977
- validationState: linkError ? "error" : "none",
5978
- validationMessage: linkError,
5979
- children: /* @__PURE__ */ jsx(
5980
- Input,
5981
- {
5982
- value: link,
5983
- appearance: "underline",
5984
- placeholder: "Link",
5985
- disabled,
5986
- onChange: (_, v) => setLink(v.value),
5987
- onKeyDown: (e) => {
5988
- if (e.key === "Enter" && text && link && !linkError) insertLink(text, link);
5989
- }
5990
- }
5991
- )
5992
- }
5993
- )
5994
- ] }) }),
5995
- /* @__PURE__ */ jsxs(DialogActions, { children: [
5996
- /* @__PURE__ */ jsx(
5997
- Button,
5998
- {
5999
- appearance: "primary",
6000
- size: "small",
6001
- disabled: disabled || !text || !link || !!linkError,
6002
- onClick: () => insertLink(text, link),
6003
- children: "Add"
6004
- }
6005
- ),
6006
- /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: handleClose, children: "Cancel" })
6007
- ] })
6008
- ] }) })
6009
- }
6010
- )
6011
- ] });
6516
+ onClick: insertLink
6517
+ },
6518
+ "insert-link"
6519
+ );
6012
6520
  };
6013
6521
  function PageSetupPlugin({ disabled, value, onChange }) {
6014
6522
  const sizeLabel = value.size === "pageless" ? "Pageless" : PAGE_SIZE_OPTIONS.find((o) => o.key === value.size)?.label ?? "Pageless";
@@ -6064,281 +6572,177 @@ function PageSetupPlugin({ disabled, value, onChange }) {
6064
6572
  }
6065
6573
  );
6066
6574
  }
6067
- var MAX_ROWS = 50;
6068
- var MAX_COLS = 50;
6069
- var TableItemPlugin = ({
6070
- disabled,
6071
- open: externalOpen,
6072
- onClose
6073
- }) => {
6575
+ var TableItemPlugin = ({ disabled }) => {
6074
6576
  const [editor] = useLexicalComposerContext();
6075
6577
  const [columns, setColumns] = useState("");
6076
6578
  const [rows, setRows] = useState("");
6077
- const [internalOpen, setInternalOpen] = useState(false);
6078
- const [rowError, setRowError] = useState("");
6079
- const [colError, setColError] = useState("");
6080
- const isControlled = externalOpen !== void 0;
6081
- const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
6579
+ const [isOpen, setIsOpen] = useState(false);
6082
6580
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#333333";
6083
- const handleClose = () => {
6084
- setRows("");
6085
- setColumns("");
6086
- setRowError("");
6087
- setColError("");
6088
- if (isControlled) onClose?.();
6089
- else setInternalOpen(false);
6090
- };
6091
- const onRowsChange = (val) => {
6092
- const clean = val.replace(/\D/g, "");
6093
- setRows(clean);
6094
- const n = Number(clean);
6095
- if (clean && n > MAX_ROWS) setRowError(`Maximum ${MAX_ROWS} rows allowed`);
6096
- else setRowError("");
6097
- };
6098
- const onColsChange = (val) => {
6099
- const clean = val.replace(/\D/g, "");
6100
- setColumns(clean);
6101
- const n = Number(clean);
6102
- if (clean && n > MAX_COLS) setColError(`Maximum ${MAX_COLS} columns allowed`);
6103
- else setColError("");
6104
- };
6105
6581
  const onAddTable = () => {
6106
6582
  if (disabled) return;
6107
6583
  const row = Number(rows);
6108
6584
  const col = Number(columns);
6109
6585
  if (!row || !col) return;
6110
- if (row > MAX_ROWS || col > MAX_COLS) return;
6111
6586
  editor.update(() => {
6112
6587
  const tableNode = $createTableNodeWithDimensions(row, col, true);
6113
6588
  $insertNodeToNearestRoot(tableNode);
6114
6589
  });
6115
- handleClose();
6590
+ setRows("");
6591
+ setColumns("");
6592
+ setIsOpen(false);
6116
6593
  };
6117
- const isAddDisabled = disabled || !rows || !columns || !!rowError || !!colError;
6118
- return /* @__PURE__ */ jsxs(Fragment, { children: [
6119
- !isControlled && /* @__PURE__ */ jsx(
6120
- Button,
6121
- {
6122
- size: "small",
6123
- title: "Add table",
6124
- disabled,
6125
- icon: /* @__PURE__ */ jsx(TableAddRegular, { style: { color: iconColor } }),
6126
- style: {
6127
- background: isOpen && !disabled ? "#ebebeb" : "none",
6128
- border: "none",
6129
- margin: 2,
6130
- opacity: disabled ? 0.55 : 1,
6131
- cursor: disabled ? "not-allowed" : "pointer"
6132
- },
6133
- onClick: () => {
6134
- if (disabled) return;
6135
- setRows("");
6136
- setColumns("");
6137
- setRowError("");
6138
- setColError("");
6139
- setInternalOpen(true);
6140
- }
6594
+ return /* @__PURE__ */ jsxs(
6595
+ Dialog,
6596
+ {
6597
+ open: disabled ? false : isOpen,
6598
+ onOpenChange: (_, data) => {
6599
+ if (!disabled) setIsOpen(data.open);
6141
6600
  },
6142
- "insert-table-nodes"
6143
- ),
6144
- /* @__PURE__ */ jsx(
6145
- Dialog,
6146
- {
6147
- open: isOpen,
6148
- onOpenChange: (_, data) => {
6149
- if (!data.open) handleClose();
6150
- },
6151
- children: /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: "380px" }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
6152
- /* @__PURE__ */ jsx(DialogTitle, { children: "Insert Table" }),
6153
- /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
6154
- /* @__PURE__ */ jsx(
6155
- Field,
6601
+ children: [
6602
+ /* @__PURE__ */ jsx(DialogTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
6603
+ Button,
6604
+ {
6605
+ size: "small",
6606
+ title: "Add table",
6607
+ disabled,
6608
+ icon: /* @__PURE__ */ jsx(TableAddRegular, { style: { color: iconColor } }),
6609
+ style: {
6610
+ background: isOpen && !disabled ? "#ebebeb" : "none",
6611
+ border: "none",
6612
+ margin: 2,
6613
+ opacity: disabled ? 0.55 : 1,
6614
+ cursor: disabled ? "not-allowed" : "pointer"
6615
+ },
6616
+ onClick: () => {
6617
+ if (disabled) return;
6618
+ setIsOpen((prev) => !prev);
6619
+ setRows("");
6620
+ setColumns("");
6621
+ }
6622
+ },
6623
+ "insert-table-nodes"
6624
+ ) }),
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,
6156
6630
  {
6157
- label: "Rows",
6158
- orientation: "horizontal",
6159
- size: "small",
6160
- validationMessage: rowError || void 0,
6161
- validationState: rowError ? "error" : "none",
6162
- children: /* @__PURE__ */ jsx(
6163
- Input,
6164
- {
6165
- autoFocus: !disabled,
6166
- type: "number",
6167
- min: 1,
6168
- max: MAX_ROWS,
6169
- value: rows,
6170
- placeholder: "Rows",
6171
- appearance: "underline",
6172
- disabled,
6173
- input: { style: { textAlign: "left" } },
6174
- onChange: (_, v) => onRowsChange(v.value)
6175
- }
6176
- )
6631
+ autoFocus: !disabled,
6632
+ value: rows,
6633
+ placeholder: "Rows",
6634
+ appearance: "underline",
6635
+ disabled,
6636
+ onChange: (_, v) => setRows(v.value)
6177
6637
  }
6178
- ),
6179
- /* @__PURE__ */ jsx(
6180
- Field,
6638
+ ) }),
6639
+ /* @__PURE__ */ jsx(Field, { label: "Columns", size: "small", children: /* @__PURE__ */ jsx(
6640
+ Input,
6181
6641
  {
6182
- label: "Columns",
6183
- orientation: "horizontal",
6184
- size: "small",
6185
- validationMessage: colError || void 0,
6186
- validationState: colError ? "error" : "none",
6187
- children: /* @__PURE__ */ jsx(
6188
- Input,
6189
- {
6190
- type: "number",
6191
- min: 1,
6192
- max: MAX_COLS,
6193
- value: columns,
6194
- placeholder: "Columns",
6195
- appearance: "underline",
6196
- disabled,
6197
- input: { style: { textAlign: "left" } },
6198
- onChange: (_, v) => onColsChange(v.value)
6199
- }
6200
- )
6642
+ value: columns,
6643
+ placeholder: "Columns",
6644
+ appearance: "underline",
6645
+ disabled,
6646
+ onChange: (_, v) => setColumns(v.value)
6201
6647
  }
6202
- )
6648
+ ) })
6203
6649
  ] }) }),
6204
6650
  /* @__PURE__ */ jsxs(DialogActions, { children: [
6205
6651
  /* @__PURE__ */ jsx(
6206
6652
  Button,
6207
6653
  {
6208
- appearance: "primary",
6209
6654
  size: "small",
6210
- disabled: isAddDisabled,
6655
+ appearance: "primary",
6656
+ disabled: disabled || !rows || !columns,
6211
6657
  onClick: onAddTable,
6212
6658
  children: "Add"
6213
6659
  }
6214
6660
  ),
6215
- /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: handleClose, children: "Cancel" })
6661
+ /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: () => setIsOpen(false), children: "Cancel" })
6216
6662
  ] })
6217
6663
  ] }) })
6218
- }
6219
- )
6220
- ] });
6664
+ ]
6665
+ }
6666
+ );
6221
6667
  };
6222
- function extractYouTubeId(url) {
6223
- const trimmed = url.trim();
6224
- if (/^[\w-]{11}$/.test(trimmed)) return trimmed;
6225
- const match = /(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/|live\/|u\/\w\/))([^#&?]{11})/.exec(trimmed);
6226
- return match ? match[1] : null;
6227
- }
6228
- var YoutubeUploadPlugin = ({
6229
- disabled,
6230
- open: externalOpen,
6231
- onClose
6232
- }) => {
6668
+ var YoutubeUploadPlugin = ({ disabled }) => {
6233
6669
  const [url, setURL] = useState("");
6234
- const [urlError, setUrlError] = useState("");
6235
- const [internalOpen, setInternalOpen] = useState(false);
6670
+ const [isOpen, setIsOpen] = useState(false);
6236
6671
  const [editor] = useLexicalComposerContext();
6237
6672
  const iconColor = disabled ? "var(--colorNeutralForegroundDisabled, #A6A6A6)" : "#424242";
6238
- const isControlled = externalOpen !== void 0;
6239
- const isOpen = isControlled ? !!externalOpen && !disabled : internalOpen && !disabled;
6240
- const handleClose = () => {
6241
- setURL("");
6242
- setUrlError("");
6243
- if (isControlled) onClose?.();
6244
- else setInternalOpen(false);
6245
- };
6246
6673
  const onHandleEmbeded = () => {
6247
6674
  if (disabled) return;
6248
6675
  if (!url) return;
6249
- const id = extractYouTubeId(url);
6250
- if (!id) {
6251
- setUrlError("Invalid YouTube URL. Supported: watch?v=, youtu.be/, /shorts/, /live/");
6252
- return;
6253
- }
6254
- setUrlError("");
6676
+ const match = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/.exec(url);
6677
+ const id = match && match[2]?.length === 11 ? match[2] : null;
6678
+ if (!id) return;
6255
6679
  editor.update(() => {
6256
6680
  const node = $createYouTubeNode(id);
6257
6681
  $insertNodes([node]);
6258
6682
  });
6259
- handleClose();
6683
+ setURL("");
6684
+ setIsOpen(false);
6260
6685
  };
6261
- return /* @__PURE__ */ jsxs(Fragment, { children: [
6262
- !isControlled && /* @__PURE__ */ jsx(
6263
- Button,
6264
- {
6265
- title: "Add youtube URL",
6266
- size: "small",
6267
- disabled,
6268
- icon: /* @__PURE__ */ jsx(VideoClipRegular, { style: { color: iconColor } }),
6269
- style: {
6270
- background: isOpen && !disabled ? "#ebebeb" : "none",
6271
- border: "none",
6272
- margin: 2,
6273
- opacity: disabled ? 0.55 : 1,
6274
- cursor: disabled ? "not-allowed" : "pointer"
6275
- },
6276
- onClick: () => {
6277
- if (disabled) return;
6278
- setURL("");
6279
- setUrlError("");
6280
- setInternalOpen(true);
6281
- }
6686
+ return /* @__PURE__ */ jsxs(
6687
+ Dialog,
6688
+ {
6689
+ open: disabled ? false : isOpen,
6690
+ onOpenChange: (_, data) => {
6691
+ if (!disabled) setIsOpen(data.open);
6282
6692
  },
6283
- "upload-video"
6284
- ),
6285
- /* @__PURE__ */ jsx(
6286
- Dialog,
6287
- {
6288
- open: isOpen,
6289
- onOpenChange: (_, data) => {
6290
- if (!data.open) handleClose();
6291
- },
6292
- children: /* @__PURE__ */ jsx(DialogSurface, { style: { maxWidth: "420px" }, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
6293
- /* @__PURE__ */ jsx(DialogTitle, { children: "Insert YouTube Video" }),
6294
- /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, style: { paddingTop: 8 }, children: [
6295
- /* @__PURE__ */ jsx(
6296
- Field,
6297
- {
6298
- label: "URL",
6299
- orientation: "horizontal",
6300
- size: "small",
6301
- validationState: urlError ? "error" : "none",
6302
- validationMessage: urlError || void 0,
6303
- children: /* @__PURE__ */ jsx(
6304
- Input,
6305
- {
6306
- autoFocus: !disabled,
6307
- disabled,
6308
- value: url,
6309
- appearance: "underline",
6310
- placeholder: "Paste YouTube URL or video ID\u2026",
6311
- onChange: (_, v) => {
6312
- setURL(v.value);
6313
- if (urlError) setUrlError("");
6314
- },
6315
- onKeyDown: (e) => {
6316
- if (e.key === "Enter") onHandleEmbeded();
6317
- }
6318
- }
6319
- )
6320
- }
6321
- ),
6322
- /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#94a3b8", lineHeight: 1.5 }, children: "Supports: youtube.com/watch?v=\u2026, youtu.be/\u2026, /shorts/\u2026, /live/\u2026" })
6323
- ] }) }),
6324
- /* @__PURE__ */ jsxs(DialogActions, { children: [
6325
- /* @__PURE__ */ jsx(
6326
- Button,
6327
- {
6328
- appearance: "primary",
6329
- size: "small",
6330
- disabled: disabled || !url,
6331
- onClick: onHandleEmbeded,
6332
- children: "Add"
6333
- }
6334
- ),
6335
- /* @__PURE__ */ jsx(Button, { size: "small", disabled, onClick: handleClose, children: "Cancel" })
6693
+ children: [
6694
+ /* @__PURE__ */ jsx(DialogTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
6695
+ Button,
6696
+ {
6697
+ title: "Add youtube URL",
6698
+ size: "small",
6699
+ disabled,
6700
+ icon: /* @__PURE__ */ jsx(VideoClipRegular, { style: { color: iconColor } }),
6701
+ style: {
6702
+ background: isOpen && !disabled ? "#ebebeb" : "none",
6703
+ border: "none",
6704
+ margin: 2,
6705
+ opacity: disabled ? 0.55 : 1,
6706
+ cursor: disabled ? "not-allowed" : "pointer"
6707
+ },
6708
+ onClick: () => {
6709
+ if (disabled) return;
6710
+ setIsOpen((prev) => !prev);
6711
+ setURL("");
6712
+ }
6713
+ },
6714
+ "upload-video"
6715
+ ) }),
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" })
6336
6732
  ] })
6337
6733
  ] }) })
6338
- }
6339
- )
6340
- ] });
6734
+ ]
6735
+ }
6736
+ );
6341
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
+ ] });
6342
6746
  var useStyles4 = makeStyles({
6343
6747
  dropdown: {
6344
6748
  minInlineSize: "90px",
@@ -6356,25 +6760,42 @@ var ALLOWED_TOKENS = {
6356
6760
  Bold: true,
6357
6761
  Italic: true,
6358
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,
6359
6781
  ColorPicker: true,
6360
6782
  Link: true,
6361
6783
  Table: true,
6362
6784
  Image: true,
6363
6785
  InlineImage: true,
6364
6786
  Youtube: true,
6365
- Insert: true,
6366
- Heading: true,
6367
6787
  FontFamily: true,
6368
6788
  FontSize: true,
6789
+ Align: true,
6790
+ Heading: true,
6369
6791
  Decorators: true,
6370
6792
  CodeBlock: true,
6371
- Align: true,
6372
6793
  PageSetup: true
6373
6794
  };
6374
6795
  function sanitizePluginGroups(groups) {
6375
- if (!groups || groups.length === 0) return [];
6796
+ if (!Array.isArray(groups) || groups.length === 0) return [];
6376
6797
  return groups.map(
6377
- (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)
6378
6799
  ).filter((g) => g.length > 0);
6379
6800
  }
6380
6801
  var ToolBarPlugins = (props) => {
@@ -6394,9 +6815,9 @@ var ToolBarPlugins = (props) => {
6394
6815
  const [isLowercase, setIsLowercase] = useState(false);
6395
6816
  const [isCapitalize, setIsCapitalize] = useState(false);
6396
6817
  const [alignment, setAlignment] = useState("left");
6397
- const [activeInsertDialog, setActiveInsertDialog] = useState(null);
6398
- const lastSelectionRef = React9__default.useRef(null);
6399
- 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);
6400
6821
  const pluginGroups = useMemo(() => sanitizePluginGroups(presetGroups), [presetGroups]);
6401
6822
  const updateToolbarPlugins = () => {
6402
6823
  const selection = $getSelection();
@@ -6438,6 +6859,10 @@ var ToolBarPlugins = (props) => {
6438
6859
  setSelectNodeType("paragraph");
6439
6860
  return;
6440
6861
  }
6862
+ if ($isAlphaListNode(element)) {
6863
+ setSelectNodeType("alpha");
6864
+ return;
6865
+ }
6441
6866
  if ($isListNode(element)) {
6442
6867
  const parentList = $getNearestNodeOfType(anchorNode, ListNode);
6443
6868
  const type2 = parentList ? parentList.getTag() : element.getTag();
@@ -6449,23 +6874,15 @@ var ToolBarPlugins = (props) => {
6449
6874
  ["paragraph", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "quote", "code"].includes(type) ? type : "paragraph"
6450
6875
  );
6451
6876
  };
6452
- const applyToBlock = React9__default.useCallback(
6453
- (fn) => {
6454
- editor.update(() => {
6455
- const saved = lastSelectionRef.current;
6456
- if (saved) $setSelection(saved.clone());
6457
- const sel = $getSelection();
6458
- if ($isRangeSelection(sel)) fn(sel);
6459
- });
6460
- },
6461
- [editor]
6462
- );
6463
6877
  const formatQuote = () => {
6464
- applyToBlock((selection) => {
6878
+ editor.update(() => {
6879
+ const selection = $getSelection();
6880
+ if (!$isRangeSelection(selection)) return;
6465
6881
  if (selectNodeType === "quote") {
6466
- $setBlocksType(selection, () => $createParagraphNode());
6882
+ formatParagraph(editor);
6467
6883
  } else {
6468
- $setBlocksType(selection, () => $createQuoteNode());
6884
+ $splitBlocksAtLineBreaks(selection);
6885
+ $setBlocksType($getSelection(), () => $createQuoteNode());
6469
6886
  }
6470
6887
  });
6471
6888
  };
@@ -6482,8 +6899,6 @@ var ToolBarPlugins = (props) => {
6482
6899
  editor.registerCommand(
6483
6900
  SELECTION_CHANGE_COMMAND,
6484
6901
  () => {
6485
- const sel = $getSelection();
6486
- if ($isRangeSelection(sel)) lastSelectionRef.current = sel.clone();
6487
6902
  updateToolbarPlugins();
6488
6903
  return false;
6489
6904
  },
@@ -6515,52 +6930,16 @@ var ToolBarPlugins = (props) => {
6515
6930
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, "highlight");
6516
6931
  break;
6517
6932
  case "leftAlign" /* LeftAlign */:
6518
- applyToBlock((sel) => {
6519
- const seen = /* @__PURE__ */ new Set();
6520
- sel.getNodes().forEach((n) => {
6521
- const t = n.getTopLevelElementOrThrow();
6522
- if (!seen.has(t.getKey())) {
6523
- seen.add(t.getKey());
6524
- t.setFormat("left");
6525
- }
6526
- });
6527
- });
6933
+ applyAlignmentWithSplit("left");
6528
6934
  break;
6529
6935
  case "rightAlign" /* RightAlign */:
6530
- applyToBlock((sel) => {
6531
- const seen = /* @__PURE__ */ new Set();
6532
- sel.getNodes().forEach((n) => {
6533
- const t = n.getTopLevelElementOrThrow();
6534
- if (!seen.has(t.getKey())) {
6535
- seen.add(t.getKey());
6536
- t.setFormat("right");
6537
- }
6538
- });
6539
- });
6936
+ applyAlignmentWithSplit("right");
6540
6937
  break;
6541
6938
  case "centerAlign" /* CenterAlign */:
6542
- applyToBlock((sel) => {
6543
- const seen = /* @__PURE__ */ new Set();
6544
- sel.getNodes().forEach((n) => {
6545
- const t = n.getTopLevelElementOrThrow();
6546
- if (!seen.has(t.getKey())) {
6547
- seen.add(t.getKey());
6548
- t.setFormat("center");
6549
- }
6550
- });
6551
- });
6939
+ applyAlignmentWithSplit("center");
6552
6940
  break;
6553
6941
  case "justifyAlign" /* JustifyAlign */:
6554
- applyToBlock((sel) => {
6555
- const seen = /* @__PURE__ */ new Set();
6556
- sel.getNodes().forEach((n) => {
6557
- const t = n.getTopLevelElementOrThrow();
6558
- if (!seen.has(t.getKey())) {
6559
- seen.add(t.getKey());
6560
- t.setFormat("justify");
6561
- }
6562
- });
6563
- });
6942
+ applyAlignmentWithSplit("justify");
6564
6943
  break;
6565
6944
  case "undo" /* Undo */:
6566
6945
  editor.dispatchCommand(UNDO_COMMAND, void 0);
@@ -6579,9 +6958,32 @@ var ToolBarPlugins = (props) => {
6579
6958
  break;
6580
6959
  }
6581
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
+ };
6582
6973
  const updateHeading = (heading) => {
6583
- applyToBlock((selection) => {
6584
- $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();
6585
6987
  });
6586
6988
  };
6587
6989
  const renderToken = (token, groupIndex, tokenIndex) => {
@@ -6593,7 +6995,6 @@ var ToolBarPlugins = (props) => {
6593
6995
  const brand = palette.themePrimary;
6594
6996
  const brandHover = palette.themeDarkAlt ?? brand;
6595
6997
  const brandPressed = palette.themeDark ?? brand;
6596
- palette.white;
6597
6998
  const bgHover = palette.neutralLighter;
6598
6999
  const bgPressed = palette.neutralLight;
6599
7000
  const bgActive = palette.neutralLighterAlt ?? palette.neutralLighter;
@@ -6640,13 +7041,14 @@ var ToolBarPlugins = (props) => {
6640
7041
  marginRight: 8,
6641
7042
  verticalAlign: "middle"
6642
7043
  };
6643
- const isDisabled = !isEditable || !!props.readOnly;
6644
7044
  switch (token) {
6645
7045
  case "Bold":
6646
7046
  return /* @__PURE__ */ jsx(
6647
7047
  Button,
6648
7048
  {
6649
7049
  size: "small",
7050
+ "aria-label": "Bold",
7051
+ "aria-pressed": isBold,
6650
7052
  disabled: !isEditable || props.readOnly,
6651
7053
  icon: /* @__PURE__ */ jsx(TextBold24Regular, { style: { color: getIconColor(isBold) } }),
6652
7054
  style: getButtonStyle(isBold),
@@ -6659,6 +7061,8 @@ var ToolBarPlugins = (props) => {
6659
7061
  Button,
6660
7062
  {
6661
7063
  size: "small",
7064
+ "aria-label": "Italic",
7065
+ "aria-pressed": isItalic,
6662
7066
  disabled: !isEditable || props.readOnly,
6663
7067
  icon: /* @__PURE__ */ jsx(TextItalicFilled, { style: { color: getIconColor(isItalic) } }),
6664
7068
  style: getButtonStyle(isItalic),
@@ -6671,6 +7075,8 @@ var ToolBarPlugins = (props) => {
6671
7075
  Button,
6672
7076
  {
6673
7077
  size: "small",
7078
+ "aria-label": "Underline",
7079
+ "aria-pressed": isUnderline,
6674
7080
  disabled: !isEditable || props.readOnly,
6675
7081
  icon: /* @__PURE__ */ jsx(TextUnderlineFilled, { style: { color: getIconColor(isUnderline) } }),
6676
7082
  style: getButtonStyle(isUnderline),
@@ -6681,7 +7087,14 @@ var ToolBarPlugins = (props) => {
6681
7087
  case "ColorPicker":
6682
7088
  return /* @__PURE__ */ jsx(ColorPickerPlugin, { disabled: !isEditable || props.readOnly }, key);
6683
7089
  case "Link":
6684
- 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
+ );
6685
7098
  case "Table":
6686
7099
  return /* @__PURE__ */ jsx(TableItemPlugin, { disabled: !isEditable || props.readOnly }, key);
6687
7100
  case "Image":
@@ -6689,7 +7102,9 @@ var ToolBarPlugins = (props) => {
6689
7102
  InsertImageDialog,
6690
7103
  {
6691
7104
  activeEditor: editor,
6692
- disabled: !isEditable || props.readOnly
7105
+ disabled: !isEditable || props.readOnly,
7106
+ maxImageSizeMB: props.maxImageSizeMB,
7107
+ validationMessages: props.validationMessages
6693
7108
  },
6694
7109
  key
6695
7110
  );
@@ -6698,139 +7113,405 @@ var ToolBarPlugins = (props) => {
6698
7113
  InsertInlineImageDialog,
6699
7114
  {
6700
7115
  activeEditor: editor,
6701
- disabled: !isEditable || props.readOnly
7116
+ disabled: !isEditable || props.readOnly,
7117
+ maxImageSizeMB: props.maxImageSizeMB,
7118
+ validationMessages: props.validationMessages
6702
7119
  },
6703
7120
  key
6704
7121
  );
6705
7122
  case "Youtube":
6706
7123
  return /* @__PURE__ */ jsx(YoutubeUploadPlugin, { disabled: !isEditable || props.readOnly }, key);
6707
- case "Insert": {
6708
- const menuIconColor = isDisabled ? fgDisabled : fg;
6709
- return /* @__PURE__ */ jsxs(React9__default.Fragment, { children: [
6710
- /* @__PURE__ */ jsxs(Menu, { children: [
6711
- /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
6712
- Button,
6713
- {
6714
- size: "small",
6715
- disabled: isDisabled,
6716
- icon: /* @__PURE__ */ jsx(AddRegular, { style: { color: menuIconColor } }),
6717
- style: {
6718
- ...getButtonStyle(false),
6719
- gap: 4,
6720
- paddingInline: 8
6721
- },
6722
- children: "Insert"
6723
- }
6724
- ) }),
6725
- /* @__PURE__ */ jsx(MenuPopover, { children: /* @__PURE__ */ jsxs(MenuList, { children: [
6726
- /* @__PURE__ */ jsx(
6727
- MenuItem,
6728
- {
6729
- icon: /* @__PURE__ */ jsx(TableAddRegular, { style: { color: menuIconColor } }),
6730
- onClick: () => !isDisabled && setActiveInsertDialog("table"),
6731
- children: "Table"
6732
- }
6733
- ),
6734
- /* @__PURE__ */ jsx(
6735
- MenuItem,
6736
- {
6737
- icon: /* @__PURE__ */ jsx(ImageAddRegular, { style: { color: menuIconColor } }),
6738
- onClick: () => !isDisabled && setActiveInsertDialog("image"),
6739
- children: "Image"
6740
- }
6741
- ),
6742
- /* @__PURE__ */ jsx(
6743
- MenuItem,
6744
- {
6745
- icon: /* @__PURE__ */ jsx(ImageEditRegular, { style: { color: menuIconColor } }),
6746
- onClick: () => !isDisabled && setActiveInsertDialog("inlineImage"),
6747
- children: "Inline Image"
6748
- }
6749
- ),
6750
- /* @__PURE__ */ jsx(
6751
- MenuItem,
6752
- {
6753
- icon: /* @__PURE__ */ jsx(VideoClipRegular, { style: { color: menuIconColor } }),
6754
- onClick: () => !isDisabled && setActiveInsertDialog("youtube"),
6755
- children: "YouTube"
6756
- }
6757
- )
6758
- ] }) })
6759
- ] }),
6760
- /* @__PURE__ */ jsx(
6761
- TableItemPlugin,
6762
- {
6763
- disabled: isDisabled,
6764
- open: activeInsertDialog === "table",
6765
- onClose: () => setActiveInsertDialog(null)
6766
- }
6767
- ),
6768
- /* @__PURE__ */ jsx(
6769
- InsertImageDialog,
6770
- {
6771
- activeEditor: editor,
6772
- disabled: isDisabled,
6773
- open: activeInsertDialog === "image",
6774
- onClose: () => setActiveInsertDialog(null)
6775
- }
6776
- ),
6777
- /* @__PURE__ */ jsx(
6778
- InsertInlineImageDialog,
6779
- {
6780
- activeEditor: editor,
6781
- disabled: isDisabled,
6782
- open: activeInsertDialog === "inlineImage",
6783
- onClose: () => setActiveInsertDialog(null)
6784
- }
6785
- ),
6786
- /* @__PURE__ */ jsx(
6787
- YoutubeUploadPlugin,
6788
- {
6789
- disabled: isDisabled,
6790
- open: activeInsertDialog === "youtube",
6791
- onClose: () => setActiveInsertDialog(null)
6792
- }
6793
- )
6794
- ] }, key);
6795
- }
6796
7124
  case "Heading": {
6797
7125
  const headingLabel = selectNodeType === "paragraph" ? "Normal" : HEADING_OPTION.find((h) => h.key === selectNodeType)?.text ?? selectNodeType;
6798
7126
  return /* @__PURE__ */ jsxs(
6799
7127
  Dropdown,
6800
7128
  {
6801
- title: "Add heading",
6802
- id: `heading-option-${groupIndex}`,
6803
- placeholder: "Format",
6804
- disabled: !isEditable,
6805
- style: { minWidth: "100px" },
6806
- className: styles.dropdown,
6807
- value: headingLabel,
6808
- selectedOptions: [selectNodeType],
6809
- onOptionSelect: (_, data) => {
6810
- const val = data.optionValue;
6811
- if (!val) return;
6812
- if (val === "paragraph") {
6813
- applyToBlock((sel) => $setBlocksType(sel, () => $createParagraphNode()));
7129
+ title: "Add heading",
7130
+ id: `heading-option-${groupIndex}`,
7131
+ placeholder: "Format",
7132
+ disabled: !isEditable,
7133
+ style: { minWidth: "100px" },
7134
+ className: styles.dropdown,
7135
+ value: headingLabel,
7136
+ selectedOptions: [selectNodeType],
7137
+ onOptionSelect: (_, data) => {
7138
+ const val = data.optionValue;
7139
+ if (!val) return;
7140
+ if (val === "paragraph") {
7141
+ formatParagraph(editor);
7142
+ setSelectNodeType("paragraph");
7143
+ } else {
7144
+ updateHeading(val);
7145
+ setSelectNodeType(val);
7146
+ }
7147
+ },
7148
+ children: [
7149
+ /* @__PURE__ */ jsx(Option, { value: "paragraph", children: "Normal" }),
7150
+ HEADING_OPTION.map((option, idx) => /* @__PURE__ */ jsx(Option, { value: option.key, children: option.text }, `${option.key}-${idx}`))
7151
+ ]
7152
+ },
7153
+ key
7154
+ );
7155
+ }
7156
+ case "FontFamily":
7157
+ return /* @__PURE__ */ jsx(FontFamilyPlugin, { disabled: !isEditable || props.readOnly }, key);
7158
+ case "FontSize":
7159
+ return /* @__PURE__ */ jsx(FontSizePlugin, { disabled: !isEditable || props.readOnly }, key);
7160
+ case "|":
7161
+ return /* @__PURE__ */ jsx(ToolbarDivider, {}, key);
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,
7168
+ {
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);
6814
7478
  setSelectNodeType("paragraph");
6815
7479
  } else {
6816
- updateHeading(val);
6817
- setSelectNodeType(val);
7480
+ updateHeading("h5");
7481
+ setSelectNodeType("h5");
6818
7482
  }
6819
7483
  },
6820
- children: [
6821
- /* @__PURE__ */ jsx(Option, { value: "paragraph", children: "Normal" }),
6822
- HEADING_OPTION.map((option, idx) => /* @__PURE__ */ jsx(Option, { value: option.key, children: option.text }, `${option.key}-${idx}`))
6823
- ]
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"
6824
7512
  },
6825
7513
  key
6826
7514
  );
6827
- }
6828
- case "FontFamily":
6829
- return /* @__PURE__ */ jsx(FontFamilyPlugin, { disabled: !isEditable || props.readOnly }, key);
6830
- case "FontSize":
6831
- return /* @__PURE__ */ jsx(FontSizePlugin, { disabled: !isEditable || props.readOnly }, key);
6832
- case "|":
6833
- return /* @__PURE__ */ jsx(ToolbarDivider, {}, key);
6834
7515
  case "Decorators": {
6835
7516
  const activeDecorators = [
6836
7517
  ...isUppercase ? ["uppercase"] : [],
@@ -6842,6 +7523,7 @@ var ToolBarPlugins = (props) => {
6842
7523
  ...isHighlight ? ["highlight"] : [],
6843
7524
  ...selectNodeType === "ul" ? ["ul-list"] : [],
6844
7525
  ...selectNodeType === "ol" ? ["ol-list"] : [],
7526
+ ...selectNodeType === "alpha" ? ["al-list"] : [],
6845
7527
  ...selectNodeType === "quote" ? ["quote"] : []
6846
7528
  ];
6847
7529
  const DECORATOR_LABEL = {
@@ -6854,6 +7536,7 @@ var ToolBarPlugins = (props) => {
6854
7536
  highlight: "Highlight",
6855
7537
  "ul-list": "Bullet list",
6856
7538
  "ol-list": "Number list",
7539
+ "al-list": "Alphabetical list",
6857
7540
  quote: "Quote"
6858
7541
  };
6859
7542
  const decoratorValue = activeDecorators.length === 0 ? "" : activeDecorators.length === 1 ? DECORATOR_LABEL[activeDecorators[0]] : `${DECORATOR_LABEL[activeDecorators[0]]} +${activeDecorators.length - 1}`;
@@ -6870,7 +7553,16 @@ var ToolBarPlugins = (props) => {
6870
7553
  button: { style: dropdownButtonStyle },
6871
7554
  expandIcon: { style: dropdownExpandIconStyle },
6872
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
+ },
6873
7564
  onOptionSelect: (_, data) => {
7565
+ decoratorSelectingRef.current = true;
6874
7566
  switch (data.optionValue) {
6875
7567
  case "uppercase":
6876
7568
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, "uppercase");
@@ -6894,10 +7586,19 @@ var ToolBarPlugins = (props) => {
6894
7586
  onHandleSelectOption("highlight" /* Highlight */);
6895
7587
  break;
6896
7588
  case "ul-list":
6897
- editor.dispatchCommand(selectNodeType === "ul" ? REMOVE_LIST_COMMAND : INSERT_UNORDERED_LIST_COMMAND, void 0);
7589
+ editor.dispatchCommand(
7590
+ selectNodeType === "ul" ? REMOVE_LIST_COMMAND : INSERT_UNORDERED_LIST_COMMAND,
7591
+ void 0
7592
+ );
6898
7593
  break;
6899
7594
  case "ol-list":
6900
- editor.dispatchCommand(selectNodeType === "ol" ? REMOVE_LIST_COMMAND : INSERT_ORDERED_LIST_COMMAND, void 0);
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());
6901
7602
  break;
6902
7603
  case "page-break":
6903
7604
  editor.dispatchCommand(INSERT_PAGE_BREAK, void 0);
@@ -6933,7 +7634,12 @@ var ToolBarPlugins = (props) => {
6933
7634
  "Superscript"
6934
7635
  ] }),
6935
7636
  /* @__PURE__ */ jsxs(Option, { value: "highlight", text: "Highlight", children: [
6936
- /* @__PURE__ */ jsx(HighlightAccentFilled, { style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled } }),
7637
+ /* @__PURE__ */ jsx(
7638
+ HighlightAccentFilled,
7639
+ {
7640
+ style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled }
7641
+ }
7642
+ ),
6937
7643
  "Highlight"
6938
7644
  ] }),
6939
7645
  /* @__PURE__ */ jsxs(Option, { value: "ul-list", text: "Bullet list", children: [
@@ -6944,6 +7650,10 @@ var ToolBarPlugins = (props) => {
6944
7650
  /* @__PURE__ */ jsx(TextNumberListLtrFilled, { style: optionIconStyle }),
6945
7651
  "Number list"
6946
7652
  ] }),
7653
+ /* @__PURE__ */ jsxs(Option, { value: "al-list", text: "Alphabetical list", children: [
7654
+ /* @__PURE__ */ jsx(TextAlphaListLtrFilled, { style: optionIconStyle }),
7655
+ "Alphabetical list"
7656
+ ] }),
6947
7657
  /* @__PURE__ */ jsxs(Option, { value: "page-break", text: "Page Break", children: [
6948
7658
  /* @__PURE__ */ jsx(DocumentPageBreakRegular, { style: optionIconStyle }),
6949
7659
  "Page break"
@@ -6969,10 +7679,30 @@ var ToolBarPlugins = (props) => {
6969
7679
  // );
6970
7680
  case "Align": {
6971
7681
  const ALIGN_OPTIONS = [
6972
- { value: "left", label: "Left Align", icon: /* @__PURE__ */ jsx(TextAlignLeftFilled, { style: optionIconStyle }), action: "leftAlign" /* LeftAlign */ },
6973
- { value: "center", label: "Center Align", icon: /* @__PURE__ */ jsx(TextAlignCenterFilled, { style: optionIconStyle }), action: "centerAlign" /* CenterAlign */ },
6974
- { value: "right", label: "Right Align", icon: /* @__PURE__ */ jsx(TextAlignRightFilled, { style: optionIconStyle }), action: "rightAlign" /* RightAlign */ },
6975
- { 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
+ }
6976
7706
  ];
6977
7707
  const alignLabel = ALIGN_OPTIONS.find((o) => o.value === alignment)?.label ?? "Left Align";
6978
7708
  return /* @__PURE__ */ jsx(
@@ -7188,32 +7918,31 @@ function BrowserSpellCheckPlugin({ enabled }) {
7188
7918
  }, [editor, enabled]);
7189
7919
  return null;
7190
7920
  }
7191
- function WordCountPlugin({
7192
- onCountChange
7193
- }) {
7194
- const [editor] = useLexicalComposerContext();
7195
- useEffect(() => {
7196
- return editor.registerUpdateListener(({ editorState }) => {
7197
- editorState.read(() => {
7198
- const text = $getRoot().getTextContent();
7199
- const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
7200
- onCountChange(words);
7201
- });
7202
- });
7203
- }, [editor, onCountChange]);
7204
- return null;
7205
- }
7206
- function CharCountPlugin({
7207
- onCountChange
7921
+ function ContentMetricsPlugin({
7922
+ onMetricsChange
7208
7923
  }) {
7209
7924
  const [editor] = useLexicalComposerContext();
7925
+ const prevRef = useRef({ words: 0, chars: 0, images: 0, links: 0, tables: 0 });
7210
7926
  useEffect(() => {
7211
- return editor.registerUpdateListener(({ editorState }) => {
7212
- editorState.read(() => {
7213
- onCountChange($getRoot().getTextContent().length);
7214
- });
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
+ }
7215
7944
  });
7216
- }, [editor, onCountChange]);
7945
+ }, [editor, onMetricsChange]);
7217
7946
  return null;
7218
7947
  }
7219
7948
  function FocusEventsPlugin({
@@ -7234,7 +7963,8 @@ function FocusEventsPlugin({
7234
7963
  const next = e.relatedTarget;
7235
7964
  const container = containerRef.current;
7236
7965
  const stillInside = !!next && (container ? container.contains(next) : root.contains(next));
7237
- if (stillInside) return;
7966
+ const isEditorPortal = !!next?.closest?.("[data-lexical-editor-portal]");
7967
+ if (stillInside || isEditorPortal) return;
7238
7968
  editor.update(() => {
7239
7969
  $setSelection(null);
7240
7970
  });
@@ -7289,10 +8019,7 @@ function _adaptRawSpell(data, text) {
7289
8019
  }
7290
8020
  const rawGrammar = data.grammar_correction ?? data.improved_text;
7291
8021
  const grammarCorrection = rawGrammar && rawGrammar.trim() !== text.trim() ? rawGrammar.trim() : void 0;
7292
- return {
7293
- issues: issues.sort((a, b) => a.offset - b.offset),
7294
- grammarCorrection
7295
- };
8022
+ return { issues: issues.sort((a, b) => a.offset - b.offset), grammarCorrection };
7296
8023
  }
7297
8024
  function _adaptRawSuggest(data) {
7298
8025
  if (!data) return null;
@@ -7347,386 +8074,458 @@ function _makeQueryFn(fn) {
7347
8074
  };
7348
8075
  };
7349
8076
  }
7350
- var ContentEditorComponent = forwardRef((props, ref) => {
7351
- const isReadOnly = !!props.readOnly;
7352
- const resolvedSpellCheck = React9__default.useMemo(
7353
- () => props.spellCheckFn ? _makeSpellCheckFn(props.spellCheckFn) : props.useSpellCheck,
7354
- // eslint-disable-next-line react-hooks/exhaustive-deps
7355
- [props.spellCheckFn, props.useSpellCheck]
7356
- );
7357
- const resolvedQuery = React9__default.useMemo(
7358
- () => props.suggestFn ? _makeQueryFn(props.suggestFn) : props.useQuery,
7359
- // eslint-disable-next-line react-hooks/exhaustive-deps
7360
- [props.suggestFn, props.useQuery]
7361
- );
7362
- const [floatingAnchorElem, setFloatingAnchorElem] = useState(null);
7363
- const [isLinkEditMode, setIsLinkEditMode] = useState(false);
7364
- const [wordCount, setWordCount] = useState(0);
7365
- const handleWordCount = useCallback(
7366
- (count) => setWordCount(count),
7367
- []
7368
- );
7369
- const [charCount, setCharCount] = useState(0);
7370
- const handleCharCount = useCallback(
7371
- (count) => setCharCount(count),
7372
- []
7373
- );
7374
- const [refErrors, setRefErrors] = useState([]);
7375
- const [pageSetup, setPageSetup] = useState(DEFAULT_PAGE_SETUP);
7376
- const pageCanvas = resolvePageCanvasMetrics(pageSetup);
7377
- const contentEditableDomRef = useRef(null);
7378
- const previousOverLimitRef = useRef(false);
7379
- const focusedRef = useRef(false);
7380
- const setFocused = (focused) => {
7381
- focusedRef.current = focused;
7382
- };
7383
- const containerRef = useRef(null);
7384
- const onAnchorRef = (elem) => {
7385
- if (elem) setFloatingAnchorElem(elem);
7386
- };
7387
- const initialConfig = {
7388
- namespace: props.namespace,
7389
- theme,
7390
- onError: () => {
7391
- },
7392
- nodes: [
7393
- HeadingNode,
7394
- QuoteNode,
7395
- CodeHighlightNode,
7396
- CodeNode,
7397
- ListNode,
7398
- ListItemNode,
7399
- LinkNode,
7400
- AutoLinkNode,
7401
- TableNode,
7402
- TableRowNode,
7403
- TableCellNode,
7404
- ImageNode,
7405
- InlineImageNode,
7406
- YouTubeNode,
7407
- PageBreakNode,
7408
- AutocompleteNode,
7409
- SpellErrorNode,
7410
- HtmlBlockNode
7411
- ]
7412
- };
7413
- const EditorStyles = mergeStyleSets({
7414
- editorPlaceholder: {
7415
- color: "var(--colorNeutralForeground3, grey)",
7416
- position: "absolute",
7417
- top: props.level !== "none" /* None */ ? "17px" : "27px",
7418
- left: pageCanvas.paddingPx,
7419
- right: pageCanvas.paddingPx,
7420
- fontSize: "14px",
7421
- pointerEvents: "none",
7422
- userSelect: "none"
7423
- },
7424
- contentEditor: {
7425
- zIndex: 0,
7426
- flex: "auto",
7427
- outline: "none",
7428
- overflow: "auto",
7429
- marginTop: "0px",
7430
- position: "relative",
7431
- background: "var(--colorNeutralBackground1, #ffffff)",
7432
- justifyContent: "center",
7433
- height: props.contentHeight ?? "100%",
7434
- ...isReadOnly && {
7435
- cursor: "not-allowed",
7436
- opacity: 0.75,
7437
- userSelect: "text"
7438
- }
7439
- }
7440
- });
7441
- const urlRegExp = new RegExp(
7442
- /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/
7443
- );
7444
- const validateUrl = (url) => url === "https://" || urlRegExp.test(url);
7445
- const handleReadOnlyClickCapture = (e) => {
7446
- if (!isReadOnly) return;
7447
- const target = e.target;
7448
- const anchor = target?.closest?.("a");
7449
- if (anchor) {
7450
- e.preventDefault();
7451
- e.stopPropagation();
7452
- }
7453
- };
7454
- const [touched, setTouched] = useState(false);
7455
- const isOverLimit = props.wordLimit !== void 0 && wordCount > props.wordLimit;
7456
- const internalErrors = [];
7457
- if (isOverLimit) {
7458
- const m = props.errorMessages?.wordLimitExceeded;
7459
- internalErrors.push(
7460
- typeof m === "function" ? m(wordCount, props.wordLimit) : m ?? `Word limit exceeded (${wordCount} / ${props.wordLimit} words used)`
7461
- );
7462
- }
7463
- if (props.required && touched && wordCount === 0) {
7464
- internalErrors.push(
7465
- props.errorMessages?.required ?? "This field is required"
7466
- );
7467
- }
7468
- if (props.minWords !== void 0 && touched && wordCount < props.minWords) {
7469
- const m = props.errorMessages?.minWords;
7470
- internalErrors.push(
7471
- typeof m === "function" ? m(wordCount, props.minWords) : m ?? `Minimum ${props.minWords} words required (${wordCount} entered)`
7472
- );
7473
- }
7474
- if (props.maxChars !== void 0 && charCount > props.maxChars) {
7475
- const m = props.errorMessages?.maxCharsExceeded;
7476
- internalErrors.push(
7477
- typeof m === "function" ? m(charCount, props.maxChars) : m ?? `Character limit exceeded (${charCount} / ${props.maxChars} characters used)`
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
+ }
8087
+ var ContentEditorComponent = forwardRef(
8088
+ (props, ref) => {
8089
+ const isReadOnly = !!props.readOnly;
8090
+ const resolvedSpellCheck = React9__default.useMemo(
8091
+ () => props.spellCheckFn ? _makeSpellCheckFn(props.spellCheckFn) : props.useSpellCheck,
8092
+ // eslint-disable-next-line react-hooks/exhaustive-deps
8093
+ [props.spellCheckFn, props.useSpellCheck]
7478
8094
  );
7479
- }
7480
- if (props.minChars !== void 0 && touched && charCount < props.minChars && charCount > 0) {
7481
- const m = props.errorMessages?.minCharsRequired;
7482
- internalErrors.push(
7483
- typeof m === "function" ? m(charCount, props.minChars) : m ?? `Minimum ${props.minChars} characters required (${charCount} entered)`
8095
+ const resolvedQuery = React9__default.useMemo(
8096
+ () => props.suggestFn ? _makeQueryFn(props.suggestFn) : props.useQuery,
8097
+ // eslint-disable-next-line react-hooks/exhaustive-deps
8098
+ [props.suggestFn, props.useQuery]
7484
8099
  );
7485
- }
7486
- const allErrors = [
7487
- ...internalErrors,
7488
- ...props.errors ?? [],
7489
- ...refErrors
7490
- ];
7491
- const hasErrors = allErrors.length > 0;
7492
- const hasRedBorder = hasErrors;
7493
- useEffect(() => {
7494
- if (props.wordLimit === void 0 || !props.onWordLimitExceeded) return;
7495
- const wasOverLimit = previousOverLimitRef.current;
7496
- if (isOverLimit !== wasOverLimit) {
7497
- props.onWordLimitExceeded({
7498
- wordCount,
7499
- wordLimit: props.wordLimit,
7500
- exceeded: isOverLimit
7501
- });
7502
- previousOverLimitRef.current = isOverLimit;
7503
- }
7504
- }, [isOverLimit, wordCount, props.wordLimit, props.onWordLimitExceeded]);
7505
- 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(
7506
- Stack,
7507
- {
7508
- style: {
7509
- zIndex: 1e3,
7510
- background: "#fff",
7511
- borderRadius: "2px",
7512
- width: props.width ?? "100%",
7513
- height: props.height ?? "100%",
7514
- margin: props.margin ?? "5px auto",
7515
- border: `1px solid ${hasRedBorder ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
7516
- transition: "border-color 0.2s",
7517
- display: "flex",
7518
- flexDirection: "column"
8100
+ const [floatingAnchorElem, setFloatingAnchorElem] = useState(null);
8101
+ const [isLinkEditMode, setIsLinkEditMode] = useState(false);
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), []);
8110
+ const [pageSetup, setPageSetup] = useState(DEFAULT_PAGE_SETUP);
8111
+ const pageCanvas = resolvePageCanvasMetrics(pageSetup);
8112
+ const wordCount = metrics.words;
8113
+ const contentEditableDomRef = useRef(null);
8114
+ const previousOverLimitRef = useRef(false);
8115
+ const focusedRef = useRef(false);
8116
+ const setFocused = (focused) => {
8117
+ focusedRef.current = focused;
8118
+ };
8119
+ const containerRef = useRef(null);
8120
+ const onAnchorRef = (elem) => {
8121
+ if (elem) setFloatingAnchorElem(elem);
8122
+ };
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
+ }, []);
8154
+ const EditorStyles = mergeStyleSets({
8155
+ editorPlaceholder: {
8156
+ color: "var(--colorNeutralForeground3, grey)",
8157
+ position: "absolute",
8158
+ top: props.level !== "none" /* None */ ? "17px" : "27px",
8159
+ left: pageCanvas.paddingPx,
8160
+ right: pageCanvas.paddingPx,
8161
+ fontSize: "14px",
8162
+ pointerEvents: "none",
8163
+ userSelect: "none"
7519
8164
  },
7520
- children: [
7521
- /* @__PURE__ */ jsx(
7522
- "div",
7523
- {
7524
- style: {
7525
- pointerEvents: isReadOnly ? "none" : "auto",
7526
- position: "sticky",
7527
- opacity: isReadOnly ? 0.85 : 1
7528
- },
7529
- children: /* @__PURE__ */ jsx(
7530
- ToolBarPlugins,
8165
+ contentEditor: {
8166
+ zIndex: 0,
8167
+ flex: "auto",
8168
+ outline: "none",
8169
+ overflow: "auto",
8170
+ marginTop: "0px",
8171
+ position: "relative",
8172
+ background: "var(--colorNeutralBackground1, #ffffff)",
8173
+ justifyContent: "center",
8174
+ height: props.contentHeight ?? "100%",
8175
+ ...isReadOnly && {
8176
+ cursor: "not-allowed",
8177
+ opacity: 0.75,
8178
+ userSelect: "text"
8179
+ }
8180
+ }
8181
+ });
8182
+ const urlRegExp = new RegExp(
8183
+ /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/
8184
+ );
8185
+ const validateUrl = (url) => url === "https://" || urlRegExp.test(url);
8186
+ const handleReadOnlyClickCapture = (e) => {
8187
+ if (!isReadOnly) return;
8188
+ const target = e.target;
8189
+ const anchor = target?.closest?.("a");
8190
+ if (anchor) {
8191
+ e.preventDefault();
8192
+ e.stopPropagation();
8193
+ }
8194
+ };
8195
+ const isOverLimit = props.wordLimit !== void 0 && wordCount > props.wordLimit;
8196
+ useEffect(() => {
8197
+ if (props.wordLimit === void 0 || !props.onWordLimitExceeded) return;
8198
+ const wasOverLimit = previousOverLimitRef.current;
8199
+ if (isOverLimit !== wasOverLimit) {
8200
+ props.onWordLimitExceeded({
8201
+ wordCount,
8202
+ wordLimit: props.wordLimit,
8203
+ exceeded: isOverLimit
8204
+ });
8205
+ previousOverLimitRef.current = isOverLimit;
8206
+ }
8207
+ }, [isOverLimit, wordCount, props.wordLimit, props.onWordLimitExceeded]);
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",
7531
8295
  {
7532
- level: props.level ?? "basic" /* Basic */,
7533
- readOnly: props.readOnly,
7534
- pageSetup,
7535
- onPageSetupChange: setPageSetup
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,
8304
+ {
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
+ )
7536
8315
  }
7537
- )
7538
- }
7539
- ),
7540
- /* @__PURE__ */ jsxs(
7541
- "div",
7542
- {
7543
- style: {
7544
- position: "relative",
7545
- flexGrow: 1,
7546
- padding: "15px 0px",
7547
- overflowY: "scroll",
7548
- overflowX: "auto",
7549
- minWidth: 0,
7550
- background: pageCanvas.widthPx !== void 0 ? "#eef0f2" : void 0
7551
- },
7552
- onClickCapture: handleReadOnlyClickCapture,
7553
- children: [
7554
- /* @__PURE__ */ jsx(
7555
- RichTextPlugin,
7556
- {
7557
- ErrorBoundary: LexicalErrorBoundary,
7558
- contentEditable: /* @__PURE__ */ jsx(
7559
- "div",
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,
7560
8333
  {
7561
- className: "editor",
7562
- style: { height: "100%", position: "relative" },
7563
- ref: onAnchorRef,
7564
- children: /* @__PURE__ */ jsx(
7565
- ContentEditable,
8334
+ ErrorBoundary: LexicalErrorBoundary,
8335
+ contentEditable: /* @__PURE__ */ jsx(
8336
+ "div",
7566
8337
  {
7567
- ref: contentEditableDomRef,
7568
- className: css(EditorStyles.contentEditor),
7569
- style: {
7570
- paddingTop: props.level !== "none" /* None */ ? 0 : 10,
7571
- paddingLeft: pageCanvas.paddingPx,
7572
- paddingRight: pageCanvas.paddingPx,
7573
- maxWidth: pageCanvas.widthPx,
7574
- marginLeft: pageCanvas.widthPx !== void 0 ? "auto" : void 0,
7575
- marginRight: pageCanvas.widthPx !== void 0 ? "auto" : void 0,
7576
- 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
7577
- },
7578
- spellCheck: !resolvedSpellCheck,
7579
- autoCorrect: resolvedSpellCheck ? "off" : void 0,
7580
- autoCapitalize: resolvedSpellCheck ? "off" : void 0
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
+ )
7581
8360
  }
7582
- )
8361
+ ),
8362
+ placeholder: /* @__PURE__ */ jsx(Stack, { className: css(EditorStyles.editorPlaceholder), children: props.placeholder })
7583
8363
  }
7584
8364
  ),
7585
- placeholder: /* @__PURE__ */ jsx(Stack, { className: css(EditorStyles.editorPlaceholder), children: props.placeholder })
7586
- }
7587
- ),
7588
- props.wordLimit !== void 0 && /* @__PURE__ */ jsx(
7589
- "div",
7590
- {
7591
- style: {
7592
- position: "sticky",
7593
- bottom: 0,
7594
- display: "flex",
7595
- justifyContent: "flex-end",
7596
- paddingRight: 14,
7597
- pointerEvents: "none",
7598
- userSelect: "none"
7599
- },
7600
- children: /* @__PURE__ */ jsxs(
7601
- "span",
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",
7602
8367
  {
7603
8368
  style: {
7604
- fontSize: "11px",
7605
- color: isOverLimit ? "#c4272c" : "var(--colorNeutralForeground3, #aaa)",
7606
- fontWeight: isOverLimit ? 600 : 400,
7607
- transition: "color 0.2s, font-weight 0.2s"
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"
7608
8377
  },
7609
8378
  children: [
7610
- wordCount,
7611
- " / ",
7612
- props.wordLimit,
7613
- " words"
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",
8398
+ {
8399
+ style: {
8400
+ fontSize: "11px",
8401
+ color: hasValidationError ? "#c4272c" : "var(--colorNeutralForeground3, #aaa)",
8402
+ fontWeight: hasValidationError ? 600 : 400,
8403
+ transition: "color 0.2s, font-weight 0.2s"
8404
+ },
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
+ ]
8411
+ }
8412
+ )
7614
8413
  ]
7615
8414
  }
7616
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
+ ]
7617
8518
  }
7618
8519
  )
7619
- ]
7620
- }
7621
- ),
7622
- hasErrors && /* @__PURE__ */ jsx(
7623
- "div",
7624
- {
7625
- style: {
7626
- borderTop: "1px solid #fbd5d5",
7627
- background: "#fff8f8",
7628
- padding: "6px 12px 8px",
7629
- display: "flex",
7630
- flexDirection: "column",
7631
- gap: 4
7632
- },
7633
- children: allErrors.map((err, i) => /* @__PURE__ */ jsxs(
7634
- "div",
7635
- {
7636
- style: { display: "flex", alignItems: "center", gap: 6 },
7637
- children: [
7638
- /* @__PURE__ */ jsx(
7639
- ErrorCircleRegular,
7640
- {
7641
- style: { fontSize: 14, color: "#c4272c", flexShrink: 0 }
7642
- }
7643
- ),
7644
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#c4272c" }, children: err })
7645
- ]
7646
- },
7647
- i
7648
- ))
7649
- }
7650
- ),
7651
- /* @__PURE__ */ jsx(ReadOnlyPlugin, { readonly: isReadOnly }),
7652
- /* @__PURE__ */ jsx(BrowserSpellCheckPlugin, { enabled: !resolvedSpellCheck }),
7653
- /* @__PURE__ */ jsx(
7654
- FocusEventsPlugin,
7655
- {
7656
- onFocus: props.onFocus,
7657
- onBlur: () => {
7658
- setTouched(true);
7659
- props.onBlur?.();
7660
- },
7661
- setFocused,
7662
- containerRef
7663
- }
7664
- ),
7665
- props.autoFocus && !isReadOnly && /* @__PURE__ */ jsx(AutoFocusPlugin, {}),
7666
- /* @__PURE__ */ jsx(HistoryPlugin, {}),
7667
- /* @__PURE__ */ jsx(ListPlugin, {}),
7668
- /* @__PURE__ */ jsx(LinkPlugin, { validateUrl }),
7669
- /* @__PURE__ */ jsx(AutoLinkPlugin, { matchers: MATCHERS }),
7670
- /* @__PURE__ */ jsx(TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true }),
7671
- !isReadOnly && /* @__PURE__ */ jsx(YoutubeDeletePlugin, {}),
7672
- !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsx(TableActionMenuPlugin, {}),
7673
- !isReadOnly && floatingAnchorElem && /* @__PURE__ */ jsx(TableCellResizerPlugin, { anchorElem: floatingAnchorElem }),
7674
- !isReadOnly && /* @__PURE__ */ jsx(
7675
- FloatingLinkEditorPlugin,
7676
- {
7677
- anchorElem: floatingAnchorElem,
7678
- isLinkEditMode,
7679
- setIsLinkEditMode
7680
- }
7681
- ),
7682
- !isReadOnly && /* @__PURE__ */ jsx(ImagePlugin_default, {}),
7683
- !isReadOnly && /* @__PURE__ */ jsx(InlineImage_default, {}),
7684
- !isReadOnly && /* @__PURE__ */ jsx(PageBreakPlugin, {}),
7685
- !!resolvedQuery && !isReadOnly && /* @__PURE__ */ jsx(
7686
- AutocompletePlugin,
7687
- {
7688
- useQuery: resolvedQuery,
7689
- isReadOnly,
7690
- onSuggestionShown: props.onSuggestionShown,
7691
- onSuggestionAccept: props.onSuggestionAccept,
7692
- idleMs: props.suggestIdleMs ?? 300,
7693
- minWords: 4,
7694
- prefixWindow: 300
7695
- }
7696
- ),
7697
- !!resolvedSpellCheck && !isReadOnly && /* @__PURE__ */ jsx(
7698
- SpellCheckPlugin,
7699
- {
7700
- useSpellCheck: resolvedSpellCheck,
7701
- onSpellCheckAccept: props.onSpellCheckAccept,
7702
- idleMs: props.spellCheckIdleMs ?? 1200,
7703
- enabled: props.spellCheckEnabled !== false
7704
- }
7705
- ),
7706
- !isReadOnly && props.showFloatingToolbar && /* @__PURE__ */ jsx(CharacterStylesPopupPlugin, {}),
7707
- /* @__PURE__ */ jsx(
7708
- CustomOnChangePlugin,
7709
- {
7710
- value: props.value,
7711
- onChange: props.onChange
7712
- }
7713
- ),
7714
- (props.wordLimit !== void 0 || props.required || props.minWords !== void 0) && /* @__PURE__ */ jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7715
- (props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsx(CharCountPlugin, { onCountChange: handleCharCount }),
7716
- /* @__PURE__ */ jsx(
7717
- RefApiPlugin,
7718
- {
7719
- forwardedRef: ref,
7720
- contentEditableDomRef,
7721
- focusedRef,
7722
- setRefErrors
7723
- }
7724
- )
7725
- ]
7726
- }
7727
- ) }) }) });
7728
- });
8520
+ ] })
8521
+ ]
8522
+ }
8523
+ ) }),
8524
+ /* @__PURE__ */ jsx(EditorReadyPlugin, { onReady: props.onReady })
8525
+ ] }) });
8526
+ }
8527
+ );
7729
8528
 
7730
- export { ContentEditorComponent, ContentEditorLevel };
8529
+ export { ContentEditorComponent, ContentEditorLevel, DEFAULT_VALIDATION_MESSAGES };
7731
8530
  //# sourceMappingURL=index.mjs.map
7732
8531
  //# sourceMappingURL=index.mjs.map