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
18 changes: 18 additions & 0 deletions skills/leaf/assets/runtime/composing/surface.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,16 @@ export function createSelectionSurface({
fabInputTakingFocus = false;
});
let primaryPointerPressed = false;
// Whether the page's own words stood selected when the key line was last painted for
// this press. The bar waits for the release; the Escape rung cannot, because from the
// first glyph a drag takes, Escape clears the selection rather than letting go of the
// control the reader is standing on, and until now nothing repainted the line inside a
// press — the word only became true when the frame the press itself scheduled happened
// to land after the drag had moved, and stayed a lie for a whole heartbeat when it
// landed before. Only the crossing is painted: a drag growing a selection that already
// stands says the same word, and repainting the chrome on every move of a drag would
// put a whole `paintHere` inside every frame of one.
let selectionStood = false;
const rememberPointerSelection = () => {
const selection = pageSelection();
const anchor = selection ? selectionAnchor(selection) : null;
Expand All @@ -399,6 +409,9 @@ export function createSelectionSurface({
selectionDragged = false;
selectionRangeDuringPress = null;
selectionPressPoint = pointerSelecting ? { x: ev.clientX, y: ev.clientY } : null;
// Read here, ahead of the browser's own collapse, so the first crossing this press
// makes is measured against what the line already says rather than against nothing.
selectionStood = Boolean(pageSelection());
const selection = pointerSelecting ? pageSelection() : null;
if (selection) {
const range = pageRange(selection);
Expand Down Expand Up @@ -449,6 +462,11 @@ export function createSelectionSurface({
if (primaryPointerPressed) {
selectionChangedDuringPress = true;
rememberPointerSelection();
const stands = Boolean(pageSelection());
if (stands !== selectionStood) {
selectionStood = stands;
paintHere();
}
return;
}
if (
Expand Down
12 changes: 11 additions & 1 deletion tests/render_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -1455,9 +1455,17 @@ def resized(page, width, height):
page.evaluate(ONE_FRAME)


def hold_selection(page, start, end, steps=8):
def hold_selection(page, start, end, steps=8, frame_the_press=False):
"""Drag a selection without releasing, pressing on a whole pixel.

`frame_the_press` lets the frame the press itself schedules land before the drag
begins, which is the ordering a loaded machine gives every drag and an idle one
gives almost none. Without it a surface repainted inside the press is read as
following the drag whenever the round trips outrun the frame, and as stale
whenever they do not — a coin toss written as an assertion. State it wherever the
read is of something the press repaints, so the drag is the only thing the read
can be about.

A fractional start point loses the selection outright wherever it and its own
floor fall either side of a glyph's caret boundary: the drag runs, the mouseup
lands, and `getSelection()` comes back empty. It reads as the widget under the
Expand All @@ -1472,6 +1480,8 @@ def hold_selection(page, start, end, steps=8):
moves the selection a character."""
page.mouse.move(math.floor(start[0]), math.floor(start[1]))
page.mouse.down()
if frame_the_press:
page.evaluate(RENDERED)
page.mouse.move(end[0], end[1], steps=steps)


Expand Down
7 changes: 7 additions & 0 deletions tests/test_render_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3840,11 +3840,18 @@ def test_a_label_press_keeps_the_controls_keyboard_standing(browser, serve):
expect(control).to_be_focused()
expect(page.locator("#frame-question-decision[data-lf-decision]")).to_have_count(1)

# The press has its own frame and the drag comes after it, so the line's only route
# to the word is the selection the drag makes: the reader is taking words out of a
# label, and from the first glyph Escape clears that selection rather than letting
# go of the control. Framed the other way round the press's frame painted the line
# after the drag had already run, and the word arrived whether or not anything
# followed the selection.
hold_selection(
page,
(bounds["x"] + 2, middle[1]),
(bounds["x"] + bounds["width"] - 2, middle[1]),
steps=10,
frame_the_press=True,
)
assert "after state" in page.evaluate("() => getSelection().toString()")
assert "unselect" in key_line(page)
Expand Down
Loading