Skip to content

Commit 20455f4

Browse files
committed
fix(richtext-lexical): floating link editor did not properly hide if selection is not a range selection
1 parent 9f37bf7 commit 20455f4

File tree

1 file changed

+84
-80
lines changed
  • packages/richtext-lexical/src/field/features/link/plugins/floatingLinkEditor/LinkEditor

1 file changed

+84
-80
lines changed

packages/richtext-lexical/src/field/features/link/plugins/floatingLinkEditor/LinkEditor/index.tsx

Lines changed: 84 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -64,87 +64,99 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
6464
depth: editDepth,
6565
})
6666

67+
const setNotLink = useCallback(() => {
68+
setIsLink(false)
69+
if (editorRef && editorRef.current) {
70+
editorRef.current.style.opacity = '0'
71+
editorRef.current.style.transform = 'translate(-10000px, -10000px)'
72+
}
73+
setIsAutoLink(false)
74+
setLinkUrl(null)
75+
setLinkLabel(null)
76+
setSelectedNodes([])
77+
}, [setIsLink, setLinkUrl, setLinkLabel, setSelectedNodes])
78+
6779
const updateLinkEditor = useCallback(() => {
6880
const selection = $getSelection()
6981
let selectedNodeDomRect: DOMRect | undefined = null
7082

83+
if (!$isRangeSelection(selection) || !selection) {
84+
setNotLink()
85+
return
86+
}
87+
7188
// Handle the data displayed in the floating link editor & drawer when you click on a link node
72-
if ($isRangeSelection(selection)) {
73-
const focusNode = getSelectedNode(selection)
74-
selectedNodeDomRect = editor.getElementByKey(focusNode.getKey())?.getBoundingClientRect()
75-
const focusLinkParent: LinkNode = $findMatchingParent(focusNode, $isLinkNode)
76-
77-
// Prevent link modal from showing if selection spans further than the link: https://github.com/facebook/lexical/issues/4064
78-
const badNode = selection
79-
.getNodes()
80-
.filter((node) => !$isLineBreakNode(node))
81-
.find((node) => {
82-
const linkNode = $findMatchingParent(node, $isLinkNode)
83-
return (
84-
(focusLinkParent && !focusLinkParent.is(linkNode)) ||
85-
(linkNode && !linkNode.is(focusLinkParent))
86-
)
87-
})
8889

89-
if (focusLinkParent == null || badNode) {
90-
setIsLink(false)
91-
setIsAutoLink(false)
92-
setLinkUrl(null)
93-
setLinkLabel(null)
94-
setSelectedNodes([])
95-
return
96-
}
90+
const focusNode = getSelectedNode(selection)
91+
selectedNodeDomRect = editor.getElementByKey(focusNode.getKey())?.getBoundingClientRect()
92+
const focusLinkParent: LinkNode = $findMatchingParent(focusNode, $isLinkNode)
93+
94+
// Prevent link modal from showing if selection spans further than the link: https://github.com/facebook/lexical/issues/4064
95+
const badNode = selection
96+
.getNodes()
97+
.filter((node) => !$isLineBreakNode(node))
98+
.find((node) => {
99+
const linkNode = $findMatchingParent(node, $isLinkNode)
100+
return (
101+
(focusLinkParent && !focusLinkParent.is(linkNode)) ||
102+
(linkNode && !linkNode.is(focusLinkParent))
103+
)
104+
})
97105

98-
// Initial state:
99-
const data: LinkFields & { text: string } = {
100-
doc: undefined,
101-
linkType: undefined,
102-
newTab: undefined,
103-
url: '',
104-
...focusLinkParent.getFields(),
105-
text: focusLinkParent.getTextContent(),
106-
}
106+
if (focusLinkParent == null || badNode) {
107+
setNotLink()
108+
return
109+
}
107110

108-
if (focusLinkParent.getFields()?.linkType === 'custom') {
109-
setLinkUrl(focusLinkParent.getFields()?.url ?? null)
110-
setLinkLabel(null)
111-
} else {
112-
// internal link
113-
setLinkUrl(
114-
`/admin/collections/${focusLinkParent.getFields()?.doc?.relationTo}/${
115-
focusLinkParent.getFields()?.doc?.value
116-
}`,
117-
)
111+
// Initial state:
112+
const data: LinkFields & { text: string } = {
113+
doc: undefined,
114+
linkType: undefined,
115+
newTab: undefined,
116+
url: '',
117+
...focusLinkParent.getFields(),
118+
text: focusLinkParent.getTextContent(),
119+
}
118120

119-
const relatedField = config.collections.find(
120-
(coll) => coll.slug === focusLinkParent.getFields()?.doc?.relationTo,
121+
if (focusLinkParent.getFields()?.linkType === 'custom') {
122+
setLinkUrl(focusLinkParent.getFields()?.url ?? null)
123+
setLinkLabel(null)
124+
} else {
125+
// internal link
126+
setLinkUrl(
127+
`/admin/collections/${focusLinkParent.getFields()?.doc?.relationTo}/${
128+
focusLinkParent.getFields()?.doc?.value
129+
}`,
130+
)
131+
132+
const relatedField = config.collections.find(
133+
(coll) => coll.slug === focusLinkParent.getFields()?.doc?.relationTo,
134+
)
135+
if (!relatedField) {
136+
// Usually happens if the user removed all default fields. In this case, we let them specify the label or do not display the label at all.
137+
// label could be a virtual field the user added. This is useful if they want to use the link feature for things other than links.
138+
setLinkLabel(
139+
focusLinkParent.getFields()?.label ? String(focusLinkParent.getFields()?.label) : null,
140+
)
141+
setLinkUrl(
142+
focusLinkParent.getFields()?.url ? String(focusLinkParent.getFields()?.url) : null,
121143
)
122-
if (!relatedField) {
123-
// Usually happens if the user removed all default fields. In this case, we let them specify the label or do not display the label at all.
124-
// label could be a virtual field the user added. This is useful if they want to use the link feature for things other than links.
125-
setLinkLabel(
126-
focusLinkParent.getFields()?.label ? String(focusLinkParent.getFields()?.label) : null,
127-
)
128-
setLinkUrl(
129-
focusLinkParent.getFields()?.url ? String(focusLinkParent.getFields()?.url) : null,
130-
)
131-
} else {
132-
const label = t('fields:linkedTo', {
133-
label: getTranslation(relatedField.labels.singular, i18n),
134-
}).replace(/<[^>]*>?/g, '')
135-
setLinkLabel(label)
136-
}
144+
} else {
145+
const label = t('fields:linkedTo', {
146+
label: getTranslation(relatedField.labels.singular, i18n),
147+
}).replace(/<[^>]*>?/g, '')
148+
setLinkLabel(label)
137149
}
150+
}
138151

139-
setStateData(data)
140-
setIsLink(true)
141-
setSelectedNodes(selection ? selection?.getNodes() : [])
152+
setStateData(data)
153+
setIsLink(true)
154+
setSelectedNodes(selection ? selection?.getNodes() : [])
142155

143-
if ($isAutoLinkNode(focusLinkParent)) {
144-
setIsAutoLink(true)
145-
} else {
146-
setIsAutoLink(false)
147-
}
156+
if ($isAutoLinkNode(focusLinkParent)) {
157+
setIsAutoLink(true)
158+
} else {
159+
setIsAutoLink(false)
148160
}
149161

150162
const editorElem = editorRef.current
@@ -158,7 +170,6 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
158170
const rootElement = editor.getRootElement()
159171

160172
if (
161-
selection !== null &&
162173
nativeSelection !== null &&
163174
rootElement !== null &&
164175
rootElement.contains(nativeSelection.anchorNode)
@@ -182,7 +193,7 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
182193
}
183194

184195
return true
185-
}, [anchorElem, editor, config, t, i18n])
196+
}, [editor, setNotLink, config.collections, t, i18n, anchorElem])
186197

187198
useEffect(() => {
188199
return mergeRegister(
@@ -202,13 +213,6 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
202213
)
203214
}, [editor, updateLinkEditor, toggleModal, drawerSlug])
204215

205-
useEffect(() => {
206-
if (!isLink && editorRef) {
207-
editorRef.current.style.opacity = '0'
208-
editorRef.current.style.transform = 'translate(-10000px, -10000px)'
209-
}
210-
}, [isLink])
211-
212216
useEffect(() => {
213217
const scrollerElem = anchorElem.parentElement
214218

@@ -253,16 +257,16 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
253257
KEY_ESCAPE_COMMAND,
254258
() => {
255259
if (isLink) {
256-
setIsLink(false)
257-
setIsAutoLink(false)
260+
setNotLink()
261+
258262
return true
259263
}
260264
return false
261265
},
262266
COMMAND_PRIORITY_HIGH,
263267
),
264268
)
265-
}, [editor, updateLinkEditor, setIsLink, isLink])
269+
}, [editor, updateLinkEditor, isLink, setNotLink])
266270

267271
useEffect(() => {
268272
editor.getEditorState().read(() => {

0 commit comments

Comments
 (0)