Tooltips after one second, and at once for the next button
From the development repository at b7ffe0e. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc3c9bb886
commit
fcd6117618
5 files changed
+64
-10
No files matched your search
+23
-6
@@ -11,7 +11,18 @@
|
||||
|
||||
import { trace } from './log.js';
|
||||
|
||||
const DELAY_MS = 2000;
|
||||
/**
|
||||
* How long the pointer must have been in a button before its tooltip
|
||||
* appears: the time in the button, movement inside it not counting
|
||||
* (Andy, 2026-10-11: one second; two until then, one until 2026-10-06).
|
||||
*/
|
||||
export const DELAY_MS = 1000;
|
||||
/**
|
||||
* The neighbour rule: within this long after a tooltip was shown, the
|
||||
* next button's tooltip appears at once, so that reading a row of
|
||||
* buttons costs one wait, not one per button. A press or a key ends it.
|
||||
*/
|
||||
export const WARM_MS = 1000;
|
||||
const GAP_PX = 6;
|
||||
|
||||
/** @type {HTMLElement | null} */
|
||||
@@ -20,6 +31,8 @@ let tooltip = null;
|
||||
let timer = null;
|
||||
/** @type {Element | null} */
|
||||
let shownFor = null;
|
||||
/** When a tooltip was last showing, for the neighbour rule. */
|
||||
let warmUntil = -Infinity;
|
||||
|
||||
/**
|
||||
* @param {EventTarget | null} target
|
||||
@@ -49,10 +62,13 @@ function show(element) {
|
||||
tooltip.style.top = `${box.bottom + GAP_PX}px`;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
/** @param {boolean} [cold] Whether the neighbour rule ends here too. */
|
||||
function hide(cold = false) {
|
||||
if (timer !== null) clearTimeout(timer);
|
||||
timer = null;
|
||||
if (shownFor) warmUntil = performance.now() + WARM_MS;
|
||||
shownFor = null;
|
||||
if (cold) warmUntil = -Infinity;
|
||||
if (tooltip) tooltip.hidden = true;
|
||||
}
|
||||
|
||||
@@ -69,11 +85,12 @@ export function enableTooltips() {
|
||||
if (element === shownFor) return;
|
||||
hide();
|
||||
trace(`Hover: ${event.pointerType} over ${element.textContent?.trim() || element.id}`);
|
||||
const wait = performance.now() < warmUntil ? 0 : DELAY_MS;
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
trace(`Tooltip: ${element.textContent?.trim() || element.id}`);
|
||||
show(element);
|
||||
}, DELAY_MS);
|
||||
}, wait);
|
||||
});
|
||||
document.addEventListener('pointerleave', (event) => {
|
||||
if (timer !== null || shownFor) trace(`Hover: ${event.pointerType} left the document`);
|
||||
@@ -85,14 +102,14 @@ export function enableTooltips() {
|
||||
let pressedAt = -Infinity;
|
||||
document.addEventListener('pointerdown', () => {
|
||||
pressedAt = performance.now();
|
||||
hide();
|
||||
hide(true);
|
||||
});
|
||||
document.addEventListener('focusin', (event) => {
|
||||
if (performance.now() - pressedAt < 500) return;
|
||||
const element = helpElement(event.target);
|
||||
if (element) show(element);
|
||||
});
|
||||
document.addEventListener('focusout', hide);
|
||||
document.addEventListener('focusout', () => hide());
|
||||
// Typing means the user's attention is on the keys, not the tooltip.
|
||||
document.addEventListener('keydown', hide);
|
||||
document.addEventListener('keydown', () => hide(true));
|
||||
}
|
||||
+1
-1
@@ -53,7 +53,7 @@ groups: on the left the tools, their settings and **Undo**; in the
|
||||
middle the page and the view; on the right the host's page buttons,
|
||||
**Notebooks**, your notebook, and **Session**, which opens a window with
|
||||
the participants and the invitation. Below it the page has the whole width of the window. Every button
|
||||
explains itself: rest the pointer on it for two seconds and its
|
||||
explains itself: rest the pointer on it for a second and its
|
||||
explanation appears beneath it.
|
||||
|
||||
### The page
|
||||
|
||||
@@ -278,8 +278,9 @@ file that uses them, so that they can be changed in one place, or offered
|
||||
as preferences later: the line widths and the five colours (`main.js`),
|
||||
the zoom step of the wheel and the zoom per 100 pixels of a pen drag
|
||||
(`renderer.js`), the dead zone of that drag (`pen_input.js`), the margin
|
||||
by which the page grows (`page_model.js`), the tooltip delay
|
||||
(`tooltip.js`), the hold that moves an image (600 ms, with 6 pixels of
|
||||
by which the page grows (`page_model.js`), the tooltip delay (1 s,
|
||||
`DELAY_MS` in `tooltip.js`) and how long after one tooltip the next
|
||||
button's comes at once (1 s, `WARM_MS` there), the hold that moves an image (600 ms, with 6 pixels of
|
||||
slack, `pen_input.js`), the reach of an image's corner (24 pixels,
|
||||
`renderer.js`), the smallest an image can be scaled to (1 cm,
|
||||
`page_model.js`), how often a dragged image's place is sent (40 ms,
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ rather than in the browser's storage.
|
||||
The toolbar holds the pen, text, eraser and hide tools, colours and
|
||||
line widths, the view commands (Fit, A4, Widen, Shrink, Extend), the
|
||||
host's page buttons (New, Image, Notebooks), your notebook, Keep, Export
|
||||
and Session. Rest the pointer on any button for two seconds for its
|
||||
and Session. Rest the pointer on any button for a second for its
|
||||
explanation. Each participant writes on a layer of their own that
|
||||
nobody else can change. The page is saved continuously to the current
|
||||
notebook, `Current.svg`; Keep makes a dated copy. Rest the pen on an
|
||||
|
||||
@@ -880,6 +880,42 @@ class EndToEndTest(unittest.TestCase):
|
||||
firefox.switch_to(host_tab)
|
||||
firefox.go("about:blank")
|
||||
|
||||
def test_tooltips(self):
|
||||
"""A button's explanation appears after the pointer has been in it
|
||||
for a second; once one is showing, a neighbour's appears at once;
|
||||
a press ends that."""
|
||||
firefox = self.firefox
|
||||
firefox.go(self.address("Late"))
|
||||
self.enter_name("Reader", "start_session")
|
||||
self.wait_until(lambda s: s["page"]["number"] == 1, "the page")
|
||||
hover = lambda tool: firefox.run("""
|
||||
document.querySelector('[data-tool=' + arguments[0] + ']')
|
||||
.dispatchEvent(new PointerEvent('pointerover', {bubbles: true, pointerType: 'pen'}));
|
||||
""", tool)
|
||||
showing = lambda: firefox.run("""
|
||||
const t = document.querySelector('.tooltip');
|
||||
return t && !t.hidden ? t.textContent.slice(0, 20) : null;
|
||||
""")
|
||||
hover("pen")
|
||||
time.sleep(0.5)
|
||||
self.assertIsNone(showing())
|
||||
time.sleep(0.8)
|
||||
self.assertEqual(showing(), "Write on the page. O")
|
||||
# The neighbour rule: the next tooltip comes without the wait.
|
||||
firefox.run("document.body.dispatchEvent(new PointerEvent('pointerover', {bubbles: true}));")
|
||||
self.assertIsNone(showing())
|
||||
hover("text")
|
||||
time.sleep(0.1)
|
||||
self.assertEqual(showing(), "Type on the page. Ta")
|
||||
# A press ends the tooltip and the rule.
|
||||
firefox.run("document.body.dispatchEvent(new PointerEvent('pointerdown', {bubbles: true}));")
|
||||
self.assertIsNone(showing())
|
||||
hover("pen")
|
||||
time.sleep(0.5)
|
||||
self.assertIsNone(showing())
|
||||
time.sleep(0.8)
|
||||
self.assertEqual(showing(), "Write on the page. O")
|
||||
|
||||
def test_hide_rectangles(self):
|
||||
"""The teacher's requests of 2026-10-08: a Hide rectangle covers part
|
||||
of the page in white for everyone, marked by blue corners."""
|
||||
|
||||
Reference in new issue
Block a user