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 {$getBlockElementNodesInSelection, $selectNodes, $toggleSelection} from "./selection";
 | 
			
		||||
import {nodeHasInset} from "./nodes";
 | 
			
		||||
import {$sortNodes, nodeHasInset} from "./nodes";
 | 
			
		||||
import {$createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode} from "@lexical/list";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -49,16 +49,11 @@ export function $unnestListItem(node: ListItemNode): ListItemNode {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    const laterSiblings = node.getNextSiblings();
 | 
			
		||||
 | 
			
		||||
    parentListItem.insertAfter(node);
 | 
			
		||||
    if (list.getChildren().length === 0) {
 | 
			
		||||
        list.remove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (parentListItem.getChildren().length === 0) {
 | 
			
		||||
        parentListItem.remove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (laterSiblings.length > 0) {
 | 
			
		||||
        const childList = $createListNode(list.getListType());
 | 
			
		||||
        childList.append(...laterSiblings);
 | 
			
		||||
| 
						 | 
				
			
			@ -69,23 +64,54 @@ export function $unnestListItem(node: ListItemNode): ListItemNode {
 | 
			
		|||
        list.remove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (parentListItem.getChildren().length === 0) {
 | 
			
		||||
        parentListItem.remove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return node;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getListItemsForSelection(selection: BaseSelection|null): (ListItemNode|null)[] {
 | 
			
		||||
    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) {
 | 
			
		||||
        if ($isListItemNode(node)) {
 | 
			
		||||
            listItemNodes.push(node);
 | 
			
		||||
            if (!itemsToIgnore.has(node.getKey())) {
 | 
			
		||||
                listItemNodes.push(node);
 | 
			
		||||
            }
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const parents = node.getParents();
 | 
			
		||||
        for (const parent of parents) {
 | 
			
		||||
            if ($isListItemNode(parent)) {
 | 
			
		||||
                listItemNodes.push(parent);
 | 
			
		||||
                if (!itemsToIgnore.has(parent.getKey())) {
 | 
			
		||||
                    listItemNodes.push(parent);
 | 
			
		||||
                }
 | 
			
		||||
                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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -94,6 +94,30 @@ export function $getNearestNodeBlockParent(node: LexicalNode): LexicalNode|null
 | 
			
		|||
    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 {
 | 
			
		||||
    return '__alignment' in node;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue