1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2026-01-03 23:42:28 +03:00

Lexical: Improved nested details interaction

- Set to open by default on insert.
- Updated selection handling not to always fully cascade to lowest
  editable child on selection, so parents can be reliably selected.
- Updated mouse handling to treat details panes like the root element,
  inserting within-details where relevant.
This commit is contained in:
Dan Brown
2025-08-26 14:41:42 +01:00
parent ee994fa2b7
commit 849bc4d6c3
8 changed files with 96 additions and 23 deletions

View File

@@ -383,6 +383,14 @@ export class LexicalNode {
return isSelected;
}
/**
* Indicate if this node should be selected directly instead of the default
* where the selection would descend to the nearest initial child element.
*/
shouldSelectDirectly(): boolean {
return false;
}
/**
* Returns this nodes key.
*/

View File

@@ -476,12 +476,12 @@ export class RangeSelection implements BaseSelection {
const startOffset = firstPoint.offset;
const endOffset = lastPoint.offset;
if ($isElementNode(firstNode)) {
if ($isElementNode(firstNode) && !firstNode.shouldSelectDirectly()) {
const firstNodeDescendant =
firstNode.getDescendantByIndex<ElementNode>(startOffset);
firstNode = firstNodeDescendant != null ? firstNodeDescendant : firstNode;
}
if ($isElementNode(lastNode)) {
if ($isElementNode(lastNode) && !lastNode.shouldSelectDirectly()) {
let lastNodeDescendant =
lastNode.getDescendantByIndex<ElementNode>(endOffset);
// We don't want to over-select, as node selection infers the child before
@@ -499,7 +499,7 @@ export class RangeSelection implements BaseSelection {
let nodes: Array<LexicalNode>;
if (firstNode.is(lastNode)) {
if ($isElementNode(firstNode) && firstNode.getChildrenSize() > 0) {
if ($isElementNode(firstNode) && firstNode.getChildrenSize() > 0 && !firstNode.shouldSelectDirectly()) {
nodes = [];
} else {
nodes = [firstNode];

View File

@@ -150,6 +150,20 @@ export class ElementNode extends LexicalNode {
}
return node;
}
getFirstSelectableDescendant<T extends LexicalNode>(): null | T {
if (this.shouldSelectDirectly()) {
return null;
}
let node = this.getFirstChild<T>();
while ($isElementNode(node) && !node.shouldSelectDirectly()) {
const child = node.getFirstChild<T>();
if (child === null) {
break;
}
node = child;
}
return node;
}
getLastDescendant<T extends LexicalNode>(): null | T {
let node = this.getLastChild<T>();
while ($isElementNode(node)) {
@@ -161,6 +175,20 @@ export class ElementNode extends LexicalNode {
}
return node;
}
getLastSelectableDescendant<T extends LexicalNode>(): null | T {
if (this.shouldSelectDirectly()) {
return null;
}
let node = this.getLastChild<T>();
while ($isElementNode(node) && !node.shouldSelectDirectly()) {
const child = node.getLastChild<T>();
if (child === null) {
break;
}
node = child;
}
return node;
}
getDescendantByIndex<T extends LexicalNode>(index: number): null | T {
const children = this.getChildren<T>();
const childrenLength = children.length;
@@ -319,11 +347,11 @@ export class ElementNode extends LexicalNode {
return selection;
}
selectStart(): RangeSelection {
const firstNode = this.getFirstDescendant();
const firstNode = this.getFirstSelectableDescendant();
return firstNode ? firstNode.selectStart() : this.select();
}
selectEnd(): RangeSelection {
const lastNode = this.getLastDescendant();
const lastNode = this.getLastSelectableDescendant();
return lastNode ? lastNode.selectEnd() : this.select();
}
clear(): this {