@lexical/selection 0.52.0 → 0.52.1-nightly.20260930.0
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/LexicalSelection.dev.js +26 -69
- package/dist/LexicalSelection.prod.js +2 -2
- package/package.json +3 -3
- package/src/lexical-node.ts +55 -87
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { getRootOwnerDocument, $isTextNode, $getEditor, $isRootNode, $getSelection, $
|
|
9
|
+
import { getRootOwnerDocument, $isTextNode, $getEditor, $isRootNode, $getSelection, $isElementNode, $isRangeSelection, $isTokenOrSegmented, $getTextPointCaretSliceForNode, $caretRangeFromSelection, $cloneWithPropertiesEphemeral, $getNodeByKey, $getPreviousSelection, $createTextNode, $splitTextPointCaretSlice, getStyleObjectFromCSS as getStyleObjectFromCSS$1, $caretFromPoint, $isExtendableTextPointCaret, $findMatchingParent, INTERNAL_$isBlock, flipDirection, $getSlotHost, $isTextPointCaret, $isTabNode, $extendCaretToRange, $isChildCaret, $isDecoratorNode, $getSlotFrame, $isRootOrShadowRoot, $hasAncestor, $isLeafNode, $setSelection } from 'lexical';
|
|
10
10
|
export { $cloneWithProperties, $selectAll } from 'lexical';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -243,44 +243,21 @@ function $isParentRTL(node) {
|
|
|
243
243
|
* @returns The updated TextNode or clone.
|
|
244
244
|
*/
|
|
245
245
|
function $sliceSelectedTextNodeContent(selection, textNode, mutates = 'self') {
|
|
246
|
-
const
|
|
247
|
-
if (textNode.isSelected(selection) && !$isTokenOrSegmented(textNode)
|
|
248
|
-
const [
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
let startOffset = 0;
|
|
260
|
-
let endOffset = undefined;
|
|
261
|
-
if (isSame) {
|
|
262
|
-
startOffset = anchorOffset > focusOffset ? focusOffset : anchorOffset;
|
|
263
|
-
endOffset = anchorOffset > focusOffset ? anchorOffset : focusOffset;
|
|
264
|
-
} else if (isFirst) {
|
|
265
|
-
const offset = isBackward ? focusOffset : anchorOffset;
|
|
266
|
-
startOffset = offset;
|
|
267
|
-
endOffset = undefined;
|
|
268
|
-
} else if (isLast) {
|
|
269
|
-
const offset = isBackward ? anchorOffset : focusOffset;
|
|
270
|
-
startOffset = 0;
|
|
271
|
-
endOffset = offset;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// NOTE: This mutates __text directly because the primary use case is to
|
|
275
|
-
// modify a $cloneWithProperties node that should never be added
|
|
276
|
-
// to the EditorState so we must not call getWritable via setTextContent
|
|
277
|
-
const text = textNode.__text.slice(startOffset, endOffset);
|
|
278
|
-
if (text !== textNode.__text) {
|
|
279
|
-
if (mutates === 'clone') {
|
|
280
|
-
textNode = $cloneWithPropertiesEphemeral(textNode);
|
|
281
|
-
}
|
|
282
|
-
textNode.__text = text;
|
|
246
|
+
const points = selection.getStartEndPoints();
|
|
247
|
+
if (points !== null && textNode.isSelected(selection) && !$isTokenOrSegmented(textNode)) {
|
|
248
|
+
const [start, end] = selection.isBackward() ? [points[1], points[0]] : points;
|
|
249
|
+
// Text endpoints can trim directly. Element endpoints need caret
|
|
250
|
+
// normalization so equivalent mixed points produce an empty interval.
|
|
251
|
+
const slice = $isRangeSelection(selection) && (start.type === 'element' || end.type === 'element') ? $getTextPointCaretSliceForNode($caretRangeFromSelection(selection).getTextSlices(), textNode) : undefined;
|
|
252
|
+
const [startOffset, endOffset] = slice ? slice.getSliceIndices() : [textNode.__key === start.key ? start.offset : 0, textNode.__key === end.key ? end.offset : undefined];
|
|
253
|
+
const text = textNode.__text.slice(startOffset, endOffset);
|
|
254
|
+
// This may be an ephemeral clone: do not resolve getLatest() or insert
|
|
255
|
+
// it into the editor state when changing its own text.
|
|
256
|
+
if (text !== textNode.__text) {
|
|
257
|
+
if (mutates === 'clone') {
|
|
258
|
+
textNode = $cloneWithPropertiesEphemeral(textNode);
|
|
283
259
|
}
|
|
260
|
+
textNode.__text = text;
|
|
284
261
|
}
|
|
285
262
|
}
|
|
286
263
|
return textNode;
|
|
@@ -477,7 +454,7 @@ function $patchStyleText(selection, patch) {
|
|
|
477
454
|
$patchStyle(emptyNode, patch);
|
|
478
455
|
}
|
|
479
456
|
}
|
|
480
|
-
$
|
|
457
|
+
$forEachSelectedTextNodeInSelection(selection, textNode => {
|
|
481
458
|
$patchStyle(textNode, patch);
|
|
482
459
|
});
|
|
483
460
|
const nodes = selection.getNodes();
|
|
@@ -496,44 +473,24 @@ function $patchStyleText(selection, patch) {
|
|
|
496
473
|
}
|
|
497
474
|
}
|
|
498
475
|
function $forEachSelectedTextNode(fn) {
|
|
499
|
-
|
|
476
|
+
$forEachSelectedTextNodeInSelection($getSelection(), fn);
|
|
477
|
+
}
|
|
478
|
+
function $forEachSelectedTextNodeInSelection(selection, fn) {
|
|
500
479
|
if (!selection) {
|
|
501
480
|
return;
|
|
502
481
|
}
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
for (const slice of $caretRangeFromSelection(selection).getTextSlices()) {
|
|
507
|
-
if (slice) {
|
|
508
|
-
slicedTextNodes.set(slice.caret.origin.getKey(), slice.getSliceIndices());
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
const selectedNodes = selection.getNodes();
|
|
513
|
-
for (const selectedNode of selectedNodes) {
|
|
514
|
-
if (!($isTextNode(selectedNode) && selectedNode.canHaveFormat())) {
|
|
482
|
+
const slices = $isRangeSelection(selection) ? $caretRangeFromSelection(selection).getTextSlices() : [];
|
|
483
|
+
for (const node of selection.getNodes()) {
|
|
484
|
+
if (!$isTextNode(node) || !node.canHaveFormat()) {
|
|
515
485
|
continue;
|
|
516
486
|
}
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
if (endOffset === startOffset) {
|
|
487
|
+
const slice = $getTextPointCaretSliceForNode(slices, node);
|
|
488
|
+
if (slice ? slice.distance === 0 : node.getTextContentSize() === 0) {
|
|
520
489
|
continue;
|
|
521
490
|
}
|
|
522
|
-
|
|
523
|
-
// The entire node is selected or a token/segment, so just format it
|
|
524
|
-
if ($isTokenOrSegmented(selectedNode) || startOffset === 0 && endOffset === selectedNode.getTextContentSize()) {
|
|
525
|
-
fn(selectedNode);
|
|
526
|
-
} else {
|
|
527
|
-
// The node is partially selected, so split it into two or three nodes
|
|
528
|
-
// and style the selected one.
|
|
529
|
-
const splitNodes = selectedNode.splitText(startOffset, endOffset);
|
|
530
|
-
const replacement = splitNodes[startOffset === 0 ? 0 : 1];
|
|
531
|
-
fn(replacement);
|
|
532
|
-
}
|
|
491
|
+
fn(slice && !$isTokenOrSegmented(node) ? $splitTextPointCaretSlice(slice, $isRangeSelection(selection) ? selection : null) : node);
|
|
533
492
|
}
|
|
534
|
-
//
|
|
535
|
-
// so we do this for test compatibility.
|
|
536
|
-
// TODO: we may want to consider simplifying by removing this
|
|
493
|
+
// Preserve the existing single-text-node forward selection convention.
|
|
537
494
|
if ($isRangeSelection(selection) && selection.anchor.type === 'text' && selection.focus.type === 'text' && selection.anchor.key === selection.focus.key) {
|
|
538
495
|
$ensureForwardRangeSelection(selection);
|
|
539
496
|
}
|
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import{getRootOwnerDocument as e,$isTextNode as t,$getEditor as n,$isRootNode as o,$getSelection as
|
|
10
|
-
/*@__INLINE__*/function
|
|
9
|
+
import{getRootOwnerDocument as e,$isTextNode as t,$getEditor as n,$isRootNode as o,$getSelection as l,$isElementNode as r,$isRangeSelection as i,$isTokenOrSegmented as s,$getTextPointCaretSliceForNode as c,$caretRangeFromSelection as f,$cloneWithPropertiesEphemeral as u,$getNodeByKey as g,$getPreviousSelection as a,$createTextNode as d,$splitTextPointCaretSlice as p,getStyleObjectFromCSS as h,$caretFromPoint as y,$isExtendableTextPointCaret as m,$findMatchingParent as S,INTERNAL_$isBlock as x,flipDirection as C,$getSlotHost as N,$isTextPointCaret as v,$isTabNode as T,$extendCaretToRange as w,$isChildCaret as P,$isDecoratorNode as E,$getSlotFrame as K,$isRootOrShadowRoot as k,$hasAncestor as I,$isLeafNode as b,$setSelection as B}from"lexical";export{$cloneWithProperties,$selectAll}from"lexical";function F(e,...t){throw function(e,...t){const n=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",e);for(const e of t)o.append("v",e);return n.search=o.toString(),new Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(e,...t)}
|
|
10
|
+
/*@__INLINE__*/function A(e){return()=>{}}function R(e){let t=e;for(;null!=t;){if(t.nodeType===Node.TEXT_NODE)return t;t=t.firstChild}return null}function _(e){const t=e.parentNode;if(null==t)throw new Error("Should never happen");return[t,Array.from(t.childNodes).indexOf(e)]}function z(n,o,l,r,i){const s=o.getKey(),c=r.getKey(),f=e(n.getRootElement()).createRange();let u=n.getElementByKey(s),g=n.getElementByKey(c),a=l,d=i;if(t(o)&&(u=R(u)),t(r)&&(g=R(g)),void 0===o||void 0===r||null===u||null===g)return null;"BR"===u.nodeName&&([u,a]=_(u)),"BR"===g.nodeName&&([g,d]=_(g));const p=u.firstChild;u===g&&null!=p&&"BR"===p.nodeName&&0===a&&0===d&&(d=1);try{f.setStart(u,a),f.setEnd(g,d)}catch(e){return null}return!f.collapsed||a===d&&s===c||(f.setStart(g,d),f.setEnd(u,a)),f}function O(e,t){const n=e.getRootElement();if(null===n)return[];const o=n.getBoundingClientRect(),l=getComputedStyle(n),r=parseFloat(l.paddingLeft)+parseFloat(l.paddingRight),i=Array.from(t.getClientRects());let s,c=i.length;i.sort((e,t)=>{const n=e.top-t.top;return Math.abs(n)<=3?e.left-t.left:n});for(let e=0;e<c;e++){const t=i[e],n=s&&s.top<=t.top&&s.bottom>=t.bottom&&s.left<=t.left&&s.right>=t.right,l=t.width+r===o.width;n||l?(i.splice(e--,1),c--):s=t}return i}function L(e){let t="";for(const n in e)n&&(t+=`${n}: ${e[n]};`);return t}function $(e){const t=n().getElementByKey(e.getKey());if(null===t)return null;const o=t.ownerDocument.defaultView;return null===o?null:o.getComputedStyle(t)}function M(e){const t=o(e)?e:e.getParent();return t&&$(t)}function D(e){const t=M(e);return null!==t&&"rtl"===t.direction}function U(e,t,n="self"){const o=e.getStartEndPoints();if(null!==o&&t.isSelected(e)&&!s(t)){const[l,r]=e.isBackward()?[o[1],o[0]]:o,s=!i(e)||"element"!==l.type&&"element"!==r.type?void 0:c(f(e).getTextSlices(),t),[g,a]=s?s.getSliceIndices():[t.__key===l.key?l.offset:0,t.__key===r.key?r.offset:void 0],d=t.__text.slice(g,a);d!==t.__text&&("clone"===n&&(t=u(t)),t.__text=d)}return t}function j(e){if("text"===e.type)return e.offset===e.getNode().getTextContentSize();const t=e.getNode();return r(t)||F(177),e.offset===t.getChildrenSize()}function H(e,n,l){let s=n.getNode(),c=l;if(r(s)){const e=s.getDescendantByIndex(n.offset);null!==e&&(s=e)}for(;c>0&&null!==s;){if(r(s)){const e=s.getLastDescendant();null!==e&&(s=e)}let l=s.getPreviousSibling(),f=0;if(null===l){let e=s.getParentOrThrow(),t=e.getPreviousSibling();for(;null===t;){if(e=e.getParent(),null===e){l=null;break}t=e.getPreviousSibling()}null!==e&&(f=e.isInline()?0:2,l=t)}let u=s.getTextContent();""===u&&r(s)&&!s.isInline()&&(u="\n\n");const p=u.length;if(!t(s)||c>=p){const e=s.getParent();s.remove(),null==e||0!==e.getChildrenSize()||o(e)||e.remove(),c-=p+f,s=l}else{const o=s.getKey(),l=e.read("latest",()=>{const e=g(o);return t(e)&&e.isSimpleText()?e.getTextContent():null}),r=p-c,f=u.slice(0,r);if(null!==l&&l!==u){const e=a();let t=s;if(s.isSimpleText())s.setTextContent(l);else{const e=d(l);s.replace(e),t=e}if(i(e)&&e.isCollapsed()){const n=e.anchor.offset;t.select(n,n)}}else if(s.isSimpleText()){const e=n.key===o;let t=n.offset;t<c&&(t=p);const l=e?t-c:0,i=e?t:r;if(e&&0===l){const[e]=s.splitText(l,i);e.remove()}else{const[,e]=s.splitText(l,i);e.remove()}}else{const e=d(f);s.replace(e)}c=0}}}const V=/* @__PURE__ */A();function W(e,n){(i(e)?e.isCollapsed():t(e)||r(e))||F(280);const o=h(i(e)?e.style:t(e)?e.getStyle():e.getTextStyle()),l=L(Object.entries(n).reduce((t,[n,l])=>("function"==typeof l?t[n]=l(o[n],e):null===l?delete t[n]:t[n]=l,t),{...o}));i(e)||t(e)?e.setStyle(l):e.setTextStyle(l)}function X(e,t){const n=new Set;if(i(e)&&e.isCollapsed()){W(e,t);const o=e.anchor.getNode();r(o)&&o.isEmpty()&&(n.add(o.getKey()),W(o,t))}G(e,e=>{W(e,t)});const o=e.getNodes();if(o.length>0)for(const e of o){if(!r(e)||!e.canBeEmpty()||0!==e.getChildrenSize())continue;const o=e.getKey();n.has(o)||(n.add(o),W(e,t))}}function q(e){G(l(),e)}function G(e,n){if(!e)return;const o=i(e)?f(e).getTextSlices():[];for(const l of e.getNodes()){if(!t(l)||!l.canHaveFormat())continue;const r=c(o,l);(r?0!==r.distance:0!==l.getTextContentSize())&&n(r&&!s(l)?p(r,i(e)?e:null):l)}i(e)&&"text"===e.anchor.type&&"text"===e.focus.type&&e.anchor.key===e.focus.key&&J(e)}function J(e){if(e.isBackward()){const{anchor:t,focus:n}=e,{key:o,offset:l,type:r}=t;t.set(n.key,n.offset,n.type),n.set(o,l,r)}}function Q(e,t){const n=e.getFormatType(),o=e.getIndent();n!==t.getFormatType()&&t.setFormat(n),o!==t.getIndent()&&t.setIndent(o)}function Y(e,t,n){let o=y(e,n);if(m(o))return!1;for(;o;o=o.getParentCaret()){const e=o.getParentAtCaret();if(!e||o.getNodeAtCaret())return!1;if(t.is(e))return!0}return!1}function Z(e,t,n=Q){if(!e)return;const o=e.getStartEndPoints();let l=!1,i=null;const s=new Map;if(o){const[t,n]=o,c=S(t.getNode(),x);i=S(n.getNode(),x);const f=e.isBackward()?"previous":"next";l=r(i)&&!i.is(c)&&function(e,t,n){const o=e.getNode();return(!r(o)||!o.isEmpty())&&Y(e,t,n)}(n,i,C(f)),r(c)&&s.set(c.getKey(),c),r(i)&&!l&&s.set(i.getKey(),i)}for(const t of e.getNodes())if(r(t)&&x(t)){if(l&&t.is(i))continue;s.set(t.getKey(),t)}else if(!o){const e=S(t,x);r(e)&&s.set(e.getKey(),e)}for(const e of s.values()){if(null!==N(e))continue;const o=t();n(e,o),e.replace(o,!0)}}function ee(e){return e.getNode().isAttached()}function te(e){let t=e;for(;null!==t&&!k(t);){const e=t.getLatest(),n=t.getParent();0===e.getChildrenSize()&&t.remove(!0),t=n}}function ne(e,t,n=null){const o=e.getStartEndPoints(),l=o?o[0]:null,r=e.getNodes(),i=r.length;if(null!==l){const e=K(l.getNode());if(null!==e&&!k(e))return}if(null!==l&&(0===i||1===i&&"element"===l.type&&0===l.getNode().getChildrenSize())){const e="text"===l.type?l.getNode().getParentOrThrow():l.getNode(),o=e.getChildren();let r=t();return r.setFormat(e.getFormatType()),r.setIndent(e.getIndent()),o.forEach(e=>r.append(e)),n&&(r=n.append(r)),void e.replace(r)}let s=null,c=[];for(let o=0;o<i;o++){const l=r[o];k(l)?(oe(e,c,c.length,t,n),c=[],s=l):null===s||null!==s&&I(l,s)?c.push(l):(oe(e,c,c.length,t,n),c=[l])}oe(e,c,c.length,t,n)}function oe(e,t,n,o,l=null){if(0===t.length)return;const s=t[0],c=new Map,f=[],u=r(s)?s:s.getParentOrThrow();let g=u.isInline()?u.getParentOrThrow():u,d=!1;for(;null!==g;){const e=g.getPreviousSibling();if(null!==e){g=e,d=!0;break}if(g=g.getParentOrThrow(),k(g))break}const p=new Set;for(let e=0;e<n;e++){const n=t[e];r(n)&&0===n.getChildrenSize()&&p.add(n.getKey())}const h=new Set;for(let e=0;e<n;e++){const n=t[e];let l=n.getParent();if(null!==l&&l.isInline()&&(l=l.getParent()),null!==l&&b(n)&&!h.has(n.getKey())){const e=l.getKey();if(void 0===c.get(e)){const t=o();t.setFormat(l.getFormatType()),t.setIndent(l.getIndent()),f.push(t),c.set(e,t);const n=l.getChildren();t.splice(t.getChildrenSize(),0,n);for(const e of n)if(h.add(e.getKey()),r(e))for(const t of e.getChildrenKeys())h.add(t);te(l)}}else if(p.has(n.getKey())){r(n)||F(179);const e=o();e.setFormat(n.getFormatType()),e.setIndent(n.getIndent()),f.push(e),n.remove(!0)}}if(null!==l)for(let e=0;e<f.length;e++){const t=f[e];l.append(t)}let y=null;if(k(g))if(d)if(null!==l)g.insertAfter(l);else for(let e=f.length-1;e>=0;e--){const t=f[e];g.insertAfter(t)}else{const e=g,t=e.getFirstChild();if(r(t)&&(g=t),null===t)if(l)e.append(l);else for(let t=0;t<f.length;t++){const n=f[t];e.append(n),y=n}else if(null!==l)t.insertBefore(l);else for(let e=0;e<f.length;e++){const n=f[e];t.insertBefore(n),y=n}}else if(l)g.insertAfter(l);else for(let e=f.length-1;e>=0;e--){const t=f[e];g.insertAfter(t),y=t}const m=a();i(m)&&ee(m.anchor)&&ee(m.focus)?B(m.clone()):null!==y?y.selectEnd():e.dirty=!0}function le(e){const t=re(e);return null!==t&&"vertical-rl"===t.writingMode}function re(e){const t=e.anchor.getNode();return r(t)?$(t):M(t)}function ie(e,n){let o=le(e)?!n:n;ce(e)&&(o=!o);const l=y(e.focus,o?"previous":"next");if(m(l))return!1;if(v(l)&&!T(l.origin)&&l.origin.isUnmergeable()){const e=l.getNodeAtCaret();if(t(e)&&!T(e))return!0}for(const e of w(l)){if(P(e))return!e.origin.isInline();if(!r(e.origin)){if(E(e.origin))return!0;break}}return!1}function se(e,t,n,o){e.modify(t?"extend":"move",n,o)}function ce(e){const t=re(e);return null!==t&&"rtl"===t.direction}function fe(e,t,n){const o=ce(e);let l;l=le(e)||o?!n:n,se(e,t,l,"character")}function ue(e,t,n){const o=e.getStyle(),l=h(o);return null!==l&&l[t]||n}function ge(e,n,o=""){let l=null;const r=e.getNodes();let s,c;if(i(e)){if(e.isCollapsed()&&""!==e.style){const t=h(e.style);if(null!==t&&n in t)return t[n]}const{anchor:o,focus:l}=e,r=e.isBackward(),i=r?l.getNode():o.getNode(),f=r?o.getNode():l.getNode(),u=r?l.offset:o.offset,g=r?o.offset:l.offset;t(i)&&u===i.getTextContentSize()&&(s=i),0===g&&(c=f)}for(let e=0;e<r.length;e++){const i=r[e];if(t(i)&&!i.is(0===e?s:c)){const e=ue(i,n,o);if(null===l)l=e;else if(l!==e){l="";break}}}return null===l?o:l}const ae=h,de=H;export{V as $addNodeStyle,Q as $copyBlockFormatIndent,J as $ensureForwardRangeSelection,q as $forEachSelectedTextNode,$ as $getComputedStyleForElement,M as $getComputedStyleForParent,ge as $getSelectionStyleValueForProperty,Y as $isAtEdgeOfElement,j as $isAtNodeEnd,ce as $isParentElementRTL,D as $isParentRTL,se as $moveCaretSelection,fe as $moveCharacter,X as $patchStyleText,Z as $setBlocksType,ie as $shouldOverrideDefaultCharacterSelection,U as $sliceSelectedTextNodeContent,H as $trimTextContentFromAnchor,ne as $wrapNodes,z as createDOMRange,O as createRectsFromDOMRange,L as getCSSFromStyleObject,ae as getStyleObjectFromCSS,de as trimTextContentFromAnchor};
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"selection"
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"version": "0.52.0",
|
|
12
|
+
"version": "0.52.1-nightly.20260930.0",
|
|
13
13
|
"main": "./dist/LexicalSelection.js",
|
|
14
14
|
"types": "./dist/typescript-too-old.d.ts",
|
|
15
15
|
"repository": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@lexical/internal": "0.52.0",
|
|
34
|
-
"lexical": "0.52.0"
|
|
33
|
+
"@lexical/internal": "0.52.1-nightly.20260930.0",
|
|
34
|
+
"lexical": "0.52.1-nightly.20260930.0"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist",
|
package/src/lexical-node.ts
CHANGED
|
@@ -11,15 +11,16 @@ import {
|
|
|
11
11
|
$caretRangeFromSelection,
|
|
12
12
|
$cloneWithPropertiesEphemeral,
|
|
13
13
|
$createTextNode,
|
|
14
|
-
$getCharacterOffsets,
|
|
15
14
|
$getNodeByKey,
|
|
16
15
|
$getPreviousSelection,
|
|
17
16
|
$getSelection,
|
|
17
|
+
$getTextPointCaretSliceForNode,
|
|
18
18
|
$isElementNode,
|
|
19
19
|
$isRangeSelection,
|
|
20
20
|
$isRootNode,
|
|
21
21
|
$isTextNode,
|
|
22
22
|
$isTokenOrSegmented,
|
|
23
|
+
$splitTextPointCaretSlice,
|
|
23
24
|
type BaseSelection,
|
|
24
25
|
type ElementNode,
|
|
25
26
|
getStyleObjectFromCSS,
|
|
@@ -46,50 +47,39 @@ export function $sliceSelectedTextNodeContent<T extends TextNode>(
|
|
|
46
47
|
textNode: T,
|
|
47
48
|
mutates: 'clone' | 'self' = 'self',
|
|
48
49
|
): T {
|
|
49
|
-
const
|
|
50
|
+
const points = selection.getStartEndPoints();
|
|
50
51
|
if (
|
|
52
|
+
points !== null &&
|
|
51
53
|
textNode.isSelected(selection) &&
|
|
52
|
-
!$isTokenOrSegmented(textNode)
|
|
53
|
-
anchorAndFocus !== null
|
|
54
|
+
!$isTokenOrSegmented(textNode)
|
|
54
55
|
) {
|
|
55
|
-
const [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
endOffset = offset;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// NOTE: This mutates __text directly because the primary use case is to
|
|
84
|
-
// modify a $cloneWithProperties node that should never be added
|
|
85
|
-
// to the EditorState so we must not call getWritable via setTextContent
|
|
86
|
-
const text = textNode.__text.slice(startOffset, endOffset);
|
|
87
|
-
if (text !== textNode.__text) {
|
|
88
|
-
if (mutates === 'clone') {
|
|
89
|
-
textNode = $cloneWithPropertiesEphemeral(textNode);
|
|
90
|
-
}
|
|
91
|
-
textNode.__text = text;
|
|
56
|
+
const [start, end] = selection.isBackward()
|
|
57
|
+
? [points[1], points[0]]
|
|
58
|
+
: points;
|
|
59
|
+
// Text endpoints can trim directly. Element endpoints need caret
|
|
60
|
+
// normalization so equivalent mixed points produce an empty interval.
|
|
61
|
+
const slice =
|
|
62
|
+
$isRangeSelection(selection) &&
|
|
63
|
+
(start.type === 'element' || end.type === 'element')
|
|
64
|
+
? $getTextPointCaretSliceForNode(
|
|
65
|
+
$caretRangeFromSelection(selection).getTextSlices(),
|
|
66
|
+
textNode,
|
|
67
|
+
)
|
|
68
|
+
: undefined;
|
|
69
|
+
const [startOffset, endOffset] = slice
|
|
70
|
+
? slice.getSliceIndices()
|
|
71
|
+
: [
|
|
72
|
+
textNode.__key === start.key ? start.offset : 0,
|
|
73
|
+
textNode.__key === end.key ? end.offset : undefined,
|
|
74
|
+
];
|
|
75
|
+
const text = textNode.__text.slice(startOffset, endOffset);
|
|
76
|
+
// This may be an ephemeral clone: do not resolve getLatest() or insert
|
|
77
|
+
// it into the editor state when changing its own text.
|
|
78
|
+
if (text !== textNode.__text) {
|
|
79
|
+
if (mutates === 'clone') {
|
|
80
|
+
textNode = $cloneWithPropertiesEphemeral(textNode);
|
|
92
81
|
}
|
|
82
|
+
textNode.__text = text;
|
|
93
83
|
}
|
|
94
84
|
}
|
|
95
85
|
return textNode;
|
|
@@ -332,7 +322,7 @@ export function $patchStyleText(
|
|
|
332
322
|
$patchStyle(emptyNode, patch);
|
|
333
323
|
}
|
|
334
324
|
}
|
|
335
|
-
$
|
|
325
|
+
$forEachSelectedTextNodeInSelection(selection, textNode => {
|
|
336
326
|
$patchStyle(textNode, patch);
|
|
337
327
|
});
|
|
338
328
|
|
|
@@ -359,59 +349,37 @@ export function $patchStyleText(
|
|
|
359
349
|
export function $forEachSelectedTextNode(
|
|
360
350
|
fn: (textNode: TextNode) => void,
|
|
361
351
|
): void {
|
|
362
|
-
|
|
352
|
+
$forEachSelectedTextNodeInSelection($getSelection(), fn);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function $forEachSelectedTextNodeInSelection(
|
|
356
|
+
selection: BaseSelection | null,
|
|
357
|
+
fn: (textNode: TextNode) => void,
|
|
358
|
+
): void {
|
|
363
359
|
if (!selection) {
|
|
364
360
|
return;
|
|
365
361
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const getSliceIndices = (
|
|
372
|
-
node: TextNode,
|
|
373
|
-
): [startIndex: number, endIndex: number] =>
|
|
374
|
-
slicedTextNodes.get(node.getKey()) || [0, node.getTextContentSize()];
|
|
375
|
-
|
|
376
|
-
if ($isRangeSelection(selection)) {
|
|
377
|
-
for (const slice of $caretRangeFromSelection(selection).getTextSlices()) {
|
|
378
|
-
if (slice) {
|
|
379
|
-
slicedTextNodes.set(
|
|
380
|
-
slice.caret.origin.getKey(),
|
|
381
|
-
slice.getSliceIndices(),
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const selectedNodes = selection.getNodes();
|
|
388
|
-
for (const selectedNode of selectedNodes) {
|
|
389
|
-
if (!($isTextNode(selectedNode) && selectedNode.canHaveFormat())) {
|
|
362
|
+
const slices = $isRangeSelection(selection)
|
|
363
|
+
? $caretRangeFromSelection(selection).getTextSlices()
|
|
364
|
+
: [];
|
|
365
|
+
for (const node of selection.getNodes()) {
|
|
366
|
+
if (!$isTextNode(node) || !node.canHaveFormat()) {
|
|
390
367
|
continue;
|
|
391
368
|
}
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
if (endOffset === startOffset) {
|
|
369
|
+
const slice = $getTextPointCaretSliceForNode(slices, node);
|
|
370
|
+
if (slice ? slice.distance === 0 : node.getTextContentSize() === 0) {
|
|
395
371
|
continue;
|
|
396
372
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
// The node is partially selected, so split it into two or three nodes
|
|
406
|
-
// and style the selected one.
|
|
407
|
-
const splitNodes = selectedNode.splitText(startOffset, endOffset);
|
|
408
|
-
const replacement = splitNodes[startOffset === 0 ? 0 : 1];
|
|
409
|
-
fn(replacement);
|
|
410
|
-
}
|
|
373
|
+
fn(
|
|
374
|
+
slice && !$isTokenOrSegmented(node)
|
|
375
|
+
? $splitTextPointCaretSlice(
|
|
376
|
+
slice,
|
|
377
|
+
$isRangeSelection(selection) ? selection : null,
|
|
378
|
+
)!
|
|
379
|
+
: node,
|
|
380
|
+
);
|
|
411
381
|
}
|
|
412
|
-
//
|
|
413
|
-
// so we do this for test compatibility.
|
|
414
|
-
// TODO: we may want to consider simplifying by removing this
|
|
382
|
+
// Preserve the existing single-text-node forward selection convention.
|
|
415
383
|
if (
|
|
416
384
|
$isRangeSelection(selection) &&
|
|
417
385
|
selection.anchor.type === 'text' &&
|