| ... | @@ -28,7 +28,10 @@ import { | ... | @@ -28,7 +28,10 @@ import { |
| 28 | KEY_ENTER_COMMAND, | 28 | KEY_ENTER_COMMAND, |
| 29 | KEY_TAB_COMMAND, | 29 | KEY_TAB_COMMAND, |
| 30 | COMMAND_PRIORITY_HIGH, | 30 | COMMAND_PRIORITY_HIGH, |
| | 31 | COMMAND_PRIORITY_LOW, |
| 31 | KEY_BACKSPACE_COMMAND, | 32 | KEY_BACKSPACE_COMMAND, |
| | 33 | BLUR_COMMAND, |
| | 34 | FOCUS_COMMAND, |
| 32 | $getRoot, | 35 | $getRoot, |
| 33 | HISTORY_MERGE_TAG, | 36 | HISTORY_MERGE_TAG, |
| 34 | DecoratorNode, | 37 | DecoratorNode, |
| ... | @@ -185,7 +188,7 @@ class ComposerMentionNode extends DecoratorNode<React.ReactElement> { | ... | @@ -185,7 +188,7 @@ class ComposerMentionNode extends DecoratorNode<React.ReactElement> { |
| 185 | | 188 | |
| 186 | override createDOM(): HTMLElement { | 189 | override createDOM(): HTMLElement { |
| 187 | const dom = document.createElement("span"); | 190 | const dom = document.createElement("span"); |
| 188 | dom.className = "inline-flex align-middle leading-none"; | 191 | dom.className = "composer-inline-chip relative inline-flex align-middle leading-none"; |
| 189 | return dom; | 192 | return dom; |
| 190 | } | 193 | } |
| 191 | | 194 | |
| ... | @@ -323,7 +326,7 @@ class ComposerSkillNode extends DecoratorNode<React.ReactElement> { | ... | @@ -323,7 +326,7 @@ class ComposerSkillNode extends DecoratorNode<React.ReactElement> { |
| 323 | | 326 | |
| 324 | override createDOM(): HTMLElement { | 327 | override createDOM(): HTMLElement { |
| 325 | const dom = document.createElement("span"); | 328 | const dom = document.createElement("span"); |
| 326 | dom.className = "inline-flex align-middle leading-none"; | 329 | dom.className = "composer-inline-chip relative inline-flex align-middle leading-none"; |
| 327 | return dom; | 330 | return dom; |
| 328 | } | 331 | } |
| 329 | | 332 | |
| ... | @@ -394,7 +397,7 @@ class ComposerTerminalContextNode extends DecoratorNode<React.ReactElement> { | ... | @@ -394,7 +397,7 @@ class ComposerTerminalContextNode extends DecoratorNode<React.ReactElement> { |
| 394 | | 397 | |
| 395 | override createDOM(): HTMLElement { | 398 | override createDOM(): HTMLElement { |
| 396 | const dom = document.createElement("span"); | 399 | const dom = document.createElement("span"); |
| 397 | dom.className = "inline-flex align-middle leading-none"; | 400 | dom.className = "composer-inline-chip relative inline-flex align-middle leading-none"; |
| 398 | return dom; | 401 | return dom; |
| 399 | } | 402 | } |
| 400 | | 403 | |
| ... | @@ -1167,6 +1170,80 @@ function ComposerInlineTokenBackspacePlugin() { | ... | @@ -1167,6 +1170,80 @@ function ComposerInlineTokenBackspacePlugin() { |
| 1167 | return null; | 1170 | return null; |
| 1168 | } | 1171 | } |
| 1169 | | 1172 | |
| | 1173 | /** |
| | 1174 | * Chips render as non-editable decorators, so the browser never paints the |
| | 1175 | * native text selection over them; without help, a selection spanning chips |
| | 1176 | * is only visible in the slivers between them. Mirror the selection onto the |
| | 1177 | * chips with a data attribute the stylesheet turns into a highlight overlay. |
| | 1178 | */ |
| | 1179 | function ComposerChipSelectionPlugin() { |
| | 1180 | const [editor] = useLexicalComposerContext(); |
| | 1181 | |
| | 1182 | useEffect(() => { |
| | 1183 | let selectedKeys = new Set<string>(); |
| | 1184 | // Lexical keeps the range selection on blur without emitting an update, |
| | 1185 | // so focus is tracked separately; while blurred the native highlight is |
| | 1186 | // gone and the mirrored one has to go with it. |
| | 1187 | let hasFocus = editor.getRootElement() === document.activeElement; |
| | 1188 | |
| | 1189 | const applyKeys = (nextKeys: Set<string>) => { |
| | 1190 | for (const key of selectedKeys) { |
| | 1191 | if (!nextKeys.has(key)) { |
| | 1192 | editor.getElementByKey(key)?.removeAttribute("data-composer-chip-selected"); |
| | 1193 | } |
| | 1194 | } |
| | 1195 | for (const key of nextKeys) { |
| | 1196 | editor.getElementByKey(key)?.setAttribute("data-composer-chip-selected", "true"); |
| | 1197 | } |
| | 1198 | selectedKeys = nextKeys; |
| | 1199 | }; |
| | 1200 | |
| | 1201 | const readSelectedKeys = () => { |
| | 1202 | const nextKeys = new Set<string>(); |
| | 1203 | editor.getEditorState().read(() => { |
| | 1204 | const selection = $getSelection(); |
| | 1205 | if ($isRangeSelection(selection) && !selection.isCollapsed()) { |
| | 1206 | for (const node of selection.getNodes()) { |
| | 1207 | if (node instanceof DecoratorNode) { |
| | 1208 | nextKeys.add(node.getKey()); |
| | 1209 | } |
| | 1210 | } |
| | 1211 | } |
| | 1212 | }); |
| | 1213 | return nextKeys; |
| | 1214 | }; |
| | 1215 | |
| | 1216 | const unregisterUpdate = editor.registerUpdateListener(() => { |
| | 1217 | applyKeys(hasFocus ? readSelectedKeys() : new Set()); |
| | 1218 | }); |
| | 1219 | const unregisterFocus = editor.registerCommand( |
| | 1220 | FOCUS_COMMAND, |
| | 1221 | () => { |
| | 1222 | hasFocus = true; |
| | 1223 | applyKeys(readSelectedKeys()); |
| | 1224 | return false; |
| | 1225 | }, |
| | 1226 | COMMAND_PRIORITY_LOW, |
| | 1227 | ); |
| | 1228 | const unregisterBlur = editor.registerCommand( |
| | 1229 | BLUR_COMMAND, |
| | 1230 | () => { |
| | 1231 | hasFocus = false; |
| | 1232 | applyKeys(new Set()); |
| | 1233 | return false; |
| | 1234 | }, |
| | 1235 | COMMAND_PRIORITY_LOW, |
| | 1236 | ); |
| | 1237 | return () => { |
| | 1238 | unregisterUpdate(); |
| | 1239 | unregisterFocus(); |
| | 1240 | unregisterBlur(); |
| | 1241 | }; |
| | 1242 | }, [editor]); |
| | 1243 | |
| | 1244 | return null; |
| | 1245 | } |
| | 1246 | |
| 1170 | function ComposerInlineTokenPastePlugin() { | 1247 | function ComposerInlineTokenPastePlugin() { |
| 1171 | const [editor] = useLexicalComposerContext(); | 1248 | const [editor] = useLexicalComposerContext(); |
| 1172 | | 1249 | |
| ... | @@ -1701,6 +1778,7 @@ function ComposerPromptEditorInner({ | ... | @@ -1701,6 +1778,7 @@ function ComposerPromptEditorInner({ |
| 1701 | <ComposerInlineTokenSelectionNormalizePlugin /> | 1778 | <ComposerInlineTokenSelectionNormalizePlugin /> |
| 1702 | <ComposerInlineTokenBackspacePlugin /> | 1779 | <ComposerInlineTokenBackspacePlugin /> |
| 1703 | <ComposerInlineTokenPastePlugin /> | 1780 | <ComposerInlineTokenPastePlugin /> |
| | 1781 | <ComposerChipSelectionPlugin /> |
| 1704 | <HistoryPlugin /> | 1782 | <HistoryPlugin /> |
| 1705 | </div> | 1783 | </div> |
| 1706 | </ComposerTerminalContextActionsContext> | 1784 | </ComposerTerminalContextActionsContext> |