@tarviks/lexical-rich-editor 1.3.11 → 1.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
6
6
  import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
7
7
  import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
8
- import { createCommand, DecoratorNode, COMMAND_PRIORITY_HIGH, TextNode, ElementNode, SELECTION_CHANGE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, $getSelection, $isRangeSelection, $findMatchingParent, COMMAND_PRIORITY_LOW, KEY_DOWN_COMMAND, $getNodeByKey, $createRangeSelection, $setSelection, $createParagraphNode, $insertNodes, $isRootOrShadowRoot, COMMAND_PRIORITY_EDITOR, PASTE_COMMAND, DRAGSTART_COMMAND, DRAGOVER_COMMAND, DROP_COMMAND, $getRoot, $createTextNode, $isTextNode, $isElementNode, FORMAT_ELEMENT_COMMAND, CLICK_COMMAND, $isNodeSelection, $applyNodeReplacement, FORMAT_TEXT_COMMAND, $isDecoratorNode, $getNearestNodeFromDOMNode, COMMAND_PRIORITY_CRITICAL, REDO_COMMAND, UNDO_COMMAND, getDOMSelection, KEY_ESCAPE_COMMAND, isHTMLElement, getDOMSelectionFromTarget, $isLineBreakNode, SKIP_SELECTION_FOCUS_TAG, createEditor, KEY_ENTER_COMMAND } from 'lexical';
8
+ import { createCommand, $isTextNode, DecoratorNode, COMMAND_PRIORITY_HIGH, TextNode, ElementNode, SELECTION_CHANGE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, $getSelection, $isRangeSelection, $findMatchingParent, COMMAND_PRIORITY_LOW, KEY_DOWN_COMMAND, $getNodeByKey, $createRangeSelection, $setSelection, $createParagraphNode, $insertNodes, $isRootOrShadowRoot, COMMAND_PRIORITY_EDITOR, PASTE_COMMAND, DRAGSTART_COMMAND, DRAGOVER_COMMAND, DROP_COMMAND, $getRoot, $createTextNode, $isElementNode, FORMAT_ELEMENT_COMMAND, CLICK_COMMAND, $isNodeSelection, $applyNodeReplacement, FORMAT_TEXT_COMMAND, $isDecoratorNode, $getNearestNodeFromDOMNode, COMMAND_PRIORITY_CRITICAL, REDO_COMMAND, UNDO_COMMAND, getDOMSelection, KEY_ESCAPE_COMMAND, isHTMLElement, getDOMSelectionFromTarget, $isLineBreakNode, SKIP_SELECTION_FOCUS_TAG, createEditor, KEY_ENTER_COMMAND } from 'lexical';
9
9
  import { mergeStyleSets, Stack, css, useTheme, Callout, TextField } from '@fluentui/react';
10
10
  import { makeStyles, FluentProvider, webLightTheme, Menu, MenuTrigger, MenuPopover, MenuList, MenuGroup, MenuGroupHeader, MenuItem, MenuDivider, Dropdown, Option, Button, ToolbarDivider, MenuItemRadio, Field, Input, MessageBar, MessageBarBody, Popover, PopoverSurface, PopoverTrigger } from '@fluentui/react-components';
11
11
  import { CodeHighlightNode, CodeNode, $isCodeHighlightNode } from '@lexical/code';
@@ -2209,6 +2209,48 @@ function $createYouTubeNode(videoID, width = DEFAULT_WIDTH, height = DEFAULT_HEI
2209
2209
  function $isYouTubeNode(node) {
2210
2210
  return node instanceof YouTubeNode;
2211
2211
  }
2212
+ var PRESERVED_STYLE_PROPS = [
2213
+ ["fontFamily", "font-family"],
2214
+ ["fontSize", "font-size"],
2215
+ ["color", "color"],
2216
+ ["backgroundColor", "background-color"]
2217
+ ];
2218
+ var $convertSpanElementWithStyle = (domNode) => {
2219
+ const style = domNode.style;
2220
+ const cssParts = [];
2221
+ for (const [domProp, cssProp] of PRESERVED_STYLE_PROPS) {
2222
+ const value = style[domProp];
2223
+ if (value) cssParts.push(`${cssProp}: ${value}`);
2224
+ }
2225
+ const cssText = cssParts.length > 0 ? `${cssParts.join("; ")};` : "";
2226
+ const fontWeight = style.fontWeight;
2227
+ const textDecoration = style.textDecoration.split(" ");
2228
+ const hasBoldFontWeight = fontWeight === "700" || fontWeight === "bold";
2229
+ const hasLinethroughTextDecoration = textDecoration.includes("line-through");
2230
+ const hasItalicFontStyle = style.fontStyle === "italic";
2231
+ const hasUnderlineTextDecoration = textDecoration.includes("underline");
2232
+ const verticalAlign = style.verticalAlign;
2233
+ return {
2234
+ node: null,
2235
+ forChild: (lexicalNode) => {
2236
+ if (!$isTextNode(lexicalNode)) return lexicalNode;
2237
+ if (cssText) lexicalNode.setStyle(cssText);
2238
+ if (hasBoldFontWeight && !lexicalNode.hasFormat("bold")) lexicalNode.toggleFormat("bold");
2239
+ if (hasLinethroughTextDecoration && !lexicalNode.hasFormat("strikethrough")) lexicalNode.toggleFormat("strikethrough");
2240
+ if (hasItalicFontStyle && !lexicalNode.hasFormat("italic")) lexicalNode.toggleFormat("italic");
2241
+ if (hasUnderlineTextDecoration && !lexicalNode.hasFormat("underline")) lexicalNode.toggleFormat("underline");
2242
+ if (verticalAlign === "sub" && !lexicalNode.hasFormat("subscript")) lexicalNode.toggleFormat("subscript");
2243
+ if (verticalAlign === "super" && !lexicalNode.hasFormat("superscript")) lexicalNode.toggleFormat("superscript");
2244
+ return lexicalNode;
2245
+ }
2246
+ };
2247
+ };
2248
+ var preserveTextStyleImportMap = {
2249
+ span: () => ({
2250
+ conversion: $convertSpanElementWithStyle,
2251
+ priority: 0
2252
+ })
2253
+ };
2212
2254
  var normalize = (s) => (s ?? "").replace(/\s+/g, " ").trim();
2213
2255
  function readContextWindow(maxChars = 300, cursorIndex) {
2214
2256
  const full = normalize($getRoot().getTextContent());
@@ -3156,12 +3198,16 @@ function splitHeadingsAtBrSequences(html) {
3156
3198
  });
3157
3199
  return doc.body.innerHTML;
3158
3200
  }
3159
- var CustomOnChangePlugin = ({ value, onChange }) => {
3201
+ var CustomOnChangePlugin = ({ value, onChange, isReadOnly }) => {
3160
3202
  const [editor] = useLexicalComposerContext();
3161
3203
  const initializedRef = useRef(false);
3204
+ const lastImportedValueRef = useRef(null);
3162
3205
  useEffect(() => {
3163
- if (!value || initializedRef.current) return;
3206
+ if (!value) return;
3207
+ if (!isReadOnly && initializedRef.current) return;
3208
+ if (value === lastImportedValueRef.current) return;
3164
3209
  initializedRef.current = true;
3210
+ lastImportedValueRef.current = value;
3165
3211
  editor.update(() => {
3166
3212
  const root = $getRoot();
3167
3213
  root.clear();
@@ -3172,14 +3218,17 @@ var CustomOnChangePlugin = ({ value, onChange }) => {
3172
3218
  const nodes = $generateNodesFromDOM(editor, dom);
3173
3219
  root.append(...nodes);
3174
3220
  });
3175
- }, [editor, value]);
3221
+ }, [editor, value, isReadOnly]);
3176
3222
  return /* @__PURE__ */ jsx(
3177
3223
  OnChangePlugin,
3178
3224
  {
3225
+ ignoreSelectionChange: true,
3179
3226
  onChange: (editorState) => {
3180
3227
  editorState.read(() => {
3181
3228
  const raw = $generateHtmlFromNodes(editor);
3182
- onChange(postProcessOutput(splitHeadingsAtBrSequences(raw)));
3229
+ const html = postProcessOutput(splitHeadingsAtBrSequences(raw));
3230
+ lastImportedValueRef.current = html;
3231
+ onChange(html);
3183
3232
  });
3184
3233
  }
3185
3234
  }
@@ -5149,16 +5198,18 @@ function getScrollClipAncestor(el) {
5149
5198
  }
5150
5199
  return null;
5151
5200
  }
5201
+ var BUTTON_VERTICAL_FOOTPRINT = 34;
5152
5202
  function isRectVisible(rect, cellDom) {
5153
5203
  if (rect.width === 0 && rect.height === 0) return false;
5154
- if (rect.bottom <= 0 || rect.top >= window.innerHeight) return false;
5155
- if (rect.right <= 0 || rect.left >= window.innerWidth) return false;
5156
5204
  const clipAncestor = getScrollClipAncestor(cellDom);
5157
- if (clipAncestor) {
5158
- const containerRect = clipAncestor.getBoundingClientRect();
5159
- if (rect.bottom <= containerRect.top || rect.top >= containerRect.bottom) return false;
5160
- if (rect.right <= containerRect.left || rect.left >= containerRect.right) return false;
5161
- }
5205
+ const containerRect = clipAncestor?.getBoundingClientRect();
5206
+ const visibleTop = containerRect ? Math.max(0, containerRect.top) : 0;
5207
+ const visibleBottom = containerRect ? Math.min(window.innerHeight, containerRect.bottom) : window.innerHeight;
5208
+ const visibleLeft = containerRect ? Math.max(0, containerRect.left) : 0;
5209
+ const visibleRight = containerRect ? Math.min(window.innerWidth, containerRect.right) : window.innerWidth;
5210
+ if (rect.top < visibleTop) return false;
5211
+ if (rect.top + BUTTON_VERTICAL_FOOTPRINT > visibleBottom) return false;
5212
+ if (rect.left >= visibleRight || rect.right <= visibleLeft) return false;
5162
5213
  return true;
5163
5214
  }
5164
5215
  function TableActionMenuPlugin({ disabled = false }) {
@@ -5310,9 +5361,25 @@ function TableActionMenuPlugin({ disabled = false }) {
5310
5361
  const canShow = isInTable && !!anchorRect && isAnchorVisible && !disabled;
5311
5362
  const handleStyle = React9.useMemo(() => {
5312
5363
  if (!anchorRect) return void 0;
5313
- const top = Math.max(8, anchorRect.top + 6);
5314
- const clampedCellRight = Math.min(anchorRect.right, window.innerWidth - 8);
5315
- const left = contentRight !== null ? Math.max(anchorRect.left + 4, Math.min(contentRight + 4, clampedCellRight - 32)) : Math.max(8, anchorRect.left + 8);
5364
+ const BUTTON_SIZE = 28;
5365
+ const PAD = 8;
5366
+ const editorRect = editor.getRootElement()?.getBoundingClientRect();
5367
+ const clipRect = editorRect ? {
5368
+ top: Math.max(editorRect.top, 0),
5369
+ left: Math.max(editorRect.left, 0),
5370
+ right: Math.min(editorRect.right, window.innerWidth),
5371
+ bottom: Math.min(editorRect.bottom, window.innerHeight)
5372
+ } : { top: 0, left: 0, right: window.innerWidth, bottom: window.innerHeight };
5373
+ const isCellVisible = anchorRect.bottom > clipRect.top && anchorRect.top < clipRect.bottom && anchorRect.right > clipRect.left && anchorRect.left < clipRect.right;
5374
+ if (!isCellVisible) return void 0;
5375
+ const minTop = editorRect ? editorRect.top + PAD : PAD;
5376
+ const maxTop = editorRect ? Math.max(minTop, editorRect.bottom - BUTTON_SIZE - PAD) : window.innerHeight - BUTTON_SIZE - PAD;
5377
+ const minLeft = editorRect ? editorRect.left + PAD : PAD;
5378
+ const maxLeft = editorRect ? Math.max(minLeft, editorRect.right - BUTTON_SIZE - PAD) : window.innerWidth - BUTTON_SIZE - PAD;
5379
+ const top = Math.min(Math.max(anchorRect.top + 6, minTop), maxTop);
5380
+ const clampedCellRight = Math.min(anchorRect.right, maxLeft + BUTTON_SIZE);
5381
+ const rawLeft = contentRight !== null ? Math.max(anchorRect.left + 4, Math.min(contentRight + 4, clampedCellRight - 32)) : anchorRect.left + 8;
5382
+ const left = Math.min(Math.max(rawLeft, minLeft), maxLeft);
5316
5383
  return {
5317
5384
  position: "fixed",
5318
5385
  top,
@@ -5322,7 +5389,7 @@ function TableActionMenuPlugin({ disabled = false }) {
5322
5389
  // button renders behind the modal chrome in a full-page/Panel view.
5323
5390
  zIndex: 10000010
5324
5391
  };
5325
- }, [anchorRect, contentRight]);
5392
+ }, [anchorRect, contentRight, editor]);
5326
5393
  const dangerStyle = {
5327
5394
  color: "var(--colorPaletteRedForeground1)"
5328
5395
  };
@@ -8266,7 +8333,10 @@ var ContentEditorComponent = forwardRef(
8266
8333
  AutocompleteNode,
8267
8334
  SpellErrorNode,
8268
8335
  HtmlBlockNode
8269
- ]
8336
+ ],
8337
+ html: {
8338
+ import: preserveTextStyleImportMap
8339
+ }
8270
8340
  };
8271
8341
  props.onBeforeInitialize?.(config);
8272
8342
  return config;
@@ -8594,7 +8664,14 @@ var ContentEditorComponent = forwardRef(
8594
8664
  }
8595
8665
  ),
8596
8666
  !isReadOnly && props.showFloatingToolbar && /* @__PURE__ */ jsx(CharacterStylesPopupPlugin, {}),
8597
- /* @__PURE__ */ jsx(CustomOnChangePlugin, { value: props.value, onChange: props.onChange }),
8667
+ /* @__PURE__ */ jsx(
8668
+ CustomOnChangePlugin,
8669
+ {
8670
+ value: props.value,
8671
+ onChange: props.onChange,
8672
+ isReadOnly
8673
+ }
8674
+ ),
8598
8675
  (props.wordLimit !== void 0 || props.required || props.minWords !== void 0 || props.maxWords !== void 0 || props.minChars !== void 0 || props.maxChars !== void 0 || props.noImages || props.maxImages !== void 0 || props.noLinks || props.maxLinks !== void 0 || props.noTables) && /* @__PURE__ */ jsx(ContentMetricsPlugin, { onMetricsChange: handleMetrics }),
8599
8676
  /* @__PURE__ */ jsx(
8600
8677
  RefApiPlugin,