@jsenv/navi 0.29.301 → 0.29.303
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 +710 -194
- package/dist/dev/jsenv_navi.js.map +21 -16
- package/dist/jsenv_navi.js +687 -191
- package/dist/jsenv_navi.js.map +21 -16
- package/docs/dialog_shape.md +23 -8
- package/docs/interactions.md +94 -16
- package/docs/pan_zoom.md +2 -2
- package/docs/popup_backdrop.md +28 -7
- package/docs/popup_open.md +17 -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, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, refuseDragTo, startDragTo, installPanZoom, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, scrollIntoViewScoped, initFocusGroup, isTouchDrivenEvent,
|
|
7
|
+
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, refuseDragTo, startDragTo, installPanZoom, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, scrollIntoViewScoped, initFocusGroup, isTouchDrivenEvent, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, getVirtualKeyboardOverlayHeight, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
8
8
|
export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
|
|
9
9
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
10
10
|
import { isValidElement, createContext, render, h, toChildArray, options, cloneElement, Fragment as Fragment$1 } from "preact";
|
|
@@ -10127,6 +10127,11 @@ const readNumberFromDom = (element, attribute, defaultValue) => {
|
|
|
10127
10127
|
* happened. Registered through the same door an application uses (see
|
|
10128
10128
|
* interaction_registry.js) — navi has no private one.
|
|
10129
10129
|
*
|
|
10130
|
+
* `dblclick` is not among them, and deliberately: the browser only fires it for a
|
|
10131
|
+
* mouse, so an element declaring it would answer half the hands that reach it.
|
|
10132
|
+
* What a hand does twice is `double_click`, counted from the pointer in
|
|
10133
|
+
* interaction_press.js.
|
|
10134
|
+
*
|
|
10130
10135
|
* `contextmenu` is the only one that takes something away: the browser's own menu
|
|
10131
10136
|
* would cover the answer to the request it is. It takes it away AFTER, though —
|
|
10132
10137
|
* a native interaction IS its own event, the very one the gate reads, and a gate
|
|
@@ -10140,7 +10145,6 @@ const NATIVE_TYPE_SET = new Set([
|
|
|
10140
10145
|
"mousedown",
|
|
10141
10146
|
"mouseup",
|
|
10142
10147
|
"click",
|
|
10143
|
-
"dblclick",
|
|
10144
10148
|
"contextmenu",
|
|
10145
10149
|
]);
|
|
10146
10150
|
|
|
@@ -10198,6 +10202,15 @@ const SWIPE_THRESHOLD_DEFAULT = 0.33;
|
|
|
10198
10202
|
const LONGPRESS_DELAY_DEFAULT = 450;
|
|
10199
10203
|
// Past this the finger is going somewhere: it swipes, it does not hold.
|
|
10200
10204
|
const LONGPRESS_SLOP_DEFAULT = 8;
|
|
10205
|
+
// The window a double click is counted in, opened by the FIRST press. Under the
|
|
10206
|
+
// hold's delay on purpose: a pause longer than the wait navi calls "held" is
|
|
10207
|
+
// longer than one gesture.
|
|
10208
|
+
const DOUBLE_CLICK_DELAY_DEFAULT = 400;
|
|
10209
|
+
// How far apart the two presses may be, and how far either of them may travel
|
|
10210
|
+
// before it is a gesture rather than a tap. A fingertip and not a pixel: between
|
|
10211
|
+
// the two the finger leaves the glass and lands again where it means to, which is
|
|
10212
|
+
// a wider question than the hold's slop above (has this finger stood still?).
|
|
10213
|
+
const DOUBLE_CLICK_SLOP_DEFAULT = 30;
|
|
10201
10214
|
// How long the element takes to reach where the gesture leaves it, or to come
|
|
10202
10215
|
// back. Written into the CSS below from here: the state is cleaned up when the
|
|
10203
10216
|
// movement is over, so a duration living only in the stylesheet would be a timing
|
|
@@ -10209,11 +10222,19 @@ const SETTLE_DURATION_MS = 200;
|
|
|
10209
10222
|
const SWIPE_THRESHOLD_ATTRIBUTE = "data-swipe-threshold";
|
|
10210
10223
|
const LONGPRESS_DELAY_ATTRIBUTE = "data-longpress-delay";
|
|
10211
10224
|
const LONGPRESS_SLOP_ATTRIBUTE = "data-longpress-slop";
|
|
10225
|
+
const DOUBLE_CLICK_DELAY_ATTRIBUTE = "data-double-click-delay";
|
|
10226
|
+
const DOUBLE_CLICK_SLOP_ATTRIBUTE = "data-double-click-slop";
|
|
10212
10227
|
|
|
10213
|
-
// Which axes this element takes a swipe on, and that it takes a hold
|
|
10214
|
-
// DOM at render time, for the CSS below and for the boxes above
|
|
10228
|
+
// Which axes this element takes a swipe on, and that it takes a hold or counts
|
|
10229
|
+
// taps: said in the DOM at render time, for the CSS below and for the boxes above
|
|
10230
|
+
// to read.
|
|
10215
10231
|
const SWIPE_AXES_ATTRIBUTE = "data-swipe";
|
|
10216
10232
|
const LONGPRESS_ATTRIBUTE = "data-longpress";
|
|
10233
|
+
const DOUBLE_CLICK_ATTRIBUTE = "data-double-click";
|
|
10234
|
+
|
|
10235
|
+
const LONGPRESS = "longpress";
|
|
10236
|
+
const DOUBLE_CLICK = "double_click";
|
|
10237
|
+
const SINGLE_CLICK = "single_click";
|
|
10217
10238
|
|
|
10218
10239
|
import.meta.css = /* css */ [`@property --swipe-progress {
|
|
10219
10240
|
syntax: "<number>";
|
|
@@ -10239,15 +10260,19 @@ import.meta.css = /* css */ [`@property --swipe-progress {
|
|
|
10239
10260
|
touch-action: none;
|
|
10240
10261
|
}
|
|
10241
10262
|
|
|
10263
|
+
:where([data-double-click]) {
|
|
10264
|
+
touch-action: manipulation;
|
|
10265
|
+
}
|
|
10266
|
+
|
|
10242
10267
|
[data-longpress] {
|
|
10243
10268
|
-webkit-touch-callout: none;
|
|
10244
10269
|
}
|
|
10245
10270
|
|
|
10246
|
-
[data-longpress], [data-swipe] {
|
|
10271
|
+
[data-longpress], [data-swipe], [data-double-click] {
|
|
10247
10272
|
user-select: none;
|
|
10248
10273
|
}
|
|
10249
10274
|
|
|
10250
|
-
:is([data-longpress], [data-swipe]) :is([data-drag-ignore], [popover], dialog), :is([data-longpress], [data-swipe]) :is(input:not([data-press-only]), textarea), :is([data-longpress], [data-swipe]) :is([contenteditable=""], [contenteditable="true"]) {
|
|
10275
|
+
:is([data-longpress], [data-swipe], [data-double-click]) :is([data-drag-ignore], [popover], dialog), :is([data-longpress], [data-swipe], [data-double-click]) :is(input:not([data-press-only]), textarea), :is([data-longpress], [data-swipe], [data-double-click]) :is([contenteditable=""], [contenteditable="true"]) {
|
|
10251
10276
|
user-select: text;
|
|
10252
10277
|
}
|
|
10253
10278
|
|
|
@@ -10266,9 +10291,13 @@ import.meta.css = /* css */ [`@property --swipe-progress {
|
|
|
10266
10291
|
|
|
10267
10292
|
defineInteractionDetector({
|
|
10268
10293
|
name: "press",
|
|
10269
|
-
claims: (type) =>
|
|
10270
|
-
|
|
10271
|
-
|
|
10294
|
+
claims: (type) =>
|
|
10295
|
+
type in AXIS_BY_SWIPE_TYPE ||
|
|
10296
|
+
type === LONGPRESS ||
|
|
10297
|
+
type === DOUBLE_CLICK ||
|
|
10298
|
+
type === SINGLE_CLICK,
|
|
10299
|
+
// Every one of them is "what this press turns out to be": until it turns out,
|
|
10300
|
+
// the press is theirs.
|
|
10272
10301
|
disputesPress: true,
|
|
10273
10302
|
setup: (element, trigger, { types, readConfig }) => {
|
|
10274
10303
|
let axes = "";
|
|
@@ -10278,7 +10307,10 @@ defineInteractionDetector({
|
|
|
10278
10307
|
axes += axis;
|
|
10279
10308
|
}
|
|
10280
10309
|
}
|
|
10281
|
-
const hasLongPress = types.includes(
|
|
10310
|
+
const hasLongPress = types.includes(LONGPRESS);
|
|
10311
|
+
const hasDoubleClick = types.includes(DOUBLE_CLICK);
|
|
10312
|
+
const hasSingleClick = types.includes(SINGLE_CLICK);
|
|
10313
|
+
const countsTaps = hasDoubleClick || hasSingleClick;
|
|
10282
10314
|
|
|
10283
10315
|
const undo = [];
|
|
10284
10316
|
const mark = (attribute, value) => {
|
|
@@ -10331,6 +10363,79 @@ defineInteractionDetector({
|
|
|
10331
10363
|
if (hasLongPress) {
|
|
10332
10364
|
mark(LONGPRESS_ATTRIBUTE, "");
|
|
10333
10365
|
}
|
|
10366
|
+
if (countsTaps) {
|
|
10367
|
+
mark(DOUBLE_CLICK_ATTRIBUTE, "");
|
|
10368
|
+
}
|
|
10369
|
+
|
|
10370
|
+
// The tap a next press may pair with, and the window it is waited for in. It
|
|
10371
|
+
// outlives the press that made it — which is what a double click IS — so it
|
|
10372
|
+
// lives here rather than in the pointerdown below.
|
|
10373
|
+
let firstTap = null;
|
|
10374
|
+
let tapWindowTimeout = null;
|
|
10375
|
+
let tapWait = null;
|
|
10376
|
+
const forgetTaps = () => {
|
|
10377
|
+
clearTimeout(tapWindowTimeout);
|
|
10378
|
+
tapWindowTimeout = null;
|
|
10379
|
+
firstTap = null;
|
|
10380
|
+
tapWait?.cancel();
|
|
10381
|
+
tapWait = null;
|
|
10382
|
+
};
|
|
10383
|
+
undo.push(forgetTaps);
|
|
10384
|
+
// The window closes on a tap nothing came back for. Counted from the press
|
|
10385
|
+
// that opened it rather than from the tap that ends it, so what the caller
|
|
10386
|
+
// tunes is one rhythm and not a press plus a pause — a press slow enough to be
|
|
10387
|
+
// a hold therefore closes its own window on the spot, and cannot start a
|
|
10388
|
+
// double click.
|
|
10389
|
+
const openTapWindow = (pressEvent, tapEvent, delay) => {
|
|
10390
|
+
firstTap = {
|
|
10391
|
+
at: pressEvent.timeStamp,
|
|
10392
|
+
x: tapEvent.clientX,
|
|
10393
|
+
y: tapEvent.clientY,
|
|
10394
|
+
pointerType: tapEvent.pointerType,
|
|
10395
|
+
};
|
|
10396
|
+
if (hasSingleClick) {
|
|
10397
|
+
// The element's click is navi's from here: it is held for the length of
|
|
10398
|
+
// the window and `single_click` is what says it happened (see the top of
|
|
10399
|
+
// this file).
|
|
10400
|
+
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
10401
|
+
clickSuppressionIsOver();
|
|
10402
|
+
}
|
|
10403
|
+
clearTimeout(tapWindowTimeout);
|
|
10404
|
+
tapWindowTimeout = setTimeout(
|
|
10405
|
+
() => {
|
|
10406
|
+
tapWindowTimeout = null;
|
|
10407
|
+
firstTap = null;
|
|
10408
|
+
if (hasSingleClick) {
|
|
10409
|
+
trigger(SINGLE_CLICK, tapEvent, {
|
|
10410
|
+
pointerType: tapEvent.pointerType,
|
|
10411
|
+
});
|
|
10412
|
+
}
|
|
10413
|
+
},
|
|
10414
|
+
delay - (tapEvent.timeStamp - pressEvent.timeStamp),
|
|
10415
|
+
);
|
|
10416
|
+
};
|
|
10417
|
+
|
|
10418
|
+
if (hasSingleClick) {
|
|
10419
|
+
// A click no press made — a keyboard activation, an `element.click()`. The
|
|
10420
|
+
// window exists to find out whether a second press is coming, and there is
|
|
10421
|
+
// no press here, so it is not held: said at once, the way the browser's own
|
|
10422
|
+
// click would have been.
|
|
10423
|
+
const onClick = (clickEvent) => {
|
|
10424
|
+
if (isPressDrivenClick(clickEvent)) {
|
|
10425
|
+
// A press's own click, swallowed by the suppression armed at its tap:
|
|
10426
|
+
// it never reaches here, and if it does (the suppression having already
|
|
10427
|
+
// been spent) the tap has said it or is about to.
|
|
10428
|
+
return;
|
|
10429
|
+
}
|
|
10430
|
+
trigger(SINGLE_CLICK, clickEvent, {
|
|
10431
|
+
pointerType: clickEvent.pointerType,
|
|
10432
|
+
});
|
|
10433
|
+
};
|
|
10434
|
+
element.addEventListener("click", onClick);
|
|
10435
|
+
undo.push(() => {
|
|
10436
|
+
element.removeEventListener("click", onClick);
|
|
10437
|
+
});
|
|
10438
|
+
}
|
|
10334
10439
|
|
|
10335
10440
|
const onPointerDown = (pointerDownEvent) => {
|
|
10336
10441
|
if (pointerDownEvent.button !== 0) {
|
|
@@ -10341,6 +10446,50 @@ defineInteractionDetector({
|
|
|
10341
10446
|
// Set below, with the hold; a no-op until then so the swipe can call it.
|
|
10342
10447
|
let forgetInnerLongPress = () => {};
|
|
10343
10448
|
|
|
10449
|
+
if (countsTaps) {
|
|
10450
|
+
const delay = readConfig(
|
|
10451
|
+
DOUBLE_CLICK_DELAY_ATTRIBUTE,
|
|
10452
|
+
DOUBLE_CLICK_DELAY_DEFAULT,
|
|
10453
|
+
);
|
|
10454
|
+
const slop = readConfig(
|
|
10455
|
+
DOUBLE_CLICK_SLOP_ATTRIBUTE,
|
|
10456
|
+
DOUBLE_CLICK_SLOP_DEFAULT,
|
|
10457
|
+
);
|
|
10458
|
+
// Whether this press is the second of a pair is settled HERE, when it
|
|
10459
|
+
// lands, and not when it is let go of: the window is about where the
|
|
10460
|
+
// hand went and how soon, and a second press held a little longer than
|
|
10461
|
+
// the window would otherwise be answered as a lone click while it is
|
|
10462
|
+
// still down.
|
|
10463
|
+
const completesTheFirstTap =
|
|
10464
|
+
firstTap && continuesTap(firstTap, pointerDownEvent, delay, slop);
|
|
10465
|
+
if (completesTheFirstTap) {
|
|
10466
|
+
clearTimeout(tapWindowTimeout);
|
|
10467
|
+
tapWindowTimeout = null;
|
|
10468
|
+
} else {
|
|
10469
|
+
forgetTaps();
|
|
10470
|
+
}
|
|
10471
|
+
tapWait = waitForTap(pointerDownEvent, {
|
|
10472
|
+
slop,
|
|
10473
|
+
onTap: (tapEvent) => {
|
|
10474
|
+
tapWait = null;
|
|
10475
|
+
if (completesTheFirstTap) {
|
|
10476
|
+
firstTap = null;
|
|
10477
|
+
// The click this second press leaves behind was already answered,
|
|
10478
|
+
// by the gesture the two of them made. Armed and released at once:
|
|
10479
|
+
// the release does not lift the suppression, it says the gesture is
|
|
10480
|
+
// over (see suppressClickAfterGesture).
|
|
10481
|
+
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
10482
|
+
clickSuppressionIsOver();
|
|
10483
|
+
trigger(DOUBLE_CLICK, tapEvent, {
|
|
10484
|
+
pointerType: tapEvent.pointerType,
|
|
10485
|
+
});
|
|
10486
|
+
return;
|
|
10487
|
+
}
|
|
10488
|
+
openTapWindow(pointerDownEvent, tapEvent, delay);
|
|
10489
|
+
},
|
|
10490
|
+
});
|
|
10491
|
+
}
|
|
10492
|
+
|
|
10344
10493
|
if (axes) {
|
|
10345
10494
|
swipe = startSwipe(pointerDownEvent, {
|
|
10346
10495
|
element,
|
|
@@ -10360,6 +10509,9 @@ defineInteractionDetector({
|
|
|
10360
10509
|
press?.cancel();
|
|
10361
10510
|
press = null;
|
|
10362
10511
|
forgetInnerLongPress();
|
|
10512
|
+
// …and it is not a tap either, nor the second of a pair: the press
|
|
10513
|
+
// has left, and what it left is a swipe.
|
|
10514
|
+
forgetTaps();
|
|
10363
10515
|
},
|
|
10364
10516
|
});
|
|
10365
10517
|
}
|
|
@@ -10377,6 +10529,7 @@ defineInteractionDetector({
|
|
|
10377
10529
|
press.cancel();
|
|
10378
10530
|
press = null;
|
|
10379
10531
|
forgetInnerLongPress();
|
|
10532
|
+
warnOnceAboutHoldTakenByInner(element, longPressEvent.target);
|
|
10380
10533
|
};
|
|
10381
10534
|
forgetInnerLongPress = () => {
|
|
10382
10535
|
element.removeEventListener("longpress", onInnerLongPress);
|
|
@@ -10389,9 +10542,12 @@ defineInteractionDetector({
|
|
|
10389
10542
|
onPressHeld: (pressEvent, { endPress }) => {
|
|
10390
10543
|
forgetInnerLongPress();
|
|
10391
10544
|
// The hold won the arbitration: the swipe never got the distance it
|
|
10392
|
-
// needed, and must not get it from whatever the finger does next.
|
|
10545
|
+
// needed, and must not get it from whatever the finger does next. The
|
|
10546
|
+
// double click loses the same way — a press held this long is the
|
|
10547
|
+
// hold's whether it is the first of a pair or the second.
|
|
10393
10548
|
swipe?.stop();
|
|
10394
10549
|
swipe = null;
|
|
10550
|
+
forgetTaps();
|
|
10395
10551
|
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
10396
10552
|
const onPointerEnd = () => {
|
|
10397
10553
|
window.removeEventListener("pointerup", onPointerEnd, true);
|
|
@@ -10420,6 +10576,11 @@ defineInteractionDetector({
|
|
|
10420
10576
|
};
|
|
10421
10577
|
},
|
|
10422
10578
|
});
|
|
10579
|
+
const warnOnceAboutHoldTakenByInner = (element, innerElement) => {
|
|
10580
|
+
{
|
|
10581
|
+
return;
|
|
10582
|
+
}
|
|
10583
|
+
};
|
|
10423
10584
|
|
|
10424
10585
|
const startSwipe = (
|
|
10425
10586
|
pointerDownEvent,
|
|
@@ -10521,6 +10682,21 @@ const startSwipe = (
|
|
|
10521
10682
|
});
|
|
10522
10683
|
};
|
|
10523
10684
|
|
|
10685
|
+
// Whether a press landing now completes the tap before it: the same hand, soon
|
|
10686
|
+
// enough, and near enough. Axis by axis rather than as a distance, the way every
|
|
10687
|
+
// other slop in this family is read.
|
|
10688
|
+
const continuesTap = (tap, pointerDownEvent, delay, slop) => {
|
|
10689
|
+
if (pointerDownEvent.pointerType !== tap.pointerType) {
|
|
10690
|
+
return false;
|
|
10691
|
+
}
|
|
10692
|
+
if (pointerDownEvent.timeStamp - tap.at >= delay) {
|
|
10693
|
+
return false;
|
|
10694
|
+
}
|
|
10695
|
+
const xApart = Math.abs(pointerDownEvent.clientX - tap.x);
|
|
10696
|
+
const yApart = Math.abs(pointerDownEvent.clientY - tap.y);
|
|
10697
|
+
return xApart < slop && yApart < slop;
|
|
10698
|
+
};
|
|
10699
|
+
|
|
10524
10700
|
const swipeTypeOf = (axis, pulled) => {
|
|
10525
10701
|
const { positive, negative } = SWIPE_TYPE_BY_AXIS[axis];
|
|
10526
10702
|
return pulled > 0 ? positive : negative;
|
|
@@ -26215,6 +26391,31 @@ const getHrefTargetInfo = (href) => {
|
|
|
26215
26391
|
};
|
|
26216
26392
|
};
|
|
26217
26393
|
|
|
26394
|
+
/*
|
|
26395
|
+
* An app can ship more than one document — an admin panel, a status page, an
|
|
26396
|
+
* entry point of its own — and a link to one of them is not a route, it is a
|
|
26397
|
+
* page load. The address does not say which of the two it is, and guessing
|
|
26398
|
+
* would be worse than not knowing: "no route matches, so it must be another
|
|
26399
|
+
* document" turns a typo'd in-app url into a silent reload of the app instead
|
|
26400
|
+
* of its own not-found screen. So the link says it.
|
|
26401
|
+
*
|
|
26402
|
+
* `<Link document>` / `<Button document>` is that, and the point of saying it
|
|
26403
|
+
* on a link rather than navigating from an action is everything a link is: an
|
|
26404
|
+
* address in the status bar, a middle click, "open in new tab", something for
|
|
26405
|
+
* assistive technology to read. Only the interception steps aside; the element
|
|
26406
|
+
* stays a real link and the browser loads the page.
|
|
26407
|
+
*
|
|
26408
|
+
* It travels as an attribute for the same reason `replace` does (see
|
|
26409
|
+
* link_replace.js): whoever answers the press sees the element, not the
|
|
26410
|
+
* component that rendered it.
|
|
26411
|
+
*/
|
|
26412
|
+
|
|
26413
|
+
const LINK_DOCUMENT_ATTRIBUTE = "data-navi-document";
|
|
26414
|
+
|
|
26415
|
+
const linkAsksForDocument = (linkElement) => {
|
|
26416
|
+
return linkElement.hasAttribute(LINK_DOCUMENT_ATTRIBUTE);
|
|
26417
|
+
};
|
|
26418
|
+
|
|
26218
26419
|
/*
|
|
26219
26420
|
* A press aims at a place; it does not always go one step deeper. A row of tabs
|
|
26220
26421
|
* is a lateral move — the neighbour is one finger away — so the whole row should
|
|
@@ -26548,6 +26749,12 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
26548
26749
|
}
|
|
26549
26750
|
return;
|
|
26550
26751
|
}
|
|
26752
|
+
if (linkAsksForDocument(linkElement)) {
|
|
26753
|
+
// The link says its address is another document of this origin (see
|
|
26754
|
+
// link_document.js). Routing to it would land on the fallback route
|
|
26755
|
+
// instead of loading the page.
|
|
26756
|
+
return;
|
|
26757
|
+
}
|
|
26551
26758
|
// Nothing here declared a route, so there is nothing to route to: the
|
|
26552
26759
|
// page is a plain document and a link in it is a plain link. Taking it
|
|
26553
26760
|
// over anyway would push the url and then have nothing to show for it —
|
|
@@ -27097,6 +27304,160 @@ const Head = ({
|
|
|
27097
27304
|
return null;
|
|
27098
27305
|
};
|
|
27099
27306
|
|
|
27307
|
+
/**
|
|
27308
|
+
* A page is `{ route, params }`, never the route alone: a section of a page is
|
|
27309
|
+
* as often a PARAM as it is a route of its own, and three branches of the same
|
|
27310
|
+
* route told apart by their params are three pages one walks between.
|
|
27311
|
+
* `params` is undefined for a page that is a route on its own.
|
|
27312
|
+
*
|
|
27313
|
+
* Whether such a page is the one on screen is asked from three places — the
|
|
27314
|
+
* row a <RouteTravel> walks, the relations a movement is written between, and
|
|
27315
|
+
* the fallback naming the pages it is the absence of — and they must all get
|
|
27316
|
+
* the same answer, which is why the reading lives here rather than next to any
|
|
27317
|
+
* one of them.
|
|
27318
|
+
*/
|
|
27319
|
+
|
|
27320
|
+
// `matchesParams` reads paramsSignal, so a caller reading this during a render
|
|
27321
|
+
// is subscribed to the param changes that walk from one tab to the next —
|
|
27322
|
+
// matchingSignal alone never moves there, and a row whose tabs are params of
|
|
27323
|
+
// one route would never re-render.
|
|
27324
|
+
//
|
|
27325
|
+
// The params are read only for a route that matches, and that is not a signal
|
|
27326
|
+
// left unread: a reader wakes on anything it read last time, so what matters is
|
|
27327
|
+
// that everything able to make this answer change is among them.
|
|
27328
|
+
// matchingSignal is read whatever happens, and it is a NECESSARY condition —
|
|
27329
|
+
// while it is false no param of that route can put this page on screen, and the
|
|
27330
|
+
// day one could, matchingSignal itself has to turn true to say so, which is the
|
|
27331
|
+
// read that brings the params back in. (Asking anyway would be worse than
|
|
27332
|
+
// useless: the params of a route that does not match are not params.)
|
|
27333
|
+
const pageIsCurrent = ({ route, params }) => {
|
|
27334
|
+
if (!route.matchingSignal.value) {
|
|
27335
|
+
return false;
|
|
27336
|
+
}
|
|
27337
|
+
return params ? route.matchesParams(params) : true;
|
|
27338
|
+
};
|
|
27339
|
+
|
|
27340
|
+
/**
|
|
27341
|
+
* The page a <Route> container shows when none of its branches matched, given
|
|
27342
|
+
* a name.
|
|
27343
|
+
*
|
|
27344
|
+
* Every other branch of a container is a route, and a route is a thing one can
|
|
27345
|
+
* talk ABOUT from outside the tree: write a movement between two of them (see
|
|
27346
|
+
* route_transition.jsx), put one in a row (see route_travel.jsx), read whether
|
|
27347
|
+
* it is on screen. The fallback branch is the one page defined by an ABSENCE —
|
|
27348
|
+
* "an address none of these claim" — so it has no url pattern to be declared
|
|
27349
|
+
* from, and until it is named, nothing can be said about it.
|
|
27350
|
+
*
|
|
27351
|
+
* const NOT_FOUND_PAGE = routeFallback();
|
|
27352
|
+
* <Route fallback={NOT_FOUND_PAGE} element={NotFoundPage} />
|
|
27353
|
+
* defineRouteTransition(null, NOT_FOUND_PAGE, "slide-x");
|
|
27354
|
+
*
|
|
27355
|
+
* The absence is of a PARTICULAR set of claims — the branches of the container
|
|
27356
|
+
* holding it — and that set is not knowable from the routes of the application:
|
|
27357
|
+
* a route matching every address is a normal thing to declare (a carrier for
|
|
27358
|
+
* search params and route actions the whole app shares), and it would answer
|
|
27359
|
+
* "something matched" everywhere. So the container fills it in from the
|
|
27360
|
+
* children it was written with (see collectBranches in route.jsx), and a
|
|
27361
|
+
* fallback no container holds claims nothing.
|
|
27362
|
+
*
|
|
27363
|
+
* That leaves what a container cannot see: the address its own subtree lives
|
|
27364
|
+
* under. A section owning its sub-router renders it several components below
|
|
27365
|
+
* the leaf that matched the section's prefix, and "none of these five matched"
|
|
27366
|
+
* is then true on every screen of the application. Such a fallback names the
|
|
27367
|
+
* route it is under, and answers only there:
|
|
27368
|
+
*
|
|
27369
|
+
* const DASHBOARD_NOT_FOUND_PAGE = routeFallback(DASHBOARD_SECTION_ROUTE);
|
|
27370
|
+
*/
|
|
27371
|
+
|
|
27372
|
+
|
|
27373
|
+
const routeFallbackPrivatePropertiesMap = new WeakMap();
|
|
27374
|
+
|
|
27375
|
+
/**
|
|
27376
|
+
* Names the fallback branch of a <Route> container, so relations can be
|
|
27377
|
+
* written about the page it shows and its `matchingSignal` read from anywhere.
|
|
27378
|
+
*
|
|
27379
|
+
* @param {object} [scopeRoute] - the route this container's subtree lives
|
|
27380
|
+
* under, when the container is not the application's own router. Without it
|
|
27381
|
+
* the fallback answers wherever its container is on screen.
|
|
27382
|
+
* @returns {object} pass it as `<Route fallback={...}>`.
|
|
27383
|
+
*/
|
|
27384
|
+
const routeFallback = (scopeRoute) => {
|
|
27385
|
+
const pagesSignal = signal(null);
|
|
27386
|
+
const nothingMatches = () => {
|
|
27387
|
+
const pages = pagesSignal.value;
|
|
27388
|
+
if (!pages) {
|
|
27389
|
+
return false;
|
|
27390
|
+
}
|
|
27391
|
+
for (const page of pages) {
|
|
27392
|
+
if (pageIsCurrent(page)) {
|
|
27393
|
+
return false;
|
|
27394
|
+
}
|
|
27395
|
+
}
|
|
27396
|
+
return true;
|
|
27397
|
+
};
|
|
27398
|
+
const routeFallback = {
|
|
27399
|
+
isRouteFallback: true,
|
|
27400
|
+
scopeRoute,
|
|
27401
|
+
matchingSignal: scopeRoute
|
|
27402
|
+
? computed(() => {
|
|
27403
|
+
if (!scopeRoute.matchingSignal.value) {
|
|
27404
|
+
return false;
|
|
27405
|
+
}
|
|
27406
|
+
return nothingMatches();
|
|
27407
|
+
})
|
|
27408
|
+
: computed(nothingMatches),
|
|
27409
|
+
toString: () => {
|
|
27410
|
+
if (scopeRoute) {
|
|
27411
|
+
return `route fallback under ${scopeRoute}`;
|
|
27412
|
+
}
|
|
27413
|
+
return `route fallback`;
|
|
27414
|
+
},
|
|
27415
|
+
};
|
|
27416
|
+
Object.preventExtensions(routeFallback);
|
|
27417
|
+
routeFallbackPrivatePropertiesMap.set(routeFallback, { pagesSignal });
|
|
27418
|
+
return routeFallback;
|
|
27419
|
+
};
|
|
27420
|
+
|
|
27421
|
+
/**
|
|
27422
|
+
* The pages the fallback is the absence of, handed over by the container
|
|
27423
|
+
* holding it. Read as a signal rather than at render: a movement is decided
|
|
27424
|
+
* while rendering is held, before the container has had a chance to choose a
|
|
27425
|
+
* branch (see rendering_hold.js), so "we are on the fallback" has to be
|
|
27426
|
+
* answerable from the route signals alone.
|
|
27427
|
+
*/
|
|
27428
|
+
const wireRouteFallback = (routeFallback, pages) => {
|
|
27429
|
+
const { pagesSignal } = routeFallbackPrivatePropertiesMap.get(routeFallback);
|
|
27430
|
+
const pagesCurrent = pagesSignal.peek();
|
|
27431
|
+
if (pagesCurrent && samePages(pagesCurrent, pages)) {
|
|
27432
|
+
return;
|
|
27433
|
+
}
|
|
27434
|
+
pagesSignal.value = pages;
|
|
27435
|
+
};
|
|
27436
|
+
|
|
27437
|
+
const unwireRouteFallback = (routeFallback) => {
|
|
27438
|
+
const { pagesSignal } = routeFallbackPrivatePropertiesMap.get(routeFallback);
|
|
27439
|
+
pagesSignal.value = null;
|
|
27440
|
+
};
|
|
27441
|
+
|
|
27442
|
+
const samePages = (pages, otherPages) => {
|
|
27443
|
+
if (pages.length !== otherPages.length) {
|
|
27444
|
+
return false;
|
|
27445
|
+
}
|
|
27446
|
+
let i = 0;
|
|
27447
|
+
while (i < pages.length) {
|
|
27448
|
+
const page = pages[i];
|
|
27449
|
+
const otherPage = otherPages[i];
|
|
27450
|
+
if (page.route !== otherPage.route) {
|
|
27451
|
+
return false;
|
|
27452
|
+
}
|
|
27453
|
+
if (!compareTwoJsValues(page.params, otherPage.params)) {
|
|
27454
|
+
return false;
|
|
27455
|
+
}
|
|
27456
|
+
i++;
|
|
27457
|
+
}
|
|
27458
|
+
return true;
|
|
27459
|
+
};
|
|
27460
|
+
|
|
27100
27461
|
/**
|
|
27101
27462
|
* Route is the single primitive for URL-based rendering.
|
|
27102
27463
|
*
|
|
@@ -27115,10 +27476,15 @@ const Head = ({
|
|
|
27115
27476
|
* <Route element={AuthLayout}>
|
|
27116
27477
|
* <Route route={PROFILE_ROUTE} element={ProfilePage} />
|
|
27117
27478
|
* <Route route={SETTINGS_ROUTE} element={SettingsPage} />
|
|
27118
|
-
* <Route fallback element={AuthNotFoundPage} />
|
|
27119
27479
|
* </Route>
|
|
27120
27480
|
* ```
|
|
27121
27481
|
*
|
|
27482
|
+
* Such a container holds no fallback, and cannot: what makes this one the right
|
|
27483
|
+
* layout is a child matching, and having no prefix is the very reason it was
|
|
27484
|
+
* reached for — so there is no address that belongs to it and to none of its
|
|
27485
|
+
* children. An address nothing claims is the router's to answer; write the
|
|
27486
|
+
* fallback there.
|
|
27487
|
+
*
|
|
27122
27488
|
* ## Self-contained section pattern
|
|
27123
27489
|
* Use this when routes share a common URL prefix (e.g. `/dashboard/`).
|
|
27124
27490
|
* A single leaf Route in the top-level router matches the prefix; the component
|
|
@@ -27199,7 +27565,10 @@ const debug$1 = (...args) => {
|
|
|
27199
27565
|
* @param {object} props
|
|
27200
27566
|
* @param {object} [props.route] - the route this branch is for, from `route()`
|
|
27201
27567
|
* @param {object} [props.routeParams] - selects a branch on a param of that route
|
|
27202
|
-
* @param {boolean} [props.fallback] - the branch taken when no sibling
|
|
27568
|
+
* @param {boolean|object} [props.fallback] - the branch taken when no sibling
|
|
27569
|
+
* matches. `routeFallback()` gives that page a name, which is what lets a
|
|
27570
|
+
* movement be written about it and its `matchingSignal` be read from outside
|
|
27571
|
+
* the tree (see route_fallback.js).
|
|
27203
27572
|
* @param {Function|import("ignore:preact").VNode} [props.element] - what the branch renders
|
|
27204
27573
|
* @param {object} [props.elementProps] - props given to `element`
|
|
27205
27574
|
*
|
|
@@ -27277,7 +27646,7 @@ const collectRoutePages = children => {
|
|
|
27277
27646
|
// RouteContainer: traverses children statically per render, finds the active branch,
|
|
27278
27647
|
// and renders only that branch — or the fallback if nothing matches.
|
|
27279
27648
|
// No contexts, no state of its own: it reads the route signals directly, and
|
|
27280
|
-
// its
|
|
27649
|
+
// its effects only tell the outside what it has just done.
|
|
27281
27650
|
const RouteContainer = ({
|
|
27282
27651
|
id,
|
|
27283
27652
|
element,
|
|
@@ -27285,8 +27654,11 @@ const RouteContainer = ({
|
|
|
27285
27654
|
children
|
|
27286
27655
|
}) => {
|
|
27287
27656
|
const {
|
|
27288
|
-
activeBranch
|
|
27657
|
+
activeBranch,
|
|
27658
|
+
fallbackBranch,
|
|
27659
|
+
pages
|
|
27289
27660
|
} = collectBranches(children);
|
|
27661
|
+
const fallbackPage = fallbackBranch ? fallbackBranch.page : null;
|
|
27290
27662
|
|
|
27291
27663
|
// Told to hold still: what is on screen stays on screen. Kept as the very
|
|
27292
27664
|
// vnode that was rendered last time, which is how Preact is told there is
|
|
@@ -27298,11 +27670,25 @@ const RouteContainer = ({
|
|
|
27298
27670
|
}
|
|
27299
27671
|
const branch = shownBranchRef.current || activeBranch;
|
|
27300
27672
|
|
|
27301
|
-
// The
|
|
27302
|
-
//
|
|
27673
|
+
// The two things this component knows that nobody outside can find out: the
|
|
27674
|
+
// pages a named fallback is the absence of — the children it was written
|
|
27675
|
+
// with — and that the branch it chose is in the DOM.
|
|
27303
27676
|
useLayoutEffect(() => {
|
|
27677
|
+
if (fallbackPage) {
|
|
27678
|
+
wireRouteFallback(fallbackPage, pages);
|
|
27679
|
+
}
|
|
27304
27680
|
publishRouteRender();
|
|
27305
27681
|
});
|
|
27682
|
+
// Being wired means the container holding it is on screen: a router taken out
|
|
27683
|
+
// of the document leaves no fallback behind still claiming an address.
|
|
27684
|
+
useLayoutEffect(() => {
|
|
27685
|
+
if (!fallbackPage) {
|
|
27686
|
+
return undefined;
|
|
27687
|
+
}
|
|
27688
|
+
return () => {
|
|
27689
|
+
unwireRouteFallback(fallbackPage);
|
|
27690
|
+
};
|
|
27691
|
+
}, [fallbackPage]);
|
|
27306
27692
|
debug$1(`[container "${id}"] RENDER, active=${branch ? branch.type : "none"}`);
|
|
27307
27693
|
const content = branch ? branch.node : null;
|
|
27308
27694
|
if (!content) {
|
|
@@ -27316,10 +27702,16 @@ const RouteContainer = ({
|
|
|
27316
27702
|
// Walk JSX children vnodes (without rendering) to build a branch list and
|
|
27317
27703
|
// find the active one in the same pass. Anything that is not a <Route> is read
|
|
27318
27704
|
// through and kept around the branch it holds (see below).
|
|
27319
|
-
// Returns { matchingBranch, fallbackBranch, activeBranch }.
|
|
27705
|
+
// Returns { matchingBranch, fallbackBranch, activeBranch, pages }.
|
|
27320
27706
|
const collectBranches = children => {
|
|
27321
27707
|
let matchingBranch = null;
|
|
27322
27708
|
let fallbackBranch = null;
|
|
27709
|
+
// Every page a branch can be on, at any depth: what a named fallback is the
|
|
27710
|
+
// ABSENCE of (see route_fallback.js). A container with a guard route counts
|
|
27711
|
+
// as a page of its own AND contributes the pages inside it — either its guard
|
|
27712
|
+
// matching or a leaf matching under it is enough to select it, and the
|
|
27713
|
+
// fallback then stands down.
|
|
27714
|
+
const pages = [];
|
|
27323
27715
|
const visit = child => {
|
|
27324
27716
|
if (!child || child === true || child === false) {
|
|
27325
27717
|
return;
|
|
@@ -27347,8 +27739,12 @@ const collectBranches = children => {
|
|
|
27347
27739
|
}
|
|
27348
27740
|
const {
|
|
27349
27741
|
matchingBranch: matchingInside,
|
|
27350
|
-
fallbackBranch: fallbackInside
|
|
27742
|
+
fallbackBranch: fallbackInside,
|
|
27743
|
+
pages: pagesInside
|
|
27351
27744
|
} = collectBranches(wrapperChildren);
|
|
27745
|
+
for (const pageInside of pagesInside) {
|
|
27746
|
+
pages.push(pageInside);
|
|
27747
|
+
}
|
|
27352
27748
|
if (matchingInside && !matchingBranch) {
|
|
27353
27749
|
matchingBranch = wrapBranch(matchingInside, child);
|
|
27354
27750
|
}
|
|
@@ -27365,8 +27761,31 @@ const collectBranches = children => {
|
|
|
27365
27761
|
} = child.props;
|
|
27366
27762
|
if (nodeChildren) {
|
|
27367
27763
|
const {
|
|
27368
|
-
matchingBranch: matchingChild
|
|
27764
|
+
matchingBranch: matchingChild,
|
|
27765
|
+
fallbackBranch: fallbackChild,
|
|
27766
|
+
pages: pagesChild
|
|
27369
27767
|
} = collectBranches(nodeChildren);
|
|
27768
|
+
if (fallbackChild && !route) {
|
|
27769
|
+
// A fallback is the branch taken for an address none of its siblings
|
|
27770
|
+
// claim — so something has to make that address BELONG to this
|
|
27771
|
+
// container, and only a route of its own does. Without one the
|
|
27772
|
+
// container is selected by a child matching, which is the one case
|
|
27773
|
+
// where its fallback is not wanted: the branch could never render, and
|
|
27774
|
+
// an unmatched address is the router's to answer, not a layout's.
|
|
27775
|
+
throw new Error(`${describeContainer(child)} has no route of its own, so its fallback could never render: it is selected by one of its children matching, and an address none of them claim belongs to the router around it. Give it a route to be the fallback of (<Route route={SECTION}>), or write the fallback in that router.`);
|
|
27776
|
+
}
|
|
27777
|
+
if (route) {
|
|
27778
|
+
// Params are left out on purpose: a guard selects its container on
|
|
27779
|
+
// matching alone (see guardMatching below), so the fallback stands
|
|
27780
|
+
// down for the whole route, not for one of its cases.
|
|
27781
|
+
pages.push({
|
|
27782
|
+
route,
|
|
27783
|
+
params: undefined
|
|
27784
|
+
});
|
|
27785
|
+
}
|
|
27786
|
+
for (const pageChild of pagesChild) {
|
|
27787
|
+
pages.push(pageChild);
|
|
27788
|
+
}
|
|
27370
27789
|
const branch = {
|
|
27371
27790
|
type: "container",
|
|
27372
27791
|
node: child
|
|
@@ -27386,7 +27805,8 @@ const collectBranches = children => {
|
|
|
27386
27805
|
if (!fallbackBranch) {
|
|
27387
27806
|
fallbackBranch = {
|
|
27388
27807
|
type: "fallback",
|
|
27389
|
-
node: child
|
|
27808
|
+
node: child,
|
|
27809
|
+
page: fallback.isRouteFallback ? fallback : null
|
|
27390
27810
|
};
|
|
27391
27811
|
}
|
|
27392
27812
|
} else {
|
|
@@ -27394,6 +27814,10 @@ const collectBranches = children => {
|
|
|
27394
27814
|
type: "leaf",
|
|
27395
27815
|
node: child
|
|
27396
27816
|
};
|
|
27817
|
+
pages.push({
|
|
27818
|
+
route,
|
|
27819
|
+
params: routeParams
|
|
27820
|
+
});
|
|
27397
27821
|
// every signal is read even once a match is found: reading is what
|
|
27398
27822
|
// subscribes the container to it, and a branch that is skipped today is
|
|
27399
27823
|
// the one that must wake the container up tomorrow
|
|
@@ -27409,9 +27833,19 @@ const collectBranches = children => {
|
|
|
27409
27833
|
return {
|
|
27410
27834
|
matchingBranch,
|
|
27411
27835
|
fallbackBranch,
|
|
27412
|
-
activeBranch
|
|
27836
|
+
activeBranch,
|
|
27837
|
+
pages
|
|
27413
27838
|
};
|
|
27414
27839
|
};
|
|
27840
|
+
const describeContainer = ({
|
|
27841
|
+
props
|
|
27842
|
+
}) => {
|
|
27843
|
+
const {
|
|
27844
|
+
element
|
|
27845
|
+
} = props;
|
|
27846
|
+
const name = typeof element === "function" ? element.name : null;
|
|
27847
|
+
return name ? `<Route element={${name}}>` : `<Route>`;
|
|
27848
|
+
};
|
|
27415
27849
|
const wrapBranch = (branch, wrapper) => {
|
|
27416
27850
|
return {
|
|
27417
27851
|
...branch,
|
|
@@ -29211,7 +29645,7 @@ const beginTransition = ({
|
|
|
29211
29645
|
// on, those would freeze the page until the browser gives up. The wait
|
|
29212
29646
|
// is raced with a short timer instead.
|
|
29213
29647
|
await Promise.race([renderWait.rendered, waitMs(50)]);
|
|
29214
|
-
} else if (pageIsCurrent
|
|
29648
|
+
} else if (pageIsCurrent(page)) {
|
|
29215
29649
|
await renderWait.rendered;
|
|
29216
29650
|
}
|
|
29217
29651
|
} finally {
|
|
@@ -29426,16 +29860,21 @@ const armRouteRenderWait$1 = () => {
|
|
|
29426
29860
|
};
|
|
29427
29861
|
const waitMs = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
29428
29862
|
|
|
29429
|
-
// A page written as the route itself,
|
|
29430
|
-
//
|
|
29863
|
+
// A page written as the route itself, the page named by the absence of every
|
|
29864
|
+
// route around it (see route_fallback.js), and the page written nowhere:
|
|
29865
|
+
// `from` may be left out, which is a relation about arriving at `to` from
|
|
29866
|
+
// anywhere.
|
|
29431
29867
|
const normalizePage$1 = page => {
|
|
29432
29868
|
if (!page) {
|
|
29433
29869
|
return null;
|
|
29434
29870
|
}
|
|
29435
|
-
|
|
29436
|
-
|
|
29437
|
-
|
|
29438
|
-
|
|
29871
|
+
if (page.isRoute || page.isRouteFallback) {
|
|
29872
|
+
return {
|
|
29873
|
+
route: page,
|
|
29874
|
+
params: undefined
|
|
29875
|
+
};
|
|
29876
|
+
}
|
|
29877
|
+
return page;
|
|
29439
29878
|
};
|
|
29440
29879
|
|
|
29441
29880
|
// Two pages are the same page when they select the same thing, not when they
|
|
@@ -29451,19 +29890,6 @@ const samePage$1 = (a, b) => {
|
|
|
29451
29890
|
};
|
|
29452
29891
|
const pageIndexOf$1 = (pages, page) => pages.findIndex(candidate => samePage$1(candidate, page));
|
|
29453
29892
|
|
|
29454
|
-
// Whether this page is the one on screen — same reading as route_travel.jsx's
|
|
29455
|
-
// own: matchingSignal is the necessary condition and is read whatever happens,
|
|
29456
|
-
// params only for a route that matches (the params of a route that does not
|
|
29457
|
-
// match are not params).
|
|
29458
|
-
const pageIsCurrent$1 = ({
|
|
29459
|
-
route,
|
|
29460
|
-
params
|
|
29461
|
-
}) => {
|
|
29462
|
-
if (!route.matchingSignal.value) {
|
|
29463
|
-
return false;
|
|
29464
|
-
}
|
|
29465
|
-
return params ? route.matchesParams(params) : true;
|
|
29466
|
-
};
|
|
29467
29893
|
// The FIRST page that answers, and every page read all the same: a page that
|
|
29468
29894
|
// is not the current one today is the one that must wake the reader tomorrow.
|
|
29469
29895
|
//
|
|
@@ -29476,7 +29902,7 @@ const pageIsCurrent$1 = ({
|
|
|
29476
29902
|
const currentPageIndex$1 = pages => {
|
|
29477
29903
|
let currentIndex = -1;
|
|
29478
29904
|
for (let i = 0; i < pages.length; i++) {
|
|
29479
|
-
const isCurrent = pageIsCurrent
|
|
29905
|
+
const isCurrent = pageIsCurrent(pages[i]);
|
|
29480
29906
|
if (!isCurrent) {
|
|
29481
29907
|
continue;
|
|
29482
29908
|
}
|
|
@@ -30829,28 +31255,6 @@ const samePage = (a, b) => {
|
|
|
30829
31255
|
};
|
|
30830
31256
|
const pageIndexOf = (pages, page) => pages.findIndex(candidate => samePage(candidate, page));
|
|
30831
31257
|
|
|
30832
|
-
// Whether this page is the one on screen. `matchesParams` reads paramsSignal,
|
|
30833
|
-
// so a caller reading this during a render is subscribed to the param changes
|
|
30834
|
-
// that walk from one tab to the next — matchingSignal alone never moves there,
|
|
30835
|
-
// and a row whose tabs are params of one route would never re-render.
|
|
30836
|
-
//
|
|
30837
|
-
// The params are read only for a route that matches, and that is not a signal
|
|
30838
|
-
// left unread: a reader wakes on anything it read last time, so what matters is
|
|
30839
|
-
// that everything able to make this answer change is among them.
|
|
30840
|
-
// matchingSignal is read whatever happens, and it is a NECESSARY condition —
|
|
30841
|
-
// while it is false no param of that route can put this page on screen, and the
|
|
30842
|
-
// day one could, matchingSignal itself has to turn true to say so, which is the
|
|
30843
|
-
// read that brings the params back in. (Asking anyway would be worse than
|
|
30844
|
-
// useless: the params of a route that does not match are not params.)
|
|
30845
|
-
const pageIsCurrent = ({
|
|
30846
|
-
route,
|
|
30847
|
-
params
|
|
30848
|
-
}) => {
|
|
30849
|
-
if (!route.matchingSignal.value) {
|
|
30850
|
-
return false;
|
|
30851
|
-
}
|
|
30852
|
-
return params ? route.matchesParams(params) : true;
|
|
30853
|
-
};
|
|
30854
31258
|
// The FIRST page that answers, as with the branches of a <Route>: several
|
|
30855
31259
|
// routes match at once — a literal one and the parameterized one it is a case of
|
|
30856
31260
|
// ("/games/new" is also a "/games/:gameId"), a section and the page inside it —
|
|
@@ -33750,6 +34154,17 @@ const useUIStateController = (
|
|
|
33750
34154
|
`merging button state into parent control group:`,
|
|
33751
34155
|
mergedState,
|
|
33752
34156
|
);
|
|
34157
|
+
// The sender's value is part of the answer, and whoever mirrors
|
|
34158
|
+
// this group — a picker, through its façade — reads the group's
|
|
34159
|
+
// state, not its children's: told the way a mount sync is
|
|
34160
|
+
// (silently, nobody has answered yet), so the send that follows
|
|
34161
|
+
// finds the value there. Not through the group's own onChange:
|
|
34162
|
+
// that re-aggregates from the children, and a button is not one.
|
|
34163
|
+
parentController.parentUIStateController?.onChildUIAction?.(
|
|
34164
|
+
parentController,
|
|
34165
|
+
e,
|
|
34166
|
+
{ stateChanged: true, silent: true },
|
|
34167
|
+
);
|
|
33753
34168
|
}
|
|
33754
34169
|
}
|
|
33755
34170
|
// Trigger uiAction/command side effects without changing UI state.
|
|
@@ -35407,6 +35822,10 @@ const useUIGroupStateController = (
|
|
|
35407
35822
|
const { controller } = s;
|
|
35408
35823
|
const prevDefaultValue = controller.defaultValue;
|
|
35409
35824
|
controller.props = props;
|
|
35825
|
+
// Published like a leaf's (see the leaf update above): a child's named
|
|
35826
|
+
// button reaches the group's owner through it (see the button branch of
|
|
35827
|
+
// onUIAction).
|
|
35828
|
+
controller.parentUIStateController = parentUIStateController;
|
|
35410
35829
|
controller.ref = ref;
|
|
35411
35830
|
controller.id = id;
|
|
35412
35831
|
controller.name = name;
|
|
@@ -41524,6 +41943,17 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
41524
41943
|
* way there changes. What a row of tabs wants — the neighbour is a lateral
|
|
41525
41944
|
* move, not a step deeper, so the whole row weighs one entry and the back
|
|
41526
41945
|
* button leaves by where the reader came in.
|
|
41946
|
+
* @param {boolean} [props.document] - The address is ANOTHER DOCUMENT of this
|
|
41947
|
+
* origin — an admin panel, a status page, anything built as its own entry
|
|
41948
|
+
* point — so the press is left to the browser and loads that page. Without
|
|
41949
|
+
* it navi routes every same-origin address it can intercept, and one no
|
|
41950
|
+
* route matches lands on the app's not-found screen. Said rather than
|
|
41951
|
+
* guessed: "no route matches" is also what a typo in an in-app url looks
|
|
41952
|
+
* like, and that one must show the not-found screen instead of silently
|
|
41953
|
+
* reloading the app. The element stays a real link — an address in the
|
|
41954
|
+
* status bar, a middle click, "open in new tab" — which is what navigating
|
|
41955
|
+
* from an `action` costs; `target="_blank"` is a different thing, a new tab.
|
|
41956
|
+
* Nothing is prefetched on the way there: the routes do not lead there.
|
|
41527
41957
|
* @param {boolean} [props.prefetch=true] Fetch the code of where this leads when the
|
|
41528
41958
|
* pointer or the focus arrives, ahead of the press (see
|
|
41529
41959
|
* docs/dynamic_import.md): the route actions that ask nothing of the
|
|
@@ -41626,13 +42056,17 @@ const LinkPlain = props => {
|
|
|
41626
42056
|
routeTransition,
|
|
41627
42057
|
pressableDuringRouteTransition,
|
|
41628
42058
|
replace,
|
|
42059
|
+
document: isDocument,
|
|
41629
42060
|
prefetch = true,
|
|
41630
42061
|
children
|
|
41631
42062
|
} = props;
|
|
41632
42063
|
if (anchor && !props.id) {
|
|
41633
42064
|
props.id = href.slice(1);
|
|
41634
42065
|
}
|
|
41635
|
-
|
|
42066
|
+
// A document link leads out of the routes, so there is nothing there to
|
|
42067
|
+
// prefetch — and the address would otherwise be preloaded against the very
|
|
42068
|
+
// routes it is escaping.
|
|
42069
|
+
usePreloadOnIntent(props.ref, href, isDocument ? false : prefetch);
|
|
41636
42070
|
const selectionContext = useContext(SelectionContext);
|
|
41637
42071
|
const nav = useContext(NavContext);
|
|
41638
42072
|
const visited = useIsVisited(href);
|
|
@@ -41747,6 +42181,12 @@ const LinkPlain = props => {
|
|
|
41747
42181
|
const replaceRequest = replace ? {
|
|
41748
42182
|
[LINK_REPLACE_ATTRIBUTE]: ""
|
|
41749
42183
|
} : null;
|
|
42184
|
+
|
|
42185
|
+
// That this address is another document is the link's to say, worn where the
|
|
42186
|
+
// interception looks for it (see link_document.js).
|
|
42187
|
+
const documentRequest = isDocument ? {
|
|
42188
|
+
[LINK_DOCUMENT_ATTRIBUTE]: ""
|
|
42189
|
+
} : null;
|
|
41750
42190
|
const innerChildren = children || (hrefFallback ? href : children);
|
|
41751
42191
|
const startIconEl = startIcon;
|
|
41752
42192
|
const endIconEl = innerEndIcon;
|
|
@@ -41800,12 +42240,14 @@ const LinkPlain = props => {
|
|
|
41800
42240
|
routeTransition: undefined,
|
|
41801
42241
|
pressableDuringRouteTransition: undefined,
|
|
41802
42242
|
replace: undefined,
|
|
42243
|
+
document: undefined,
|
|
41803
42244
|
prefetch: undefined,
|
|
41804
42245
|
"data-navi-route-transition-request": routeTransitionRequest,
|
|
41805
42246
|
...(pressableDuringRouteTransition ? {
|
|
41806
42247
|
[PRESSABLE_ATTRIBUTE]: ""
|
|
41807
42248
|
} : null),
|
|
41808
42249
|
...replaceRequest,
|
|
42250
|
+
...documentRequest,
|
|
41809
42251
|
// The control's own handlers first — the interaction gate, the caller's
|
|
41810
42252
|
// onClick/onKeyDown, the command and the action — then what only a link
|
|
41811
42253
|
// does. Written over the spread above, so they have to be called here.
|
|
@@ -47643,6 +48085,7 @@ const ButtonUI = props => {
|
|
|
47643
48085
|
target,
|
|
47644
48086
|
rel,
|
|
47645
48087
|
replace,
|
|
48088
|
+
document: isDocument,
|
|
47646
48089
|
pressableDuringRouteTransition,
|
|
47647
48090
|
prefetch = true,
|
|
47648
48091
|
// visual
|
|
@@ -47697,13 +48140,21 @@ const ButtonUI = props => {
|
|
|
47697
48140
|
[LINK_REPLACE_ATTRIBUTE]: ""
|
|
47698
48141
|
} : null;
|
|
47699
48142
|
|
|
48143
|
+
// And this one says the address is another document of this origin, so the
|
|
48144
|
+
// press is the browser's (see link_document.js).
|
|
48145
|
+
const documentRequest = isDocument ? {
|
|
48146
|
+
[LINK_DOCUMENT_ATTRIBUTE]: ""
|
|
48147
|
+
} : null;
|
|
48148
|
+
|
|
47700
48149
|
// Worn as an attribute too, and read at the document by whoever catches the
|
|
47701
48150
|
// press a movement would have swallowed (see transition_press.js).
|
|
47702
48151
|
const pressableRequest = pressableDuringRouteTransition ? {
|
|
47703
48152
|
[PRESSABLE_ATTRIBUTE]: ""
|
|
47704
48153
|
} : null;
|
|
47705
48154
|
const visualSelector = ".navi_button_content";
|
|
47706
|
-
|
|
48155
|
+
// Nothing to prefetch on the way to another document: the routes do not
|
|
48156
|
+
// lead there.
|
|
48157
|
+
usePreloadOnIntent(ref, href, isDocument ? false : prefetch);
|
|
47707
48158
|
useAccentColorAttributes(ref, null, {
|
|
47708
48159
|
elementSelector: visualSelector
|
|
47709
48160
|
});
|
|
@@ -47731,6 +48182,8 @@ const ButtonUI = props => {
|
|
|
47731
48182
|
rel: innerRel,
|
|
47732
48183
|
replace: undefined,
|
|
47733
48184
|
...replaceRequest,
|
|
48185
|
+
document: undefined,
|
|
48186
|
+
...documentRequest,
|
|
47734
48187
|
pressableDuringRouteTransition: undefined,
|
|
47735
48188
|
...pressableRequest,
|
|
47736
48189
|
prefetch: undefined
|
|
@@ -48174,6 +48627,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48174
48627
|
* selfInteractions?: string,
|
|
48175
48628
|
* whenSelfInteractionsBlocked?: "hide" | "refuse" | "ignore",
|
|
48176
48629
|
* replace?: boolean,
|
|
48630
|
+
* document?: boolean,
|
|
48177
48631
|
* actionStandalone?: boolean,
|
|
48178
48632
|
* actionAbortable?: boolean,
|
|
48179
48633
|
* optimistic?: boolean,
|
|
@@ -48183,6 +48637,13 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48183
48637
|
* `--navi-nav-to` command — by TAKING THE PLACE of the current history entry
|
|
48184
48638
|
* rather than stacking on it: what `<Link replace>` says, for a press drawn
|
|
48185
48639
|
* as a button.
|
|
48640
|
+
* @param {boolean} [document] The `href` is ANOTHER DOCUMENT of this origin —
|
|
48641
|
+
* an admin panel, a status page, anything built as its own entry point — so
|
|
48642
|
+
* the press is left to the browser and loads that page, instead of being
|
|
48643
|
+
* routed to the app's not-found screen. What `<Link document>` says, for a
|
|
48644
|
+
* press drawn as a button; and the reason to say it rather than navigate
|
|
48645
|
+
* from an `action` is that the button stays a real link (an address, a
|
|
48646
|
+
* middle click, "open in new tab"). Nothing is prefetched on the way there.
|
|
48186
48647
|
* @param {boolean} [prefetch=true] Fetch the code of where this leads when the
|
|
48187
48648
|
* pointer or the focus arrives, ahead of the press (see
|
|
48188
48649
|
* docs/dynamic_import.md): the route actions that ask nothing of the
|
|
@@ -58121,10 +58582,10 @@ const css$E = /* css */`
|
|
|
58121
58582
|
var(--dialog-maxmax-height)
|
|
58122
58583
|
);
|
|
58123
58584
|
|
|
58124
|
-
/* Base default: also
|
|
58585
|
+
/* Base default: also a local dialog's own permanent value — its
|
|
58125
58586
|
containing block is genuinely its nearest positioned ancestor,
|
|
58126
58587
|
regardless of positionArea. See the [data-layer="top"] rule below for
|
|
58127
|
-
why
|
|
58588
|
+
why a top-layer one overrides this. Position is always
|
|
58128
58589
|
JS-driven (pickPositionRelativeTo, see useDialogProps below) — no CSS
|
|
58129
58590
|
alignment/inset math here at all. */
|
|
58130
58591
|
position: absolute;
|
|
@@ -58207,13 +58668,8 @@ const css$E = /* css */`
|
|
|
58207
58668
|
var(--container-position-remaining-width, var(--dialog-maxmax-width)),
|
|
58208
58669
|
var(--dialog-maxmax-width)
|
|
58209
58670
|
);
|
|
58210
|
-
/* The sheet rests on the screen's bottom edge, which on a phone is the
|
|
58211
|
-
home indicator and, since iOS 26, Safari's own floating bar — a band
|
|
58212
|
-
the browser does not paint fixed content into. The surface still
|
|
58213
|
-
reaches the edge (the sheet comes out from behind the bar); what it
|
|
58214
|
-
holds stops above it. */
|
|
58215
|
-
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
58216
58671
|
}
|
|
58672
|
+
|
|
58217
58673
|
/* The clamped max, not --dialog-maxmax-*: that one is the viewport minus
|
|
58218
58674
|
the spacing, which is only the real ceiling for layer="top". A local
|
|
58219
58675
|
dialog is confined to its positioned ancestor, whose size reaches here
|
|
@@ -58241,6 +58697,35 @@ const css$E = /* css */`
|
|
|
58241
58697
|
border-bottom-left-radius: 0;
|
|
58242
58698
|
}
|
|
58243
58699
|
|
|
58700
|
+
/* An edge the dialog sits flush against is an edge of the screen, and the
|
|
58701
|
+
device keeps a band there it paints nothing into: the notch, the home
|
|
58702
|
+
indicator, Safari's own floating bar since iOS 26. The surface still
|
|
58703
|
+
reaches the edge — a sheet comes out from behind the bar rather than
|
|
58704
|
+
stopping short of it — and what it holds stops at the band.
|
|
58705
|
+
|
|
58706
|
+
env(), not --navi-safe-area-inset-*: that one is the app's own free
|
|
58707
|
+
region and counts its fixed bars, which this dialog is in front of. The
|
|
58708
|
+
device's inset is the only thing left to keep.
|
|
58709
|
+
|
|
58710
|
+
layer="top" only. A local dialog is flush with the box it was declared
|
|
58711
|
+
in, and whether that box reaches the screen's edge is something only the
|
|
58712
|
+
app knows — it gives that band back on its own container (see
|
|
58713
|
+
safe_area.js's [data-navi-safe-area]). */
|
|
58714
|
+
&[data-layer="top"] {
|
|
58715
|
+
&[data-flush-top] {
|
|
58716
|
+
padding-top: env(safe-area-inset-top, 0px);
|
|
58717
|
+
}
|
|
58718
|
+
&[data-flush-right] {
|
|
58719
|
+
padding-right: env(safe-area-inset-right, 0px);
|
|
58720
|
+
}
|
|
58721
|
+
&[data-flush-bottom] {
|
|
58722
|
+
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
58723
|
+
}
|
|
58724
|
+
&[data-flush-left] {
|
|
58725
|
+
padding-left: env(safe-area-inset-left, 0px);
|
|
58726
|
+
}
|
|
58727
|
+
}
|
|
58728
|
+
|
|
58244
58729
|
/* The placement is a translate, so the translate property is spoken for
|
|
58245
58730
|
here (see applyNewPosition in visible_rect.js, which owns it and animates
|
|
58246
58731
|
it itself through the Web Animations API rather than through this file's
|
|
@@ -58253,8 +58738,8 @@ const css$E = /* css */`
|
|
|
58253
58738
|
--backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
|
|
58254
58739
|
}
|
|
58255
58740
|
/* backdropVariant, after the rules it overrides: same specificity, so
|
|
58256
|
-
order is what decides.
|
|
58257
|
-
|
|
58741
|
+
order is what decides. The wall is still there either way — only the
|
|
58742
|
+
paint goes away. */
|
|
58258
58743
|
&[data-backdrop-variant="discrete"] {
|
|
58259
58744
|
--backdrop-background: var(--navi-backdrop-discrete-background);
|
|
58260
58745
|
--backdrop-filter: var(--navi-backdrop-discrete-backdrop-filter);
|
|
@@ -58318,7 +58803,10 @@ const css$E = /* css */`
|
|
|
58318
58803
|
--navi-focus-outline-style: none;
|
|
58319
58804
|
}
|
|
58320
58805
|
|
|
58321
|
-
|
|
58806
|
+
/* :popover-open too — a top-layer dialog with no wall is shown with
|
|
58807
|
+
showPopover(), which never sets the [open] attribute. */
|
|
58808
|
+
&[open],
|
|
58809
|
+
&:popover-open {
|
|
58322
58810
|
display: flex;
|
|
58323
58811
|
}
|
|
58324
58812
|
|
|
@@ -58355,14 +58843,12 @@ const css$E = /* css */`
|
|
|
58355
58843
|
}
|
|
58356
58844
|
}
|
|
58357
58845
|
|
|
58358
|
-
/*
|
|
58846
|
+
/* layer="local" only — a .show()'d dialog gets no ::backdrop, so its wall is
|
|
58359
58847
|
a real sibling element instead, same idea/CSS shape as Popover's own
|
|
58360
|
-
.navi_popover_backdrop (see popover.jsx's top comment for the design
|
|
58361
|
-
|
|
58362
|
-
|
|
58363
|
-
|
|
58364
|
-
via-attribute renderer for free regardless of
|
|
58365
|
-
pointerInteractionOutsideEffect. */
|
|
58848
|
+
.navi_popover_backdrop (see popover.jsx's top comment for the design this
|
|
58849
|
+
mirrors). Rendered whenever backdrop={true}, whatever
|
|
58850
|
+
pointerInteractionOutsideEffect says: a wall that closes nothing still
|
|
58851
|
+
absorbs the press, matching what showModal() gives a modal for free. */
|
|
58366
58852
|
.navi_dialog_backdrop {
|
|
58367
58853
|
--popup-animation-duration: 0.18s;
|
|
58368
58854
|
|
|
@@ -58377,7 +58863,7 @@ const css$E = /* css */`
|
|
|
58377
58863
|
in openEffect) gets pointer-events: none mid-transition. */
|
|
58378
58864
|
pointer-events: auto;
|
|
58379
58865
|
|
|
58380
|
-
/* Painted through the same two variables as
|
|
58866
|
+
/* Painted through the same two variables as a modal's own
|
|
58381
58867
|
::backdrop (see them for what each rule is for) — here they resolve on
|
|
58382
58868
|
the element that paints, which is also the element the
|
|
58383
58869
|
backdropColor/backdropFilter props are set on. Declared unconditionally
|
|
@@ -58469,12 +58955,15 @@ const css$E = /* css */`
|
|
|
58469
58955
|
* grammar, anchor's sizing-only role, backdrop mechanics).
|
|
58470
58956
|
*
|
|
58471
58957
|
* @param {object} props
|
|
58472
|
-
* @param {"top"|"local"} [props.layer="top"] -
|
|
58473
|
-
*
|
|
58474
|
-
*
|
|
58475
|
-
* shown via the non-modal `.show()` instead, staying in normal document
|
|
58958
|
+
* @param {"top"|"local"} [props.layer="top"] - What the dialog is placed
|
|
58959
|
+
* against. `"top"`: the browser's own top layer, placed against the screen,
|
|
58960
|
+
* never clipped by anything the app declared. `"local"`: normal document
|
|
58476
58961
|
* flow inside its own positioned ancestor — confined to (and clipped by)
|
|
58477
|
-
* that container instead of the whole viewport.
|
|
58962
|
+
* that container instead of the whole viewport. Whether the top-layer one
|
|
58963
|
+
* is modal is `backdrop`'s answer, not this one's: with a backdrop it is
|
|
58964
|
+
* `showModal()`'d (native focus trap, `Escape`-to-cancel, hardware
|
|
58965
|
+
* back-button dismissal, rest-of-document made inert), without one it is
|
|
58966
|
+
* shown through the Popover API and the page behind stays live.
|
|
58478
58967
|
* @param {boolean} [props.dockedOnSmallTouchScreen] - Turns the dialog into a
|
|
58479
58968
|
* bottom sheet (docked flush to the bottom edge, full width) on a small touch
|
|
58480
58969
|
* screen, and leaves it alone otherwise. For a dialog meant to be interacted
|
|
@@ -58537,9 +59026,12 @@ const css$E = /* css */`
|
|
|
58537
59026
|
* outside press does or how the backdrop is painted. `false` leaves the page
|
|
58538
59027
|
* reachable: a press outside closes the dialog (per
|
|
58539
59028
|
* `pointerInteractionOutsideEffect`) *and* is answered by whatever it landed
|
|
58540
|
-
* on, in the same gesture
|
|
58541
|
-
*
|
|
58542
|
-
*
|
|
59029
|
+
* on, in the same gesture; focus is free to leave too, the page behind being
|
|
59030
|
+
* meant to be reached. Works in either layer — a top-layer dialog with no
|
|
59031
|
+
* wall is shown through the Popover API rather than `showModal()`, which is
|
|
59032
|
+
* what a sheet docked to the screen's bottom edge over a still-readable map
|
|
59033
|
+
* needs. What it gives up is what only a modal gets natively: the hardware
|
|
59034
|
+
* back button no longer dismisses it. See docs/popup_backdrop.md.
|
|
58543
59035
|
* @param {"close"|"cancel"|"capture"|"none"} [props.pointerInteractionOutsideEffect="close"]
|
|
58544
59036
|
* - `"close"` closes the dialog on an outside click. `"capture"`/`"none"`
|
|
58545
59037
|
* both just absorb the click without closing (visually dimmed backdrop vs.
|
|
@@ -58799,7 +59291,7 @@ const UncontrolledDialog = props => {
|
|
|
58799
59291
|
// Picks which rendering strategy actually mounts, from `layer` alone — see
|
|
58800
59292
|
// this file's top comment. Done after the controlled/uncontrolled split
|
|
58801
59293
|
// above, so an openController is always already resolved by the time
|
|
58802
|
-
//
|
|
59294
|
+
// DialogInTopLayer/DialogLocal (and the useDialogProps hook they share) ever
|
|
58803
59295
|
// run.
|
|
58804
59296
|
const ControlledDialog = props => {
|
|
58805
59297
|
if (props.layer === "local") {
|
|
@@ -58807,11 +59299,11 @@ const ControlledDialog = props => {
|
|
|
58807
59299
|
...props
|
|
58808
59300
|
});
|
|
58809
59301
|
}
|
|
58810
|
-
return jsx(
|
|
59302
|
+
return jsx(DialogInTopLayer, {
|
|
58811
59303
|
...props
|
|
58812
59304
|
});
|
|
58813
59305
|
};
|
|
58814
|
-
const
|
|
59306
|
+
const DialogInTopLayer = props => {
|
|
58815
59307
|
const [backdropProps, contentProps] = useDialogProps(props);
|
|
58816
59308
|
return jsxs(Fragment, {
|
|
58817
59309
|
children: [backdropProps && jsx(Box, {
|
|
@@ -58843,12 +59335,14 @@ const DialogLocal = props => {
|
|
|
58843
59335
|
/**
|
|
58844
59336
|
* Everything both rendering strategies share once an `openController` is
|
|
58845
59337
|
* already resolved: focus/debug/id plumbing, the open-commit sequence, the
|
|
58846
|
-
* close handler — inlined in `openEffect`, branching on
|
|
59338
|
+
* close handler — inlined in `openEffect`, branching on how the dialog is
|
|
59339
|
+
* shown at each
|
|
58847
59340
|
* point the two renderers genuinely differ (same pattern as popover.jsx's
|
|
58848
59341
|
* own usePopoverProps — see its top comment for why this stays inline
|
|
58849
59342
|
* rather than split into two functions). Returns `[backdropProps,
|
|
58850
|
-
* contentProps]` — `backdropProps` is `null`
|
|
58851
|
-
*
|
|
59343
|
+
* contentProps]` — `backdropProps` is `null` unless there is a wall of our
|
|
59344
|
+
* own to render: a modal's is the native `::backdrop`, and `backdrop={false}`
|
|
59345
|
+
* has none at all.
|
|
58852
59346
|
*/
|
|
58853
59347
|
// What a dialog turns into on a small touch screen. "bottom" is not a taste:
|
|
58854
59348
|
// it puts the dialog in the zone a phone is actually operated from — where the
|
|
@@ -58880,10 +59374,10 @@ const useDialogProps = props => {
|
|
|
58880
59374
|
props.ref = props.ref || defaultRef;
|
|
58881
59375
|
const {
|
|
58882
59376
|
openController,
|
|
58883
|
-
// "top" (default) →
|
|
58884
|
-
//
|
|
58885
|
-
// .
|
|
58886
|
-
//
|
|
59377
|
+
// "top" (default) → the browser's own top layer, placed against the
|
|
59378
|
+
// screen. "local" → normal document flow, position: absolute relative to
|
|
59379
|
+
// its own positioned ancestor. Which show call each takes also depends on
|
|
59380
|
+
// `backdrop` — see this file's top comment.
|
|
58887
59381
|
layer = "top",
|
|
58888
59382
|
dockedOnSmallTouchScreen,
|
|
58889
59383
|
// Same grammar as Popover's own positionArea — see this file's top
|
|
@@ -58897,17 +59391,16 @@ const useDialogProps = props => {
|
|
|
58897
59391
|
expandX: expandXProp,
|
|
58898
59392
|
expandY: expandYProp,
|
|
58899
59393
|
// "close" (default) closes on an outside click. "capture"/"none" both
|
|
58900
|
-
// just absorb it without closing — for
|
|
58901
|
-
//
|
|
58902
|
-
//
|
|
58903
|
-
//
|
|
58904
|
-
// actually makes "capture"/"none" behave the same way here too.
|
|
59394
|
+
// just absorb it without closing — for a modal, showModal() already makes
|
|
59395
|
+
// the rest of the page inert, so there's nothing for a click to reach
|
|
59396
|
+
// either way; otherwise there's no native inert-ing, so the real backdrop
|
|
59397
|
+
// below is what makes "capture"/"none" behave the same way here too.
|
|
58905
59398
|
pointerInteractionOutsideEffect = "close",
|
|
58906
59399
|
// Whether there is a wall between the dialog and the page at all, asked
|
|
58907
59400
|
// before what a press on it does (pointerInteractionOutsideEffect) and
|
|
58908
|
-
// before how it is painted (backdropVariant below).
|
|
58909
|
-
//
|
|
58910
|
-
//
|
|
59401
|
+
// before how it is painted (backdropVariant below). It also picks the show
|
|
59402
|
+
// call for layer="top": a wall is what showModal() is for, and without one
|
|
59403
|
+
// the dialog goes to the same top layer through the Popover API instead.
|
|
58911
59404
|
backdrop = true,
|
|
58912
59405
|
// How loudly the backdrop says it is there — independent of what it
|
|
58913
59406
|
// *does* (that's pointerInteractionOutsideEffect above). "invisible" is a
|
|
@@ -58964,10 +59457,11 @@ const useDialogProps = props => {
|
|
|
58964
59457
|
anchor
|
|
58965
59458
|
});
|
|
58966
59459
|
const children = contentMounted ? childrenProp : null;
|
|
58967
|
-
|
|
58968
|
-
|
|
58969
|
-
|
|
58970
|
-
|
|
59460
|
+
// Where it is placed, and whether there is a wall — the two questions that
|
|
59461
|
+
// pick the show call (see this file's top comment).
|
|
59462
|
+
const isTopLayer = layer === "top";
|
|
59463
|
+
const isModal = isTopLayer && backdrop;
|
|
59464
|
+
const isTopLayerPopover = isTopLayer && !backdrop;
|
|
58971
59465
|
if (!backdrop && pointerInteractionOutsideEffect === "capture") {
|
|
58972
59466
|
console.warn(`Dialog: pointerInteractionOutsideEffect="capture" needs a backdrop. Absorbing a press is what a wall does, and backdrop={false} takes it away.`);
|
|
58973
59467
|
}
|
|
@@ -58988,7 +59482,7 @@ const useDialogProps = props => {
|
|
|
58988
59482
|
// container being that screen (the viewport, unless the app declared a
|
|
58989
59483
|
// narrower one) — and the positioned ancestor for a local one, where
|
|
58990
59484
|
// reading 3% of the screen gives an absurd gap inside a small box.
|
|
58991
|
-
|
|
59485
|
+
isTopLayer ? "3appw" : "3cqw");
|
|
58992
59486
|
// "expand || expandX", the shorthand semantics Popup used to apply before
|
|
58993
59487
|
// handing them over — the docked default only applies when neither was said
|
|
58994
59488
|
const expandXUnset = expand === undefined && expandXProp === undefined;
|
|
@@ -59177,14 +59671,14 @@ const useDialogProps = props => {
|
|
|
59177
59671
|
// from dialogEl.parentElement, which for DialogLocal is the
|
|
59178
59672
|
// .navi_dialog_clip_wrapper (itself position: absolute) rather than the
|
|
59179
59673
|
// real, meaningful ancestor beyond it.
|
|
59180
|
-
const positionedAncestor =
|
|
59674
|
+
const positionedAncestor = isTopLayer ? document.documentElement : getPositionedParent(dialogEl.parentElement /* dialogEl is inside the clip_wrapper */);
|
|
59181
59675
|
const [cleanup, addCleanup] = createPubSub(true);
|
|
59182
59676
|
const anchorElement = resolveAnchorElement(e);
|
|
59183
59677
|
// Kept for the closing, which has no opening event to read it back from:
|
|
59184
59678
|
// the box the dialog shrinks back into is the one it came out of.
|
|
59185
59679
|
anchorElementRef.current = anchorElement;
|
|
59186
59680
|
debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> openDialog`);
|
|
59187
|
-
if (!
|
|
59681
|
+
if (!isTopLayer) {
|
|
59188
59682
|
// see openLocalDialogCount's own comment
|
|
59189
59683
|
dialogEl.style.setProperty("--dialog-stack-order", openLocalDialogCount++);
|
|
59190
59684
|
}
|
|
@@ -59227,10 +59721,12 @@ const useDialogProps = props => {
|
|
|
59227
59721
|
}
|
|
59228
59722
|
if (isModal) {
|
|
59229
59723
|
dialogEl.showModal();
|
|
59724
|
+
} else if (isTopLayerPopover) {
|
|
59725
|
+
dialogEl.showPopover();
|
|
59230
59726
|
} else {
|
|
59231
59727
|
dialogEl.show();
|
|
59232
59728
|
}
|
|
59233
|
-
//
|
|
59729
|
+
// Whichever show call ran — see the backdrop's own [navi-hidden] CSS rule
|
|
59234
59730
|
// and popover.jsx's identical reasoning: showModal()/show() alone only
|
|
59235
59731
|
// wins over a stray, still-present [navi-hidden] { display: none }
|
|
59236
59732
|
// default when nothing else authored also sets display on dialogEl —
|
|
@@ -59238,24 +59734,23 @@ const useDialogProps = props => {
|
|
|
59238
59734
|
// property (e.g. Popup's own flex prop) defeats the UA stylesheet's own
|
|
59239
59735
|
// dialog:not([open]) default the same way it can for Popover.
|
|
59240
59736
|
dialogEl.removeAttribute("navi-hidden");
|
|
59241
|
-
if (isModal) ; else {
|
|
59737
|
+
if (isModal) ; else if (backdrop) {
|
|
59242
59738
|
addCleanup(trapFocusInside(dialogEl, {
|
|
59243
59739
|
debug: debugFocus,
|
|
59244
59740
|
boundaryElement: positionedAncestor,
|
|
59245
|
-
//
|
|
59246
|
-
//
|
|
59247
|
-
//
|
|
59248
|
-
// from it either, not just a Tab press.
|
|
59741
|
+
// The wall stops the keyboard too — a mousedown on some other
|
|
59742
|
+
// focusable element behind it must not steal the focus away either,
|
|
59743
|
+
// not just a Tab press.
|
|
59249
59744
|
pointerTrap: true
|
|
59250
59745
|
}));
|
|
59251
59746
|
}
|
|
59252
59747
|
if (scrollCapture) {
|
|
59253
|
-
// A modal dialog always has its own ::backdrop;
|
|
59254
|
-
//
|
|
59748
|
+
// A modal dialog always has its own ::backdrop; a local one has the
|
|
59749
|
+
// backdrop element when it renders one.
|
|
59255
59750
|
addCleanup(trapScrollInside(dialogEl, {
|
|
59256
59751
|
backdrop: isModal || backdropEl
|
|
59257
59752
|
}));
|
|
59258
|
-
} else if (!
|
|
59753
|
+
} else if (!isTopLayer) {
|
|
59259
59754
|
// A local dialog is confined to its positioned ancestor, and so is its
|
|
59260
59755
|
// backdrop (inset: 0 covers the scrollport, not the scrolled content):
|
|
59261
59756
|
// letting that ancestor scroll would slide the dialog away and reveal
|
|
@@ -59272,7 +59767,7 @@ const useDialogProps = props => {
|
|
|
59272
59767
|
// Positioning: dialogEl is already shown (display: flex, per this
|
|
59273
59768
|
// file's own [open] CSS) by this point, so its own dimensions are real
|
|
59274
59769
|
// — pickPositionRelativeTo's own no-anchor/docked mode (no `anchor`
|
|
59275
|
-
// argument at all) docks it against the viewport (layer="top"
|
|
59770
|
+
// argument at all) docks it against the viewport (layer="top")
|
|
59276
59771
|
// or its own positioned ancestor (layer="local", the same
|
|
59277
59772
|
// positionedAncestor computed above), same mechanism as Popover's own
|
|
59278
59773
|
// custom renderer. applyDialogPosition sets --container-position-remaining-height/-width
|
|
@@ -59304,7 +59799,7 @@ const useDialogProps = props => {
|
|
|
59304
59799
|
// box is what the caps must read, and nothing in CSS tracks it.
|
|
59305
59800
|
const applyDialogPosition = position => {
|
|
59306
59801
|
applyNewPosition(dialogEl, position);
|
|
59307
|
-
if (
|
|
59802
|
+
if (isTopLayer) {
|
|
59308
59803
|
dialogEl.style.removeProperty("--container-position-remaining-height");
|
|
59309
59804
|
dialogEl.style.removeProperty("--container-position-remaining-width");
|
|
59310
59805
|
}
|
|
@@ -59403,7 +59898,7 @@ const useDialogProps = props => {
|
|
|
59403
59898
|
|
|
59404
59899
|
// Reposition on the same triggers Popover's own visibleRectEffect
|
|
59405
59900
|
// already reacts to generically — window resize/scroll/visual-viewport
|
|
59406
|
-
// changes for layer="top"
|
|
59901
|
+
// changes for layer="top" (positionedAncestor is already
|
|
59407
59902
|
// document.documentElement there, see its own computation above;
|
|
59408
59903
|
// visibleRectEffect already debounces visualViewport resize by 100ms
|
|
59409
59904
|
// to avoid the mobile tap-to-tap-input keyboard flicker, so no
|
|
@@ -59501,15 +59996,19 @@ const useDialogProps = props => {
|
|
|
59501
59996
|
debugPopup(`"${closeEvent.type}" on ${getElementSignature(closeEvent.target)} -> closeDialog`);
|
|
59502
59997
|
clearTextSelectionInside(dialogEl);
|
|
59503
59998
|
dialogEl.setAttribute("aria-expanded", "false");
|
|
59504
|
-
if (!
|
|
59999
|
+
if (!isTopLayer) {
|
|
59505
60000
|
openLocalDialogCount = Math.max(0, openLocalDialogCount - 1);
|
|
59506
60001
|
dialogEl.style.removeProperty("--dialog-stack-order");
|
|
59507
60002
|
}
|
|
59508
60003
|
// See openEffect's own identical comment for why this is needed
|
|
59509
|
-
//
|
|
60004
|
+
// whichever show call ran, not just when a stray authored display
|
|
59510
60005
|
// property is actually present — harmless the rest of the time.
|
|
59511
60006
|
dialogEl.setAttribute("navi-hidden", "");
|
|
59512
|
-
|
|
60007
|
+
if (isTopLayerPopover) {
|
|
60008
|
+
dialogEl.hidePopover();
|
|
60009
|
+
} else {
|
|
60010
|
+
dialogEl.close();
|
|
60011
|
+
}
|
|
59513
60012
|
// Held at the size it has right now, for the whole way out. cleanup()
|
|
59514
60013
|
// below already stops the JS repositioning, but the size is CSS-driven
|
|
59515
60014
|
// (--x-dialog-max-height, and `height` outright under expandY) and
|
|
@@ -59536,9 +60035,8 @@ const useDialogProps = props => {
|
|
|
59536
60035
|
};
|
|
59537
60036
|
const onKeyDownShortcuts = createOnKeyDownForShortcuts({
|
|
59538
60037
|
escape: e => {
|
|
59539
|
-
// Only
|
|
59540
|
-
//
|
|
59541
|
-
// non-modal .show()'d one doesn't.
|
|
60038
|
+
// Only a modal <dialog> fires "cancel" on Escape natively (handled via
|
|
60039
|
+
// onCancel below); neither a .show()n nor a showPopover()n one does.
|
|
59542
60040
|
if (isModal || !openController.opened) {
|
|
59543
60041
|
return null;
|
|
59544
60042
|
}
|
|
@@ -59557,10 +60055,10 @@ const useDialogProps = props => {
|
|
|
59557
60055
|
// most fields are shared: renderer-specific bits (the outside-click
|
|
59558
60056
|
// handler below, in particular) are just assigned onto whichever of the
|
|
59559
60057
|
// two actually owns that concern for a given renderer, instead of one
|
|
59560
|
-
// object's own field branching internally
|
|
59561
|
-
//
|
|
59562
|
-
//
|
|
59563
|
-
//
|
|
60058
|
+
// object's own field branching internally. backdropProps only gets returned
|
|
60059
|
+
// (see the bottom of this function) for the one dialog whose wall is a real
|
|
60060
|
+
// element of ours: a modal's is native (::backdrop), and backdrop={false}
|
|
60061
|
+
// has none at all.
|
|
59564
60062
|
Object.assign(backdropProps, {
|
|
59565
60063
|
"ref": backdropRef,
|
|
59566
60064
|
// Out of flow like the popup it belongs to — see the content element's own
|
|
@@ -59610,21 +60108,21 @@ const useDialogProps = props => {
|
|
|
59610
60108
|
// after measuring (see this file's top comment) — so there's no reason
|
|
59611
60109
|
// to withhold the attribute for the auto case the way Popover has to.
|
|
59612
60110
|
"navi-animation": resolvedAnimation,
|
|
59613
|
-
// Only meaningful for
|
|
59614
|
-
//
|
|
60111
|
+
// Only meaningful for a modal's own native ::backdrop (see this file's
|
|
60112
|
+
// CSS for the "capture" glass effect) — a
|
|
59615
60113
|
// pseudo-element can't carry its own attributes, so this has to live on
|
|
59616
60114
|
// the originating .navi_dialog element instead, same reasoning as
|
|
59617
|
-
// navi-animation above. Harmless for
|
|
59618
|
-
//
|
|
59619
|
-
//
|
|
60115
|
+
// navi-animation above. Harmless for a local dialog too (its own real
|
|
60116
|
+
// backdrop element already gets the same attribute via backdropProps
|
|
60117
|
+
// above, which is what its own CSS actually keys off).
|
|
59620
60118
|
"data-pointer-interaction-outside": pointerInteractionOutsideEffect,
|
|
59621
|
-
// Only load-bearing for
|
|
59622
|
-
//
|
|
59623
|
-
//
|
|
59624
|
-
//
|
|
60119
|
+
// Only load-bearing for a modal's own native ::backdrop, same "a
|
|
60120
|
+
// pseudo-element can't carry attributes" reasoning as the prop just above
|
|
60121
|
+
// (and harmless for a local dialog, whose real backdrop element gets it
|
|
60122
|
+
// via backdropProps).
|
|
59625
60123
|
"data-backdrop-variant": backdropVariant,
|
|
59626
60124
|
// Read by the native ::backdrop, which inherits them from here (see this
|
|
59627
|
-
// file's CSS) —
|
|
60125
|
+
// file's CSS) — a local dialog's own backdrop element gets them via
|
|
59628
60126
|
// backdropProps above.
|
|
59629
60127
|
backdropColor,
|
|
59630
60128
|
backdropFilter,
|
|
@@ -59653,6 +60151,12 @@ const useDialogProps = props => {
|
|
|
59653
60151
|
// absolutely placed by its own code — so it says so once, here (group.jsx
|
|
59654
60152
|
// reads this attribute to tell a member from anything else on its line).
|
|
59655
60153
|
"navi-out-of-flow": "",
|
|
60154
|
+
// The top layer without a wall: showPopover() is what puts it there, and
|
|
60155
|
+
// it only accepts an element that declares itself a popover. "manual" so
|
|
60156
|
+
// the browser's own light dismiss stays out of it — what an outside press
|
|
60157
|
+
// does is pointerInteractionOutsideEffect's answer, given by the
|
|
60158
|
+
// document-level listener in openEffect above.
|
|
60159
|
+
"popover": isTopLayerPopover ? "manual" : undefined,
|
|
59656
60160
|
"baseClassName": "navi_dialog",
|
|
59657
60161
|
"pseudoClasses": DIALOG_PSEUDO_CLASSES,
|
|
59658
60162
|
// Distinguishes the two renderers for the CSS above (position: fixed
|
|
@@ -59702,8 +60206,8 @@ const useDialogProps = props => {
|
|
|
59702
60206
|
},
|
|
59703
60207
|
"onCancel": e => {
|
|
59704
60208
|
// The dialog's own "cancel" (Escape on a modal showModal() dialog —
|
|
59705
|
-
// the
|
|
59706
|
-
// above) fires on the dialog element itself. But a child
|
|
60209
|
+
// the Escape handling for the other two shows lives in
|
|
60210
|
+
// onKeyDownShortcuts above) fires on the dialog element itself. But a child
|
|
59707
60211
|
// <input type="file"> also fires a BUBBLING "cancel" when the user
|
|
59708
60212
|
// dismisses the file chooser (per the HTML spec), and it lands here
|
|
59709
60213
|
// too; only the dialog's own cancel means "close".
|
|
@@ -59717,12 +60221,12 @@ const useDialogProps = props => {
|
|
|
59717
60221
|
children
|
|
59718
60222
|
});
|
|
59719
60223
|
|
|
59720
|
-
// Outside-click handling for
|
|
59721
|
-
//
|
|
59722
|
-
//
|
|
59723
|
-
//
|
|
59724
|
-
//
|
|
59725
|
-
if (!
|
|
60224
|
+
// Outside-click handling for the wall we render ourselves. A modal's is a
|
|
60225
|
+
// plain document-level listener instead, set up in openEffect above (see
|
|
60226
|
+
// this file's top comment for why: neither a real backdrop element nor
|
|
60227
|
+
// dialogEl's own mousedown reliably fires for a native ::backdrop click),
|
|
60228
|
+
// and so is a wall-less dialog's.
|
|
60229
|
+
if (!isTopLayer && backdrop) {
|
|
59726
60230
|
backdropProps.onMouseDown = mouseDownEvent => {
|
|
59727
60231
|
if (mouseDownEvent.button !== 0) {
|
|
59728
60232
|
return;
|
|
@@ -59732,7 +60236,7 @@ const useDialogProps = props => {
|
|
|
59732
60236
|
if (openedDuringThisPress(openController)) {
|
|
59733
60237
|
return;
|
|
59734
60238
|
}
|
|
59735
|
-
// See the
|
|
60239
|
+
// See the document-level listener in openEffect above: a click inside
|
|
59736
60240
|
// another popup is a click on what is in front, not an outside click.
|
|
59737
60241
|
const dialogEl = ref.current;
|
|
59738
60242
|
const popupUnderPointer = mouseDownEvent.target.closest?.(`[navi-control="dialog"], [navi-control="popover"]`);
|
|
@@ -59749,7 +60253,7 @@ const useDialogProps = props => {
|
|
|
59749
60253
|
// the same behavior for Dialog.
|
|
59750
60254
|
};
|
|
59751
60255
|
}
|
|
59752
|
-
return [
|
|
60256
|
+
return [!isTopLayer && backdrop ? backdropProps : null, contentProps];
|
|
59753
60257
|
};
|
|
59754
60258
|
const DIALOG_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
|
|
59755
60259
|
|
|
@@ -61468,22 +61972,18 @@ const css$C = /* css */`@layer navi {
|
|
|
61468
61972
|
* — without this, the exact same `<Popup>` usage would behave
|
|
61469
61973
|
* differently (close-on-outside-click or not) purely based on which mode
|
|
61470
61974
|
* the screen-size check happens to pick, which defeats the point of
|
|
61471
|
-
* having one shared API in the first place. Note this
|
|
61472
|
-
*
|
|
61473
|
-
*
|
|
61474
|
-
*
|
|
61475
|
-
* `dialog.jsx`'s own doc) — a popover's fully passive, click-through
|
|
61476
|
-
* backdrop has no dialog-mode equivalent. Whatever content `Popup` opens
|
|
61477
|
-
* is unavoidably *more* intrusive once it switches to dialog mode than
|
|
61478
|
-
* the exact same usage would be as a popover — worth keeping in mind for
|
|
61479
|
-
* anything that relies on `Popup` and can end up on a small screen.
|
|
61975
|
+
* having one shared API in the first place. Note this only says what a
|
|
61976
|
+
* press outside *does*; whether it reaches the page at all is `backdrop`
|
|
61977
|
+
* below, and `"none"`/`"capture"` describe a wall either way — the popup
|
|
61978
|
+
* absorbs the press without closing, dimmed or not.
|
|
61480
61979
|
* @param {boolean} [props.backdrop] - Whether anything is laid between the
|
|
61481
61980
|
* popup and the page at all: `false` lets a press outside both dismiss the
|
|
61482
|
-
* popup and reach whatever it landed on, in one gesture.
|
|
61483
|
-
*
|
|
61484
|
-
*
|
|
61485
|
-
*
|
|
61486
|
-
*
|
|
61981
|
+
* popup and reach whatever it landed on, in one gesture. Forwarded as-is and
|
|
61982
|
+
* honoured in either mode — a wall-less `Dialog` is shown through the
|
|
61983
|
+
* Popover API rather than `showModal()` — so which mode the screen-size
|
|
61984
|
+
* resolution picks says nothing about whether the page behind stays live.
|
|
61985
|
+
* It is also what makes a sheet docked to the bottom of a phone's screen
|
|
61986
|
+
* (`dockedOnSmallTouchScreen`) non-modal.
|
|
61487
61987
|
* @param {"auto"|"discrete"|"invisible"} [props.backdropVariant] - Forwarded
|
|
61488
61988
|
* as-is to whichever component renders (both understand it identically):
|
|
61489
61989
|
* how visible the backdrop is, independently of what an outside click
|
|
@@ -61554,9 +62054,6 @@ const Popup = props => {
|
|
|
61554
62054
|
// exact same <Popup> usage behaves identically regardless of which
|
|
61555
62055
|
// mode the automatic screen-size resolution happens to pick.
|
|
61556
62056
|
pointerInteractionOutsideEffect = "close",
|
|
61557
|
-
// Read here rather than left in ...rest so the dialog branch can drop it:
|
|
61558
|
-
// a top-layer dialog is modal and has no press left to let through, and
|
|
61559
|
-
// warns when asked (see Dialog's own backdrop prop).
|
|
61560
62057
|
backdrop,
|
|
61561
62058
|
// Popover-only (see this component's own doc) — destructured out so
|
|
61562
62059
|
// they're never part of ...rest, and therefore never forwarded to
|
|
@@ -61591,7 +62088,7 @@ const Popup = props => {
|
|
|
61591
62088
|
sizeFromAnchor: sizeFromAnchor,
|
|
61592
62089
|
maxWidth: maxWidth,
|
|
61593
62090
|
pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
|
|
61594
|
-
backdrop:
|
|
62091
|
+
backdrop: backdrop,
|
|
61595
62092
|
className: withPropsClassName("navi_popup", className),
|
|
61596
62093
|
expand: expand,
|
|
61597
62094
|
expandX: expandX,
|
|
@@ -83058,11 +83555,11 @@ const css = /* css */`.navi_side_panel {
|
|
|
83058
83555
|
* @param {boolean} [props.closeOnClickOutside=false] - `false` (default):
|
|
83059
83556
|
* maps to `pointerInteractionOutsideEffect="none"` — in popover mode, no
|
|
83060
83557
|
* backdrop at all, outside clicks pass straight through; in dialog mode,
|
|
83061
|
-
* the outside click is
|
|
83062
|
-
*
|
|
83063
|
-
*
|
|
83064
|
-
* panel on an outside click instead, and also enables trapping
|
|
83065
|
-
* navigation inside the panel (`focusCapture`) — closing on outside
|
|
83558
|
+
* the outside click is absorbed by the panel's own wall but changes
|
|
83559
|
+
* nothing. Pass `backdrop={false}` (forwarded to `Popup`) for a panel with
|
|
83560
|
+
* no wall in either mode, whose outside presses reach the page. `true`:
|
|
83561
|
+
* closes the panel on an outside click instead, and also enables trapping
|
|
83562
|
+
* Tab navigation inside the panel (`focusCapture`) — closing on outside
|
|
83066
83563
|
* interaction only makes sense paired with not letting focus silently
|
|
83067
83564
|
* leave the panel first.
|
|
83068
83565
|
* @param {boolean} [props.swipeToClose=true] - Pushing the panel back
|
|
@@ -83072,12 +83569,11 @@ const css = /* css */`.navi_side_panel {
|
|
|
83072
83569
|
* deliberately. See `swipe_to_close.js` for when the gesture is claimed.
|
|
83073
83570
|
* @param {"dialog"|"popover"} [props.mode] - Forwarded to `Popup` — forces
|
|
83074
83571
|
* one underlying renderer instead of its automatic screen-size
|
|
83075
|
-
* resolution. Note that if `Popup` ends up in dialog mode (small screen,
|
|
83076
|
-
*
|
|
83077
|
-
* `closeOnClickOutside`/`pointerInteractionOutsideEffect
|
|
83078
|
-
*
|
|
83079
|
-
*
|
|
83080
|
-
* popover's fully passive, click-through backdrop.
|
|
83572
|
+
* resolution. Note that if `Popup` ends up in dialog mode (small screen, or
|
|
83573
|
+
* forced here), the panel is modal unless it says `backdrop={false}`:
|
|
83574
|
+
* `closeOnClickOutside`/`pointerInteractionOutsideEffect` only say what a
|
|
83575
|
+
* press on the wall does, not whether there is one (see `dialog.jsx`'s own
|
|
83576
|
+
* doc).
|
|
83081
83577
|
* @param {import("ignore:preact").ComponentChildren} props.children - No built-in
|
|
83082
83578
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
83083
83579
|
* plain `<Button command="--navi-close">`), use `SidePanel.Head`'s own
|
|
@@ -83382,5 +83878,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
83382
83878
|
})
|
|
83383
83879
|
});
|
|
83384
83880
|
|
|
83385
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, ControlSwap, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, OfflineError, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRange, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, dispatchRequestSetUIState, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, getNowHours, getNowHoursRoundedToStep, isCellSelected, isColumnSelected, isOfflineError, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markAsOutsideTextFlow, markErrorAsDisplayedBy, moveArrayItemByIndex, moveFocusTo, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, preloadUrl, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setNetworkPolicy, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useNetworkPolicyReason, useOrderedColumns, usePickerState, usePopupMode, useRouteStatus, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useTransitionCover, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
83881
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, ControlSwap, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, OfflineError, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRange, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, dispatchRequestSetUIState, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, getNowHours, getNowHoursRoundedToStep, isCellSelected, isColumnSelected, isOfflineError, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markAsOutsideTextFlow, markErrorAsDisplayedBy, moveArrayItemByIndex, moveFocusTo, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, preloadUrl, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, routeFallback, scrollActivitySignal, setBaseUrl, setNetworkPolicy, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useNetworkPolicyReason, useOrderedColumns, usePickerState, usePopupMode, useRouteStatus, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useTransitionCover, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
83386
83882
|
//# sourceMappingURL=jsenv_navi.js.map
|