editor: give the outline-list collapse chevron a finger-sized touch target

This commit is contained in:
Ammar Ahmed
2026-08-09 19:07:01 +05:00
parent b8a02640d6
commit 7de9440d88
3 changed files with 93 additions and 4 deletions

View File

@@ -73,6 +73,71 @@ describe("outline list item", () => {
expect(editor.getJSON()).toMatchSnapshot();
});
test("tapping the gutter of a nested item toggles it; a flat item ignores it", () => {
const el = outlineList(
outlineListItem(["flat item"]),
li(
[
h("p", ["Parent"]),
outlineList(
outlineListItem(["Child one"]),
outlineListItem(["Child two"])
)
],
{ "data-type": "outlineListItem" }
)
);
const { editor } = createEditor({
initialContent: el.outerHTML,
extensions: {
outlineList: OutlineList,
outlineListItem: OutlineListItem,
paragraph: Paragraph
}
});
const dom = editor.view.dom as HTMLElement;
const nested = dom.querySelector("li.nested");
const flat = Array.from(dom.querySelectorAll("li")).find(
(item) => !item.classList.contains("nested")
);
expect(nested).toBeTruthy();
expect(flat).toBeTruthy();
const collapsed = () => {
let value: boolean | undefined;
editor.state.doc.descendants((node) => {
if (
node.type.name === OutlineListItem.name &&
node.lastChild?.type.name === OutlineList.name
)
value = node.attrs.collapsed;
});
return value;
};
const tapGutter = (item: Element) =>
item.dispatchEvent(
new MouseEvent("mousedown", {
button: 0,
clientX: -10,
clientY: 0,
bubbles: true,
cancelable: true
})
);
expect(collapsed()).toBe(false);
tapGutter(nested!);
expect(collapsed()).toBe(true);
tapGutter(nested!);
expect(collapsed()).toBe(false);
tapGutter(flat!);
expect(collapsed()).toBe(false);
});
/**
* Two changes happened:
* 1. Images were converted from inline nodes to block nodes (https://github.com/streetwriters/notesnook/pull/8563)

View File

@@ -24,7 +24,6 @@ import {
} from "@tiptap/core";
import {
findParentNodeOfTypeClosestToPos,
isClickWithinBounds,
ensureLeadingParagraph
} from "../../utils/prosemirror.js";
import { OutlineList } from "../outline-list/outline-list.js";
@@ -147,14 +146,24 @@ export const OutlineListItem = Node.create<ListItemOptions>({
function onClick(e: MouseEvent | TouchEvent) {
if (e instanceof MouseEvent && e.button !== 0) return;
if (!(e.target instanceof HTMLElement)) return;
if (!li.classList.contains("nested")) return;
const pos = typeof getPos === "function" ? getPos() : 0;
if (typeof pos !== "number") return;
const resolvedPos = editor.state.doc.resolve(pos);
if (isClickWithinBounds(e, resolvedPos, "left")) {
const point = e instanceof MouseEvent ? e : e.touches[0];
if (!point) return;
const rect = li.getBoundingClientRect();
const row = (li.firstElementChild ?? li).getBoundingClientRect();
const rtl = getComputedStyle(li).direction === "rtl";
const withinX = rtl
? point.clientX >= rect.right && point.clientX <= rect.right + 40
: point.clientX >= rect.left - 40 && point.clientX <= rect.left;
const withinY = point.clientY >= row.top && point.clientY <= row.bottom;
if (withinX && withinY) {
e.preventDefault();
e.stopImmediatePropagation();
editor.commands.command(({ tr }) => {

View File

@@ -657,6 +657,21 @@ p > *::selection {
border-left: 1px solid var(--hover);
}
.outline-list > li.nested::after {
content: "";
position: absolute;
top: 0;
left: -40px;
width: 40px;
height: 1lh;
cursor: pointer;
}
.outline-list[dir="rtl"] > li.nested::after {
left: unset;
right: -40px;
}
/* IMAGE */
.image-view-content-wrap.ProseMirror-selectednode .resizer::before {