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';
|
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;
|
const GAP_PX = 6;
|
||||||
|
|
||||||
/** @type {HTMLElement | null} */
|
/** @type {HTMLElement | null} */
|
||||||
@@ -20,6 +31,8 @@ let tooltip = null;
|
|||||||
let timer = null;
|
let timer = null;
|
||||||
/** @type {Element | null} */
|
/** @type {Element | null} */
|
||||||
let shownFor = null;
|
let shownFor = null;
|
||||||
|
/** When a tooltip was last showing, for the neighbour rule. */
|
||||||
|
let warmUntil = -Infinity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {EventTarget | null} target
|
* @param {EventTarget | null} target
|
||||||
@@ -49,10 +62,13 @@ function show(element) {
|
|||||||
tooltip.style.top = `${box.bottom + GAP_PX}px`;
|
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);
|
if (timer !== null) clearTimeout(timer);
|
||||||
timer = null;
|
timer = null;
|
||||||
|
if (shownFor) warmUntil = performance.now() + WARM_MS;
|
||||||
shownFor = null;
|
shownFor = null;
|
||||||
|
if (cold) warmUntil = -Infinity;
|
||||||
if (tooltip) tooltip.hidden = true;
|
if (tooltip) tooltip.hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,11 +85,12 @@ export function enableTooltips() {
|
|||||||
if (element === shownFor) return;
|
if (element === shownFor) return;
|
||||||
hide();
|
hide();
|
||||||
trace(`Hover: ${event.pointerType} over ${element.textContent?.trim() || element.id}`);
|
trace(`Hover: ${event.pointerType} over ${element.textContent?.trim() || element.id}`);
|
||||||
|
const wait = performance.now() < warmUntil ? 0 : DELAY_MS;
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
timer = null;
|
timer = null;
|
||||||
trace(`Tooltip: ${element.textContent?.trim() || element.id}`);
|
trace(`Tooltip: ${element.textContent?.trim() || element.id}`);
|
||||||
show(element);
|
show(element);
|
||||||
}, DELAY_MS);
|
}, wait);
|
||||||
});
|
});
|
||||||
document.addEventListener('pointerleave', (event) => {
|
document.addEventListener('pointerleave', (event) => {
|
||||||
if (timer !== null || shownFor) trace(`Hover: ${event.pointerType} left the document`);
|
if (timer !== null || shownFor) trace(`Hover: ${event.pointerType} left the document`);
|
||||||
@@ -85,14 +102,14 @@ export function enableTooltips() {
|
|||||||
let pressedAt = -Infinity;
|
let pressedAt = -Infinity;
|
||||||
document.addEventListener('pointerdown', () => {
|
document.addEventListener('pointerdown', () => {
|
||||||
pressedAt = performance.now();
|
pressedAt = performance.now();
|
||||||
hide();
|
hide(true);
|
||||||
});
|
});
|
||||||
document.addEventListener('focusin', (event) => {
|
document.addEventListener('focusin', (event) => {
|
||||||
if (performance.now() - pressedAt < 500) return;
|
if (performance.now() - pressedAt < 500) return;
|
||||||
const element = helpElement(event.target);
|
const element = helpElement(event.target);
|
||||||
if (element) show(element);
|
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.
|
// 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,
|
middle the page and the view; on the right the host's page buttons,
|
||||||
**Notebooks**, your notebook, and **Session**, which opens a window with
|
**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
|
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.
|
explanation appears beneath it.
|
||||||
|
|
||||||
### The page
|
### 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`),
|
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
|
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
|
(`renderer.js`), the dead zone of that drag (`pen_input.js`), the margin
|
||||||
by which the page grows (`page_model.js`), the tooltip delay
|
by which the page grows (`page_model.js`), the tooltip delay (1 s,
|
||||||
(`tooltip.js`), the hold that moves an image (600 ms, with 6 pixels of
|
`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,
|
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,
|
`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,
|
`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
|
The toolbar holds the pen, text, eraser and hide tools, colours and
|
||||||
line widths, the view commands (Fit, A4, Widen, Shrink, Extend), the
|
line widths, the view commands (Fit, A4, Widen, Shrink, Extend), the
|
||||||
host's page buttons (New, Image, Notebooks), your notebook, Keep, Export
|
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
|
explanation. Each participant writes on a layer of their own that
|
||||||
nobody else can change. The page is saved continuously to the current
|
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
|
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.switch_to(host_tab)
|
||||||
firefox.go("about:blank")
|
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):
|
def test_hide_rectangles(self):
|
||||||
"""The teacher's requests of 2026-10-08: a Hide rectangle covers part
|
"""The teacher's requests of 2026-10-08: a Hide rectangle covers part
|
||||||
of the page in white for everyone, marked by blue corners."""
|
of the page in white for everyone, marked by blue corners."""
|
||||||
|
|||||||
Reference in new issue
Block a user