@37signals/lexxy 0.1.19-beta → 0.1.20-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lexxy.esm.js +140 -23
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -1095,8 +1095,10 @@ class CommandDispatcher {
|
|
|
1095
1095
|
|
|
1096
1096
|
dispatchInsertHorizontalDivider() {
|
|
1097
1097
|
this.editor.update(() => {
|
|
1098
|
-
this.contents.
|
|
1098
|
+
this.contents.insertAtCursorEnsuringLineBelow(new HorizontalDividerNode());
|
|
1099
1099
|
});
|
|
1100
|
+
|
|
1101
|
+
this.editor.focus();
|
|
1100
1102
|
}
|
|
1101
1103
|
|
|
1102
1104
|
dispatchRotateHeadingFormat() {
|
|
@@ -1308,6 +1310,52 @@ class Selection {
|
|
|
1308
1310
|
});
|
|
1309
1311
|
}
|
|
1310
1312
|
|
|
1313
|
+
selectedNodeWithOffset() {
|
|
1314
|
+
const selection = $getSelection();
|
|
1315
|
+
if (!selection) return { node: null, offset: 0 }
|
|
1316
|
+
|
|
1317
|
+
if ($isRangeSelection(selection)) {
|
|
1318
|
+
return {
|
|
1319
|
+
node: selection.anchor.getNode(),
|
|
1320
|
+
offset: selection.anchor.offset
|
|
1321
|
+
}
|
|
1322
|
+
} else if ($isNodeSelection(selection)) {
|
|
1323
|
+
const [ node ] = selection.getNodes();
|
|
1324
|
+
return {
|
|
1325
|
+
node,
|
|
1326
|
+
offset: 0
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
return { node: null, offset: 0 }
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
preservingSelection(fn) {
|
|
1334
|
+
let selectionState = null;
|
|
1335
|
+
|
|
1336
|
+
this.editor.getEditorState().read(() => {
|
|
1337
|
+
const selection = $getSelection();
|
|
1338
|
+
if (selection && $isRangeSelection(selection)) {
|
|
1339
|
+
selectionState = {
|
|
1340
|
+
anchor: { key: selection.anchor.key, offset: selection.anchor.offset },
|
|
1341
|
+
focus: { key: selection.focus.key, offset: selection.focus.offset }
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
fn();
|
|
1347
|
+
|
|
1348
|
+
if (selectionState) {
|
|
1349
|
+
this.editor.update(() => {
|
|
1350
|
+
const selection = $getSelection();
|
|
1351
|
+
if (selection && $isRangeSelection(selection)) {
|
|
1352
|
+
selection.anchor.set(selectionState.anchor.key, selectionState.anchor.offset, "text");
|
|
1353
|
+
selection.focus.set(selectionState.focus.key, selectionState.focus.offset, "text");
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1311
1359
|
get hasSelectedWordsInSingleLine() {
|
|
1312
1360
|
const selection = $getSelection();
|
|
1313
1361
|
if (!$isRangeSelection(selection)) return false
|
|
@@ -2265,6 +2313,11 @@ class Contents {
|
|
|
2265
2313
|
});
|
|
2266
2314
|
}
|
|
2267
2315
|
|
|
2316
|
+
insertAtCursorEnsuringLineBelow(node) {
|
|
2317
|
+
this.insertAtCursor(node);
|
|
2318
|
+
this.#insertLineBelowIfLastNode(node);
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2268
2321
|
insertNodeWrappingEachSelectedLine(newNodeFn) {
|
|
2269
2322
|
this.editor.update(() => {
|
|
2270
2323
|
const selection = $getSelection();
|
|
@@ -2549,6 +2602,17 @@ class Contents {
|
|
|
2549
2602
|
return this.editorElement.selection
|
|
2550
2603
|
}
|
|
2551
2604
|
|
|
2605
|
+
#insertLineBelowIfLastNode(node) {
|
|
2606
|
+
this.editor.update(() => {
|
|
2607
|
+
const nextSibling = node.getNextSibling();
|
|
2608
|
+
if (!nextSibling) {
|
|
2609
|
+
const newParagraph = $createParagraphNode();
|
|
2610
|
+
node.insertAfter(newParagraph);
|
|
2611
|
+
newParagraph.selectStart();
|
|
2612
|
+
}
|
|
2613
|
+
});
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2552
2616
|
#unwrap(node) {
|
|
2553
2617
|
const children = node.getChildren();
|
|
2554
2618
|
|
|
@@ -2891,7 +2955,8 @@ class Contents {
|
|
|
2891
2955
|
lastInsertedNode.insertAfter(textNodeAfter);
|
|
2892
2956
|
|
|
2893
2957
|
this.#appendLineBreakIfNeeded(textNodeAfter.getParentOrThrow());
|
|
2894
|
-
|
|
2958
|
+
const cursorOffset = textAfterCursor ? 0 : 1;
|
|
2959
|
+
textNodeAfter.select(cursorOffset, cursorOffset);
|
|
2895
2960
|
}
|
|
2896
2961
|
|
|
2897
2962
|
#insertReplacementNodes(startNode, replacementNodes) {
|
|
@@ -3681,6 +3746,14 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3681
3746
|
return this.hasAttribute("supports-space-in-searches")
|
|
3682
3747
|
}
|
|
3683
3748
|
|
|
3749
|
+
get open() {
|
|
3750
|
+
return this.popoverElement?.classList?.contains("lexxy-prompt-menu--visible")
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3753
|
+
get closed() {
|
|
3754
|
+
return !this.open
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3684
3757
|
get #doesSpaceSelect() {
|
|
3685
3758
|
return !this.supportsSpaceInSearches
|
|
3686
3759
|
}
|
|
@@ -3701,20 +3774,14 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3701
3774
|
#addTriggerListener() {
|
|
3702
3775
|
const unregister = this.#editor.registerUpdateListener(() => {
|
|
3703
3776
|
this.#editor.read(() => {
|
|
3704
|
-
const
|
|
3705
|
-
if (!
|
|
3706
|
-
let node;
|
|
3707
|
-
if ($isRangeSelection(selection)) {
|
|
3708
|
-
node = selection.anchor.getNode();
|
|
3709
|
-
} else if ($isNodeSelection(selection)) {
|
|
3710
|
-
[ node ] = selection.getNodes();
|
|
3711
|
-
}
|
|
3777
|
+
const { node, offset } = this.#selection.selectedNodeWithOffset();
|
|
3778
|
+
if (!node) return
|
|
3712
3779
|
|
|
3713
|
-
if (
|
|
3714
|
-
const
|
|
3715
|
-
const
|
|
3780
|
+
if ($isTextNode(node) && offset > 0) {
|
|
3781
|
+
const fullText = node.getTextContent();
|
|
3782
|
+
const charBeforeCursor = fullText[offset - 1];
|
|
3716
3783
|
|
|
3717
|
-
if (
|
|
3784
|
+
if (charBeforeCursor === this.trigger) {
|
|
3718
3785
|
unregister();
|
|
3719
3786
|
this.#showPopover();
|
|
3720
3787
|
}
|
|
@@ -3723,6 +3790,38 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3723
3790
|
});
|
|
3724
3791
|
}
|
|
3725
3792
|
|
|
3793
|
+
#addCursorPositionListener() {
|
|
3794
|
+
this.cursorPositionListener = this.#editor.registerUpdateListener(() => {
|
|
3795
|
+
if (this.closed) return
|
|
3796
|
+
|
|
3797
|
+
this.#editor.read(() => {
|
|
3798
|
+
const { node, offset } = this.#selection.selectedNodeWithOffset();
|
|
3799
|
+
if (!node) return
|
|
3800
|
+
|
|
3801
|
+
if ($isTextNode(node) && offset > 0) {
|
|
3802
|
+
const fullText = node.getTextContent();
|
|
3803
|
+
const textBeforeCursor = fullText.slice(0, offset);
|
|
3804
|
+
const lastTriggerIndex = textBeforeCursor.lastIndexOf(this.trigger);
|
|
3805
|
+
|
|
3806
|
+
// If trigger is not found, or cursor is at or before the trigger position, hide popover
|
|
3807
|
+
if (lastTriggerIndex === -1 || offset <= lastTriggerIndex) {
|
|
3808
|
+
this.#hidePopover();
|
|
3809
|
+
}
|
|
3810
|
+
} else {
|
|
3811
|
+
// Cursor is not in a text node or at offset 0, hide popover
|
|
3812
|
+
this.#hidePopover();
|
|
3813
|
+
}
|
|
3814
|
+
});
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
#removeCursorPositionListener() {
|
|
3819
|
+
if (this.cursorPositionListener) {
|
|
3820
|
+
this.cursorPositionListener();
|
|
3821
|
+
this.cursorPositionListener = null;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3726
3825
|
get #editor() {
|
|
3727
3826
|
return this.#editorElement.editor
|
|
3728
3827
|
}
|
|
@@ -3746,6 +3845,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3746
3845
|
this.#editorElement.addEventListener("lexxy:change", this.#filterOptions);
|
|
3747
3846
|
|
|
3748
3847
|
this.#registerKeyListeners();
|
|
3848
|
+
this.#addCursorPositionListener();
|
|
3749
3849
|
}
|
|
3750
3850
|
|
|
3751
3851
|
#registerKeyListeners() {
|
|
@@ -3756,6 +3856,22 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3756
3856
|
if (this.#doesSpaceSelect) {
|
|
3757
3857
|
this.keyListeners.push(this.#editor.registerCommand(KEY_SPACE_COMMAND, this.#handleSelectedOption.bind(this), COMMAND_PRIORITY_HIGH));
|
|
3758
3858
|
}
|
|
3859
|
+
|
|
3860
|
+
// Register arrow keys with HIGH priority to prevent Lexical's selection handlers from running
|
|
3861
|
+
this.keyListeners.push(this.#editor.registerCommand(KEY_ARROW_UP_COMMAND, this.#handleArrowUp.bind(this), COMMAND_PRIORITY_HIGH));
|
|
3862
|
+
this.keyListeners.push(this.#editor.registerCommand(KEY_ARROW_DOWN_COMMAND, this.#handleArrowDown.bind(this), COMMAND_PRIORITY_HIGH));
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
#handleArrowUp(event) {
|
|
3866
|
+
this.#moveSelectionUp();
|
|
3867
|
+
event.preventDefault();
|
|
3868
|
+
return true
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
#handleArrowDown(event) {
|
|
3872
|
+
this.#moveSelectionDown();
|
|
3873
|
+
event.preventDefault();
|
|
3874
|
+
return true
|
|
3759
3875
|
}
|
|
3760
3876
|
|
|
3761
3877
|
#selectFirstOption() {
|
|
@@ -3773,8 +3889,14 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3773
3889
|
#selectOption(listItem) {
|
|
3774
3890
|
this.#clearSelection();
|
|
3775
3891
|
listItem.toggleAttribute("aria-selected", true);
|
|
3892
|
+
listItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
3776
3893
|
listItem.focus();
|
|
3777
|
-
|
|
3894
|
+
|
|
3895
|
+
// Preserve selection to prevent cursor jump
|
|
3896
|
+
this.#selection.preservingSelection(() => {
|
|
3897
|
+
this.#editorElement.focus();
|
|
3898
|
+
});
|
|
3899
|
+
|
|
3778
3900
|
this.#editorContentElement.setAttribute("aria-controls", this.popoverElement.id);
|
|
3779
3901
|
this.#editorContentElement.setAttribute("aria-activedescendant", listItem.id);
|
|
3780
3902
|
this.#editorContentElement.setAttribute("aria-haspopup", "listbox");
|
|
@@ -3823,6 +3945,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3823
3945
|
this.#editorElement.removeEventListener("keydown", this.#handleKeydownOnPopover);
|
|
3824
3946
|
|
|
3825
3947
|
this.#unregisterKeyListeners();
|
|
3948
|
+
this.#removeCursorPositionListener();
|
|
3826
3949
|
|
|
3827
3950
|
await nextFrame();
|
|
3828
3951
|
this.#addTriggerListener();
|
|
@@ -3841,6 +3964,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3841
3964
|
|
|
3842
3965
|
if (this.#editorContents.containsTextBackUntil(this.trigger)) {
|
|
3843
3966
|
await this.#showFilteredOptions();
|
|
3967
|
+
await nextFrame();
|
|
3844
3968
|
this.#positionPopover();
|
|
3845
3969
|
} else {
|
|
3846
3970
|
this.#hidePopover();
|
|
@@ -3881,15 +4005,8 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3881
4005
|
this.#hidePopover();
|
|
3882
4006
|
this.#editorElement.focus();
|
|
3883
4007
|
event.stopPropagation();
|
|
3884
|
-
} else if (event.key === "ArrowDown") {
|
|
3885
|
-
this.#moveSelectionDown();
|
|
3886
|
-
event.preventDefault();
|
|
3887
|
-
event.stopPropagation();
|
|
3888
|
-
} else if (event.key === "ArrowUp") {
|
|
3889
|
-
this.#moveSelectionUp();
|
|
3890
|
-
event.preventDefault();
|
|
3891
|
-
event.stopPropagation();
|
|
3892
4008
|
}
|
|
4009
|
+
// Arrow keys are now handled via Lexical commands with HIGH priority
|
|
3893
4010
|
}
|
|
3894
4011
|
|
|
3895
4012
|
#moveSelectionDown() {
|