@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.js CHANGED
@@ -9,6 +9,7 @@ var useLexicalNodeSelection = require('@lexical/react/useLexicalNodeSelection');
9
9
  var lexical = require('lexical');
10
10
  var react = require('@fluentui/react');
11
11
  var reactComponents = require('@fluentui/react-components');
12
+ var reactIcons = require('@fluentui/react-icons');
12
13
  var code = require('@lexical/code');
13
14
  var link = require('@lexical/link');
14
15
  var list = require('@lexical/list');
@@ -26,7 +27,6 @@ var richText = require('@lexical/rich-text');
26
27
  var table = require('@lexical/table');
27
28
  var LexicalBlockWithAlignableContents = require('@lexical/react/LexicalBlockWithAlignableContents');
28
29
  var LexicalDecoratorBlockNode = require('@lexical/react/LexicalDecoratorBlockNode');
29
- var reactIcons = require('@fluentui/react-icons');
30
30
  var selection = require('@lexical/selection');
31
31
  var reactDom = require('react-dom');
32
32
  var html = require('@lexical/html');
@@ -243,80 +243,107 @@ var init_ImageResizer = __esm({
243
243
  document.removeEventListener("pointerup", handlePointerUp);
244
244
  }
245
245
  };
246
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: controlWrapperRef, children: [
247
- /* @__PURE__ */ jsxRuntime.jsx(
248
- "div",
249
- {
250
- className: "image-resizer image-resizer-n",
251
- onPointerDown: (event) => {
252
- handlePointerDown(event, Direction.north);
253
- }
254
- }
255
- ),
256
- /* @__PURE__ */ jsxRuntime.jsx(
257
- "div",
258
- {
259
- className: "image-resizer image-resizer-ne",
260
- onPointerDown: (event) => {
261
- handlePointerDown(event, Direction.north | Direction.east);
262
- }
263
- }
264
- ),
265
- /* @__PURE__ */ jsxRuntime.jsx(
266
- "div",
267
- {
268
- className: "image-resizer image-resizer-e",
269
- onPointerDown: (event) => {
270
- handlePointerDown(event, Direction.east);
271
- }
272
- }
273
- ),
274
- /* @__PURE__ */ jsxRuntime.jsx(
275
- "div",
276
- {
277
- className: "image-resizer image-resizer-se",
278
- onPointerDown: (event) => {
279
- handlePointerDown(event, Direction.south | Direction.east);
280
- }
281
- }
282
- ),
283
- /* @__PURE__ */ jsxRuntime.jsx(
284
- "div",
285
- {
286
- className: "image-resizer image-resizer-s",
287
- onPointerDown: (event) => {
288
- handlePointerDown(event, Direction.south);
289
- }
290
- }
291
- ),
292
- /* @__PURE__ */ jsxRuntime.jsx(
293
- "div",
294
- {
295
- className: "image-resizer image-resizer-sw",
296
- onPointerDown: (event) => {
297
- handlePointerDown(event, Direction.south | Direction.west);
298
- }
299
- }
300
- ),
301
- /* @__PURE__ */ jsxRuntime.jsx(
302
- "div",
303
- {
304
- className: "image-resizer image-resizer-w",
305
- onPointerDown: (event) => {
306
- handlePointerDown(event, Direction.west);
307
- }
308
- }
309
- ),
310
- /* @__PURE__ */ jsxRuntime.jsx(
246
+ return (
247
+ // Overlay that exactly covers the image container (position:relative parent).
248
+ // pointer-events:none lets clicks pass through to the image; each handle
249
+ // re-enables pointer-events so drag-resize still works.
250
+ /* @__PURE__ */ jsxRuntime.jsxs(
311
251
  "div",
312
252
  {
313
- className: "image-resizer image-resizer-nw",
314
- onPointerDown: (event) => {
315
- handlePointerDown(event, Direction.north | Direction.west);
316
- }
253
+ ref: controlWrapperRef,
254
+ style: {
255
+ position: "absolute",
256
+ top: 0,
257
+ right: 0,
258
+ bottom: 0,
259
+ left: 0,
260
+ pointerEvents: "none"
261
+ },
262
+ children: [
263
+ /* @__PURE__ */ jsxRuntime.jsx(
264
+ "div",
265
+ {
266
+ className: "image-resizer image-resizer-n",
267
+ style: { pointerEvents: "auto" },
268
+ onPointerDown: (event) => {
269
+ handlePointerDown(event, Direction.north);
270
+ }
271
+ }
272
+ ),
273
+ /* @__PURE__ */ jsxRuntime.jsx(
274
+ "div",
275
+ {
276
+ className: "image-resizer image-resizer-ne",
277
+ style: { pointerEvents: "auto" },
278
+ onPointerDown: (event) => {
279
+ handlePointerDown(event, Direction.north | Direction.east);
280
+ }
281
+ }
282
+ ),
283
+ /* @__PURE__ */ jsxRuntime.jsx(
284
+ "div",
285
+ {
286
+ className: "image-resizer image-resizer-e",
287
+ style: { pointerEvents: "auto" },
288
+ onPointerDown: (event) => {
289
+ handlePointerDown(event, Direction.east);
290
+ }
291
+ }
292
+ ),
293
+ /* @__PURE__ */ jsxRuntime.jsx(
294
+ "div",
295
+ {
296
+ className: "image-resizer image-resizer-se",
297
+ style: { pointerEvents: "auto" },
298
+ onPointerDown: (event) => {
299
+ handlePointerDown(event, Direction.south | Direction.east);
300
+ }
301
+ }
302
+ ),
303
+ /* @__PURE__ */ jsxRuntime.jsx(
304
+ "div",
305
+ {
306
+ className: "image-resizer image-resizer-s",
307
+ style: { pointerEvents: "auto" },
308
+ onPointerDown: (event) => {
309
+ handlePointerDown(event, Direction.south);
310
+ }
311
+ }
312
+ ),
313
+ /* @__PURE__ */ jsxRuntime.jsx(
314
+ "div",
315
+ {
316
+ className: "image-resizer image-resizer-sw",
317
+ style: { pointerEvents: "auto" },
318
+ onPointerDown: (event) => {
319
+ handlePointerDown(event, Direction.south | Direction.west);
320
+ }
321
+ }
322
+ ),
323
+ /* @__PURE__ */ jsxRuntime.jsx(
324
+ "div",
325
+ {
326
+ className: "image-resizer image-resizer-w",
327
+ style: { pointerEvents: "auto" },
328
+ onPointerDown: (event) => {
329
+ handlePointerDown(event, Direction.west);
330
+ }
331
+ }
332
+ ),
333
+ /* @__PURE__ */ jsxRuntime.jsx(
334
+ "div",
335
+ {
336
+ className: "image-resizer image-resizer-nw",
337
+ style: { pointerEvents: "auto" },
338
+ onPointerDown: (event) => {
339
+ handlePointerDown(event, Direction.north | Direction.west);
340
+ }
341
+ }
342
+ )
343
+ ]
317
344
  }
318
345
  )
319
- ] });
346
+ );
320
347
  };
321
348
  ImageResizer_default = ImageResizer;
322
349
  }
@@ -3567,6 +3594,204 @@ function PageBreakPlugin() {
3567
3594
  }, [editor]);
3568
3595
  return null;
3569
3596
  }
3597
+
3598
+ // src/Utils/Sanitize.ts
3599
+ var DROP_ENTIRELY = /* @__PURE__ */ new Set([
3600
+ "script",
3601
+ "noscript",
3602
+ "style",
3603
+ "object",
3604
+ "embed",
3605
+ "form",
3606
+ "input",
3607
+ "button",
3608
+ "select",
3609
+ "textarea",
3610
+ "meta",
3611
+ "link",
3612
+ "base"
3613
+ ]);
3614
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
3615
+ // Block
3616
+ "p",
3617
+ "h1",
3618
+ "h2",
3619
+ "h3",
3620
+ "h4",
3621
+ "h5",
3622
+ "h6",
3623
+ "ul",
3624
+ "ol",
3625
+ "li",
3626
+ "blockquote",
3627
+ "pre",
3628
+ "div",
3629
+ "table",
3630
+ "thead",
3631
+ "tbody",
3632
+ "tfoot",
3633
+ "tr",
3634
+ "td",
3635
+ "th",
3636
+ // Inline
3637
+ "span",
3638
+ "a",
3639
+ "strong",
3640
+ "b",
3641
+ "em",
3642
+ "i",
3643
+ "u",
3644
+ "s",
3645
+ "del",
3646
+ "strike",
3647
+ "sub",
3648
+ "sup",
3649
+ "mark",
3650
+ "code",
3651
+ "br",
3652
+ "hr",
3653
+ // Media / embeds
3654
+ "img",
3655
+ "iframe"
3656
+ ]);
3657
+ var ALLOWED_ATTRS = /* @__PURE__ */ new Set([
3658
+ // Presentation
3659
+ "class",
3660
+ "style",
3661
+ "dir",
3662
+ "lang",
3663
+ // Anchors
3664
+ "href",
3665
+ "target",
3666
+ "rel",
3667
+ // Images
3668
+ "src",
3669
+ "alt",
3670
+ "width",
3671
+ "height",
3672
+ // Tables
3673
+ "colspan",
3674
+ "rowspan",
3675
+ // Lists — <ol type="a"> and <ol start="2">
3676
+ "start",
3677
+ "type",
3678
+ // Lexical-internal data markers
3679
+ "data-lex-block",
3680
+ "data-kind",
3681
+ "data-lexical-decorator",
3682
+ // Misc
3683
+ "title",
3684
+ "allowfullscreen"
3685
+ ]);
3686
+ var DANGEROUS_URL = /^\s*(javascript|data\s*:|vbscript)/i;
3687
+ function sanitizeElement(el) {
3688
+ for (const child of Array.from(el.children)) {
3689
+ sanitizeElement(child);
3690
+ }
3691
+ const tag = el.tagName.toLowerCase();
3692
+ if (DROP_ENTIRELY.has(tag)) {
3693
+ el.parentNode?.removeChild(el);
3694
+ return;
3695
+ }
3696
+ if (tag === "iframe") {
3697
+ const src = el.getAttribute("src") ?? "";
3698
+ const isYouTube = /^\s*https:\/\/(www\.)?(youtube\.com|youtube-nocookie\.com)\/embed\/[^?&/]+/i.test(src);
3699
+ if (!isYouTube) {
3700
+ el.parentNode?.removeChild(el);
3701
+ return;
3702
+ }
3703
+ }
3704
+ if (!ALLOWED_TAGS.has(tag)) {
3705
+ while (el.firstChild) {
3706
+ el.parentNode?.insertBefore(el.firstChild, el);
3707
+ }
3708
+ el.parentNode?.removeChild(el);
3709
+ return;
3710
+ }
3711
+ for (const { name, value } of Array.from(el.attributes)) {
3712
+ const lname = name.toLowerCase();
3713
+ if (!ALLOWED_ATTRS.has(lname)) {
3714
+ el.removeAttribute(name);
3715
+ continue;
3716
+ }
3717
+ if (lname === "href" || lname === "src") {
3718
+ if (DANGEROUS_URL.test(value)) {
3719
+ el.removeAttribute(name);
3720
+ }
3721
+ }
3722
+ if (lname === "style") {
3723
+ const safe = value.replace(/expression\s*\(/gi, "(").replace(/url\s*\(\s*['"]?\s*javascript:/gi, "url(");
3724
+ el.setAttribute("style", safe);
3725
+ }
3726
+ }
3727
+ if (tag === "a" && (el.getAttribute("target") || "").toLowerCase() === "_blank") {
3728
+ const rel = (el.getAttribute("rel") || "").trim();
3729
+ const tokens = new Set(
3730
+ rel.split(/\s+/).filter(Boolean).map((t) => t.toLowerCase())
3731
+ );
3732
+ tokens.add("noopener");
3733
+ tokens.add("noreferrer");
3734
+ el.setAttribute("rel", Array.from(tokens).join(" "));
3735
+ }
3736
+ }
3737
+ function sanitizeHtml(html) {
3738
+ if (!html || typeof html !== "string") return "";
3739
+ const doc = new DOMParser().parseFromString(html, "text/html");
3740
+ for (const child of Array.from(doc.body.children)) {
3741
+ sanitizeElement(child);
3742
+ }
3743
+ return doc.body.innerHTML;
3744
+ }
3745
+ var BLOCK_TAGS = /* @__PURE__ */ new Set([
3746
+ "p",
3747
+ "h1",
3748
+ "h2",
3749
+ "h3",
3750
+ "h4",
3751
+ "h5",
3752
+ "h6",
3753
+ "div",
3754
+ "ul",
3755
+ "ol",
3756
+ "li",
3757
+ "table",
3758
+ "blockquote",
3759
+ "pre",
3760
+ "hr"
3761
+ ]);
3762
+ function normalizeToBlockHtml(html) {
3763
+ if (!html) return "";
3764
+ const doc = new DOMParser().parseFromString(html, "text/html");
3765
+ const body = doc.body;
3766
+ const childNodes = Array.from(body.childNodes);
3767
+ const needsWrap = childNodes.some((node) => {
3768
+ if (node.nodeType === Node.TEXT_NODE) return !!node.nodeValue?.trim();
3769
+ if (node.nodeType === Node.ELEMENT_NODE)
3770
+ return !BLOCK_TAGS.has(node.tagName.toLowerCase());
3771
+ return false;
3772
+ });
3773
+ if (!needsWrap) return html;
3774
+ while (body.firstChild) body.removeChild(body.firstChild);
3775
+ let pendingP = null;
3776
+ for (const node of childNodes) {
3777
+ const isBlock = node.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(node.tagName.toLowerCase());
3778
+ const isWhitespace = node.nodeType === Node.TEXT_NODE && !node.nodeValue?.trim();
3779
+ if (isBlock) {
3780
+ if (pendingP) {
3781
+ body.appendChild(pendingP);
3782
+ pendingP = null;
3783
+ }
3784
+ body.appendChild(node);
3785
+ } else if (!isWhitespace) {
3786
+ if (!pendingP) pendingP = doc.createElement("p");
3787
+ pendingP.appendChild(node);
3788
+ }
3789
+ }
3790
+ if (pendingP) body.appendChild(pendingP);
3791
+ return body.innerHTML;
3792
+ }
3793
+
3794
+ // src/Utils/Helper.ts
3570
3795
  function findBlockByKind(kind) {
3571
3796
  const root = lexical.$getRoot();
3572
3797
  for (const child of root.getChildren()) {
@@ -3575,8 +3800,9 @@ function findBlockByKind(kind) {
3575
3800
  return null;
3576
3801
  }
3577
3802
  function importHtmlIntoBlock(editor, block, html$1) {
3803
+ const safe = normalizeToBlockHtml(sanitizeHtml(html$1));
3578
3804
  const parser = new DOMParser();
3579
- const doc = parser.parseFromString(html$1 || "<p></p>", "text/html");
3805
+ const doc = parser.parseFromString(safe || "<p></p>", "text/html");
3580
3806
  const nodes = html.$generateNodesFromDOM(editor, doc);
3581
3807
  block.clear();
3582
3808
  block.append(...nodes);
@@ -3615,7 +3841,8 @@ function hasBlock(editor, kind) {
3615
3841
  function RefApiPlugin({
3616
3842
  forwardedRef,
3617
3843
  contentEditableDomRef,
3618
- focusedRef
3844
+ focusedRef,
3845
+ setRefErrors
3619
3846
  }) {
3620
3847
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
3621
3848
  React6.useImperativeHandle(
@@ -3654,12 +3881,14 @@ function RefApiPlugin({
3654
3881
  },
3655
3882
  isFocused: () => focusedRef.current,
3656
3883
  getEditor: () => editor,
3884
+ setErrors: (messages) => setRefErrors(messages),
3885
+ clearErrors: () => setRefErrors([]),
3657
3886
  // Generic blocks (signature, footer, banner, etc.)
3658
3887
  upsertBlock: (spec) => upsertBlock(editor, spec),
3659
3888
  removeBlock: (kind) => removeBlock(editor, kind),
3660
3889
  hasBlock: (kind) => hasBlock(editor, kind)
3661
3890
  }),
3662
- [editor, contentEditableDomRef, focusedRef]
3891
+ [editor, contentEditableDomRef, focusedRef, setRefErrors]
3663
3892
  );
3664
3893
  return null;
3665
3894
  }
@@ -4299,8 +4528,8 @@ function SpellCheckPlugin({
4299
4528
  function TableActionMenuPlugin({ disabled = false }) {
4300
4529
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
4301
4530
  const [isInTable, setIsInTable] = React6__namespace.useState(false);
4531
+ const [anchorRect, setAnchorRect] = React6__namespace.useState(null);
4302
4532
  const [open, setOpen] = React6__namespace.useState(false);
4303
- const [menuPos, setMenuPos] = React6__namespace.useState(null);
4304
4533
  const updateFromSelection = React6__namespace.useCallback(() => {
4305
4534
  const root = editor.getRootElement();
4306
4535
  if (!root) return;
@@ -4309,21 +4538,34 @@ function TableActionMenuPlugin({ disabled = false }) {
4309
4538
  if (table.$isTableSelection(selection)) {
4310
4539
  const tableNode = selection.getNodes().find((n) => table.$isTableNode(n));
4311
4540
  if (tableNode) {
4312
- setIsInTable(true);
4313
- return;
4541
+ const dom = editor.getElementByKey(tableNode.getKey());
4542
+ if (dom) {
4543
+ setIsInTable(true);
4544
+ setAnchorRect(dom.getBoundingClientRect());
4545
+ return;
4546
+ }
4314
4547
  }
4315
4548
  }
4316
4549
  if (!lexical.$isRangeSelection(selection)) {
4317
4550
  setIsInTable(false);
4551
+ setAnchorRect(null);
4318
4552
  return;
4319
4553
  }
4320
4554
  const anchorNode = selection.anchor.getNode();
4321
4555
  const cellNode = table.$isTableCellNode(anchorNode) ? anchorNode : lexical.$findMatchingParent(anchorNode, (n) => table.$isTableCellNode(n));
4322
4556
  if (!cellNode || !table.$isTableCellNode(cellNode)) {
4323
4557
  setIsInTable(false);
4558
+ setAnchorRect(null);
4559
+ return;
4560
+ }
4561
+ const cellDom = editor.getElementByKey(cellNode.getKey());
4562
+ if (!cellDom) {
4563
+ setIsInTable(false);
4564
+ setAnchorRect(null);
4324
4565
  return;
4325
4566
  }
4326
4567
  setIsInTable(true);
4568
+ setAnchorRect(cellDom.getBoundingClientRect());
4327
4569
  });
4328
4570
  }, [editor]);
4329
4571
  React6__namespace.useEffect(() => {
@@ -4385,33 +4627,18 @@ function TableActionMenuPlugin({ disabled = false }) {
4385
4627
  React6__namespace.useEffect(() => {
4386
4628
  if (!isInTable && open) setOpen(false);
4387
4629
  }, [isInTable, open]);
4388
- React6__namespace.useEffect(() => {
4389
- const root = editor.getRootElement();
4390
- if (!root) return;
4391
- const handleContextMenu = (e) => {
4392
- if (disabled) return;
4393
- let inTable = false;
4394
- editor.getEditorState().read(() => {
4395
- const selection = lexical.$getSelection();
4396
- if (table.$isTableSelection(selection)) {
4397
- inTable = true;
4398
- return;
4399
- }
4400
- if (lexical.$isRangeSelection(selection)) {
4401
- const node = selection.anchor.getNode();
4402
- const cell = table.$isTableCellNode(node) ? node : lexical.$findMatchingParent(node, (n) => table.$isTableCellNode(n));
4403
- if (cell) inTable = true;
4404
- }
4405
- });
4406
- if (inTable) {
4407
- e.preventDefault();
4408
- setMenuPos({ x: e.clientX, y: e.clientY });
4409
- setOpen(true);
4410
- }
4630
+ const canShow = isInTable && !!anchorRect && !disabled;
4631
+ const handleStyle = React6__namespace.useMemo(() => {
4632
+ if (!anchorRect) return void 0;
4633
+ const top = Math.max(8, anchorRect.top + 6);
4634
+ const left = Math.max(8, anchorRect.right - 34);
4635
+ return {
4636
+ position: "fixed",
4637
+ top,
4638
+ left,
4639
+ zIndex: 9999
4411
4640
  };
4412
- root.addEventListener("contextmenu", handleContextMenu);
4413
- return () => root.removeEventListener("contextmenu", handleContextMenu);
4414
- }, [editor, disabled]);
4641
+ }, [anchorRect]);
4415
4642
  const dangerStyle = {
4416
4643
  color: "var(--colorPaletteRedForeground1)"
4417
4644
  };
@@ -4444,55 +4671,63 @@ function TableActionMenuPlugin({ disabled = false }) {
4444
4671
  const table$1 = table.$getTableNodeFromLexicalNodeOrThrow(cell);
4445
4672
  table$1.remove();
4446
4673
  });
4447
- const virtualTarget = React6__namespace.useMemo(() => {
4448
- if (!menuPos) return void 0;
4449
- return {
4450
- getBoundingClientRect: () => new DOMRect(menuPos.x, menuPos.y, 0, 0)
4451
- };
4452
- }, [menuPos]);
4453
- if (disabled) return null;
4674
+ if (!canShow || !handleStyle) return null;
4454
4675
  return reactDom.createPortal(
4455
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Menu, { open, onOpenChange: (_, data) => setOpen(data.open), positioning: { target: virtualTarget }, children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuPopover, { className: "aoTableActionPopover", children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuList, { children: [
4456
- /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuGroup, { children: [
4457
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuGroupHeader, { children: "Insert" }),
4458
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.RowTripleRegular, {}), onClick: insertRowAbove, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuRow", children: [
4459
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuLabel", children: [
4460
- /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowUpRegular, {}),
4461
- " Row above"
4462
- ] }),
4463
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2191" })
4464
- ] }) }),
4465
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.RowTripleRegular, {}), onClick: insertRowBelow, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuRow", children: [
4466
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuLabel", children: [
4467
- /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowDownRegular, {}),
4468
- " Row below"
4469
- ] }),
4470
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2193" })
4471
- ] }) }),
4676
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: handleStyle, className: "aoTableActionHandleRoot", children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Menu, { open, onOpenChange: (_, data) => setOpen(data.open), children: [
4677
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4678
+ "button",
4679
+ {
4680
+ type: "button",
4681
+ className: "aoTableActionHandleBtn",
4682
+ "aria-label": "Table options",
4683
+ onMouseDown: (e) => {
4684
+ e.preventDefault();
4685
+ },
4686
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronDown12Regular, {})
4687
+ }
4688
+ ) }),
4689
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuPopover, { className: "aoTableActionPopover", children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuList, { children: [
4690
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuGroup, { children: [
4691
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuGroupHeader, { children: "Insert" }),
4692
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.RowTripleRegular, {}), onClick: insertRowAbove, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuRow", children: [
4693
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuLabel", children: [
4694
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowUpRegular, {}),
4695
+ " Row above"
4696
+ ] }),
4697
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2191" })
4698
+ ] }) }),
4699
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.RowTripleRegular, {}), onClick: insertRowBelow, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuRow", children: [
4700
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuLabel", children: [
4701
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowDownRegular, {}),
4702
+ " Row below"
4703
+ ] }),
4704
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2193" })
4705
+ ] }) }),
4706
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuDivider, {}),
4707
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ColumnTripleRegular, {}), onClick: insertColLeft, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuRow", children: [
4708
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuLabel", children: [
4709
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowLeftRegular, {}),
4710
+ " Column left"
4711
+ ] }),
4712
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2190" })
4713
+ ] }) }),
4714
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ColumnTripleRegular, {}), onClick: insertColRight, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuRow", children: [
4715
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuLabel", children: [
4716
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowRightRegular, {}),
4717
+ " Column right"
4718
+ ] }),
4719
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2192" })
4720
+ ] }) })
4721
+ ] }),
4472
4722
  /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuDivider, {}),
4473
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ColumnTripleRegular, {}), onClick: insertColLeft, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuRow", children: [
4474
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuLabel", children: [
4475
- /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowLeftRegular, {}),
4476
- " Column left"
4477
- ] }),
4478
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2190" })
4479
- ] }) }),
4480
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ColumnTripleRegular, {}), onClick: insertColRight, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuRow", children: [
4481
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "aoMenuLabel", children: [
4482
- /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowRightRegular, {}),
4483
- " Column right"
4484
- ] }),
4485
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aoMenuShortcut", children: "Alt \u21E7 \u2192" })
4486
- ] }) })
4487
- ] }),
4488
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuDivider, {}),
4489
- /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuGroup, { children: [
4490
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuGroupHeader, { children: "Delete" }),
4491
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DeleteRegular, {}), onClick: deleteRow, style: dangerStyle, children: "Delete row" }),
4492
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DeleteRegular, {}), onClick: deleteCol, style: dangerStyle, children: "Delete column" }),
4493
- /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DeleteRegular, {}), onClick: deleteTable, style: dangerStyle, children: "Delete table" })
4494
- ] })
4495
- ] }) }) }),
4723
+ /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuGroup, { children: [
4724
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuGroupHeader, { children: "Delete" }),
4725
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DeleteRegular, {}), onClick: deleteRow, style: dangerStyle, children: "Delete row" }),
4726
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DeleteRegular, {}), onClick: deleteCol, style: dangerStyle, children: "Delete column" }),
4727
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DeleteRegular, {}), onClick: deleteTable, style: dangerStyle, children: "Delete table" })
4728
+ ] })
4729
+ ] }) })
4730
+ ] }) }),
4496
4731
  document.body
4497
4732
  );
4498
4733
  }
@@ -4796,13 +5031,6 @@ function getToolbarGroupsByLevel(level) {
4796
5031
  ];
4797
5032
  }
4798
5033
  }
4799
- var DEFAULT_FONT_SIZE = 15;
4800
- var formatParagraph = (editor) => {
4801
- editor.update(() => {
4802
- const selection$1 = lexical.$getSelection();
4803
- selection.$setBlocksType(selection$1, () => lexical.$createParagraphNode());
4804
- });
4805
- };
4806
5034
  var PRESET = [
4807
5035
  "#000000",
4808
5036
  "#434343",
@@ -4878,11 +5106,12 @@ var hsvToRgb = (h, s, v) => {
4878
5106
  b: Math.round((bb + m) * 255)
4879
5107
  };
4880
5108
  };
4881
- function useDrag(onMove, onEnd) {
5109
+ function useDrag(onMove, onEnd, interactingRef) {
4882
5110
  const draggingRef = React6__namespace.useRef(false);
4883
5111
  const start = React6__namespace.useCallback(
4884
5112
  (e) => {
4885
5113
  draggingRef.current = true;
5114
+ if (interactingRef) interactingRef.current = true;
4886
5115
  onMove(e.clientX, e.clientY);
4887
5116
  const move = (ev) => {
4888
5117
  if (!draggingRef.current) return;
@@ -4892,17 +5121,55 @@ function useDrag(onMove, onEnd) {
4892
5121
  draggingRef.current = false;
4893
5122
  window.removeEventListener("mousemove", move);
4894
5123
  window.removeEventListener("mouseup", up);
5124
+ if (interactingRef) {
5125
+ const clearFlag = () => {
5126
+ interactingRef.current = false;
5127
+ };
5128
+ window.addEventListener("click", clearFlag, { once: true });
5129
+ setTimeout(() => {
5130
+ window.removeEventListener("click", clearFlag);
5131
+ interactingRef.current = false;
5132
+ }, 0);
5133
+ }
4895
5134
  };
4896
5135
  window.addEventListener("mousemove", move);
4897
5136
  window.addEventListener("mouseup", up);
4898
5137
  },
4899
- [onMove, onEnd]
5138
+ [onMove, onEnd, interactingRef]
4900
5139
  );
4901
5140
  return start;
4902
5141
  }
4903
5142
  var ColorPickerControl = ({ value, title, disabled, onChange, icon }) => {
4904
5143
  const [open, setOpen] = React6__namespace.useState(false);
4905
5144
  const btnRef = React6__namespace.useRef(null);
5145
+ const interactingRef = React6__namespace.useRef(false);
5146
+ const handleDismiss = React6__namespace.useCallback(() => setOpen(false), []);
5147
+ const preventDismissOnEvent = React6__namespace.useCallback(
5148
+ (ev) => {
5149
+ if (interactingRef.current) return true;
5150
+ return ev.type !== "click";
5151
+ },
5152
+ []
5153
+ );
5154
+ const [, forceReposition] = React6__namespace.useState(0);
5155
+ React6__namespace.useEffect(() => {
5156
+ if (!open) return;
5157
+ let rafId = null;
5158
+ const reposition = () => {
5159
+ if (rafId != null) return;
5160
+ rafId = requestAnimationFrame(() => {
5161
+ rafId = null;
5162
+ forceReposition((n) => n + 1);
5163
+ });
5164
+ };
5165
+ window.addEventListener("scroll", reposition, true);
5166
+ window.addEventListener("resize", reposition);
5167
+ return () => {
5168
+ if (rafId != null) cancelAnimationFrame(rafId);
5169
+ window.removeEventListener("scroll", reposition, true);
5170
+ window.removeEventListener("resize", reposition);
5171
+ };
5172
+ }, [open]);
4906
5173
  const [hex, setHex] = React6__namespace.useState(normalizeHex(value || "#000000"));
4907
5174
  const { r, g, b } = React6__namespace.useMemo(() => hexToRgb(hex), [hex]);
4908
5175
  const hsv = React6__namespace.useMemo(() => rgbToHsv(r, g, b), [r, g, b]);
@@ -4943,7 +5210,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon }) => {
4943
5210
  },
4944
5211
  [h, commitHsv]
4945
5212
  );
4946
- const startSV = useDrag(onSVMove);
5213
+ const startSV = useDrag(onSVMove, void 0, interactingRef);
4947
5214
  const hueRef = React6__namespace.useRef(null);
4948
5215
  const onHueMove = React6__namespace.useCallback(
4949
5216
  (clientX) => {
@@ -4956,7 +5223,7 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon }) => {
4956
5223
  },
4957
5224
  [s, v, commitHsv]
4958
5225
  );
4959
- const startHue = useDrag((x) => onHueMove(x));
5226
+ const startHue = useDrag((x) => onHueMove(x), void 0, interactingRef);
4960
5227
  const svThumb = React6__namespace.useMemo(() => ({ left: `${s * 100}%`, top: `${(1 - v) * 100}%` }), [s, v]);
4961
5228
  const hueThumb = React6__namespace.useMemo(() => ({ left: `${h / 360 * 100}%` }), [h]);
4962
5229
  const hueColor = React6__namespace.useMemo(() => {
@@ -5005,10 +5272,11 @@ var ColorPickerControl = ({ value, title, disabled, onChange, icon }) => {
5005
5272
  react.Callout,
5006
5273
  {
5007
5274
  target: btnRef,
5008
- onDismiss: () => setOpen(false),
5275
+ onDismiss: handleDismiss,
5009
5276
  setInitialFocus: true,
5010
5277
  directionalHint: 4,
5011
5278
  className: "aoColorCallout",
5279
+ preventDismissOnEvent,
5012
5280
  children: /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { tokens: { childrenGap: 10 }, styles: { root: { padding: 12, width: 320 } }, children: [
5013
5281
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aoLexRow", children: [
5014
5282
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aoLexSwatch", style: { background: hex } }),
@@ -5108,7 +5376,9 @@ var ColorPickerPlugin = ({ disabled }) => {
5108
5376
  }, [editor]);
5109
5377
  const applyStyle = (args) => {
5110
5378
  if (disabled) return;
5111
- editor.focus();
5379
+ const root = editor.getRootElement();
5380
+ const editorIsActive = !!lastRangeSelectionRef.current && !!root && (document.activeElement === root || root.contains(document.activeElement));
5381
+ if (editorIsActive) editor.focus();
5112
5382
  editor.update(() => {
5113
5383
  const saved = lastRangeSelectionRef.current;
5114
5384
  if (saved) {
@@ -5247,6 +5517,7 @@ var FontFamilyPlugin = ({ disabled = false }) => {
5247
5517
  "font-family"
5248
5518
  );
5249
5519
  };
5520
+ var DEFAULT_FONT_SIZE = 15;
5250
5521
  var FONT_SIZE_OPTIONS = [
5251
5522
  "8",
5252
5523
  "9",
@@ -5547,21 +5818,27 @@ var TableItemPlugin = ({ disabled }) => {
5547
5818
  reactComponents.Input,
5548
5819
  {
5549
5820
  autoFocus: !disabled,
5821
+ type: "number",
5822
+ min: 1,
5550
5823
  value: rows,
5551
5824
  placeholder: "Rows",
5552
5825
  appearance: "underline",
5553
5826
  disabled,
5554
- onChange: (_, v) => setRows(v.value)
5827
+ input: { style: { textAlign: "left" } },
5828
+ onChange: (_, v) => setRows(v.value.replace(/\D/g, ""))
5555
5829
  }
5556
5830
  ) }),
5557
5831
  /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Columns", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
5558
5832
  reactComponents.Input,
5559
5833
  {
5834
+ type: "number",
5835
+ min: 1,
5560
5836
  value: columns,
5561
5837
  placeholder: "Columns",
5562
5838
  appearance: "underline",
5563
5839
  disabled,
5564
- onChange: (_, v) => setColumns(v.value)
5840
+ input: { style: { textAlign: "left" } },
5841
+ onChange: (_, v) => setColumns(v.value.replace(/\D/g, ""))
5565
5842
  }
5566
5843
  ) }),
5567
5844
  /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
@@ -5718,8 +5995,7 @@ var ToolBarPlugins = (props) => {
5718
5995
  const [isLowercase, setIsLowercase] = React6.useState(false);
5719
5996
  const [isCapitalize, setIsCapitalize] = React6.useState(false);
5720
5997
  const [alignment, setAlignment] = React6.useState("left");
5721
- const [isInTable, setIsInTable] = React6.useState(false);
5722
- const [tableNodeKey, setTableNodeKey] = React6.useState(null);
5998
+ const lastSelectionRef = React6__namespace.default.useRef(null);
5723
5999
  const presetGroups = getToolbarGroupsByLevel(props.level);
5724
6000
  const pluginGroups = React6.useMemo(() => sanitizePluginGroups(presetGroups), [presetGroups]);
5725
6001
  const updateToolbarPlugins = () => {
@@ -5736,8 +6012,6 @@ var ToolBarPlugins = (props) => {
5736
6012
  setIsLowercase(false);
5737
6013
  setIsCapitalize(false);
5738
6014
  setSelectNodeType("paragraph");
5739
- setIsInTable(false);
5740
- setTableNodeKey(null);
5741
6015
  return;
5742
6016
  }
5743
6017
  setIsBold(selection.hasFormat("bold"));
@@ -5753,21 +6027,8 @@ var ToolBarPlugins = (props) => {
5753
6027
  const anchorNode = selection.anchor.getNode();
5754
6028
  if (anchorNode.getKey() === "root") {
5755
6029
  setSelectNodeType("paragraph");
5756
- setIsInTable(false);
5757
- setTableNodeKey(null);
5758
6030
  return;
5759
6031
  }
5760
- let tableAncestorKey = null;
5761
- let cursor = anchorNode.getParent();
5762
- while (cursor !== null) {
5763
- if (cursor.getType() === "table") {
5764
- tableAncestorKey = cursor.getKey();
5765
- break;
5766
- }
5767
- cursor = cursor.getParent();
5768
- }
5769
- setIsInTable(tableAncestorKey !== null);
5770
- setTableNodeKey(tableAncestorKey);
5771
6032
  const element = anchorNode.getTopLevelElementOrThrow();
5772
6033
  setAlignment(
5773
6034
  typeof element.getFormatType === "function" ? element.getFormatType() || "left" : "left"
@@ -5788,12 +6049,21 @@ var ToolBarPlugins = (props) => {
5788
6049
  ["paragraph", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "quote", "code"].includes(type) ? type : "paragraph"
5789
6050
  );
5790
6051
  };
6052
+ const applyToBlock = React6__namespace.default.useCallback(
6053
+ (fn) => {
6054
+ editor.update(() => {
6055
+ const saved = lastSelectionRef.current;
6056
+ if (saved) lexical.$setSelection(saved.clone());
6057
+ const sel = lexical.$getSelection();
6058
+ if (lexical.$isRangeSelection(sel)) fn(sel);
6059
+ });
6060
+ },
6061
+ [editor]
6062
+ );
5791
6063
  const formatQuote = () => {
5792
- editor.update(() => {
5793
- const selection$1 = lexical.$getSelection();
5794
- if (!lexical.$isRangeSelection(selection$1)) return;
6064
+ applyToBlock((selection$1) => {
5795
6065
  if (selectNodeType === "quote") {
5796
- formatParagraph(editor);
6066
+ selection.$setBlocksType(selection$1, () => lexical.$createParagraphNode());
5797
6067
  } else {
5798
6068
  selection.$setBlocksType(selection$1, () => richText.$createQuoteNode());
5799
6069
  }
@@ -5812,6 +6082,8 @@ var ToolBarPlugins = (props) => {
5812
6082
  editor.registerCommand(
5813
6083
  lexical.SELECTION_CHANGE_COMMAND,
5814
6084
  () => {
6085
+ const sel = lexical.$getSelection();
6086
+ if (lexical.$isRangeSelection(sel)) lastSelectionRef.current = sel.clone();
5815
6087
  updateToolbarPlugins();
5816
6088
  return false;
5817
6089
  },
@@ -5843,16 +6115,52 @@ var ToolBarPlugins = (props) => {
5843
6115
  editor.dispatchCommand(lexical.FORMAT_TEXT_COMMAND, "highlight");
5844
6116
  break;
5845
6117
  case "leftAlign" /* LeftAlign */:
5846
- editor.dispatchCommand(lexical.FORMAT_ELEMENT_COMMAND, "left");
6118
+ applyToBlock((sel) => {
6119
+ const seen = /* @__PURE__ */ new Set();
6120
+ sel.getNodes().forEach((n) => {
6121
+ const t = n.getTopLevelElementOrThrow();
6122
+ if (!seen.has(t.getKey())) {
6123
+ seen.add(t.getKey());
6124
+ t.setFormat("left");
6125
+ }
6126
+ });
6127
+ });
5847
6128
  break;
5848
6129
  case "rightAlign" /* RightAlign */:
5849
- editor.dispatchCommand(lexical.FORMAT_ELEMENT_COMMAND, "right");
6130
+ applyToBlock((sel) => {
6131
+ const seen = /* @__PURE__ */ new Set();
6132
+ sel.getNodes().forEach((n) => {
6133
+ const t = n.getTopLevelElementOrThrow();
6134
+ if (!seen.has(t.getKey())) {
6135
+ seen.add(t.getKey());
6136
+ t.setFormat("right");
6137
+ }
6138
+ });
6139
+ });
5850
6140
  break;
5851
6141
  case "centerAlign" /* CenterAlign */:
5852
- editor.dispatchCommand(lexical.FORMAT_ELEMENT_COMMAND, "center");
6142
+ applyToBlock((sel) => {
6143
+ const seen = /* @__PURE__ */ new Set();
6144
+ sel.getNodes().forEach((n) => {
6145
+ const t = n.getTopLevelElementOrThrow();
6146
+ if (!seen.has(t.getKey())) {
6147
+ seen.add(t.getKey());
6148
+ t.setFormat("center");
6149
+ }
6150
+ });
6151
+ });
5853
6152
  break;
5854
6153
  case "justifyAlign" /* JustifyAlign */:
5855
- editor.dispatchCommand(lexical.FORMAT_ELEMENT_COMMAND, "justify");
6154
+ applyToBlock((sel) => {
6155
+ const seen = /* @__PURE__ */ new Set();
6156
+ sel.getNodes().forEach((n) => {
6157
+ const t = n.getTopLevelElementOrThrow();
6158
+ if (!seen.has(t.getKey())) {
6159
+ seen.add(t.getKey());
6160
+ t.setFormat("justify");
6161
+ }
6162
+ });
6163
+ });
5856
6164
  break;
5857
6165
  case "undo" /* Undo */:
5858
6166
  editor.dispatchCommand(lexical.UNDO_COMMAND, void 0);
@@ -5872,14 +6180,8 @@ var ToolBarPlugins = (props) => {
5872
6180
  }
5873
6181
  };
5874
6182
  const updateHeading = (heading) => {
5875
- editor.update(() => {
5876
- const selection$1 = lexical.$getSelection();
5877
- if (lexical.$isRangeSelection(selection$1)) {
5878
- selection.$setBlocksType(selection$1, () => richText.$createHeadingNode(heading));
5879
- }
5880
- });
5881
- editor.getEditorState().read(() => {
5882
- updateToolbarPlugins();
6183
+ applyToBlock((selection$1) => {
6184
+ selection.$setBlocksType(selection$1, () => richText.$createHeadingNode(heading));
5883
6185
  });
5884
6186
  };
5885
6187
  const renderToken = (token, groupIndex, tokenIndex) => {
@@ -6018,7 +6320,7 @@ var ToolBarPlugins = (props) => {
6018
6320
  const val = data.optionValue;
6019
6321
  if (!val) return;
6020
6322
  if (val === "paragraph") {
6021
- formatParagraph(editor);
6323
+ applyToBlock((sel) => selection.$setBlocksType(sel, () => lexical.$createParagraphNode()));
6022
6324
  setSelectNodeType("paragraph");
6023
6325
  } else {
6024
6326
  updateHeading(val);
@@ -6102,16 +6404,10 @@ var ToolBarPlugins = (props) => {
6102
6404
  onHandleSelectOption("highlight" /* Highlight */);
6103
6405
  break;
6104
6406
  case "ul-list":
6105
- editor.dispatchCommand(
6106
- selectNodeType === "ul" ? list.REMOVE_LIST_COMMAND : list.INSERT_UNORDERED_LIST_COMMAND,
6107
- void 0
6108
- );
6407
+ editor.dispatchCommand(selectNodeType === "ul" ? list.REMOVE_LIST_COMMAND : list.INSERT_UNORDERED_LIST_COMMAND, void 0);
6109
6408
  break;
6110
6409
  case "ol-list":
6111
- editor.dispatchCommand(
6112
- selectNodeType === "ol" ? list.REMOVE_LIST_COMMAND : list.INSERT_ORDERED_LIST_COMMAND,
6113
- void 0
6114
- );
6410
+ editor.dispatchCommand(selectNodeType === "ol" ? list.REMOVE_LIST_COMMAND : list.INSERT_ORDERED_LIST_COMMAND, void 0);
6115
6411
  break;
6116
6412
  case "page-break":
6117
6413
  editor.dispatchCommand(INSERT_PAGE_BREAK, void 0);
@@ -6147,12 +6443,7 @@ var ToolBarPlugins = (props) => {
6147
6443
  "Superscript"
6148
6444
  ] }),
6149
6445
  /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Option, { value: "highlight", text: "Highlight", children: [
6150
- /* @__PURE__ */ jsxRuntime.jsx(
6151
- reactIcons.HighlightAccentFilled,
6152
- {
6153
- style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled }
6154
- }
6155
- ),
6446
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.HighlightAccentFilled, { style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled } }),
6156
6447
  "Highlight"
6157
6448
  ] }),
6158
6449
  /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Option, { value: "ul-list", text: "Bullet list", children: [
@@ -6188,30 +6479,10 @@ var ToolBarPlugins = (props) => {
6188
6479
  // );
6189
6480
  case "Align": {
6190
6481
  const ALIGN_OPTIONS = [
6191
- {
6192
- value: "left",
6193
- label: "Left Align",
6194
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignLeftFilled, { style: optionIconStyle }),
6195
- action: "leftAlign" /* LeftAlign */
6196
- },
6197
- {
6198
- value: "center",
6199
- label: "Center Align",
6200
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignCenterFilled, { style: optionIconStyle }),
6201
- action: "centerAlign" /* CenterAlign */
6202
- },
6203
- {
6204
- value: "right",
6205
- label: "Right Align",
6206
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignRightFilled, { style: optionIconStyle }),
6207
- action: "rightAlign" /* RightAlign */
6208
- },
6209
- {
6210
- value: "justify",
6211
- label: "Justify Align",
6212
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignJustifyFilled, { style: optionIconStyle }),
6213
- action: "justifyAlign" /* JustifyAlign */
6214
- }
6482
+ { value: "left", label: "Left Align", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignLeftFilled, { style: optionIconStyle }), action: "leftAlign" /* LeftAlign */ },
6483
+ { value: "center", label: "Center Align", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignCenterFilled, { style: optionIconStyle }), action: "centerAlign" /* CenterAlign */ },
6484
+ { value: "right", label: "Right Align", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignRightFilled, { style: optionIconStyle }), action: "rightAlign" /* RightAlign */ },
6485
+ { value: "justify", label: "Justify Align", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignJustifyFilled, { style: optionIconStyle }), action: "justifyAlign" /* JustifyAlign */ }
6215
6486
  ];
6216
6487
  const alignLabel = ALIGN_OPTIONS.find((o) => o.value === alignment)?.label ?? "Left Align";
6217
6488
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -6245,69 +6516,29 @@ var ToolBarPlugins = (props) => {
6245
6516
  if (!pluginGroups || pluginGroups.length === 0) {
6246
6517
  return null;
6247
6518
  }
6248
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { children: [
6249
- /* @__PURE__ */ jsxRuntime.jsx(
6250
- "div",
6251
- {
6252
- role: "toolbar",
6253
- "aria-label": "Editor toolbar",
6254
- style: {
6255
- display: "flex",
6256
- flexWrap: "wrap",
6257
- alignItems: "center",
6258
- borderBottom: "1px solid #ccced1",
6259
- background: "#ffffff",
6260
- padding: "0px",
6261
- minHeight: 36
6262
- },
6263
- children: pluginGroups.map((group, groupIndex) => /* @__PURE__ */ jsxRuntime.jsx(React6__namespace.default.Fragment, { children: group.map((token, tokenIndex) => {
6264
- try {
6265
- return renderToken(token, groupIndex, tokenIndex);
6266
- } catch {
6267
- return null;
6268
- }
6269
- }) }, `group-${groupIndex}`))
6270
- }
6271
- ),
6272
- isInTable && /* @__PURE__ */ jsxRuntime.jsxs(
6273
- "div",
6274
- {
6275
- style: {
6276
- display: "flex",
6277
- alignItems: "center",
6278
- gap: 6,
6279
- padding: "2px 8px",
6280
- borderBottom: "1px solid #ccced1",
6281
- background: "#fff8f8"
6282
- },
6283
- children: [
6284
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#888", userSelect: "none" }, children: "Table" }),
6285
- /* @__PURE__ */ jsxRuntime.jsx(
6286
- reactComponents.Button,
6287
- {
6288
- size: "small",
6289
- icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DeleteRegular, { style: { color: "#c4272c" } }),
6290
- title: "Delete table",
6291
- style: {
6292
- background: "transparent",
6293
- border: "none",
6294
- color: "#c4272c",
6295
- fontWeight: 500
6296
- },
6297
- onClick: () => {
6298
- editor.update(() => {
6299
- if (!tableNodeKey) return;
6300
- const node = lexical.$getNodeByKey(tableNodeKey);
6301
- if (node?.getType() === "table") node.remove();
6302
- });
6303
- },
6304
- children: "Delete Table"
6305
- }
6306
- )
6307
- ]
6308
- }
6309
- )
6310
- ] });
6519
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Stack, { children: /* @__PURE__ */ jsxRuntime.jsx(
6520
+ "div",
6521
+ {
6522
+ role: "toolbar",
6523
+ "aria-label": "Editor toolbar",
6524
+ style: {
6525
+ display: "flex",
6526
+ flexWrap: "wrap",
6527
+ alignItems: "center",
6528
+ borderBottom: "1px solid #ccced1",
6529
+ background: "#ffffff",
6530
+ padding: "0px",
6531
+ minHeight: 36
6532
+ },
6533
+ children: pluginGroups.map((group, groupIndex) => /* @__PURE__ */ jsxRuntime.jsx(React6__namespace.default.Fragment, { children: group.map((token, tokenIndex) => {
6534
+ try {
6535
+ return renderToken(token, groupIndex, tokenIndex);
6536
+ } catch {
6537
+ return null;
6538
+ }
6539
+ }) }, `group-${groupIndex}`))
6540
+ }
6541
+ ) });
6311
6542
  };
6312
6543
  function isYoutubeLikeNode(node) {
6313
6544
  try {
@@ -6460,10 +6691,23 @@ function BrowserSpellCheckPlugin({ enabled }) {
6460
6691
  function WordCountPlugin({ onCountChange }) {
6461
6692
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
6462
6693
  React6.useEffect(() => {
6463
- return editor.registerUpdateListener(() => {
6464
- const text = editor.getRootElement()?.innerText ?? "";
6465
- const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
6466
- onCountChange(words);
6694
+ return editor.registerUpdateListener(({ editorState }) => {
6695
+ editorState.read(() => {
6696
+ const text = lexical.$getRoot().getTextContent();
6697
+ const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
6698
+ onCountChange(words);
6699
+ });
6700
+ });
6701
+ }, [editor, onCountChange]);
6702
+ return null;
6703
+ }
6704
+ function CharCountPlugin({ onCountChange }) {
6705
+ const [editor] = LexicalComposerContext.useLexicalComposerContext();
6706
+ React6.useEffect(() => {
6707
+ return editor.registerUpdateListener(({ editorState }) => {
6708
+ editorState.read(() => {
6709
+ onCountChange(lexical.$getRoot().getTextContent().length);
6710
+ });
6467
6711
  });
6468
6712
  }, [editor, onCountChange]);
6469
6713
  return null;
@@ -6613,6 +6857,9 @@ var ContentEditorComponent = React6.forwardRef(
6613
6857
  const [isLinkEditMode, setIsLinkEditMode] = React6.useState(false);
6614
6858
  const [wordCount, setWordCount] = React6.useState(0);
6615
6859
  const handleWordCount = React6.useCallback((count) => setWordCount(count), []);
6860
+ const [charCount, setCharCount] = React6.useState(0);
6861
+ const handleCharCount = React6.useCallback((count) => setCharCount(count), []);
6862
+ const [refErrors, setRefErrors] = React6.useState([]);
6616
6863
  const contentEditableDomRef = React6.useRef(null);
6617
6864
  const previousOverLimitRef = React6.useRef(false);
6618
6865
  const focusedRef = React6.useRef(false);
@@ -6692,7 +6939,41 @@ var ContentEditorComponent = React6.forwardRef(
6692
6939
  e.stopPropagation();
6693
6940
  }
6694
6941
  };
6942
+ const [touched, setTouched] = React6.useState(false);
6695
6943
  const isOverLimit = props.wordLimit !== void 0 && wordCount > props.wordLimit;
6944
+ const internalErrors = [];
6945
+ if (isOverLimit) {
6946
+ const m = props.errorMessages?.wordLimitExceeded;
6947
+ internalErrors.push(
6948
+ typeof m === "function" ? m(wordCount, props.wordLimit) : m ?? `Word limit exceeded (${wordCount} / ${props.wordLimit} words used)`
6949
+ );
6950
+ }
6951
+ if (props.required && touched && wordCount === 0) {
6952
+ internalErrors.push(
6953
+ props.errorMessages?.required ?? "This field is required"
6954
+ );
6955
+ }
6956
+ if (props.minWords !== void 0 && touched && wordCount < props.minWords) {
6957
+ const m = props.errorMessages?.minWords;
6958
+ internalErrors.push(
6959
+ typeof m === "function" ? m(wordCount, props.minWords) : m ?? `Minimum ${props.minWords} words required (${wordCount} entered)`
6960
+ );
6961
+ }
6962
+ if (props.maxChars !== void 0 && charCount > props.maxChars) {
6963
+ const m = props.errorMessages?.maxCharsExceeded;
6964
+ internalErrors.push(
6965
+ typeof m === "function" ? m(charCount, props.maxChars) : m ?? `Character limit exceeded (${charCount} / ${props.maxChars} characters used)`
6966
+ );
6967
+ }
6968
+ if (props.minChars !== void 0 && touched && charCount < props.minChars && charCount > 0) {
6969
+ const m = props.errorMessages?.minCharsRequired;
6970
+ internalErrors.push(
6971
+ typeof m === "function" ? m(charCount, props.minChars) : m ?? `Minimum ${props.minChars} characters required (${charCount} entered)`
6972
+ );
6973
+ }
6974
+ const allErrors = [...internalErrors, ...props.errors ?? [], ...refErrors];
6975
+ const hasErrors = allErrors.length > 0;
6976
+ const hasRedBorder = hasErrors;
6696
6977
  React6.useEffect(() => {
6697
6978
  if (props.wordLimit === void 0 || !props.onWordLimitExceeded) return;
6698
6979
  const wasOverLimit = previousOverLimitRef.current;
@@ -6715,7 +6996,7 @@ var ContentEditorComponent = React6.forwardRef(
6715
6996
  width: props.width ?? "100%",
6716
6997
  height: props.height ?? "100%",
6717
6998
  margin: props.margin ?? "5px auto",
6718
- border: `1px solid ${isOverLimit ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
6999
+ border: `1px solid ${hasRedBorder ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
6719
7000
  transition: "border-color 0.2s",
6720
7001
  display: "flex",
6721
7002
  flexDirection: "column"
@@ -6745,7 +7026,9 @@ var ContentEditorComponent = React6.forwardRef(
6745
7026
  position: "relative",
6746
7027
  flexGrow: 1,
6747
7028
  padding: "15px 0px",
6748
- overflowY: "scroll"
7029
+ overflowY: "scroll",
7030
+ overflowX: "auto",
7031
+ minWidth: 0
6749
7032
  },
6750
7033
  onClickCapture: handleReadOnlyClickCapture,
6751
7034
  children: [
@@ -6809,13 +7092,33 @@ var ContentEditorComponent = React6.forwardRef(
6809
7092
  ]
6810
7093
  }
6811
7094
  ),
7095
+ hasErrors && /* @__PURE__ */ jsxRuntime.jsx(
7096
+ "div",
7097
+ {
7098
+ style: {
7099
+ borderTop: "1px solid #fbd5d5",
7100
+ background: "#fff8f8",
7101
+ padding: "6px 12px 8px",
7102
+ display: "flex",
7103
+ flexDirection: "column",
7104
+ gap: 4
7105
+ },
7106
+ children: allErrors.map((err, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
7107
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ErrorCircleRegular, { style: { fontSize: 14, color: "#c4272c", flexShrink: 0 } }),
7108
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#c4272c" }, children: err })
7109
+ ] }, i))
7110
+ }
7111
+ ),
6812
7112
  /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyPlugin, { readonly: isReadOnly }),
6813
7113
  /* @__PURE__ */ jsxRuntime.jsx(BrowserSpellCheckPlugin, { enabled: !resolvedSpellCheck }),
6814
7114
  /* @__PURE__ */ jsxRuntime.jsx(
6815
7115
  FocusEventsPlugin,
6816
7116
  {
6817
7117
  onFocus: props.onFocus,
6818
- onBlur: props.onBlur,
7118
+ onBlur: () => {
7119
+ setTouched(true);
7120
+ props.onBlur?.();
7121
+ },
6819
7122
  setFocused,
6820
7123
  containerRef
6821
7124
  }
@@ -6862,20 +7165,16 @@ var ContentEditorComponent = React6.forwardRef(
6862
7165
  }
6863
7166
  ),
6864
7167
  !isReadOnly && props.showFloatingToolbar && /* @__PURE__ */ jsxRuntime.jsx(CharacterStylesPopupPlugin, {}),
6865
- /* @__PURE__ */ jsxRuntime.jsx(
6866
- CustomOnChangePlugin,
6867
- {
6868
- value: props.value,
6869
- onChange: props.onChange
6870
- }
6871
- ),
6872
- props.wordLimit !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7168
+ /* @__PURE__ */ jsxRuntime.jsx(CustomOnChangePlugin, { value: props.value, onChange: props.onChange }),
7169
+ (props.wordLimit !== void 0 || props.required || props.minWords !== void 0) && /* @__PURE__ */ jsxRuntime.jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7170
+ (props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsxRuntime.jsx(CharCountPlugin, { onCountChange: handleCharCount }),
6873
7171
  /* @__PURE__ */ jsxRuntime.jsx(
6874
7172
  RefApiPlugin,
6875
7173
  {
6876
7174
  forwardedRef: ref,
6877
7175
  contentEditableDomRef,
6878
- focusedRef
7176
+ focusedRef,
7177
+ setRefErrors
6879
7178
  }
6880
7179
  )
6881
7180
  ]