Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ The release run heads these entries with the version and opens a fresh
it, as a second set of glyphs over the drawn ones. That layer now stays
transparent under `::selection`.

- A tap opens a sheet cell's editor where the pointer is coarse, so a phone
needs one tap rather than a double tap. A mouse keeps the double click.

- **Fix**: a second click on a pinned cell cleared the pin while the sheet was
being edited, so a reader who tapped a cell twice lost it. A cell now holds
its pin there; a row or column header still clears on that click.

## v7.1.0 - 2026-09-19

- The pdf view reports the pending annotation count on
Expand Down
19 changes: 18 additions & 1 deletion src/odr/internal/html/frontend/sheet-editing.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,12 +405,29 @@
}
});

/// A coarse pointer opens the editor on one tap: a phone gives up the first
/// tap of a double tap as a click of its own anyway. A fine pointer keeps the
/// double click, because there a single click selects a cell without editing.
function tapEdits() {
return (
typeof window.matchMedia === "function" &&
window.matchMedia("(pointer: coarse)").matches
);
}

// A locked cell says so on the click, not on the double click.
table.addEventListener("click", function (event) {
var at =
odr.editing.isEnabled() && overlay === null ? targetPosition(event) : null;
if (at !== null && odr.editing.lockAt(at.column, at.row) !== null) {
if (at === null) {
return;
}
if (odr.editing.lockAt(at.column, at.row) !== null) {
odr.editing.refuseAt(at.column, at.row);
return;
}
if (tapEdits()) {
edit(at.column, at.row, null);
}
});

Expand Down
17 changes: 15 additions & 2 deletions src/odr/internal/html/frontend/spreadsheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,16 @@
paint();
});

/// Whether the sheet is in the mode that writes cells. The editor is a
/// script of its own, and a read-only view carries none of it.
function editingEnabled() {
return (
odr.editing !== undefined &&
typeof odr.editing.isEnabled === "function" &&
odr.editing.isEnabled()
);
}

table.addEventListener("click", function (event) {
// Selecting inside what is raised must not put the cell back.
if (raisedContent !== null && raisedContent.contains(event.target)) {
Expand All @@ -468,9 +478,12 @@

// Clicking what is pinned clears it - but `detail` counts the clicks, and
// the second of a double click is the reader selecting a word. Clearing
// the pin under that flickers the border off again.
// the pin under that flickers the border off again. A cell in an edited
// sheet holds its pin too, because the editor opens over that cell and
// reads it. A header opens none, so it still clears.
if (cell === pinnedCell) {
if (event.detail <= 1) {
var takenByEditor = editingEnabled() && cell.tagName === "TD";
if (event.detail <= 1 && !takenByEditor) {
pin(-1, null, null);
}
return;
Expand Down
46 changes: 46 additions & 0 deletions test/browser/sheet/editing.html
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,52 @@
ops().length === 0 &&
last().dirty === false
);

// What a tap does. `tapEdits` reads `(pointer: coarse)`, so the check
// answers that query the way a phone does and puts the real one back.
var realMatchMedia = window.matchMedia;
function pointer(kind) {
window.matchMedia = function (query) {
return query === "(pointer: coarse)"
? { matches: kind === "coarse" }
: realMatchMedia.call(window, query);
};
}

check("the mode is still on", odr.editing.isEnabled() === true);
pointer("fine");
odr.sheet.pin(null);
click(cell(0, 0));
check("a fine pointer selects a cell rather than opening it", editor() === null);
doubleClick(cell(0, 0));
check("and opens it on the double click", editor() !== null);
press(editor(), "Escape");

pointer("coarse");
click(cell(0, 0));
check(
"a coarse pointer opens it on one click",
editor() !== null && editor().value === "a string that runs on"
);
press(editor(), "Escape");

check("the locked cell is the one this uses", odr.editing.lockAt(1, 2) === "formula");
click(cell(1, 2));
check("a locked cell refuses the tap rather than opening", editor() === null);

// A tap, a pause, a tap is two clicks of `detail: 1`, so the second one
// reaches the cell the first pinned.
pointer("fine");
odr.sheet.pin({ column: 0, row: 0 });
click(cell(0, 0));
check("a second click holds the pin while the sheet is edited", odr.sheet.pinned() !== null);
var header = document.querySelectorAll(".odr-sheet-column-header")[1];
click(header);
check("a header takes the pin", odr.sheet.pinned().column === 1);
click(header);
check("and a second click on it still clears the pin", odr.sheet.pinned() === null);

window.matchMedia = realMatchMedia;
</script>
</body>
</html>
Loading