mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-07-15 21:31:36 +03:00
Lexical: Made table cell up/down arrow nav smarter
Added logic to attempt to retain x position when navigating cells up/down via arrow keys.
This commit is contained in:
@@ -2219,6 +2219,13 @@ export function $createRangeSelection(): RangeSelection {
|
||||
return new RangeSelection(anchor, focus, 0, '');
|
||||
}
|
||||
|
||||
export function $createCollapsedRangeSelectionForNode(node: LexicalNode, offset: number = 0): RangeSelection {
|
||||
const type = $isTextNode(node) ? 'text' : 'element';
|
||||
const anchor = $createPoint(node.getKey(), offset, type);
|
||||
const focus = $createPoint(node.getKey(), offset, type);
|
||||
return new RangeSelection(anchor, focus, 0, '');
|
||||
}
|
||||
|
||||
export function $createNodeSelection(): NodeSelection {
|
||||
return new NodeSelection(new Set());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
$createTextNode,
|
||||
DOMConversionMap,
|
||||
DOMExportOutput,
|
||||
EditorConfig,
|
||||
ElementNode,
|
||||
LexicalEditor,
|
||||
|
||||
@@ -72,6 +72,7 @@ import {$isTableRowNode} from './LexicalTableRowNode';
|
||||
import {$isTableSelection} from './LexicalTableSelection';
|
||||
import {$computeTableMap, $getNodeTriplet} from './LexicalTableUtils';
|
||||
import {$selectOrCreateAdjacent} from "../../utils/nodes";
|
||||
import {$selectNodeAtXPixelOffset} from "../../utils/selection";
|
||||
|
||||
const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
|
||||
|
||||
@@ -1073,6 +1074,7 @@ const selectTableNodeInDirection = (
|
||||
x: number,
|
||||
y: number,
|
||||
direction: Direction,
|
||||
selectionOffset: number = -1
|
||||
): boolean => {
|
||||
const isForward = direction === 'forward';
|
||||
|
||||
@@ -1112,6 +1114,7 @@ const selectTableNodeInDirection = (
|
||||
selectTableCellNode(
|
||||
tableNode.getCellNodeFromCordsOrThrow(x, y - 1, tableObserver.table),
|
||||
false,
|
||||
selectionOffset,
|
||||
);
|
||||
} else {
|
||||
$selectOrCreateAdjacent(tableNode, false);
|
||||
@@ -1124,6 +1127,7 @@ const selectTableNodeInDirection = (
|
||||
selectTableCellNode(
|
||||
tableNode.getCellNodeFromCordsOrThrow(x, y + 1, tableObserver.table),
|
||||
true,
|
||||
selectionOffset,
|
||||
);
|
||||
} else {
|
||||
$selectOrCreateAdjacent(tableNode, true);
|
||||
@@ -1197,7 +1201,14 @@ function $isSelectionInTable(
|
||||
return false;
|
||||
}
|
||||
|
||||
function selectTableCellNode(tableCell: TableCellNode, fromStart: boolean) {
|
||||
function selectTableCellNode(tableCell: TableCellNode, fromStart: boolean, selectionOffsetPixels : number = -1) {
|
||||
if (selectionOffsetPixels !== -1) {
|
||||
const selection = $selectNodeAtXPixelOffset(tableCell, selectionOffsetPixels, fromStart);
|
||||
if (selection) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fromStart) {
|
||||
tableCell.selectStart();
|
||||
} else {
|
||||
@@ -1491,12 +1502,14 @@ function $handleArrowKey(
|
||||
tableObserver.setAnchorCellForSelection(cell);
|
||||
tableObserver.setFocusCellForSelection(cell, true);
|
||||
} else {
|
||||
const selectionOffset = edgeSelectionRect.x - edgeRect.x;
|
||||
return selectTableNodeInDirection(
|
||||
tableObserver,
|
||||
tableNode,
|
||||
cords.x,
|
||||
cords.y,
|
||||
direction,
|
||||
selectionOffset
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
$createNodeSelection,
|
||||
$createParagraphNode, $createRangeSelection,
|
||||
$createParagraphNode, $createRangeSelection, $getEditor, $getNearestNodeFromDOMNode,
|
||||
$getRoot,
|
||||
$getSelection, $isBlockElementNode, $isDecoratorNode,
|
||||
$isElementNode, $isParagraphNode,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
$setSelection,
|
||||
BaseSelection, DecoratorNode,
|
||||
ElementNode, LexicalEditor,
|
||||
LexicalNode,
|
||||
LexicalNode, RangeSelection,
|
||||
TextFormatType, TextNode
|
||||
} from "lexical";
|
||||
import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
|
||||
@@ -18,6 +18,7 @@ import {$setBlocksType} from "@lexical/selection";
|
||||
import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes";
|
||||
import {CommonBlockAlignment} from "lexical/nodes/common";
|
||||
import {$isListItemNode} from "@lexical/list";
|
||||
import {$createCollapsedRangeSelectionForNode} from "lexical/LexicalSelection";
|
||||
|
||||
const lastSelectionByEditor = new WeakMap<LexicalEditor, BaseSelection|null>;
|
||||
|
||||
@@ -300,4 +301,39 @@ export function $getDecoratorNodesInSelection(selection: BaseSelection | null):
|
||||
}
|
||||
|
||||
return selection.getNodes().filter(node => $isDecoratorNode(node));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to select the given node at roughly the pixel offset, relative to the left of the node.
|
||||
* Returns the range selection if a selection could be made.
|
||||
* Returns null if no selection can be made.
|
||||
*/
|
||||
export function $selectNodeAtXPixelOffset(node: LexicalNode, pixelOffset: number, targetStart: boolean = true): RangeSelection|null {
|
||||
const targetDOM = $getEditor().getElementByKey(node.getKey());
|
||||
if (!targetDOM) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetChild = targetDOM.children[targetStart ? 0 : targetDOM.children.length - 1] || targetDOM;
|
||||
const targetBounds = targetChild.getBoundingClientRect();
|
||||
const targetY = targetBounds[targetStart ? 'top' : 'bottom'] + (targetStart ? 1 : -1);
|
||||
const targetX = targetBounds.x + pixelOffset;
|
||||
// Temporary caretRangeFromPoint usage due to caretPositionFromPoint being only
|
||||
// very recently supported in Safari
|
||||
// To remove post 2026
|
||||
const caretRange = document.caretRangeFromPoint?.(targetX, targetY);
|
||||
const caret = document.caretPositionFromPoint?.(targetX, targetY)
|
||||
?? (caretRange ? { offsetNode: caretRange.startContainer, offset: caretRange.startOffset } : undefined);
|
||||
if (!caret) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetNode = $getNearestNodeFromDOMNode(caret.offsetNode);
|
||||
if (!targetNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rangeSelection = $createCollapsedRangeSelectionForNode(targetNode, caret.offset);
|
||||
$setSelection(rangeSelection);
|
||||
return rangeSelection;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user