@tarviks/lexical-rich-editor 1.0.3 → 1.0.5

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",
@@ -5108,7 +5336,9 @@ var ColorPickerPlugin = ({ disabled }) => {
5108
5336
  }, [editor]);
5109
5337
  const applyStyle = (args) => {
5110
5338
  if (disabled) return;
5111
- editor.focus();
5339
+ const root = editor.getRootElement();
5340
+ const editorIsActive = !!lastRangeSelectionRef.current && !!root && (document.activeElement === root || root.contains(document.activeElement));
5341
+ if (editorIsActive) editor.focus();
5112
5342
  editor.update(() => {
5113
5343
  const saved = lastRangeSelectionRef.current;
5114
5344
  if (saved) {
@@ -5247,6 +5477,7 @@ var FontFamilyPlugin = ({ disabled = false }) => {
5247
5477
  "font-family"
5248
5478
  );
5249
5479
  };
5480
+ var DEFAULT_FONT_SIZE = 15;
5250
5481
  var FONT_SIZE_OPTIONS = [
5251
5482
  "8",
5252
5483
  "9",
@@ -5547,21 +5778,27 @@ var TableItemPlugin = ({ disabled }) => {
5547
5778
  reactComponents.Input,
5548
5779
  {
5549
5780
  autoFocus: !disabled,
5781
+ type: "number",
5782
+ min: 1,
5550
5783
  value: rows,
5551
5784
  placeholder: "Rows",
5552
5785
  appearance: "underline",
5553
5786
  disabled,
5554
- onChange: (_, v) => setRows(v.value)
5787
+ input: { style: { textAlign: "left" } },
5788
+ onChange: (_, v) => setRows(v.value.replace(/\D/g, ""))
5555
5789
  }
5556
5790
  ) }),
5557
5791
  /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: "Columns", orientation: "horizontal", size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(
5558
5792
  reactComponents.Input,
5559
5793
  {
5794
+ type: "number",
5795
+ min: 1,
5560
5796
  value: columns,
5561
5797
  placeholder: "Columns",
5562
5798
  appearance: "underline",
5563
5799
  disabled,
5564
- onChange: (_, v) => setColumns(v.value)
5800
+ input: { style: { textAlign: "left" } },
5801
+ onChange: (_, v) => setColumns(v.value.replace(/\D/g, ""))
5565
5802
  }
5566
5803
  ) }),
5567
5804
  /* @__PURE__ */ jsxRuntime.jsxs(react.Stack, { horizontal: true, horizontalAlign: "end", tokens: { childrenGap: 6 }, children: [
@@ -5718,8 +5955,7 @@ var ToolBarPlugins = (props) => {
5718
5955
  const [isLowercase, setIsLowercase] = React6.useState(false);
5719
5956
  const [isCapitalize, setIsCapitalize] = React6.useState(false);
5720
5957
  const [alignment, setAlignment] = React6.useState("left");
5721
- const [isInTable, setIsInTable] = React6.useState(false);
5722
- const [tableNodeKey, setTableNodeKey] = React6.useState(null);
5958
+ const lastSelectionRef = React6__namespace.default.useRef(null);
5723
5959
  const presetGroups = getToolbarGroupsByLevel(props.level);
5724
5960
  const pluginGroups = React6.useMemo(() => sanitizePluginGroups(presetGroups), [presetGroups]);
5725
5961
  const updateToolbarPlugins = () => {
@@ -5736,8 +5972,6 @@ var ToolBarPlugins = (props) => {
5736
5972
  setIsLowercase(false);
5737
5973
  setIsCapitalize(false);
5738
5974
  setSelectNodeType("paragraph");
5739
- setIsInTable(false);
5740
- setTableNodeKey(null);
5741
5975
  return;
5742
5976
  }
5743
5977
  setIsBold(selection.hasFormat("bold"));
@@ -5753,21 +5987,8 @@ var ToolBarPlugins = (props) => {
5753
5987
  const anchorNode = selection.anchor.getNode();
5754
5988
  if (anchorNode.getKey() === "root") {
5755
5989
  setSelectNodeType("paragraph");
5756
- setIsInTable(false);
5757
- setTableNodeKey(null);
5758
5990
  return;
5759
5991
  }
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
5992
  const element = anchorNode.getTopLevelElementOrThrow();
5772
5993
  setAlignment(
5773
5994
  typeof element.getFormatType === "function" ? element.getFormatType() || "left" : "left"
@@ -5788,12 +6009,21 @@ var ToolBarPlugins = (props) => {
5788
6009
  ["paragraph", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "quote", "code"].includes(type) ? type : "paragraph"
5789
6010
  );
5790
6011
  };
6012
+ const applyToBlock = React6__namespace.default.useCallback(
6013
+ (fn) => {
6014
+ editor.update(() => {
6015
+ const saved = lastSelectionRef.current;
6016
+ if (saved) lexical.$setSelection(saved.clone());
6017
+ const sel = lexical.$getSelection();
6018
+ if (lexical.$isRangeSelection(sel)) fn(sel);
6019
+ });
6020
+ },
6021
+ [editor]
6022
+ );
5791
6023
  const formatQuote = () => {
5792
- editor.update(() => {
5793
- const selection$1 = lexical.$getSelection();
5794
- if (!lexical.$isRangeSelection(selection$1)) return;
6024
+ applyToBlock((selection$1) => {
5795
6025
  if (selectNodeType === "quote") {
5796
- formatParagraph(editor);
6026
+ selection.$setBlocksType(selection$1, () => lexical.$createParagraphNode());
5797
6027
  } else {
5798
6028
  selection.$setBlocksType(selection$1, () => richText.$createQuoteNode());
5799
6029
  }
@@ -5812,6 +6042,8 @@ var ToolBarPlugins = (props) => {
5812
6042
  editor.registerCommand(
5813
6043
  lexical.SELECTION_CHANGE_COMMAND,
5814
6044
  () => {
6045
+ const sel = lexical.$getSelection();
6046
+ if (lexical.$isRangeSelection(sel)) lastSelectionRef.current = sel.clone();
5815
6047
  updateToolbarPlugins();
5816
6048
  return false;
5817
6049
  },
@@ -5843,16 +6075,52 @@ var ToolBarPlugins = (props) => {
5843
6075
  editor.dispatchCommand(lexical.FORMAT_TEXT_COMMAND, "highlight");
5844
6076
  break;
5845
6077
  case "leftAlign" /* LeftAlign */:
5846
- editor.dispatchCommand(lexical.FORMAT_ELEMENT_COMMAND, "left");
6078
+ applyToBlock((sel) => {
6079
+ const seen = /* @__PURE__ */ new Set();
6080
+ sel.getNodes().forEach((n) => {
6081
+ const t = n.getTopLevelElementOrThrow();
6082
+ if (!seen.has(t.getKey())) {
6083
+ seen.add(t.getKey());
6084
+ t.setFormat("left");
6085
+ }
6086
+ });
6087
+ });
5847
6088
  break;
5848
6089
  case "rightAlign" /* RightAlign */:
5849
- editor.dispatchCommand(lexical.FORMAT_ELEMENT_COMMAND, "right");
6090
+ applyToBlock((sel) => {
6091
+ const seen = /* @__PURE__ */ new Set();
6092
+ sel.getNodes().forEach((n) => {
6093
+ const t = n.getTopLevelElementOrThrow();
6094
+ if (!seen.has(t.getKey())) {
6095
+ seen.add(t.getKey());
6096
+ t.setFormat("right");
6097
+ }
6098
+ });
6099
+ });
5850
6100
  break;
5851
6101
  case "centerAlign" /* CenterAlign */:
5852
- editor.dispatchCommand(lexical.FORMAT_ELEMENT_COMMAND, "center");
6102
+ applyToBlock((sel) => {
6103
+ const seen = /* @__PURE__ */ new Set();
6104
+ sel.getNodes().forEach((n) => {
6105
+ const t = n.getTopLevelElementOrThrow();
6106
+ if (!seen.has(t.getKey())) {
6107
+ seen.add(t.getKey());
6108
+ t.setFormat("center");
6109
+ }
6110
+ });
6111
+ });
5853
6112
  break;
5854
6113
  case "justifyAlign" /* JustifyAlign */:
5855
- editor.dispatchCommand(lexical.FORMAT_ELEMENT_COMMAND, "justify");
6114
+ applyToBlock((sel) => {
6115
+ const seen = /* @__PURE__ */ new Set();
6116
+ sel.getNodes().forEach((n) => {
6117
+ const t = n.getTopLevelElementOrThrow();
6118
+ if (!seen.has(t.getKey())) {
6119
+ seen.add(t.getKey());
6120
+ t.setFormat("justify");
6121
+ }
6122
+ });
6123
+ });
5856
6124
  break;
5857
6125
  case "undo" /* Undo */:
5858
6126
  editor.dispatchCommand(lexical.UNDO_COMMAND, void 0);
@@ -5872,14 +6140,8 @@ var ToolBarPlugins = (props) => {
5872
6140
  }
5873
6141
  };
5874
6142
  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();
6143
+ applyToBlock((selection$1) => {
6144
+ selection.$setBlocksType(selection$1, () => richText.$createHeadingNode(heading));
5883
6145
  });
5884
6146
  };
5885
6147
  const renderToken = (token, groupIndex, tokenIndex) => {
@@ -6018,7 +6280,7 @@ var ToolBarPlugins = (props) => {
6018
6280
  const val = data.optionValue;
6019
6281
  if (!val) return;
6020
6282
  if (val === "paragraph") {
6021
- formatParagraph(editor);
6283
+ applyToBlock((sel) => selection.$setBlocksType(sel, () => lexical.$createParagraphNode()));
6022
6284
  setSelectNodeType("paragraph");
6023
6285
  } else {
6024
6286
  updateHeading(val);
@@ -6102,16 +6364,10 @@ var ToolBarPlugins = (props) => {
6102
6364
  onHandleSelectOption("highlight" /* Highlight */);
6103
6365
  break;
6104
6366
  case "ul-list":
6105
- editor.dispatchCommand(
6106
- selectNodeType === "ul" ? list.REMOVE_LIST_COMMAND : list.INSERT_UNORDERED_LIST_COMMAND,
6107
- void 0
6108
- );
6367
+ editor.dispatchCommand(selectNodeType === "ul" ? list.REMOVE_LIST_COMMAND : list.INSERT_UNORDERED_LIST_COMMAND, void 0);
6109
6368
  break;
6110
6369
  case "ol-list":
6111
- editor.dispatchCommand(
6112
- selectNodeType === "ol" ? list.REMOVE_LIST_COMMAND : list.INSERT_ORDERED_LIST_COMMAND,
6113
- void 0
6114
- );
6370
+ editor.dispatchCommand(selectNodeType === "ol" ? list.REMOVE_LIST_COMMAND : list.INSERT_ORDERED_LIST_COMMAND, void 0);
6115
6371
  break;
6116
6372
  case "page-break":
6117
6373
  editor.dispatchCommand(INSERT_PAGE_BREAK, void 0);
@@ -6147,12 +6403,7 @@ var ToolBarPlugins = (props) => {
6147
6403
  "Superscript"
6148
6404
  ] }),
6149
6405
  /* @__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
- ),
6406
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.HighlightAccentFilled, { style: { ...optionIconStyle, color: isEditable ? brand : fgDisabled } }),
6156
6407
  "Highlight"
6157
6408
  ] }),
6158
6409
  /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Option, { value: "ul-list", text: "Bullet list", children: [
@@ -6188,30 +6439,10 @@ var ToolBarPlugins = (props) => {
6188
6439
  // );
6189
6440
  case "Align": {
6190
6441
  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
- }
6442
+ { value: "left", label: "Left Align", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignLeftFilled, { style: optionIconStyle }), action: "leftAlign" /* LeftAlign */ },
6443
+ { value: "center", label: "Center Align", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignCenterFilled, { style: optionIconStyle }), action: "centerAlign" /* CenterAlign */ },
6444
+ { value: "right", label: "Right Align", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignRightFilled, { style: optionIconStyle }), action: "rightAlign" /* RightAlign */ },
6445
+ { value: "justify", label: "Justify Align", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.TextAlignJustifyFilled, { style: optionIconStyle }), action: "justifyAlign" /* JustifyAlign */ }
6215
6446
  ];
6216
6447
  const alignLabel = ALIGN_OPTIONS.find((o) => o.value === alignment)?.label ?? "Left Align";
6217
6448
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -6245,69 +6476,29 @@ var ToolBarPlugins = (props) => {
6245
6476
  if (!pluginGroups || pluginGroups.length === 0) {
6246
6477
  return null;
6247
6478
  }
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
- ] });
6479
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Stack, { children: /* @__PURE__ */ jsxRuntime.jsx(
6480
+ "div",
6481
+ {
6482
+ role: "toolbar",
6483
+ "aria-label": "Editor toolbar",
6484
+ style: {
6485
+ display: "flex",
6486
+ flexWrap: "wrap",
6487
+ alignItems: "center",
6488
+ borderBottom: "1px solid #ccced1",
6489
+ background: "#ffffff",
6490
+ padding: "0px",
6491
+ minHeight: 36
6492
+ },
6493
+ children: pluginGroups.map((group, groupIndex) => /* @__PURE__ */ jsxRuntime.jsx(React6__namespace.default.Fragment, { children: group.map((token, tokenIndex) => {
6494
+ try {
6495
+ return renderToken(token, groupIndex, tokenIndex);
6496
+ } catch {
6497
+ return null;
6498
+ }
6499
+ }) }, `group-${groupIndex}`))
6500
+ }
6501
+ ) });
6311
6502
  };
6312
6503
  function isYoutubeLikeNode(node) {
6313
6504
  try {
@@ -6460,10 +6651,23 @@ function BrowserSpellCheckPlugin({ enabled }) {
6460
6651
  function WordCountPlugin({ onCountChange }) {
6461
6652
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
6462
6653
  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);
6654
+ return editor.registerUpdateListener(({ editorState }) => {
6655
+ editorState.read(() => {
6656
+ const text = lexical.$getRoot().getTextContent();
6657
+ const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
6658
+ onCountChange(words);
6659
+ });
6660
+ });
6661
+ }, [editor, onCountChange]);
6662
+ return null;
6663
+ }
6664
+ function CharCountPlugin({ onCountChange }) {
6665
+ const [editor] = LexicalComposerContext.useLexicalComposerContext();
6666
+ React6.useEffect(() => {
6667
+ return editor.registerUpdateListener(({ editorState }) => {
6668
+ editorState.read(() => {
6669
+ onCountChange(lexical.$getRoot().getTextContent().length);
6670
+ });
6467
6671
  });
6468
6672
  }, [editor, onCountChange]);
6469
6673
  return null;
@@ -6613,6 +6817,9 @@ var ContentEditorComponent = React6.forwardRef(
6613
6817
  const [isLinkEditMode, setIsLinkEditMode] = React6.useState(false);
6614
6818
  const [wordCount, setWordCount] = React6.useState(0);
6615
6819
  const handleWordCount = React6.useCallback((count) => setWordCount(count), []);
6820
+ const [charCount, setCharCount] = React6.useState(0);
6821
+ const handleCharCount = React6.useCallback((count) => setCharCount(count), []);
6822
+ const [refErrors, setRefErrors] = React6.useState([]);
6616
6823
  const contentEditableDomRef = React6.useRef(null);
6617
6824
  const previousOverLimitRef = React6.useRef(false);
6618
6825
  const focusedRef = React6.useRef(false);
@@ -6692,7 +6899,41 @@ var ContentEditorComponent = React6.forwardRef(
6692
6899
  e.stopPropagation();
6693
6900
  }
6694
6901
  };
6902
+ const [touched, setTouched] = React6.useState(false);
6695
6903
  const isOverLimit = props.wordLimit !== void 0 && wordCount > props.wordLimit;
6904
+ const internalErrors = [];
6905
+ if (isOverLimit) {
6906
+ const m = props.errorMessages?.wordLimitExceeded;
6907
+ internalErrors.push(
6908
+ typeof m === "function" ? m(wordCount, props.wordLimit) : m ?? `Word limit exceeded (${wordCount} / ${props.wordLimit} words used)`
6909
+ );
6910
+ }
6911
+ if (props.required && touched && wordCount === 0) {
6912
+ internalErrors.push(
6913
+ props.errorMessages?.required ?? "This field is required"
6914
+ );
6915
+ }
6916
+ if (props.minWords !== void 0 && touched && wordCount < props.minWords) {
6917
+ const m = props.errorMessages?.minWords;
6918
+ internalErrors.push(
6919
+ typeof m === "function" ? m(wordCount, props.minWords) : m ?? `Minimum ${props.minWords} words required (${wordCount} entered)`
6920
+ );
6921
+ }
6922
+ if (props.maxChars !== void 0 && charCount > props.maxChars) {
6923
+ const m = props.errorMessages?.maxCharsExceeded;
6924
+ internalErrors.push(
6925
+ typeof m === "function" ? m(charCount, props.maxChars) : m ?? `Character limit exceeded (${charCount} / ${props.maxChars} characters used)`
6926
+ );
6927
+ }
6928
+ if (props.minChars !== void 0 && touched && charCount < props.minChars && charCount > 0) {
6929
+ const m = props.errorMessages?.minCharsRequired;
6930
+ internalErrors.push(
6931
+ typeof m === "function" ? m(charCount, props.minChars) : m ?? `Minimum ${props.minChars} characters required (${charCount} entered)`
6932
+ );
6933
+ }
6934
+ const allErrors = [...internalErrors, ...props.errors ?? [], ...refErrors];
6935
+ const hasErrors = allErrors.length > 0;
6936
+ const hasRedBorder = hasErrors;
6696
6937
  React6.useEffect(() => {
6697
6938
  if (props.wordLimit === void 0 || !props.onWordLimitExceeded) return;
6698
6939
  const wasOverLimit = previousOverLimitRef.current;
@@ -6715,7 +6956,7 @@ var ContentEditorComponent = React6.forwardRef(
6715
6956
  width: props.width ?? "100%",
6716
6957
  height: props.height ?? "100%",
6717
6958
  margin: props.margin ?? "5px auto",
6718
- border: `1px solid ${isOverLimit ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
6959
+ border: `1px solid ${hasRedBorder ? "#c4272c" : "var(--colorNeutralStroke1, #ccced1)"}`,
6719
6960
  transition: "border-color 0.2s",
6720
6961
  display: "flex",
6721
6962
  flexDirection: "column"
@@ -6745,7 +6986,9 @@ var ContentEditorComponent = React6.forwardRef(
6745
6986
  position: "relative",
6746
6987
  flexGrow: 1,
6747
6988
  padding: "15px 0px",
6748
- overflowY: "scroll"
6989
+ overflowY: "scroll",
6990
+ overflowX: "auto",
6991
+ minWidth: 0
6749
6992
  },
6750
6993
  onClickCapture: handleReadOnlyClickCapture,
6751
6994
  children: [
@@ -6809,13 +7052,33 @@ var ContentEditorComponent = React6.forwardRef(
6809
7052
  ]
6810
7053
  }
6811
7054
  ),
7055
+ hasErrors && /* @__PURE__ */ jsxRuntime.jsx(
7056
+ "div",
7057
+ {
7058
+ style: {
7059
+ borderTop: "1px solid #fbd5d5",
7060
+ background: "#fff8f8",
7061
+ padding: "6px 12px 8px",
7062
+ display: "flex",
7063
+ flexDirection: "column",
7064
+ gap: 4
7065
+ },
7066
+ children: allErrors.map((err, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
7067
+ /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ErrorCircleRegular, { style: { fontSize: 14, color: "#c4272c", flexShrink: 0 } }),
7068
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#c4272c" }, children: err })
7069
+ ] }, i))
7070
+ }
7071
+ ),
6812
7072
  /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyPlugin, { readonly: isReadOnly }),
6813
7073
  /* @__PURE__ */ jsxRuntime.jsx(BrowserSpellCheckPlugin, { enabled: !resolvedSpellCheck }),
6814
7074
  /* @__PURE__ */ jsxRuntime.jsx(
6815
7075
  FocusEventsPlugin,
6816
7076
  {
6817
7077
  onFocus: props.onFocus,
6818
- onBlur: props.onBlur,
7078
+ onBlur: () => {
7079
+ setTouched(true);
7080
+ props.onBlur?.();
7081
+ },
6819
7082
  setFocused,
6820
7083
  containerRef
6821
7084
  }
@@ -6862,20 +7125,16 @@ var ContentEditorComponent = React6.forwardRef(
6862
7125
  }
6863
7126
  ),
6864
7127
  !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 }),
7128
+ /* @__PURE__ */ jsxRuntime.jsx(CustomOnChangePlugin, { value: props.value, onChange: props.onChange }),
7129
+ (props.wordLimit !== void 0 || props.required || props.minWords !== void 0) && /* @__PURE__ */ jsxRuntime.jsx(WordCountPlugin, { onCountChange: handleWordCount }),
7130
+ (props.maxChars !== void 0 || props.minChars !== void 0) && /* @__PURE__ */ jsxRuntime.jsx(CharCountPlugin, { onCountChange: handleCharCount }),
6873
7131
  /* @__PURE__ */ jsxRuntime.jsx(
6874
7132
  RefApiPlugin,
6875
7133
  {
6876
7134
  forwardedRef: ref,
6877
7135
  contentEditableDomRef,
6878
- focusedRef
7136
+ focusedRef,
7137
+ setRefErrors
6879
7138
  }
6880
7139
  )
6881
7140
  ]