@lexical/react 0.4.1 → 0.5.1-next.0
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/DEPRECATED_useLexical.js.flow +0 -1
- package/DEPRECATED_useLexicalDecorators.d.ts +2 -1
- package/DEPRECATED_useLexicalDecorators.dev.js +125 -10
- package/DEPRECATED_useLexicalDecorators.js.flow +8 -0
- package/DEPRECATED_useLexicalDecorators.prod.js +6 -2
- package/DEPRECATED_useLexicalPlainText.d.ts +2 -2
- package/DEPRECATED_useLexicalPlainText.dev.js +4 -4
- package/DEPRECATED_useLexicalPlainText.js.flow +0 -1
- package/DEPRECATED_useLexicalPlainText.prod.js +2 -2
- package/DEPRECATED_useLexicalRichText.d.ts +2 -2
- package/DEPRECATED_useLexicalRichText.dev.js +4 -4
- package/DEPRECATED_useLexicalRichText.js.flow +0 -1
- package/DEPRECATED_useLexicalRichText.prod.js +2 -2
- package/LexicalAutoEmbedPlugin.d.ts +3 -13
- package/LexicalAutoEmbedPlugin.dev.js +3 -19
- package/LexicalAutoEmbedPlugin.js.flow +3 -11
- package/LexicalAutoEmbedPlugin.prod.js +4 -5
- package/LexicalAutoLinkPlugin.dev.js +49 -45
- package/LexicalAutoLinkPlugin.prod.js +6 -6
- package/LexicalBlockWithAlignableContents.dev.js +1 -1
- package/LexicalBlockWithAlignableContents.prod.js +1 -1
- package/LexicalCollaborationPlugin.d.ts +5 -1
- package/LexicalCollaborationPlugin.dev.js +54 -16
- package/LexicalCollaborationPlugin.js.flow +1 -0
- package/LexicalCollaborationPlugin.prod.js +9 -9
- package/LexicalComposer.d.ts +1 -2
- package/LexicalComposer.dev.js +15 -1
- package/LexicalComposer.js.flow +14 -7
- package/LexicalComposer.prod.js +3 -3
- package/LexicalContentEditable.dev.js +1 -1
- package/LexicalContentEditable.prod.js +1 -1
- package/LexicalHorizontalRuleNode.d.ts +4 -2
- package/LexicalHorizontalRuleNode.dev.js +25 -3
- package/LexicalHorizontalRuleNode.js.flow +0 -1
- package/LexicalHorizontalRuleNode.prod.js +5 -5
- package/LexicalNestedComposer.d.ts +4 -2
- package/LexicalNestedComposer.dev.js +25 -3
- package/LexicalNestedComposer.js.flow +1 -0
- package/LexicalNestedComposer.prod.js +3 -3
- package/LexicalOnChangePlugin.d.ts +1 -2
- package/LexicalOnChangePlugin.dev.js +3 -9
- package/LexicalOnChangePlugin.js.flow +0 -2
- package/LexicalOnChangePlugin.prod.js +2 -2
- package/LexicalPlainTextPlugin.d.ts +3 -3
- package/LexicalPlainTextPlugin.dev.js +111 -20
- package/LexicalPlainTextPlugin.js.flow +3 -1
- package/LexicalPlainTextPlugin.prod.js +6 -4
- package/LexicalRichTextPlugin.d.ts +3 -3
- package/LexicalRichTextPlugin.dev.js +111 -20
- package/LexicalRichTextPlugin.js.flow +3 -1
- package/LexicalRichTextPlugin.prod.js +6 -4
- package/LexicalTableOfContents__EXPERIMENTAL.js.flow +1 -1
- package/LexicalTablePlugin.dev.js +1 -1
- package/LexicalTablePlugin.prod.js +1 -1
- package/LexicalTreeView.dev.js +28 -13
- package/LexicalTreeView.prod.js +14 -13
- package/LexicalTypeaheadMenuPlugin.d.ts +20 -8
- package/LexicalTypeaheadMenuPlugin.dev.js +200 -57
- package/LexicalTypeaheadMenuPlugin.js.flow +19 -21
- package/LexicalTypeaheadMenuPlugin.prod.js +18 -14
- package/package.json +19 -19
- package/shared/ReactErrorBoundary.d.ts +63 -0
- package/shared/useDecorators.d.ts +8 -1
- package/shared/usePlainTextSetup.d.ts +1 -2
- package/shared/useRichTextSetup.d.ts +1 -2
- package/shared/useYjsCollaboration.d.ts +4 -1
package/LexicalTreeView.prod.js
CHANGED
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
'use strict';var
|
|
8
|
-
function H(a){let
|
|
9
|
-
function
|
|
10
|
-
0===
|
|
11
|
-
`: node\n \u2514 [${Array.from(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function
|
|
15
|
-
|
|
7
|
+
'use strict';var k=require("@lexical/link"),r=require("@lexical/mark"),v=require("@lexical/utils"),C=require("lexical"),D=require("react");let E=Object.freeze({"\t":"\\t","\n":"\\n"}),F=new RegExp(Object.keys(E).join("|"),"g"),G=Object.freeze({ancestorHasNextSibling:"|",ancestorIsLastChild:" ",hasNextSibling:"\u251c",isLastChild:"\u2514",selectedChar:"^",selectedLine:">"});
|
|
8
|
+
function H(a){let c="",e=K(a),l=a.anchor;a=a.focus;let f=l.offset,b=a.offset;c=c+`: range ${""!==e?`{ ${e} }`:""}`+`\n \u251c anchor { key: ${l.key}, offset: ${null===f?"null":f}, type: ${l.type} }`;return c+=`\n \u2514 focus { key: ${a.key}, offset: ${null===b?"null":b}, type: ${a.type} }`}
|
|
9
|
+
function L(a,c,e,l){let f=" root\n";a=a.read(()=>{const b=C.$getSelection();M(C.$getRoot(),(d,n)=>{const x=`(${d.getKey()})`,t=d.getType()||"",q=d.isSelected(),z=r.$isMarkNode(d)?` id: [ ${d.getIDs().join(", ")} ] `:"";var m=f,w=q?G.selectedLine:" ",A=n.join(" ");if(C.$isTextNode(d)){var h=d.getTextContent();var p=0===h.length?"(empty)":`"${N(h)}"`;h=[K(d),O(d),P(d)].filter(Boolean).join(", ");h=[p,0!==h.length?`{ ${h} }`:null].filter(Boolean).join(" ").trim()}else if(k.$isLinkNode(d)){h=d.getURL();
|
|
10
|
+
h=0===h.length?"(empty)":`"${N(h)}"`;p=d.getTarget();null!=p&&(p="target: "+p);var y=Boolean;let g=d.getRel();null!=g&&(g="rel: "+g);p=[p,g].filter(y).join(", ");h=[h,0!==p.length?`{ ${p} }`:null].filter(Boolean).join(" ").trim()}else h="";f=m+`${w} ${A} ${x} ${t} ${z} ${h}\n`;f+=Q({indent:n,isSelected:q,node:d,nodeKeyDisplay:x,selection:b,typeDisplay:t})});return null===b?": null":C.$isRangeSelection(b)?H(b):C.DEPRECATED_$isGridSelection(b)?`: grid\n \u2514 { grid: ${b.gridKey}, anchorCell: ${b.anchor.key}, focusCell: ${b.focus.key} }`:
|
|
11
|
+
`: node\n \u2514 [${Array.from(b._nodes).join(", ")}]`});f+="\n selection"+a;f+="\n\n editor:";f+=`\n \u2514 namespace ${c.namespace}`;null!==e&&(f+=`\n \u2514 compositionKey ${e}`);return f+=`\n \u2514 editable ${String(l)}`}function M(a,c,e=[]){a=a.getChildren();let l=a.length;a.forEach((f,b)=>{c(f,e.concat(b===l-1?G.isLastChild:G.hasNextSibling));C.$isElementNode(f)&&M(f,c,e.concat(b===l-1?G.ancestorIsLastChild:G.ancestorHasNextSibling))})}
|
|
12
|
+
function N(a){return Object.entries(E).reduce((c,[e,l])=>c.replace(new RegExp(e,"g"),String(l)),a)}
|
|
13
|
+
let R=[a=>a.hasFormat("bold")&&"Bold",a=>a.hasFormat("code")&&"Code",a=>a.hasFormat("italic")&&"Italic",a=>a.hasFormat("strikethrough")&&"Strikethrough",a=>a.hasFormat("subscript")&&"Subscript",a=>a.hasFormat("superscript")&&"Superscript",a=>a.hasFormat("underline")&&"Underline"],S=[a=>a.isDirectionless()&&"Directionless",a=>a.isUnmergeable()&&"Unmergeable"],T=[a=>a.isToken()&&"Token",a=>a.isSegmented()&&"Segmented"];
|
|
14
|
+
function O(a){let c=S.map(e=>e(a)).filter(Boolean).join(", ").toLocaleLowerCase();""!==c&&(c="detail: "+c);return c}function P(a){let c=T.map(e=>e(a)).filter(Boolean).join(", ").toLocaleLowerCase();""!==c&&(c="mode: "+c);return c}function K(a){let c=R.map(e=>e(a)).filter(Boolean).join(", ").toLocaleLowerCase();""!==c&&(c="format: "+c);return c}
|
|
15
|
+
function Q({indent:a,isSelected:c,node:e,nodeKeyDisplay:l,selection:f,typeDisplay:b}){if(!C.$isTextNode(e)||!C.$isRangeSelection(f)||!c||C.$isElementNode(e))return"";c=f.anchor;var d=f.focus;if(""===e.getTextContent()||c.getNode()===f.focus.getNode()&&c.offset===d.offset)return"";d=f.anchor;let n=f.focus,x=e.getTextContent(),t=x.length;c=f=-1;if("text"===d.type&&"text"===n.type){let m=d.getNode(),w=n.getNode();m===w&&e===m&&d.offset!==n.offset?[f,c]=d.offset<n.offset?[d.offset,n.offset]:[n.offset,
|
|
16
|
+
d.offset]:e===m?[f,c]=m.isBefore(w)?[d.offset,t]:[0,d.offset]:e===w?[f,c]=w.isBefore(m)?[n.offset,t]:[0,n.offset]:[f,c]=[0,t]}e=(x.slice(0,f).match(F)||[]).length;d=(x.slice(f,c).match(F)||[]).length;let [q,z]=[f+e,c+e+d];if(q===z)return"";e=a[a.length-1]===G.hasNextSibling?G.ancestorHasNextSibling:G.ancestorIsLastChild;a=[...a.slice(0,a.length-1),e];e=Array(q+1).fill(" ");f=Array(z-q).fill(G.selectedChar);l=Array(l.length+(b.length+3)).fill(" ");return[G.selectedLine,a.join(" "),[...l,...e,...f].join("")].join(" ")+
|
|
16
17
|
"\n"}
|
|
17
|
-
exports.TreeView=function({timeTravelButtonClassName:a,timeTravelPanelSliderClassName:
|
|
18
|
-
n(B=>[...B,[Date.now(),
|
|
19
|
-
{let
|
|
20
|
-
{
|
|
18
|
+
exports.TreeView=function({timeTravelButtonClassName:a,timeTravelPanelSliderClassName:c,timeTravelPanelButtonClassName:e,viewClassName:l,timeTravelPanelClassName:f,editor:b}){let [d,n]=D.useState([]),[x,t]=D.useState(""),[q,z]=D.useState(!1),m=D.useRef(0),w=D.useRef(null),A=D.useRef(null),[h,p]=D.useState(!1);D.useEffect(()=>{t(L(b.getEditorState(),b._config,b._compositionKey,b._editable));return v.mergeRegister(b.registerUpdateListener(({editorState:g})=>{let u=L(b.getEditorState(),b._config,b._compositionKey,
|
|
19
|
+
b._editable);t(u);q||n(B=>[...B,[Date.now(),g]])}),b.registerEditableListener(()=>{let g=L(b.getEditorState(),b._config,b._compositionKey,b._editable);t(g)}))},[q,b]);let y=d.length;D.useEffect(()=>{if(h){let g,u=()=>{const B=m.current;B===y-1?p(!1):g=setTimeout(()=>{m.current++;const I=m.current,J=A.current;null!==J&&(J.value=String(I));b.setEditorState(d[I][1]);u()},d[B+1][0]-d[B][0])};u();return()=>{clearTimeout(g)}}},[d,h,b,y]);D.useEffect(()=>{let g=w.current;if(null!==g)return g.__lexicalEditor=
|
|
20
|
+
b,()=>{g.__lexicalEditor=null}},[b]);return D.createElement("div",{className:l},!q&&2<y&&D.createElement("button",{onClick:()=>{let g=b.getRootElement();null!==g&&(g.contentEditable="false",m.current=y-1,z(!0))},className:a,type:"button"},"Time Travel"),D.createElement("pre",{ref:w},x),q&&D.createElement("div",{className:f},D.createElement("button",{className:e,onClick:()=>{m.current===y-1&&(m.current=1);p(!h)},type:"button"},h?"Pause":"Play"),D.createElement("input",{className:c,ref:A,onChange:g=>
|
|
21
|
+
{g=Number(g.target.value);let u=d[g];u&&(m.current=g,b.setEditorState(u[1]))},type:"range",min:"1",max:y-1}),D.createElement("button",{className:e,onClick:()=>{var g=b.getRootElement();if(null!==g){g.contentEditable="true";g=d.length-1;b.setEditorState(d[g][1]);let u=A.current;null!==u&&(u.value=String(g));z(!1);p(!1)}},type:"button"},"Exit")))}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
|
-
import { LexicalEditor, NodeKey, TextNode } from 'lexical';
|
|
8
|
+
import { LexicalCommand, LexicalEditor, NodeKey, TextNode } from 'lexical';
|
|
9
9
|
import { MutableRefObject, ReactPortal } from 'react';
|
|
10
10
|
export declare type QueryMatch = {
|
|
11
11
|
leadOffset: number;
|
|
@@ -14,7 +14,7 @@ export declare type QueryMatch = {
|
|
|
14
14
|
};
|
|
15
15
|
export declare type Resolution = {
|
|
16
16
|
match: QueryMatch;
|
|
17
|
-
getRect: () =>
|
|
17
|
+
getRect: () => DOMRect;
|
|
18
18
|
};
|
|
19
19
|
export declare const PUNCTUATION = "\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%'\"~=<>_:;";
|
|
20
20
|
export declare class TypeaheadOption {
|
|
@@ -23,30 +23,42 @@ export declare class TypeaheadOption {
|
|
|
23
23
|
constructor(key: string);
|
|
24
24
|
setRefElement(element: HTMLElement | null): void;
|
|
25
25
|
}
|
|
26
|
-
export declare type MenuRenderFn<TOption extends TypeaheadOption> = (
|
|
26
|
+
export declare type MenuRenderFn<TOption extends TypeaheadOption> = (anchorElementRef: MutableRefObject<HTMLElement | null>, itemProps: {
|
|
27
27
|
selectedIndex: number | null;
|
|
28
28
|
selectOptionAndCleanUp: (option: TOption) => void;
|
|
29
29
|
setHighlightedIndex: (index: number) => void;
|
|
30
|
+
options: Array<TOption>;
|
|
30
31
|
}, matchingString: string) => ReactPortal | JSX.Element | null;
|
|
32
|
+
export declare function getScrollParent(element: HTMLElement, includeHidden: boolean): HTMLElement | HTMLBodyElement;
|
|
33
|
+
export declare function useDynamicPositioning(resolution: Resolution | null, targetElement: HTMLElement | null, onReposition: () => void, onVisibilityChange?: (isInView: boolean) => void): void;
|
|
34
|
+
export declare const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
|
|
35
|
+
index: number;
|
|
36
|
+
option: TypeaheadOption;
|
|
37
|
+
}>;
|
|
31
38
|
export declare function useBasicTypeaheadTriggerMatch(trigger: string, { minLength, maxLength }: {
|
|
32
39
|
minLength?: number;
|
|
33
40
|
maxLength?: number;
|
|
34
41
|
}): TriggerFn;
|
|
35
|
-
export declare type
|
|
42
|
+
export declare type TypeaheadMenuPluginProps<TOption extends TypeaheadOption> = {
|
|
36
43
|
onQueryChange: (matchingString: string | null) => void;
|
|
37
44
|
onSelectOption: (option: TOption, textNodeContainingQuery: TextNode | null, closeMenu: () => void, matchingString: string) => void;
|
|
38
45
|
options: Array<TOption>;
|
|
39
46
|
menuRenderFn: MenuRenderFn<TOption>;
|
|
40
47
|
triggerFn: TriggerFn;
|
|
48
|
+
onOpen?: (resolution: Resolution) => void;
|
|
49
|
+
onClose?: () => void;
|
|
50
|
+
anchorClassName?: string;
|
|
41
51
|
};
|
|
42
52
|
export declare type TriggerFn = (text: string, editor: LexicalEditor) => QueryMatch | null;
|
|
43
|
-
export declare function LexicalTypeaheadMenuPlugin<TOption extends TypeaheadOption>({ options, onQueryChange, onSelectOption, menuRenderFn, triggerFn, }:
|
|
44
|
-
declare type
|
|
53
|
+
export declare function LexicalTypeaheadMenuPlugin<TOption extends TypeaheadOption>({ options, onQueryChange, onSelectOption, onOpen, onClose, menuRenderFn, triggerFn, anchorClassName, }: TypeaheadMenuPluginProps<TOption>): JSX.Element | null;
|
|
54
|
+
declare type NodeMenuPluginProps<TOption extends TypeaheadOption> = {
|
|
45
55
|
onSelectOption: (option: TOption, textNodeContainingQuery: TextNode | null, closeMenu: () => void, matchingString: string) => void;
|
|
46
56
|
options: Array<TOption>;
|
|
47
57
|
nodeKey: NodeKey | null;
|
|
48
|
-
onClose
|
|
58
|
+
onClose?: () => void;
|
|
59
|
+
onOpen?: (resolution: Resolution) => void;
|
|
49
60
|
menuRenderFn: MenuRenderFn<TOption>;
|
|
61
|
+
anchorClassName?: string;
|
|
50
62
|
};
|
|
51
|
-
export declare function LexicalNodeMenuPlugin<TOption extends TypeaheadOption>({ options, nodeKey, onClose, onSelectOption, menuRenderFn, }:
|
|
63
|
+
export declare function LexicalNodeMenuPlugin<TOption extends TypeaheadOption>({ options, nodeKey, onClose, onOpen, onSelectOption, menuRenderFn, anchorClassName, }: NodeMenuPluginProps<TOption>): JSX.Element | null;
|
|
52
64
|
export {};
|
|
@@ -213,12 +213,90 @@ function startTransition(callback) {
|
|
|
213
213
|
} else {
|
|
214
214
|
callback();
|
|
215
215
|
}
|
|
216
|
+
} // Got from https://stackoverflow.com/a/42543908/2013580
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
function getScrollParent(element, includeHidden) {
|
|
220
|
+
let style = getComputedStyle(element);
|
|
221
|
+
const excludeStaticParent = style.position === 'absolute';
|
|
222
|
+
const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
|
|
223
|
+
|
|
224
|
+
if (style.position === 'fixed') {
|
|
225
|
+
return document.body;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (let parent = element; parent = parent.parentElement;) {
|
|
229
|
+
style = getComputedStyle(parent);
|
|
230
|
+
|
|
231
|
+
if (excludeStaticParent && style.position === 'static') {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) {
|
|
236
|
+
return parent;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return document.body;
|
|
216
241
|
}
|
|
217
242
|
|
|
243
|
+
function isTriggerVisibleInNearestScrollContainer(targetElement, containerElement) {
|
|
244
|
+
const tRect = targetElement.getBoundingClientRect();
|
|
245
|
+
const cRect = containerElement.getBoundingClientRect();
|
|
246
|
+
return tRect.top > cRect.top && tRect.top < cRect.bottom;
|
|
247
|
+
} // Reposition the menu on scroll, window resize, and element resize.
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
function useDynamicPositioning(resolution, targetElement, onReposition, onVisibilityChange) {
|
|
251
|
+
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
252
|
+
React.useEffect(() => {
|
|
253
|
+
if (targetElement != null && resolution != null) {
|
|
254
|
+
const rootElement = editor.getRootElement();
|
|
255
|
+
const rootScrollParent = rootElement != null ? getScrollParent(rootElement, false) : document.body;
|
|
256
|
+
let ticking = false;
|
|
257
|
+
let previousIsInView = isTriggerVisibleInNearestScrollContainer(targetElement, rootScrollParent);
|
|
258
|
+
|
|
259
|
+
const handleScroll = function () {
|
|
260
|
+
if (!ticking) {
|
|
261
|
+
window.requestAnimationFrame(function () {
|
|
262
|
+
onReposition();
|
|
263
|
+
ticking = false;
|
|
264
|
+
});
|
|
265
|
+
ticking = true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const isInView = isTriggerVisibleInNearestScrollContainer(targetElement, rootScrollParent);
|
|
269
|
+
|
|
270
|
+
if (isInView !== previousIsInView) {
|
|
271
|
+
previousIsInView = isInView;
|
|
272
|
+
|
|
273
|
+
if (onVisibilityChange != null) {
|
|
274
|
+
onVisibilityChange(isInView);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const resizeObserver = new ResizeObserver(onReposition);
|
|
280
|
+
window.addEventListener('resize', onReposition);
|
|
281
|
+
document.addEventListener('scroll', handleScroll, {
|
|
282
|
+
capture: true,
|
|
283
|
+
passive: true
|
|
284
|
+
});
|
|
285
|
+
resizeObserver.observe(targetElement);
|
|
286
|
+
return () => {
|
|
287
|
+
resizeObserver.unobserve(targetElement);
|
|
288
|
+
window.removeEventListener('resize', onReposition);
|
|
289
|
+
document.removeEventListener('scroll', handleScroll);
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}, [targetElement, editor, onVisibilityChange, onReposition, resolution]);
|
|
293
|
+
}
|
|
294
|
+
const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND = lexical.createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
|
|
295
|
+
|
|
218
296
|
function LexicalPopoverMenu({
|
|
219
297
|
close,
|
|
220
298
|
editor,
|
|
221
|
-
|
|
299
|
+
anchorElementRef,
|
|
222
300
|
resolution,
|
|
223
301
|
options,
|
|
224
302
|
menuRenderFn,
|
|
@@ -228,7 +306,7 @@ function LexicalPopoverMenu({
|
|
|
228
306
|
React.useEffect(() => {
|
|
229
307
|
setHighlightedIndex(0);
|
|
230
308
|
}, [resolution.match.matchingString]);
|
|
231
|
-
const selectOptionAndCleanUp = React.useCallback(
|
|
309
|
+
const selectOptionAndCleanUp = React.useCallback(selectedEntry => {
|
|
232
310
|
editor.update(() => {
|
|
233
311
|
const textNodeContainingQuery = splitNodeContainingQuery(editor, resolution.match);
|
|
234
312
|
onSelectOption(selectedEntry, textNodeContainingQuery, close, resolution.match.matchingString);
|
|
@@ -258,6 +336,18 @@ function LexicalPopoverMenu({
|
|
|
258
336
|
updateSelectedIndex(0);
|
|
259
337
|
}
|
|
260
338
|
}, [options, selectedIndex, updateSelectedIndex]);
|
|
339
|
+
React.useEffect(() => {
|
|
340
|
+
return utils.mergeRegister(editor.registerCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, ({
|
|
341
|
+
option
|
|
342
|
+
}) => {
|
|
343
|
+
if (option.ref && option.ref.current != null) {
|
|
344
|
+
scrollIntoViewIfNeeded(option.ref.current);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return false;
|
|
349
|
+
}, lexical.COMMAND_PRIORITY_LOW));
|
|
350
|
+
}, [editor, updateSelectedIndex]);
|
|
261
351
|
React.useEffect(() => {
|
|
262
352
|
return utils.mergeRegister(editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, payload => {
|
|
263
353
|
const event = payload;
|
|
@@ -268,7 +358,10 @@ function LexicalPopoverMenu({
|
|
|
268
358
|
const option = options[newSelectedIndex];
|
|
269
359
|
|
|
270
360
|
if (option.ref != null && option.ref.current) {
|
|
271
|
-
|
|
361
|
+
editor.dispatchCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, {
|
|
362
|
+
index: newSelectedIndex,
|
|
363
|
+
option
|
|
364
|
+
});
|
|
272
365
|
}
|
|
273
366
|
|
|
274
367
|
event.preventDefault();
|
|
@@ -276,7 +369,7 @@ function LexicalPopoverMenu({
|
|
|
276
369
|
}
|
|
277
370
|
|
|
278
371
|
return true;
|
|
279
|
-
}, lexical.
|
|
372
|
+
}, lexical.COMMAND_PRIORITY_CRITICAL), editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, payload => {
|
|
280
373
|
const event = payload;
|
|
281
374
|
|
|
282
375
|
if (options !== null && options.length && selectedIndex !== null) {
|
|
@@ -293,13 +386,13 @@ function LexicalPopoverMenu({
|
|
|
293
386
|
}
|
|
294
387
|
|
|
295
388
|
return true;
|
|
296
|
-
}, lexical.
|
|
389
|
+
}, lexical.COMMAND_PRIORITY_CRITICAL), editor.registerCommand(lexical.KEY_ESCAPE_COMMAND, payload => {
|
|
297
390
|
const event = payload;
|
|
298
391
|
event.preventDefault();
|
|
299
392
|
event.stopImmediatePropagation();
|
|
300
393
|
close();
|
|
301
394
|
return true;
|
|
302
|
-
}, lexical.
|
|
395
|
+
}, lexical.COMMAND_PRIORITY_CRITICAL), editor.registerCommand(lexical.KEY_TAB_COMMAND, payload => {
|
|
303
396
|
const event = payload;
|
|
304
397
|
|
|
305
398
|
if (options === null || selectedIndex === null || options[selectedIndex] == null) {
|
|
@@ -310,7 +403,7 @@ function LexicalPopoverMenu({
|
|
|
310
403
|
event.stopImmediatePropagation();
|
|
311
404
|
selectOptionAndCleanUp(options[selectedIndex]);
|
|
312
405
|
return true;
|
|
313
|
-
}, lexical.
|
|
406
|
+
}, lexical.COMMAND_PRIORITY_CRITICAL), editor.registerCommand(lexical.KEY_ENTER_COMMAND, event => {
|
|
314
407
|
if (options === null || selectedIndex === null || options[selectedIndex] == null) {
|
|
315
408
|
return false;
|
|
316
409
|
}
|
|
@@ -322,14 +415,15 @@ function LexicalPopoverMenu({
|
|
|
322
415
|
|
|
323
416
|
selectOptionAndCleanUp(options[selectedIndex]);
|
|
324
417
|
return true;
|
|
325
|
-
}, lexical.
|
|
418
|
+
}, lexical.COMMAND_PRIORITY_CRITICAL));
|
|
326
419
|
}, [selectOptionAndCleanUp, close, editor, options, selectedIndex, updateSelectedIndex]);
|
|
327
420
|
const listItemProps = React.useMemo(() => ({
|
|
421
|
+
options,
|
|
328
422
|
selectOptionAndCleanUp,
|
|
329
423
|
selectedIndex,
|
|
330
424
|
setHighlightedIndex
|
|
331
|
-
}), [selectOptionAndCleanUp, selectedIndex]);
|
|
332
|
-
return menuRenderFn(
|
|
425
|
+
}), [selectOptionAndCleanUp, selectedIndex, options]);
|
|
426
|
+
return menuRenderFn(anchorElementRef, listItemProps, resolution.match.matchingString);
|
|
333
427
|
}
|
|
334
428
|
|
|
335
429
|
function useBasicTypeaheadTriggerMatch(trigger, {
|
|
@@ -358,50 +452,68 @@ function useBasicTypeaheadTriggerMatch(trigger, {
|
|
|
358
452
|
}, [maxLength, minLength, trigger]);
|
|
359
453
|
}
|
|
360
454
|
|
|
361
|
-
function
|
|
455
|
+
function useMenuAnchorRef(resolution, setResolution, className) {
|
|
362
456
|
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
363
457
|
const anchorElementRef = React.useRef(document.createElement('div'));
|
|
364
|
-
React.
|
|
458
|
+
const positionMenu = React.useCallback(() => {
|
|
365
459
|
const rootElement = editor.getRootElement();
|
|
460
|
+
const containerDiv = anchorElementRef.current;
|
|
461
|
+
|
|
462
|
+
if (rootElement !== null && resolution !== null) {
|
|
463
|
+
const {
|
|
464
|
+
left,
|
|
465
|
+
top,
|
|
466
|
+
width,
|
|
467
|
+
height
|
|
468
|
+
} = resolution.getRect();
|
|
469
|
+
containerDiv.style.top = `${top + window.pageYOffset}px`;
|
|
470
|
+
containerDiv.style.left = `${left + window.pageXOffset}px`;
|
|
471
|
+
containerDiv.style.height = `${height}px`;
|
|
472
|
+
containerDiv.style.width = `${width}px`;
|
|
473
|
+
|
|
474
|
+
if (!containerDiv.isConnected) {
|
|
475
|
+
if (className != null) {
|
|
476
|
+
containerDiv.className = className;
|
|
477
|
+
}
|
|
366
478
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
containerDiv.setAttribute('id', 'typeahead-menu');
|
|
371
|
-
containerDiv.setAttribute('role', 'listbox');
|
|
372
|
-
|
|
373
|
-
if (rootElement !== null && resolution !== null) {
|
|
374
|
-
const {
|
|
375
|
-
left,
|
|
376
|
-
top,
|
|
377
|
-
height
|
|
378
|
-
} = resolution.getRect();
|
|
379
|
-
containerDiv.style.top = `${top + height + 5 + window.pageYOffset}px`;
|
|
380
|
-
containerDiv.style.left = `${left + window.pageXOffset}px`;
|
|
479
|
+
containerDiv.setAttribute('aria-label', 'Typeahead menu');
|
|
480
|
+
containerDiv.setAttribute('id', 'typeahead-menu');
|
|
481
|
+
containerDiv.setAttribute('role', 'listbox');
|
|
381
482
|
containerDiv.style.display = 'block';
|
|
382
483
|
containerDiv.style.position = 'absolute';
|
|
383
|
-
|
|
384
|
-
if (!containerDiv.isConnected) {
|
|
385
|
-
document.body.append(containerDiv);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
anchorElementRef.current = containerDiv;
|
|
389
|
-
rootElement.setAttribute('aria-controls', 'typeahead-menu');
|
|
484
|
+
document.body.append(containerDiv);
|
|
390
485
|
}
|
|
486
|
+
|
|
487
|
+
anchorElementRef.current = containerDiv;
|
|
488
|
+
rootElement.setAttribute('aria-controls', 'typeahead-menu');
|
|
391
489
|
}
|
|
490
|
+
}, [editor, resolution, className]);
|
|
491
|
+
React.useEffect(() => {
|
|
492
|
+
const rootElement = editor.getRootElement();
|
|
392
493
|
|
|
393
494
|
if (resolution !== null) {
|
|
394
495
|
positionMenu();
|
|
395
|
-
window.addEventListener('resize', positionMenu);
|
|
396
496
|
return () => {
|
|
397
|
-
window.removeEventListener('resize', positionMenu);
|
|
398
|
-
|
|
399
497
|
if (rootElement !== null) {
|
|
400
498
|
rootElement.removeAttribute('aria-controls');
|
|
401
499
|
}
|
|
500
|
+
|
|
501
|
+
const containerDiv = anchorElementRef.current;
|
|
502
|
+
|
|
503
|
+
if (containerDiv !== null && containerDiv.isConnected) {
|
|
504
|
+
containerDiv.remove();
|
|
505
|
+
}
|
|
402
506
|
};
|
|
403
507
|
}
|
|
404
|
-
}, [editor, resolution]);
|
|
508
|
+
}, [editor, positionMenu, resolution]);
|
|
509
|
+
const onVisibilityChange = React.useCallback(isInView => {
|
|
510
|
+
if (resolution !== null) {
|
|
511
|
+
if (!isInView) {
|
|
512
|
+
setResolution(null);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}, [resolution, setResolution]);
|
|
516
|
+
useDynamicPositioning(resolution, anchorElementRef.current, positionMenu, onVisibilityChange);
|
|
405
517
|
return anchorElementRef;
|
|
406
518
|
}
|
|
407
519
|
|
|
@@ -409,15 +521,31 @@ function LexicalTypeaheadMenuPlugin({
|
|
|
409
521
|
options,
|
|
410
522
|
onQueryChange,
|
|
411
523
|
onSelectOption,
|
|
524
|
+
onOpen,
|
|
525
|
+
onClose,
|
|
412
526
|
menuRenderFn,
|
|
413
|
-
triggerFn
|
|
527
|
+
triggerFn,
|
|
528
|
+
anchorClassName
|
|
414
529
|
}) {
|
|
415
530
|
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
416
531
|
const [resolution, setResolution] = React.useState(null);
|
|
417
|
-
const anchorElementRef =
|
|
532
|
+
const anchorElementRef = useMenuAnchorRef(resolution, setResolution, anchorClassName);
|
|
533
|
+
const closeTypeahead = React.useCallback(() => {
|
|
534
|
+
setResolution(null);
|
|
535
|
+
|
|
536
|
+
if (onClose != null && resolution !== null) {
|
|
537
|
+
onClose();
|
|
538
|
+
}
|
|
539
|
+
}, [onClose, resolution]);
|
|
540
|
+
const openTypeahead = React.useCallback(res => {
|
|
541
|
+
setResolution(res);
|
|
542
|
+
|
|
543
|
+
if (onOpen != null && resolution === null) {
|
|
544
|
+
onOpen(res);
|
|
545
|
+
}
|
|
546
|
+
}, [onOpen, resolution]);
|
|
418
547
|
React.useEffect(() => {
|
|
419
548
|
let activeRange = document.createRange();
|
|
420
|
-
let previousText = null;
|
|
421
549
|
|
|
422
550
|
const updateListener = () => {
|
|
423
551
|
editor.getEditorState().read(() => {
|
|
@@ -425,12 +553,11 @@ function LexicalTypeaheadMenuPlugin({
|
|
|
425
553
|
const selection = lexical.$getSelection();
|
|
426
554
|
const text = getQueryTextForSearch(editor);
|
|
427
555
|
|
|
428
|
-
if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed() || text ===
|
|
429
|
-
|
|
556
|
+
if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed() || text === null || range === null) {
|
|
557
|
+
closeTypeahead();
|
|
430
558
|
return;
|
|
431
559
|
}
|
|
432
560
|
|
|
433
|
-
previousText = text;
|
|
434
561
|
const match = triggerFn(text, editor);
|
|
435
562
|
onQueryChange(match ? match.matchingString : null);
|
|
436
563
|
|
|
@@ -438,7 +565,7 @@ function LexicalTypeaheadMenuPlugin({
|
|
|
438
565
|
const isRangePositioned = tryToPositionRange(match.leadOffset, range);
|
|
439
566
|
|
|
440
567
|
if (isRangePositioned !== null) {
|
|
441
|
-
startTransition(() =>
|
|
568
|
+
startTransition(() => openTypeahead({
|
|
442
569
|
getRect: () => range.getBoundingClientRect(),
|
|
443
570
|
match
|
|
444
571
|
}));
|
|
@@ -446,7 +573,7 @@ function LexicalTypeaheadMenuPlugin({
|
|
|
446
573
|
}
|
|
447
574
|
}
|
|
448
575
|
|
|
449
|
-
|
|
576
|
+
closeTypeahead();
|
|
450
577
|
});
|
|
451
578
|
};
|
|
452
579
|
|
|
@@ -455,15 +582,12 @@ function LexicalTypeaheadMenuPlugin({
|
|
|
455
582
|
activeRange = null;
|
|
456
583
|
removeUpdateListener();
|
|
457
584
|
};
|
|
458
|
-
}, [editor, triggerFn, onQueryChange, resolution]);
|
|
459
|
-
const closeTypeahead = React.useCallback(() => {
|
|
460
|
-
setResolution(null);
|
|
461
|
-
}, []);
|
|
585
|
+
}, [editor, triggerFn, onQueryChange, resolution, closeTypeahead, openTypeahead]);
|
|
462
586
|
return resolution === null || editor === null ? null : /*#__PURE__*/React.createElement(LexicalPopoverMenu, {
|
|
463
587
|
close: closeTypeahead,
|
|
464
588
|
resolution: resolution,
|
|
465
589
|
editor: editor,
|
|
466
|
-
|
|
590
|
+
anchorElementRef: anchorElementRef,
|
|
467
591
|
options: options,
|
|
468
592
|
menuRenderFn: menuRenderFn,
|
|
469
593
|
onSelectOption: onSelectOption
|
|
@@ -473,12 +597,28 @@ function LexicalNodeMenuPlugin({
|
|
|
473
597
|
options,
|
|
474
598
|
nodeKey,
|
|
475
599
|
onClose,
|
|
600
|
+
onOpen,
|
|
476
601
|
onSelectOption,
|
|
477
|
-
menuRenderFn
|
|
602
|
+
menuRenderFn,
|
|
603
|
+
anchorClassName
|
|
478
604
|
}) {
|
|
479
605
|
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
480
606
|
const [resolution, setResolution] = React.useState(null);
|
|
481
|
-
const anchorElementRef =
|
|
607
|
+
const anchorElementRef = useMenuAnchorRef(resolution, setResolution, anchorClassName);
|
|
608
|
+
const closeNodeMenu = React.useCallback(() => {
|
|
609
|
+
setResolution(null);
|
|
610
|
+
|
|
611
|
+
if (onClose != null && resolution !== null) {
|
|
612
|
+
onClose();
|
|
613
|
+
}
|
|
614
|
+
}, [onClose, resolution]);
|
|
615
|
+
const openNodeMenu = React.useCallback(res => {
|
|
616
|
+
setResolution(res);
|
|
617
|
+
|
|
618
|
+
if (onOpen != null && resolution === null) {
|
|
619
|
+
onOpen(res);
|
|
620
|
+
}
|
|
621
|
+
}, [onOpen, resolution]);
|
|
482
622
|
React.useEffect(() => {
|
|
483
623
|
if (nodeKey && resolution == null) {
|
|
484
624
|
editor.update(() => {
|
|
@@ -487,7 +627,7 @@ function LexicalNodeMenuPlugin({
|
|
|
487
627
|
|
|
488
628
|
if (node != null && domElement != null) {
|
|
489
629
|
const text = node.getTextContent();
|
|
490
|
-
startTransition(() =>
|
|
630
|
+
startTransition(() => openNodeMenu({
|
|
491
631
|
getRect: () => domElement.getBoundingClientRect(),
|
|
492
632
|
match: {
|
|
493
633
|
leadOffset: text.length,
|
|
@@ -498,14 +638,14 @@ function LexicalNodeMenuPlugin({
|
|
|
498
638
|
}
|
|
499
639
|
});
|
|
500
640
|
} else if (nodeKey == null && resolution != null) {
|
|
501
|
-
|
|
641
|
+
closeNodeMenu();
|
|
502
642
|
}
|
|
503
|
-
}, [editor, nodeKey, resolution]);
|
|
643
|
+
}, [closeNodeMenu, editor, nodeKey, openNodeMenu, resolution]);
|
|
504
644
|
return resolution === null || editor === null ? null : /*#__PURE__*/React.createElement(LexicalPopoverMenu, {
|
|
505
|
-
close:
|
|
645
|
+
close: closeNodeMenu,
|
|
506
646
|
resolution: resolution,
|
|
507
647
|
editor: editor,
|
|
508
|
-
|
|
648
|
+
anchorElementRef: anchorElementRef,
|
|
509
649
|
options: options,
|
|
510
650
|
menuRenderFn: menuRenderFn,
|
|
511
651
|
onSelectOption: onSelectOption
|
|
@@ -515,5 +655,8 @@ function LexicalNodeMenuPlugin({
|
|
|
515
655
|
exports.LexicalNodeMenuPlugin = LexicalNodeMenuPlugin;
|
|
516
656
|
exports.LexicalTypeaheadMenuPlugin = LexicalTypeaheadMenuPlugin;
|
|
517
657
|
exports.PUNCTUATION = PUNCTUATION;
|
|
658
|
+
exports.SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND = SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND;
|
|
518
659
|
exports.TypeaheadOption = TypeaheadOption;
|
|
660
|
+
exports.getScrollParent = getScrollParent;
|
|
519
661
|
exports.useBasicTypeaheadTriggerMatch = useBasicTypeaheadTriggerMatch;
|
|
662
|
+
exports.useDynamicPositioning = useDynamicPositioning;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @flow strict
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {LexicalEditor, NodeKey, TextNode} from 'lexical';
|
|
10
|
+
import type {LexicalCommand, LexicalEditor, NodeKey, TextNode} from 'lexical';
|
|
11
11
|
import * as React from 'react';
|
|
12
12
|
|
|
13
13
|
export type QueryMatch = {
|
|
@@ -26,40 +26,33 @@ export const PUNCTUATION: string =
|
|
|
26
26
|
|
|
27
27
|
declare export class TypeaheadOption {
|
|
28
28
|
key: string;
|
|
29
|
-
ref:
|
|
29
|
+
ref: {current: HTMLElement | null};
|
|
30
30
|
constructor(key: string): void;
|
|
31
31
|
setRefElement(element: HTMLElement | null): void;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
declare export var SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
|
|
35
|
+
index: number,
|
|
36
|
+
option: TypeaheadOption,
|
|
37
|
+
}>;
|
|
38
|
+
|
|
34
39
|
export type MenuRenderFn<TOption> = (
|
|
35
|
-
|
|
40
|
+
anchorElementRef: {current: HTMLElement | null},
|
|
36
41
|
itemProps: {
|
|
37
42
|
selectedIndex: number | null,
|
|
38
43
|
selectOptionAndCleanUp: (option: TOption) => void,
|
|
39
44
|
setHighlightedIndex: (index: number) => void,
|
|
45
|
+
options: Array<TOption>,
|
|
40
46
|
},
|
|
41
47
|
matchingString: string,
|
|
42
48
|
) => React.Portal | React.MixedElement | null;
|
|
43
49
|
|
|
44
|
-
const scrollIntoViewIfNeeded = (target: HTMLElement) => {
|
|
45
|
-
const container = document.getElementById('typeahead-menu');
|
|
46
|
-
if (container) {
|
|
47
|
-
const containerRect = container.getBoundingClientRect();
|
|
48
|
-
const targetRect = target.getBoundingClientRect();
|
|
49
|
-
if (targetRect.bottom > containerRect.bottom) {
|
|
50
|
-
target.scrollIntoView(false);
|
|
51
|
-
} else if (targetRect.top < containerRect.top) {
|
|
52
|
-
target.scrollIntoView();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
50
|
declare export function useBasicTypeaheadTriggerMatch(
|
|
58
51
|
trigger: string,
|
|
59
52
|
options: {minLength?: number, maxLength?: number},
|
|
60
53
|
): TriggerFn;
|
|
61
54
|
|
|
62
|
-
export type
|
|
55
|
+
export type TypeaheadMenuPluginProps<TOption> = {
|
|
63
56
|
onQueryChange: (matchingString: string | null) => void,
|
|
64
57
|
onSelectOption: (
|
|
65
58
|
option: TOption,
|
|
@@ -70,6 +63,9 @@ export type TypeaheadMenuPluginArgs<TOption> = {
|
|
|
70
63
|
options: Array<TOption>,
|
|
71
64
|
menuRenderFn: MenuRenderFn<TOption>,
|
|
72
65
|
triggerFn: TriggerFn,
|
|
66
|
+
onOpen?: (resolution: Resolution) => void,
|
|
67
|
+
onClose?: () => void,
|
|
68
|
+
anchorClassName?: string,
|
|
73
69
|
};
|
|
74
70
|
|
|
75
71
|
export type TriggerFn = (
|
|
@@ -78,10 +74,10 @@ export type TriggerFn = (
|
|
|
78
74
|
) => QueryMatch | null;
|
|
79
75
|
|
|
80
76
|
declare export function LexicalTypeaheadMenuPlugin<TOption>(
|
|
81
|
-
options:
|
|
77
|
+
options: TypeaheadMenuPluginProps<TOption>,
|
|
82
78
|
): React.MixedElement | null;
|
|
83
79
|
|
|
84
|
-
type
|
|
80
|
+
type NodeMenuPluginProps<TOption> = {
|
|
85
81
|
onSelectOption: (
|
|
86
82
|
option: TOption,
|
|
87
83
|
textNodeContainingQuery: TextNode | null,
|
|
@@ -90,10 +86,12 @@ type NodeMenuPluginArgs<TOption> = {
|
|
|
90
86
|
) => void,
|
|
91
87
|
options: Array<TOption>,
|
|
92
88
|
nodeKey: NodeKey | null,
|
|
93
|
-
onClose
|
|
89
|
+
onClose?: () => void,
|
|
90
|
+
onOpen?: (resolution: Resolution) => void,
|
|
94
91
|
menuRenderFn: MenuRenderFn<TOption>,
|
|
92
|
+
anchorClassName?: string,
|
|
95
93
|
};
|
|
96
94
|
|
|
97
95
|
declare export function LexicalNodeMenuPlugin<TOption>(
|
|
98
|
-
options:
|
|
96
|
+
options: NodeMenuPluginProps<TOption>,
|
|
99
97
|
): React.MixedElement | null;
|