Skip to content

Commit 76b8ccb

Browse files
committed
fix(editor): keep heading controls outside text flow
1 parent 4842317 commit 76b8ccb

4 files changed

Lines changed: 68 additions & 14 deletions

File tree

packages/app/src/components/CodeMirrorPaperSurface.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,41 @@ describe("CodeMirrorPaperSurface", () => {
286286
expect(screen.getByRole("menuitem", { name: "Table" })).toBeInTheDocument();
287287
});
288288

289+
it("keeps block controls before the heading-level control while editing", () => {
290+
const onEditorReady = vi.fn();
291+
const { container } = render(
292+
<CodeMirrorPaperSurface
293+
autoFocus={false}
294+
initialContent="## Synthetic heading"
295+
onEditorReady={onEditorReady}
296+
onMarkdownChange={() => {}}
297+
/>,
298+
);
299+
const view = onEditorReady.mock.calls[0]?.[0] as EditorView;
300+
301+
act(() => {
302+
view.focus();
303+
view.dispatch({
304+
selection: EditorSelection.cursor(view.state.doc.length),
305+
});
306+
});
307+
308+
const line = container.querySelector<HTMLElement>(".cm-markra-h2");
309+
const toolbar = line?.querySelector<HTMLElement>(
310+
":scope > .cm-markra-block-toolbar",
311+
);
312+
const levelControl = line?.querySelector<HTMLElement>(
313+
":scope > .markra-heading-level-control",
314+
);
315+
316+
expect(toolbar).not.toBeNull();
317+
expect(levelControl).not.toBeNull();
318+
expect(
319+
toolbar!.compareDocumentPosition(levelControl!) &
320+
Node.DOCUMENT_POSITION_FOLLOWING,
321+
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
322+
});
323+
289324
it("enables Markra extended Markdown previews without rewriting source", () => {
290325
const onEditorReady = vi.fn();
291326
const doc = [

packages/app/src/styles.css

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,10 +2119,13 @@
21192119
}
21202120

21212121
.markdown-paper .markra-heading-level-control {
2122-
position: relative;
2122+
position: absolute;
2123+
left: -28px;
2124+
top: calc(50% + var(--markra-heading-toggle-center-offset));
2125+
z-index: 5;
21232126
display: inline-grid;
2124-
margin-right: 0.5rem;
2125-
vertical-align: 0.18em;
2127+
margin: 0;
2128+
transform: translateY(-50%);
21262129
}
21272130

21282131
.markdown-paper .markra-heading-level-button {
@@ -2212,12 +2215,12 @@
22122215
--markra-heading-toggle-center-offset: 0px;
22132216
}
22142217

2215-
/* Heading fold and level controls already occupy the positions beside the
2216-
text. Anchor block tools in their own gutter slot so widget order cannot
2217-
make the controls overlap. */
2218+
/* These controls are all absolutely positioned. Keeping them out of the
2219+
line layout prevents a pointer-down from moving heading text before the
2220+
matching pointer-up, which browsers can misread as a drag selection. */
22182221
.markdown-paper .markra-heading-toggle-heading > .cm-markra-block-toolbar {
22192222
position: absolute !important;
2220-
left: -78px;
2223+
left: -104px;
22212224
margin: 0 !important;
22222225
top: calc(50% + var(--markra-heading-toggle-center-offset));
22232226
transform: translateY(-50%);
@@ -2241,7 +2244,7 @@
22412244
.markdown-paper .markra-heading-toggle-button {
22422245
@apply absolute inline-flex size-5 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 outline-none transition-[opacity,color,background-color] duration-150 ease-out focus-visible:bg-(--bg-hover) focus-visible:outline-none;
22432246
color: color-mix(in srgb, var(--editor-text-secondary) 62%, transparent);
2244-
left: -28px;
2247+
left: -54px;
22452248
display: inline-flex !important;
22462249
margin: 0 !important;
22472250
padding: 0 !important;

packages/app/src/styles.test.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ describe("editor stylesheet", () => {
223223

224224
expect(headingToolbarRuleStart).toBeGreaterThanOrEqual(0);
225225
expect(headingToolbarRule).toContain("position: absolute !important");
226-
expect(headingToolbarRule).toContain("left: -78px");
226+
expect(headingToolbarRule).toContain("left: -104px");
227227
expect(headingToolbarRule).toContain("margin: 0 !important");
228228
expect(headingToolbarRule).toContain(
229229
"top: calc(50% + var(--markra-heading-toggle-center-offset))",
@@ -411,22 +411,35 @@ describe("editor stylesheet", () => {
411411
expect(styles).toContain("list-style-type: square;");
412412
});
413413

414-
it("shows a quiet inline level list control for the active rendered heading", () => {
414+
it("keeps the active heading-level control out of the editable text layout", () => {
415415
const styles = readFileSync(`${process.cwd()}/src/styles.css`, "utf8");
416416
const labelStart = styles.indexOf(".markdown-paper .markra-heading-level-control {");
417417
const labelEnd = styles.indexOf(".markdown-paper .markra-heading-toggle-heading");
418418
const labelStyles = styles.slice(labelStart, labelEnd);
419+
const foldButtonStart = styles.indexOf(
420+
".markdown-paper .markra-heading-toggle-button {",
421+
);
422+
const foldButtonEnd = styles.indexOf(
423+
".markdown-paper .markra-heading-toggle-heading:hover",
424+
foldButtonStart,
425+
);
426+
const foldButtonStyles = styles.slice(foldButtonStart, foldButtonEnd);
419427

420428
expect(labelStart).toBeGreaterThanOrEqual(0);
421429
expect(labelEnd).toBeGreaterThan(labelStart);
422-
expect(labelStyles).toContain("position: relative");
430+
expect(labelStyles).toContain("position: absolute");
423431
expect(labelStyles).toContain("display: inline-grid");
424-
expect(labelStyles).toContain("margin-right");
432+
expect(labelStyles).toContain("left: -28px");
433+
expect(labelStyles).toContain("margin: 0");
434+
expect(labelStyles).toContain(
435+
"top: calc(50% + var(--markra-heading-toggle-center-offset))",
436+
);
437+
expect(labelStyles).toContain("transform: translateY(-50%)");
425438
expect(labelStyles).toContain(".markdown-paper .markra-heading-level-list");
426439
expect(labelStyles).toContain("[role=\"option\"]");
427440
expect(labelStyles).toContain(".markdown-paper .markra-heading-level-button::before");
428-
expect(labelStyles).not.toContain("left:");
429441
expect(labelStyles).toContain("color: color-mix");
442+
expect(foldButtonStyles).toContain("left: -54px");
430443
});
431444

432445
it("keeps table add controls hidden until table hover or focus", () => {

packages/editor/src/codemirror/block-drag.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,10 @@ function blockDecorations(
449449
attributes: { "data-markra-block-from": String(block.from) },
450450
}).range(block.from),
451451
Decoration.widget({
452-
side: -1,
452+
// Block tools must be the outermost start-of-line widget. Heading-level
453+
// controls also use side -1, and their negative gutter margin would
454+
// otherwise pull the drag handle back over the H1-H6 button.
455+
side: -2,
453456
widget: new BlockToolbarWidget(block.from, labels),
454457
}).range(block.from),
455458
]);

0 commit comments

Comments
 (0)