@jsenv/navi 0.30.1 → 0.30.3
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 +272 -108
- package/dist/dev/jsenv_navi.js.map +10 -8
- package/dist/jsenv_navi.js +272 -108
- package/dist/jsenv_navi.js.map +10 -8
- package/docs/dialog_shape.md +3 -0
- package/docs/navigation.md +10 -7
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -41552,6 +41552,73 @@ const publishRouteRowMove = (move) => {
|
|
|
41552
41552
|
}
|
|
41553
41553
|
};
|
|
41554
41554
|
|
|
41555
|
+
/**
|
|
41556
|
+
* What a hand holding a row of routes leaves the address unable to say.
|
|
41557
|
+
*
|
|
41558
|
+
* A page not built yet builds by the address moving, so the address runs ahead of
|
|
41559
|
+
* the hand. Nothing is decided while the hand holds the row: whatever says where the
|
|
41560
|
+
* reader is answers from here until the hand lets go.
|
|
41561
|
+
*
|
|
41562
|
+
* Apart from route_travel.jsx so that every link can read it without bringing a row
|
|
41563
|
+
* along.
|
|
41564
|
+
*/
|
|
41565
|
+
|
|
41566
|
+
|
|
41567
|
+
/**
|
|
41568
|
+
* The page the reader is still on while a hand holds the row and the address has run
|
|
41569
|
+
* ahead of it, `null` otherwise. A link to a page of the row answers from it by
|
|
41570
|
+
* itself; a counter, a title or a breadcrumb around the box reads it first.
|
|
41571
|
+
*
|
|
41572
|
+
* It goes back to `null` as soon as the hand has let go and the row moves. Nothing
|
|
41573
|
+
* says when the fingers leave a trackpad: there it lasts until the row has come to
|
|
41574
|
+
* rest. A travel no hand set off holds nothing: it goes where it goes.
|
|
41575
|
+
*
|
|
41576
|
+
* One for the document. `route` says which row, for a page holding several boxes.
|
|
41577
|
+
*/
|
|
41578
|
+
const routeTravelSettledPageSignal = signal(null);
|
|
41579
|
+
|
|
41580
|
+
// Where the address ran ahead to. A link has to tell it from any other link that
|
|
41581
|
+
// matches the address: an entry of a bottom bar whose route is a section around the
|
|
41582
|
+
// row stays lit.
|
|
41583
|
+
const headingPageSignal = signal(null);
|
|
41584
|
+
|
|
41585
|
+
const holdPage = (page, headingPage) => {
|
|
41586
|
+
headingPageSignal.value = headingPage;
|
|
41587
|
+
if (routeTravelSettledPageSignal.peek() === null) {
|
|
41588
|
+
routeTravelSettledPageSignal.value = page;
|
|
41589
|
+
}
|
|
41590
|
+
};
|
|
41591
|
+
|
|
41592
|
+
const releaseHeldPage = () => {
|
|
41593
|
+
routeTravelSettledPageSignal.value = null;
|
|
41594
|
+
headingPageSignal.value = null;
|
|
41595
|
+
};
|
|
41596
|
+
|
|
41597
|
+
/**
|
|
41598
|
+
* What a link to `route` with `params` answers because a hand holds a row.
|
|
41599
|
+
*
|
|
41600
|
+
* @returns {boolean|undefined} `undefined` when the hold says nothing about this
|
|
41601
|
+
* link, and the address answers.
|
|
41602
|
+
*/
|
|
41603
|
+
const readHeldLinkCurrent = (route, params) => {
|
|
41604
|
+
const settledPage = routeTravelSettledPageSignal.value;
|
|
41605
|
+
const headingPage = headingPageSignal.value;
|
|
41606
|
+
if (!settledPage) {
|
|
41607
|
+
return undefined;
|
|
41608
|
+
}
|
|
41609
|
+
// By address: a link may name fewer params than the page it leads to.
|
|
41610
|
+
const leadsTo = (page) =>
|
|
41611
|
+
page.route === route &&
|
|
41612
|
+
route.buildUrl(params) === route.buildUrl(page.params);
|
|
41613
|
+
if (leadsTo(settledPage)) {
|
|
41614
|
+
return true;
|
|
41615
|
+
}
|
|
41616
|
+
if (leadsTo(headingPage)) {
|
|
41617
|
+
return false;
|
|
41618
|
+
}
|
|
41619
|
+
return undefined;
|
|
41620
|
+
};
|
|
41621
|
+
|
|
41555
41622
|
/**
|
|
41556
41623
|
* A row of pages the browser scrolls itself, and what is left to read from it.
|
|
41557
41624
|
*
|
|
@@ -41640,12 +41707,21 @@ const readRowStretch = (scroller, pages, page) => {
|
|
|
41640
41707
|
* @param {(event: Event|null) => void} options.onLand - the row may have stopped.
|
|
41641
41708
|
* Read `pageAtRest` to know whether it stopped on a page. Heard more than once
|
|
41642
41709
|
* for one landing, so what it does must find nothing to do the second time.
|
|
41710
|
+
* @param {(forward: boolean) => void} [options.onLeftToSnap] - a finger has lifted
|
|
41711
|
+
* and the row moves: snapping plays out what the browser has decided and does not
|
|
41712
|
+
* turn round, so this is where it is going. Heard once per lift, after `onScroll`.
|
|
41713
|
+
* A trackpad has no lift, and never says this.
|
|
41643
41714
|
* @returns {() => void} stop listening.
|
|
41644
41715
|
*/
|
|
41645
|
-
const watchRowScroll = (
|
|
41716
|
+
const watchRowScroll = (
|
|
41717
|
+
scroller,
|
|
41718
|
+
{ onScroll, onLand, onLeftToSnap },
|
|
41719
|
+
) => {
|
|
41646
41720
|
let stoppedTimeout = null;
|
|
41647
41721
|
let touching = false;
|
|
41722
|
+
let liftedAt = null;
|
|
41648
41723
|
const landOnSilence = () => {
|
|
41724
|
+
liftedAt = null;
|
|
41649
41725
|
// A finger holding the row still: nothing has stopped.
|
|
41650
41726
|
if (!touching) {
|
|
41651
41727
|
onLand(null);
|
|
@@ -41658,14 +41734,27 @@ const watchRowScroll = (scroller, { onScroll, onLand }) => {
|
|
|
41658
41734
|
const onScrollEvent = () => {
|
|
41659
41735
|
onScroll();
|
|
41660
41736
|
armSilence();
|
|
41737
|
+
if (!liftedAt) {
|
|
41738
|
+
return;
|
|
41739
|
+
}
|
|
41740
|
+
// A map moves along one axis at a time, so the two add up to the one it moves on.
|
|
41741
|
+
const moved =
|
|
41742
|
+
scroller.scrollLeft - liftedAt.left + (scroller.scrollTop - liftedAt.top);
|
|
41743
|
+
if (Math.abs(moved) < AT_REST_DISTANCE) {
|
|
41744
|
+
return;
|
|
41745
|
+
}
|
|
41746
|
+
liftedAt = null;
|
|
41747
|
+
onLeftToSnap?.(moved > 0);
|
|
41661
41748
|
};
|
|
41662
41749
|
const onTouchStart = () => {
|
|
41663
41750
|
touching = true;
|
|
41751
|
+
liftedAt = null;
|
|
41664
41752
|
clearTimeout(stoppedTimeout);
|
|
41665
41753
|
};
|
|
41666
41754
|
const onTouchEnd = (touchEvent) => {
|
|
41667
41755
|
touching = touchEvent.touches.length > 0;
|
|
41668
41756
|
if (!touching) {
|
|
41757
|
+
liftedAt = { left: scroller.scrollLeft, top: scroller.scrollTop };
|
|
41669
41758
|
armSilence();
|
|
41670
41759
|
}
|
|
41671
41760
|
};
|
|
@@ -41764,19 +41853,6 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41764
41853
|
// neither a navigation nor the requests of a page.
|
|
41765
41854
|
const SET_OFF_PULL = 16;
|
|
41766
41855
|
|
|
41767
|
-
/**
|
|
41768
|
-
* What the URL says while a hand holds the row is where it is heading, for a page
|
|
41769
|
-
* not built yet: it builds by the address moving. A counter, a title or a
|
|
41770
|
-
* breadcrumb around the box must not move for a pull that is let go of: it reads
|
|
41771
|
-
* this first. The page the reader is still on while that lasts, and `null` when
|
|
41772
|
-
* the row has come to rest, which is when `scrollsnapchange` fires and a pager's
|
|
41773
|
-
* `settledPage` changes. A travel no hand set off holds nothing: it goes where it
|
|
41774
|
-
* goes.
|
|
41775
|
-
*
|
|
41776
|
-
* One for the document. `route` says which row, for a page holding several boxes.
|
|
41777
|
-
*/
|
|
41778
|
-
const routeTravelSettledPageSignal = signal(null);
|
|
41779
|
-
|
|
41780
41856
|
/**
|
|
41781
41857
|
* @type {import("preact").FunctionComponent<{
|
|
41782
41858
|
* routes?: Array<object|{route: object, params?: object}>,
|
|
@@ -41922,6 +41998,33 @@ const RouteTravel = ({
|
|
|
41922
41998
|
cause: "drag"
|
|
41923
41999
|
});
|
|
41924
42000
|
};
|
|
42001
|
+
|
|
42002
|
+
// The page the row goes to, once nothing is left to the hand.
|
|
42003
|
+
const askForPageOfRow = (index, movement) => {
|
|
42004
|
+
const row = rowRef.current;
|
|
42005
|
+
const page = latestRef.current.pages[index];
|
|
42006
|
+
if (windowed && !movement.pictures.has(index)) {
|
|
42007
|
+
leavePictureOfLive(movement);
|
|
42008
|
+
}
|
|
42009
|
+
Promise.resolve(askFor(page)).then(() => {
|
|
42010
|
+
if (pageIsCurrent(page)) {
|
|
42011
|
+
return;
|
|
42012
|
+
}
|
|
42013
|
+
// Refused, or sent elsewhere: the row says where the address is.
|
|
42014
|
+
askedPageRef.current = null;
|
|
42015
|
+
const {
|
|
42016
|
+
currentIndex
|
|
42017
|
+
} = latestRef.current;
|
|
42018
|
+
const paneCurrent = panesOf(row)[currentIndex];
|
|
42019
|
+
if (paneCurrent) {
|
|
42020
|
+
movement.travelling = true;
|
|
42021
|
+
row.scrollTo({
|
|
42022
|
+
left: paneCurrent.offsetLeft,
|
|
42023
|
+
behavior: "smooth"
|
|
42024
|
+
});
|
|
42025
|
+
}
|
|
42026
|
+
});
|
|
42027
|
+
};
|
|
41925
42028
|
const sayRowMoves = () => {
|
|
41926
42029
|
const row = rowRef.current;
|
|
41927
42030
|
const panes = panesOf(row);
|
|
@@ -41954,7 +42057,7 @@ const RouteTravel = ({
|
|
|
41954
42057
|
movementRef.current = null;
|
|
41955
42058
|
row.removeAttribute("data-moving");
|
|
41956
42059
|
publishRouteRowMove(null);
|
|
41957
|
-
|
|
42060
|
+
releaseHeldPage();
|
|
41958
42061
|
if (liveRef.current) {
|
|
41959
42062
|
liveRef.current.style.top = "";
|
|
41960
42063
|
}
|
|
@@ -42100,11 +42203,38 @@ const RouteTravel = ({
|
|
|
42100
42203
|
} else if (paneTowards.hasAttribute("data-built")) {
|
|
42101
42204
|
return;
|
|
42102
42205
|
}
|
|
42103
|
-
|
|
42104
|
-
routeTravelSettledPageSignal.value = pages[currentIndex];
|
|
42105
|
-
}
|
|
42206
|
+
holdPage(pages[currentIndex], page);
|
|
42106
42207
|
askFor(page);
|
|
42107
42208
|
};
|
|
42209
|
+
// The address, the tabs and what reads the held page all move here, the way they
|
|
42210
|
+
// do for a tab pressed. If the row ends up elsewhere after all, onLand corrects
|
|
42211
|
+
// the address when it rests.
|
|
42212
|
+
const onLeftToSnap = forward => {
|
|
42213
|
+
const movement = movementRef.current;
|
|
42214
|
+
const {
|
|
42215
|
+
pages,
|
|
42216
|
+
travelByDrag
|
|
42217
|
+
} = latestRef.current;
|
|
42218
|
+
if (!movement || movement.travelling || !travelByDrag) {
|
|
42219
|
+
return;
|
|
42220
|
+
}
|
|
42221
|
+
const panes = panesOf(row);
|
|
42222
|
+
const {
|
|
42223
|
+
base,
|
|
42224
|
+
toward
|
|
42225
|
+
} = readRowStretch(row, panes, panes[0]);
|
|
42226
|
+
const index = panes.indexOf(forward && toward ? toward : base);
|
|
42227
|
+
const page = pages[index];
|
|
42228
|
+
if (!page) {
|
|
42229
|
+
return;
|
|
42230
|
+
}
|
|
42231
|
+
if (!pageIsCurrent(page) && !samePage(askedPageRef.current, page)) {
|
|
42232
|
+
askForPageOfRow(index, movement);
|
|
42233
|
+
}
|
|
42234
|
+
// After the address: a pull coming back to its page must not light the tab the
|
|
42235
|
+
// address ran ahead to, even for a render.
|
|
42236
|
+
releaseHeldPage();
|
|
42237
|
+
};
|
|
42108
42238
|
const onLand = () => {
|
|
42109
42239
|
const movement = movementRef.current;
|
|
42110
42240
|
if (!movement) {
|
|
@@ -42129,37 +42259,19 @@ const RouteTravel = ({
|
|
|
42129
42259
|
if (samePage(askedPageRef.current, page)) {
|
|
42130
42260
|
return;
|
|
42131
42261
|
}
|
|
42132
|
-
|
|
42133
|
-
leavePictureOfLive(movement);
|
|
42134
|
-
}
|
|
42135
|
-
Promise.resolve(askFor(page)).then(() => {
|
|
42136
|
-
if (pageIsCurrent(page)) {
|
|
42137
|
-
return;
|
|
42138
|
-
}
|
|
42139
|
-
// Refused, or sent elsewhere: the row says where the address is.
|
|
42140
|
-
askedPageRef.current = null;
|
|
42141
|
-
const {
|
|
42142
|
-
currentIndex
|
|
42143
|
-
} = latestRef.current;
|
|
42144
|
-
const paneCurrent = panesOf(row)[currentIndex];
|
|
42145
|
-
if (paneCurrent) {
|
|
42146
|
-
movement.travelling = true;
|
|
42147
|
-
row.scrollTo({
|
|
42148
|
-
left: paneCurrent.offsetLeft,
|
|
42149
|
-
behavior: "smooth"
|
|
42150
|
-
});
|
|
42151
|
-
}
|
|
42152
|
-
});
|
|
42262
|
+
askForPageOfRow(panes.indexOf(pane), movement);
|
|
42153
42263
|
};
|
|
42154
42264
|
const stopWatching = watchRowScroll(row, {
|
|
42155
42265
|
onScroll,
|
|
42156
|
-
onLand
|
|
42266
|
+
onLand,
|
|
42267
|
+
onLeftToSnap
|
|
42157
42268
|
});
|
|
42158
42269
|
return () => {
|
|
42159
42270
|
stopWatching();
|
|
42160
42271
|
movementRef.current?.resumeScrollRecording?.();
|
|
42161
42272
|
movementRef.current = null;
|
|
42162
42273
|
publishRouteRowMove(null);
|
|
42274
|
+
releaseHeldPage();
|
|
42163
42275
|
};
|
|
42164
42276
|
}, [windowed]);
|
|
42165
42277
|
|
|
@@ -56845,7 +56957,8 @@ const NavContext = createContext(null);
|
|
|
56845
56957
|
|
|
56846
56958
|
/**
|
|
56847
56959
|
* Whether the page one is on is `route` with `params` — what a link or a button
|
|
56848
|
-
* pointing at a route shows as current.
|
|
56960
|
+
* pointing at a route shows as current. While a hand holds a row of routes, the
|
|
56961
|
+
* page one is on is the one held, wherever the address has run ahead to.
|
|
56849
56962
|
*
|
|
56850
56963
|
* Read through a computed of that one answer, so the component reading it
|
|
56851
56964
|
* re-renders when the answer flips, and not whenever the route's status moves:
|
|
@@ -56897,6 +57010,10 @@ const readRouteCurrent = (route, params, exceptRoutes, alsoRoutes) => {
|
|
|
56897
57010
|
someAlsoMatching = true;
|
|
56898
57011
|
}
|
|
56899
57012
|
}
|
|
57013
|
+
const heldAnswer = readHeldLinkCurrent(route, params);
|
|
57014
|
+
if (heldAnswer !== undefined) {
|
|
57015
|
+
return heldAnswer;
|
|
57016
|
+
}
|
|
56900
57017
|
if (someExceptMatching) {
|
|
56901
57018
|
return false;
|
|
56902
57019
|
}
|
|
@@ -75094,72 +75211,73 @@ const popupCss = /* css */ `
|
|
|
75094
75211
|
`;
|
|
75095
75212
|
|
|
75096
75213
|
/**
|
|
75097
|
-
* A
|
|
75098
|
-
*
|
|
75099
|
-
*
|
|
75100
|
-
*
|
|
75101
|
-
* Two things make an open
|
|
75102
|
-
*
|
|
75103
|
-
* - its ceiling (--dialog-maxmax-height), which CSS keeps
|
|
75104
|
-
* --navi-app-height. Written back resolved, inline. `freezeSize` cannot
|
|
75105
|
-
* a max-* wins over the width/height it writes;
|
|
75214
|
+
* A popup covered by a modal dialog is not in use, so it does not answer the
|
|
75215
|
+
* keyboard: a sheet raising one leaves what is behind it where it is, under the
|
|
75216
|
+
* keyboard like anything else behind that sheet.
|
|
75217
|
+
*
|
|
75218
|
+
* Two things make an open popup follow the keyboard, and both are held here for
|
|
75219
|
+
* as long as it is covered:
|
|
75220
|
+
* - its ceiling (--dialog-maxmax-height, --popover-maxmax-height), which CSS keeps
|
|
75221
|
+
* tracking --navi-app-height. Written back resolved, inline. `freezeSize` cannot
|
|
75222
|
+
* do it: a max-* wins over the width/height it writes;
|
|
75106
75223
|
* - its placement, which reads the live viewport. Deferred, see
|
|
75107
75224
|
* deferPlacementWhileCovered.
|
|
75108
75225
|
*
|
|
75109
|
-
* A
|
|
75110
|
-
* a keyboard on its way out. It is still counted for a while after the
|
|
75111
|
-
* closes (the browser takes its time, then --navi-vvh waits for the resize
|
|
75112
|
-
* settle, see responsive.js), and answering that makes the
|
|
75226
|
+
* A popup uncovered answers the screen as it is, at once, with one
|
|
75227
|
+
* exception: a keyboard on its way out. It is still counted for a while after the
|
|
75228
|
+
* sheet closes (the browser takes its time, then --navi-vvh waits for the resize
|
|
75229
|
+
* to settle, see responsive.js), and answering that makes the popup jump up and
|
|
75113
75230
|
* back down. A keyboard belongs to a focused field, so the focus says which case
|
|
75114
75231
|
* this is: on a field the keyboard stays, anywhere else it is leaving and the
|
|
75115
|
-
*
|
|
75116
|
-
*
|
|
75232
|
+
* popup waits for the viewport to change. No timer: how long a keyboard takes to
|
|
75233
|
+
* leave is unknown, and a duration would also delay a keyboard that stays.
|
|
75117
75234
|
*
|
|
75118
|
-
*
|
|
75119
|
-
* usable, and keeps following the keyboard.
|
|
75235
|
+
* Modal dialogs hold what is below them. What sits under a non-modal one is
|
|
75236
|
+
* still usable, and keeps following the keyboard. A top-layer popover can be
|
|
75237
|
+
* held and holds nothing: it follows its anchor, and that anchor may be inside
|
|
75238
|
+
* the popup below, whose rising is what carries a field in the popover above the
|
|
75239
|
+
* keyboard.
|
|
75120
75240
|
*/
|
|
75121
75241
|
|
|
75122
75242
|
|
|
75123
|
-
const
|
|
75124
|
-
|
|
75125
|
-
const modalStack = [];
|
|
75243
|
+
const coverStack = [];
|
|
75126
75244
|
|
|
75127
75245
|
/**
|
|
75128
|
-
* @param {
|
|
75246
|
+
* @param {HTMLElement} popupEl - The popup being shown.
|
|
75129
75247
|
* @param {object} params
|
|
75130
|
-
* @param {
|
|
75131
|
-
*
|
|
75132
|
-
* @
|
|
75248
|
+
* @param {string} params.ceiling - The custom property capping its height.
|
|
75249
|
+
* @param {boolean} params.holdsBelow - Whether it covers what is below it.
|
|
75250
|
+
* @param {() => void} params.place - Places the popup against the viewport as
|
|
75251
|
+
* it is. Called on a popup that was held once it answers the viewport again.
|
|
75252
|
+
* @returns {() => void} To call when the popup closes.
|
|
75133
75253
|
*/
|
|
75134
|
-
const
|
|
75135
|
-
const entry = {
|
|
75136
|
-
|
|
75137
|
-
|
|
75138
|
-
|
|
75254
|
+
const enterCoverStack = (popupEl, { ceiling, holdsBelow, place }) => {
|
|
75255
|
+
const entry = { popupEl, ceiling, holdsBelow, place, hold: null };
|
|
75256
|
+
if (holdsBelow) {
|
|
75257
|
+
// All of them: one under a popover was not held when that popover opened.
|
|
75258
|
+
for (const entryBelow of coverStack) {
|
|
75259
|
+
holdEntry(entryBelow);
|
|
75260
|
+
}
|
|
75139
75261
|
}
|
|
75140
|
-
|
|
75262
|
+
coverStack.push(entry);
|
|
75141
75263
|
return () => {
|
|
75142
|
-
|
|
75143
|
-
const wasOnTop = index === modalStack.length - 1;
|
|
75144
|
-
modalStack.splice(index, 1);
|
|
75264
|
+
coverStack.splice(coverStack.indexOf(entry), 1);
|
|
75145
75265
|
dropHold(entry);
|
|
75146
|
-
|
|
75147
|
-
releaseBackOnTop(modalStack.at(-1), { closedDialogEl: dialogEl });
|
|
75148
|
-
}
|
|
75266
|
+
releaseUncovered({ closedPopupEl: popupEl });
|
|
75149
75267
|
};
|
|
75150
75268
|
};
|
|
75151
75269
|
|
|
75152
75270
|
/**
|
|
75153
|
-
* Answers true when the
|
|
75271
|
+
* Answers true when the popup must not be placed right now, and remembers to
|
|
75154
75272
|
* place it on release.
|
|
75155
75273
|
*
|
|
75156
75274
|
* Only while the room differs from the one it was covered with: the placement
|
|
75157
75275
|
* reads the live viewport whatever triggered it, so with the same room it lands
|
|
75158
|
-
* where the
|
|
75276
|
+
* where the popup already is, and content growing under the sheet is answered
|
|
75159
75277
|
* as usual.
|
|
75160
75278
|
*/
|
|
75161
|
-
const deferPlacementWhileCovered = (
|
|
75162
|
-
const entry =
|
|
75279
|
+
const deferPlacementWhileCovered = (popupEl) => {
|
|
75280
|
+
const entry = coverStack.find((candidate) => candidate.popupEl === popupEl);
|
|
75163
75281
|
if (!entry || !entry.hold) {
|
|
75164
75282
|
return false;
|
|
75165
75283
|
}
|
|
@@ -75178,8 +75296,8 @@ const holdEntry = (entry) => {
|
|
|
75178
75296
|
entry.hold.stopWaiting = null;
|
|
75179
75297
|
return;
|
|
75180
75298
|
}
|
|
75181
|
-
const {
|
|
75182
|
-
holdCeiling(
|
|
75299
|
+
const { place } = entry;
|
|
75300
|
+
holdCeiling(entry);
|
|
75183
75301
|
entry.hold = {
|
|
75184
75302
|
room: readRoom(),
|
|
75185
75303
|
placementOwed: false,
|
|
@@ -75200,7 +75318,7 @@ const holdEntry = (entry) => {
|
|
|
75200
75318
|
}
|
|
75201
75319
|
hold.windowWidth = window.innerWidth;
|
|
75202
75320
|
hold.windowHeight = window.innerHeight;
|
|
75203
|
-
holdCeiling(
|
|
75321
|
+
holdCeiling(entry);
|
|
75204
75322
|
hold.room = readRoom();
|
|
75205
75323
|
hold.placementOwed = false;
|
|
75206
75324
|
place();
|
|
@@ -75208,18 +75326,33 @@ const holdEntry = (entry) => {
|
|
|
75208
75326
|
};
|
|
75209
75327
|
};
|
|
75210
75328
|
|
|
75211
|
-
|
|
75329
|
+
// From the top down to the first popup that holds what is below it: nothing
|
|
75330
|
+
// above covers any of these.
|
|
75331
|
+
const releaseUncovered = ({ closedPopupEl }) => {
|
|
75332
|
+
for (let i = coverStack.length - 1; i >= 0; i--) {
|
|
75333
|
+
const entry = coverStack[i];
|
|
75334
|
+
releaseBackOnTop(entry, { closedPopupEl });
|
|
75335
|
+
if (entry.holdsBelow) {
|
|
75336
|
+
return;
|
|
75337
|
+
}
|
|
75338
|
+
}
|
|
75339
|
+
};
|
|
75340
|
+
|
|
75341
|
+
const releaseBackOnTop = (entry, { closedPopupEl }) => {
|
|
75212
75342
|
const { hold } = entry;
|
|
75213
75343
|
if (!hold) {
|
|
75214
75344
|
return;
|
|
75215
75345
|
}
|
|
75346
|
+
// Already waiting when a popover above it closes in turn: this close asks again.
|
|
75347
|
+
hold.stopWaiting?.();
|
|
75348
|
+
hold.stopWaiting = null;
|
|
75216
75349
|
// Nothing taken away, or a keyboard that stays: the room read here is the
|
|
75217
|
-
// screen as it is. A field in the
|
|
75218
|
-
//
|
|
75350
|
+
// screen as it is. A field in the popup that closed is one still leaving: a
|
|
75351
|
+
// popup leaving the tree only gives the focus back after the commit.
|
|
75219
75352
|
const { activeElement } = document;
|
|
75220
75353
|
if (
|
|
75221
75354
|
readRoom() >= hold.room ||
|
|
75222
|
-
(isEditableTarget(activeElement) && !
|
|
75355
|
+
(isEditableTarget(activeElement) && !closedPopupEl.contains(activeElement))
|
|
75223
75356
|
) {
|
|
75224
75357
|
release(entry);
|
|
75225
75358
|
return;
|
|
@@ -75265,25 +75398,25 @@ const release = (entry) => {
|
|
|
75265
75398
|
};
|
|
75266
75399
|
|
|
75267
75400
|
const dropHold = (entry) => {
|
|
75268
|
-
const { hold,
|
|
75401
|
+
const { hold, popupEl, ceiling } = entry;
|
|
75269
75402
|
if (!hold) {
|
|
75270
75403
|
return;
|
|
75271
75404
|
}
|
|
75272
75405
|
hold.stopWaiting?.();
|
|
75273
75406
|
hold.stopWatchingWindow();
|
|
75274
|
-
|
|
75407
|
+
popupEl.style.removeProperty(ceiling);
|
|
75275
75408
|
entry.hold = null;
|
|
75276
75409
|
};
|
|
75277
75410
|
|
|
75278
75411
|
// The declared ceiling rather than the used max-height: it is the one cap that
|
|
75279
75412
|
// follows the keyboard, and the other caps (maxHeight) keep their say. Its
|
|
75280
75413
|
// computed value has every var() resolved, so it stops following once written
|
|
75281
|
-
// back. Registered with inherits: false, it does not reach a
|
|
75282
|
-
//
|
|
75283
|
-
const holdCeiling = (
|
|
75284
|
-
|
|
75285
|
-
const
|
|
75286
|
-
|
|
75414
|
+
// back. Registered with inherits: false, it does not reach a popup nested inside
|
|
75415
|
+
// this one.
|
|
75416
|
+
const holdCeiling = ({ popupEl, ceiling }) => {
|
|
75417
|
+
popupEl.style.removeProperty(ceiling);
|
|
75418
|
+
const value = getComputedStyle(popupEl).getPropertyValue(ceiling);
|
|
75419
|
+
popupEl.style.setProperty(ceiling, value);
|
|
75287
75420
|
};
|
|
75288
75421
|
|
|
75289
75422
|
// An overlaying keyboard leaves the viewport alone and arrives on its own.
|
|
@@ -77719,7 +77852,9 @@ const useDialogProps = props => {
|
|
|
77719
77852
|
}
|
|
77720
77853
|
rectEffect.observeSize(dialogEl);
|
|
77721
77854
|
if (isModal) {
|
|
77722
|
-
addCleanup(
|
|
77855
|
+
addCleanup(enterCoverStack(dialogEl, {
|
|
77856
|
+
ceiling: "--dialog-maxmax-height",
|
|
77857
|
+
holdsBelow: true,
|
|
77723
77858
|
// "resize": what it was held from is a viewport change, and it answers
|
|
77724
77859
|
// the way it answers one, animated. Called directly because the
|
|
77725
77860
|
// effect's dedup already saw that viewport go by.
|
|
@@ -78297,6 +78432,10 @@ const css$C = /* css */`
|
|
|
78297
78432
|
syntax: "*";
|
|
78298
78433
|
inherits: false;
|
|
78299
78434
|
}
|
|
78435
|
+
@property --popover-maxmax-height {
|
|
78436
|
+
syntax: "*";
|
|
78437
|
+
inherits: false;
|
|
78438
|
+
}
|
|
78300
78439
|
@property --x-popover-max-width {
|
|
78301
78440
|
syntax: "*";
|
|
78302
78441
|
inherits: false;
|
|
@@ -79672,27 +79811,37 @@ const usePopoverProps = props => {
|
|
|
79672
79811
|
popoverEl.showPopover();
|
|
79673
79812
|
}));
|
|
79674
79813
|
}
|
|
79675
|
-
|
|
79676
|
-
|
|
79677
|
-
}, {
|
|
79678
|
-
event
|
|
79679
|
-
}) => {
|
|
79814
|
+
let anchorVisibilityRatio = 1;
|
|
79815
|
+
const placeUnlessAnchorOutOfView = placeEvent => {
|
|
79680
79816
|
// Only a real anchor can meaningfully go "out of view" — gating on
|
|
79681
79817
|
// document.documentElement's own visibilityRatio (used for
|
|
79682
79818
|
// anchorless/docked popups) would wrongly skip positioning on a
|
|
79683
79819
|
// tall page, since its ratio is often low even when nothing's
|
|
79684
79820
|
// hidden. hasAnchorElement is already false for the custom
|
|
79685
79821
|
// renderer, so this never triggers for it either.
|
|
79686
|
-
if (hasAnchorElement &&
|
|
79822
|
+
if (hasAnchorElement && anchorVisibilityRatio <= 0.2) {
|
|
79687
79823
|
popoverEl.setAttribute("data-anchor-out-of-view", "");
|
|
79688
|
-
return;
|
|
79824
|
+
return false;
|
|
79689
79825
|
}
|
|
79690
79826
|
popoverEl.removeAttribute("data-anchor-out-of-view");
|
|
79691
|
-
positionPopover(
|
|
79827
|
+
positionPopover(placeEvent);
|
|
79828
|
+
return true;
|
|
79829
|
+
};
|
|
79830
|
+
const rectEffect = visibleRectEffect(effectiveAnchor, ({
|
|
79831
|
+
visibilityRatio
|
|
79832
|
+
}, {
|
|
79833
|
+
event
|
|
79834
|
+
}) => {
|
|
79835
|
+
anchorVisibilityRatio = visibilityRatio;
|
|
79836
|
+
// Before the out-of-view rule: a keyboard going over the anchor, behind
|
|
79837
|
+
// the sheet that raised it, hides nothing.
|
|
79838
|
+
if (deferPlacementWhileCovered(popoverEl)) {
|
|
79839
|
+
return;
|
|
79840
|
+
}
|
|
79692
79841
|
// Same as Dialog's own — a popup with a scrolling body and a footer
|
|
79693
79842
|
// swallows the field it just got shorter around. See
|
|
79694
79843
|
// keepFocusedElementVisible's own doc in popup_shared.js.
|
|
79695
|
-
if (mayHaveHiddenFocus(event)) {
|
|
79844
|
+
if (placeUnlessAnchorOutOfView(event) && mayHaveHiddenFocus(event)) {
|
|
79696
79845
|
keepFocusedElementVisible(popoverEl);
|
|
79697
79846
|
}
|
|
79698
79847
|
}, {
|
|
@@ -79705,6 +79854,21 @@ const usePopoverProps = props => {
|
|
|
79705
79854
|
// while open (e.g. an expand/collapse toggle inside it) — not just when
|
|
79706
79855
|
// the anchor itself moves/resizes/re-anchors.
|
|
79707
79856
|
rectEffect.observeSize(popoverEl);
|
|
79857
|
+
// Whatever its own backdrop: a modal dialog above leaves all of the page
|
|
79858
|
+
// inert, a popover without a wall included. layer="local" is left out, its
|
|
79859
|
+
// ceiling answers to its container.
|
|
79860
|
+
if (isTopLayer) {
|
|
79861
|
+
addCleanup(enterCoverStack(popoverEl, {
|
|
79862
|
+
ceiling: "--popover-maxmax-height",
|
|
79863
|
+
holdsBelow: false,
|
|
79864
|
+
// "resize": see Dialog's own.
|
|
79865
|
+
place: () => {
|
|
79866
|
+
if (placeUnlessAnchorOutOfView(new CustomEvent("resize"))) {
|
|
79867
|
+
keepFocusedElementVisible(popoverEl);
|
|
79868
|
+
}
|
|
79869
|
+
}
|
|
79870
|
+
}));
|
|
79871
|
+
}
|
|
79708
79872
|
// Content that sizes itself once on screen (a shrink-wrapped BadgeList)
|
|
79709
79873
|
// says so, after the placement read it: placed again on the spot, where
|
|
79710
79874
|
// observeSize answers a frame late. Stopped here, since a popup further up
|
|
@@ -107986,10 +108150,10 @@ const css = /* css */`.navi_side_panel {
|
|
|
107986
108150
|
height: var(--navi-side-panel-height, auto);
|
|
107987
108151
|
|
|
107988
108152
|
&[data-layer="top"] {
|
|
107989
|
-
--popover-max-height: var(--navi-app-height);
|
|
107990
108153
|
--popover-maxmax-height: var(--navi-app-height);
|
|
108154
|
+
--popover-max-height: var(--popover-maxmax-height);
|
|
107991
108155
|
--popover-maxmax-width: var(--navi-app-width);
|
|
107992
|
-
--dialog-maxmax-height: var(--
|
|
108156
|
+
--dialog-maxmax-height: var(--popover-maxmax-height);
|
|
107993
108157
|
--dialog-maxmax-width: var(--navi-app-width);
|
|
107994
108158
|
|
|
107995
108159
|
&[navi-side="left"], &[navi-side="right"] {
|