@jsenv/navi 0.29.132 → 0.29.133
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/jsenv_navi.js +462 -194
- package/dist/jsenv_navi.js.map +22 -13
- package/docs/AI_INSTRUCTIONS.md +3 -1
- package/docs/navigation.md +41 -2
- package/docs/scroll.md +5 -0
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, coarsePointerSignal, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
6
|
export { disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
|
|
7
|
-
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, initFocusGroup, scrollIntoViewScoped, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, isTouchDrivenEvent, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen,
|
|
7
|
+
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, initFocusGroup, scrollIntoViewScoped, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, isTouchDrivenEvent, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, canScroll, measureWidestChildRow, performTabNavigation, wheelGestureIsTakenFrom, releaseWheelGesture, claimWheelGesture, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
8
8
|
export { clickIsSuppressed, contrastColor, findEvent, startDragTo } from "@jsenv/dom";
|
|
9
9
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
10
10
|
import { isValidElement, h, Fragment, createContext, render, toChildArray, options, cloneElement } from "preact";
|
|
@@ -6081,10 +6081,11 @@ const css$11 = /* css */`
|
|
|
6081
6081
|
right where it's set) — same reasoning as Popover's identical
|
|
6082
6082
|
attribute (popover.jsx's own top comment has the full case). */
|
|
6083
6083
|
position: fixed;
|
|
6084
|
-
/* Popover resets
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6084
|
+
/* Popover resets. Laid out at its containing block's own origin and moved
|
|
6085
|
+
from there by a translate (applyNewPosition), never by left/top — see
|
|
6086
|
+
applyNewPosition's own doc for why the placement may not go through
|
|
6087
|
+
left/top themselves. */
|
|
6088
|
+
inset: 0 auto auto 0;
|
|
6088
6089
|
/* For some reason callout could end up behing elements when it's redisplayed in a dialog
|
|
6089
6090
|
(behind button relatively positioned in dialog footer while callout is appended into dialog body)
|
|
6090
6091
|
To ensure ti goes above we put a z-index: 1, I hope it won't bite use in the future */
|
|
@@ -6108,10 +6109,9 @@ const css$11 = /* css */`
|
|
|
6108
6109
|
border: none;
|
|
6109
6110
|
outline: none; /* programmatic focus may land here briefly before being redirected to close button */
|
|
6110
6111
|
opacity: 0;
|
|
6111
|
-
/*
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
Popover/Dialog use. */
|
|
6112
|
+
/* opacity only: the placement lives in the translate property, which
|
|
6113
|
+
applyNewPosition (visible_rect.js) owns and animates itself through the
|
|
6114
|
+
Web Animations API, same mechanism Popover/Dialog use. */
|
|
6115
6115
|
transition: opacity 0.2s ease-in-out;
|
|
6116
6116
|
cursor: initial; /* Do not inherit element cursor, inside the element but should use regular cursor */
|
|
6117
6117
|
pointer-events: auto; /* Must be interactive to be closabled (overrid list item pointer-events none for instance) */
|
|
@@ -25350,6 +25350,43 @@ const updateDocumentState = (value) => {
|
|
|
25350
25350
|
documentStateSignal.value = value;
|
|
25351
25351
|
};
|
|
25352
25352
|
|
|
25353
|
+
/**
|
|
25354
|
+
* What the history entry being written ends up holding.
|
|
25355
|
+
*
|
|
25356
|
+
* `state`:
|
|
25357
|
+
* - `undefined` — a neutral navigation: a link click, a `replaceUrl` writing a
|
|
25358
|
+
* search param. A **replace** stays on the entry the document is already on,
|
|
25359
|
+
* so what that entry holds stays with it. A **push** opens a NEW entry, and
|
|
25360
|
+
* what was written for the one being left — an open dialog, an expanded
|
|
25361
|
+
* picker (see `useNavState`) — describes that entry alone: carried forward it
|
|
25362
|
+
* would reopen on the next screen, and on the one after that, until something
|
|
25363
|
+
* mounting the same id opens out of nowhere.
|
|
25364
|
+
* - `null` — an explicit reset.
|
|
25365
|
+
* - an object — built by the caller (`enter()`/`leave()` copy the current state
|
|
25366
|
+
* themselves), taken as given.
|
|
25367
|
+
*
|
|
25368
|
+
* `sharedState` always wins: it describes the document, not the entry.
|
|
25369
|
+
*
|
|
25370
|
+
* The push/replace split is the Navigation API's own rule for a `navigate()`
|
|
25371
|
+
* carrying no state — which is why via_navigation.js gets it from the browser
|
|
25372
|
+
* and via_history.js has to spell it out to say the same thing.
|
|
25373
|
+
*/
|
|
25374
|
+
const resolveEffectiveDocumentState = (
|
|
25375
|
+
state,
|
|
25376
|
+
{ navigationType, currentState, sharedState },
|
|
25377
|
+
) => {
|
|
25378
|
+
if (state === undefined) {
|
|
25379
|
+
if (navigationType === "push") {
|
|
25380
|
+
return sharedState;
|
|
25381
|
+
}
|
|
25382
|
+
return { ...(currentState || {}), ...sharedState };
|
|
25383
|
+
}
|
|
25384
|
+
if (state === null) {
|
|
25385
|
+
return sharedState;
|
|
25386
|
+
}
|
|
25387
|
+
return { ...state, ...sharedState };
|
|
25388
|
+
};
|
|
25389
|
+
|
|
25353
25390
|
/**
|
|
25354
25391
|
* The document's rendering, held for the one frame a view transition needs.
|
|
25355
25392
|
*
|
|
@@ -25482,6 +25519,13 @@ const takeoverRoutingRenderingHold = () => {
|
|
|
25482
25519
|
* something still loading. Its content is not there at the moment it is put
|
|
25483
25520
|
* back, so a position beyond what has arrived is clamped as before. Only the
|
|
25484
25521
|
* page knows when it is whole.
|
|
25522
|
+
*
|
|
25523
|
+
* WHEN a page is arrived at is not decided here either. A document navigation
|
|
25524
|
+
* lands where its kind says (see via_history.js), and one scrollport can be
|
|
25525
|
+
* shared by pages the browser is never told apart: a row of tabs replaces the
|
|
25526
|
+
* url under the same document, so the arrival — and the deafness the swap
|
|
25527
|
+
* needs, see suspendScrollRecording — is asked for by the row itself (see
|
|
25528
|
+
* route_travel.jsx).
|
|
25485
25529
|
*/
|
|
25486
25530
|
|
|
25487
25531
|
|
|
@@ -25519,6 +25563,29 @@ const storePositions = () => {
|
|
|
25519
25563
|
}
|
|
25520
25564
|
};
|
|
25521
25565
|
|
|
25566
|
+
// The document is one scrollport for every page put in it, so a page swapped
|
|
25567
|
+
// under it for a shorter one is an offset the browser CLAMPS — and a clamp is
|
|
25568
|
+
// a scroll event like any other. It is not the reader scrolling, and by the
|
|
25569
|
+
// time it fires the url is already the arriving page's: written down, it is
|
|
25570
|
+
// that page's own position that the page being left destroys.
|
|
25571
|
+
//
|
|
25572
|
+
// Only whoever swaps the page knows when that is happening, so the deafness is
|
|
25573
|
+
// asked for from there and lasts exactly as long as the swap. Counted rather
|
|
25574
|
+
// than flagged: two swaps overlap — a travel relaying into the next one under
|
|
25575
|
+
// the same finger, a travel being undone while it plays.
|
|
25576
|
+
let suspendCount = 0;
|
|
25577
|
+
const suspendScrollRecording = () => {
|
|
25578
|
+
suspendCount++;
|
|
25579
|
+
let resumed = false;
|
|
25580
|
+
return () => {
|
|
25581
|
+
if (resumed) {
|
|
25582
|
+
return;
|
|
25583
|
+
}
|
|
25584
|
+
resumed = true;
|
|
25585
|
+
suspendCount--;
|
|
25586
|
+
};
|
|
25587
|
+
};
|
|
25588
|
+
|
|
25522
25589
|
let installed = false;
|
|
25523
25590
|
const installScrollRestoration = () => {
|
|
25524
25591
|
if (installed) {
|
|
@@ -25536,6 +25603,9 @@ const installScrollRestoration = () => {
|
|
|
25536
25603
|
window.addEventListener(
|
|
25537
25604
|
"scroll",
|
|
25538
25605
|
() => {
|
|
25606
|
+
if (suspendCount) {
|
|
25607
|
+
return;
|
|
25608
|
+
}
|
|
25539
25609
|
positionByUrl.set(window.location.href, {
|
|
25540
25610
|
x: window.scrollX,
|
|
25541
25611
|
y: window.scrollY,
|
|
@@ -25558,12 +25628,43 @@ const installScrollRestoration = () => {
|
|
|
25558
25628
|
|
|
25559
25629
|
// Nothing to put back is not the same as putting back the top: a page arrived
|
|
25560
25630
|
// at for the first time is startAtTop's business, and this must not step on it.
|
|
25631
|
+
// Whether there was anything, for a caller who has an answer of its own for the
|
|
25632
|
+
// page that has never been read.
|
|
25561
25633
|
const restoreScrollPosition = (url) => {
|
|
25562
25634
|
const position = positionByUrl.get(new URL(url, window.location.href).href);
|
|
25563
25635
|
if (!position) {
|
|
25564
|
-
return;
|
|
25636
|
+
return false;
|
|
25565
25637
|
}
|
|
25566
25638
|
scrollTo(position);
|
|
25639
|
+
return true;
|
|
25640
|
+
};
|
|
25641
|
+
|
|
25642
|
+
// A page one arrives at for the first time starts at its top. Only a document
|
|
25643
|
+
// navigation does that on its own: a pushState creates its entry with whatever
|
|
25644
|
+
// scroll happened to be there, so without this the page opens at the offset of
|
|
25645
|
+
// the one before it — and that borrowed offset is what is then remembered FOR
|
|
25646
|
+
// it, and handed back on the way forward.
|
|
25647
|
+
//
|
|
25648
|
+
// The document, because the document is the scrollport in the common case. An
|
|
25649
|
+
// app that scrolls an element of its own scrolls it itself.
|
|
25650
|
+
const startAtTop = (url) => {
|
|
25651
|
+
// A fragment names where to land, and the browser is the one that finds it.
|
|
25652
|
+
if (new URL(url, window.location.href).hash) {
|
|
25653
|
+
return;
|
|
25654
|
+
}
|
|
25655
|
+
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
|
|
25656
|
+
};
|
|
25657
|
+
|
|
25658
|
+
// An arrival at a page whose scrollport is already showing another one: the
|
|
25659
|
+
// tabs of a row share the document, and the offset on it is whichever tab was
|
|
25660
|
+
// last read. Where this one was read, and its top when it never was — leaving
|
|
25661
|
+
// the offset alone would seat the reader wherever the neighbour happened to
|
|
25662
|
+
// be, so here "nothing recorded" and "stay" are not the same thing.
|
|
25663
|
+
const arriveAtScrollPosition = (url) => {
|
|
25664
|
+
if (restoreScrollPosition(url)) {
|
|
25665
|
+
return;
|
|
25666
|
+
}
|
|
25667
|
+
startAtTop(url);
|
|
25567
25668
|
};
|
|
25568
25669
|
|
|
25569
25670
|
const scrollTo = ({ x, y }) => {
|
|
@@ -25907,28 +26008,14 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
25907
26008
|
|
|
25908
26009
|
if (navigationType === "push" || navigationType === "replace") {
|
|
25909
26010
|
markUrlAsVisited(url);
|
|
25910
|
-
|
|
25911
|
-
|
|
25912
|
-
|
|
25913
|
-
|
|
25914
|
-
|
|
25915
|
-
|
|
25916
|
-
|
|
25917
|
-
|
|
25918
|
-
};
|
|
25919
|
-
if (state === undefined) {
|
|
25920
|
-
effectiveState = {
|
|
25921
|
-
...(getDocumentState() || {}),
|
|
25922
|
-
...sharedState,
|
|
25923
|
-
};
|
|
25924
|
-
} else if (state === null) {
|
|
25925
|
-
effectiveState = sharedState;
|
|
25926
|
-
} else if (state) {
|
|
25927
|
-
effectiveState = {
|
|
25928
|
-
...state,
|
|
25929
|
-
...sharedState,
|
|
25930
|
-
};
|
|
25931
|
-
}
|
|
26011
|
+
const effectiveState = resolveEffectiveDocumentState(state, {
|
|
26012
|
+
navigationType,
|
|
26013
|
+
currentState: getDocumentState(),
|
|
26014
|
+
sharedState: {
|
|
26015
|
+
jsenv_visited_urls: Array.from(visitedUrlSet),
|
|
26016
|
+
[NAV_DEPTH_STATE_KEY]: getNavDepth(),
|
|
26017
|
+
},
|
|
26018
|
+
});
|
|
25932
26019
|
if (navigationType === "push") {
|
|
25933
26020
|
window.history.pushState(effectiveState, null, url);
|
|
25934
26021
|
} else {
|
|
@@ -25977,12 +26064,21 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
25977
26064
|
isVisited,
|
|
25978
26065
|
state,
|
|
25979
26066
|
});
|
|
26067
|
+
// Where the document lands, said by what kind of arrival this is. Both are
|
|
26068
|
+
// waited for, and for the same two reasons: the page has to be there to be
|
|
26069
|
+
// scrolled, and a picture taken before it would be of a page at its top
|
|
26070
|
+
// (see rendering_hold.js, which is where the waiting happens). After the
|
|
26071
|
+
// history has been written too, so the entry being left keeps the offset
|
|
26072
|
+
// it is at.
|
|
26073
|
+
//
|
|
26074
|
+
// A replace gets neither: it is the same place said differently — a param
|
|
26075
|
+
// settling, a state written — and moving the reader for it would throw
|
|
26076
|
+
// them out of a page they never left. The one replace that IS an arrival
|
|
26077
|
+
// is a row of tabs travelling, and the row says so for itself (see
|
|
26078
|
+
// route_travel.jsx).
|
|
25980
26079
|
if (navigationType === "push") {
|
|
25981
26080
|
whenRenderingResumes(() => startAtTop(url));
|
|
25982
26081
|
} else if (navigationType === "traverse") {
|
|
25983
|
-
// Where this entry was left. Waited for like the reset above, and for
|
|
25984
|
-
// the same two reasons: the page has to be there to be scrolled, and a
|
|
25985
|
-
// picture taken before it would be of a page at its top.
|
|
25986
26082
|
whenRenderingResumes(() => restoreScrollPosition(url));
|
|
25987
26083
|
}
|
|
25988
26084
|
executeWithCleanup(
|
|
@@ -26177,39 +26273,6 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
26177
26273
|
};
|
|
26178
26274
|
};
|
|
26179
26275
|
|
|
26180
|
-
// A page one arrives at for the first time starts at its top. Only a document
|
|
26181
|
-
// navigation does that on its own: a pushState creates its entry with whatever
|
|
26182
|
-
// scroll happened to be there, so without this the new page opens at the offset
|
|
26183
|
-
// of the one before it — and worse, that borrowed offset is what the browser
|
|
26184
|
-
// then remembers FOR that entry, and hands back on the way forward.
|
|
26185
|
-
//
|
|
26186
|
-
// Push only. A traverse is the browser's business and it is already right: it
|
|
26187
|
-
// keeps a position per entry and restores it. A replace is not an arrival —
|
|
26188
|
-
// it is the same place, said differently (a tab row travelling, see
|
|
26189
|
-
// route_travel.jsx), and resetting there would throw the reader out of a page
|
|
26190
|
-
// they never left.
|
|
26191
|
-
//
|
|
26192
|
-
// After the routes have been told, and after the picture of the page being
|
|
26193
|
-
// left has been taken — that ordering is the whole subtlety. The routes
|
|
26194
|
-
// changing is what sets a movement off, and a movement measures the box it is
|
|
26195
|
-
// leaving as it stands; put the document back to its top any earlier and the
|
|
26196
|
-
// picture is of a page at its first line, which the reader was not at. The
|
|
26197
|
-
// browser paints what the new offset shows and nothing else, so what is kept
|
|
26198
|
-
// of the page being left is the band it had already painted, and the movement
|
|
26199
|
-
// carries a fragment (see rendering_hold.js, which is where the waiting
|
|
26200
|
-
// happens). After pushState too, so the entry being left keeps the offset it
|
|
26201
|
-
// is at.
|
|
26202
|
-
//
|
|
26203
|
-
// The document, because the document is the scrollport in the common case. An
|
|
26204
|
-
// app that scrolls an element of its own scrolls it itself.
|
|
26205
|
-
const startAtTop = (url) => {
|
|
26206
|
-
// A fragment names where to land, and the browser is the one that finds it.
|
|
26207
|
-
if (new URL(url, window.location.href).hash) {
|
|
26208
|
-
return;
|
|
26209
|
-
}
|
|
26210
|
-
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
|
|
26211
|
-
};
|
|
26212
|
-
|
|
26213
26276
|
let updateRoutes;
|
|
26214
26277
|
|
|
26215
26278
|
const applyActions = (params) => {
|
|
@@ -26996,8 +27059,38 @@ const releaseTransitionDestination = (owner) => {
|
|
|
26996
27059
|
* the page arriving is in the DOM and the transition has not started playing.
|
|
26997
27060
|
* Everything DERIVED from these numbers — the band a fixed bar covers, how far
|
|
26998
27061
|
* a page travels — is derived in CSS, so the application's own numbers (the
|
|
26999
|
-
* room its bars give back, see layout/safe_area.js
|
|
27062
|
+
* room its bars give back, see layout/safe_area.js; what covers the box from
|
|
27063
|
+
* inside the document, --navi-transition-cover-* below) take part in it.
|
|
27064
|
+
*/
|
|
27065
|
+
|
|
27066
|
+
/**
|
|
27067
|
+
* What covers the box from INSIDE the document: a sticky row of tabs above the
|
|
27068
|
+
* pages, a header pinned to the top of a scroller. Declared at zero and
|
|
27069
|
+
* written by whoever covers it, exactly as a fixed bar publishes the room it
|
|
27070
|
+
* gives back (layout/safe_area.js).
|
|
27071
|
+
*
|
|
27072
|
+
* A slot rather than a measurement, and a slot navi cannot fill itself: the
|
|
27073
|
+
* safe area answers for what is pinned to the WINDOW's edges, and a sticky row
|
|
27074
|
+
* is none of those — it lives in the document, below the bars, and covers the
|
|
27075
|
+
* top of the box exactly as a fixed bar covers the top of the screen. The
|
|
27076
|
+
* pictures of a transition are drawn in the top layer, where no z-index of the
|
|
27077
|
+
* document reaches them, so what the document paints over the box has to be
|
|
27078
|
+
* counted here or it is the pictures that paint over it.
|
|
27079
|
+
*
|
|
27080
|
+
* Each one is a distance inward from the band the safe area already leaves
|
|
27081
|
+
* free, so a row states its own height and nothing else: what the bars above
|
|
27082
|
+
* it take is already counted.
|
|
27000
27083
|
*/
|
|
27084
|
+
const TRANSITION_WINDOW_CSS = /* css */ `
|
|
27085
|
+
@layer navi {
|
|
27086
|
+
:root {
|
|
27087
|
+
--navi-transition-cover-top: 0px;
|
|
27088
|
+
--navi-transition-cover-right: 0px;
|
|
27089
|
+
--navi-transition-cover-bottom: 0px;
|
|
27090
|
+
--navi-transition-cover-left: 0px;
|
|
27091
|
+
}
|
|
27092
|
+
}
|
|
27093
|
+
`;
|
|
27001
27094
|
|
|
27002
27095
|
const WINDOW_TOP_PROPERTY = "--navi-transition-window-top";
|
|
27003
27096
|
const WINDOW_LEFT_PROPERTY = "--navi-transition-window-left";
|
|
@@ -27144,6 +27237,8 @@ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
|
|
|
27144
27237
|
// included).
|
|
27145
27238
|
|
|
27146
27239
|
const css$10 = /* css */`
|
|
27240
|
+
${TRANSITION_WINDOW_CSS}
|
|
27241
|
+
|
|
27147
27242
|
/* The marked region is a picture of its own for the length of a transition of
|
|
27148
27243
|
OURS, and only then — the name is what makes the pages a picture the
|
|
27149
27244
|
movement below can carry.
|
|
@@ -27228,39 +27323,46 @@ const css$10 = /* css */`
|
|
|
27228
27323
|
height: var(--navi-transition-window-height);
|
|
27229
27324
|
animation-name: none;
|
|
27230
27325
|
|
|
27231
|
-
/* Cut at the
|
|
27232
|
-
pictures are drawn in the top layer, so they cover a fixed bar
|
|
27233
|
-
easily as anything else — and the area runs UNDER the bars by
|
|
27234
|
-
that is what a fixed bar is for, and what the room it gives
|
|
27235
|
-
for. An area taller than the screen therefore ends below the
|
|
27236
|
-
bar, and a scrolled one starts above the top bar, so the
|
|
27237
|
-
would be watched painting over them for its whole length.
|
|
27238
|
-
|
|
27239
|
-
|
|
27240
|
-
layout/safe_area.js)
|
|
27241
|
-
|
|
27242
|
-
|
|
27243
|
-
the
|
|
27244
|
-
|
|
27326
|
+
/* Cut at what covers the area, on top of being cut at the area's own
|
|
27327
|
+
box. The pictures are drawn in the top layer, so they cover a fixed bar
|
|
27328
|
+
as easily as anything else — and the area runs UNDER the bars by
|
|
27329
|
+
design: that is what a fixed bar is for, and what the room it gives
|
|
27330
|
+
back is for. An area taller than the screen therefore ends below the
|
|
27331
|
+
bottom bar, and a scrolled one starts above the top bar, so the
|
|
27332
|
+
movement would be watched painting over them for its whole length.
|
|
27333
|
+
|
|
27334
|
+
Two bands are left free, and they answer for two different things: the
|
|
27335
|
+
app's own safe area (layout/safe_area.js), everything pinned to the
|
|
27336
|
+
WINDOW's edges, and --navi-transition-cover-* (transition_window.js),
|
|
27337
|
+
what covers the area from inside the document — a sticky header above
|
|
27338
|
+
the pages covers the top of the area exactly as a fixed bar covers the
|
|
27339
|
+
top of the screen. Both are read rather than asked for, so one that
|
|
27340
|
+
grows, shrinks or unmounts mid-transition is followed without anything
|
|
27341
|
+
being told. What the window cannot know is only where it itself stands,
|
|
27342
|
+
and that is the measured half. */
|
|
27245
27343
|
--navi-route-transition-clip-top: max(
|
|
27246
27344
|
0px,
|
|
27247
|
-
var(--navi-safe-area-inset-top)
|
|
27345
|
+
var(--navi-safe-area-inset-top) +
|
|
27346
|
+
var(--navi-transition-cover-top) - var(--navi-transition-window-top)
|
|
27248
27347
|
);
|
|
27249
27348
|
--navi-route-transition-clip-left: max(
|
|
27250
27349
|
0px,
|
|
27251
|
-
var(--navi-safe-area-inset-left)
|
|
27350
|
+
var(--navi-safe-area-inset-left) +
|
|
27351
|
+
var(--navi-transition-cover-left) - var(--navi-transition-window-left)
|
|
27252
27352
|
);
|
|
27253
27353
|
--navi-route-transition-clip-bottom: max(
|
|
27254
27354
|
0px,
|
|
27255
27355
|
var(--navi-transition-window-top) +
|
|
27256
27356
|
var(--navi-transition-window-height) +
|
|
27257
|
-
var(--navi-safe-area-inset-bottom)
|
|
27357
|
+
var(--navi-safe-area-inset-bottom) +
|
|
27358
|
+
var(--navi-transition-cover-bottom) - 100dvh
|
|
27258
27359
|
);
|
|
27259
27360
|
--navi-route-transition-clip-right: max(
|
|
27260
27361
|
0px,
|
|
27261
27362
|
var(--navi-transition-window-left) +
|
|
27262
27363
|
var(--navi-transition-window-width) +
|
|
27263
|
-
var(--navi-safe-area-inset-right)
|
|
27364
|
+
var(--navi-safe-area-inset-right) +
|
|
27365
|
+
var(--navi-transition-cover-right) - 100dvw
|
|
27264
27366
|
);
|
|
27265
27367
|
clip-path: inset(
|
|
27266
27368
|
var(--navi-route-transition-clip-top)
|
|
@@ -28278,6 +28380,8 @@ const TURNED_ATTRIBUTE = "data-navi-route-travel-turned";
|
|
|
28278
28380
|
// The name the box wears while it travels, and only then (see nameForTravel).
|
|
28279
28381
|
const TRAVEL_NAME = "navi-route-travel";
|
|
28280
28382
|
const css$$ = /* css */`
|
|
28383
|
+
${TRANSITION_WINDOW_CSS}
|
|
28384
|
+
|
|
28281
28385
|
/* The name that makes the page inside this box a picture of its own during a
|
|
28282
28386
|
transition — rather than part of the one big picture the document takes, so
|
|
28283
28387
|
the two pages can move past each other while everything else stays where it
|
|
@@ -28396,38 +28500,46 @@ const css$$ = /* css */`
|
|
|
28396
28500
|
route to the next. */
|
|
28397
28501
|
height: var(--navi-transition-window-height);
|
|
28398
28502
|
|
|
28399
|
-
/* Cut at the
|
|
28400
|
-
drawn in the top layer, so they cover a fixed bar as
|
|
28401
|
-
else — and the box they travel in runs UNDER the
|
|
28402
|
-
is what a fixed bar is for, and what the room it
|
|
28403
|
-
box scrolled by so much as a pixel therefore
|
|
28404
|
-
and ends below the bottom one, and the travel
|
|
28405
|
-
over both for its whole length.
|
|
28406
|
-
|
|
28407
|
-
|
|
28408
|
-
|
|
28409
|
-
|
|
28410
|
-
|
|
28411
|
-
|
|
28503
|
+
/* Cut at what covers the box, on top of being cut at the box. The
|
|
28504
|
+
pictures are drawn in the top layer, so they cover a fixed bar as
|
|
28505
|
+
easily as anything else — and the box they travel in runs UNDER the
|
|
28506
|
+
bars by design: that is what a fixed bar is for, and what the room it
|
|
28507
|
+
gives back is for. A box scrolled by so much as a pixel therefore
|
|
28508
|
+
starts above the top bar and ends below the bottom one, and the travel
|
|
28509
|
+
would be watched painting over both for its whole length.
|
|
28510
|
+
|
|
28511
|
+
Two bands are left free, and they answer for two different things: the
|
|
28512
|
+
app's own safe area (layout/safe_area.js), everything pinned to the
|
|
28513
|
+
WINDOW's edges, and --navi-transition-cover-* (transition_window.js),
|
|
28514
|
+
what covers the box from inside the document — a sticky row of tabs
|
|
28515
|
+
above the pages covers the top of the box exactly as a fixed bar covers
|
|
28516
|
+
the top of the screen. Both are read rather than asked for, so one that
|
|
28517
|
+
grows, shrinks or unmounts mid-travel is followed without anything
|
|
28518
|
+
being told. What the group cannot know is only where it itself stands,
|
|
28519
|
+
and that is the measured half. */
|
|
28412
28520
|
--navi-route-travel-clip-top: max(
|
|
28413
28521
|
0px,
|
|
28414
|
-
var(--navi-safe-area-inset-top)
|
|
28522
|
+
var(--navi-safe-area-inset-top) +
|
|
28523
|
+
var(--navi-transition-cover-top) - var(--navi-transition-window-top)
|
|
28415
28524
|
);
|
|
28416
28525
|
--navi-route-travel-clip-left: max(
|
|
28417
28526
|
0px,
|
|
28418
|
-
var(--navi-safe-area-inset-left)
|
|
28527
|
+
var(--navi-safe-area-inset-left) +
|
|
28528
|
+
var(--navi-transition-cover-left) - var(--navi-transition-window-left)
|
|
28419
28529
|
);
|
|
28420
28530
|
--navi-route-travel-clip-bottom: max(
|
|
28421
28531
|
0px,
|
|
28422
28532
|
var(--navi-transition-window-top) +
|
|
28423
28533
|
var(--navi-transition-window-height) +
|
|
28424
|
-
var(--navi-safe-area-inset-bottom)
|
|
28534
|
+
var(--navi-safe-area-inset-bottom) +
|
|
28535
|
+
var(--navi-transition-cover-bottom) - 100dvh
|
|
28425
28536
|
);
|
|
28426
28537
|
--navi-route-travel-clip-right: max(
|
|
28427
28538
|
0px,
|
|
28428
28539
|
var(--navi-transition-window-left) +
|
|
28429
28540
|
var(--navi-transition-window-width) +
|
|
28430
|
-
var(--navi-safe-area-inset-right)
|
|
28541
|
+
var(--navi-safe-area-inset-right) +
|
|
28542
|
+
var(--navi-transition-cover-right) - 100dvw
|
|
28431
28543
|
);
|
|
28432
28544
|
clip-path: inset(
|
|
28433
28545
|
var(--navi-route-travel-clip-top) var(--navi-route-travel-clip-right)
|
|
@@ -28736,6 +28848,19 @@ const RouteTravel = ({
|
|
|
28736
28848
|
ended: false
|
|
28737
28849
|
};
|
|
28738
28850
|
travelRef.current = travel;
|
|
28851
|
+
// Whether the document's offset is this row's business at all (see
|
|
28852
|
+
// pagesScrollTheDocument), asked once and before anything is swapped.
|
|
28853
|
+
travel.scrollsDocument = pagesScrollTheDocument(elementRef.current);
|
|
28854
|
+
if (travel.scrollsDocument) {
|
|
28855
|
+
// One of the tabs is about to be swapped out from under that scrollport,
|
|
28856
|
+
// and whatever the browser does with the offset meanwhile — clamping it
|
|
28857
|
+
// to a page shorter than the trip — is not the reader scrolling. The url
|
|
28858
|
+
// it would be written against is already the arriving tab's, so recorded
|
|
28859
|
+
// it is that tab's own position that the tab being left destroys. Deaf
|
|
28860
|
+
// for the whole travel, which is exactly as long as the offset belongs
|
|
28861
|
+
// to nobody (see endTravel).
|
|
28862
|
+
travel.resumeScrollRecording = suspendScrollRecording();
|
|
28863
|
+
}
|
|
28739
28864
|
// Taken before the picture is: the browser reads the name off the DOM as it
|
|
28740
28865
|
// stands when the transition starts, and this box is only a picture of its
|
|
28741
28866
|
// own for as long as it is the one travelling. Where it travels to is said
|
|
@@ -28778,8 +28903,24 @@ const RouteTravel = ({
|
|
|
28778
28903
|
await change();
|
|
28779
28904
|
}
|
|
28780
28905
|
}, renderWait);
|
|
28906
|
+
// Where the arriving tab was left, or its top when it has never been
|
|
28907
|
+
// read. A travel is an arrival — the tab pressed is another route, and
|
|
28908
|
+
// the replace it navigates by is the only thing about it that says
|
|
28909
|
+
// otherwise — and this is the one place that knows it. Read from the
|
|
28910
|
+
// travel rather than from the page it set off for: a travel aimed
|
|
28911
|
+
// somewhere else while it waited lands where it is aimed now.
|
|
28912
|
+
if (travel.scrollsDocument) {
|
|
28913
|
+
const {
|
|
28914
|
+
route,
|
|
28915
|
+
params
|
|
28916
|
+
} = travel.page;
|
|
28917
|
+
arriveAtScrollPosition(route.buildUrl(params));
|
|
28918
|
+
}
|
|
28781
28919
|
// The page arriving is in the DOM and the transition has not started
|
|
28782
|
-
// playing: the one moment both boxes can be known.
|
|
28920
|
+
// playing: the one moment both boxes can be known. After the scroll,
|
|
28921
|
+
// which is what the box is measured through: the two states are at the
|
|
28922
|
+
// same place in the layout without being at the same place in the window
|
|
28923
|
+
// (see transition_window.js).
|
|
28783
28924
|
holdTransitionWindow(travel, elementRef.current, rectBefore);
|
|
28784
28925
|
});
|
|
28785
28926
|
travel.viewTransition = viewTransition;
|
|
@@ -29000,6 +29141,25 @@ const RouteTravel = ({
|
|
|
29000
29141
|
}
|
|
29001
29142
|
travel.viewTransition.skipTransition();
|
|
29002
29143
|
} finally {
|
|
29144
|
+
if (travel.scrollsDocument) {
|
|
29145
|
+
// The page coming back was read somewhere else, and the offset
|
|
29146
|
+
// currently on the document is the one the page that came in put
|
|
29147
|
+
// there. Given back once the page is really back — the render is
|
|
29148
|
+
// what makes the document tall enough to hold that offset again —
|
|
29149
|
+
// and the recording stays deaf until then, over the clamp the swap
|
|
29150
|
+
// back makes on the way. Its own deafness rather than the travel's:
|
|
29151
|
+
// the travel ends here, and this outlives it by a render.
|
|
29152
|
+
const resumeScrollRecording = suspendScrollRecording();
|
|
29153
|
+
const stopWatchingRender = observeRouteRender(() => {
|
|
29154
|
+
stopWatchingRender();
|
|
29155
|
+
const {
|
|
29156
|
+
route,
|
|
29157
|
+
params
|
|
29158
|
+
} = travel.fromPage;
|
|
29159
|
+
arriveAtScrollPosition(route.buildUrl(params));
|
|
29160
|
+
resumeScrollRecording();
|
|
29161
|
+
});
|
|
29162
|
+
}
|
|
29003
29163
|
releaseRendering();
|
|
29004
29164
|
// A travel ENDS, whatever happened on the way back: put the state back,
|
|
29005
29165
|
// fail to drop the picture, be interrupted by something else — the one
|
|
@@ -29085,6 +29245,11 @@ const RouteTravel = ({
|
|
|
29085
29245
|
travel.ended = true;
|
|
29086
29246
|
travel.dropHold?.();
|
|
29087
29247
|
travel.dropHold = null;
|
|
29248
|
+
// The offset on the scrollport belongs to a page again — the one that
|
|
29249
|
+
// arrived, or the one put back. A travel that never got as far as saying
|
|
29250
|
+
// which still has to give the recording back; the gesture's own
|
|
29251
|
+
// pseudo-travel never took it (see noPicture).
|
|
29252
|
+
travel.resumeScrollRecording?.();
|
|
29088
29253
|
// Its own hold, always — whether or not this travel is still the current
|
|
29089
29254
|
// one. Nobody else will lift it.
|
|
29090
29255
|
releaseHold(travel);
|
|
@@ -29620,6 +29785,26 @@ const whilePageRenders = async (page, change, wait = armRouteRenderWait()) => {
|
|
|
29620
29785
|
}
|
|
29621
29786
|
};
|
|
29622
29787
|
|
|
29788
|
+
// Whether the document's offset belongs to the tabs. The pages of a row scroll
|
|
29789
|
+
// the document when nothing between the box and the viewport scrolls or clips:
|
|
29790
|
+
// the tab on screen is then what makes the document tall, and the offset on it
|
|
29791
|
+
// is that tab's — it has to be given back with the tab, and the browser's
|
|
29792
|
+
// clamping of it while pages are swapped has to be ignored.
|
|
29793
|
+
//
|
|
29794
|
+
// A box that lives inside a scroller of its own — a frame in an article, a
|
|
29795
|
+
// panel beside other content — shares nothing with the document: the offset
|
|
29796
|
+
// there is the surrounding page's, the reader never left it, and a travel has
|
|
29797
|
+
// no business moving it. Each of its pages brings its own scrollport, which
|
|
29798
|
+
// goes away with the page and has nothing to restore.
|
|
29799
|
+
const pagesScrollTheDocument = element => {
|
|
29800
|
+
const scrollContainer = getScrollContainer(element, {
|
|
29801
|
+
includeHidden: true
|
|
29802
|
+
});
|
|
29803
|
+
// html and body are one answer: whichever of them the walk stops on, what
|
|
29804
|
+
// scrolls is the viewport, which is what window.scrollTo moves.
|
|
29805
|
+
return scrollContainer === document.documentElement || scrollContainer === document.body;
|
|
29806
|
+
};
|
|
29807
|
+
|
|
29623
29808
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
29624
29809
|
// several of them share it. Written as a bare route by a caller whose tabs are
|
|
29625
29810
|
// routes of their own — which is the same page with nothing to tell apart.
|
|
@@ -53768,7 +53953,7 @@ const createOpenController = (
|
|
|
53768
53953
|
// Last: the close effects above are what starts the exit transition the
|
|
53769
53954
|
// content must outlive (see popup_content_mount.js).
|
|
53770
53955
|
controller.unmountContent?.();
|
|
53771
|
-
controller.onOpenedChange?.(false);
|
|
53956
|
+
controller.onOpenedChange?.(false, closeEvent);
|
|
53772
53957
|
};
|
|
53773
53958
|
const controller = {
|
|
53774
53959
|
opened: false,
|
|
@@ -53788,9 +53973,10 @@ const createOpenController = (
|
|
|
53788
53973
|
// away on close (`unmountWhenClosed`). Called from performClose above.
|
|
53789
53974
|
unmountContent: null,
|
|
53790
53975
|
// Told whenever `opened` actually changes, whatever asked for it — an
|
|
53791
|
-
// interaction, a command, a prop. What lets a
|
|
53792
|
-
// popup's real state
|
|
53793
|
-
//
|
|
53976
|
+
// interaction, a command, a prop — with the event that asked. What lets a
|
|
53977
|
+
// `signal` prop reflect the popup's real state, and a `navState` prop write
|
|
53978
|
+
// it into the history entry (see useOpenPropsEffectOnOpenController);
|
|
53979
|
+
// called once the open/close has fully happened rather than mid-sequence.
|
|
53794
53980
|
onOpenedChange: null,
|
|
53795
53981
|
open: (e, detail) => {
|
|
53796
53982
|
if (controller.opened || !controller.openEffect) {
|
|
@@ -53894,7 +54080,7 @@ const createOpenController = (
|
|
|
53894
54080
|
openEffectReturnValue?.(closeEvent);
|
|
53895
54081
|
};
|
|
53896
54082
|
closeHandlers = openHandler(requestOpenEvent) || null;
|
|
53897
|
-
controller.onOpenedChange?.(true);
|
|
54083
|
+
controller.onOpenedChange?.(true, requestOpenEvent);
|
|
53898
54084
|
},
|
|
53899
54085
|
requestClose: (
|
|
53900
54086
|
e = new CustomEvent("programmatic", { detail: {} }),
|
|
@@ -54014,33 +54200,83 @@ const scheduleMountOpen = (run) => {
|
|
|
54014
54200
|
});
|
|
54015
54201
|
};
|
|
54016
54202
|
|
|
54203
|
+
// Where the popup's open state is kept, when it is kept anywhere: `navState`
|
|
54204
|
+
// resolved to the `{ id, type }` useNavState wants.
|
|
54205
|
+
//
|
|
54206
|
+
// `true` takes the popup's own id — a popup a `--navi-open` command can name is
|
|
54207
|
+
// a popup that already has a stable one, and that id is what identifies its
|
|
54208
|
+
// open state too.
|
|
54209
|
+
const NO_NAV_STATE = { id: undefined, type: "replace" };
|
|
54210
|
+
const resolveNavStateProp = (navState, popupId) => {
|
|
54211
|
+
if (!navState) {
|
|
54212
|
+
return NO_NAV_STATE;
|
|
54213
|
+
}
|
|
54214
|
+
if (navState === true) {
|
|
54215
|
+
return { id: popupId, type: "replace" };
|
|
54216
|
+
}
|
|
54217
|
+
if (typeof navState === "string") {
|
|
54218
|
+
return { id: navState, type: "replace" };
|
|
54219
|
+
}
|
|
54220
|
+
return { id: navState.id || popupId, type: navState.type || "replace" };
|
|
54221
|
+
};
|
|
54222
|
+
|
|
54017
54223
|
/**
|
|
54018
|
-
* Keeps an open controller in sync with
|
|
54019
|
-
*
|
|
54020
|
-
*
|
|
54021
|
-
*
|
|
54022
|
-
*
|
|
54023
|
-
*
|
|
54024
|
-
* `
|
|
54224
|
+
* Keeps an open controller in sync with where the caller says the popup should
|
|
54225
|
+
* be: an `open`/`defaultOpen` pair, a `signal`, or a `navState` — the open
|
|
54226
|
+
* state written into the history entry, so a screen left and come back to finds
|
|
54227
|
+
* its popup as it was.
|
|
54228
|
+
*
|
|
54229
|
+
* Shared between `useOpenControllerByProps` below (Dialog/Popover driving their
|
|
54230
|
+
* own controller) and `picker_custom.jsx` (which owns its controller but wants
|
|
54231
|
+
* the same skip-if-already-matching / open-or-requestClose control flow).
|
|
54025
54232
|
*
|
|
54026
54233
|
* @param {{ open: (e: Event, detail?: object) => void, requestClose: (e: Event, detail?: object) => void, opened: boolean }} openController
|
|
54027
|
-
* @param {{ open?: boolean|"interaction", defaultOpen?: boolean|"interaction", signal?: import("@preact/signals").Signal<boolean
|
|
54234
|
+
* @param {{ id?: string, open?: boolean|"interaction", defaultOpen?: boolean|"interaction", signal?: import("@preact/signals").Signal<boolean>, navState?: boolean|string|{id?: string, type?: "push"|"replace"} }} props
|
|
54028
54235
|
*/
|
|
54029
54236
|
const useOpenPropsEffectOnOpenController = (openController, props) => {
|
|
54030
|
-
const { signal, defaultOpen } = props;
|
|
54031
|
-
|
|
54032
|
-
|
|
54033
|
-
|
|
54034
|
-
|
|
54237
|
+
const { signal, defaultOpen, navState } = props;
|
|
54238
|
+
const { id: navStateId, type: navStateType } = resolveNavStateProp(
|
|
54239
|
+
navState,
|
|
54240
|
+
props.id,
|
|
54241
|
+
);
|
|
54242
|
+
// Called unconditionally (it answers with no-ops for an absent id), like
|
|
54243
|
+
// every other hook here.
|
|
54244
|
+
const [navStateValue, enterNavState, leaveNavState] = useNavState(
|
|
54245
|
+
navStateId,
|
|
54246
|
+
{ type: navStateType },
|
|
54247
|
+
);
|
|
54248
|
+
// What the caller holds, however they hold it: the history entry when there
|
|
54249
|
+
// is a `navState`, an `open` they re-render themselves, or a `signal` this
|
|
54250
|
+
// hook also writes (see onOpenedChange below). Reading .value during render
|
|
54251
|
+
// is what subscribes the popup to a signal; reading the document state is
|
|
54252
|
+
// what subscribes it to the history entry, back button included.
|
|
54253
|
+
const open = navStateId
|
|
54254
|
+
? Boolean(navStateValue)
|
|
54255
|
+
: signal
|
|
54256
|
+
? signal.value
|
|
54257
|
+
: props.open;
|
|
54035
54258
|
// Assigned on every render, like openEffect, so it always closes over the
|
|
54036
54259
|
// latest prop: a popup that opens or closes on its own (Escape, backdrop, a
|
|
54037
|
-
// --navi-close command) writes what happened
|
|
54038
|
-
// holds it always reads where the popup is.
|
|
54039
|
-
openController.onOpenedChange =
|
|
54040
|
-
|
|
54041
|
-
|
|
54042
|
-
|
|
54043
|
-
|
|
54260
|
+
// --navi-close command) writes what happened where the caller keeps it, so
|
|
54261
|
+
// whoever holds it always reads where the popup is.
|
|
54262
|
+
openController.onOpenedChange =
|
|
54263
|
+
navStateId || signal
|
|
54264
|
+
? (opened, event) => {
|
|
54265
|
+
if (navStateId) {
|
|
54266
|
+
if (opened) {
|
|
54267
|
+
enterNavState();
|
|
54268
|
+
} else {
|
|
54269
|
+
// Under type "push" a cancel goes back rather than rewriting the
|
|
54270
|
+
// entry, so everything else written to the url while the popup
|
|
54271
|
+
// was open goes back with it (see useNavState's own leave()).
|
|
54272
|
+
leaveNavState({ isBack: Boolean(event?.detail?.isCancel) });
|
|
54273
|
+
}
|
|
54274
|
+
}
|
|
54275
|
+
if (signal) {
|
|
54276
|
+
signal.value = opened;
|
|
54277
|
+
}
|
|
54278
|
+
}
|
|
54279
|
+
: null;
|
|
54044
54280
|
// Tracks whether the effect below has ever run before — only the very
|
|
54045
54281
|
// first run gets the "mount already open" treatment (`open` truthy from
|
|
54046
54282
|
// the start, or the uncontrolled, mount-only `defaultOpen`); every
|
|
@@ -54093,12 +54329,15 @@ const useOpenPropsEffectOnOpenController = (openController, props) => {
|
|
|
54093
54329
|
{ isCancel: true },
|
|
54094
54330
|
);
|
|
54095
54331
|
}
|
|
54332
|
+
// The request can be refused (a busy form denying the close): the popup
|
|
54333
|
+
// then stays where it was, and whoever holds the open state is told so —
|
|
54334
|
+
// otherwise it would keep saying "closed" about a popup still open.
|
|
54096
54335
|
if (signal) {
|
|
54097
|
-
// The request can be refused (a busy form denying the close): the popup
|
|
54098
|
-
// then stays where it was, and the signal is told so — otherwise it
|
|
54099
|
-
// would keep saying "closed" about a popup still open.
|
|
54100
54336
|
signal.value = openController.opened;
|
|
54101
54337
|
}
|
|
54338
|
+
if (navStateId && openController.opened) {
|
|
54339
|
+
enterNavState();
|
|
54340
|
+
}
|
|
54102
54341
|
}, [open]);
|
|
54103
54342
|
};
|
|
54104
54343
|
|
|
@@ -54358,7 +54597,14 @@ const usePopupContentMount = (
|
|
|
54358
54597
|
*
|
|
54359
54598
|
* `animation="slide-from-*"` (anchorReference/point mode only): a real
|
|
54360
54599
|
* translate-based entrance, 8 directions (cardinal + 4 diagonals), each
|
|
54361
|
-
* 100%-of-own-size.
|
|
54600
|
+
* 100%-of-own-size. It travels through `transform`, not through the
|
|
54601
|
+
* `translate` property, which belongs to the popup's own placement
|
|
54602
|
+
* (applyNewPosition in visible_rect.js — see its doc for why the placement is
|
|
54603
|
+
* a transform at all, and why it has to be the outermost one: the individual
|
|
54604
|
+
* transform properties apply translate, then rotate, then scale, then
|
|
54605
|
+
* `transform`, so both the travel here and the `scale` below compose *under* a
|
|
54606
|
+
* placement that stays where it was put).
|
|
54607
|
+
* Popover always resolves `animation="auto"`/`"sliding"`
|
|
54362
54608
|
* to one of these concretely in JS (see popover.jsx's
|
|
54363
54609
|
* `resolveDirectionValue`), so there's no bare `animation="sliding"`
|
|
54364
54610
|
* selector here at all — a point/corner has no anchor edge to grow out of,
|
|
@@ -54399,14 +54645,15 @@ const popupCss = /* css */ `
|
|
|
54399
54645
|
|
|
54400
54646
|
.navi_popover,
|
|
54401
54647
|
.navi_dialog {
|
|
54402
|
-
/*
|
|
54403
|
-
|
|
54404
|
-
|
|
54405
|
-
|
|
54406
|
-
propertyName to filter).
|
|
54648
|
+
/* The translate property is deliberately absent from this list — it
|
|
54649
|
+
carries where the popup stands, and applyNewPosition (visible_rect.js)
|
|
54650
|
+
owns it and drives its own transition through the Web Animations API
|
|
54651
|
+
instead of CSS, so it stays independent of whatever this list contains
|
|
54652
|
+
(no shared transition-property to clobber, no propertyName to filter).
|
|
54653
|
+
What moves here is transform, which composes under it. */
|
|
54407
54654
|
&[navi-animation] {
|
|
54408
54655
|
transition-property:
|
|
54409
|
-
display, overlay, opacity,
|
|
54656
|
+
display, overlay, opacity, transform, scale, box-shadow;
|
|
54410
54657
|
transition-duration:
|
|
54411
54658
|
var(--popup-animation-duration), var(--popup-animation-duration),
|
|
54412
54659
|
var(--popup-opacity-duration), var(--popup-translate-duration),
|
|
@@ -54435,7 +54682,7 @@ const popupCss = /* css */ `
|
|
|
54435
54682
|
centered, no direction involved. */
|
|
54436
54683
|
&[navi-animation="scaling"] {
|
|
54437
54684
|
opacity: 1;
|
|
54438
|
-
|
|
54685
|
+
transform: translate(0px, 0px);
|
|
54439
54686
|
scale: 1;
|
|
54440
54687
|
&[aria-expanded="false"] {
|
|
54441
54688
|
opacity: 0;
|
|
@@ -54487,14 +54734,16 @@ const popupCss = /* css */ `
|
|
|
54487
54734
|
&[navi-animation="slide-from-bottom-left"],
|
|
54488
54735
|
&[navi-animation="slide-from-bottom-right"] {
|
|
54489
54736
|
opacity: 1;
|
|
54490
|
-
|
|
54737
|
+
transform: translate(0px, 0px);
|
|
54491
54738
|
|
|
54492
54739
|
/* No fade: the travel is the whole effect. Fading it out on top would
|
|
54493
54740
|
make the popup disappear before it has finished leaving, which reads as
|
|
54494
54741
|
two things happening rather than one movement. */
|
|
54495
54742
|
&[aria-expanded="false"] {
|
|
54496
|
-
|
|
54497
|
-
calc(var(--x-popup-slide-
|
|
54743
|
+
transform: translate(
|
|
54744
|
+
calc(var(--x-popup-slide-x, 0) * 100%),
|
|
54745
|
+
calc(var(--x-popup-slide-y, -1) * 100%)
|
|
54746
|
+
);
|
|
54498
54747
|
}
|
|
54499
54748
|
}
|
|
54500
54749
|
}
|
|
@@ -54686,14 +54935,17 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
54686
54935
|
}
|
|
54687
54936
|
}
|
|
54688
54937
|
|
|
54689
|
-
//
|
|
54690
|
-
// distance in screen coordinates, which is exactly what a
|
|
54938
|
+
// How far the panel has been pulled, written on it directly: the gesture
|
|
54939
|
+
// reports a distance in screen coordinates, which is exactly what a
|
|
54940
|
+
// translate takes. It goes through `transform` because the `translate`
|
|
54941
|
+
// property carries where the panel *stands* (applyNewPosition in
|
|
54942
|
+
// visible_rect.js, which owns it) — the pull composes under the placement
|
|
54943
|
+
// instead of replacing it.
|
|
54691
54944
|
const paint = (distance) => {
|
|
54692
|
-
panelEl.style.
|
|
54693
|
-
axis === "x" ? `${distance}px 0px` : `0px ${distance}px`;
|
|
54945
|
+
panelEl.style.transform = translateOf(axis, distance);
|
|
54694
54946
|
};
|
|
54695
54947
|
const restore = () => {
|
|
54696
|
-
panelEl.style.
|
|
54948
|
+
panelEl.style.transform = "";
|
|
54697
54949
|
panelEl.style.transitionProperty = "";
|
|
54698
54950
|
panelEl.style.userSelect = "";
|
|
54699
54951
|
};
|
|
@@ -54705,8 +54957,8 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
54705
54957
|
paint(to);
|
|
54706
54958
|
const animation = panelEl.animate(
|
|
54707
54959
|
[
|
|
54708
|
-
{
|
|
54709
|
-
{
|
|
54960
|
+
{ transform: translateOf(axis, from) },
|
|
54961
|
+
{ transform: translateOf(axis, to) },
|
|
54710
54962
|
],
|
|
54711
54963
|
{
|
|
54712
54964
|
duration: (covered / sizeOf(panelEl, axis)) * TRAVEL_DURATION,
|
|
@@ -54776,7 +55028,9 @@ const sizeOf = (element, axis) => {
|
|
|
54776
55028
|
return axis === "x" ? rect.width : rect.height;
|
|
54777
55029
|
};
|
|
54778
55030
|
const translateOf = (axis, distance) =>
|
|
54779
|
-
axis === "x"
|
|
55031
|
+
axis === "x"
|
|
55032
|
+
? `translate(${distance}px, 0px)`
|
|
55033
|
+
: `translate(0px, ${distance}px)`;
|
|
54780
55034
|
|
|
54781
55035
|
const PopupClose = ({
|
|
54782
55036
|
label,
|
|
@@ -54973,11 +55227,17 @@ const css$E = /* css */`
|
|
|
54973
55227
|
containing block is genuinely its nearest positioned ancestor,
|
|
54974
55228
|
regardless of positionArea. See the [data-layer="top"] rule below for
|
|
54975
55229
|
why the via-attribute renderer overrides this. Position is always
|
|
54976
|
-
JS-driven (pickPositionRelativeTo
|
|
54977
|
-
|
|
54978
|
-
unlike an earlier version of this file. */
|
|
55230
|
+
JS-driven (pickPositionRelativeTo, see useDialogProps below) — no CSS
|
|
55231
|
+
alignment/inset math here at all. */
|
|
54979
55232
|
position: absolute;
|
|
54980
|
-
|
|
55233
|
+
/* Laid out at its containing block's own origin and moved from there by a
|
|
55234
|
+
translate (applyNewPosition), never by left/top: a shrink-to-fit box
|
|
55235
|
+
placed with left is only ever as wide as what is left of the container
|
|
55236
|
+
to its right, and that width is what decides where it gets placed — see
|
|
55237
|
+
applyNewPosition's own doc. right/bottom stay auto: an inset there would
|
|
55238
|
+
over-constrain the box against the UA's margin: auto and re-center
|
|
55239
|
+
it. */
|
|
55240
|
+
inset: 0 auto auto 0;
|
|
54981
55241
|
/* Custom renderer only — see openLocalDialogCount above */
|
|
54982
55242
|
z-index: calc(var(--navi-z-index-popup) + var(--dialog-stack-order, 0));
|
|
54983
55243
|
min-width: min(
|
|
@@ -55073,11 +55333,12 @@ const css$E = /* css */`
|
|
|
55073
55333
|
&[data-flush-bottom][data-flush-left] {
|
|
55074
55334
|
border-bottom-left-radius: 0;
|
|
55075
55335
|
}
|
|
55076
|
-
/*
|
|
55077
|
-
|
|
55078
|
-
|
|
55079
|
-
|
|
55080
|
-
|
|
55336
|
+
/* The placement is a translate, so the translate property is spoken for
|
|
55337
|
+
here (see applyNewPosition in visible_rect.js, which owns it and animates
|
|
55338
|
+
it itself through the Web Animations API rather than through this file's
|
|
55339
|
+
transitions — no shared transition-property to clobber, no propertyName
|
|
55340
|
+
to filter). An entrance animation moves the dialog through scale and
|
|
55341
|
+
transform instead, which compose under it: see popup_css.js. */
|
|
55081
55342
|
|
|
55082
55343
|
&::backdrop {
|
|
55083
55344
|
background: var(--navi-backdrop-close-background);
|
|
@@ -55157,7 +55418,7 @@ const css$E = /* css */`
|
|
|
55157
55418
|
containing block is the viewport rather than any positioned
|
|
55158
55419
|
ancestor. Not left to the native :modal UA stylesheet's own default
|
|
55159
55420
|
(also position: fixed, but with its own margin/inset assumptions) so
|
|
55160
|
-
that JS-
|
|
55421
|
+
that the JS-driven placement (see useDialogProps below) always wins
|
|
55161
55422
|
cleanly. */
|
|
55162
55423
|
&[data-layer="top"] {
|
|
55163
55424
|
position: fixed;
|
|
@@ -55442,6 +55703,16 @@ const css$E = /* css */`
|
|
|
55442
55703
|
* actually closes — not preventable (see `open_controller.js`'s own
|
|
55443
55704
|
* `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
|
|
55444
55705
|
* veto a close instead).
|
|
55706
|
+
* @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
|
|
55707
|
+
* Keeps the open state in the history entry, so a screen left and come back
|
|
55708
|
+
* to finds this popup as it was — open, and without an entrance playing: it
|
|
55709
|
+
* was already open when the page reappeared. `true` stores it under the
|
|
55710
|
+
* popup's own `id`; a string names the key instead.
|
|
55711
|
+
* `{ type: "push" }` also makes the opening a history entry of its own, so
|
|
55712
|
+
* the back button closes the popup rather than leaving the screen — and a
|
|
55713
|
+
* cancel (Escape) goes back, taking whatever was written to the url while it
|
|
55714
|
+
* was open with it. The state belongs to the entry that wrote it: a
|
|
55715
|
+
* navigation that stacks a new entry does not carry it along.
|
|
55445
55716
|
* @param {object} [props.openController] - Advanced: an externally-owned
|
|
55446
55717
|
* open controller (see `open_controller.js`) for a caller that wants to
|
|
55447
55718
|
* drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
|
|
@@ -55509,6 +55780,7 @@ const UncontrolledDialog = props => {
|
|
|
55509
55780
|
open: undefined,
|
|
55510
55781
|
signal: undefined,
|
|
55511
55782
|
defaultOpen: undefined,
|
|
55783
|
+
navState: undefined,
|
|
55512
55784
|
onClose: undefined,
|
|
55513
55785
|
openController: openController,
|
|
55514
55786
|
onnavi_request_open: e => {
|
|
@@ -56088,8 +56360,8 @@ const useDialogProps = props => {
|
|
|
56088
56360
|
rectEffect.disconnect();
|
|
56089
56361
|
});
|
|
56090
56362
|
// A descendant anchored to something inside this dialog (a Callout, a
|
|
56091
|
-
// nested Popover) needing to know about this dialog's own
|
|
56092
|
-
//
|
|
56363
|
+
// nested Popover) needing to know about this dialog's own repositioning
|
|
56364
|
+
// transition — not just that the target changed
|
|
56093
56365
|
// (navi_position_change above), but that a real, currently-playing
|
|
56094
56366
|
// transition is moving it right now — is handled generically by
|
|
56095
56367
|
// applyNewPosition itself (see its own notifyPositionTransition), since
|
|
@@ -56536,7 +56808,14 @@ const css$D = /* css */`
|
|
|
56536
56808
|
whether it has a real anchor — this file's top comment has the full
|
|
56537
56809
|
reasoning. */
|
|
56538
56810
|
position: absolute;
|
|
56539
|
-
|
|
56811
|
+
/* Laid out at its containing block's own origin and moved from there by a
|
|
56812
|
+
translate (applyNewPosition), never by left/top: a shrink-to-fit box
|
|
56813
|
+
placed with left is only ever as wide as what is left of the container
|
|
56814
|
+
to its right, and that width is what decides which side it gets placed
|
|
56815
|
+
on — see applyNewPosition's own doc. right/bottom stay auto: an inset
|
|
56816
|
+
there would over-constrain the box against the UA's margin: auto and
|
|
56817
|
+
re-center it. */
|
|
56818
|
+
inset: 0 auto auto 0;
|
|
56540
56819
|
/* Custom renderer only: --popover-stack-order is set to
|
|
56541
56820
|
openLocalPopoverCount on every open (see openEffect below) so the
|
|
56542
56821
|
most-recently-opened local popover always outranks an earlier one,
|
|
@@ -56568,35 +56847,13 @@ const css$D = /* css */`
|
|
|
56568
56847
|
(see box.jsx), which is what makes it scroll — and what a
|
|
56569
56848
|
header/footer/body inside it then rearranges. */
|
|
56570
56849
|
overscroll-behavior: none;
|
|
56571
|
-
/*
|
|
56572
|
-
|
|
56573
|
-
|
|
56574
|
-
|
|
56575
|
-
|
|
56576
|
-
|
|
56577
|
-
(see box.jsx), which is what makes it scroll — and what a
|
|
56578
|
-
header/footer/body inside it then rearranges. */
|
|
56579
|
-
overscroll-behavior: none;
|
|
56850
|
+
/* The placement is a translate, so the translate property is spoken for
|
|
56851
|
+
here (see applyNewPosition in visible_rect.js, which owns it and animates
|
|
56852
|
+
it itself through the Web Animations API rather than through this file's
|
|
56853
|
+
transitions — no shared transition-property to clobber, no propertyName
|
|
56854
|
+
to filter). An entrance animation moves the popover through scale and
|
|
56855
|
+
transform instead, which compose under it: see popup_css.js. */
|
|
56580
56856
|
|
|
56581
|
-
/* overflow is not declared here: the popover carries [data-scrollable]
|
|
56582
|
-
(see box.jsx), which is what makes it scroll — and what a
|
|
56583
|
-
header/footer/body inside it then rearranges. */
|
|
56584
|
-
overscroll-behavior: none;
|
|
56585
|
-
/* left/top are NOT transitioned here — applyNewPosition (visible_rect.js)
|
|
56586
|
-
drives that itself via the Web Animations API instead of CSS, so it
|
|
56587
|
-
stays independent from navi-animation's own opacity/scale/display
|
|
56588
|
-
transition list below (no shared transition-property to clobber, no
|
|
56589
|
-
propertyName to filter). */
|
|
56590
|
-
/* overflow is not declared here: the popover carries [data-scrollable]
|
|
56591
|
-
(see box.jsx), which is what makes it scroll — and what a
|
|
56592
|
-
header/footer/body inside it then rearranges. */
|
|
56593
|
-
overscroll-behavior: none;
|
|
56594
|
-
|
|
56595
|
-
/* left/top are NOT transitioned here — applyNewPosition (visible_rect.js)
|
|
56596
|
-
drives that itself via the Web Animations API instead of CSS, so it
|
|
56597
|
-
stays independent from navi-animation's own opacity/scale/display
|
|
56598
|
-
transition list below (no shared transition-property to clobber, no
|
|
56599
|
-
propertyName to filter). */
|
|
56600
56857
|
/* The via-attribute renderer starts hidden for free (native UA default
|
|
56601
56858
|
for any [popover] element, same as <dialog> without [open]) — the
|
|
56602
56859
|
custom renderer is a plain div with no such native default, so
|
|
@@ -56900,6 +57157,16 @@ const css$D = /* css */`
|
|
|
56900
57157
|
* actually closes — not preventable (see `open_controller.js`'s own
|
|
56901
57158
|
* `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
|
|
56902
57159
|
* veto a close instead).
|
|
57160
|
+
* @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
|
|
57161
|
+
* Keeps the open state in the history entry, so a screen left and come back
|
|
57162
|
+
* to finds this popup as it was — open, and without an entrance playing: it
|
|
57163
|
+
* was already open when the page reappeared. `true` stores it under the
|
|
57164
|
+
* popup's own `id`; a string names the key instead.
|
|
57165
|
+
* `{ type: "push" }` also makes the opening a history entry of its own, so
|
|
57166
|
+
* the back button closes the popup rather than leaving the screen — and a
|
|
57167
|
+
* cancel (Escape) goes back, taking whatever was written to the url while it
|
|
57168
|
+
* was open with it. The state belongs to the entry that wrote it: a
|
|
57169
|
+
* navigation that stacks a new entry does not carry it along.
|
|
56903
57170
|
* @param {object} [props.openController] - Advanced: an externally-owned
|
|
56904
57171
|
* open controller (see `open_controller.js`) for a caller that wants to
|
|
56905
57172
|
* drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
|
|
@@ -56965,6 +57232,7 @@ const UncontrolledPopover = props => {
|
|
|
56965
57232
|
open: undefined,
|
|
56966
57233
|
signal: undefined,
|
|
56967
57234
|
defaultOpen: undefined,
|
|
57235
|
+
navState: undefined,
|
|
56968
57236
|
onClose: undefined,
|
|
56969
57237
|
openController: openController,
|
|
56970
57238
|
onnavi_request_open: e => {
|
|
@@ -57580,7 +57848,7 @@ const usePopoverProps = props => {
|
|
|
57580
57848
|
});
|
|
57581
57849
|
// A descendant anchored to something inside this popover (a Callout, a
|
|
57582
57850
|
// further-nested Popover) needing to know about this popover's own
|
|
57583
|
-
//
|
|
57851
|
+
// repositioning transition — not just that the target changed
|
|
57584
57852
|
// (navi_position_change above), but that a real, currently-playing
|
|
57585
57853
|
// transition is moving it right now — is handled generically by
|
|
57586
57854
|
// applyNewPosition itself (see its own notifyPositionTransition in
|