@tarviks/lexical-rich-editor 1.3.5 → 1.3.7

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
@@ -7,7 +7,7 @@ import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
7
7
  import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
8
8
  import { createCommand, DecoratorNode, COMMAND_PRIORITY_HIGH, TextNode, ElementNode, SELECTION_CHANGE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, $getSelection, $isRangeSelection, $findMatchingParent, COMMAND_PRIORITY_LOW, KEY_DOWN_COMMAND, $getNodeByKey, $createRangeSelection, $setSelection, $createParagraphNode, $insertNodes, $isRootOrShadowRoot, COMMAND_PRIORITY_EDITOR, PASTE_COMMAND, DRAGSTART_COMMAND, DRAGOVER_COMMAND, DROP_COMMAND, $getRoot, $createTextNode, $isTextNode, $isElementNode, FORMAT_ELEMENT_COMMAND, CLICK_COMMAND, $isNodeSelection, $applyNodeReplacement, FORMAT_TEXT_COMMAND, $isDecoratorNode, $getNearestNodeFromDOMNode, COMMAND_PRIORITY_CRITICAL, REDO_COMMAND, UNDO_COMMAND, getDOMSelection, KEY_ESCAPE_COMMAND, isHTMLElement, getDOMSelectionFromTarget, $isLineBreakNode, SKIP_SELECTION_FOCUS_TAG, createEditor, KEY_ENTER_COMMAND } from 'lexical';
9
9
  import { mergeStyleSets, Stack, css, useTheme, Callout, TextField } from '@fluentui/react';
10
- import { makeStyles, FluentProvider, webLightTheme, Menu, MenuTrigger, MenuPopover, MenuList, MenuGroup, MenuGroupHeader, MenuItem, MenuDivider, Dropdown, Option, Button, ToolbarDivider, MenuItemRadio, Field, Input, MessageBar, MessageBarBody, Popover, PopoverTrigger, PopoverSurface } from '@fluentui/react-components';
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';
12
12
  import { LinkNode, AutoLinkNode, $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND, $createLinkNode } from '@lexical/link';
13
13
  import { ListNode, ListItemNode, $isListNode, REMOVE_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, $removeList, $insertList, $isListItemNode } from '@lexical/list';
@@ -3143,33 +3143,6 @@ var getSelectedNode2 = (selection) => {
3143
3143
  return $isAtNodeEnd(anchor) ? anchorNode : focusNode;
3144
3144
  }
3145
3145
  };
3146
- var VERTICAL_GAP = 10;
3147
- var HORIZONTAL_OFFSET = 5;
3148
- var VIEWPORT_MARGIN = 8;
3149
- var setFloatingElemPositionForLinkEditor = (targetRect, floatingElem, topBoundary = VIEWPORT_MARGIN, verticalGap = VERTICAL_GAP, horizontalOffset = HORIZONTAL_OFFSET) => {
3150
- if (targetRect === null) {
3151
- floatingElem.style.opacity = "0";
3152
- floatingElem.style.transform = "translate(-10000px, -10000px)";
3153
- return;
3154
- }
3155
- const floatingElemRect = floatingElem.getBoundingClientRect();
3156
- let top = targetRect.bottom + verticalGap;
3157
- let left = targetRect.left - horizontalOffset;
3158
- if (top + floatingElemRect.height > window.innerHeight - VIEWPORT_MARGIN) {
3159
- top = targetRect.top - floatingElemRect.height - verticalGap;
3160
- }
3161
- if (top < topBoundary) {
3162
- top = topBoundary;
3163
- }
3164
- left = Math.max(VIEWPORT_MARGIN, Math.min(left, window.innerWidth - floatingElemRect.width - VIEWPORT_MARGIN));
3165
- const origin = getFixedPositionOrigin(floatingElem);
3166
- top -= origin.top;
3167
- left -= origin.left;
3168
- floatingElem.style.opacity = "1";
3169
- floatingElem.style.transform = "none";
3170
- floatingElem.style.top = `${top}px`;
3171
- floatingElem.style.left = `${left}px`;
3172
- };
3173
3146
  var SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set([
3174
3147
  "http:",
3175
3148
  "https:",
@@ -3195,49 +3168,38 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, isLinkEditMode, setIsLink
3195
3168
  const [editedLinkUrl, setEditedLinkUrl] = useState("https://");
3196
3169
  const [lastSelection, setLastSelection] = useState(null);
3197
3170
  const [linkUrl, setLinkUrl] = useState("");
3198
- const editorRef = useRef(null);
3171
+ const [target, setTarget] = useState(null);
3199
3172
  const inputRef = useRef(null);
3200
3173
  const $updateLinkEditor = useCallback(() => {
3201
3174
  const selection = $getSelection();
3175
+ let hasLink = false;
3202
3176
  if ($isRangeSelection(selection)) {
3203
3177
  const node = getSelectedNode2(selection);
3204
3178
  const linkParent = $findMatchingParent$1(node, $isLinkNode);
3205
- if (linkParent) {
3206
- setLinkUrl(linkParent.getURL());
3207
- } else if ($isLinkNode(node)) {
3208
- setLinkUrl(node.getURL());
3209
- } else {
3210
- setLinkUrl("");
3211
- }
3179
+ hasLink = !!linkParent || $isLinkNode(node);
3180
+ const url = linkParent ? linkParent.getURL() : $isLinkNode(node) ? node.getURL() : "";
3181
+ setLinkUrl(url);
3212
3182
  if (isLinkEditMode) {
3213
- setEditedLinkUrl(linkUrl);
3183
+ setEditedLinkUrl(url);
3214
3184
  }
3215
3185
  }
3216
- const editorElem = editorRef.current;
3217
3186
  const nativeSelection = getDOMSelection(editor._window);
3218
3187
  const activeElement = document.activeElement;
3219
- if (editorElem === null) {
3220
- return;
3221
- }
3222
3188
  const rootElement = editor.getRootElement();
3223
- if (isLink && selection !== null && nativeSelection !== null && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) && editor.isEditable()) {
3189
+ if (hasLink && selection !== null && nativeSelection !== null && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) && editor.isEditable()) {
3224
3190
  const domRect = nativeSelection.focusNode?.parentElement?.getBoundingClientRect();
3225
3191
  if (domRect) {
3226
- const toolbarEl = rootElement.closest(".lexical-rich-editor-root")?.querySelector(".editor-toolbar-root");
3227
- const topBoundary = toolbarEl ? toolbarEl.getBoundingClientRect().bottom + 8 : 8;
3228
- setFloatingElemPositionForLinkEditor(domRect, editorElem, topBoundary);
3192
+ setTarget({ getBoundingClientRect: () => domRect });
3229
3193
  }
3230
3194
  setLastSelection(selection);
3231
- } else if (!activeElement || activeElement.className !== "aoLinkInput") {
3232
- if (rootElement !== null) {
3233
- setFloatingElemPositionForLinkEditor(null, editorElem);
3234
- }
3195
+ } else if (!(activeElement instanceof HTMLInputElement) || activeElement !== inputRef.current) {
3196
+ setTarget(null);
3235
3197
  setLastSelection(null);
3236
3198
  setIsLinkEditMode(false);
3237
3199
  setLinkUrl("");
3238
3200
  }
3239
3201
  return true;
3240
- }, [editor, setIsLinkEditMode, isLinkEditMode, isLink, linkUrl]);
3202
+ }, [editor, setIsLinkEditMode, isLinkEditMode]);
3241
3203
  useEffect(() => {
3242
3204
  const update = () => {
3243
3205
  editor.getEditorState().read(() => {
@@ -3305,6 +3267,7 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, isLinkEditMode, setIsLink
3305
3267
  if (lastSelection !== null) {
3306
3268
  if (linkUrl !== "") {
3307
3269
  editor.update(() => {
3270
+ $setSelection(lastSelection.clone());
3308
3271
  editor.dispatchCommand(
3309
3272
  TOGGLE_LINK_COMMAND,
3310
3273
  sanitizeUrl(editedLinkUrl)
@@ -3327,99 +3290,119 @@ var FloatingLinkEditor = ({ editor, isLink, setIsLink, isLinkEditMode, setIsLink
3327
3290
  setIsLinkEditMode(false);
3328
3291
  }
3329
3292
  };
3330
- return /* @__PURE__ */ jsx("div", { ref: editorRef, className: "aoLinkEditor", children: !isLink ? null : isLinkEditMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
3331
- /* @__PURE__ */ jsx(
3332
- "input",
3333
- {
3334
- ref: inputRef,
3335
- className: "aoLinkInput",
3336
- value: editedLinkUrl,
3337
- onChange: (event) => {
3338
- setEditedLinkUrl(event.target.value);
3339
- },
3340
- onKeyDown: (event) => {
3341
- monitorInputInteraction(event);
3342
- }
3343
- }
3344
- ),
3345
- /* @__PURE__ */ jsxs("div", { className: "aoLinkInputActions", children: [
3346
- /* @__PURE__ */ jsx(
3347
- "div",
3348
- {
3349
- className: "aoLinkCancel",
3350
- role: "button",
3351
- tabIndex: 0,
3352
- title: "Cancel",
3353
- "aria-label": "Cancel",
3354
- onMouseDown: preventDefault,
3355
- onClick: () => {
3356
- setIsLinkEditMode(false);
3357
- },
3358
- children: /* @__PURE__ */ jsx(DismissRegular, { fontSize: 16 })
3359
- }
3360
- ),
3361
- /* @__PURE__ */ jsx(
3362
- "div",
3363
- {
3364
- className: "aoLinkConfirm",
3365
- role: "button",
3366
- tabIndex: 0,
3367
- title: "Confirm",
3368
- "aria-label": "Confirm",
3369
- onMouseDown: preventDefault,
3370
- onClick: handleLinkSubmission,
3371
- children: /* @__PURE__ */ jsx(CheckmarkRegular, { fontSize: 16 })
3372
- }
3373
- )
3374
- ] })
3375
- ] }) : /* @__PURE__ */ jsxs("div", { className: "aoLinkView", children: [
3376
- /* @__PURE__ */ jsx(
3377
- "a",
3378
- {
3379
- href: sanitizeUrl(linkUrl),
3380
- target: "_blank",
3381
- rel: "noopener noreferrer",
3382
- children: linkUrl
3383
- }
3384
- ),
3385
- /* @__PURE__ */ jsx(
3386
- "div",
3387
- {
3388
- className: "aoLinkEdit",
3389
- role: "button",
3390
- tabIndex: 0,
3391
- title: "Edit link",
3392
- "aria-label": "Edit link",
3393
- onMouseDown: preventDefault,
3394
- onClick: (event) => {
3395
- event.preventDefault();
3396
- setEditedLinkUrl(linkUrl);
3397
- setIsLinkEditMode(true);
3398
- },
3399
- children: /* @__PURE__ */ jsx(EditRegular, { fontSize: 16 })
3400
- }
3401
- ),
3402
- /* @__PURE__ */ jsx(
3403
- "div",
3404
- {
3405
- className: "aoLinkTrash",
3406
- role: "button",
3407
- tabIndex: 0,
3408
- title: "Remove link",
3409
- "aria-label": "Remove link",
3410
- onMouseDown: preventDefault,
3411
- onClick: () => {
3412
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
3413
- },
3414
- children: /* @__PURE__ */ jsx(DeleteRegular, { fontSize: 16 })
3415
- }
3416
- )
3417
- ] }) });
3293
+ return /* @__PURE__ */ jsx(
3294
+ Popover,
3295
+ {
3296
+ open: isLink && !!target,
3297
+ onOpenChange: (_, data) => {
3298
+ if (!data.open) setIsLink(false);
3299
+ },
3300
+ positioning: { target: target ?? void 0, position: "below", align: "start" },
3301
+ unstable_disableAutoFocus: true,
3302
+ children: /* @__PURE__ */ jsx(PopoverSurface, { style: { display: "flex", alignItems: "center", gap: 6, padding: 8, maxWidth: 360 }, children: isLinkEditMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
3303
+ /* @__PURE__ */ jsx(
3304
+ "input",
3305
+ {
3306
+ ref: inputRef,
3307
+ value: editedLinkUrl,
3308
+ style: {
3309
+ flex: 1,
3310
+ minWidth: 180,
3311
+ border: "none",
3312
+ outline: "none",
3313
+ background: "#f1f1f1",
3314
+ borderRadius: 15,
3315
+ padding: "8px 12px",
3316
+ fontSize: 14
3317
+ },
3318
+ onChange: (event) => {
3319
+ setEditedLinkUrl(event.target.value);
3320
+ },
3321
+ onKeyDown: (event) => {
3322
+ monitorInputInteraction(event);
3323
+ }
3324
+ }
3325
+ ),
3326
+ /* @__PURE__ */ jsx(
3327
+ Button,
3328
+ {
3329
+ appearance: "subtle",
3330
+ size: "small",
3331
+ icon: /* @__PURE__ */ jsx(DismissRegular, { fontSize: 16 }),
3332
+ title: "Cancel",
3333
+ "aria-label": "Cancel",
3334
+ onMouseDown: preventDefault,
3335
+ onClick: () => {
3336
+ setIsLinkEditMode(false);
3337
+ }
3338
+ }
3339
+ ),
3340
+ /* @__PURE__ */ jsx(
3341
+ Button,
3342
+ {
3343
+ appearance: "primary",
3344
+ size: "small",
3345
+ icon: /* @__PURE__ */ jsx(CheckmarkRegular, { fontSize: 16 }),
3346
+ title: "Confirm",
3347
+ "aria-label": "Confirm",
3348
+ onMouseDown: preventDefault,
3349
+ onClick: handleLinkSubmission
3350
+ }
3351
+ )
3352
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3353
+ /* @__PURE__ */ jsx(
3354
+ "a",
3355
+ {
3356
+ href: sanitizeUrl(linkUrl),
3357
+ target: "_blank",
3358
+ rel: "noopener noreferrer",
3359
+ style: {
3360
+ padding: "0 8px",
3361
+ overflow: "hidden",
3362
+ textOverflow: "ellipsis",
3363
+ whiteSpace: "nowrap",
3364
+ maxWidth: 260
3365
+ },
3366
+ children: linkUrl
3367
+ }
3368
+ ),
3369
+ /* @__PURE__ */ jsx(
3370
+ Button,
3371
+ {
3372
+ appearance: "subtle",
3373
+ size: "small",
3374
+ icon: /* @__PURE__ */ jsx(EditRegular, { fontSize: 16 }),
3375
+ title: "Edit link",
3376
+ "aria-label": "Edit link",
3377
+ onMouseDown: preventDefault,
3378
+ onClick: (event) => {
3379
+ event.preventDefault();
3380
+ setEditedLinkUrl(linkUrl);
3381
+ setIsLinkEditMode(true);
3382
+ }
3383
+ }
3384
+ ),
3385
+ /* @__PURE__ */ jsx(
3386
+ Button,
3387
+ {
3388
+ appearance: "subtle",
3389
+ size: "small",
3390
+ icon: /* @__PURE__ */ jsx(DeleteRegular, { fontSize: 16 }),
3391
+ title: "Remove link",
3392
+ "aria-label": "Remove link",
3393
+ onMouseDown: preventDefault,
3394
+ onClick: () => {
3395
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
3396
+ }
3397
+ }
3398
+ )
3399
+ ] }) })
3400
+ }
3401
+ );
3418
3402
  };
3419
- var useFloatingLinkEditorToolbar = (editor, anchorElem, isLinkEditMode, setIsLinkEditMode) => {
3403
+ var useFloatingLinkEditorToolbar = (editor, isLinkEditMode, setIsLinkEditMode) => {
3420
3404
  const [activeEditor, setActiveEditor] = useState(editor);
3421
3405
  const [isLink, setIsLink] = useState(false);
3422
- const portalContainer = useFloatingPortalContainer(editor);
3423
3406
  useEffect(() => {
3424
3407
  function $updateToolbar() {
3425
3408
  const selection = $getSelection();
@@ -3467,29 +3450,21 @@ var useFloatingLinkEditorToolbar = (editor, anchorElem, isLinkEditMode, setIsLin
3467
3450
  )
3468
3451
  );
3469
3452
  }, [editor]);
3470
- if (!anchorElem || !(anchorElem instanceof HTMLElement) || !portalContainer) {
3471
- return null;
3472
- }
3473
- return createPortal(
3474
- /* @__PURE__ */ jsx(
3475
- FloatingLinkEditor,
3476
- {
3477
- isLink,
3478
- editor: activeEditor,
3479
- setIsLink,
3480
- isLinkEditMode,
3481
- setIsLinkEditMode
3482
- }
3483
- ),
3484
- portalContainer
3453
+ return /* @__PURE__ */ jsx(
3454
+ FloatingLinkEditor,
3455
+ {
3456
+ isLink,
3457
+ editor: activeEditor,
3458
+ setIsLink,
3459
+ isLinkEditMode,
3460
+ setIsLinkEditMode
3461
+ }
3485
3462
  );
3486
3463
  };
3487
- var FloatingLinkEditorPlugin = ({ anchorElem, isLinkEditMode, setIsLinkEditMode }) => {
3464
+ var FloatingLinkEditorPlugin = ({ isLinkEditMode, setIsLinkEditMode }) => {
3488
3465
  const [editor] = useLexicalComposerContext();
3489
- const validAnchorElem = anchorElem && anchorElem instanceof HTMLElement ? anchorElem : document.body;
3490
3466
  return useFloatingLinkEditorToolbar(
3491
3467
  editor,
3492
- validAnchorElem,
3493
3468
  isLinkEditMode,
3494
3469
  setIsLinkEditMode
3495
3470
  );
@@ -6561,6 +6536,7 @@ var InsertLinkPlugin = ({
6561
6536
  opacity: disabled ? 0.55 : 1,
6562
6537
  cursor: disabled ? "not-allowed" : "pointer"
6563
6538
  },
6539
+ onMouseDown: (event) => event.preventDefault(),
6564
6540
  onClick: insertLink
6565
6541
  },
6566
6542
  "insert-link"