Seed the mouse editor from its target, and mark the target on the first edit (improvise-ugj4, improvise-jibp)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
+9
-8
@@ -546,20 +546,19 @@ function gridTable(model) {
|
||||
|
||||
/** Open an inline editor. Committing sends a frame; the value that comes
|
||||
* back is the server's, never the string typed here. */
|
||||
function edit(td, row, col, seed) {
|
||||
function edit(td, row, col) {
|
||||
if (editing) return;
|
||||
// WHICH cell, captured now: rows renumber while the editor is open, so the
|
||||
// position is not an address by commit time (improvise-2wu3).
|
||||
const target = client.edit_target(row, col);
|
||||
let target = client.edit_target(row, col);
|
||||
if (!target) return;
|
||||
// `seed` is `RenderCell::edit_text` -- the STORED value, not the drawn one:
|
||||
// `td.textContent` has been through the number format.
|
||||
const original = seed;
|
||||
// What was on the screen, kept so closing can put it back: `close` no longer
|
||||
// always repaints, so restoring `seed` would leave `1200` where `1,200` was.
|
||||
// always repaints, so restoring the seed would leave `1200` where `1,200` was.
|
||||
const drawn = td.textContent;
|
||||
const input = document.createElement("input");
|
||||
input.value = original;
|
||||
// The seed the target captured, never the paint, which may be stale while
|
||||
// another editor is open: one capture shown and compared (improvise-ugj4).
|
||||
input.value = client.edit_seed(target);
|
||||
td.replaceChildren(input);
|
||||
input.focus();
|
||||
input.select();
|
||||
@@ -590,6 +589,8 @@ function edit(td, row, col, seed) {
|
||||
|
||||
// These two stay DIRECT while the five surfaces above are delegated: one
|
||||
// transient node, one at a time, and `blur` does not bubble (improvise-b8ot).
|
||||
// The first edit marks the target, so retyping the seed is still a write.
|
||||
input.addEventListener("input", () => (target = client.edited_target(target)), { once: true });
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") dismiss(true);
|
||||
else if (e.key === "Escape") dismiss(false);
|
||||
@@ -745,7 +746,7 @@ el.grid.addEventListener("mousedown", (event) => {
|
||||
if (frame) transport.send(frame);
|
||||
// Every cell moves the selection; an editable one also opens its editor.
|
||||
// NOT repainted in that branch: a repaint would take the <input> away.
|
||||
if (action.editable) edit(td, action.row, action.col, action.editText);
|
||||
if (action.editable) edit(td, action.row, action.col);
|
||||
else repaint();
|
||||
});
|
||||
|
||||
|
||||
+2
-3
@@ -222,7 +222,6 @@ export function gridPlan(model) {
|
||||
row: dataRow,
|
||||
col,
|
||||
text: typing ? `${model.editing}▏` : cell.text,
|
||||
editText: cell.edit_text,
|
||||
editable: cell.editable,
|
||||
classes: {
|
||||
numeric: cell.numeric && !typing,
|
||||
@@ -260,7 +259,7 @@ export function gridPlan(model) {
|
||||
/** What a data cell carries on its node, so one delegated handler can read
|
||||
* back the answers `gridPlan` already made (improvise-b8ot, clients.md). */
|
||||
export function cellAttrs(cell) {
|
||||
return { row: String(cell.row), col: String(cell.col), edit: cell.editText };
|
||||
return { row: String(cell.row), col: String(cell.col) };
|
||||
}
|
||||
|
||||
/** …and the inverse: what a delegated mousedown MEANS. `null` where the event
|
||||
@@ -271,7 +270,7 @@ export function cellAction(dataset, editable) {
|
||||
// A node that carries no coordinates is not a cell: every header and the
|
||||
// corner reach here, and none of them means anything.
|
||||
if (row === null || col === null) return null;
|
||||
return { row, col, editable, editText: dataset.edit ?? "" };
|
||||
return { row, col, editable };
|
||||
}
|
||||
|
||||
/** The same, for the surfaces whose rows are named by one index: the panels,
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
|
||||
const cell = (text, extra = {}) => ({
|
||||
text,
|
||||
edit_text: text,
|
||||
numeric: true,
|
||||
error: false,
|
||||
selected: false,
|
||||
@@ -40,7 +39,7 @@ function grouped() {
|
||||
{
|
||||
kind: "data",
|
||||
labels: ["Cold Brew"],
|
||||
cells: [cell("3", { editable: false }), cell("4", { edit_text: "4000" })],
|
||||
cells: [cell("3", { editable: false }), cell("4")],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -50,7 +49,7 @@ test("a cell's attributes carry the plan's answers back to the handler", () => {
|
||||
const plan = gridPlan(grouped());
|
||||
const last = plan.body.at(-1).cells[1];
|
||||
const action = cellAction(cellAttrs(last), last.editable);
|
||||
assert.deepEqual(action, { row: 1, col: 1, editable: true, editText: "4000" });
|
||||
assert.deepEqual(action, { row: 1, col: 1, editable: true });
|
||||
});
|
||||
|
||||
// improvise-b8ot: the DATA row index is what a click sends, and `gridPlan`
|
||||
|
||||
@@ -8,7 +8,6 @@ import { gridPlan } from "../shell.js";
|
||||
|
||||
const cell = (text, extra = {}) => ({
|
||||
text,
|
||||
edit_text: text,
|
||||
numeric: true,
|
||||
error: false,
|
||||
selected: false,
|
||||
@@ -102,14 +101,6 @@ test("an empty edit buffer still means an editor is open", () => {
|
||||
assert.equal(plan.body[1].cells[0].text, "▏");
|
||||
});
|
||||
|
||||
test("the editor is seeded from the stored value, not the drawn one", () => {
|
||||
const model = grouped();
|
||||
model.rows[1].cells[0] = cell("20,000", { edit_text: "20000" });
|
||||
const plan = gridPlan(model);
|
||||
assert.equal(plan.body[1].cells[0].text, "20,000");
|
||||
assert.equal(plan.body[1].cells[0].editText, "20000");
|
||||
});
|
||||
|
||||
test("an empty totals strip is no strip at all, not a row of blanks", () => {
|
||||
// Gotcha 33: an EMPTY strip means this grid has no totals — records mode,
|
||||
// or an empty grid — so there is nothing to draw and nothing to pad.
|
||||
|
||||
Reference in New Issue
Block a user