Skip to content

Commit 9653a5f

Browse files
committed
work
1 parent bd23982 commit 9653a5f

9 files changed

Lines changed: 194 additions & 47 deletions

File tree

packages/frontend/dom/dist/jsenv_dom.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13077,14 +13077,18 @@ const watchWheelTravel = (element, { axes = "xy", onStep }) => {
1307713077
// gesture that is over.
1307813078
claimWheelGesture(element, { onEnd: forgetGesture });
1307913079
if (sign !== gesture.sign) {
13080-
// Turned around: what was adding up was going the other way.
13080+
// Turned around: what was adding up was going the other way, so the
13081+
// ledger starts over — and only the ledger. The burst has been answered
13082+
// (stepped) and its stream is as much momentum as an event ago (faded):
13083+
// a flipping sign is the one thing a dying tail and a hand share, and a
13084+
// tail rocking to zero read as a first event walks a slide per event. A
13085+
// screen the other way costs what any screen after the first does, and a
13086+
// counter-push over a tail is heard by the regrow rule below.
1308113087
gesture.sign = sign;
1308213088
gesture.pushed = 0;
1308313089
gesture.lastMagnitude = 0;
1308413090
gesture.fadeRun = 0;
1308513091
gesture.growRun = 0;
13086-
gesture.faded = false;
13087-
gesture.stepped = false;
1308813092
}
1308913093
if (!gesture.stepped) {
1309013094
// The first event of a gesture moves a screen, whatever it is worth —

packages/frontend/dom/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@jsenv/dom",
3-
"version": "0.17.58",
3+
"version": "0.17.59",
44
"type": "module",
55
"description": "DOM utilities for writing frontend code",
66
"repository": {

packages/frontend/dom/src/interaction/drag/drag_to_travel.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,14 +1088,18 @@ export const watchWheelTravel = (element, { axes = "xy", onStep }) => {
10881088
// gesture that is over.
10891089
claimWheelGesture(element, { onEnd: forgetGesture });
10901090
if (sign !== gesture.sign) {
1091-
// Turned around: what was adding up was going the other way.
1091+
// Turned around: what was adding up was going the other way, so the
1092+
// ledger starts over — and only the ledger. The burst has been answered
1093+
// (stepped) and its stream is as much momentum as an event ago (faded):
1094+
// a flipping sign is the one thing a dying tail and a hand share, and a
1095+
// tail rocking to zero read as a first event walks a slide per event. A
1096+
// screen the other way costs what any screen after the first does, and a
1097+
// counter-push over a tail is heard by the regrow rule below.
10921098
gesture.sign = sign;
10931099
gesture.pushed = 0;
10941100
gesture.lastMagnitude = 0;
10951101
gesture.fadeRun = 0;
10961102
gesture.growRun = 0;
1097-
gesture.faded = false;
1098-
gesture.stepped = false;
10991103
}
11001104
if (!gesture.stepped) {
11011105
// The first event of a gesture moves a screen, whatever it is worth —

packages/frontend/navi/dist/dev/jsenv_navi.js

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4520,6 +4520,9 @@ const isControlRoot = (el) => {
45204520
const isControlHost = (el) => {
45214521
return el.hasAttribute("navi-control-host");
45224522
};
4523+
const isControl = (el) => {
4524+
return isControlRoot(el) || isControlHost(el);
4525+
};
45234526

45244527
/**
45254528
* Returns the nearest ancestor of `el` (exclusive of `el`'s own control) that
@@ -6236,7 +6239,10 @@ const css$12 = /* css */ `
62366239
* Shows a callout attached to the specified element
62376240
* @param {string} message - HTML content for the callout
62386241
* @param {Object} options - Configuration options
6239-
* @param {HTMLElement} [options.anchorElement] - Element the callout should follow. If not provided or too big, callout will be centered in viewport
6242+
* @param {HTMLElement} [options.anchorElement] - Element the callout points at and follows.
6243+
* If not provided or too big, callout will be centered in viewport. It says where the
6244+
* callout is drawn, not what dismisses it: see `openingEvent` for what a container anchor
6245+
* costs
62406246
* @param {string} [options.status=""] - Callout status: "info" | "warning" | "error" | "success"
62416247
* @param {string} [options.testId] - `data-testid` on the callout element. The callout is
62426248
* drawn by navi, so nothing the caller renders can carry the name a test needs — same
@@ -6245,6 +6251,18 @@ const css$12 = /* css */ `
62456251
* callouts apart.
62466252
* @param {Function} [options.onClose] - Callback when callout is closed
62476253
* @param {boolean} [options.closeOnClickOutside] - Whether to close on outside clicks (defaults to true for "info" status)
6254+
* @param {Event} [options.openingEvent] - The event being handled when the callout was asked
6255+
* for. While it is still dispatching, its `currentTarget` names the opener: the one part of
6256+
* the anchor that does not count as "outside", so the press reaches the handler owning the
6257+
* callout and `reopen` decides (toggle by default), instead of the callout being closed here
6258+
* and opened again within that same press.
6259+
*
6260+
* A callout opened later — after an await, from an effect — has no opener. The anchor keeps
6261+
* the exemption only if it is itself a control, which does have a handler that would re-open
6262+
* it; a container anchor (a card, a block of a settings page) has none, so all of it
6263+
* dismisses. The cost is the toggle: pressing what started the work closes the callout as an
6264+
* outside press, and the work opens a fresh one when it ends. Anchor to an always-mounted box
6265+
* around the opener instead of to the container to keep the toggle
62486266
* @param {boolean} [options.icon=true] - Whether the status icon is shown beside the message.
62496267
* Never shown without a status either way (see the CSS).
62506268
* @param {boolean} [options.closeButton=true] - Whether the cross is shown. Without it the callout
@@ -6626,13 +6644,41 @@ const openCallout = (
66266644
})();
66276645

66286646
{
6629-
// document.body as anchor means "no anchor" (the callout is docked in the
6630-
// viewport); everything would be inside it.
6631-
const isInsideAnchor = (target) => {
6647+
// The exemption below belongs to the opener, not to the whole anchor: only
6648+
// something carrying a handler can decide, and what a callout is anchored
6649+
// to is not always what opened it.
6650+
const openerElement = (() => {
66326651
if (!anchorElement || anchorElement === document.body) {
6652+
// document.body as anchor means "no anchor" (the callout is docked in
6653+
// the viewport); everything would be inside it.
6654+
return null;
6655+
}
6656+
// `currentTarget` is set only while an event is dispatching, so reading
6657+
// it here tells a callout opened from a handler — one that has an owner
6658+
// about to decide on the next press — from one opened later, out of any
6659+
// gesture, which has none. Kept within the anchor: an opener elsewhere on
6660+
// the page is outside like anything else.
6661+
const openingTarget = openingEvent ? openingEvent.currentTarget : null;
6662+
if (
6663+
openingTarget &&
6664+
openingTarget.nodeType === Node.ELEMENT_NODE &&
6665+
(anchorElement === openingTarget ||
6666+
anchorElement.contains(openingTarget))
6667+
) {
6668+
return findControlRoot(openingTarget) || openingTarget;
6669+
}
6670+
// No handler was running: the anchor speaks for itself only if it is a
6671+
// control, which does have one. A container anchor (a card, a block of a
6672+
// settings page) has nothing that would re-open the callout, and
6673+
// exempting all of it would leave the one place the user is most likely
6674+
// to press — the thing the callout points at — unable to dismiss it.
6675+
return isControl(anchorElement) ? anchorElement : null;
6676+
})();
6677+
const isInsideOpener = (target) => {
6678+
if (!openerElement) {
66336679
return false;
66346680
}
6635-
return anchorElement === target || anchorElement.contains(target);
6681+
return openerElement === target || openerElement.contains(target);
66366682
};
66376683
const handleClickOutside = (event) => {
66386684
if (event.button !== 0) {
@@ -6646,14 +6692,14 @@ const openCallout = (
66466692
) {
66476693
return;
66486694
}
6649-
if (isInsideAnchor(clickTarget)) {
6650-
// Pressing the anchor is not "outside": this listener is on document in
6695+
if (isInsideOpener(clickTarget)) {
6696+
// Pressing the opener is not "outside": this listener is on document in
66516697
// the capture phase, so closing here would destroy the callout before
66526698
// the event reaches the handler that owns it — and that handler,
66536699
// opening a callout on the very anchor it was just removed from, would
66546700
// create a second one within the same click. Left open, openCallout's
66556701
// `reopen` decides (toggle by default).
6656-
debug(event, `click on anchor, let the anchor handler decide`);
6702+
debug(event, `click on opener, let its handler decide`);
66576703
return;
66586704
}
66596705
requestClose(event, "click_outside");
@@ -6666,10 +6712,10 @@ const openCallout = (
66666712
if (keyTarget === calloutElement || calloutElement.contains(keyTarget)) {
66676713
return;
66686714
}
6669-
if (isInsideAnchor(keyTarget)) {
6670-
// Space on the anchor produces a click afterwards — same reasoning as
6715+
if (isInsideOpener(keyTarget)) {
6716+
// Space on the opener produces a click afterwards — same reasoning as
66716717
// handleClickOutside above.
6672-
debug(event, `space on anchor, let the anchor handler decide`);
6718+
debug(event, `space on opener, let its handler decide`);
66736719
return;
66746720
}
66756721
requestClose(event, "space_outside");

packages/frontend/navi/dist/dev/jsenv_navi.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/frontend/navi/dist/jsenv_navi.js

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4460,6 +4460,9 @@ const isControlRoot = (el) => {
44604460
const isControlHost = (el) => {
44614461
return el.hasAttribute("navi-control-host");
44624462
};
4463+
const isControl = (el) => {
4464+
return isControlRoot(el) || isControlHost(el);
4465+
};
44634466

44644467
/**
44654468
* Returns the nearest ancestor of `el` (exclusive of `el`'s own control) that
@@ -6121,7 +6124,10 @@ const css$12 = /* css */ `
61216124
* Shows a callout attached to the specified element
61226125
* @param {string} message - HTML content for the callout
61236126
* @param {Object} options - Configuration options
6124-
* @param {HTMLElement} [options.anchorElement] - Element the callout should follow. If not provided or too big, callout will be centered in viewport
6127+
* @param {HTMLElement} [options.anchorElement] - Element the callout points at and follows.
6128+
* If not provided or too big, callout will be centered in viewport. It says where the
6129+
* callout is drawn, not what dismisses it: see `openingEvent` for what a container anchor
6130+
* costs
61256131
* @param {string} [options.status=""] - Callout status: "info" | "warning" | "error" | "success"
61266132
* @param {string} [options.testId] - `data-testid` on the callout element. The callout is
61276133
* drawn by navi, so nothing the caller renders can carry the name a test needs — same
@@ -6130,6 +6136,18 @@ const css$12 = /* css */ `
61306136
* callouts apart.
61316137
* @param {Function} [options.onClose] - Callback when callout is closed
61326138
* @param {boolean} [options.closeOnClickOutside] - Whether to close on outside clicks (defaults to true for "info" status)
6139+
* @param {Event} [options.openingEvent] - The event being handled when the callout was asked
6140+
* for. While it is still dispatching, its `currentTarget` names the opener: the one part of
6141+
* the anchor that does not count as "outside", so the press reaches the handler owning the
6142+
* callout and `reopen` decides (toggle by default), instead of the callout being closed here
6143+
* and opened again within that same press.
6144+
*
6145+
* A callout opened later — after an await, from an effect — has no opener. The anchor keeps
6146+
* the exemption only if it is itself a control, which does have a handler that would re-open
6147+
* it; a container anchor (a card, a block of a settings page) has none, so all of it
6148+
* dismisses. The cost is the toggle: pressing what started the work closes the callout as an
6149+
* outside press, and the work opens a fresh one when it ends. Anchor to an always-mounted box
6150+
* around the opener instead of to the container to keep the toggle
61336151
* @param {boolean} [options.icon=true] - Whether the status icon is shown beside the message.
61346152
* Never shown without a status either way (see the CSS).
61356153
* @param {boolean} [options.closeButton=true] - Whether the cross is shown. Without it the callout
@@ -6513,13 +6531,41 @@ const openCallout = (
65136531
})();
65146532

65156533
{
6516-
// document.body as anchor means "no anchor" (the callout is docked in the
6517-
// viewport); everything would be inside it.
6518-
const isInsideAnchor = (target) => {
6534+
// The exemption below belongs to the opener, not to the whole anchor: only
6535+
// something carrying a handler can decide, and what a callout is anchored
6536+
// to is not always what opened it.
6537+
const openerElement = (() => {
65196538
if (!anchorElement || anchorElement === document.body) {
6539+
// document.body as anchor means "no anchor" (the callout is docked in
6540+
// the viewport); everything would be inside it.
6541+
return null;
6542+
}
6543+
// `currentTarget` is set only while an event is dispatching, so reading
6544+
// it here tells a callout opened from a handler — one that has an owner
6545+
// about to decide on the next press — from one opened later, out of any
6546+
// gesture, which has none. Kept within the anchor: an opener elsewhere on
6547+
// the page is outside like anything else.
6548+
const openingTarget = openingEvent ? openingEvent.currentTarget : null;
6549+
if (
6550+
openingTarget &&
6551+
openingTarget.nodeType === Node.ELEMENT_NODE &&
6552+
(anchorElement === openingTarget ||
6553+
anchorElement.contains(openingTarget))
6554+
) {
6555+
return findControlRoot(openingTarget) || openingTarget;
6556+
}
6557+
// No handler was running: the anchor speaks for itself only if it is a
6558+
// control, which does have one. A container anchor (a card, a block of a
6559+
// settings page) has nothing that would re-open the callout, and
6560+
// exempting all of it would leave the one place the user is most likely
6561+
// to press — the thing the callout points at — unable to dismiss it.
6562+
return isControl(anchorElement) ? anchorElement : null;
6563+
})();
6564+
const isInsideOpener = (target) => {
6565+
if (!openerElement) {
65206566
return false;
65216567
}
6522-
return anchorElement === target || anchorElement.contains(target);
6568+
return openerElement === target || openerElement.contains(target);
65236569
};
65246570
const handleClickOutside = (event) => {
65256571
if (event.button !== 0) {
@@ -6533,14 +6579,14 @@ const openCallout = (
65336579
) {
65346580
return;
65356581
}
6536-
if (isInsideAnchor(clickTarget)) {
6537-
// Pressing the anchor is not "outside": this listener is on document in
6582+
if (isInsideOpener(clickTarget)) {
6583+
// Pressing the opener is not "outside": this listener is on document in
65386584
// the capture phase, so closing here would destroy the callout before
65396585
// the event reaches the handler that owns it — and that handler,
65406586
// opening a callout on the very anchor it was just removed from, would
65416587
// create a second one within the same click. Left open, openCallout's
65426588
// `reopen` decides (toggle by default).
6543-
debug(event, `click on anchor, let the anchor handler decide`);
6589+
debug(event, `click on opener, let its handler decide`);
65446590
return;
65456591
}
65466592
requestClose(event, "click_outside");
@@ -6553,10 +6599,10 @@ const openCallout = (
65536599
if (keyTarget === calloutElement || calloutElement.contains(keyTarget)) {
65546600
return;
65556601
}
6556-
if (isInsideAnchor(keyTarget)) {
6557-
// Space on the anchor produces a click afterwards — same reasoning as
6602+
if (isInsideOpener(keyTarget)) {
6603+
// Space on the opener produces a click afterwards — same reasoning as
65586604
// handleClickOutside above.
6559-
debug(event, `space on anchor, let the anchor handler decide`);
6605+
debug(event, `space on opener, let its handler decide`);
65606606
return;
65616607
}
65626608
requestClose(event, "space_outside");

0 commit comments

Comments
 (0)