@37signals/lexxy 0.1.18-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 +159 -25
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -756,6 +756,10 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
756
756
|
return new ActionTextAttachmentUploadNode({ ...node }, node.__key)
|
|
757
757
|
}
|
|
758
758
|
|
|
759
|
+
static importJSON(serializedNode) {
|
|
760
|
+
return new ActionTextAttachmentUploadNode({ ...serializedNode })
|
|
761
|
+
}
|
|
762
|
+
|
|
759
763
|
constructor({ file, uploadUrl, blobUrlTemplate, editor, progress }, key) {
|
|
760
764
|
super({ contentType: file.type }, key);
|
|
761
765
|
this.file = file;
|
|
@@ -795,6 +799,17 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
795
799
|
return { element: img }
|
|
796
800
|
}
|
|
797
801
|
|
|
802
|
+
exportJSON() {
|
|
803
|
+
return {
|
|
804
|
+
type: "action_text_attachment_upload",
|
|
805
|
+
version: 1,
|
|
806
|
+
progress: this.progress,
|
|
807
|
+
uploadUrl: this.uploadUrl,
|
|
808
|
+
blobUrlTemplate: this.blobUrlTemplate,
|
|
809
|
+
...super.exportJSON()
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
798
813
|
#createDOMForImage() {
|
|
799
814
|
return createElement("img")
|
|
800
815
|
}
|
|
@@ -1080,8 +1095,10 @@ class CommandDispatcher {
|
|
|
1080
1095
|
|
|
1081
1096
|
dispatchInsertHorizontalDivider() {
|
|
1082
1097
|
this.editor.update(() => {
|
|
1083
|
-
this.contents.
|
|
1098
|
+
this.contents.insertAtCursorEnsuringLineBelow(new HorizontalDividerNode());
|
|
1084
1099
|
});
|
|
1100
|
+
|
|
1101
|
+
this.editor.focus();
|
|
1085
1102
|
}
|
|
1086
1103
|
|
|
1087
1104
|
dispatchRotateHeadingFormat() {
|
|
@@ -1293,6 +1310,52 @@ class Selection {
|
|
|
1293
1310
|
});
|
|
1294
1311
|
}
|
|
1295
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
|
+
|
|
1296
1359
|
get hasSelectedWordsInSingleLine() {
|
|
1297
1360
|
const selection = $getSelection();
|
|
1298
1361
|
if (!$isRangeSelection(selection)) return false
|
|
@@ -1661,22 +1724,24 @@ class Selection {
|
|
|
1661
1724
|
const node = this.nodeAfterCursor;
|
|
1662
1725
|
if (node instanceof DecoratorNode) {
|
|
1663
1726
|
this.#selectInLexical(node);
|
|
1727
|
+
return true
|
|
1664
1728
|
} else {
|
|
1665
1729
|
this.#contents.deleteSelectedNodes();
|
|
1666
1730
|
}
|
|
1667
1731
|
|
|
1668
|
-
return
|
|
1732
|
+
return false
|
|
1669
1733
|
}
|
|
1670
1734
|
|
|
1671
1735
|
#deletePreviousOrNext() {
|
|
1672
1736
|
const node = this.nodeBeforeCursor;
|
|
1673
1737
|
if (node instanceof DecoratorNode) {
|
|
1674
1738
|
this.#selectInLexical(node);
|
|
1739
|
+
return true
|
|
1675
1740
|
} else {
|
|
1676
1741
|
this.#contents.deleteSelectedNodes();
|
|
1677
1742
|
}
|
|
1678
1743
|
|
|
1679
|
-
return
|
|
1744
|
+
return false
|
|
1680
1745
|
}
|
|
1681
1746
|
|
|
1682
1747
|
#getValidSelectionRange() {
|
|
@@ -2248,6 +2313,11 @@ class Contents {
|
|
|
2248
2313
|
});
|
|
2249
2314
|
}
|
|
2250
2315
|
|
|
2316
|
+
insertAtCursorEnsuringLineBelow(node) {
|
|
2317
|
+
this.insertAtCursor(node);
|
|
2318
|
+
this.#insertLineBelowIfLastNode(node);
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2251
2321
|
insertNodeWrappingEachSelectedLine(newNodeFn) {
|
|
2252
2322
|
this.editor.update(() => {
|
|
2253
2323
|
const selection = $getSelection();
|
|
@@ -2532,6 +2602,17 @@ class Contents {
|
|
|
2532
2602
|
return this.editorElement.selection
|
|
2533
2603
|
}
|
|
2534
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
|
+
|
|
2535
2616
|
#unwrap(node) {
|
|
2536
2617
|
const children = node.getChildren();
|
|
2537
2618
|
|
|
@@ -2874,7 +2955,8 @@ class Contents {
|
|
|
2874
2955
|
lastInsertedNode.insertAfter(textNodeAfter);
|
|
2875
2956
|
|
|
2876
2957
|
this.#appendLineBreakIfNeeded(textNodeAfter.getParentOrThrow());
|
|
2877
|
-
|
|
2958
|
+
const cursorOffset = textAfterCursor ? 0 : 1;
|
|
2959
|
+
textNodeAfter.select(cursorOffset, cursorOffset);
|
|
2878
2960
|
}
|
|
2879
2961
|
|
|
2880
2962
|
#insertReplacementNodes(startNode, replacementNodes) {
|
|
@@ -3664,6 +3746,14 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3664
3746
|
return this.hasAttribute("supports-space-in-searches")
|
|
3665
3747
|
}
|
|
3666
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
|
+
|
|
3667
3757
|
get #doesSpaceSelect() {
|
|
3668
3758
|
return !this.supportsSpaceInSearches
|
|
3669
3759
|
}
|
|
@@ -3684,20 +3774,14 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3684
3774
|
#addTriggerListener() {
|
|
3685
3775
|
const unregister = this.#editor.registerUpdateListener(() => {
|
|
3686
3776
|
this.#editor.read(() => {
|
|
3687
|
-
const
|
|
3688
|
-
if (!
|
|
3689
|
-
let node;
|
|
3690
|
-
if ($isRangeSelection(selection)) {
|
|
3691
|
-
node = selection.anchor.getNode();
|
|
3692
|
-
} else if ($isNodeSelection(selection)) {
|
|
3693
|
-
[ node ] = selection.getNodes();
|
|
3694
|
-
}
|
|
3777
|
+
const { node, offset } = this.#selection.selectedNodeWithOffset();
|
|
3778
|
+
if (!node) return
|
|
3695
3779
|
|
|
3696
|
-
if (
|
|
3697
|
-
const
|
|
3698
|
-
const
|
|
3780
|
+
if ($isTextNode(node) && offset > 0) {
|
|
3781
|
+
const fullText = node.getTextContent();
|
|
3782
|
+
const charBeforeCursor = fullText[offset - 1];
|
|
3699
3783
|
|
|
3700
|
-
if (
|
|
3784
|
+
if (charBeforeCursor === this.trigger) {
|
|
3701
3785
|
unregister();
|
|
3702
3786
|
this.#showPopover();
|
|
3703
3787
|
}
|
|
@@ -3706,6 +3790,38 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3706
3790
|
});
|
|
3707
3791
|
}
|
|
3708
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
|
+
|
|
3709
3825
|
get #editor() {
|
|
3710
3826
|
return this.#editorElement.editor
|
|
3711
3827
|
}
|
|
@@ -3729,6 +3845,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3729
3845
|
this.#editorElement.addEventListener("lexxy:change", this.#filterOptions);
|
|
3730
3846
|
|
|
3731
3847
|
this.#registerKeyListeners();
|
|
3848
|
+
this.#addCursorPositionListener();
|
|
3732
3849
|
}
|
|
3733
3850
|
|
|
3734
3851
|
#registerKeyListeners() {
|
|
@@ -3739,6 +3856,22 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3739
3856
|
if (this.#doesSpaceSelect) {
|
|
3740
3857
|
this.keyListeners.push(this.#editor.registerCommand(KEY_SPACE_COMMAND, this.#handleSelectedOption.bind(this), COMMAND_PRIORITY_HIGH));
|
|
3741
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
|
|
3742
3875
|
}
|
|
3743
3876
|
|
|
3744
3877
|
#selectFirstOption() {
|
|
@@ -3756,8 +3889,14 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3756
3889
|
#selectOption(listItem) {
|
|
3757
3890
|
this.#clearSelection();
|
|
3758
3891
|
listItem.toggleAttribute("aria-selected", true);
|
|
3892
|
+
listItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
3759
3893
|
listItem.focus();
|
|
3760
|
-
|
|
3894
|
+
|
|
3895
|
+
// Preserve selection to prevent cursor jump
|
|
3896
|
+
this.#selection.preservingSelection(() => {
|
|
3897
|
+
this.#editorElement.focus();
|
|
3898
|
+
});
|
|
3899
|
+
|
|
3761
3900
|
this.#editorContentElement.setAttribute("aria-controls", this.popoverElement.id);
|
|
3762
3901
|
this.#editorContentElement.setAttribute("aria-activedescendant", listItem.id);
|
|
3763
3902
|
this.#editorContentElement.setAttribute("aria-haspopup", "listbox");
|
|
@@ -3806,6 +3945,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3806
3945
|
this.#editorElement.removeEventListener("keydown", this.#handleKeydownOnPopover);
|
|
3807
3946
|
|
|
3808
3947
|
this.#unregisterKeyListeners();
|
|
3948
|
+
this.#removeCursorPositionListener();
|
|
3809
3949
|
|
|
3810
3950
|
await nextFrame();
|
|
3811
3951
|
this.#addTriggerListener();
|
|
@@ -3824,6 +3964,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3824
3964
|
|
|
3825
3965
|
if (this.#editorContents.containsTextBackUntil(this.trigger)) {
|
|
3826
3966
|
await this.#showFilteredOptions();
|
|
3967
|
+
await nextFrame();
|
|
3827
3968
|
this.#positionPopover();
|
|
3828
3969
|
} else {
|
|
3829
3970
|
this.#hidePopover();
|
|
@@ -3864,15 +4005,8 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3864
4005
|
this.#hidePopover();
|
|
3865
4006
|
this.#editorElement.focus();
|
|
3866
4007
|
event.stopPropagation();
|
|
3867
|
-
} else if (event.key === "ArrowDown") {
|
|
3868
|
-
this.#moveSelectionDown();
|
|
3869
|
-
event.preventDefault();
|
|
3870
|
-
event.stopPropagation();
|
|
3871
|
-
} else if (event.key === "ArrowUp") {
|
|
3872
|
-
this.#moveSelectionUp();
|
|
3873
|
-
event.preventDefault();
|
|
3874
|
-
event.stopPropagation();
|
|
3875
4008
|
}
|
|
4009
|
+
// Arrow keys are now handled via Lexical commands with HIGH priority
|
|
3876
4010
|
}
|
|
3877
4011
|
|
|
3878
4012
|
#moveSelectionDown() {
|