Lexical: Made a range of selection improvements
Updated up/down handling to create where a selection candidate does not exist, to apply to a wider scenario via the selectPrevious/Next methods. Updated DOM selection change handling to identify single selections within decorated nodes to select them in full, instead of losing selection due to partial selection of their contents. Updated table selection handling so that our colgroups are ignored for internal selection focus handling.
This commit is contained in:
parent
1243108e0f
commit
2a32475541
|
@ -48,6 +48,7 @@ import {
|
|||
internalMarkNodeAsDirty,
|
||||
removeFromParent,
|
||||
} from './LexicalUtils';
|
||||
import {$insertAndSelectNewEmptyAdjacentNode} from "../../utils/nodes";
|
||||
|
||||
export type NodeMap = Map<NodeKey, LexicalNode>;
|
||||
|
||||
|
@ -1130,7 +1131,7 @@ export class LexicalNode {
|
|||
const prevSibling = this.getPreviousSibling();
|
||||
const parent = this.getParentOrThrow();
|
||||
if (prevSibling === null) {
|
||||
return parent.select(0, 0);
|
||||
return $insertAndSelectNewEmptyAdjacentNode(this, false);
|
||||
}
|
||||
if ($isElementNode(prevSibling)) {
|
||||
return prevSibling.select();
|
||||
|
@ -1152,7 +1153,7 @@ export class LexicalNode {
|
|||
const nextSibling = this.getNextSibling();
|
||||
const parent = this.getParentOrThrow();
|
||||
if (nextSibling === null) {
|
||||
return parent.select();
|
||||
return $insertAndSelectNewEmptyAdjacentNode(this, true);
|
||||
}
|
||||
if ($isElementNode(nextSibling)) {
|
||||
return nextSibling.select(0, 0);
|
||||
|
|
|
@ -17,7 +17,7 @@ import invariant from 'lexical/shared/invariant';
|
|||
import {
|
||||
$createLineBreakNode,
|
||||
$createParagraphNode,
|
||||
$createTextNode,
|
||||
$createTextNode, $getNearestNodeFromDOMNode,
|
||||
$isDecoratorNode,
|
||||
$isElementNode,
|
||||
$isLineBreakNode,
|
||||
|
@ -63,6 +63,7 @@ import {
|
|||
toggleTextFormatType,
|
||||
} from './LexicalUtils';
|
||||
import {$createTabNode, $isTabNode} from './nodes/LexicalTabNode';
|
||||
import {$selectSingleNode} from "../../utils/selection";
|
||||
|
||||
export type TextPointType = {
|
||||
_selection: BaseSelection;
|
||||
|
@ -2568,6 +2569,17 @@ export function updateDOMSelection(
|
|||
}
|
||||
|
||||
if (!$isRangeSelection(nextSelection)) {
|
||||
|
||||
// If the DOM selection enters a decorator node update the selection to a single node selection
|
||||
if (activeElement !== null && domSelection.isCollapsed && focusDOMNode instanceof Node) {
|
||||
const node = $getNearestNodeFromDOMNode(focusDOMNode);
|
||||
if ($isDecoratorNode(node)) {
|
||||
domSelection.removeAllRanges();
|
||||
$selectSingleNode(node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't remove selection if the prevSelection is null because
|
||||
// of editor.setRootElement(). If this occurs on init when the
|
||||
// editor is already focused, then this can cause the editor to
|
||||
|
|
|
@ -917,6 +917,11 @@ export function getTable(tableElement: HTMLElement): TableDOMTable {
|
|||
while (currentNode != null) {
|
||||
const nodeMame = currentNode.nodeName;
|
||||
|
||||
if (nodeMame === 'COLGROUP') {
|
||||
currentNode = currentNode.nextSibling;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nodeMame === 'TD' || nodeMame === 'TH') {
|
||||
const elem = currentNode as HTMLElement;
|
||||
const cell = {
|
||||
|
|
|
@ -47,16 +47,21 @@ function deleteSingleSelectedNode(editor: LexicalEditor) {
|
|||
* Insert a new empty node before/after the selection if the selection contains a single
|
||||
* selected node (like image, media etc...).
|
||||
*/
|
||||
function insertAfterSingleSelectedNode(editor: LexicalEditor, event: KeyboardEvent|null): boolean {
|
||||
function insertAdjacentToSingleSelectedNode(editor: LexicalEditor, event: KeyboardEvent|null): boolean {
|
||||
const selectionNodes = getLastSelection(editor)?.getNodes() || [];
|
||||
if (isSingleSelectedNode(selectionNodes)) {
|
||||
const node = selectionNodes[0];
|
||||
const nearestBlock = $getNearestNodeBlockParent(node) || node;
|
||||
const insertBefore = event?.shiftKey === true;
|
||||
if (nearestBlock) {
|
||||
requestAnimationFrame(() => {
|
||||
editor.update(() => {
|
||||
const newParagraph = $createParagraphNode();
|
||||
nearestBlock.insertAfter(newParagraph);
|
||||
if (insertBefore) {
|
||||
nearestBlock.insertBefore(newParagraph);
|
||||
} else {
|
||||
nearestBlock.insertAfter(newParagraph);
|
||||
}
|
||||
newParagraph.select();
|
||||
});
|
||||
});
|
||||
|
@ -75,22 +80,14 @@ function focusAdjacentOrInsertForSingleSelectNode(editor: LexicalEditor, event:
|
|||
}
|
||||
|
||||
event?.preventDefault();
|
||||
|
||||
const node = selectionNodes[0];
|
||||
const nearestBlock = $getNearestNodeBlockParent(node) || node;
|
||||
let target = after ? nearestBlock.getNextSibling() : nearestBlock.getPreviousSibling();
|
||||
|
||||
editor.update(() => {
|
||||
if (!target) {
|
||||
target = $createParagraphNode();
|
||||
if (after) {
|
||||
nearestBlock.insertAfter(target)
|
||||
} else {
|
||||
nearestBlock.insertBefore(target);
|
||||
}
|
||||
if (after) {
|
||||
node.selectNext();
|
||||
} else {
|
||||
node.selectPrevious();
|
||||
}
|
||||
|
||||
target.selectStart();
|
||||
});
|
||||
|
||||
return true;
|
||||
|
@ -220,7 +217,7 @@ export function registerKeyboardHandling(context: EditorUiContext): () => void {
|
|||
}, COMMAND_PRIORITY_LOW);
|
||||
|
||||
const unregisterEnter = context.editor.registerCommand(KEY_ENTER_COMMAND, (event): boolean => {
|
||||
return insertAfterSingleSelectedNode(context.editor, event)
|
||||
return insertAdjacentToSingleSelectedNode(context.editor, event)
|
||||
|| moveAfterDetailsOnEmptyLine(context.editor, event);
|
||||
}, COMMAND_PRIORITY_LOW);
|
||||
|
||||
|
|
|
@ -244,6 +244,7 @@ export class EditorUIManager {
|
|||
if (selectionChange) {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection();
|
||||
// console.log('manager::selection', selection);
|
||||
this.triggerStateUpdate({
|
||||
editor, selection,
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
$isTextNode,
|
||||
ElementNode,
|
||||
LexicalEditor,
|
||||
LexicalNode
|
||||
LexicalNode, RangeSelection
|
||||
} from "lexical";
|
||||
import {LexicalNodeMatcher} from "../nodes";
|
||||
import {$generateNodesFromDOM} from "@lexical/html";
|
||||
|
@ -118,6 +118,17 @@ export function $sortNodes(nodes: LexicalNode[]): LexicalNode[] {
|
|||
return sorted;
|
||||
}
|
||||
|
||||
export function $insertAndSelectNewEmptyAdjacentNode(node: LexicalNode, after: boolean): RangeSelection {
|
||||
const target = $createParagraphNode();
|
||||
if (after) {
|
||||
node.insertAfter(target)
|
||||
} else {
|
||||
node.insertBefore(target);
|
||||
}
|
||||
|
||||
return target.select();
|
||||
}
|
||||
|
||||
export function nodeHasAlignment(node: object): node is NodeHasAlignment {
|
||||
return '__alignment' in node;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue