@tarviks/lexical-rich-editor 1.0.4 → 1.0.6

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,9 +5,10 @@ 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, SELECTION_CHANGE_COMMAND, $getNodeByKey, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, $getSelection, $isRangeSelection, $findMatchingParent, COMMAND_PRIORITY_LOW, KEY_DOWN_COMMAND, $insertNodes, $isRootOrShadowRoot, $createParagraphNode, COMMAND_PRIORITY_EDITOR, PASTE_COMMAND, DRAGSTART_COMMAND, DRAGOVER_COMMAND, DROP_COMMAND, $getRoot, $createTextNode, $isTextNode, $createRangeSelection, $setSelection, $isElementNode, $isNodeSelection, CLICK_COMMAND, $applyNodeReplacement, FORMAT_TEXT_COMMAND, $isDecoratorNode, $getNearestNodeFromDOMNode, COMMAND_PRIORITY_CRITICAL, REDO_COMMAND, UNDO_COMMAND, FORMAT_ELEMENT_COMMAND, getDOMSelection, KEY_ESCAPE_COMMAND, isHTMLElement, getDOMSelectionFromTarget, createEditor, KEY_ENTER_COMMAND } from 'lexical';
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, createEditor, KEY_ENTER_COMMAND } from 'lexical';
9
9
  import { mergeStyleSets, Stack, css, useTheme, Callout, TextField, DefaultButton } from '@fluentui/react';
10
- import { makeStyles, FluentProvider, webLightTheme, Button, Menu, MenuPopover, MenuList, MenuGroup, MenuGroupHeader, MenuItem, MenuDivider, Dropdown, Option, ToolbarDivider, Popover, PopoverTrigger, PopoverSurface, Field, Input } from '@fluentui/react-components';
10
+ import { makeStyles, FluentProvider, webLightTheme, Menu, MenuTrigger, MenuPopover, MenuList, MenuGroup, MenuGroupHeader, MenuItem, MenuDivider, Dropdown, Option, ToolbarDivider, Button, Popover, PopoverTrigger, PopoverSurface, Field, Input } from '@fluentui/react-components';
11
+ import { ErrorCircleRegular, ChevronDown12Regular, ArrowUpRegular, RowTripleRegular, ArrowDownRegular, ArrowLeftRegular, ColumnTripleRegular, ArrowRightRegular, DeleteRegular, TextCaseUppercaseFilled, TextCaseLowercaseFilled, TextCaseTitleFilled, TextStrikethroughFilled, TextSubscriptFilled, TextSuperscriptFilled, HighlightAccentFilled, TextBulletListLtrFilled, TextNumberListLtrFilled, DocumentPageBreakRegular, CommentQuoteRegular, TextUnderlineFilled, TextItalicFilled, TextBold24Regular, TextAlignLeftFilled, TextAlignCenterFilled, TextAlignRightFilled, TextAlignJustifyFilled, VideoClipRegular, ImageEditRegular, AttachFilled, ImageAddRegular, TableAddRegular, LinkAddRegular, TextColorRegular, PaintBucket16Filled, CodeFilled, LinkFilled } from '@fluentui/react-icons';
11
12
  import { CodeHighlightNode, CodeNode, $isCodeHighlightNode } from '@lexical/code';
12
13
  import { LinkNode, AutoLinkNode, $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND, $createLinkNode } from '@lexical/link';
13
14
  import { ListNode, ListItemNode, $isListNode, REMOVE_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list';
@@ -25,7 +26,6 @@ import { HeadingNode, QuoteNode, $isHeadingNode, $createQuoteNode, $createHeadin
25
26
  import { TableNode, TableRowNode, TableCellNode, $isTableSelection, $isTableNode, $isTableCellNode, $insertTableColumnAtSelection, $insertTableRowAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, $getTableNodeFromLexicalNodeOrThrow, getDOMCellFromTarget, getTableObserverFromTableElement, $getTableRowIndexFromTableCellNode, $isTableRowNode, $getTableColumnIndexFromTableCellNode, $createTableNodeWithDimensions } from '@lexical/table';
26
27
  import { BlockWithAlignableContents } from '@lexical/react/LexicalBlockWithAlignableContents';
27
28
  import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
28
- import { DeleteRegular, ArrowUpRegular, RowTripleRegular, ArrowDownRegular, ArrowLeftRegular, ColumnTripleRegular, ArrowRightRegular, TextCaseUppercaseFilled, TextCaseLowercaseFilled, TextCaseTitleFilled, TextStrikethroughFilled, TextSubscriptFilled, TextSuperscriptFilled, HighlightAccentFilled, TextBulletListLtrFilled, TextNumberListLtrFilled, DocumentPageBreakRegular, CommentQuoteRegular, TextUnderlineFilled, TextItalicFilled, TextBold24Regular, TextAlignLeftFilled, TextAlignCenterFilled, TextAlignRightFilled, TextAlignJustifyFilled, VideoClipRegular, ImageEditRegular, AttachFilled, ImageAddRegular, TableAddRegular, LinkAddRegular, TextColorRegular, PaintBucket16Filled, CodeFilled, LinkFilled } from '@fluentui/react-icons';
29
29
  import { $setBlocksType, $patchStyleText, $isAtNodeEnd, $getSelectionStyleValueForProperty } from '@lexical/selection';
30
30
  import { createPortal } from 'react-dom';
31
31
  import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
@@ -222,80 +222,107 @@ var init_ImageResizer = __esm({
222
222
  document.removeEventListener("pointerup", handlePointerUp);
223
223
  }
224
224
  };
225
- return /* @__PURE__ */ jsxs("div", { ref: controlWrapperRef, children: [
226
- /* @__PURE__ */ jsx(
227
- "div",
228
- {
229
- className: "image-resizer image-resizer-n",
230
- onPointerDown: (event) => {
231
- handlePointerDown(event, Direction.north);
232
- }
233
- }
234
- ),
235
- /* @__PURE__ */ jsx(
236
- "div",
237
- {
238
- className: "image-resizer image-resizer-ne",
239
- onPointerDown: (event) => {
240
- handlePointerDown(event, Direction.north | Direction.east);
241
- }
242
- }
243
- ),
244
- /* @__PURE__ */ jsx(
245
- "div",
246
- {
247
- className: "image-resizer image-resizer-e",
248
- onPointerDown: (event) => {
249
- handlePointerDown(event, Direction.east);
250
- }
251
- }
252
- ),
253
- /* @__PURE__ */ jsx(
254
- "div",
255
- {
256
- className: "image-resizer image-resizer-se",
257
- onPointerDown: (event) => {
258
- handlePointerDown(event, Direction.south | Direction.east);
259
- }
260
- }
261
- ),
262
- /* @__PURE__ */ jsx(
263
- "div",
264
- {
265
- className: "image-resizer image-resizer-s",
266
- onPointerDown: (event) => {
267
- handlePointerDown(event, Direction.south);
268
- }
269
- }
270
- ),
271
- /* @__PURE__ */ jsx(
272
- "div",
273
- {
274
- className: "image-resizer image-resizer-sw",
275
- onPointerDown: (event) => {
276
- handlePointerDown(event, Direction.south | Direction.west);
277
- }
278
- }
279
- ),
280
- /* @__PURE__ */ jsx(
281
- "div",
282
- {
283
- className: "image-resizer image-resizer-w",
284
- onPointerDown: (event) => {
285
- handlePointerDown(event, Direction.west);
286
- }
287
- }
288
- ),
289
- /* @__PURE__ */ jsx(
225
+ return (
226
+ // Overlay that exactly covers the image container (position:relative parent).
227
+ // pointer-events:none lets clicks pass through to the image; each handle
228
+ // re-enables pointer-events so drag-resize still works.
229
+ /* @__PURE__ */ jsxs(
290
230
  "div",
291
231
  {
292
- className: "image-resizer image-resizer-nw",
293
- onPointerDown: (event) => {
294
- handlePointerDown(event, Direction.north | Direction.west);
295
- }
232
+ ref: controlWrapperRef,
233
+ style: {
234
+ position: "absolute",
235
+ top: 0,
236
+ right: 0,
237
+ bottom: 0,
238
+ left: 0,
239
+ pointerEvents: "none"
240
+ },
241
+ children: [
242
+ /* @__PURE__ */ jsx(
243
+ "div",
244
+ {
245
+ className: "image-resizer image-resizer-n",
246
+ style: { pointerEvents: "auto" },
247
+ onPointerDown: (event) => {
248
+ handlePointerDown(event, Direction.north);
249
+ }
250
+ }
251
+ ),
252
+ /* @__PURE__ */ jsx(
253
+ "div",
254
+ {
255
+ className: "image-resizer image-resizer-ne",
256
+ style: { pointerEvents: "auto" },
257
+ onPointerDown: (event) => {
258
+ handlePointerDown(event, Direction.north | Direction.east);
259
+ }
260
+ }
261
+ ),
262
+ /* @__PURE__ */ jsx(
263
+ "div",
264
+ {
265
+ className: "image-resizer image-resizer-e",
266
+ style: { pointerEvents: "auto" },
267
+ onPointerDown: (event) => {
268
+ handlePointerDown(event, Direction.east);
269
+ }
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsx(
273
+ "div",
274
+ {
275
+ className: "image-resizer image-resizer-se",
276
+ style: { pointerEvents: "auto" },
277
+ onPointerDown: (event) => {
278
+ handlePointerDown(event, Direction.south | Direction.east);
279
+ }
280
+ }
281
+ ),
282
+ /* @__PURE__ */ jsx(
283
+ "div",
284
+ {
285
+ className: "image-resizer image-resizer-s",
286
+ style: { pointerEvents: "auto" },
287
+ onPointerDown: (event) => {
288
+ handlePointerDown(event, Direction.south);
289
+ }
290
+ }
291
+ ),
292
+ /* @__PURE__ */ jsx(
293
+ "div",
294
+ {
295
+ className: "image-resizer image-resizer-sw",
296
+ style: { pointerEvents: "auto" },
297
+ onPointerDown: (event) => {
298
+ handlePointerDown(event, Direction.south | Direction.west);
299
+ }
300
+ }
301
+ ),
302
+ /* @__PURE__ */ jsx(
303
+ "div",
304
+ {
305
+ className: "image-resizer image-resizer-w",
306
+ style: { pointerEvents: "auto" },
307
+ onPointerDown: (event) => {
308
+ handlePointerDown(event, Direction.west);
309
+ }
310
+ }
311
+ ),
312
+ /* @__PURE__ */ jsx(
313
+ "div",
314
+ {
315
+ className: "image-resizer image-resizer-nw",
316
+ style: { pointerEvents: "auto" },
317
+ onPointerDown: (event) => {
318
+ handlePointerDown(event, Direction.north | Direction.west);
319
+ }
320
+ }
321
+ )
322
+ ]
296
323
  }
297
324
  )
298
- ] });
325
+ );
299
326
  };
300
327
  ImageResizer_default = ImageResizer;
301
328
  }
@@ -3546,6 +3573,204 @@ function PageBreakPlugin() {
3546
3573
  }, [editor]);
3547
3574
  return null;
3548
3575
  }
3576
+
3577
+ // src/Utils/Sanitize.ts
3578
+ var DROP_ENTIRELY = /* @__PURE__ */ new Set([
3579
+ "script",
3580
+ "noscript",
3581
+ "style",
3582
+ "object",
3583
+ "embed",
3584
+ "form",
3585
+ "input",
3586
+ "button",
3587
+ "select",
3588
+ "textarea",
3589
+ "meta",
3590
+ "link",
3591
+ "base"
3592
+ ]);
3593
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
3594
+ // Block
3595
+ "p",
3596
+ "h1",
3597
+ "h2",
3598
+ "h3",
3599
+ "h4",
3600
+ "h5",
3601
+ "h6",
3602
+ "ul",
3603
+ "ol",
3604
+ "li",
3605
+ "blockquote",
3606
+ "pre",
3607
+ "div",
3608
+ "table",
3609
+ "thead",
3610
+ "tbody",
3611
+ "tfoot",
3612
+ "tr",
3613
+ "td",
3614
+ "th",
3615
+ // Inline
3616
+ "span",
3617
+ "a",
3618
+ "strong",
3619
+ "b",
3620
+ "em",
3621
+ "i",
3622
+ "u",
3623
+ "s",
3624
+ "del",
3625
+ "strike",
3626
+ "sub",
3627
+ "sup",
3628
+ "mark",
3629
+ "code",
3630
+ "br",
3631
+ "hr",
3632
+ // Media / embeds
3633
+ "img",
3634
+ "iframe"
3635
+ ]);
3636
+ var ALLOWED_ATTRS = /* @__PURE__ */ new Set([
3637
+ // Presentation
3638
+ "class",
3639
+ "style",
3640
+ "dir",
3641
+ "lang",
3642
+ // Anchors
3643
+ "href",
3644
+ "target",
3645
+ "rel",
3646
+ // Images
3647
+ "src",
3648
+ "alt",
3649
+ "width",
3650
+ "height",
3651
+ // Tables
3652
+ "colspan",
3653
+ "rowspan",
3654
+ // Lists — <ol type="a"> and <ol start="2">
3655
+ "start",
3656
+ "type",
3657
+ // Lexical-internal data markers
3658
+ "data-lex-block",
3659
+ "data-kind",
3660
+ "data-lexical-decorator",
3661
+ // Misc
3662
+ "title",
3663
+ "allowfullscreen"
3664
+ ]);
3665
+ var DANGEROUS_URL = /^\s*(javascript|data\s*:|vbscript)/i;
3666
+ function sanitizeElement(el) {
3667
+ for (const child of Array.from(el.children)) {
3668
+ sanitizeElement(child);
3669
+ }
3670
+ const tag = el.tagName.toLowerCase();
3671
+ if (DROP_ENTIRELY.has(tag)) {
3672
+ el.parentNode?.removeChild(el);
3673
+ return;
3674
+ }
3675
+ if (tag === "iframe") {
3676
+ const src = el.getAttribute("src") ?? "";
3677
+ const isYouTube = /^\s*https:\/\/(www\.)?(youtube\.com|youtube-nocookie\.com)\/embed\/[^?&/]+/i.test(src);
3678
+ if (!isYouTube) {
3679
+ el.parentNode?.removeChild(el);
3680
+ return;
3681
+ }
3682
+ }
3683
+ if (!ALLOWED_TAGS.has(tag)) {
3684
+ while (el.firstChild) {
3685
+ el.parentNode?.insertBefore(el.firstChild, el);
3686
+ }
3687
+ el.parentNode?.removeChild(el);
3688
+ return;
3689
+ }
3690
+ for (const { name, value } of Array.from(el.attributes)) {
3691
+ const lname = name.toLowerCase();
3692
+ if (!ALLOWED_ATTRS.has(lname)) {
3693
+ el.removeAttribute(name);
3694
+ continue;
3695
+ }
3696
+ if (lname === "href" || lname === "src") {
3697
+ if (DANGEROUS_URL.test(value)) {
3698
+ el.removeAttribute(name);
3699
+ }
3700
+ }
3701
+ if (lname === "style") {
3702
+ const safe = value.replace(/expression\s*\(/gi, "(").replace(/url\s*\(\s*['"]?\s*javascript:/gi, "url(");
3703
+ el.setAttribute("style", safe);
3704
+ }
3705
+ }
3706
+ if (tag === "a" && (el.getAttribute("target") || "").toLowerCase() === "_blank") {
3707
+ const rel = (el.getAttribute("rel") || "").trim();
3708
+ const tokens = new Set(
3709
+ rel.split(/\s+/).filter(Boolean).map((t) => t.toLowerCase())
3710
+ );
3711
+ tokens.add("noopener");
3712
+ tokens.add("noreferrer");
3713
+ el.setAttribute("rel", Array.from(tokens).join(" "));
3714
+ }
3715
+ }
3716
+ function sanitizeHtml(html) {
3717
+ if (!html || typeof html !== "string") return "";
3718
+ const doc = new DOMParser().parseFromString(html, "text/html");
3719
+ for (const child of Array.from(doc.body.children)) {
3720
+ sanitizeElement(child);
3721
+ }
3722
+ return doc.body.innerHTML;
3723
+ }
3724
+ var BLOCK_TAGS = /* @__PURE__ */ new Set([
3725
+ "p",
3726
+ "h1",
3727
+ "h2",
3728
+ "h3",
3729
+ "h4",
3730
+ "h5",
3731
+ "h6",
3732
+ "div",
3733
+ "ul",
3734
+ "ol",
3735
+ "li",
3736
+ "table",
3737
+ "blockquote",
3738
+ "pre",
3739
+ "hr"
3740
+ ]);
3741
+ function normalizeToBlockHtml(html) {
3742
+ if (!html) return "";
3743
+ const doc = new DOMParser().parseFromString(html, "text/html");
3744
+ const body = doc.body;
3745
+ const childNodes = Array.from(body.childNodes);
3746
+ const needsWrap = childNodes.some((node) => {
3747
+ if (node.nodeType === Node.TEXT_NODE) return !!node.nodeValue?.trim();
3748
+ if (node.nodeType === Node.ELEMENT_NODE)
3749
+ return !BLOCK_TAGS.has(node.tagName.toLowerCase());
3750
+ return false;
3751
+ });
3752
+ if (!needsWrap) return html;
3753
+ while (body.firstChild) body.removeChild(body.firstChild);
3754
+ let pendingP = null;
3755
+ for (const node of childNodes) {
3756
+ const isBlock = node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
3757
+ const isWhitespace = node.nodeType === Node.TEXT_NODE && !node.nodeValue?.trim();
3758
+ if (isBlock) {
3759
+ if (pendingP) {
3760
+ body.appendChild(pendingP);
3761
+ pendingP = null;
3762
+ }
3763
+ body.appendChild(node);
3764
+ } else if (!isWhitespace) {
3765
+ if (!pendingP) pendingP = doc.createElement("p");
3766
+ pendingP.appendChild(node);
3767
+ }
3768
+ }
3769
+ if (pendingP) body.appendChild(pendingP);
3770
+ return body.innerHTML;
3771
+ }
3772
+
3773
+ // src/Utils/Helper.ts
3549
3774
  function findBlockByKind(kind) {
3550
3775
  const root = $getRoot();
3551
3776
  for (const child of root.getChildren()) {
@@ -3554,8 +3779,9 @@ function findBlockByKind(kind) {
3554
3779
  return null;
3555
3780
  }
3556
3781
  function importHtmlIntoBlock(editor, block, html) {
3782
+ const safe = normalizeToBlockHtml(sanitizeHtml(html));
3557
3783
  const parser = new DOMParser();
3558
- const doc = parser.parseFromString(html || "<p></p>", "text/html");
3784
+ const doc = parser.parseFromString(safe || "<p></p>", "text/html");
3559
3785
  const nodes = $generateNodesFromDOM(editor, doc);
3560
3786
  block.clear();
3561
3787
  block.append(...nodes);
@@ -3594,7 +3820,8 @@ function hasBlock(editor, kind) {
3594
3820
  function RefApiPlugin({
3595
3821
  forwardedRef,
3596
3822
  contentEditableDomRef,
3597
- focusedRef
3823
+ focusedRef,
3824
+ setRefErrors
3598
3825
  }) {
3599
3826
  const [editor] = useLexicalComposerContext();
3600
3827
  useImperativeHandle(
@@ -3633,12 +3860,14 @@ function RefApiPlugin({
3633
3860
  },
3634
3861
  isFocused: () => focusedRef.current,
3635
3862
  getEditor: () => editor,
3863
+ setErrors: (messages) => setRefErrors(messages),
3864
+ clearErrors: () => setRefErrors([]),
3636
3865
  // Generic blocks (signature, footer, banner, etc.)
3637
3866
  upsertBlock: (spec) => upsertBlock(editor, spec),
3638
3867
  removeBlock: (kind) => removeBlock(editor, kind),
3639
3868
  hasBlock: (kind) => hasBlock(editor, kind)
3640
3869
  }),
3641
- [editor, contentEditableDomRef, focusedRef]
3870
+ [editor, contentEditableDomRef, focusedRef, setRefErrors]
3642
3871
  );
3643
3872
  return null;
3644
3873
  }
@@ -4278,8 +4507,8 @@ function SpellCheckPlugin({
4278
4507
  function TableActionMenuPlugin({ disabled = false }) {
4279
4508
  const [editor] = useLexicalComposerContext();
4280
4509
  const [isInTable, setIsInTable] = React6.useState(false);
4510
+ const [anchorRect, setAnchorRect] = React6.useState(null);
4281
4511
  const [open, setOpen] = React6.useState(false);
4282
- const [menuPos, setMenuPos] = React6.useState(null);
4283
4512
  const updateFromSelection = React6.useCallback(() => {
4284
4513
  const root = editor.getRootElement();
4285
4514
  if (!root) return;
@@ -4288,21 +4517,34 @@ function TableActionMenuPlugin({ disabled = false }) {
4288
4517
  if ($isTableSelection(selection)) {
4289
4518
  const tableNode = selection.getNodes().find((n) => $isTableNode(n));
4290
4519
  if (tableNode) {
4291
- setIsInTable(true);
4292
- return;
4520
+ const dom = editor.getElementByKey(tableNode.getKey());
4521
+ if (dom) {
4522
+ setIsInTable(true);
4523
+ setAnchorRect(dom.getBoundingClientRect());
4524
+ return;
4525
+ }
4293
4526
  }
4294
4527
  }
4295
4528
  if (!$isRangeSelection(selection)) {
4296
4529
  setIsInTable(false);
4530
+ setAnchorRect(null);
4297
4531
  return;
4298
4532
  }
4299
4533
  const anchorNode = selection.anchor.getNode();
4300
4534
  const cellNode = $isTableCellNode(anchorNode) ? anchorNode : $findMatchingParent(anchorNode, (n) => $isTableCellNode(n));
4301
4535
  if (!cellNode || !$isTableCellNode(cellNode)) {
4302
4536
  setIsInTable(false);
4537
+ setAnchorRect(null);
4538
+ return;
4539
+ }
4540
+ const cellDom = editor.getElementByKey(cellNode.getKey());
4541
+ if (!cellDom) {
4542
+ setIsInTable(false);
4543
+ setAnchorRect(null);
4303
4544
  return;
4304
4545
  }
4305
4546
  setIsInTable(true);
4547
+ setAnchorRect(cellDom.getBoundingClientRect());
4306
4548
  });
4307
4549
  }, [editor]);
4308
4550
  React6.useEffect(() => {
@@ -4364,33 +4606,18 @@ function TableActionMenuPlugin({ disabled = false }) {
4364
4606
  React6.useEffect(() => {
4365
4607
  if (!isInTable && open) setOpen(false);
4366
4608
  }, [isInTable, open]);
4367
- React6.useEffect(() => {
4368
- const root = editor.getRootElement();
4369
- if (!root) return;
4370
- const handleContextMenu = (e) => {
4371
- if (disabled) return;
4372
- let inTable = false;
4373
- editor.getEditorState().read(() => {
4374
- const selection = $getSelection();
4375
- if ($isTableSelection(selection)) {
4376
- inTable = true;
4377
- return;
4378
- }
4379
- if ($isRangeSelection(selection)) {
4380
- const node = selection.anchor.getNode();
4381
- const cell = $isTableCellNode(node) ? node : $findMatchingParent(node, (n) => $isTableCellNode(n));
4382
- if (cell) inTable = true;
4383
- }
4384
- });
4385
- if (inTable) {
4386
- e.preventDefault();
4387
- setMenuPos({ x: e.clientX, y: e.clientY });
4388
- setOpen(true);
4389
- }
4609
+ const canShow = isInTable && !!anchorRect && !disabled;
4610
+ const handleStyle = React6.useMemo(() => {
4611
+ if (!anchorRect) return void 0;
4612
+ const top = Math.max(8, anchorRect.top + 6);
4613
+ const left = Math.max(8, anchorRect.right - 34);
4614
+ return {
4615
+ position: "fixed",
4616
+ top,
4617
+ left,
4618
+ zIndex: 9999
4390
4619
  };
4391
- root.addEventListener("contextmenu", handleContextMenu);
4392
- return () => root.removeEventListener("contextmenu", handleContextMenu);
4393
- }, [editor, disabled]);
4620
+ }, [anchorRect]);
4394
4621
  const dangerStyle = {
4395
4622
  color: "var(--colorPaletteRedForeground1)"
4396
4623
  };
@@ -4423,55 +4650,63 @@ function TableActionMenuPlugin({ disabled = false }) {
4423
4650
  const table = $getTableNodeFromLexicalNodeOrThrow(cell);
4424
4651
  table.remove();
4425
4652
  });
4426
- const virtualTarget = React6.useMemo(() => {
4427
- if (!menuPos) return void 0;
4428
- return {
4429
- getBoundingClientRect: () => new DOMRect(menuPos.x, menuPos.y, 0, 0)
4430
- };
4431
- }, [menuPos]);
4432
- if (disabled) return null;
4653
+ if (!canShow || !handleStyle) return null;
4433
4654
  return createPortal(
4434
- /* @__PURE__ */ jsx(Menu, { open, onOpenChange: (_, data) => setOpen(data.open), positioning: { target: virtualTarget }, children: /* @__PURE__ */ jsx(MenuPopover, { className: "aoTableActionPopover", children: /* @__PURE__ */ jsxs(MenuList, { children: [
4435
- /* @__PURE__ */ jsxs(MenuGroup, { children: [
4436
- /* @__PURE__ */ jsx(MenuGroupHeader, { children: "Insert" }),
4437
- /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(RowTripleRegular, {}), onClick: insertRowAbove, children: /* @__PURE__ */ jsxs("span", { className: "aoMenuRow", children: [
4438
- /* @__PURE__ */ jsxs("span", { className: "aoMenuLabel", children: [
4439
- /* @__PURE__ */ jsx(ArrowUpRegular, {}),
4440
- " Row above"
4441
- ] }),
4442
- /* @__PURE__ */ jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2191" })
4443
- ] }) }),
4444
- /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(RowTripleRegular, {}), onClick: insertRowBelow, children: /* @__PURE__ */ jsxs("span", { className: "aoMenuRow", children: [
4445
- /* @__PURE__ */ jsxs("span", { className: "aoMenuLabel", children: [
4446
- /* @__PURE__ */ jsx(ArrowDownRegular, {}),
4447
- " Row below"
4448
- ] }),
4449
- /* @__PURE__ */ jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2193" })
4450
- ] }) }),
4655
+ /* @__PURE__ */ jsx("div", { style: handleStyle, className: "aoTableActionHandleRoot", children: /* @__PURE__ */ jsxs(Menu, { open, onOpenChange: (_, data) => setOpen(data.open), children: [
4656
+ /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(
4657
+ "button",
4658
+ {
4659
+ type: "button",
4660
+ className: "aoTableActionHandleBtn",
4661
+ "aria-label": "Table options",
4662
+ onMouseDown: (e) => {
4663
+ e.preventDefault();
4664
+ },
4665
+ children: /* @__PURE__ */ jsx(ChevronDown12Regular, {})
4666
+ }
4667
+ ) }),
4668
+ /* @__PURE__ */ jsx(MenuPopover, { className: "aoTableActionPopover", children: /* @__PURE__ */ jsxs(MenuList, { children: [
4669
+ /* @__PURE__ */ jsxs(MenuGroup, { children: [
4670
+ /* @__PURE__ */ jsx(MenuGroupHeader, { children: "Insert" }),
4671
+ /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(RowTripleRegular, {}), onClick: insertRowAbove, children: /* @__PURE__ */ jsxs("span", { className: "aoMenuRow", children: [
4672
+ /* @__PURE__ */ jsxs("span", { className: "aoMenuLabel", children: [
4673
+ /* @__PURE__ */ jsx(ArrowUpRegular, {}),
4674
+ " Row above"
4675
+ ] }),
4676
+ /* @__PURE__ */ jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2191" })
4677
+ ] }) }),
4678
+ /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(RowTripleRegular, {}), onClick: insertRowBelow, children: /* @__PURE__ */ jsxs("span", { className: "aoMenuRow", children: [
4679
+ /* @__PURE__ */ jsxs("span", { className: "aoMenuLabel", children: [
4680
+ /* @__PURE__ */ jsx(ArrowDownRegular, {}),
4681
+ " Row below"
4682
+ ] }),
4683
+ /* @__PURE__ */ jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2193" })
4684
+ ] }) }),
4685
+ /* @__PURE__ */ jsx(MenuDivider, {}),
4686
+ /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(ColumnTripleRegular, {}), onClick: insertColLeft, children: /* @__PURE__ */ jsxs("span", { className: "aoMenuRow", children: [
4687
+ /* @__PURE__ */ jsxs("span", { className: "aoMenuLabel", children: [
4688
+ /* @__PURE__ */ jsx(ArrowLeftRegular, {}),
4689
+ " Column left"
4690
+ ] }),
4691
+ /* @__PURE__ */ jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2190" })
4692
+ ] }) }),
4693
+ /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(ColumnTripleRegular, {}), onClick: insertColRight, children: /* @__PURE__ */ jsxs("span", { className: "aoMenuRow", children: [
4694
+ /* @__PURE__ */ jsxs("span", { className: "aoMenuLabel", children: [
4695
+ /* @__PURE__ */ jsx(ArrowRightRegular, {}),
4696
+ " Column right"
4697
+ ] }),
4698
+ /* @__PURE__ */ jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2192" })
4699
+ ] }) })
4700
+ ] }),
4451
4701
  /* @__PURE__ */ jsx(MenuDivider, {}),
4452
- /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(ColumnTripleRegular, {}), onClick: insertColLeft, children: /* @__PURE__ */ jsxs("span", { className: "aoMenuRow", children: [
4453
- /* @__PURE__ */ jsxs("span", { className: "aoMenuLabel", children: [
4454
- /* @__PURE__ */ jsx(ArrowLeftRegular, {}),
4455
- " Column left"
4456
- ] }),
4457
- /* @__PURE__ */ jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2190" })
4458
- ] }) }),
4459
- /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(ColumnTripleRegular, {}), onClick: insertColRight, children: /* @__PURE__ */ jsxs("span", { className: "aoMenuRow", children: [
4460
- /* @__PURE__ */ jsxs("span", { className: "aoMenuLabel", children: [
4461
- /* @__PURE__ */ jsx(ArrowRightRegular, {}),
4462
- " Column right"
4463
- ] }),
4464
- /* @__PURE__ */ jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2192" })
4465
- ] }) })
4466
- ] }),
4467
- /* @__PURE__ */ jsx(MenuDivider, {}),
4468
- /* @__PURE__ */ jsxs(MenuGroup, { children: [
4469
- /* @__PURE__ */ jsx(MenuGroupHeader, { children: "Delete" }),
4470
- /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(DeleteRegular, {}), onClick: deleteRow, style: dangerStyle, children: "Delete row" }),
4471
- /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(DeleteRegular, {}), onClick: deleteCol, style: dangerStyle, children: "Delete column" }),
4472
- /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(DeleteRegular, {}), onClick: deleteTable, style: dangerStyle, children: "Delete table" })
4473
- ] })
4474
- ] }) }) }),
4702
+ /* @__PURE__ */ jsxs(MenuGroup, { children: [
4703
+ /* @__PURE__ */ jsx(MenuGroupHeader, { children: "Delete" }),
4704
+ /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(DeleteRegular, {}), onClick: deleteRow, style: dangerStyle, children: "Delete row" }),
4705
+ /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(DeleteRegular, {}), onClick: deleteCol, style: dangerStyle, children: "Delete column" }),
4706
+ /* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(DeleteRegular, {}), onClick: deleteTable, style: dangerStyle, children: "Delete table" })
4707
+ ] })
4708
+ ] }) })
4709
+ ] }) }),
4475
4710
  document.body
4476
4711
  );
4477
4712
  }
@@ -4775,13 +5010,6 @@ function getToolbarGroupsByLevel(level) {
4775
5010
  ];
4776
5011
  }
4777
5012
  }
4778
- var DEFAULT_FONT_SIZE = 15;
4779
- var formatParagraph = (editor) => {
4780
- editor.update(() => {
4781
- const selection = $getSelection();
4782
- $setBlocksType(selection, () => $createParagraphNode());
4783
- });
4784
- };
4785
5013
  var PRESET = [
4786
5014
  "#000000",
4787
5015
  "#434343",
@@ -4857,11 +5085,12 @@ var hsvToRgb = (h, s, v) => {
4857
5085
  b: Math.round((bb + m) * 255)
4858
5086
  };
4859
5087
  };
4860
- function useDrag(onMove, onEnd) {
5088
+ function useDrag(onMove, onEnd, interactingRef) {
4861
5089
  const draggingRef = React6.useRef(false);
4862
5090
  const start = React6.useCallback(
4863
5091
  (e) => {
4864
5092
  draggingRef.current = true;
5093
+ if (interactingRef) interactingRef.current = true;
4865
5094
  onMove(e.clientX, e.clientY);
4866
5095
  const move = (ev) => {
4867
5096
  if (!draggingRef.current) return;
@@ -4871,17 +5100,55 @@ function useDrag(onMove, onEnd) {
4871
5100
  draggingRef.current = false;
4872
5101
  window.removeEventListener("mousemove", move);
4873
5102
  window.removeEventListener("mouseup", up);
5103
+ if (interactingRef) {
5104
+ const clearFlag = () => {
5105
+ interactingRef.current = false;
5106
+ };
5107
+ window.addEventListener("click", clearFlag, { once: true });
5108
+ setTimeout(() => {
5109
+ window.removeEventListener("click", clearFlag);
5110
+ interactingRef.current = false;
5111
+ }, 0);
5112
+ }
4874
5113
  };
4875
5114
  window.addEventListener("mousemove", move);
4876
5115
  window.addEventListener("mouseup", up);
4877
5116
  },
4878
- [onMove, onEnd]
5117
+ [onMove, onEnd, interactingRef]
4879
5118
  );
4880
5119
  return start;
4881
5120
  }
4882
5121
  var ColorPickerControl = ({ value, title, disabled, onChange, icon }) => {
4883
5122
  const [open, setOpen] = React6.useState(false);
4884
5123
  const btnRef = React6.useRef(null);
5124
+ const interactingRef = React6.useRef(false);
5125
+ const handleDismiss = React6.useCallback(() => setOpen(false), []);
5126
+ const preventDismissOnEvent = React6.useCallback(
5127
+ (ev) => {
5128
+ if (interactingRef.current) return true;
5129
+ return ev.type !== "click";
5130
+ },
5131
+ []
5132
+ );
5133
+ const [, forceReposition] = React6.useState(0);
5134
+ React6.useEffect(() => {
5135
+ if (!open) return;
5136
+ let rafId = null;
5137
+ const reposition = () => {
5138
+ if (rafId != null) return;
5139
+ rafId = requestAnimationFrame(() => {
5140
+ rafId = null;
5141
+ forceReposition((n) => n + 1);
5142
+ });
5143
+ };
5144
+ window.addEventListener("scroll", reposition, true);
5145
+ window.addEventListener("resize", reposition);
5146
+ return () => {
5147
+ if (rafId != null) cancelAnimationFrame(rafId);
5148
+ window.removeEventListener("scroll", reposition, true);
5149
+ window.removeEventListener("resize", reposition);
5150
+ };
5151
+ }, [open]);
4885
5152
  const [hex, setHex] = React6.useState(normalizeHex(value || "#000000"));
4886
5153
  const { r, g, b } = React6.useMemo(() => hexToRgb(hex), [hex]);
4887
5154
  const hsv = React6.useMemo(() => rgbToHsv(r, g, b), [r, g, b]);
@@ -4922,7 +5189,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon }) => {
4922
5189
  },
4923
5190
  [h, commitHsv]
4924
5191
  );
4925
- const startSV = useDrag(onSVMove);
5192
+ const startSV = useDrag(onSVMove, void 0, interactingRef);
4926
5193
  const hueRef = React6.useRef(null);
4927
5194
  const onHueMove = React6.useCallback(
4928
5195
  (clientX) => {
@@ -4935,7 +5202,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon }) => {
4935
5202
  },
4936
5203
  [s, v, commitHsv]
4937
5204
  );
4938
- const startHue = useDrag((x) => onHueMove(x));
5205
+ const startHue = useDrag((x) => onHueMove(x), void 0, interactingRef);
4939
5206
  const svThumb = React6.useMemo(() => ({ left: `${s * 100}%`, top: `${(1 - v) * 100}%` }), [s, v]);
4940
5207
  const hueThumb = React6.useMemo(() => ({ left: `${h / 360 * 100}%` }), [h]);
4941
5208
  const hueColor = React6.useMemo(() => {
@@ -4984,10 +5251,11 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon }) => {
4984
5251
  Callout,
4985
5252
  {
4986
5253
  target: btnRef,
4987
- onDismiss: () => setOpen(false),
5254
+ onDismiss: handleDismiss,
4988
5255
  setInitialFocus: true,
4989
5256
  directionalHint: 4,
4990
5257
  className: "aoColorCallout",
5258
+ preventDismissOnEvent,
4991
5259
  children: /* @__PURE__ */ jsxs(Stack, { tokens: { childrenGap: 10 }, styles: { root: { padding: 12, width: 320 } }, children: [
4992
5260
  /* @__PURE__ */ jsxs("div", { className: "aoLexRow", children: [
4993
5261
  /* @__PURE__ */ jsx("div", { className: "aoLexSwatch", style: { background: hex } }),
@@ -5087,7 +5355,9 @@ var ColorPickerPlugin = ({ disabled }) => {
5087
5355
  }, [editor]);
5088
5356
  const applyStyle = (args) => {
5089
5357
  if (disabled) return;
5090
- editor.focus();
5358
+ const root = editor.getRootElement();
5359
+ const editorIsActive = !!lastRangeSelectionRef.current && !!root && (document.activeElement === root || root.contains(document.activeElement));
5360
+ if (editorIsActive) editor.focus();
5091
5361
  editor.update(() => {
5092
5362
  const saved = lastRangeSelectionRef.current;
5093
5363
  if (saved) {
@@ -5226,6 +5496,7 @@ var FontFamilyPlugin = ({ disabled = false }) => {
5226
5496
  "font-family"
5227
5497
  );
5228
5498
  };
5499
+ var DEFAULT_FONT_SIZE = 15;
5229
5500
  var FONT_SIZE_OPTIONS = [
5230
5501
  "8",
5231
5502
  "9",
@@ -5526,21 +5797,27 @@ var TableItemPlugin = ({ disabled }) => {
5526
5797
  Input,
5527
5798
  {
5528
5799
  autoFocus: !disabled,
5800
+ type: "number",
5801
+ min: 1,
5529
5802
  value: rows,
5530
5803
  placeholder: "Rows",
5531
5804
  appearance: "underline",
5532
5805
  disabled,
5533
- onChange: (_, v) => setRows(v.value)
5806
+ input: { style: { textAlign: "left" } },
5807
+ onChange: (_, v) => setRows(v.value.replace(/\D/g, ""))
5534
5808
  }
5535
5809
  ) }),
5536
5810
  /* @__PURE__ */ jsx(Field, { label: "Columns", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsx(
5537
5811
  Input,
5538
5812
  {
5813
+ type: "number",
5814
+ min: 1,
5539
5815
  value: columns,
5540
5816
  placeholder: "Columns",
5541
5817
  appearance: "underline",
5542
5818
  disabled,
5543
- onChange: (_, v) => setColumns(v.value)
5819
+ input: { style: { textAlign: "left" } },
5820
+ onChange: (_, v) => setColumns(v.value.replace(/\D/g, ""))
5544
5821
  }
5545
5822
  ) }),
5546
5823
  /* @__PURE__ */ jsxs(Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
@@ -5697,8 +5974,7 @@ var ToolBarPlugins = (props) => {
5697
5974
  const [isLowercase, setIsLowercase] = useState(false);
5698
5975
  const [isCapitalize, setIsCapitalize] = useState(false);
5699
5976
  const [alignment, setAlignment] = useState("left");
5700
- const [isInTable, setIsInTable] = useState(false);
5701
- const [tableNodeKey, setTableNodeKey] = useState(null);
5977
+ const lastSelectionRef = React6__default.useRef(null);
5702
5978
  const presetGroups = getToolbarGroupsByLevel(props.level);
5703
5979
  const pluginGroups = useMemo(() => sanitizePluginGroups(presetGroups), [presetGroups]);
5704
5980
  const updateToolbarPlugins = () => {
@@ -5715,8 +5991,6 @@ var ToolBarPlugins = (props) => {
5715
5991
  setIsLowercase(false);
5716
5992
  setIsCapitalize(false);
5717
5993
  setSelectNodeType("paragraph");
5718
- setIsInTable(false);
5719
- setTableNodeKey(null);
5720
5994
  return;
5721
5995
  }
5722
5996
  setIsBold(selection.hasFormat("bold"));
@@ -5732,21 +6006,8 @@ var ToolBarPlugins = (props) => {
5732
6006
  const anchorNode = selection.anchor.getNode();
5733
6007
  if (anchorNode.getKey() === "root") {
5734
6008
  setSelectNodeType("paragraph");
5735
- setIsInTable(false);
5736
- setTableNodeKey(null);
5737
6009
  return;
5738
6010
  }
5739
- let tableAncestorKey = null;
5740
- let cursor = anchorNode.getParent();
5741
- while (cursor !== null) {
5742
- if (cursor.getType() === "table") {
5743
- tableAncestorKey = cursor.getKey();
5744
- break;
5745
- }
5746
- cursor = cursor.getParent();
5747
- }
5748
- setIsInTable(tableAncestorKey !== null);
5749
- setTableNodeKey(tableAncestorKey);
5750
6011
  const element = anchorNode.getTopLevelElementOrThrow();
5751
6012
  setAlignment(
5752
6013
  typeof element.getFormatType === "function" ? element.getFormatType() || "left" : "left"
@@ -5767,12 +6028,21 @@ var ToolBarPlugins = (props) => {
5767
6028
  ["paragraph", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "quote", "code"].includes(type) ? type : "paragraph"
5768
6029
  );
5769
6030
  };
6031
+ const applyToBlock = React6__default.useCallback(
6032
+ (fn) => {
6033
+ editor.update(() => {
6034
+ const saved = lastSelectionRef.current;
6035
+ if (saved) $setSelection(saved.clone());
6036
+ const sel = $getSelection();
6037
+ if ($isRangeSelection(sel)) fn(sel);
6038
+ });
6039
+ },
6040
+ [editor]
6041
+ );
5770
6042
  const formatQuote = () => {
5771
- editor.update(() => {
5772
- const selection = $getSelection();
5773
- if (!$isRangeSelection(selection)) return;
6043
+ applyToBlock((selection) => {
5774
6044
  if (selectNodeType === "quote") {
5775
- formatParagraph(editor);
6045
+ $setBlocksType(selection, () => $createParagraphNode());
5776
6046
  } else {
5777
6047
  $setBlocksType(selection, () => $createQuoteNode());
5778
6048
  }
@@ -5791,6 +6061,8 @@ var ToolBarPlugins = (props) => {
5791
6061
  editor.registerCommand(
5792
6062
  SELECTION_CHANGE_COMMAND,
5793
6063
  () => {
6064
+ const sel = $getSelection();
6065
+ if ($isRangeSelection(sel)) lastSelectionRef.current = sel.clone();
5794
6066
  updateToolbarPlugins();
5795
6067
  return false;
5796
6068
  },
@@ -5822,16 +6094,52 @@ var ToolBarPlugins = (props) => {
5822
6094
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, "highlight");
5823
6095
  break;
5824
6096
  case "leftAlign" /* LeftAlign */:
5825
- editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "left");
6097
+ applyToBlock((sel) => {
6098
+ const seen = /* @__PURE__ */ new Set();
6099
+ sel.getNodes().forEach((n) => {
6100
+ const t = n.getTopLevelElementOrThrow();
6101
+ if (!seen.has(t.getKey())) {
6102
+ seen.add(t.getKey());
6103
+ t.setFormat("left");
6104
+ }
6105
+ });
6106
+ });
5826
6107
  break;
5827
6108
  case "rightAlign" /* RightAlign */:
5828
- editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "right");
6109
+ applyToBlock((sel) => {
6110
+ const seen = /* @__PURE__ */ new Set();
6111
+ sel.getNodes().forEach((n) => {
6112
+ const t = n.getTopLevelElementOrThrow();
6113
+ if (!seen.has(t.getKey())) {
6114
+ seen.add(t.getKey());
6115
+ t.setFormat("right");
6116
+ }
6117
+ });
6118
+ });
5829
6119
  break;
5830
6120
  case "centerAlign" /* CenterAlign */:
5831
- editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "center");
6121
+ applyToBlock((sel) => {
6122
+ const seen = /* @__PURE__ */ new Set();
6123
+ sel.getNodes().forEach((n) => {
6124
+ const t = n.getTopLevelElementOrThrow();
6125
+ if (!seen.has(t.getKey())) {
6126
+ seen.add(t.getKey());
6127
+ t.setFormat("center");
6128
+ }
6129
+ });
6130
+ });
5832
6131
  break;
5833
6132
  case "justifyAlign" /* JustifyAlign */:
5834
- editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "justify");
6133
+ applyToBlock((sel) => {
6134
+ const seen = /* @__PURE__ */ new Set();
6135
+ sel.getNodes().forEach((n) => {
6136
+ const t = n.getTopLevelElementOrThrow();
6137
+ if (!seen.has(t.getKey())) {
6138
+ seen.add(t.getKey());
6139
+ t.setFormat("justify");
6140
+ }
6141
+ });
6142
+ });
5835
6143
  break;
5836
6144
  case "undo" /* Undo */:
5837
6145
  editor.dispatchCommand(UNDO_COMMAND, void 0);
@@ -5851,14 +6159,8 @@ var ToolBarPlugins = (props) => {
5851
6159
  }
5852
6160
  };
5853
6161
  const updateHeading = (heading) => {
5854
- editor.update(() => {
5855
- const selection = $getSelection();
5856
- if ($isRangeSelection(selection)) {
5857
- $setBlocksType(selection, () => $createHeadingNode(heading));
5858
- }
5859
- });
5860
- editor.getEditorState().read(() => {
5861
- updateToolbarPlugins();
6162
+ applyToBlock((selection) => {
6163
+ $setBlocksType(selection, () => $createHeadingNode(heading));
5862
6164
  });
5863
6165
  };
5864
6166
  const renderToken = (token, groupIndex, tokenIndex) => {
@@ -5997,7 +6299,7 @@ var ToolBarPlugins = (props) => {
5997
6299
  const val = data.optionValue;
5998
6300
  if (!val) return;
5999
6301
  if (val === "paragraph") {
6000
- formatParagraph(editor);
6302
+ applyToBlock((sel) => $setBlocksType(sel, () => $createParagraphNode()));
6001
6303
  setSelectNodeType("paragraph");
6002
6304
  } else {
6003
6305
  updateHeading(val);
@@ -6081,16 +6383,10 @@ var ToolBarPlugins = (props) => {
6081
6383
  onHandleSelectOption("highlight" /* Highlight */);
6082
6384
  break;
6083
6385
  case "ul-list":
6084
- editor.dispatchCommand(
6085
- selectNodeType === "ul" ? REMOVE_LIST_COMMAND : INSERT_UNORDERED_LIST_COMMAND,
6086
- void 0
6087
- );
6386
+ editor.dispatchCommand(selectNodeType === "ul" ? REMOVE_LIST_COMMAND : INSERT_UNORDERED_LIST_COMMAND, void 0);
6088
6387
  break;
6089
6388
  case "ol-list":
6090
- editor.dispatchCommand(
6091
- selectNodeType === "ol" ? REMOVE_LIST_COMMAND : INSERT_ORDERED_LIST_COMMAND,
6092
- void 0
6093
- );
6389
+ editor.dispatchCommand(selectNodeType === "ol" ? REMOVE_LIST_COMMAND : INSERT_ORDERED_LIST_COMMAND, void 0);
6094
6390
  break;
6095
6391
  case "page-break":
6096
6392
  editor.dispatchCommand(INSERT_PAGE_BREAK, void 0);
@@ -6126,12 +6422,7 @@ var ToolBarPlugins = (props) => {
6126
6422
  "Superscript"
6127
6423
  ] }),
6128
6424
  /* @__PURE__ */ jsxs(Option, { value: "highlight", text: "Highlight", children: [
6129
- /* @__PURE__ */ jsx(
6130
- HighlightAccentFilled,
6131
- {
6132
- style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled }
6133
- }
6134
- ),
6425
+ /* @__PURE__ */ jsx(HighlightAccentFilled, { style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled } }),
6135
6426
  "Highlight"
6136
6427
  ] }),
6137
6428
  /* @__PURE__ */ jsxs(Option, { value: "ul-list", text: "Bullet list", children: [
@@ -6167,30 +6458,10 @@ var ToolBarPlugins = (props) => {
6167
6458
  // );
6168
6459
  case "Align": {
6169
6460
  const ALIGN_OPTIONS = [
6170
- {
6171
- value: "left",
6172
- label: "Left Align",
6173
- icon: /* @__PURE__ */ jsx(TextAlignLeftFilled, { style: optionIconStyle }),
6174
- action: "leftAlign" /* LeftAlign */
6175
- },
6176
- {
6177
- value: "center",
6178
- label: "Center Align",
6179
- icon: /* @__PURE__ */ jsx(TextAlignCenterFilled, { style: optionIconStyle }),
6180
- action: "centerAlign" /* CenterAlign */
6181
- },
6182
- {
6183
- value: "right",
6184
- label: "Right Align",
6185
- icon: /* @__PURE__ */ jsx(TextAlignRightFilled, { style: optionIconStyle }),
6186
- action: "rightAlign" /* RightAlign */
6187
- },
6188
- {
6189
- value: "justify",
6190
- label: "Justify Align",
6191
- icon: /* @__PURE__ */ jsx(TextAlignJustifyFilled, { style: optionIconStyle }),
6192
- action: "justifyAlign" /* JustifyAlign */
6193
- }
6461
+ { value: "left", label: "Left Align", icon: /* @__PURE__ */ jsx(TextAlignLeftFilled, { style: optionIconStyle }), action: "leftAlign" /* LeftAlign */ },
6462
+ { value: "center", label: "Center Align", icon: /* @__PURE__ */ jsx(TextAlignCenterFilled, { style: optionIconStyle }), action: "centerAlign" /* CenterAlign */ },
6463
+ { value: "right", label: "Right Align", icon: /* @__PURE__ */ jsx(TextAlignRightFilled, { style: optionIconStyle }), action: "rightAlign" /* RightAlign */ },
6464
+ { value: "justify", label: "Justify Align", icon: /* @__PURE__ */ jsx(TextAlignJustifyFilled, { style: optionIconStyle }), action: "justifyAlign" /* JustifyAlign */ }
6194
6465
  ];
6195
6466
  const alignLabel = ALIGN_OPTIONS.find((o) => o.value === alignment)?.label ?? "Left Align";
6196
6467
  return /* @__PURE__ */ jsx(
@@ -6224,69 +6495,29 @@ var ToolBarPlugins = (props) => {
6224
6495
  if (!pluginGroups || pluginGroups.length === 0) {
6225
6496
  return null;
6226
6497
  }
6227
- return /* @__PURE__ */ jsxs(Stack, { children: [
6228
- /* @__PURE__ */ jsx(
6229
- "div",
6230
- {
6231
- role: "toolbar",
6232
- "aria-label": "Editor toolbar",
6233
- style: {
6234
- display: "flex",
6235
- flexWrap: "wrap",
6236
- alignItems: "center",
6237
- borderBottom: "1px solid #ccced1",
6238
- background: "#ffffff",
6239
- padding: "0px",
6240
- minHeight: 36
6241
- },
6242
- children: pluginGroups.map((group, groupIndex) => /* @__PURE__ */ jsx(React6__default.Fragment, { children: group.map((token, tokenIndex) => {
6243
- try {
6244
- return renderToken(token, groupIndex, tokenIndex);
6245
- } catch {
6246
- return null;
6247
- }
6248
- }) }, `group-${groupIndex}`))
6249
- }
6250
- ),
6251
- isInTable && /* @__PURE__ */ jsxs(
6252
- "div",
6253
- {
6254
- style: {
6255
- display: "flex",
6256
- alignItems: "center",
6257
- gap: 6,
6258
- padding: "2px 8px",
6259
- borderBottom: "1px solid #ccced1",
6260
- background: "#fff8f8"
6261
- },
6262
- children: [
6263
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#888", userSelect: "none" }, children: "Table" }),
6264
- /* @__PURE__ */ jsx(
6265
- Button,
6266
- {
6267
- size: "small",
6268
- icon: /* @__PURE__ */ jsx(DeleteRegular, { style: { color: "#c4272c" } }),
6269
- title: "Delete table",
6270
- style: {
6271
- background: "transparent",
6272
- border: "none",
6273
- color: "#c4272c",
6274
- fontWeight: 500
6275
- },
6276
- onClick: () => {
6277
- editor.update(() => {
6278
- if (!tableNodeKey) return;
6279
- const node = $getNodeByKey(tableNodeKey);
6280
- if (node?.getType() === "table") node.remove();
6281
- });
6282
- },
6283
- children: "Delete Table"
6284
- }
6285
- )
6286
- ]
6287
- }
6288
- )
6289
- ] });
6498
+ return /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(
6499
+ "div",
6500
+ {
6501
+ role: "toolbar",
6502
+ "aria-label": "Editor toolbar",
6503
+ style: {
6504
+ display: "flex",
6505
+ flexWrap: "wrap",
6506
+ alignItems: "center",
6507
+ borderBottom: "1px solid #ccced1",
6508
+ background: "#ffffff",
6509
+ padding: "0px",
6510
+ minHeight: 36
6511
+ },
6512
+ children: pluginGroups.map((group, groupIndex) => /* @__PURE__ */ jsx(React6__default.Fragment, { children: group.map((token, tokenIndex) => {
6513
+ try {
6514
+ return renderToken(token, groupIndex, tokenIndex);
6515
+ } catch {
6516
+ return null;
6517
+ }
6518
+ }) }, `group-${groupIndex}`))
6519
+ }
6520
+ ) });
6290
6521
  };
6291
6522
  function isYoutubeLikeNode(node) {
6292
6523
  try {
@@ -6439,10 +6670,23 @@ function BrowserSpellCheckPlugin({ enabled }) {
6439
6670
  function WordCountPlugin({ onCountChange }) {
6440
6671
  const [editor] = useLexicalComposerContext();
6441
6672
  useEffect(() => {
6442
- return editor.registerUpdateListener(() => {
6443
- const text = editor.getRootElement()?.innerText ?? "";
6444
- const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
6445
- onCountChange(words);
6673
+ return editor.registerUpdateListener(({ editorState }) => {
6674
+ editorState.read(() => {
6675
+ const text = $getRoot().getTextContent();
6676
+ const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
6677
+ onCountChange(words);
6678
+ });
6679
+ });
6680
+ }, [editor, onCountChange]);
6681
+ return null;
6682
+ }
6683
+ function CharCountPlugin({ onCountChange }) {
6684
+ const [editor] = useLexicalComposerContext();
6685
+ useEffect(() => {
6686
+ return editor.registerUpdateListener(({ editorState }) => {
6687
+ editorState.read(() => {
6688
+ onCountChange($getRoot().getTextContent().length);
6689
+ });
6446
6690
  });
6447
6691
  }, [editor, onCountChange]);
6448
6692
  return null;
@@ -6592,6 +6836,9 @@ var ContentEditorComponent = forwardRef(
6592
6836
  const [isLinkEditMode, setIsLinkEditMode] = useState(false);
6593
6837
  const [wordCount, setWordCount] = useState(0);
6594
6838
  const handleWordCount = useCallback((count) => setWordCount(count), []);
6839
+ const [charCount, setCharCount] = useState(0);
6840
+ const handleCharCount = useCallback((count) => setCharCount(count), []);
6841
+ const [refErrors, setRefErrors] = useState([]);
6595
6842
  const contentEditableDomRef = useRef(null);
6596
6843
  const previousOverLimitRef = useRef(false);
6597
6844
  const focusedRef = useRef(false);
@@ -6671,7 +6918,41 @@ var ContentEditorComponent = forwardRef(
6671
6918
  e.stopPropagation();
6672
6919
  }
6673
6920
  };
6921
+ const [touched, setTouched] = useState(false);
6674
6922
  const isOverLimit = props.wordLimit !== void 0 && wordCount > props.wordLimit;
6923
+ const internalErrors = [];
6924
+ if (isOverLimit) {
6925
+ const m = props.errorMessages?.wordLimitExceeded;
6926
+ internalErrors.push(
6927
+ typeof m === "function" ? m(wordCount, props.wordLimit) : m ?? `Word limit exceeded (${wordCount} / ${props.wordLimit} words used)`
6928
+ );
6929
+ }
6930
+ if (props.required && touched && wordCount === 0) {
6931
+ internalErrors.push(
6932
+ props.errorMessages?.required ?? "This field is required"
6933
+ );
6934
+ }
6935
+ if (props.minWords !== void 0 && touched && wordCount < props.minWords) {
6936
+ const m = props.errorMessages?.minWords;
6937
+ internalErrors.push(
6938
+ typeof m === "function" ? m(wordCount, props.minWords) : m ?? `Minimum ${props.minWords} words required (${wordCount} entered)`
6939
+ );
6940
+ }
6941
+ if (props.maxChars !== void 0 && charCount > props.maxChars) {
6942
+ const m = props.errorMessages?.maxCharsExceeded;
6943
+ internalErrors.push(
6944
+ typeof m === "function" ? m(charCount, props.maxChars) : m ?? `Character limit exceeded (${charCount} / ${props.maxChars} characters used)`
6945
+ );
6946
+ }
6947
+ if (props.minChars !== void 0 && touched && charCount < props.minChars && charCount > 0) {
6948
+ const m = props.errorMessages?.minCharsRequired;
6949
+ internalErrors.push(
6950
+ typeof m === "function" ? m(charCount, props.minChars) : m ?? `Minimum ${props.minChars} characters required (${charCount} entered)`
6951
+ );
6952
+ }
6953
+ const allErrors = [...internalErrors, ...props.errors ?? [], ...refErrors];
6954
+ const hasErrors = allErrors.length > 0;
6955
+ const hasRedBorder = hasErrors;
6675
6956
  useEffect(() => {
6676
6957
  if (props.wordLimit === void 0 || !props.onWordLimitExceeded) return;
6677
6958
  const wasOverLimit = previousOverLimitRef.current;
@@ -6694,7 +6975,7 @@ var ContentEditorComponent = forwardRef(
6694
6975
  width: props.width ?? "100%",
6695
6976
  height: props.height ?? "100%",
6696
6977
  margin: props.margin ?? "5px auto",
6697
- border: `1px solid ${isOverLimit ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
6978
+ border: `1px solid ${hasRedBorder ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
6698
6979
  transition: "border-color 0.2s",
6699
6980
  display: "flex",
6700
6981
  flexDirection: "column"
@@ -6724,7 +7005,9 @@ var ContentEditorComponent = forwardRef(
6724
7005
  position: "relative",
6725
7006
  flexGrow: 1,
6726
7007
  padding: "15px 0px",
6727
- overflowY: "scroll"
7008
+ overflowY: "scroll",
7009
+ overflowX: "auto",
7010
+ minWidth: 0
6728
7011
  },
6729
7012
  onClickCapture: handleReadOnlyClickCapture,
6730
7013
  children: [
@@ -6788,13 +7071,33 @@ var ContentEditorComponent = forwardRef(
6788
7071
  ]
6789
7072
  }
6790
7073
  ),
7074
+ hasErrors && /* @__PURE__ */ jsx(
7075
+ "div",
7076
+ {
7077
+ style: {
7078
+ borderTop: "1px solid #fbd5d5",
7079
+ background: "#fff8f8",
7080
+ padding: "6px 12px 8px",
7081
+ display: "flex",
7082
+ flexDirection: "column",
7083
+ gap: 4
7084
+ },
7085
+ children: allErrors.map((err, i) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
7086
+ /* @__PURE__ */ jsx(ErrorCircleRegular, { style: { fontSize: 14, color: "#c4272c", flexShrink: 0 } }),
7087
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#c4272c" }, children: err })
7088
+ ] }, i))
7089
+ }
7090
+ ),
6791
7091
  /* @__PURE__ */ jsx(ReadOnlyPlugin, { readonly: isReadOnly }),
6792
7092
  /* @__PURE__ */ jsx(BrowserSpellCheckPlugin, { enabled: !resolvedSpellCheck }),
6793
7093
  /* @__PURE__ */ jsx(
6794
7094
  FocusEventsPlugin,
6795
7095
  {
6796
7096
  onFocus: props.onFocus,
6797
- onBlur: props.onBlur,
7097
+ onBlur: () => {
7098
+ setTouched(true);
7099
+ props.onBlur?.();
7100
+ },
6798
7101
  setFocused,
6799
7102
  containerRef
6800
7103
  }
@@ -6841,20 +7144,16 @@ var ContentEditorComponent = forwardRef(
6841
7144
  }
6842
7145
  ),
6843
7146
  !isReadOnly && props.showFloatingToolbar && /* @__PURE__ */ jsx(CharacterStylesPopupPlugin, {}),
6844
- /* @__PURE__ */ jsx(
6845
- CustomOnChangePlugin,
6846
- {
6847
- value: props.value,
6848
- onChange: props.onChange
6849
- }
6850
- ),
6851
- props.wordLimit !== void 0 && /* @__PURE__ */ jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7147
+ /* @__PURE__ */ jsx(CustomOnChangePlugin, { value: props.value, onChange: props.onChange }),
7148
+ (props.wordLimit !== void 0 || props.required || props.minWords !== void 0) && /* @__PURE__ */ jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7149
+ (props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsx(CharCountPlugin, { onCountChange: handleCharCount }),
6852
7150
  /* @__PURE__ */ jsx(
6853
7151
  RefApiPlugin,
6854
7152
  {
6855
7153
  forwardedRef: ref,
6856
7154
  contentEditableDomRef,
6857
- focusedRef
7155
+ focusedRef,
7156
+ setRefErrors
6858
7157
  }
6859
7158
  )
6860
7159
  ]