History

fix(web): paint text selection over composer chips (#4139)

Yordis Prieto committed 3 months ago

Commit c7101671 parent a135f2c2 files · +93 −3Browse files

Unified · Split

2 files
  1. apps/web/src/components/ComposerPromptEditor.tsx +81 −3
  2. apps/web/src/index.css +12 −0
apps/web/src/components/ComposerPromptEditor.tsx+81−3
@@ -28,7 +28,10 @@ import { · 27 unmodified lines
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> { · 150 unmodified lines
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> { · 131 unmodified lines
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> { · 64 unmodified lines
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() { · 766 unmodified lines
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 */
1179function 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
1170function ComposerInlineTokenPastePlugin() {1247function ComposerInlineTokenPastePlugin() {
1171 const [editor] = useLexicalComposerContext();1248 const [editor] = useLexicalComposerContext();
1172 1249
@@ -1701,6 +1778,7 @@ function ComposerPromptEditorInner({ · 528 unmodified lines
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>
apps/web/src/index.css+12−0
@@ -1067,3 +1067,15 @@ label:has(> select#reasoning-effort) select { · 1066 unmodified lines
1067.dark .model-picker-list::-webkit-scrollbar-thumb:hover {1067.dark .model-picker-list::-webkit-scrollbar-thumb:hover {
1068 background: rgba(255, 255, 255, 0.15);1068 background: rgba(255, 255, 255, 0.15);
1069}1069}
1070
1071/* Composer chips are non-editable decorators, so the browser skips them when
1072 painting text selection; this overlay stands in for the native highlight. */
1073.composer-inline-chip[data-composer-chip-selected]::after {
1074 content: "";
1075 position: absolute;
1076 inset: 0;
1077 border-radius: 6px;
1078 background-color: Highlight;
1079 opacity: 0.3;
1080 pointer-events: none;
1081}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t