Skip to content

Commit 228a732

Browse files
Fix empty task caret alignment
1 parent 1302e33 commit 228a732

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

app/src/features/editor/extensions/list-advanced-rendering.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,24 @@ describe("Advanced list rendering", () => {
208208

209209
view.destroy();
210210
});
211+
212+
it("adds an invisible baseline placeholder for empty task lines only", async () => {
213+
const { view } = createView(["- [ ] ", "- [ ] Task item"].join("\n"));
214+
215+
await flush();
216+
217+
const taskLines = [
218+
...view.dom.querySelectorAll<HTMLElement>(".cm-line.cm-md-task-list"),
219+
];
220+
expect(taskLines).toHaveLength(2);
221+
expect(
222+
taskLines[0]?.querySelector(".cm-md-task-empty-placeholder"),
223+
).not.toBeNull();
224+
expect(
225+
taskLines[1]?.querySelector(".cm-md-task-empty-placeholder"),
226+
).toBeNull();
227+
expect(taskLines[1]?.textContent).toBe("Task item");
228+
229+
view.destroy();
230+
});
211231
});

app/src/features/editor/extensions/markdown-decorations/lists.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,24 @@ class TaskMarkerWidget extends WidgetType {
9797
}
9898
}
9999

100+
class EmptyTaskPlaceholderWidget extends WidgetType {
101+
override eq(other: WidgetType): boolean {
102+
return other instanceof EmptyTaskPlaceholderWidget;
103+
}
104+
105+
override ignoreEvent(): boolean {
106+
return true;
107+
}
108+
109+
override toDOM(): HTMLElement {
110+
const placeholder = document.createElement("span");
111+
placeholder.className = "cm-md-task-empty-placeholder";
112+
placeholder.setAttribute("aria-hidden", "true");
113+
placeholder.textContent = "\u200B";
114+
return placeholder;
115+
}
116+
}
117+
100118
type ListMarkerNodeRef = Pick<SyntaxNodeRef, "from" | "to" | "type" | "node">;
101119
type DocLine = ReturnType<EditorState["doc"]["lineAt"]>;
102120

@@ -1399,6 +1417,15 @@ function addMarkerDecorations(
13991417
);
14001418
atomicRanges.push(taskMarkerDecoration.range(taskStart, taskEnd + 1));
14011419

1420+
if (taskEnd + 1 >= line.to) {
1421+
decorationRanges.push(
1422+
Decoration.widget({
1423+
side: -1,
1424+
widget: new EmptyTaskPlaceholderWidget(),
1425+
}).range(line.to),
1426+
);
1427+
}
1428+
14021429
if (checked && taskEnd + 1 < line.to) {
14031430
decorationRanges.push(
14041431
Decoration.mark({
@@ -1894,6 +1921,15 @@ const listTheme = EditorView.theme({
18941921
justifyContent: "center",
18951922
width: "var(--cm-md-list-marker-width)",
18961923
},
1924+
".cm-md-task-empty-placeholder": {
1925+
display: "inline",
1926+
font: "inherit",
1927+
lineHeight: "inherit",
1928+
opacity: "0",
1929+
pointerEvents: "none",
1930+
userSelect: "none",
1931+
verticalAlign: "baseline",
1932+
},
18971933
".cm-md-task-marker-box": {
18981934
backgroundColor: "var(--background)",
18991935
border: "1px solid var(--editor-checkbox-border)",

0 commit comments

Comments
 (0)