@jsenv/navi 0.30.34 → 0.30.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/jsenv_navi.js +235 -93
- package/dist/dev/jsenv_navi.js.map +7 -7
- package/dist/jsenv_navi.js +210 -93
- package/dist/jsenv_navi.js.map +7 -7
- package/docs/AI_INSTRUCTIONS.md +3 -2
- package/docs/drag_to_travel.md +5 -3
- package/docs/interactions.md +19 -0
- package/docs/popup_open.md +7 -6
- package/docs/safe_area.md +27 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -41536,9 +41536,23 @@ const RouteTravel = ({
|
|
|
41536
41536
|
params
|
|
41537
41537
|
}) => new URL(route.buildUrl(params), window.location.href).pathname;
|
|
41538
41538
|
|
|
41539
|
+
// The pane laid out as the page on screen for the length of a movement, let go of
|
|
41540
|
+
// when the row rests. Not what readers are told the page held is
|
|
41541
|
+
// (route_travel_hold.js): that follows the hand, and goes at its lift.
|
|
41542
|
+
const heldPaneRef = useRef(null);
|
|
41543
|
+
const releasePaneAtRest = () => {
|
|
41544
|
+
const heldPane = heldPaneRef.current;
|
|
41545
|
+
if (heldPane) {
|
|
41546
|
+
heldPaneRef.current = null;
|
|
41547
|
+
heldPane.removeAttribute("data-route-row-held");
|
|
41548
|
+
}
|
|
41549
|
+
};
|
|
41550
|
+
|
|
41539
41551
|
// One movement, whoever set it off, from the first thing that moves to the row
|
|
41540
|
-
// at rest. Null when nothing moves.
|
|
41541
|
-
|
|
41552
|
+
// at rest. Null when nothing moves. `paneAtRest` is the pane the row rests on as it
|
|
41553
|
+
// begins: laid out as the page on screen until the row rests again, wherever the
|
|
41554
|
+
// address goes meanwhile (see the CSS).
|
|
41555
|
+
const beginMovement = (fields, paneAtRest) => {
|
|
41542
41556
|
const row = rowRef.current;
|
|
41543
41557
|
if (movementRef.current) {
|
|
41544
41558
|
Object.assign(movementRef.current, fields);
|
|
@@ -41553,6 +41567,11 @@ const RouteTravel = ({
|
|
|
41553
41567
|
...fields
|
|
41554
41568
|
};
|
|
41555
41569
|
movementRef.current = movement;
|
|
41570
|
+
// In a window the page is a picture, contained with what it holds.
|
|
41571
|
+
if (!windowed && paneAtRest) {
|
|
41572
|
+
heldPaneRef.current = paneAtRest;
|
|
41573
|
+
paneAtRest.setAttribute("data-route-row-held", "");
|
|
41574
|
+
}
|
|
41556
41575
|
if (movement.scrollsDocument) {
|
|
41557
41576
|
// Whatever the browser does with the offset meanwhile (clamping it to a page
|
|
41558
41577
|
// shorter than the trip) is not the reader scrolling, and the address it
|
|
@@ -41790,24 +41809,6 @@ const RouteTravel = ({
|
|
|
41790
41809
|
rowRef.current.removeAttribute("data-route-row-travelling");
|
|
41791
41810
|
};
|
|
41792
41811
|
|
|
41793
|
-
// The address runs ahead of a hand to build the page it pulls towards (onScroll):
|
|
41794
|
-
// the pane under the hand stays laid out as the page on screen until the hand lets
|
|
41795
|
-
// go, whatever `inert` says (see the CSS). What reads the page held is told apart
|
|
41796
|
-
// (route_travel_hold.js), and both are let go of together.
|
|
41797
|
-
const heldPaneRef = useRef(null);
|
|
41798
|
-
const holdPaneOnScreen = pane => {
|
|
41799
|
-
heldPaneRef.current = pane;
|
|
41800
|
-
pane.setAttribute("data-route-row-held", "");
|
|
41801
|
-
};
|
|
41802
|
-
const releaseHold = () => {
|
|
41803
|
-
releaseHeldPage();
|
|
41804
|
-
const heldPane = heldPaneRef.current;
|
|
41805
|
-
if (heldPane) {
|
|
41806
|
-
heldPaneRef.current = null;
|
|
41807
|
-
heldPane.removeAttribute("data-route-row-held");
|
|
41808
|
-
}
|
|
41809
|
-
};
|
|
41810
|
-
|
|
41811
41812
|
// Every column back where the map says, and the row on the page of the address, in
|
|
41812
41813
|
// one go: that page stands at the same place on screen either way.
|
|
41813
41814
|
const putRowBack = movement => {
|
|
@@ -41836,7 +41837,7 @@ const RouteTravel = ({
|
|
|
41836
41837
|
const position = row.scrollLeft;
|
|
41837
41838
|
movement.rowTaken = true;
|
|
41838
41839
|
// A travel no hand set off holds nothing: it goes where it goes.
|
|
41839
|
-
|
|
41840
|
+
releaseHeldPage();
|
|
41840
41841
|
row.style.setProperty("--navi-route-row-stand", `${-position}px`);
|
|
41841
41842
|
row.setAttribute("data-route-row-taken", "");
|
|
41842
41843
|
afterFrames(2, () => {
|
|
@@ -41868,7 +41869,8 @@ const RouteTravel = ({
|
|
|
41868
41869
|
putRowBack(movement);
|
|
41869
41870
|
row.removeAttribute("data-route-row-taken");
|
|
41870
41871
|
publishRouteRowMove(null);
|
|
41871
|
-
|
|
41872
|
+
releaseHeldPage();
|
|
41873
|
+
releasePaneAtRest();
|
|
41872
41874
|
if (liveRef.current) {
|
|
41873
41875
|
liveRef.current.style.top = "";
|
|
41874
41876
|
}
|
|
@@ -41981,9 +41983,11 @@ const RouteTravel = ({
|
|
|
41981
41983
|
return;
|
|
41982
41984
|
}
|
|
41983
41985
|
const movementBefore = movementRef.current;
|
|
41986
|
+
// The row still rests on the page answered before. Held in the commit that made
|
|
41987
|
+
// its pane inert, so no frame paints it contained.
|
|
41984
41988
|
const movement = beginMovement({
|
|
41985
41989
|
travelling: true
|
|
41986
|
-
});
|
|
41990
|
+
}, panesOf(row)[pages.findIndex(each => samePage(each, answered))]);
|
|
41987
41991
|
if (movementBefore?.byHand && !movementBefore.rowTaken) {
|
|
41988
41992
|
sayRowMoves();
|
|
41989
41993
|
takeRowFromHand(movement);
|
|
@@ -42051,7 +42055,7 @@ const RouteTravel = ({
|
|
|
42051
42055
|
}
|
|
42052
42056
|
const movement = moving || beginMovement({
|
|
42053
42057
|
byHand: true
|
|
42054
|
-
});
|
|
42058
|
+
}, pane);
|
|
42055
42059
|
sayRowMoves();
|
|
42056
42060
|
if (movement.travelling || !travelByDrag || !pane) {
|
|
42057
42061
|
return;
|
|
@@ -42088,12 +42092,8 @@ const RouteTravel = ({
|
|
|
42088
42092
|
return;
|
|
42089
42093
|
}
|
|
42090
42094
|
leavePictureOfLive(movement);
|
|
42091
|
-
} else {
|
|
42092
|
-
|
|
42093
|
-
return;
|
|
42094
|
-
}
|
|
42095
|
-
// Before the address moves: the render that makes it inert must find it held.
|
|
42096
|
-
holdPaneOnScreen(pane);
|
|
42095
|
+
} else if (paneTowards.hasAttribute("data-built")) {
|
|
42096
|
+
return;
|
|
42097
42097
|
}
|
|
42098
42098
|
holdPage(pages[currentIndex], page);
|
|
42099
42099
|
askFor(page);
|
|
@@ -42124,8 +42124,9 @@ const RouteTravel = ({
|
|
|
42124
42124
|
askForPageOfRow(index, movement);
|
|
42125
42125
|
}
|
|
42126
42126
|
// After the address: a pull coming back to its page must not light the tab the
|
|
42127
|
-
// address ran ahead to, even for a render.
|
|
42128
|
-
|
|
42127
|
+
// address ran ahead to, even for a render. Only what readers are told: the
|
|
42128
|
+
// panes keep their layout until the row rests.
|
|
42129
|
+
releaseHeldPage();
|
|
42129
42130
|
};
|
|
42130
42131
|
const stopWatching = watchRowScroll(row, {
|
|
42131
42132
|
onScroll,
|
|
@@ -42181,7 +42182,8 @@ const RouteTravel = ({
|
|
|
42181
42182
|
row.removeAttribute("data-route-row-travelling");
|
|
42182
42183
|
movementRef.current = null;
|
|
42183
42184
|
publishRouteRowMove(null);
|
|
42184
|
-
|
|
42185
|
+
releaseHeldPage();
|
|
42186
|
+
releasePaneAtRest();
|
|
42185
42187
|
};
|
|
42186
42188
|
}, [windowed]);
|
|
42187
42189
|
|
|
@@ -75470,18 +75472,19 @@ const readRoom = () =>
|
|
|
75470
75472
|
visualViewportHeightSignal.peek() - getVirtualKeyboardOverlayHeight();
|
|
75471
75473
|
|
|
75472
75474
|
/**
|
|
75473
|
-
* Pushing a popup docked to an edge (a side panel, a bottom sheet
|
|
75474
|
-
* it came in closes it: it follows the finger while it travels,
|
|
75475
|
-
* either finishes the departure or brings it back to rest —
|
|
75476
|
-
* travel was already heading to.
|
|
75475
|
+
* Pushing a popup docked to an edge (a side panel, a bottom sheet, a toast)
|
|
75476
|
+
* back the way it came in closes it: it follows the finger while it travels,
|
|
75477
|
+
* and letting go either finishes the departure or brings it back to rest —
|
|
75478
|
+
* whichever the travel was already heading to. A popup may be given other ways
|
|
75479
|
+
* out than that one: a toast is also thrown sideways.
|
|
75477
75480
|
*
|
|
75478
75481
|
* Reading the pointer is not done here: when a press becomes a gesture, which
|
|
75479
75482
|
* axis it leans on, how fast it was going when it was let go, who else has a
|
|
75480
75483
|
* claim on it (a field, a scroller with room left, a box inside the panel that
|
|
75481
75484
|
* travels the same way) is one gesture system for the whole codebase
|
|
75482
75485
|
* (@jsenv/dom's startDragToTravel). A panel closing is a travel of exactly one
|
|
75483
|
-
* box,
|
|
75484
|
-
* file has to say.
|
|
75486
|
+
* box, out by one of the edges it may leave by and nowhere else — which is all
|
|
75487
|
+
* this file has to say.
|
|
75485
75488
|
*
|
|
75486
75489
|
* The release travel is driven here (Web Animations) rather than handed to the
|
|
75487
75490
|
* exit transition the `animation` prop may install: its pace is what is left to
|
|
@@ -75496,7 +75499,7 @@ const readRoom = () =>
|
|
|
75496
75499
|
// less, so the panel keeps the same speed whenever the finger let go.
|
|
75497
75500
|
const TRAVEL_DURATION = 220;
|
|
75498
75501
|
|
|
75499
|
-
// Which way a popup
|
|
75502
|
+
// Which way a popup leaving by each side travels — read by the panel itself to
|
|
75500
75503
|
// say so in the DOM, which is how a box travelling INSIDE it (a row of slides,
|
|
75501
75504
|
// a route travel) knows that axis is already walked.
|
|
75502
75505
|
const SWIPE_AXIS_BY_SIDE = {
|
|
@@ -75505,12 +75508,27 @@ const SWIPE_AXIS_BY_SIDE = {
|
|
|
75505
75508
|
top: "y",
|
|
75506
75509
|
bottom: "y",
|
|
75507
75510
|
};
|
|
75508
|
-
// Which way the finger pushes to send the panel
|
|
75509
|
-
//
|
|
75511
|
+
// Which way the finger pushes to send the panel out by each side, in screen
|
|
75512
|
+
// coordinates: positive is right/down, the same sign the gesture reports.
|
|
75510
75513
|
const CLOSE_DIRECTION_BY_SIDE = { left: -1, right: 1, top: -1, bottom: 1 };
|
|
75511
75514
|
|
|
75512
75515
|
/**
|
|
75513
|
-
*
|
|
75516
|
+
* The axes a popup leaving by `sides` travels on, as the gesture and the DOM
|
|
75517
|
+
* say them: "x", "y" or "xy".
|
|
75518
|
+
*/
|
|
75519
|
+
const swipeAxesOf = (sides) => {
|
|
75520
|
+
const hasX = sides.some((side) => SWIPE_AXIS_BY_SIDE[side] === "x");
|
|
75521
|
+
const hasY = sides.some((side) => SWIPE_AXIS_BY_SIDE[side] === "y");
|
|
75522
|
+
if (hasX && hasY) {
|
|
75523
|
+
return "xy";
|
|
75524
|
+
}
|
|
75525
|
+
return hasX ? "x" : "y";
|
|
75526
|
+
};
|
|
75527
|
+
|
|
75528
|
+
/**
|
|
75529
|
+
* Builds the `pointerdown` handler of a popup that leaves by any of `sides`:
|
|
75530
|
+
* usually the one edge it is docked to, the way it came in; a toast is also
|
|
75531
|
+
* thrown sideways, and leaves the way it was thrown.
|
|
75514
75532
|
*
|
|
75515
75533
|
* `grip` says where the gesture may start, as a selector the pressed element is
|
|
75516
75534
|
* matched against with `closest`. A popup that names one is held THERE and
|
|
@@ -75518,11 +75536,17 @@ const CLOSE_DIRECTION_BY_SIDE = { left: -1, right: 1, top: -1, bottom: 1 };
|
|
|
75518
75536
|
* and a press on it is that content's — including gestures of its own, which
|
|
75519
75537
|
* this file has no way of knowing about. A popup that names no grip is pushed
|
|
75520
75538
|
* from its whole surface, which only suits one made of nothing else (a side
|
|
75521
|
-
* panel showing a page).
|
|
75522
|
-
*/
|
|
75523
|
-
const createSwipeToClose = (
|
|
75524
|
-
const
|
|
75525
|
-
|
|
75539
|
+
* panel showing a page, a toast).
|
|
75540
|
+
*/
|
|
75541
|
+
const createSwipeToClose = (sides, { grip } = {}) => {
|
|
75542
|
+
const axes = swipeAxesOf(sides);
|
|
75543
|
+
// The side a push along `axis` towards `sign` leaves by, if it leaves at all.
|
|
75544
|
+
const sideTowards = (axis, sign) =>
|
|
75545
|
+
sides.find(
|
|
75546
|
+
(side) =>
|
|
75547
|
+
SWIPE_AXIS_BY_SIDE[side] === axis &&
|
|
75548
|
+
CLOSE_DIRECTION_BY_SIDE[side] === sign,
|
|
75549
|
+
);
|
|
75526
75550
|
|
|
75527
75551
|
return (pointerDownEvent) => {
|
|
75528
75552
|
const panelEl = pointerDownEvent.currentTarget;
|
|
@@ -75539,6 +75563,11 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
75539
75563
|
}
|
|
75540
75564
|
}
|
|
75541
75565
|
|
|
75566
|
+
// Decided when the finger picks its axis (onStart), once for the gesture.
|
|
75567
|
+
let axis;
|
|
75568
|
+
// How far the panel travels to be out, for each way it can go on that axis.
|
|
75569
|
+
const distanceOutBySign = new Map();
|
|
75570
|
+
|
|
75542
75571
|
// How far the panel has been pulled, as the style showing it — written on
|
|
75543
75572
|
// the panel while the finger drives it, handed to the release travel as
|
|
75544
75573
|
// its two keyframes. The gesture reports a distance in screen coordinates,
|
|
@@ -75548,7 +75577,7 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
75548
75577
|
// composes under the placement instead of replacing it.
|
|
75549
75578
|
const styleAt = (distance) => ({
|
|
75550
75579
|
transform: translateOf(axis, distance),
|
|
75551
|
-
clipPath: clipOf(
|
|
75580
|
+
clipPath: clipOf(sides, axis, distance),
|
|
75552
75581
|
});
|
|
75553
75582
|
const paint = (distance) => {
|
|
75554
75583
|
Object.assign(panelEl.style, styleAt(distance));
|
|
@@ -75560,21 +75589,22 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
75560
75589
|
};
|
|
75561
75590
|
// The resting value is written first and the animation only covers the way
|
|
75562
75591
|
// to it: both end on the same number, so nothing is seen changing when the
|
|
75563
|
-
// animation hands the panel back to its own style.
|
|
75564
|
-
|
|
75592
|
+
// animation hands the panel back to its own style. `span` is the full travel
|
|
75593
|
+
// on that side, which the duration is a fraction of.
|
|
75594
|
+
const travelTo = (from, to, span, onArrival) => {
|
|
75565
75595
|
const covered = from > to ? from - to : to - from;
|
|
75566
75596
|
paint(to);
|
|
75567
75597
|
const animation = panelEl.animate([styleAt(from), styleAt(to)], {
|
|
75568
|
-
duration: (covered /
|
|
75598
|
+
duration: (covered / span) * TRAVEL_DURATION,
|
|
75569
75599
|
easing: "ease-out",
|
|
75570
75600
|
});
|
|
75571
75601
|
animation.finished.then(onArrival, () => {});
|
|
75572
75602
|
};
|
|
75573
|
-
const close = (event) => {
|
|
75603
|
+
const close = (event, out, span) => {
|
|
75574
75604
|
triggerNaviCommand(panelEl, "--navi-close", event);
|
|
75575
75605
|
if (panelEl.getAttribute("aria-expanded") === "true") {
|
|
75576
75606
|
// Refused: the panel is staying, so it goes back where it was.
|
|
75577
|
-
travelTo(
|
|
75607
|
+
travelTo(out, 0, span, restore);
|
|
75578
75608
|
return;
|
|
75579
75609
|
}
|
|
75580
75610
|
restore();
|
|
@@ -75582,79 +75612,94 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
75582
75612
|
|
|
75583
75613
|
startDragToTravel(pointerDownEvent, {
|
|
75584
75614
|
element: panelEl,
|
|
75585
|
-
axes
|
|
75586
|
-
onStart: ({ sign, target }) => {
|
|
75587
|
-
|
|
75615
|
+
axes,
|
|
75616
|
+
onStart: ({ axis: gestureAxis, sign, target }) => {
|
|
75617
|
+
const side = sideTowards(gestureAxis, sign);
|
|
75618
|
+
if (!side) {
|
|
75588
75619
|
// Pushing the panel further in is not a travel: there is nowhere that
|
|
75589
75620
|
// way, and reading it as one would make the panel lean at every
|
|
75590
75621
|
// gesture that starts by going the wrong way.
|
|
75591
75622
|
return false;
|
|
75592
75623
|
}
|
|
75593
|
-
const size =
|
|
75624
|
+
const size = distanceOut(panelEl, side);
|
|
75594
75625
|
if (!size) {
|
|
75595
75626
|
return false;
|
|
75596
75627
|
}
|
|
75597
75628
|
// Something else with a better claim on the gesture: a scroller between
|
|
75598
75629
|
// the finger and the panel, with room left that way.
|
|
75599
|
-
if (scrollRoomTowards(target, panelEl,
|
|
75630
|
+
if (scrollRoomTowards(target, panelEl, gestureAxis, sign)) {
|
|
75600
75631
|
return false;
|
|
75601
75632
|
}
|
|
75633
|
+
axis = gestureAxis;
|
|
75634
|
+
distanceOutBySign.set(sign, size);
|
|
75635
|
+
const otherSide = sideTowards(gestureAxis, -sign);
|
|
75636
|
+
if (otherSide) {
|
|
75637
|
+
distanceOutBySign.set(-sign, distanceOut(panelEl, otherSide));
|
|
75638
|
+
}
|
|
75602
75639
|
panelEl.style.transitionProperty = "none";
|
|
75603
|
-
//
|
|
75604
|
-
// leans against a wall and comes back.
|
|
75640
|
+
// Only the ways out lead anywhere: pulling towards a side the panel
|
|
75641
|
+
// does not leave by leans against a wall and comes back.
|
|
75605
75642
|
return {
|
|
75606
75643
|
size,
|
|
75607
|
-
travelBack:
|
|
75608
|
-
travelOn:
|
|
75644
|
+
travelBack: Boolean(sideTowards(gestureAxis, 1)),
|
|
75645
|
+
travelOn: Boolean(sideTowards(gestureAxis, -1)),
|
|
75609
75646
|
};
|
|
75610
75647
|
},
|
|
75611
75648
|
onPull: ({ pulled }) => {
|
|
75612
75649
|
paint(pulled);
|
|
75613
75650
|
},
|
|
75614
|
-
onEnd: ({ pulled, size, travels, event }) => {
|
|
75651
|
+
onEnd: ({ pulled, size, sign, travels, event }) => {
|
|
75615
75652
|
if (travels) {
|
|
75616
|
-
|
|
75653
|
+
const span = distanceOutBySign.get(sign);
|
|
75654
|
+
travelTo(pulled, span * sign, span, () =>
|
|
75655
|
+
close(event, span * sign, span),
|
|
75656
|
+
);
|
|
75617
75657
|
return;
|
|
75618
75658
|
}
|
|
75619
|
-
travelTo(pulled, 0, restore);
|
|
75659
|
+
travelTo(pulled, 0, size, restore);
|
|
75620
75660
|
},
|
|
75621
75661
|
});
|
|
75622
75662
|
};
|
|
75623
75663
|
};
|
|
75624
75664
|
|
|
75625
|
-
|
|
75665
|
+
// What takes the popup out of sight: its own size, plus the gap a margin keeps
|
|
75666
|
+
// between it and the edge it is pushed through — the room applyNewPosition
|
|
75667
|
+
// publishes on that side. Travelling its size alone, a popup kept off its edge
|
|
75668
|
+
// (a toast 12px above a bar) would stop with that much of itself still showing.
|
|
75669
|
+
const distanceOut = (element, side) => {
|
|
75626
75670
|
const rect = element.getBoundingClientRect();
|
|
75627
|
-
|
|
75671
|
+
const size = SWIPE_AXIS_BY_SIDE[side] === "x" ? rect.width : rect.height;
|
|
75672
|
+
const room = parseFloat(
|
|
75673
|
+
element.style.getPropertyValue(`--container-position-room-${side}`),
|
|
75674
|
+
);
|
|
75675
|
+
return Number.isNaN(room) ? size : size + room;
|
|
75628
75676
|
};
|
|
75629
75677
|
const translateOf = (axis, distance) =>
|
|
75630
75678
|
axis === "x"
|
|
75631
75679
|
? `translate(${distance}px, 0px)`
|
|
75632
75680
|
: `translate(0px, ${distance}px)`;
|
|
75633
75681
|
// The same cut the entry/exit animation makes (popup_css.js), at a distance the
|
|
75634
|
-
// finger decides instead of a transition:
|
|
75635
|
-
//
|
|
75636
|
-
// edge where a band of glass lies past it, 100vmax out where the
|
|
75637
|
-
// edge does the cutting),
|
|
75638
|
-
//
|
|
75639
|
-
// the box — the pull never takes the popup
|
|
75640
|
-
// only shave what it legitimately paints
|
|
75641
|
-
// policy from popup_css.js rather than the
|
|
75642
|
-
// (a Web Animation on transform AND clip-path,
|
|
75643
|
-
// cut the entrance would not.
|
|
75682
|
+
// finger decides instead of a transition: each edge the popup may leave by on
|
|
75683
|
+
// the axis it travels, as popup_css.js placed it (--x-popup-cut-*-at-area — on
|
|
75684
|
+
// the area's edge where a band of glass lies past it, 100vmax out where the
|
|
75685
|
+
// window's own edge does the cutting), moved by what has been pulled towards
|
|
75686
|
+
// it, so the cut holds that line while the box travels under it, whichever way.
|
|
75687
|
+
// Every other side is left far outside the box — the pull never takes the popup
|
|
75688
|
+
// past them, and cutting there would only shave what it legitimately paints
|
|
75689
|
+
// outside its own box. Reading the policy from popup_css.js rather than the
|
|
75690
|
+
// rooms keeps the release travel (a Web Animation on transform AND clip-path,
|
|
75691
|
+
// two clocks) from animating a cut the entrance would not.
|
|
75644
75692
|
const UNCUT = "-100vmax";
|
|
75645
|
-
const
|
|
75646
|
-
|
|
75647
|
-
const
|
|
75648
|
-
|
|
75649
|
-
|
|
75650
|
-
|
|
75651
|
-
|
|
75652
|
-
return `
|
|
75653
|
-
}
|
|
75654
|
-
|
|
75655
|
-
return `inset(${UNCUT} ${UNCUT} ${cut} ${UNCUT})`;
|
|
75656
|
-
}
|
|
75657
|
-
return `inset(${UNCUT} ${UNCUT} ${UNCUT} ${cut})`;
|
|
75693
|
+
const INSET_ORDER = ["top", "right", "bottom", "left"];
|
|
75694
|
+
const clipOf = (sides, axis, distance) => {
|
|
75695
|
+
const insets = INSET_ORDER.map((side) => {
|
|
75696
|
+
if (!sides.includes(side) || SWIPE_AXIS_BY_SIDE[side] !== axis) {
|
|
75697
|
+
return UNCUT;
|
|
75698
|
+
}
|
|
75699
|
+
const towards = distance * CLOSE_DIRECTION_BY_SIDE[side];
|
|
75700
|
+
return `calc(var(--x-popup-cut-${side}-at-area, ${UNCUT}) + ${towards}px)`;
|
|
75701
|
+
});
|
|
75702
|
+
return `inset(${insets.join(" ")})`;
|
|
75658
75703
|
};
|
|
75659
75704
|
|
|
75660
75705
|
/**
|
|
@@ -77436,7 +77481,7 @@ const useDialogProps = props => {
|
|
|
77436
77481
|
// actually resting on an edge: anywhere else the gesture would send the
|
|
77437
77482
|
// dialog somewhere it never came from.
|
|
77438
77483
|
const swipeToCloseSide = isDocked && (parsedPositionArea.y === "top" || parsedPositionArea.y === "bottom") ? parsedPositionArea.y : null;
|
|
77439
|
-
const onSwipePointerDown = swipeToCloseSide ? createSwipeToClose(swipeToCloseSide, {
|
|
77484
|
+
const onSwipePointerDown = swipeToCloseSide ? createSwipeToClose([swipeToCloseSide], {
|
|
77440
77485
|
grip: DOCKED_SWIPE_GRIP
|
|
77441
77486
|
}) : null;
|
|
77442
77487
|
// A corner sitting exactly on the container's own corner must not be
|
|
@@ -78529,6 +78574,16 @@ const css$C = /* css */`
|
|
|
78529
78574
|
to filter). An entrance animation moves the popover through scale and
|
|
78530
78575
|
transform instead, which compose under it: see popup_css.js. */
|
|
78531
78576
|
|
|
78577
|
+
/* What a touch may do on a popover pushed sideways to close it (see
|
|
78578
|
+
swipe_to_close.js): the push takes the sideways axis, the page keeps the
|
|
78579
|
+
vertical scroll. A push up or down travels the way the page scrolls and
|
|
78580
|
+
cannot give that axis away: the browser keeps it, and the gesture is
|
|
78581
|
+
settled by keepTouchRefusable. */
|
|
78582
|
+
&[data-swipe-to-close~="left"],
|
|
78583
|
+
&[data-swipe-to-close~="right"] {
|
|
78584
|
+
touch-action: pan-y;
|
|
78585
|
+
}
|
|
78586
|
+
|
|
78532
78587
|
/* The clamped max, not --popover-maxmax-*: that one ignores what the
|
|
78533
78588
|
placement left available, which reaches here through
|
|
78534
78589
|
--container-position-remaining-* (see applyNewPosition) — the min()
|
|
@@ -78759,6 +78814,20 @@ const css$C = /* css */`
|
|
|
78759
78814
|
* overlap with the anchor; wrap it in `inset(...)` (e.g. `inset(top)`,
|
|
78760
78815
|
* `inset(top-left)`) to overlap the anchor instead (edges touching or
|
|
78761
78816
|
* inside it).
|
|
78817
|
+
* @param {boolean|Array<"top"|"right"|"bottom"|"left">} [props.closeByDrag=false] -
|
|
78818
|
+
* Pushing the popover out of its container closes it: it follows the pointer
|
|
78819
|
+
* and, when released, finishes leaving or comes back to rest. `true` pushes
|
|
78820
|
+
* it back through the edge it is docked to, the word `positionArea` leads
|
|
78821
|
+
* with (`"bottom"`, `"bottom-start"`: pushed down; `"left-end"`: pushed
|
|
78822
|
+
* left); a centred popover rests on no edge and is not pushed (warned in
|
|
78823
|
+
* dev). A list names the sides it may leave by, and it leaves the way it was
|
|
78824
|
+
* pushed: `["bottom", "left", "right"]` for a toast pushed back down behind
|
|
78825
|
+
* the bar or thrown sideways. A popover placed against an anchor at its
|
|
78826
|
+
* opening is not pushed either way (warned in dev). It is pushed from its
|
|
78827
|
+
* whole surface, so it suits a popover holding little else: a button inside
|
|
78828
|
+
* keeps its tap, and a press that turns into a push swallows the click it
|
|
78829
|
+
* would have left. The close keeps, as a cross's does. See docs/interactions.md
|
|
78830
|
+
* for a swipe on the popover's own element, which does not move it.
|
|
78762
78831
|
* @param {string} [props.positionAreaFixed] - Overrides `positionArea` once
|
|
78763
78832
|
* the popover has actually been positioned once, so a live reposition
|
|
78764
78833
|
* (e.g. anchor moved) doesn't jump to a different side.
|
|
@@ -79210,6 +79279,7 @@ const usePopoverProps = props => {
|
|
|
79210
79279
|
positionAreaWhenAnchorIsInvalid,
|
|
79211
79280
|
marginWithContainer = 0,
|
|
79212
79281
|
container = "app",
|
|
79282
|
+
closeByDrag = false,
|
|
79213
79283
|
// "close" → pointer press outside closes (a plain close = commit)
|
|
79214
79284
|
// "cancel" → pointer press outside closes AS A CANCEL (revert)
|
|
79215
79285
|
// "capture"→ absorb the press, stay open
|
|
@@ -79337,6 +79407,7 @@ const usePopoverProps = props => {
|
|
|
79337
79407
|
return popupGroup.register(openController);
|
|
79338
79408
|
}, [popupGroup, openController]);
|
|
79339
79409
|
const hasBackdrop = pressOutside !== "ignore" && backdrop && !popupGroup;
|
|
79410
|
+
const swipeToCloseSides = resolveSwipeToCloseSides(closeByDrag, positionArea);
|
|
79340
79411
|
if (!popupGroup && !backdrop && pressOutside === "capture") {
|
|
79341
79412
|
console.warn(`Popover: pressOutside="capture" needs a backdrop. Absorbing a press is what a wall does, and backdrop={false} takes it away — every press outside reaches the page, exactly as with "ignore".`);
|
|
79342
79413
|
}
|
|
@@ -80170,6 +80241,7 @@ const usePopoverProps = props => {
|
|
|
80170
80241
|
}
|
|
80171
80242
|
}
|
|
80172
80243
|
});
|
|
80244
|
+
const swipeToClose = swipeToCloseSides ? createSwipeToClose(swipeToCloseSides) : null;
|
|
80173
80245
|
Object.assign(contentProps, {
|
|
80174
80246
|
id,
|
|
80175
80247
|
tabIndex,
|
|
@@ -80248,6 +80320,26 @@ const usePopoverProps = props => {
|
|
|
80248
80320
|
onKeyDown?.(e);
|
|
80249
80321
|
onKeyDownShortcuts(e);
|
|
80250
80322
|
},
|
|
80323
|
+
...(swipeToClose ? {
|
|
80324
|
+
"data-swipe-to-close": swipeToCloseSides.join(" "),
|
|
80325
|
+
// The axes it travels on when pushed out, said to the shared gesture
|
|
80326
|
+
// layer before any finger lands (see side_panel.jsx, which says the
|
|
80327
|
+
// same for the same reasons).
|
|
80328
|
+
"data-drag-travel": swipeAxesOf(swipeToCloseSides),
|
|
80329
|
+
"data-travel-by-drag": swipeAxesOf(swipeToCloseSides),
|
|
80330
|
+
"onTouchMove": e => {
|
|
80331
|
+
rest.onTouchMove?.(e);
|
|
80332
|
+
keepTouchRefusable(e);
|
|
80333
|
+
},
|
|
80334
|
+
"onPointerDown": e => {
|
|
80335
|
+
rest.onPointerDown?.(e);
|
|
80336
|
+
// Placed against an anchor at this opening: it is not in its
|
|
80337
|
+
// container to be pushed out of (warned at the opening).
|
|
80338
|
+
if (!anchorElementRef.current) {
|
|
80339
|
+
swipeToClose(e);
|
|
80340
|
+
}
|
|
80341
|
+
}
|
|
80342
|
+
} : null),
|
|
80251
80343
|
// A popup opened from inside this one is in front of it, not a peer of
|
|
80252
80344
|
// it: the group stops here.
|
|
80253
80345
|
"children": popupGroup ? jsx(PopupGroupContext.Provider, {
|
|
@@ -80321,6 +80413,31 @@ const resolvePositionAreaAndAnimationKind = ({
|
|
|
80321
80413
|
Popover.Close = PopupClose;
|
|
80322
80414
|
Popover.Title = PopupTitle;
|
|
80323
80415
|
|
|
80416
|
+
// The sides a popover pushed away leaves by, or null when it is not pushed.
|
|
80417
|
+
const resolveSwipeToCloseSides = (closeByDrag, positionArea) => {
|
|
80418
|
+
if (!closeByDrag) {
|
|
80419
|
+
return null;
|
|
80420
|
+
}
|
|
80421
|
+
if (Array.isArray(closeByDrag)) {
|
|
80422
|
+
const sides = closeByDrag.filter(side => side in SWIPE_AXIS_BY_SIDE);
|
|
80423
|
+
return sides.length ? sides : null;
|
|
80424
|
+
}
|
|
80425
|
+
const side = dockedSideOf(positionArea);
|
|
80426
|
+
if (!side) {
|
|
80427
|
+
return null;
|
|
80428
|
+
}
|
|
80429
|
+
return [side];
|
|
80430
|
+
};
|
|
80431
|
+
const INSET_TOKEN_RE = /^inset\(\s*([a-z-]+)\s*\)$/;
|
|
80432
|
+
// The edge a popover docked to its container rests on: the word its
|
|
80433
|
+
// positionArea leads with, "bottom-start" resting on the bottom and "left-end"
|
|
80434
|
+
// on the left. "center" rests on none.
|
|
80435
|
+
const dockedSideOf = positionArea => {
|
|
80436
|
+
const token = INSET_TOKEN_RE.exec(positionArea)?.[1] ?? positionArea;
|
|
80437
|
+
const side = token.split("-")[0];
|
|
80438
|
+
return side in SWIPE_AXIS_BY_SIDE ? side : null;
|
|
80439
|
+
};
|
|
80440
|
+
|
|
80324
80441
|
// The placement needs pixels: a value only CSS could evaluate (a percentage, a
|
|
80325
80442
|
// calc()…) would put the popover at NaN, which is the top of the page.
|
|
80326
80443
|
const resolveMarginInPixels = (margin, name) => {
|
|
@@ -108028,7 +108145,7 @@ const SidePanel = ({
|
|
|
108028
108145
|
}) => {
|
|
108029
108146
|
import.meta.css = [css, "@jsenv/navi/src/layout/side_panel.jsx"];
|
|
108030
108147
|
const rest = dropPickerPopupProps(restGiven);
|
|
108031
|
-
const onSwipePointerDown = closeByDrag ? createSwipeToClose(side) : null;
|
|
108148
|
+
const onSwipePointerDown = closeByDrag ? createSwipeToClose([side]) : null;
|
|
108032
108149
|
return jsx(Popup, {
|
|
108033
108150
|
mode: mode
|
|
108034
108151
|
// Spread rather than written: the collision warning between `signal` and
|