@37signals/lexxy 0.1.15-beta → 0.1.17-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.
Files changed (2) hide show
  1. package/dist/lexxy.esm.js +419 -16
  2. package/package.json +1 -1
package/dist/lexxy.esm.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import DOMPurify from 'dompurify';
2
- import { $getSelection, $isRangeSelection, DecoratorNode, $getNodeByKey, HISTORY_MERGE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, $isNodeSelection, $getRoot, $isLineBreakNode, $isTextNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND, $createNodeSelection, $setSelection, $createParagraphNode, $createTextNode, $insertNodes, $isParagraphNode, $createLineBreakNode, CLEAR_HISTORY_COMMAND, $addUpdateTag, SKIP_DOM_SELECTION_TAG, createEditor, KEY_ENTER_COMMAND, COMMAND_PRIORITY_NORMAL, COMMAND_PRIORITY_HIGH, KEY_TAB_COMMAND, KEY_SPACE_COMMAND } from 'lexical';
3
- import { $isListNode, $isListItemNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, ListNode, ListItemNode, registerList } from '@lexical/list';
2
+ import { $getSelection, $isRangeSelection, DecoratorNode, $getNodeByKey, HISTORY_MERGE_TAG, FORMAT_TEXT_COMMAND, $createTextNode, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, $isNodeSelection, $getRoot, $isLineBreakNode, $isTextNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND, $createNodeSelection, $setSelection, $createParagraphNode, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, $isParagraphNode, $insertNodes, $createLineBreakNode, CLEAR_HISTORY_COMMAND, $addUpdateTag, SKIP_DOM_SELECTION_TAG, createEditor, COMMAND_PRIORITY_NORMAL, KEY_TAB_COMMAND, KEY_SPACE_COMMAND } from 'lexical';
3
+ import { $isListNode, $isListItemNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $createListNode, ListNode, ListItemNode, registerList } from '@lexical/list';
4
4
  import { $isQuoteNode, $isHeadingNode, $createQuoteNode, $createHeadingNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
5
5
  import { $isCodeNode, CodeNode, CodeHighlightNode, registerCodeHighlighting, CODE_LANGUAGE_FRIENDLY_NAME_MAP, normalizeCodeLang } from '@lexical/code';
6
- import { $isLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
6
+ import { $isLinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
7
7
  import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
8
8
  import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';
9
9
  import { createEmptyHistoryState, registerHistory } from '@lexical/history';
@@ -1019,7 +1019,19 @@ class CommandDispatcher {
1019
1019
  }
1020
1020
 
1021
1021
  dispatchLink(url) {
1022
- this.#toggleLink(url);
1022
+ this.editor.update(() => {
1023
+ const selection = $getSelection();
1024
+ if (!$isRangeSelection(selection)) return
1025
+
1026
+ if (selection.isCollapsed()) {
1027
+ const autoLinkNode = $createAutoLinkNode(url);
1028
+ const textNode = $createTextNode(url);
1029
+ autoLinkNode.append(textNode);
1030
+ selection.insertNodes([ autoLinkNode ]);
1031
+ } else {
1032
+ $toggleLink(url);
1033
+ }
1034
+ });
1023
1035
  }
1024
1036
 
1025
1037
  dispatchUnlink() {
@@ -1053,7 +1065,7 @@ class CommandDispatcher {
1053
1065
  }
1054
1066
 
1055
1067
  dispatchInsertQuoteBlock() {
1056
- this.contents.toggleNodeWrappingAllSelectedLines((node) => $isQuoteNode(node), () => $createQuoteNode());
1068
+ this.contents.toggleNodeWrappingAllSelectedNodes((node) => $isQuoteNode(node), () => $createQuoteNode());
1057
1069
  }
1058
1070
 
1059
1071
  dispatchInsertCodeBlock() {
@@ -1920,10 +1932,292 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
1920
1932
  }
1921
1933
  }
1922
1934
 
1935
+ class FormatEscaper {
1936
+ constructor(editorElement) {
1937
+ this.editorElement = editorElement;
1938
+ this.editor = editorElement.editor;
1939
+ }
1940
+
1941
+ monitor() {
1942
+ this.editor.registerCommand(
1943
+ KEY_ENTER_COMMAND,
1944
+ (event) => this.#handleEnterKey(event),
1945
+ COMMAND_PRIORITY_HIGH
1946
+ );
1947
+ }
1948
+
1949
+ #handleEnterKey(event) {
1950
+ const selection = $getSelection();
1951
+ if (!$isRangeSelection(selection)) return false
1952
+
1953
+ const anchorNode = selection.anchor.getNode();
1954
+
1955
+ if (!this.#isInsideBlockquote(anchorNode)) return false
1956
+
1957
+ return this.#handleLists(event, anchorNode)
1958
+ || this.#handleBlockquotes(event, anchorNode)
1959
+ }
1960
+
1961
+ #handleLists(event, anchorNode) {
1962
+ if (this.#shouldEscapeFromEmptyListItem(anchorNode) || this.#shouldEscapeFromEmptyParagraphInListItem(anchorNode)) {
1963
+ event.preventDefault();
1964
+ this.#escapeFromList(anchorNode);
1965
+ return true
1966
+ }
1967
+
1968
+ return false
1969
+ }
1970
+
1971
+ #handleBlockquotes(event, anchorNode) {
1972
+ if (this.#shouldEscapeFromEmptyParagraphInBlockquote(anchorNode)) {
1973
+ event.preventDefault();
1974
+ this.#escapeFromBlockquote(anchorNode);
1975
+ return true
1976
+ }
1977
+
1978
+ return false
1979
+ }
1980
+
1981
+ #isInsideBlockquote(node) {
1982
+ let currentNode = node;
1983
+
1984
+ while (currentNode) {
1985
+ if ($isQuoteNode(currentNode)) {
1986
+ return true
1987
+ }
1988
+ currentNode = currentNode.getParent();
1989
+ }
1990
+
1991
+ return false
1992
+ }
1993
+
1994
+ #shouldEscapeFromEmptyListItem(node) {
1995
+ const listItem = this.#getListItemNode(node);
1996
+ if (!listItem) return false
1997
+
1998
+ return this.#isNodeEmpty(listItem)
1999
+ }
2000
+
2001
+ #shouldEscapeFromEmptyParagraphInListItem(node) {
2002
+ const paragraph = this.#getParagraphNode(node);
2003
+ if (!paragraph) return false
2004
+
2005
+ if (!this.#isNodeEmpty(paragraph)) return false
2006
+
2007
+ const parent = paragraph.getParent();
2008
+ return parent && $isListItemNode(parent)
2009
+ }
2010
+
2011
+ #isNodeEmpty(node) {
2012
+ if (node.getTextContent().trim() !== "") return false
2013
+
2014
+ const children = node.getChildren();
2015
+ if (children.length === 0) return true
2016
+
2017
+ return children.every(child => {
2018
+ if ($isLineBreakNode(child)) return true
2019
+ return this.#isNodeEmpty(child)
2020
+ })
2021
+ }
2022
+
2023
+ #getListItemNode(node) {
2024
+ let currentNode = node;
2025
+
2026
+ while (currentNode) {
2027
+ if ($isListItemNode(currentNode)) {
2028
+ return currentNode
2029
+ }
2030
+ currentNode = currentNode.getParent();
2031
+ }
2032
+
2033
+ return null
2034
+ }
2035
+
2036
+ #escapeFromList(anchorNode) {
2037
+ const listItem = this.#getListItemNode(anchorNode);
2038
+ if (!listItem) return
2039
+
2040
+ const parentList = listItem.getParent();
2041
+ if (!parentList || !$isListNode(parentList)) return
2042
+
2043
+ const blockquote = parentList.getParent();
2044
+ const isInBlockquote = blockquote && $isQuoteNode(blockquote);
2045
+
2046
+ if (isInBlockquote) {
2047
+ const listItemsAfter = this.#getListItemSiblingsAfter(listItem);
2048
+ const nonEmptyListItems = listItemsAfter.filter(item => !this.#isNodeEmpty(item));
2049
+
2050
+ if (nonEmptyListItems.length > 0) {
2051
+ this.#splitBlockquoteWithList(blockquote, parentList, listItem, nonEmptyListItems);
2052
+ return
2053
+ }
2054
+ }
2055
+
2056
+ const paragraph = $createParagraphNode();
2057
+ parentList.insertAfter(paragraph);
2058
+
2059
+ listItem.remove();
2060
+ paragraph.selectStart();
2061
+ }
2062
+
2063
+ #shouldEscapeFromEmptyParagraphInBlockquote(node) {
2064
+ const paragraph = this.#getParagraphNode(node);
2065
+ if (!paragraph) return false
2066
+
2067
+ if (!this.#isNodeEmpty(paragraph)) return false
2068
+
2069
+ const parent = paragraph.getParent();
2070
+ return parent && $isQuoteNode(parent)
2071
+ }
2072
+
2073
+ #getParagraphNode(node) {
2074
+ let currentNode = node;
2075
+
2076
+ while (currentNode) {
2077
+ if ($isParagraphNode(currentNode)) {
2078
+ return currentNode
2079
+ }
2080
+ currentNode = currentNode.getParent();
2081
+ }
2082
+
2083
+ return null
2084
+ }
2085
+
2086
+ #escapeFromBlockquote(anchorNode) {
2087
+ const paragraph = this.#getParagraphNode(anchorNode);
2088
+ if (!paragraph) return
2089
+
2090
+ const blockquote = paragraph.getParent();
2091
+ if (!blockquote || !$isQuoteNode(blockquote)) return
2092
+
2093
+ const siblingsAfter = this.#getSiblingsAfter(paragraph);
2094
+ const nonEmptySiblings = siblingsAfter.filter(sibling => !this.#isNodeEmpty(sibling));
2095
+
2096
+ if (nonEmptySiblings.length > 0) {
2097
+ this.#splitBlockquote(blockquote, paragraph, nonEmptySiblings);
2098
+ } else {
2099
+ const newParagraph = $createParagraphNode();
2100
+ blockquote.insertAfter(newParagraph);
2101
+ paragraph.remove();
2102
+ newParagraph.selectStart();
2103
+ }
2104
+ }
2105
+
2106
+ #getSiblingsAfter(node) {
2107
+ const siblings = [];
2108
+ let sibling = node.getNextSibling();
2109
+
2110
+ while (sibling) {
2111
+ siblings.push(sibling);
2112
+ sibling = sibling.getNextSibling();
2113
+ }
2114
+
2115
+ return siblings
2116
+ }
2117
+
2118
+ #getListItemSiblingsAfter(listItem) {
2119
+ const siblings = [];
2120
+ let sibling = listItem.getNextSibling();
2121
+
2122
+ while (sibling) {
2123
+ if ($isListItemNode(sibling)) {
2124
+ siblings.push(sibling);
2125
+ }
2126
+ sibling = sibling.getNextSibling();
2127
+ }
2128
+
2129
+ return siblings
2130
+ }
2131
+
2132
+ #splitBlockquoteWithList(blockquote, parentList, emptyListItem, listItemsAfter) {
2133
+ const blockquoteSiblingsAfterList = this.#getSiblingsAfter(parentList);
2134
+ const nonEmptyBlockquoteSiblings = blockquoteSiblingsAfterList.filter(sibling => !this.#isNodeEmpty(sibling));
2135
+
2136
+ const middleParagraph = $createParagraphNode();
2137
+ blockquote.insertAfter(middleParagraph);
2138
+
2139
+ const newList = $createListNode(parentList.getListType());
2140
+
2141
+ const newBlockquote = $createQuoteNode();
2142
+ middleParagraph.insertAfter(newBlockquote);
2143
+ newBlockquote.append(newList);
2144
+
2145
+ listItemsAfter.forEach(item => {
2146
+ newList.append(item);
2147
+ });
2148
+
2149
+ nonEmptyBlockquoteSiblings.forEach(sibling => {
2150
+ newBlockquote.append(sibling);
2151
+ });
2152
+
2153
+ emptyListItem.remove();
2154
+
2155
+ this.#removeTrailingEmptyListItems(parentList);
2156
+ this.#removeTrailingEmptyNodes(newBlockquote);
2157
+
2158
+ if (parentList.getChildrenSize() === 0) {
2159
+ parentList.remove();
2160
+
2161
+ if (blockquote.getChildrenSize() === 0) {
2162
+ blockquote.remove();
2163
+ }
2164
+ } else {
2165
+ this.#removeTrailingEmptyNodes(blockquote);
2166
+ }
2167
+
2168
+ middleParagraph.selectStart();
2169
+ }
2170
+
2171
+ #removeTrailingEmptyListItems(list) {
2172
+ const items = list.getChildren();
2173
+ for (let i = items.length - 1; i >= 0; i--) {
2174
+ const item = items[i];
2175
+ if ($isListItemNode(item) && this.#isNodeEmpty(item)) {
2176
+ item.remove();
2177
+ } else {
2178
+ break
2179
+ }
2180
+ }
2181
+ }
2182
+
2183
+ #removeTrailingEmptyNodes(blockquote) {
2184
+ const children = blockquote.getChildren();
2185
+ for (let i = children.length - 1; i >= 0; i--) {
2186
+ const child = children[i];
2187
+ if (this.#isNodeEmpty(child)) {
2188
+ child.remove();
2189
+ } else {
2190
+ break
2191
+ }
2192
+ }
2193
+ }
2194
+
2195
+ #splitBlockquote(blockquote, emptyParagraph, siblingsAfter) {
2196
+ const newParagraph = $createParagraphNode();
2197
+ blockquote.insertAfter(newParagraph);
2198
+
2199
+ const newBlockquote = $createQuoteNode();
2200
+ newParagraph.insertAfter(newBlockquote);
2201
+
2202
+ siblingsAfter.forEach(sibling => {
2203
+ newBlockquote.append(sibling);
2204
+ });
2205
+
2206
+ emptyParagraph.remove();
2207
+
2208
+ this.#removeTrailingEmptyNodes(blockquote);
2209
+ this.#removeTrailingEmptyNodes(newBlockquote);
2210
+
2211
+ newParagraph.selectStart();
2212
+ }
2213
+ }
2214
+
1923
2215
  class Contents {
1924
2216
  constructor(editorElement) {
1925
2217
  this.editorElement = editorElement;
1926
2218
  this.editor = editorElement.editor;
2219
+
2220
+ new FormatEscaper(editorElement).monitor();
1927
2221
  }
1928
2222
 
1929
2223
  insertHtml(html) {
@@ -1984,20 +2278,23 @@ class Contents {
1984
2278
  if (isFormatAppliedFn(topLevelElement)) {
1985
2279
  this.removeFormattingFromSelectedLines();
1986
2280
  } else {
1987
- this.insertNodeWrappingAllSelectedLines(newNodeFn);
2281
+ this.#insertNodeWrappingAllSelectedLines(newNodeFn);
1988
2282
  }
1989
2283
  });
1990
2284
  }
1991
2285
 
1992
- insertNodeWrappingAllSelectedLines(newNodeFn) {
2286
+ toggleNodeWrappingAllSelectedNodes(isFormatAppliedFn, newNodeFn) {
1993
2287
  this.editor.update(() => {
1994
2288
  const selection = $getSelection();
1995
2289
  if (!$isRangeSelection(selection)) return
1996
2290
 
1997
- if (selection.isCollapsed()) {
1998
- this.#wrapCurrentLine(selection, newNodeFn);
2291
+ const topLevelElement = selection.anchor.getNode().getTopLevelElementOrThrow();
2292
+
2293
+ // Check if format is already applied
2294
+ if (isFormatAppliedFn(topLevelElement)) {
2295
+ this.#unwrap(topLevelElement);
1999
2296
  } else {
2000
- this.#wrapMultipleSelectedLines(selection, newNodeFn);
2297
+ this.#insertNodeWrappingAllSelectedNodes(newNodeFn);
2001
2298
  }
2002
2299
  });
2003
2300
  }
@@ -2238,6 +2535,80 @@ class Contents {
2238
2535
  return this.editorElement.selection
2239
2536
  }
2240
2537
 
2538
+ #unwrap(node) {
2539
+ const children = node.getChildren();
2540
+
2541
+ children.forEach((child) => {
2542
+ node.insertBefore(child);
2543
+ });
2544
+
2545
+ node.remove();
2546
+ }
2547
+
2548
+ #insertNodeWrappingAllSelectedNodes(newNodeFn) {
2549
+ this.editor.update(() => {
2550
+ const selection = $getSelection();
2551
+ if (!$isRangeSelection(selection)) return
2552
+
2553
+ const selectedNodes = selection.extract();
2554
+ if (selectedNodes.length === 0) return
2555
+
2556
+ const topLevelElements = new Set();
2557
+ selectedNodes.forEach((node) => {
2558
+ const topLevel = node.getTopLevelElementOrThrow();
2559
+ topLevelElements.add(topLevel);
2560
+ });
2561
+
2562
+ const elements = this.#removeTrailingEmptyParagraphs(Array.from(topLevelElements));
2563
+ if (elements.length === 0) return
2564
+
2565
+ const wrappingNode = newNodeFn();
2566
+ elements[0].insertBefore(wrappingNode);
2567
+ elements.forEach((element) => {
2568
+ wrappingNode.append(element);
2569
+ });
2570
+
2571
+ $setSelection(null);
2572
+ });
2573
+ }
2574
+
2575
+ #removeTrailingEmptyParagraphs(elements) {
2576
+ let lastNonEmptyIndex = elements.length - 1;
2577
+
2578
+ // Find the last non-empty paragraph
2579
+ while (lastNonEmptyIndex >= 0) {
2580
+ const element = elements[lastNonEmptyIndex];
2581
+ if (!$isParagraphNode(element) || !this.#isElementEmpty(element)) {
2582
+ break
2583
+ }
2584
+ lastNonEmptyIndex--;
2585
+ }
2586
+
2587
+ return elements.slice(0, lastNonEmptyIndex + 1)
2588
+ }
2589
+
2590
+ #isElementEmpty(element) {
2591
+ // Check text content first
2592
+ if (element.getTextContent().trim() !== "") return false
2593
+
2594
+ // Check if it only contains line breaks
2595
+ const children = element.getChildren();
2596
+ return children.length === 0 || children.every(child => $isLineBreakNode(child))
2597
+ }
2598
+
2599
+ #insertNodeWrappingAllSelectedLines(newNodeFn) {
2600
+ this.editor.update(() => {
2601
+ const selection = $getSelection();
2602
+ if (!$isRangeSelection(selection)) return
2603
+
2604
+ if (selection.isCollapsed()) {
2605
+ this.#wrapCurrentLine(selection, newNodeFn);
2606
+ } else {
2607
+ this.#wrapMultipleSelectedLines(selection, newNodeFn);
2608
+ }
2609
+ });
2610
+ }
2611
+
2241
2612
  #wrapCurrentLine(selection, newNodeFn) {
2242
2613
  const anchorNode = selection.anchor.getNode();
2243
2614
  const topLevelElement = anchorNode.getTopLevelElementOrThrow();
@@ -2566,7 +2937,7 @@ class Clipboard {
2566
2937
 
2567
2938
  if (!clipboardData) return false
2568
2939
 
2569
- if (this.#isOnlyPlainTextPasted(clipboardData)) {
2940
+ if (this.#isOnlyPlainTextPasted(clipboardData) && !this.#isPastingIntoCodeBlock()) {
2570
2941
  this.#pastePlainText(clipboardData);
2571
2942
  event.preventDefault();
2572
2943
  return true
@@ -2580,6 +2951,27 @@ class Clipboard {
2580
2951
  return types.length === 1 && types[0] === "text/plain"
2581
2952
  }
2582
2953
 
2954
+ #isPastingIntoCodeBlock() {
2955
+ let result = false;
2956
+
2957
+ this.editor.getEditorState().read(() => {
2958
+ const selection = $getSelection();
2959
+ if (!$isRangeSelection(selection)) return
2960
+
2961
+ let currentNode = selection.anchor.getNode();
2962
+
2963
+ while (currentNode) {
2964
+ if ($isCodeNode(currentNode)) {
2965
+ result = true;
2966
+ return
2967
+ }
2968
+ currentNode = currentNode.getParent();
2969
+ }
2970
+ });
2971
+
2972
+ return result
2973
+ }
2974
+
2583
2975
  #pastePlainText(clipboardData) {
2584
2976
  const item = clipboardData.items[0];
2585
2977
  item.getAsString((text) => {
@@ -3316,8 +3708,8 @@ class LexicalPromptElement extends HTMLElement {
3316
3708
 
3317
3709
  async #showPopover() {
3318
3710
  this.popoverElement ??= await this.#buildPopover();
3711
+ this.#resetPopoverPosition();
3319
3712
  await this.#filterOptions();
3320
- this.#positionPopover();
3321
3713
  this.popoverElement.classList.toggle("lexxy-prompt-menu--visible", true);
3322
3714
  this.#selectFirstOption();
3323
3715
 
@@ -3372,19 +3764,29 @@ class LexicalPromptElement extends HTMLElement {
3372
3764
  const contentRect = this.#editorContentElement.getBoundingClientRect();
3373
3765
  const verticalOffset = contentRect.top - editorRect.top;
3374
3766
 
3375
- this.popoverElement.style.left = `${x}px`;
3767
+ if (!this.popoverElement.hasAttribute("data-anchored")) {
3768
+ this.popoverElement.style.left = `${x}px`;
3769
+ this.popoverElement.toggleAttribute("data-anchored", true);
3770
+ }
3771
+
3376
3772
  this.popoverElement.style.top = `${y + verticalOffset}px`;
3377
3773
  this.popoverElement.style.bottom = "auto";
3378
3774
 
3379
3775
  const popoverRect = this.popoverElement.getBoundingClientRect();
3380
3776
  const isClippedAtBottom = popoverRect.bottom > window.innerHeight;
3381
3777
 
3382
- if (isClippedAtBottom) {
3383
- this.popoverElement.style.bottom = `${y - verticalOffset + fontSize}px`;
3384
- this.popoverElement.style.top = "auto";
3778
+ if (isClippedAtBottom || this.popoverElement.hasAttribute("data-clipped-at-bottom")) {
3779
+ this.popoverElement.style.top = `${y + verticalOffset - popoverRect.height - fontSize}px`;
3780
+ this.popoverElement.style.bottom = "auto";
3781
+ this.popoverElement.toggleAttribute("data-clipped-at-bottom", true);
3385
3782
  }
3386
3783
  }
3387
3784
 
3785
+ #resetPopoverPosition() {
3786
+ this.popoverElement.removeAttribute("data-clipped-at-bottom");
3787
+ this.popoverElement.removeAttribute("data-anchored");
3788
+ }
3789
+
3388
3790
  async #hidePopover() {
3389
3791
  this.#clearSelection();
3390
3792
  this.popoverElement.classList.toggle("lexxy-prompt-menu--visible", false);
@@ -3410,6 +3812,7 @@ class LexicalPromptElement extends HTMLElement {
3410
3812
 
3411
3813
  if (this.#editorContents.containsTextBackUntil(this.trigger)) {
3412
3814
  await this.#showFilteredOptions();
3815
+ this.#positionPopover();
3413
3816
  } else {
3414
3817
  this.#hidePopover();
3415
3818
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@37signals/lexxy",
3
- "version": "0.1.15-beta",
3
+ "version": "0.1.17-beta",
4
4
  "description": "Lexxy - A modern rich text editor for Rails.",
5
5
  "module": "dist/lexxy.esm.js",
6
6
  "type": "module",