Skip to content

Commit dcf444f

Browse files
authored
fix(web): paint text selection over composer chips (#5)
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent e8e4c61 commit dcf444f

4 files changed

Lines changed: 79 additions & 3 deletions

File tree

apps/web/src/components/ComposerPromptEditor.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ class ComposerMentionNode extends DecoratorNode<React.ReactElement> {
184184

185185
override createDOM(): HTMLElement {
186186
const dom = document.createElement("span");
187-
dom.className = "inline-flex align-middle leading-none";
187+
dom.className = "composer-inline-chip relative inline-flex align-middle leading-none";
188188
return dom;
189189
}
190190

@@ -322,7 +322,7 @@ class ComposerSkillNode extends DecoratorNode<React.ReactElement> {
322322

323323
override createDOM(): HTMLElement {
324324
const dom = document.createElement("span");
325-
dom.className = "inline-flex align-middle leading-none";
325+
dom.className = "composer-inline-chip relative inline-flex align-middle leading-none";
326326
return dom;
327327
}
328328

@@ -393,7 +393,7 @@ class ComposerTerminalContextNode extends DecoratorNode<React.ReactElement> {
393393

394394
override createDOM(): HTMLElement {
395395
const dom = document.createElement("span");
396-
dom.className = "inline-flex align-middle leading-none";
396+
dom.className = "composer-inline-chip relative inline-flex align-middle leading-none";
397397
return dom;
398398
}
399399

@@ -1119,6 +1119,44 @@ function ComposerInlineTokenBackspacePlugin() {
11191119
return null;
11201120
}
11211121

1122+
/**
1123+
* Chips render as non-editable decorators, so the browser never paints the
1124+
* native text selection over them; without help, a selection spanning chips
1125+
* is only visible in the slivers between them. Mirror the selection onto the
1126+
* chips with a data attribute the stylesheet turns into a highlight overlay.
1127+
*/
1128+
function ComposerChipSelectionPlugin() {
1129+
const [editor] = useLexicalComposerContext();
1130+
1131+
useEffect(() => {
1132+
let selectedKeys = new Set<string>();
1133+
return editor.registerUpdateListener(() => {
1134+
const nextKeys = new Set<string>();
1135+
editor.getEditorState().read(() => {
1136+
const selection = $getSelection();
1137+
if ($isRangeSelection(selection) && !selection.isCollapsed()) {
1138+
for (const node of selection.getNodes()) {
1139+
if (node instanceof DecoratorNode) {
1140+
nextKeys.add(node.getKey());
1141+
}
1142+
}
1143+
}
1144+
});
1145+
for (const key of selectedKeys) {
1146+
if (!nextKeys.has(key)) {
1147+
editor.getElementByKey(key)?.removeAttribute("data-composer-chip-selected");
1148+
}
1149+
}
1150+
for (const key of nextKeys) {
1151+
editor.getElementByKey(key)?.setAttribute("data-composer-chip-selected", "true");
1152+
}
1153+
selectedKeys = nextKeys;
1154+
});
1155+
}, [editor]);
1156+
1157+
return null;
1158+
}
1159+
11221160
function ComposerInlineTokenPastePlugin() {
11231161
const [editor] = useLexicalComposerContext();
11241162

@@ -1711,6 +1749,7 @@ function ComposerPromptEditorInner({
17111749
<ComposerInlineTokenSelectionNormalizePlugin />
17121750
<ComposerInlineTokenBackspacePlugin />
17131751
<ComposerInlineTokenPastePlugin />
1752+
<ComposerChipSelectionPlugin />
17141753
<HistoryPlugin />
17151754
</div>
17161755
</ComposerTerminalContextActionsContext>

apps/web/src/index.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,3 +954,15 @@ label:has(> select#reasoning-effort) select {
954954
.dark .model-picker-list::-webkit-scrollbar-thumb:hover {
955955
background: rgba(255, 255, 255, 0.15);
956956
}
957+
958+
/* Composer chips are non-editable decorators, so the browser skips them when
959+
painting text selection; this overlay stands in for the native highlight. */
960+
.composer-inline-chip[data-composer-chip-selected]::after {
961+
content: "";
962+
position: absolute;
963+
inset: 0;
964+
border-radius: 6px;
965+
background-color: Highlight;
966+
opacity: 0.3;
967+
pointer-events: none;
968+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# 0005: Selection highlight over composer chips
2+
3+
- PR: [TrogonStack/t3code#5](https://github.com/TrogonStack/t3code/pull/5)
4+
- Status: active
5+
6+
## What you can do now
7+
8+
- Selecting text in the composer now visibly covers file, folder, skill, and
9+
terminal-context chips: each chip inside the selection gets the same
10+
highlight tint as the surrounding text, in the system selection color.
11+
- The highlight tracks the selection live: partial selections cover only the
12+
chips inside the range, and collapsing the selection clears it.
13+
14+
## Why
15+
16+
Chips are non-editable islands, so the browser paints no selection over them;
17+
a selection spanning several mentions was only visible in the slivers between
18+
chips, making it impossible to tell what a copy, cut, or delete was about to
19+
act on.
20+
21+
## Upstream considerations
22+
23+
Self-contained editor polish with no dependency on other fork divergences;
24+
straightforward to propose upstream as is.

docs/fork/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ Each entry uses these sections:
3535
| 0002 | [Draft hero landing on the index route](./0002-draft-hero-landing.md) | [#2](https://github.com/TrogonStack/t3code/pull/2) | active |
3636
| 0003 | [Native subagent threads for Claude orchestrators](./0003-native-subagent-threads.md) | [#3](https://github.com/TrogonStack/t3code/pull/3) | active |
3737
| 0004 | [Drag files from the explorer into the chat](./0004-file-tree-drag-to-chat.md) | [#4](https://github.com/TrogonStack/t3code/pull/4) | active |
38+
| 0005 | [Selection highlight over composer chips](./0005-composer-chip-selection-highlight.md) | [#5](https://github.com/TrogonStack/t3code/pull/5) | active |

0 commit comments

Comments
 (0)