Lexical: Made list selections & intendting more reliable
- Added handling to not include parent of top-most list range selection so that it's not also changed while not visually part of the selection range. - Fixed issue where list items could be left over after unnesting, due to empty checks/removals occuring before all child handling. - Added node sorting, applied to list items during nest operations so that selection range remains reliable.
This commit is contained in:
parent
c03e44124a
commit
62c8eb3357
|
@ -1,6 +1,6 @@
|
||||||
import {$createTextNode, $getSelection, BaseSelection, LexicalEditor, TextNode} from "lexical";
|
import {$createTextNode, $getSelection, BaseSelection, LexicalEditor, TextNode} from "lexical";
|
||||||
import {$getBlockElementNodesInSelection, $selectNodes, $toggleSelection} from "./selection";
|
import {$getBlockElementNodesInSelection, $selectNodes, $toggleSelection} from "./selection";
|
||||||
import {nodeHasInset} from "./nodes";
|
import {$sortNodes, nodeHasInset} from "./nodes";
|
||||||
import {$createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode} from "@lexical/list";
|
import {$createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode} from "@lexical/list";
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,16 +49,11 @@ export function $unnestListItem(node: ListItemNode): ListItemNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
const laterSiblings = node.getNextSiblings();
|
const laterSiblings = node.getNextSiblings();
|
||||||
|
|
||||||
parentListItem.insertAfter(node);
|
parentListItem.insertAfter(node);
|
||||||
if (list.getChildren().length === 0) {
|
if (list.getChildren().length === 0) {
|
||||||
list.remove();
|
list.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentListItem.getChildren().length === 0) {
|
|
||||||
parentListItem.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (laterSiblings.length > 0) {
|
if (laterSiblings.length > 0) {
|
||||||
const childList = $createListNode(list.getListType());
|
const childList = $createListNode(list.getListType());
|
||||||
childList.append(...laterSiblings);
|
childList.append(...laterSiblings);
|
||||||
|
@ -69,23 +64,54 @@ export function $unnestListItem(node: ListItemNode): ListItemNode {
|
||||||
list.remove();
|
list.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parentListItem.getChildren().length === 0) {
|
||||||
|
parentListItem.remove();
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getListItemsForSelection(selection: BaseSelection|null): (ListItemNode|null)[] {
|
function getListItemsForSelection(selection: BaseSelection|null): (ListItemNode|null)[] {
|
||||||
const nodes = selection?.getNodes() || [];
|
const nodes = selection?.getNodes() || [];
|
||||||
const listItemNodes = [];
|
let [start, end] = selection?.getStartEndPoints() || [null, null];
|
||||||
|
|
||||||
|
// Ensure we ignore parent list items of the top-most list item since,
|
||||||
|
// although technically part of the selection, from a user point of
|
||||||
|
// view the selection does not spread to encompass this outer element.
|
||||||
|
const itemsToIgnore: Set<string> = new Set();
|
||||||
|
if (selection && start) {
|
||||||
|
if (selection.isBackward() && end) {
|
||||||
|
[end, start] = [start, end];
|
||||||
|
}
|
||||||
|
|
||||||
|
const startParents = start.getNode().getParents();
|
||||||
|
let foundList = false;
|
||||||
|
for (const parent of startParents) {
|
||||||
|
if ($isListItemNode(parent)) {
|
||||||
|
if (foundList) {
|
||||||
|
itemsToIgnore.add(parent.getKey());
|
||||||
|
} else {
|
||||||
|
foundList = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listItemNodes = [];
|
||||||
outer: for (const node of nodes) {
|
outer: for (const node of nodes) {
|
||||||
if ($isListItemNode(node)) {
|
if ($isListItemNode(node)) {
|
||||||
|
if (!itemsToIgnore.has(node.getKey())) {
|
||||||
listItemNodes.push(node);
|
listItemNodes.push(node);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parents = node.getParents();
|
const parents = node.getParents();
|
||||||
for (const parent of parents) {
|
for (const parent of parents) {
|
||||||
if ($isListItemNode(parent)) {
|
if ($isListItemNode(parent)) {
|
||||||
|
if (!itemsToIgnore.has(parent.getKey())) {
|
||||||
listItemNodes.push(parent);
|
listItemNodes.push(parent);
|
||||||
|
}
|
||||||
continue outer;
|
continue outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +136,8 @@ function $reduceDedupeListItems(listItems: (ListItemNode|null)[]): ListItemNode[
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(listItemMap);
|
const items = Object.values(listItemMap);
|
||||||
|
return $sortNodes(items) as ListItemNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $setInsetForSelection(editor: LexicalEditor, change: number): void {
|
export function $setInsetForSelection(editor: LexicalEditor, change: number): void {
|
||||||
|
|
|
@ -94,6 +94,30 @@ export function $getNearestNodeBlockParent(node: LexicalNode): LexicalNode|null
|
||||||
return $findMatchingParent(node, isBlockNode);
|
return $findMatchingParent(node, isBlockNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function $sortNodes(nodes: LexicalNode[]): LexicalNode[] {
|
||||||
|
const idChain: string[] = [];
|
||||||
|
const addIds = (n: ElementNode) => {
|
||||||
|
for (const child of n.getChildren()) {
|
||||||
|
idChain.push(child.getKey())
|
||||||
|
if ($isElementNode(child)) {
|
||||||
|
addIds(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = $getRoot();
|
||||||
|
addIds(root);
|
||||||
|
|
||||||
|
const sorted = Array.from(nodes);
|
||||||
|
sorted.sort((a, b) => {
|
||||||
|
const aIndex = idChain.indexOf(a.getKey());
|
||||||
|
const bIndex = idChain.indexOf(b.getKey());
|
||||||
|
return aIndex - bIndex;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
export function nodeHasAlignment(node: object): node is NodeHasAlignment {
|
export function nodeHasAlignment(node: object): node is NodeHasAlignment {
|
||||||
return '__alignment' in node;
|
return '__alignment' in node;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue