@jsenv/navi 0.29.300 → 0.29.302
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 +892 -226
- package/dist/dev/jsenv_navi.js.map +25 -20
- package/dist/jsenv_navi.js +826 -207
- package/dist/jsenv_navi.js.map +25 -20
- package/docs/dialog_shape.md +23 -8
- package/docs/interactions.md +100 -16
- package/docs/pan_zoom.md +2 -2
- package/docs/popup_backdrop.md +28 -7
- package/docs/popup_open.md +43 -7
- package/package.json +1 -1
package/dist/dev/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";
|
|
@@ -10376,6 +10376,11 @@ const readNumberFromDom = (element, attribute, defaultValue) => {
|
|
|
10376
10376
|
* happened. Registered through the same door an application uses (see
|
|
10377
10377
|
* interaction_registry.js) — navi has no private one.
|
|
10378
10378
|
*
|
|
10379
|
+
* `dblclick` is not among them, and deliberately: the browser only fires it for a
|
|
10380
|
+
* mouse, so an element declaring it would answer half the hands that reach it.
|
|
10381
|
+
* What a hand does twice is `double_click`, counted from the pointer in
|
|
10382
|
+
* interaction_press.js.
|
|
10383
|
+
*
|
|
10379
10384
|
* `contextmenu` is the only one that takes something away: the browser's own menu
|
|
10380
10385
|
* would cover the answer to the request it is. It takes it away AFTER, though —
|
|
10381
10386
|
* a native interaction IS its own event, the very one the gate reads, and a gate
|
|
@@ -10389,7 +10394,6 @@ const NATIVE_TYPE_SET = new Set([
|
|
|
10389
10394
|
"mousedown",
|
|
10390
10395
|
"mouseup",
|
|
10391
10396
|
"click",
|
|
10392
|
-
"dblclick",
|
|
10393
10397
|
"contextmenu",
|
|
10394
10398
|
]);
|
|
10395
10399
|
|
|
@@ -10447,6 +10451,15 @@ const SWIPE_THRESHOLD_DEFAULT = 0.33;
|
|
|
10447
10451
|
const LONGPRESS_DELAY_DEFAULT = 450;
|
|
10448
10452
|
// Past this the finger is going somewhere: it swipes, it does not hold.
|
|
10449
10453
|
const LONGPRESS_SLOP_DEFAULT = 8;
|
|
10454
|
+
// The window a double click is counted in, opened by the FIRST press. Under the
|
|
10455
|
+
// hold's delay on purpose: a pause longer than the wait navi calls "held" is
|
|
10456
|
+
// longer than one gesture.
|
|
10457
|
+
const DOUBLE_CLICK_DELAY_DEFAULT = 400;
|
|
10458
|
+
// How far apart the two presses may be, and how far either of them may travel
|
|
10459
|
+
// before it is a gesture rather than a tap. A fingertip and not a pixel: between
|
|
10460
|
+
// the two the finger leaves the glass and lands again where it means to, which is
|
|
10461
|
+
// a wider question than the hold's slop above (has this finger stood still?).
|
|
10462
|
+
const DOUBLE_CLICK_SLOP_DEFAULT = 30;
|
|
10450
10463
|
// How long the element takes to reach where the gesture leaves it, or to come
|
|
10451
10464
|
// back. Written into the CSS below from here: the state is cleaned up when the
|
|
10452
10465
|
// movement is over, so a duration living only in the stylesheet would be a timing
|
|
@@ -10458,11 +10471,19 @@ const SETTLE_DURATION_MS = 200;
|
|
|
10458
10471
|
const SWIPE_THRESHOLD_ATTRIBUTE = "data-swipe-threshold";
|
|
10459
10472
|
const LONGPRESS_DELAY_ATTRIBUTE = "data-longpress-delay";
|
|
10460
10473
|
const LONGPRESS_SLOP_ATTRIBUTE = "data-longpress-slop";
|
|
10474
|
+
const DOUBLE_CLICK_DELAY_ATTRIBUTE = "data-double-click-delay";
|
|
10475
|
+
const DOUBLE_CLICK_SLOP_ATTRIBUTE = "data-double-click-slop";
|
|
10461
10476
|
|
|
10462
|
-
// Which axes this element takes a swipe on, and that it takes a hold
|
|
10463
|
-
// DOM at render time, for the CSS below and for the boxes above
|
|
10477
|
+
// Which axes this element takes a swipe on, and that it takes a hold or counts
|
|
10478
|
+
// taps: said in the DOM at render time, for the CSS below and for the boxes above
|
|
10479
|
+
// to read.
|
|
10464
10480
|
const SWIPE_AXES_ATTRIBUTE = "data-swipe";
|
|
10465
10481
|
const LONGPRESS_ATTRIBUTE = "data-longpress";
|
|
10482
|
+
const DOUBLE_CLICK_ATTRIBUTE = "data-double-click";
|
|
10483
|
+
|
|
10484
|
+
const LONGPRESS = "longpress";
|
|
10485
|
+
const DOUBLE_CLICK = "double_click";
|
|
10486
|
+
const SINGLE_CLICK = "single_click";
|
|
10466
10487
|
|
|
10467
10488
|
import.meta.css = /* css */ [`@property --swipe-progress {
|
|
10468
10489
|
syntax: "<number>";
|
|
@@ -10488,15 +10509,19 @@ import.meta.css = /* css */ [`@property --swipe-progress {
|
|
|
10488
10509
|
touch-action: none;
|
|
10489
10510
|
}
|
|
10490
10511
|
|
|
10512
|
+
:where([data-double-click]) {
|
|
10513
|
+
touch-action: manipulation;
|
|
10514
|
+
}
|
|
10515
|
+
|
|
10491
10516
|
[data-longpress] {
|
|
10492
10517
|
-webkit-touch-callout: none;
|
|
10493
10518
|
}
|
|
10494
10519
|
|
|
10495
|
-
[data-longpress], [data-swipe] {
|
|
10520
|
+
[data-longpress], [data-swipe], [data-double-click] {
|
|
10496
10521
|
user-select: none;
|
|
10497
10522
|
}
|
|
10498
10523
|
|
|
10499
|
-
: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"]) {
|
|
10524
|
+
: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"]) {
|
|
10500
10525
|
user-select: text;
|
|
10501
10526
|
}
|
|
10502
10527
|
|
|
@@ -10515,9 +10540,13 @@ import.meta.css = /* css */ [`@property --swipe-progress {
|
|
|
10515
10540
|
|
|
10516
10541
|
defineInteractionDetector({
|
|
10517
10542
|
name: "press",
|
|
10518
|
-
claims: (type) =>
|
|
10519
|
-
|
|
10520
|
-
|
|
10543
|
+
claims: (type) =>
|
|
10544
|
+
type in AXIS_BY_SWIPE_TYPE ||
|
|
10545
|
+
type === LONGPRESS ||
|
|
10546
|
+
type === DOUBLE_CLICK ||
|
|
10547
|
+
type === SINGLE_CLICK,
|
|
10548
|
+
// Every one of them is "what this press turns out to be": until it turns out,
|
|
10549
|
+
// the press is theirs.
|
|
10521
10550
|
disputesPress: true,
|
|
10522
10551
|
setup: (element, trigger, { types, readConfig }) => {
|
|
10523
10552
|
let axes = "";
|
|
@@ -10527,7 +10556,15 @@ defineInteractionDetector({
|
|
|
10527
10556
|
axes += axis;
|
|
10528
10557
|
}
|
|
10529
10558
|
}
|
|
10530
|
-
const hasLongPress = types.includes(
|
|
10559
|
+
const hasLongPress = types.includes(LONGPRESS);
|
|
10560
|
+
const hasDoubleClick = types.includes(DOUBLE_CLICK);
|
|
10561
|
+
const hasSingleClick = types.includes(SINGLE_CLICK);
|
|
10562
|
+
const countsTaps = hasDoubleClick || hasSingleClick;
|
|
10563
|
+
if (hasSingleClick && !hasDoubleClick) {
|
|
10564
|
+
console.warn(
|
|
10565
|
+
`interactions: "${SINGLE_CLICK}" waits for a "${DOUBLE_CLICK}" that is not declared here, so all it does is answer a click late. Declare "${DOUBLE_CLICK}" beside it, or use "click".`,
|
|
10566
|
+
);
|
|
10567
|
+
}
|
|
10531
10568
|
|
|
10532
10569
|
const undo = [];
|
|
10533
10570
|
const mark = (attribute, value) => {
|
|
@@ -10580,6 +10617,79 @@ defineInteractionDetector({
|
|
|
10580
10617
|
if (hasLongPress) {
|
|
10581
10618
|
mark(LONGPRESS_ATTRIBUTE, "");
|
|
10582
10619
|
}
|
|
10620
|
+
if (countsTaps) {
|
|
10621
|
+
mark(DOUBLE_CLICK_ATTRIBUTE, "");
|
|
10622
|
+
}
|
|
10623
|
+
|
|
10624
|
+
// The tap a next press may pair with, and the window it is waited for in. It
|
|
10625
|
+
// outlives the press that made it — which is what a double click IS — so it
|
|
10626
|
+
// lives here rather than in the pointerdown below.
|
|
10627
|
+
let firstTap = null;
|
|
10628
|
+
let tapWindowTimeout = null;
|
|
10629
|
+
let tapWait = null;
|
|
10630
|
+
const forgetTaps = () => {
|
|
10631
|
+
clearTimeout(tapWindowTimeout);
|
|
10632
|
+
tapWindowTimeout = null;
|
|
10633
|
+
firstTap = null;
|
|
10634
|
+
tapWait?.cancel();
|
|
10635
|
+
tapWait = null;
|
|
10636
|
+
};
|
|
10637
|
+
undo.push(forgetTaps);
|
|
10638
|
+
// The window closes on a tap nothing came back for. Counted from the press
|
|
10639
|
+
// that opened it rather than from the tap that ends it, so what the caller
|
|
10640
|
+
// tunes is one rhythm and not a press plus a pause — a press slow enough to be
|
|
10641
|
+
// a hold therefore closes its own window on the spot, and cannot start a
|
|
10642
|
+
// double click.
|
|
10643
|
+
const openTapWindow = (pressEvent, tapEvent, delay) => {
|
|
10644
|
+
firstTap = {
|
|
10645
|
+
at: pressEvent.timeStamp,
|
|
10646
|
+
x: tapEvent.clientX,
|
|
10647
|
+
y: tapEvent.clientY,
|
|
10648
|
+
pointerType: tapEvent.pointerType,
|
|
10649
|
+
};
|
|
10650
|
+
if (hasSingleClick) {
|
|
10651
|
+
// The element's click is navi's from here: it is held for the length of
|
|
10652
|
+
// the window and `single_click` is what says it happened (see the top of
|
|
10653
|
+
// this file).
|
|
10654
|
+
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
10655
|
+
clickSuppressionIsOver();
|
|
10656
|
+
}
|
|
10657
|
+
clearTimeout(tapWindowTimeout);
|
|
10658
|
+
tapWindowTimeout = setTimeout(
|
|
10659
|
+
() => {
|
|
10660
|
+
tapWindowTimeout = null;
|
|
10661
|
+
firstTap = null;
|
|
10662
|
+
if (hasSingleClick) {
|
|
10663
|
+
trigger(SINGLE_CLICK, tapEvent, {
|
|
10664
|
+
pointerType: tapEvent.pointerType,
|
|
10665
|
+
});
|
|
10666
|
+
}
|
|
10667
|
+
},
|
|
10668
|
+
delay - (tapEvent.timeStamp - pressEvent.timeStamp),
|
|
10669
|
+
);
|
|
10670
|
+
};
|
|
10671
|
+
|
|
10672
|
+
if (hasSingleClick) {
|
|
10673
|
+
// A click no press made — a keyboard activation, an `element.click()`. The
|
|
10674
|
+
// window exists to find out whether a second press is coming, and there is
|
|
10675
|
+
// no press here, so it is not held: said at once, the way the browser's own
|
|
10676
|
+
// click would have been.
|
|
10677
|
+
const onClick = (clickEvent) => {
|
|
10678
|
+
if (isPressDrivenClick(clickEvent)) {
|
|
10679
|
+
// A press's own click, swallowed by the suppression armed at its tap:
|
|
10680
|
+
// it never reaches here, and if it does (the suppression having already
|
|
10681
|
+
// been spent) the tap has said it or is about to.
|
|
10682
|
+
return;
|
|
10683
|
+
}
|
|
10684
|
+
trigger(SINGLE_CLICK, clickEvent, {
|
|
10685
|
+
pointerType: clickEvent.pointerType,
|
|
10686
|
+
});
|
|
10687
|
+
};
|
|
10688
|
+
element.addEventListener("click", onClick);
|
|
10689
|
+
undo.push(() => {
|
|
10690
|
+
element.removeEventListener("click", onClick);
|
|
10691
|
+
});
|
|
10692
|
+
}
|
|
10583
10693
|
|
|
10584
10694
|
const onPointerDown = (pointerDownEvent) => {
|
|
10585
10695
|
if (pointerDownEvent.button !== 0) {
|
|
@@ -10587,6 +10697,52 @@ defineInteractionDetector({
|
|
|
10587
10697
|
}
|
|
10588
10698
|
let swipe = null;
|
|
10589
10699
|
let press = null;
|
|
10700
|
+
// Set below, with the hold; a no-op until then so the swipe can call it.
|
|
10701
|
+
let forgetInnerLongPress = () => {};
|
|
10702
|
+
|
|
10703
|
+
if (countsTaps) {
|
|
10704
|
+
const delay = readConfig(
|
|
10705
|
+
DOUBLE_CLICK_DELAY_ATTRIBUTE,
|
|
10706
|
+
DOUBLE_CLICK_DELAY_DEFAULT,
|
|
10707
|
+
);
|
|
10708
|
+
const slop = readConfig(
|
|
10709
|
+
DOUBLE_CLICK_SLOP_ATTRIBUTE,
|
|
10710
|
+
DOUBLE_CLICK_SLOP_DEFAULT,
|
|
10711
|
+
);
|
|
10712
|
+
// Whether this press is the second of a pair is settled HERE, when it
|
|
10713
|
+
// lands, and not when it is let go of: the window is about where the
|
|
10714
|
+
// hand went and how soon, and a second press held a little longer than
|
|
10715
|
+
// the window would otherwise be answered as a lone click while it is
|
|
10716
|
+
// still down.
|
|
10717
|
+
const completesTheFirstTap =
|
|
10718
|
+
firstTap && continuesTap(firstTap, pointerDownEvent, delay, slop);
|
|
10719
|
+
if (completesTheFirstTap) {
|
|
10720
|
+
clearTimeout(tapWindowTimeout);
|
|
10721
|
+
tapWindowTimeout = null;
|
|
10722
|
+
} else {
|
|
10723
|
+
forgetTaps();
|
|
10724
|
+
}
|
|
10725
|
+
tapWait = waitForTap(pointerDownEvent, {
|
|
10726
|
+
slop,
|
|
10727
|
+
onTap: (tapEvent) => {
|
|
10728
|
+
tapWait = null;
|
|
10729
|
+
if (completesTheFirstTap) {
|
|
10730
|
+
firstTap = null;
|
|
10731
|
+
// The click this second press leaves behind was already answered,
|
|
10732
|
+
// by the gesture the two of them made. Armed and released at once:
|
|
10733
|
+
// the release does not lift the suppression, it says the gesture is
|
|
10734
|
+
// over (see suppressClickAfterGesture).
|
|
10735
|
+
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
10736
|
+
clickSuppressionIsOver();
|
|
10737
|
+
trigger(DOUBLE_CLICK, tapEvent, {
|
|
10738
|
+
pointerType: tapEvent.pointerType,
|
|
10739
|
+
});
|
|
10740
|
+
return;
|
|
10741
|
+
}
|
|
10742
|
+
openTapWindow(pointerDownEvent, tapEvent, delay);
|
|
10743
|
+
},
|
|
10744
|
+
});
|
|
10745
|
+
}
|
|
10590
10746
|
|
|
10591
10747
|
if (axes) {
|
|
10592
10748
|
swipe = startSwipe(pointerDownEvent, {
|
|
@@ -10606,18 +10762,46 @@ defineInteractionDetector({
|
|
|
10606
10762
|
onSwipeStart: () => {
|
|
10607
10763
|
press?.cancel();
|
|
10608
10764
|
press = null;
|
|
10765
|
+
forgetInnerLongPress();
|
|
10766
|
+
// …and it is not a tap either, nor the second of a pair: the press
|
|
10767
|
+
// has left, and what it left is a swipe.
|
|
10768
|
+
forgetTaps();
|
|
10609
10769
|
},
|
|
10610
10770
|
});
|
|
10611
10771
|
}
|
|
10612
10772
|
if (hasLongPress) {
|
|
10773
|
+
// A hold declared INSIDE this element, on this same press: the nearer
|
|
10774
|
+
// one answers, the way a click is the innermost target's, and this
|
|
10775
|
+
// wait is given up — two sheets opening from one hold is nobody's
|
|
10776
|
+
// intention. Delays being equal, the inner timer was set first (the
|
|
10777
|
+
// press reached it first) and fires first; the inner hold arrives here
|
|
10778
|
+
// as its own event, bubbling, before this timer runs.
|
|
10779
|
+
const onInnerLongPress = (longPressEvent) => {
|
|
10780
|
+
if (longPressEvent.target === element || !press) {
|
|
10781
|
+
return;
|
|
10782
|
+
}
|
|
10783
|
+
press.cancel();
|
|
10784
|
+
press = null;
|
|
10785
|
+
forgetInnerLongPress();
|
|
10786
|
+
warnOnceAboutHoldTakenByInner(element, longPressEvent.target);
|
|
10787
|
+
};
|
|
10788
|
+
forgetInnerLongPress = () => {
|
|
10789
|
+
element.removeEventListener("longpress", onInnerLongPress);
|
|
10790
|
+
};
|
|
10791
|
+
element.addEventListener("longpress", onInnerLongPress);
|
|
10613
10792
|
press = waitForPressHeld(pointerDownEvent, {
|
|
10614
10793
|
delay: readConfig(LONGPRESS_DELAY_ATTRIBUTE, LONGPRESS_DELAY_DEFAULT),
|
|
10615
10794
|
slop: readConfig(LONGPRESS_SLOP_ATTRIBUTE, LONGPRESS_SLOP_DEFAULT),
|
|
10795
|
+
onPressCancel: forgetInnerLongPress,
|
|
10616
10796
|
onPressHeld: (pressEvent, { endPress }) => {
|
|
10797
|
+
forgetInnerLongPress();
|
|
10617
10798
|
// The hold won the arbitration: the swipe never got the distance it
|
|
10618
|
-
// needed, and must not get it from whatever the finger does next.
|
|
10799
|
+
// needed, and must not get it from whatever the finger does next. The
|
|
10800
|
+
// double click loses the same way — a press held this long is the
|
|
10801
|
+
// hold's whether it is the first of a pair or the second.
|
|
10619
10802
|
swipe?.stop();
|
|
10620
10803
|
swipe = null;
|
|
10804
|
+
forgetTaps();
|
|
10621
10805
|
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
10622
10806
|
const onPointerEnd = () => {
|
|
10623
10807
|
window.removeEventListener("pointerup", onPointerEnd, true);
|
|
@@ -10647,6 +10831,24 @@ defineInteractionDetector({
|
|
|
10647
10831
|
},
|
|
10648
10832
|
});
|
|
10649
10833
|
|
|
10834
|
+
// Said once per element, in dev: the hold declared here can never fire while
|
|
10835
|
+
// the one inside it is declared, and nothing else would tell — the outer one
|
|
10836
|
+
// simply never happens. Two holds on the same pixels are usually one hold too
|
|
10837
|
+
// many (see interactions.md); the fix is on the caller's side, so it has to be
|
|
10838
|
+
// named there.
|
|
10839
|
+
const holdTakenWarnedSet = new WeakSet();
|
|
10840
|
+
const warnOnceAboutHoldTakenByInner = (element, innerElement) => {
|
|
10841
|
+
if (holdTakenWarnedSet.has(element)) {
|
|
10842
|
+
return;
|
|
10843
|
+
}
|
|
10844
|
+
holdTakenWarnedSet.add(element);
|
|
10845
|
+
console.warn(
|
|
10846
|
+
`interactions: the "longpress" declared on this element was taken by the "longpress" declared inside it — the nearer hold wins, and this one never fires while the inner one is declared. One hold, one meaning: give the two things two gestures (a hold and a click, say), or declare the hold on one element only.`,
|
|
10847
|
+
element,
|
|
10848
|
+
innerElement,
|
|
10849
|
+
);
|
|
10850
|
+
};
|
|
10851
|
+
|
|
10650
10852
|
const startSwipe = (
|
|
10651
10853
|
pointerDownEvent,
|
|
10652
10854
|
{ element, axes, types, trigger, threshold, onSwipeStart },
|
|
@@ -10747,6 +10949,21 @@ const startSwipe = (
|
|
|
10747
10949
|
});
|
|
10748
10950
|
};
|
|
10749
10951
|
|
|
10952
|
+
// Whether a press landing now completes the tap before it: the same hand, soon
|
|
10953
|
+
// enough, and near enough. Axis by axis rather than as a distance, the way every
|
|
10954
|
+
// other slop in this family is read.
|
|
10955
|
+
const continuesTap = (tap, pointerDownEvent, delay, slop) => {
|
|
10956
|
+
if (pointerDownEvent.pointerType !== tap.pointerType) {
|
|
10957
|
+
return false;
|
|
10958
|
+
}
|
|
10959
|
+
if (pointerDownEvent.timeStamp - tap.at >= delay) {
|
|
10960
|
+
return false;
|
|
10961
|
+
}
|
|
10962
|
+
const xApart = Math.abs(pointerDownEvent.clientX - tap.x);
|
|
10963
|
+
const yApart = Math.abs(pointerDownEvent.clientY - tap.y);
|
|
10964
|
+
return xApart < slop && yApart < slop;
|
|
10965
|
+
};
|
|
10966
|
+
|
|
10750
10967
|
const swipeTypeOf = (axis, pulled) => {
|
|
10751
10968
|
const { positive, negative } = SWIPE_TYPE_BY_AXIS[axis];
|
|
10752
10969
|
return pulled > 0 ? positive : negative;
|
|
@@ -26634,6 +26851,31 @@ const getHrefTargetInfo = (href) => {
|
|
|
26634
26851
|
};
|
|
26635
26852
|
};
|
|
26636
26853
|
|
|
26854
|
+
/*
|
|
26855
|
+
* An app can ship more than one document — an admin panel, a status page, an
|
|
26856
|
+
* entry point of its own — and a link to one of them is not a route, it is a
|
|
26857
|
+
* page load. The address does not say which of the two it is, and guessing
|
|
26858
|
+
* would be worse than not knowing: "no route matches, so it must be another
|
|
26859
|
+
* document" turns a typo'd in-app url into a silent reload of the app instead
|
|
26860
|
+
* of its own not-found screen. So the link says it.
|
|
26861
|
+
*
|
|
26862
|
+
* `<Link document>` / `<Button document>` is that, and the point of saying it
|
|
26863
|
+
* on a link rather than navigating from an action is everything a link is: an
|
|
26864
|
+
* address in the status bar, a middle click, "open in new tab", something for
|
|
26865
|
+
* assistive technology to read. Only the interception steps aside; the element
|
|
26866
|
+
* stays a real link and the browser loads the page.
|
|
26867
|
+
*
|
|
26868
|
+
* It travels as an attribute for the same reason `replace` does (see
|
|
26869
|
+
* link_replace.js): whoever answers the press sees the element, not the
|
|
26870
|
+
* component that rendered it.
|
|
26871
|
+
*/
|
|
26872
|
+
|
|
26873
|
+
const LINK_DOCUMENT_ATTRIBUTE = "data-navi-document";
|
|
26874
|
+
|
|
26875
|
+
const linkAsksForDocument = (linkElement) => {
|
|
26876
|
+
return linkElement.hasAttribute(LINK_DOCUMENT_ATTRIBUTE);
|
|
26877
|
+
};
|
|
26878
|
+
|
|
26637
26879
|
/*
|
|
26638
26880
|
* A press aims at a place; it does not always go one step deeper. A row of tabs
|
|
26639
26881
|
* is a lateral move — the neighbour is one finger away — so the whole row should
|
|
@@ -26967,6 +27209,12 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
26967
27209
|
}
|
|
26968
27210
|
return;
|
|
26969
27211
|
}
|
|
27212
|
+
if (linkAsksForDocument(linkElement)) {
|
|
27213
|
+
// The link says its address is another document of this origin (see
|
|
27214
|
+
// link_document.js). Routing to it would land on the fallback route
|
|
27215
|
+
// instead of loading the page.
|
|
27216
|
+
return;
|
|
27217
|
+
}
|
|
26970
27218
|
// Nothing here declared a route, so there is nothing to route to: the
|
|
26971
27219
|
// page is a plain document and a link in it is a plain link. Taking it
|
|
26972
27220
|
// over anyway would push the url and then have nothing to show for it —
|
|
@@ -27554,6 +27802,160 @@ const Head = ({
|
|
|
27554
27802
|
return null;
|
|
27555
27803
|
};
|
|
27556
27804
|
|
|
27805
|
+
/**
|
|
27806
|
+
* A page is `{ route, params }`, never the route alone: a section of a page is
|
|
27807
|
+
* as often a PARAM as it is a route of its own, and three branches of the same
|
|
27808
|
+
* route told apart by their params are three pages one walks between.
|
|
27809
|
+
* `params` is undefined for a page that is a route on its own.
|
|
27810
|
+
*
|
|
27811
|
+
* Whether such a page is the one on screen is asked from three places — the
|
|
27812
|
+
* row a <RouteTravel> walks, the relations a movement is written between, and
|
|
27813
|
+
* the fallback naming the pages it is the absence of — and they must all get
|
|
27814
|
+
* the same answer, which is why the reading lives here rather than next to any
|
|
27815
|
+
* one of them.
|
|
27816
|
+
*/
|
|
27817
|
+
|
|
27818
|
+
// `matchesParams` reads paramsSignal, so a caller reading this during a render
|
|
27819
|
+
// is subscribed to the param changes that walk from one tab to the next —
|
|
27820
|
+
// matchingSignal alone never moves there, and a row whose tabs are params of
|
|
27821
|
+
// one route would never re-render.
|
|
27822
|
+
//
|
|
27823
|
+
// The params are read only for a route that matches, and that is not a signal
|
|
27824
|
+
// left unread: a reader wakes on anything it read last time, so what matters is
|
|
27825
|
+
// that everything able to make this answer change is among them.
|
|
27826
|
+
// matchingSignal is read whatever happens, and it is a NECESSARY condition —
|
|
27827
|
+
// while it is false no param of that route can put this page on screen, and the
|
|
27828
|
+
// day one could, matchingSignal itself has to turn true to say so, which is the
|
|
27829
|
+
// read that brings the params back in. (Asking anyway would be worse than
|
|
27830
|
+
// useless: the params of a route that does not match are not params.)
|
|
27831
|
+
const pageIsCurrent = ({ route, params }) => {
|
|
27832
|
+
if (!route.matchingSignal.value) {
|
|
27833
|
+
return false;
|
|
27834
|
+
}
|
|
27835
|
+
return params ? route.matchesParams(params) : true;
|
|
27836
|
+
};
|
|
27837
|
+
|
|
27838
|
+
/**
|
|
27839
|
+
* The page a <Route> container shows when none of its branches matched, given
|
|
27840
|
+
* a name.
|
|
27841
|
+
*
|
|
27842
|
+
* Every other branch of a container is a route, and a route is a thing one can
|
|
27843
|
+
* talk ABOUT from outside the tree: write a movement between two of them (see
|
|
27844
|
+
* route_transition.jsx), put one in a row (see route_travel.jsx), read whether
|
|
27845
|
+
* it is on screen. The fallback branch is the one page defined by an ABSENCE —
|
|
27846
|
+
* "an address none of these claim" — so it has no url pattern to be declared
|
|
27847
|
+
* from, and until it is named, nothing can be said about it.
|
|
27848
|
+
*
|
|
27849
|
+
* const NOT_FOUND_PAGE = routeFallback();
|
|
27850
|
+
* <Route fallback={NOT_FOUND_PAGE} element={NotFoundPage} />
|
|
27851
|
+
* defineRouteTransition(null, NOT_FOUND_PAGE, "slide-x");
|
|
27852
|
+
*
|
|
27853
|
+
* The absence is of a PARTICULAR set of claims — the branches of the container
|
|
27854
|
+
* holding it — and that set is not knowable from the routes of the application:
|
|
27855
|
+
* a route matching every address is a normal thing to declare (a carrier for
|
|
27856
|
+
* search params and route actions the whole app shares), and it would answer
|
|
27857
|
+
* "something matched" everywhere. So the container fills it in from the
|
|
27858
|
+
* children it was written with (see collectBranches in route.jsx), and a
|
|
27859
|
+
* fallback no container holds claims nothing.
|
|
27860
|
+
*
|
|
27861
|
+
* That leaves what a container cannot see: the address its own subtree lives
|
|
27862
|
+
* under. A section owning its sub-router renders it several components below
|
|
27863
|
+
* the leaf that matched the section's prefix, and "none of these five matched"
|
|
27864
|
+
* is then true on every screen of the application. Such a fallback names the
|
|
27865
|
+
* route it is under, and answers only there:
|
|
27866
|
+
*
|
|
27867
|
+
* const DASHBOARD_NOT_FOUND_PAGE = routeFallback(DASHBOARD_SECTION_ROUTE);
|
|
27868
|
+
*/
|
|
27869
|
+
|
|
27870
|
+
|
|
27871
|
+
const routeFallbackPrivatePropertiesMap = new WeakMap();
|
|
27872
|
+
|
|
27873
|
+
/**
|
|
27874
|
+
* Names the fallback branch of a <Route> container, so relations can be
|
|
27875
|
+
* written about the page it shows and its `matchingSignal` read from anywhere.
|
|
27876
|
+
*
|
|
27877
|
+
* @param {object} [scopeRoute] - the route this container's subtree lives
|
|
27878
|
+
* under, when the container is not the application's own router. Without it
|
|
27879
|
+
* the fallback answers wherever its container is on screen.
|
|
27880
|
+
* @returns {object} pass it as `<Route fallback={...}>`.
|
|
27881
|
+
*/
|
|
27882
|
+
const routeFallback = (scopeRoute) => {
|
|
27883
|
+
const pagesSignal = signal(null);
|
|
27884
|
+
const nothingMatches = () => {
|
|
27885
|
+
const pages = pagesSignal.value;
|
|
27886
|
+
if (!pages) {
|
|
27887
|
+
return false;
|
|
27888
|
+
}
|
|
27889
|
+
for (const page of pages) {
|
|
27890
|
+
if (pageIsCurrent(page)) {
|
|
27891
|
+
return false;
|
|
27892
|
+
}
|
|
27893
|
+
}
|
|
27894
|
+
return true;
|
|
27895
|
+
};
|
|
27896
|
+
const routeFallback = {
|
|
27897
|
+
isRouteFallback: true,
|
|
27898
|
+
scopeRoute,
|
|
27899
|
+
matchingSignal: scopeRoute
|
|
27900
|
+
? computed(() => {
|
|
27901
|
+
if (!scopeRoute.matchingSignal.value) {
|
|
27902
|
+
return false;
|
|
27903
|
+
}
|
|
27904
|
+
return nothingMatches();
|
|
27905
|
+
})
|
|
27906
|
+
: computed(nothingMatches),
|
|
27907
|
+
toString: () => {
|
|
27908
|
+
if (scopeRoute) {
|
|
27909
|
+
return `route fallback under ${scopeRoute}`;
|
|
27910
|
+
}
|
|
27911
|
+
return `route fallback`;
|
|
27912
|
+
},
|
|
27913
|
+
};
|
|
27914
|
+
Object.preventExtensions(routeFallback);
|
|
27915
|
+
routeFallbackPrivatePropertiesMap.set(routeFallback, { pagesSignal });
|
|
27916
|
+
return routeFallback;
|
|
27917
|
+
};
|
|
27918
|
+
|
|
27919
|
+
/**
|
|
27920
|
+
* The pages the fallback is the absence of, handed over by the container
|
|
27921
|
+
* holding it. Read as a signal rather than at render: a movement is decided
|
|
27922
|
+
* while rendering is held, before the container has had a chance to choose a
|
|
27923
|
+
* branch (see rendering_hold.js), so "we are on the fallback" has to be
|
|
27924
|
+
* answerable from the route signals alone.
|
|
27925
|
+
*/
|
|
27926
|
+
const wireRouteFallback = (routeFallback, pages) => {
|
|
27927
|
+
const { pagesSignal } = routeFallbackPrivatePropertiesMap.get(routeFallback);
|
|
27928
|
+
const pagesCurrent = pagesSignal.peek();
|
|
27929
|
+
if (pagesCurrent && samePages(pagesCurrent, pages)) {
|
|
27930
|
+
return;
|
|
27931
|
+
}
|
|
27932
|
+
pagesSignal.value = pages;
|
|
27933
|
+
};
|
|
27934
|
+
|
|
27935
|
+
const unwireRouteFallback = (routeFallback) => {
|
|
27936
|
+
const { pagesSignal } = routeFallbackPrivatePropertiesMap.get(routeFallback);
|
|
27937
|
+
pagesSignal.value = null;
|
|
27938
|
+
};
|
|
27939
|
+
|
|
27940
|
+
const samePages = (pages, otherPages) => {
|
|
27941
|
+
if (pages.length !== otherPages.length) {
|
|
27942
|
+
return false;
|
|
27943
|
+
}
|
|
27944
|
+
let i = 0;
|
|
27945
|
+
while (i < pages.length) {
|
|
27946
|
+
const page = pages[i];
|
|
27947
|
+
const otherPage = otherPages[i];
|
|
27948
|
+
if (page.route !== otherPage.route) {
|
|
27949
|
+
return false;
|
|
27950
|
+
}
|
|
27951
|
+
if (!compareTwoJsValues(page.params, otherPage.params)) {
|
|
27952
|
+
return false;
|
|
27953
|
+
}
|
|
27954
|
+
i++;
|
|
27955
|
+
}
|
|
27956
|
+
return true;
|
|
27957
|
+
};
|
|
27958
|
+
|
|
27557
27959
|
/**
|
|
27558
27960
|
* Route is the single primitive for URL-based rendering.
|
|
27559
27961
|
*
|
|
@@ -27572,10 +27974,15 @@ const Head = ({
|
|
|
27572
27974
|
* <Route element={AuthLayout}>
|
|
27573
27975
|
* <Route route={PROFILE_ROUTE} element={ProfilePage} />
|
|
27574
27976
|
* <Route route={SETTINGS_ROUTE} element={SettingsPage} />
|
|
27575
|
-
* <Route fallback element={AuthNotFoundPage} />
|
|
27576
27977
|
* </Route>
|
|
27577
27978
|
* ```
|
|
27578
27979
|
*
|
|
27980
|
+
* Such a container holds no fallback, and cannot: what makes this one the right
|
|
27981
|
+
* layout is a child matching, and having no prefix is the very reason it was
|
|
27982
|
+
* reached for — so there is no address that belongs to it and to none of its
|
|
27983
|
+
* children. An address nothing claims is the router's to answer; write the
|
|
27984
|
+
* fallback there.
|
|
27985
|
+
*
|
|
27579
27986
|
* ## Self-contained section pattern
|
|
27580
27987
|
* Use this when routes share a common URL prefix (e.g. `/dashboard/`).
|
|
27581
27988
|
* A single leaf Route in the top-level router matches the prefix; the component
|
|
@@ -27656,7 +28063,10 @@ const debug$1 = (...args) => {
|
|
|
27656
28063
|
* @param {object} props
|
|
27657
28064
|
* @param {object} [props.route] - the route this branch is for, from `route()`
|
|
27658
28065
|
* @param {object} [props.routeParams] - selects a branch on a param of that route
|
|
27659
|
-
* @param {boolean} [props.fallback] - the branch taken when no sibling
|
|
28066
|
+
* @param {boolean|object} [props.fallback] - the branch taken when no sibling
|
|
28067
|
+
* matches. `routeFallback()` gives that page a name, which is what lets a
|
|
28068
|
+
* movement be written about it and its `matchingSignal` be read from outside
|
|
28069
|
+
* the tree (see route_fallback.js).
|
|
27660
28070
|
* @param {Function|import("ignore:preact").VNode} [props.element] - what the branch renders
|
|
27661
28071
|
* @param {object} [props.elementProps] - props given to `element`
|
|
27662
28072
|
*
|
|
@@ -27734,7 +28144,7 @@ const collectRoutePages = children => {
|
|
|
27734
28144
|
// RouteContainer: traverses children statically per render, finds the active branch,
|
|
27735
28145
|
// and renders only that branch — or the fallback if nothing matches.
|
|
27736
28146
|
// No contexts, no state of its own: it reads the route signals directly, and
|
|
27737
|
-
// its
|
|
28147
|
+
// its effects only tell the outside what it has just done.
|
|
27738
28148
|
const RouteContainer = ({
|
|
27739
28149
|
id,
|
|
27740
28150
|
element,
|
|
@@ -27742,8 +28152,11 @@ const RouteContainer = ({
|
|
|
27742
28152
|
children
|
|
27743
28153
|
}) => {
|
|
27744
28154
|
const {
|
|
27745
|
-
activeBranch
|
|
28155
|
+
activeBranch,
|
|
28156
|
+
fallbackBranch,
|
|
28157
|
+
pages
|
|
27746
28158
|
} = collectBranches(children);
|
|
28159
|
+
const fallbackPage = fallbackBranch ? fallbackBranch.page : null;
|
|
27747
28160
|
|
|
27748
28161
|
// Told to hold still: what is on screen stays on screen. Kept as the very
|
|
27749
28162
|
// vnode that was rendered last time, which is how Preact is told there is
|
|
@@ -27755,11 +28168,25 @@ const RouteContainer = ({
|
|
|
27755
28168
|
}
|
|
27756
28169
|
const branch = shownBranchRef.current || activeBranch;
|
|
27757
28170
|
|
|
27758
|
-
// The
|
|
27759
|
-
//
|
|
28171
|
+
// The two things this component knows that nobody outside can find out: the
|
|
28172
|
+
// pages a named fallback is the absence of — the children it was written
|
|
28173
|
+
// with — and that the branch it chose is in the DOM.
|
|
27760
28174
|
useLayoutEffect(() => {
|
|
28175
|
+
if (fallbackPage) {
|
|
28176
|
+
wireRouteFallback(fallbackPage, pages);
|
|
28177
|
+
}
|
|
27761
28178
|
publishRouteRender();
|
|
27762
28179
|
});
|
|
28180
|
+
// Being wired means the container holding it is on screen: a router taken out
|
|
28181
|
+
// of the document leaves no fallback behind still claiming an address.
|
|
28182
|
+
useLayoutEffect(() => {
|
|
28183
|
+
if (!fallbackPage) {
|
|
28184
|
+
return undefined;
|
|
28185
|
+
}
|
|
28186
|
+
return () => {
|
|
28187
|
+
unwireRouteFallback(fallbackPage);
|
|
28188
|
+
};
|
|
28189
|
+
}, [fallbackPage]);
|
|
27763
28190
|
debug$1(`[container "${id}"] RENDER, active=${branch ? branch.type : "none"}`);
|
|
27764
28191
|
const content = branch ? branch.node : null;
|
|
27765
28192
|
if (!content) {
|
|
@@ -27773,10 +28200,16 @@ const RouteContainer = ({
|
|
|
27773
28200
|
// Walk JSX children vnodes (without rendering) to build a branch list and
|
|
27774
28201
|
// find the active one in the same pass. Anything that is not a <Route> is read
|
|
27775
28202
|
// through and kept around the branch it holds (see below).
|
|
27776
|
-
// Returns { matchingBranch, fallbackBranch, activeBranch }.
|
|
28203
|
+
// Returns { matchingBranch, fallbackBranch, activeBranch, pages }.
|
|
27777
28204
|
const collectBranches = children => {
|
|
27778
28205
|
let matchingBranch = null;
|
|
27779
28206
|
let fallbackBranch = null;
|
|
28207
|
+
// Every page a branch can be on, at any depth: what a named fallback is the
|
|
28208
|
+
// ABSENCE of (see route_fallback.js). A container with a guard route counts
|
|
28209
|
+
// as a page of its own AND contributes the pages inside it — either its guard
|
|
28210
|
+
// matching or a leaf matching under it is enough to select it, and the
|
|
28211
|
+
// fallback then stands down.
|
|
28212
|
+
const pages = [];
|
|
27780
28213
|
const visit = child => {
|
|
27781
28214
|
if (!child || child === true || child === false) {
|
|
27782
28215
|
return;
|
|
@@ -27804,8 +28237,12 @@ const collectBranches = children => {
|
|
|
27804
28237
|
}
|
|
27805
28238
|
const {
|
|
27806
28239
|
matchingBranch: matchingInside,
|
|
27807
|
-
fallbackBranch: fallbackInside
|
|
28240
|
+
fallbackBranch: fallbackInside,
|
|
28241
|
+
pages: pagesInside
|
|
27808
28242
|
} = collectBranches(wrapperChildren);
|
|
28243
|
+
for (const pageInside of pagesInside) {
|
|
28244
|
+
pages.push(pageInside);
|
|
28245
|
+
}
|
|
27809
28246
|
if (matchingInside && !matchingBranch) {
|
|
27810
28247
|
matchingBranch = wrapBranch(matchingInside, child);
|
|
27811
28248
|
}
|
|
@@ -27822,8 +28259,31 @@ const collectBranches = children => {
|
|
|
27822
28259
|
} = child.props;
|
|
27823
28260
|
if (nodeChildren) {
|
|
27824
28261
|
const {
|
|
27825
|
-
matchingBranch: matchingChild
|
|
28262
|
+
matchingBranch: matchingChild,
|
|
28263
|
+
fallbackBranch: fallbackChild,
|
|
28264
|
+
pages: pagesChild
|
|
27826
28265
|
} = collectBranches(nodeChildren);
|
|
28266
|
+
if (fallbackChild && !route) {
|
|
28267
|
+
// A fallback is the branch taken for an address none of its siblings
|
|
28268
|
+
// claim — so something has to make that address BELONG to this
|
|
28269
|
+
// container, and only a route of its own does. Without one the
|
|
28270
|
+
// container is selected by a child matching, which is the one case
|
|
28271
|
+
// where its fallback is not wanted: the branch could never render, and
|
|
28272
|
+
// an unmatched address is the router's to answer, not a layout's.
|
|
28273
|
+
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.`);
|
|
28274
|
+
}
|
|
28275
|
+
if (route) {
|
|
28276
|
+
// Params are left out on purpose: a guard selects its container on
|
|
28277
|
+
// matching alone (see guardMatching below), so the fallback stands
|
|
28278
|
+
// down for the whole route, not for one of its cases.
|
|
28279
|
+
pages.push({
|
|
28280
|
+
route,
|
|
28281
|
+
params: undefined
|
|
28282
|
+
});
|
|
28283
|
+
}
|
|
28284
|
+
for (const pageChild of pagesChild) {
|
|
28285
|
+
pages.push(pageChild);
|
|
28286
|
+
}
|
|
27827
28287
|
const branch = {
|
|
27828
28288
|
type: "container",
|
|
27829
28289
|
node: child
|
|
@@ -27843,7 +28303,8 @@ const collectBranches = children => {
|
|
|
27843
28303
|
if (!fallbackBranch) {
|
|
27844
28304
|
fallbackBranch = {
|
|
27845
28305
|
type: "fallback",
|
|
27846
|
-
node: child
|
|
28306
|
+
node: child,
|
|
28307
|
+
page: fallback.isRouteFallback ? fallback : null
|
|
27847
28308
|
};
|
|
27848
28309
|
}
|
|
27849
28310
|
} else {
|
|
@@ -27851,6 +28312,10 @@ const collectBranches = children => {
|
|
|
27851
28312
|
type: "leaf",
|
|
27852
28313
|
node: child
|
|
27853
28314
|
};
|
|
28315
|
+
pages.push({
|
|
28316
|
+
route,
|
|
28317
|
+
params: routeParams
|
|
28318
|
+
});
|
|
27854
28319
|
// every signal is read even once a match is found: reading is what
|
|
27855
28320
|
// subscribes the container to it, and a branch that is skipped today is
|
|
27856
28321
|
// the one that must wake the container up tomorrow
|
|
@@ -27866,9 +28331,19 @@ const collectBranches = children => {
|
|
|
27866
28331
|
return {
|
|
27867
28332
|
matchingBranch,
|
|
27868
28333
|
fallbackBranch,
|
|
27869
|
-
activeBranch
|
|
28334
|
+
activeBranch,
|
|
28335
|
+
pages
|
|
27870
28336
|
};
|
|
27871
28337
|
};
|
|
28338
|
+
const describeContainer = ({
|
|
28339
|
+
props
|
|
28340
|
+
}) => {
|
|
28341
|
+
const {
|
|
28342
|
+
element
|
|
28343
|
+
} = props;
|
|
28344
|
+
const name = typeof element === "function" ? element.name : null;
|
|
28345
|
+
return name ? `<Route element={${name}}>` : `<Route>`;
|
|
28346
|
+
};
|
|
27872
28347
|
const wrapBranch = (branch, wrapper) => {
|
|
27873
28348
|
return {
|
|
27874
28349
|
...branch,
|
|
@@ -29708,7 +30183,7 @@ const beginTransition = ({
|
|
|
29708
30183
|
// on, those would freeze the page until the browser gives up. The wait
|
|
29709
30184
|
// is raced with a short timer instead.
|
|
29710
30185
|
await Promise.race([renderWait.rendered, waitMs(50)]);
|
|
29711
|
-
} else if (pageIsCurrent
|
|
30186
|
+
} else if (pageIsCurrent(page)) {
|
|
29712
30187
|
await renderWait.rendered;
|
|
29713
30188
|
}
|
|
29714
30189
|
} finally {
|
|
@@ -29923,16 +30398,21 @@ const armRouteRenderWait$1 = () => {
|
|
|
29923
30398
|
};
|
|
29924
30399
|
const waitMs = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
29925
30400
|
|
|
29926
|
-
// A page written as the route itself,
|
|
29927
|
-
//
|
|
30401
|
+
// A page written as the route itself, the page named by the absence of every
|
|
30402
|
+
// route around it (see route_fallback.js), and the page written nowhere:
|
|
30403
|
+
// `from` may be left out, which is a relation about arriving at `to` from
|
|
30404
|
+
// anywhere.
|
|
29928
30405
|
const normalizePage$1 = page => {
|
|
29929
30406
|
if (!page) {
|
|
29930
30407
|
return null;
|
|
29931
30408
|
}
|
|
29932
|
-
|
|
29933
|
-
|
|
29934
|
-
|
|
29935
|
-
|
|
30409
|
+
if (page.isRoute || page.isRouteFallback) {
|
|
30410
|
+
return {
|
|
30411
|
+
route: page,
|
|
30412
|
+
params: undefined
|
|
30413
|
+
};
|
|
30414
|
+
}
|
|
30415
|
+
return page;
|
|
29936
30416
|
};
|
|
29937
30417
|
|
|
29938
30418
|
// Two pages are the same page when they select the same thing, not when they
|
|
@@ -29948,19 +30428,6 @@ const samePage$1 = (a, b) => {
|
|
|
29948
30428
|
};
|
|
29949
30429
|
const pageIndexOf$1 = (pages, page) => pages.findIndex(candidate => samePage$1(candidate, page));
|
|
29950
30430
|
|
|
29951
|
-
// Whether this page is the one on screen — same reading as route_travel.jsx's
|
|
29952
|
-
// own: matchingSignal is the necessary condition and is read whatever happens,
|
|
29953
|
-
// params only for a route that matches (the params of a route that does not
|
|
29954
|
-
// match are not params).
|
|
29955
|
-
const pageIsCurrent$1 = ({
|
|
29956
|
-
route,
|
|
29957
|
-
params
|
|
29958
|
-
}) => {
|
|
29959
|
-
if (!route.matchingSignal.value) {
|
|
29960
|
-
return false;
|
|
29961
|
-
}
|
|
29962
|
-
return params ? route.matchesParams(params) : true;
|
|
29963
|
-
};
|
|
29964
30431
|
// The FIRST page that answers, and every page read all the same: a page that
|
|
29965
30432
|
// is not the current one today is the one that must wake the reader tomorrow.
|
|
29966
30433
|
//
|
|
@@ -29973,7 +30440,7 @@ const pageIsCurrent$1 = ({
|
|
|
29973
30440
|
const currentPageIndex$1 = pages => {
|
|
29974
30441
|
let currentIndex = -1;
|
|
29975
30442
|
for (let i = 0; i < pages.length; i++) {
|
|
29976
|
-
const isCurrent = pageIsCurrent
|
|
30443
|
+
const isCurrent = pageIsCurrent(pages[i]);
|
|
29977
30444
|
if (!isCurrent) {
|
|
29978
30445
|
continue;
|
|
29979
30446
|
}
|
|
@@ -31326,28 +31793,6 @@ const samePage = (a, b) => {
|
|
|
31326
31793
|
};
|
|
31327
31794
|
const pageIndexOf = (pages, page) => pages.findIndex(candidate => samePage(candidate, page));
|
|
31328
31795
|
|
|
31329
|
-
// Whether this page is the one on screen. `matchesParams` reads paramsSignal,
|
|
31330
|
-
// so a caller reading this during a render is subscribed to the param changes
|
|
31331
|
-
// that walk from one tab to the next — matchingSignal alone never moves there,
|
|
31332
|
-
// and a row whose tabs are params of one route would never re-render.
|
|
31333
|
-
//
|
|
31334
|
-
// The params are read only for a route that matches, and that is not a signal
|
|
31335
|
-
// left unread: a reader wakes on anything it read last time, so what matters is
|
|
31336
|
-
// that everything able to make this answer change is among them.
|
|
31337
|
-
// matchingSignal is read whatever happens, and it is a NECESSARY condition —
|
|
31338
|
-
// while it is false no param of that route can put this page on screen, and the
|
|
31339
|
-
// day one could, matchingSignal itself has to turn true to say so, which is the
|
|
31340
|
-
// read that brings the params back in. (Asking anyway would be worse than
|
|
31341
|
-
// useless: the params of a route that does not match are not params.)
|
|
31342
|
-
const pageIsCurrent = ({
|
|
31343
|
-
route,
|
|
31344
|
-
params
|
|
31345
|
-
}) => {
|
|
31346
|
-
if (!route.matchingSignal.value) {
|
|
31347
|
-
return false;
|
|
31348
|
-
}
|
|
31349
|
-
return params ? route.matchesParams(params) : true;
|
|
31350
|
-
};
|
|
31351
31796
|
// The FIRST page that answers, as with the branches of a <Route>: several
|
|
31352
31797
|
// routes match at once — a literal one and the parameterized one it is a case of
|
|
31353
31798
|
// ("/games/new" is also a "/games/:gameId"), a section and the page inside it —
|
|
@@ -34263,6 +34708,17 @@ const useUIStateController = (
|
|
|
34263
34708
|
`merging button state into parent control group:`,
|
|
34264
34709
|
mergedState,
|
|
34265
34710
|
);
|
|
34711
|
+
// The sender's value is part of the answer, and whoever mirrors
|
|
34712
|
+
// this group — a picker, through its façade — reads the group's
|
|
34713
|
+
// state, not its children's: told the way a mount sync is
|
|
34714
|
+
// (silently, nobody has answered yet), so the send that follows
|
|
34715
|
+
// finds the value there. Not through the group's own onChange:
|
|
34716
|
+
// that re-aggregates from the children, and a button is not one.
|
|
34717
|
+
parentController.parentUIStateController?.onChildUIAction?.(
|
|
34718
|
+
parentController,
|
|
34719
|
+
e,
|
|
34720
|
+
{ stateChanged: true, silent: true },
|
|
34721
|
+
);
|
|
34266
34722
|
}
|
|
34267
34723
|
}
|
|
34268
34724
|
// Trigger uiAction/command side effects without changing UI state.
|
|
@@ -34631,6 +35087,12 @@ const useUIStateController = (
|
|
|
34631
35087
|
},
|
|
34632
35088
|
resetUIState: (e) => {
|
|
34633
35089
|
controller.setUIState(controller.state, e);
|
|
35090
|
+
// What the control holds moved back, and a bound signal mirrors what
|
|
35091
|
+
// it holds (see PROPAGATE_DOWN_EVENT_SET). Left on the value the
|
|
35092
|
+
// reset just undid — written there by the control itself at the
|
|
35093
|
+
// pick — the signal would hand it straight back on the next render,
|
|
35094
|
+
// and the refused value would win over the rollback.
|
|
35095
|
+
writeBoundSignal(controller.state);
|
|
34634
35096
|
},
|
|
34635
35097
|
// Read by the callout manager when it has nowhere else to point.
|
|
34636
35098
|
getCalloutAnchorElement: (event) =>
|
|
@@ -34734,9 +35196,27 @@ const useUIStateController = (
|
|
|
34734
35196
|
controller.hasStateProp = true;
|
|
34735
35197
|
const currentState = controller.state;
|
|
34736
35198
|
if (!compareTwoJsValues(state, currentState)) {
|
|
34737
|
-
|
|
34738
|
-
|
|
34739
|
-
|
|
35199
|
+
// A bound signal controls the state the way a `value` prop does —
|
|
35200
|
+
// and, unlike a `value`, it is written by the control itself at
|
|
35201
|
+
// every ui action. Read back here on a control that runs an action,
|
|
35202
|
+
// a signal matching the ui state is that echo (see the signal branch
|
|
35203
|
+
// below for the same rule on an uncontrolled control): `state` is
|
|
35204
|
+
// the rollback target, and taking the echo would make resetOnError
|
|
35205
|
+
// put back the very value the action is about to send, or has just
|
|
35206
|
+
// been refused. The action settles it — success acknowledges,
|
|
35207
|
+
// failure resets to what is kept here.
|
|
35208
|
+
const signalEchoesOwnWrite =
|
|
35209
|
+
Boolean(controlInfo.signal) &&
|
|
35210
|
+
Boolean(props.action) &&
|
|
35211
|
+
compareTwoJsValues(state, controller.uiState);
|
|
35212
|
+
if (!signalEchoesOwnWrite) {
|
|
35213
|
+
controller.state = state;
|
|
35214
|
+
if (!optimisticWorkInFlight) {
|
|
35215
|
+
controller.setUIState(
|
|
35216
|
+
state,
|
|
35217
|
+
new CustomEvent("state_prop_change"),
|
|
35218
|
+
);
|
|
35219
|
+
}
|
|
34740
35220
|
}
|
|
34741
35221
|
}
|
|
34742
35222
|
} else {
|
|
@@ -35903,6 +36383,10 @@ const useUIGroupStateController = (
|
|
|
35903
36383
|
const { controller } = s;
|
|
35904
36384
|
const prevDefaultValue = controller.defaultValue;
|
|
35905
36385
|
controller.props = props;
|
|
36386
|
+
// Published like a leaf's (see the leaf update above): a child's named
|
|
36387
|
+
// button reaches the group's owner through it (see the button branch of
|
|
36388
|
+
// onUIAction).
|
|
36389
|
+
controller.parentUIStateController = parentUIStateController;
|
|
35906
36390
|
controller.ref = ref;
|
|
35907
36391
|
controller.id = id;
|
|
35908
36392
|
controller.name = name;
|
|
@@ -42121,6 +42605,17 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
42121
42605
|
* way there changes. What a row of tabs wants — the neighbour is a lateral
|
|
42122
42606
|
* move, not a step deeper, so the whole row weighs one entry and the back
|
|
42123
42607
|
* button leaves by where the reader came in.
|
|
42608
|
+
* @param {boolean} [props.document] - The address is ANOTHER DOCUMENT of this
|
|
42609
|
+
* origin — an admin panel, a status page, anything built as its own entry
|
|
42610
|
+
* point — so the press is left to the browser and loads that page. Without
|
|
42611
|
+
* it navi routes every same-origin address it can intercept, and one no
|
|
42612
|
+
* route matches lands on the app's not-found screen. Said rather than
|
|
42613
|
+
* guessed: "no route matches" is also what a typo in an in-app url looks
|
|
42614
|
+
* like, and that one must show the not-found screen instead of silently
|
|
42615
|
+
* reloading the app. The element stays a real link — an address in the
|
|
42616
|
+
* status bar, a middle click, "open in new tab" — which is what navigating
|
|
42617
|
+
* from an `action` costs; `target="_blank"` is a different thing, a new tab.
|
|
42618
|
+
* Nothing is prefetched on the way there: the routes do not lead there.
|
|
42124
42619
|
* @param {boolean} [props.prefetch=true] Fetch the code of where this leads when the
|
|
42125
42620
|
* pointer or the focus arrives, ahead of the press (see
|
|
42126
42621
|
* docs/dynamic_import.md): the route actions that ask nothing of the
|
|
@@ -42232,13 +42727,17 @@ const LinkPlain = props => {
|
|
|
42232
42727
|
routeTransition,
|
|
42233
42728
|
pressableDuringRouteTransition,
|
|
42234
42729
|
replace,
|
|
42730
|
+
document: isDocument,
|
|
42235
42731
|
prefetch = true,
|
|
42236
42732
|
children
|
|
42237
42733
|
} = props;
|
|
42238
42734
|
if (anchor && !props.id) {
|
|
42239
42735
|
props.id = href.slice(1);
|
|
42240
42736
|
}
|
|
42241
|
-
|
|
42737
|
+
// A document link leads out of the routes, so there is nothing there to
|
|
42738
|
+
// prefetch — and the address would otherwise be preloaded against the very
|
|
42739
|
+
// routes it is escaping.
|
|
42740
|
+
usePreloadOnIntent(props.ref, href, isDocument ? false : prefetch);
|
|
42242
42741
|
const selectionContext = useContext(SelectionContext);
|
|
42243
42742
|
const nav = useContext(NavContext);
|
|
42244
42743
|
const visited = useIsVisited(href);
|
|
@@ -42353,6 +42852,12 @@ const LinkPlain = props => {
|
|
|
42353
42852
|
const replaceRequest = replace ? {
|
|
42354
42853
|
[LINK_REPLACE_ATTRIBUTE]: ""
|
|
42355
42854
|
} : null;
|
|
42855
|
+
|
|
42856
|
+
// That this address is another document is the link's to say, worn where the
|
|
42857
|
+
// interception looks for it (see link_document.js).
|
|
42858
|
+
const documentRequest = isDocument ? {
|
|
42859
|
+
[LINK_DOCUMENT_ATTRIBUTE]: ""
|
|
42860
|
+
} : null;
|
|
42356
42861
|
const innerChildren = children || (hrefFallback ? href : children);
|
|
42357
42862
|
const startIconEl = startIcon;
|
|
42358
42863
|
const endIconEl = innerEndIcon;
|
|
@@ -42406,12 +42911,14 @@ const LinkPlain = props => {
|
|
|
42406
42911
|
routeTransition: undefined,
|
|
42407
42912
|
pressableDuringRouteTransition: undefined,
|
|
42408
42913
|
replace: undefined,
|
|
42914
|
+
document: undefined,
|
|
42409
42915
|
prefetch: undefined,
|
|
42410
42916
|
"data-navi-route-transition-request": routeTransitionRequest,
|
|
42411
42917
|
...(pressableDuringRouteTransition ? {
|
|
42412
42918
|
[PRESSABLE_ATTRIBUTE]: ""
|
|
42413
42919
|
} : null),
|
|
42414
42920
|
...replaceRequest,
|
|
42921
|
+
...documentRequest,
|
|
42415
42922
|
// The control's own handlers first — the interaction gate, the caller's
|
|
42416
42923
|
// onClick/onKeyDown, the command and the action — then what only a link
|
|
42417
42924
|
// does. Written over the spread above, so they have to be called here.
|
|
@@ -45536,9 +46043,15 @@ const createOpenController = (
|
|
|
45536
46043
|
change();
|
|
45537
46044
|
};
|
|
45538
46045
|
const runChange = (change, { opened, event }) => {
|
|
46046
|
+
const applyChange = () => {
|
|
46047
|
+
// Recorded with the change itself: what the DOM shows is what a render
|
|
46048
|
+
// landing between the ask and the picture must draw (see `openedInDom`).
|
|
46049
|
+
controller.openedInDom = opened;
|
|
46050
|
+
change();
|
|
46051
|
+
};
|
|
45539
46052
|
const { transitionChange } = controller;
|
|
45540
46053
|
if (!transitionChange) {
|
|
45541
|
-
|
|
46054
|
+
applyChange();
|
|
45542
46055
|
return;
|
|
45543
46056
|
}
|
|
45544
46057
|
// What the controller answers about itself does not wait for the picture:
|
|
@@ -45547,10 +46060,10 @@ const createOpenController = (
|
|
|
45547
46060
|
// caller's own signal), and a request arriving before the change lands
|
|
45548
46061
|
// runs it first rather than reading a DOM that disagrees.
|
|
45549
46062
|
controller.opened = opened;
|
|
45550
|
-
changeAwaitingTransition =
|
|
46063
|
+
changeAwaitingTransition = applyChange;
|
|
45551
46064
|
transitionChange(
|
|
45552
46065
|
() => {
|
|
45553
|
-
if (changeAwaitingTransition ===
|
|
46066
|
+
if (changeAwaitingTransition === applyChange) {
|
|
45554
46067
|
flushChangeAwaitingTransition();
|
|
45555
46068
|
}
|
|
45556
46069
|
},
|
|
@@ -45668,6 +46181,13 @@ const createOpenController = (
|
|
|
45668
46181
|
};
|
|
45669
46182
|
const controller = {
|
|
45670
46183
|
opened: false,
|
|
46184
|
+
// What the DOM currently shows, as opposed to what the controller has
|
|
46185
|
+
// decided: the two differ for the frame a change spends waiting for the
|
|
46186
|
+
// browser to photograph the state being left (see runChange). A render
|
|
46187
|
+
// landing in that frame — a picker re-rendering because its action just
|
|
46188
|
+
// started — must draw THIS, or it paints the closed state before the
|
|
46189
|
+
// picture is taken and the movement has nothing to leave from.
|
|
46190
|
+
openedInDom: false,
|
|
45671
46191
|
// Which press the popup opened during, written at every open (see
|
|
45672
46192
|
// openedDuringThisPress). Never any press before there has been one.
|
|
45673
46193
|
pressCountAtOpen: null,
|
|
@@ -48292,6 +48812,7 @@ const ButtonUI = props => {
|
|
|
48292
48812
|
target,
|
|
48293
48813
|
rel,
|
|
48294
48814
|
replace,
|
|
48815
|
+
document: isDocument,
|
|
48295
48816
|
pressableDuringRouteTransition,
|
|
48296
48817
|
prefetch = true,
|
|
48297
48818
|
// visual
|
|
@@ -48346,13 +48867,21 @@ const ButtonUI = props => {
|
|
|
48346
48867
|
[LINK_REPLACE_ATTRIBUTE]: ""
|
|
48347
48868
|
} : null;
|
|
48348
48869
|
|
|
48870
|
+
// And this one says the address is another document of this origin, so the
|
|
48871
|
+
// press is the browser's (see link_document.js).
|
|
48872
|
+
const documentRequest = isDocument ? {
|
|
48873
|
+
[LINK_DOCUMENT_ATTRIBUTE]: ""
|
|
48874
|
+
} : null;
|
|
48875
|
+
|
|
48349
48876
|
// Worn as an attribute too, and read at the document by whoever catches the
|
|
48350
48877
|
// press a movement would have swallowed (see transition_press.js).
|
|
48351
48878
|
const pressableRequest = pressableDuringRouteTransition ? {
|
|
48352
48879
|
[PRESSABLE_ATTRIBUTE]: ""
|
|
48353
48880
|
} : null;
|
|
48354
48881
|
const visualSelector = ".navi_button_content";
|
|
48355
|
-
|
|
48882
|
+
// Nothing to prefetch on the way to another document: the routes do not
|
|
48883
|
+
// lead there.
|
|
48884
|
+
usePreloadOnIntent(ref, href, isDocument ? false : prefetch);
|
|
48356
48885
|
useAccentColorAttributes(ref, null, {
|
|
48357
48886
|
elementSelector: visualSelector
|
|
48358
48887
|
});
|
|
@@ -48380,6 +48909,8 @@ const ButtonUI = props => {
|
|
|
48380
48909
|
rel: innerRel,
|
|
48381
48910
|
replace: undefined,
|
|
48382
48911
|
...replaceRequest,
|
|
48912
|
+
document: undefined,
|
|
48913
|
+
...documentRequest,
|
|
48383
48914
|
pressableDuringRouteTransition: undefined,
|
|
48384
48915
|
...pressableRequest,
|
|
48385
48916
|
prefetch: undefined
|
|
@@ -48826,6 +49357,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48826
49357
|
* selfInteractions?: string,
|
|
48827
49358
|
* whenSelfInteractionsBlocked?: "hide" | "refuse" | "ignore",
|
|
48828
49359
|
* replace?: boolean,
|
|
49360
|
+
* document?: boolean,
|
|
48829
49361
|
* actionStandalone?: boolean,
|
|
48830
49362
|
* actionAbortable?: boolean,
|
|
48831
49363
|
* optimistic?: boolean,
|
|
@@ -48835,6 +49367,13 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48835
49367
|
* `--navi-nav-to` command — by TAKING THE PLACE of the current history entry
|
|
48836
49368
|
* rather than stacking on it: what `<Link replace>` says, for a press drawn
|
|
48837
49369
|
* as a button.
|
|
49370
|
+
* @param {boolean} [document] The `href` is ANOTHER DOCUMENT of this origin —
|
|
49371
|
+
* an admin panel, a status page, anything built as its own entry point — so
|
|
49372
|
+
* the press is left to the browser and loads that page, instead of being
|
|
49373
|
+
* routed to the app's not-found screen. What `<Link document>` says, for a
|
|
49374
|
+
* press drawn as a button; and the reason to say it rather than navigate
|
|
49375
|
+
* from an `action` is that the button stays a real link (an address, a
|
|
49376
|
+
* middle click, "open in new tab"). Nothing is prefetched on the way there.
|
|
48838
49377
|
* @param {boolean} [prefetch=true] Fetch the code of where this leads when the
|
|
48839
49378
|
* pointer or the focus arrives, ahead of the press (see
|
|
48840
49379
|
* docs/dynamic_import.md): the route actions that ask nothing of the
|
|
@@ -58826,10 +59365,10 @@ const css$E = /* css */`
|
|
|
58826
59365
|
var(--dialog-maxmax-height)
|
|
58827
59366
|
);
|
|
58828
59367
|
|
|
58829
|
-
/* Base default: also
|
|
59368
|
+
/* Base default: also a local dialog's own permanent value — its
|
|
58830
59369
|
containing block is genuinely its nearest positioned ancestor,
|
|
58831
59370
|
regardless of positionArea. See the [data-layer="top"] rule below for
|
|
58832
|
-
why
|
|
59371
|
+
why a top-layer one overrides this. Position is always
|
|
58833
59372
|
JS-driven (pickPositionRelativeTo, see useDialogProps below) — no CSS
|
|
58834
59373
|
alignment/inset math here at all. */
|
|
58835
59374
|
position: absolute;
|
|
@@ -58912,13 +59451,8 @@ const css$E = /* css */`
|
|
|
58912
59451
|
var(--container-position-remaining-width, var(--dialog-maxmax-width)),
|
|
58913
59452
|
var(--dialog-maxmax-width)
|
|
58914
59453
|
);
|
|
58915
|
-
/* The sheet rests on the screen's bottom edge, which on a phone is the
|
|
58916
|
-
home indicator and, since iOS 26, Safari's own floating bar — a band
|
|
58917
|
-
the browser does not paint fixed content into. The surface still
|
|
58918
|
-
reaches the edge (the sheet comes out from behind the bar); what it
|
|
58919
|
-
holds stops above it. */
|
|
58920
|
-
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
58921
59454
|
}
|
|
59455
|
+
|
|
58922
59456
|
/* The clamped max, not --dialog-maxmax-*: that one is the viewport minus
|
|
58923
59457
|
the spacing, which is only the real ceiling for layer="top". A local
|
|
58924
59458
|
dialog is confined to its positioned ancestor, whose size reaches here
|
|
@@ -58946,6 +59480,35 @@ const css$E = /* css */`
|
|
|
58946
59480
|
border-bottom-left-radius: 0;
|
|
58947
59481
|
}
|
|
58948
59482
|
|
|
59483
|
+
/* An edge the dialog sits flush against is an edge of the screen, and the
|
|
59484
|
+
device keeps a band there it paints nothing into: the notch, the home
|
|
59485
|
+
indicator, Safari's own floating bar since iOS 26. The surface still
|
|
59486
|
+
reaches the edge — a sheet comes out from behind the bar rather than
|
|
59487
|
+
stopping short of it — and what it holds stops at the band.
|
|
59488
|
+
|
|
59489
|
+
env(), not --navi-safe-area-inset-*: that one is the app's own free
|
|
59490
|
+
region and counts its fixed bars, which this dialog is in front of. The
|
|
59491
|
+
device's inset is the only thing left to keep.
|
|
59492
|
+
|
|
59493
|
+
layer="top" only. A local dialog is flush with the box it was declared
|
|
59494
|
+
in, and whether that box reaches the screen's edge is something only the
|
|
59495
|
+
app knows — it gives that band back on its own container (see
|
|
59496
|
+
safe_area.js's [data-navi-safe-area]). */
|
|
59497
|
+
&[data-layer="top"] {
|
|
59498
|
+
&[data-flush-top] {
|
|
59499
|
+
padding-top: env(safe-area-inset-top, 0px);
|
|
59500
|
+
}
|
|
59501
|
+
&[data-flush-right] {
|
|
59502
|
+
padding-right: env(safe-area-inset-right, 0px);
|
|
59503
|
+
}
|
|
59504
|
+
&[data-flush-bottom] {
|
|
59505
|
+
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
59506
|
+
}
|
|
59507
|
+
&[data-flush-left] {
|
|
59508
|
+
padding-left: env(safe-area-inset-left, 0px);
|
|
59509
|
+
}
|
|
59510
|
+
}
|
|
59511
|
+
|
|
58949
59512
|
/* The placement is a translate, so the translate property is spoken for
|
|
58950
59513
|
here (see applyNewPosition in visible_rect.js, which owns it and animates
|
|
58951
59514
|
it itself through the Web Animations API rather than through this file's
|
|
@@ -58958,8 +59521,8 @@ const css$E = /* css */`
|
|
|
58958
59521
|
--backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
|
|
58959
59522
|
}
|
|
58960
59523
|
/* backdropVariant, after the rules it overrides: same specificity, so
|
|
58961
|
-
order is what decides.
|
|
58962
|
-
|
|
59524
|
+
order is what decides. The wall is still there either way — only the
|
|
59525
|
+
paint goes away. */
|
|
58963
59526
|
&[data-backdrop-variant="discrete"] {
|
|
58964
59527
|
--backdrop-background: var(--navi-backdrop-discrete-background);
|
|
58965
59528
|
--backdrop-filter: var(--navi-backdrop-discrete-backdrop-filter);
|
|
@@ -59023,7 +59586,10 @@ const css$E = /* css */`
|
|
|
59023
59586
|
--navi-focus-outline-style: none;
|
|
59024
59587
|
}
|
|
59025
59588
|
|
|
59026
|
-
|
|
59589
|
+
/* :popover-open too — a top-layer dialog with no wall is shown with
|
|
59590
|
+
showPopover(), which never sets the [open] attribute. */
|
|
59591
|
+
&[open],
|
|
59592
|
+
&:popover-open {
|
|
59027
59593
|
display: flex;
|
|
59028
59594
|
}
|
|
59029
59595
|
|
|
@@ -59060,14 +59626,12 @@ const css$E = /* css */`
|
|
|
59060
59626
|
}
|
|
59061
59627
|
}
|
|
59062
59628
|
|
|
59063
|
-
/*
|
|
59629
|
+
/* layer="local" only — a .show()'d dialog gets no ::backdrop, so its wall is
|
|
59064
59630
|
a real sibling element instead, same idea/CSS shape as Popover's own
|
|
59065
|
-
.navi_popover_backdrop (see popover.jsx's top comment for the design
|
|
59066
|
-
|
|
59067
|
-
|
|
59068
|
-
|
|
59069
|
-
via-attribute renderer for free regardless of
|
|
59070
|
-
pointerInteractionOutsideEffect. */
|
|
59631
|
+
.navi_popover_backdrop (see popover.jsx's top comment for the design this
|
|
59632
|
+
mirrors). Rendered whenever backdrop={true}, whatever
|
|
59633
|
+
pointerInteractionOutsideEffect says: a wall that closes nothing still
|
|
59634
|
+
absorbs the press, matching what showModal() gives a modal for free. */
|
|
59071
59635
|
.navi_dialog_backdrop {
|
|
59072
59636
|
--popup-animation-duration: 0.18s;
|
|
59073
59637
|
|
|
@@ -59082,7 +59646,7 @@ const css$E = /* css */`
|
|
|
59082
59646
|
in openEffect) gets pointer-events: none mid-transition. */
|
|
59083
59647
|
pointer-events: auto;
|
|
59084
59648
|
|
|
59085
|
-
/* Painted through the same two variables as
|
|
59649
|
+
/* Painted through the same two variables as a modal's own
|
|
59086
59650
|
::backdrop (see them for what each rule is for) — here they resolve on
|
|
59087
59651
|
the element that paints, which is also the element the
|
|
59088
59652
|
backdropColor/backdropFilter props are set on. Declared unconditionally
|
|
@@ -59174,12 +59738,15 @@ const css$E = /* css */`
|
|
|
59174
59738
|
* grammar, anchor's sizing-only role, backdrop mechanics).
|
|
59175
59739
|
*
|
|
59176
59740
|
* @param {object} props
|
|
59177
|
-
* @param {"top"|"local"} [props.layer="top"] -
|
|
59178
|
-
*
|
|
59179
|
-
*
|
|
59180
|
-
* shown via the non-modal `.show()` instead, staying in normal document
|
|
59741
|
+
* @param {"top"|"local"} [props.layer="top"] - What the dialog is placed
|
|
59742
|
+
* against. `"top"`: the browser's own top layer, placed against the screen,
|
|
59743
|
+
* never clipped by anything the app declared. `"local"`: normal document
|
|
59181
59744
|
* flow inside its own positioned ancestor — confined to (and clipped by)
|
|
59182
|
-
* that container instead of the whole viewport.
|
|
59745
|
+
* that container instead of the whole viewport. Whether the top-layer one
|
|
59746
|
+
* is modal is `backdrop`'s answer, not this one's: with a backdrop it is
|
|
59747
|
+
* `showModal()`'d (native focus trap, `Escape`-to-cancel, hardware
|
|
59748
|
+
* back-button dismissal, rest-of-document made inert), without one it is
|
|
59749
|
+
* shown through the Popover API and the page behind stays live.
|
|
59183
59750
|
* @param {boolean} [props.dockedOnSmallTouchScreen] - Turns the dialog into a
|
|
59184
59751
|
* bottom sheet (docked flush to the bottom edge, full width) on a small touch
|
|
59185
59752
|
* screen, and leaves it alone otherwise. For a dialog meant to be interacted
|
|
@@ -59242,9 +59809,12 @@ const css$E = /* css */`
|
|
|
59242
59809
|
* outside press does or how the backdrop is painted. `false` leaves the page
|
|
59243
59810
|
* reachable: a press outside closes the dialog (per
|
|
59244
59811
|
* `pointerInteractionOutsideEffect`) *and* is answered by whatever it landed
|
|
59245
|
-
* on, in the same gesture
|
|
59246
|
-
*
|
|
59247
|
-
*
|
|
59812
|
+
* on, in the same gesture; focus is free to leave too, the page behind being
|
|
59813
|
+
* meant to be reached. Works in either layer — a top-layer dialog with no
|
|
59814
|
+
* wall is shown through the Popover API rather than `showModal()`, which is
|
|
59815
|
+
* what a sheet docked to the screen's bottom edge over a still-readable map
|
|
59816
|
+
* needs. What it gives up is what only a modal gets natively: the hardware
|
|
59817
|
+
* back button no longer dismisses it. See docs/popup_backdrop.md.
|
|
59248
59818
|
* @param {"close"|"cancel"|"capture"|"none"} [props.pointerInteractionOutsideEffect="close"]
|
|
59249
59819
|
* - `"close"` closes the dialog on an outside click. `"capture"`/`"none"`
|
|
59250
59820
|
* both just absorb the click without closing (visually dimmed backdrop vs.
|
|
@@ -59504,7 +60074,7 @@ const UncontrolledDialog = props => {
|
|
|
59504
60074
|
// Picks which rendering strategy actually mounts, from `layer` alone — see
|
|
59505
60075
|
// this file's top comment. Done after the controlled/uncontrolled split
|
|
59506
60076
|
// above, so an openController is always already resolved by the time
|
|
59507
|
-
//
|
|
60077
|
+
// DialogInTopLayer/DialogLocal (and the useDialogProps hook they share) ever
|
|
59508
60078
|
// run.
|
|
59509
60079
|
const ControlledDialog = props => {
|
|
59510
60080
|
if (props.layer === "local") {
|
|
@@ -59512,11 +60082,11 @@ const ControlledDialog = props => {
|
|
|
59512
60082
|
...props
|
|
59513
60083
|
});
|
|
59514
60084
|
}
|
|
59515
|
-
return jsx(
|
|
60085
|
+
return jsx(DialogInTopLayer, {
|
|
59516
60086
|
...props
|
|
59517
60087
|
});
|
|
59518
60088
|
};
|
|
59519
|
-
const
|
|
60089
|
+
const DialogInTopLayer = props => {
|
|
59520
60090
|
const [backdropProps, contentProps] = useDialogProps(props);
|
|
59521
60091
|
return jsxs(Fragment, {
|
|
59522
60092
|
children: [backdropProps && jsx(Box, {
|
|
@@ -59548,12 +60118,14 @@ const DialogLocal = props => {
|
|
|
59548
60118
|
/**
|
|
59549
60119
|
* Everything both rendering strategies share once an `openController` is
|
|
59550
60120
|
* already resolved: focus/debug/id plumbing, the open-commit sequence, the
|
|
59551
|
-
* close handler — inlined in `openEffect`, branching on
|
|
60121
|
+
* close handler — inlined in `openEffect`, branching on how the dialog is
|
|
60122
|
+
* shown at each
|
|
59552
60123
|
* point the two renderers genuinely differ (same pattern as popover.jsx's
|
|
59553
60124
|
* own usePopoverProps — see its top comment for why this stays inline
|
|
59554
60125
|
* rather than split into two functions). Returns `[backdropProps,
|
|
59555
|
-
* contentProps]` — `backdropProps` is `null`
|
|
59556
|
-
*
|
|
60126
|
+
* contentProps]` — `backdropProps` is `null` unless there is a wall of our
|
|
60127
|
+
* own to render: a modal's is the native `::backdrop`, and `backdrop={false}`
|
|
60128
|
+
* has none at all.
|
|
59557
60129
|
*/
|
|
59558
60130
|
// What a dialog turns into on a small touch screen. "bottom" is not a taste:
|
|
59559
60131
|
// it puts the dialog in the zone a phone is actually operated from — where the
|
|
@@ -59585,10 +60157,10 @@ const useDialogProps = props => {
|
|
|
59585
60157
|
props.ref = props.ref || defaultRef;
|
|
59586
60158
|
const {
|
|
59587
60159
|
openController,
|
|
59588
|
-
// "top" (default) →
|
|
59589
|
-
//
|
|
59590
|
-
// .
|
|
59591
|
-
//
|
|
60160
|
+
// "top" (default) → the browser's own top layer, placed against the
|
|
60161
|
+
// screen. "local" → normal document flow, position: absolute relative to
|
|
60162
|
+
// its own positioned ancestor. Which show call each takes also depends on
|
|
60163
|
+
// `backdrop` — see this file's top comment.
|
|
59592
60164
|
layer = "top",
|
|
59593
60165
|
dockedOnSmallTouchScreen,
|
|
59594
60166
|
// Same grammar as Popover's own positionArea — see this file's top
|
|
@@ -59602,17 +60174,16 @@ const useDialogProps = props => {
|
|
|
59602
60174
|
expandX: expandXProp,
|
|
59603
60175
|
expandY: expandYProp,
|
|
59604
60176
|
// "close" (default) closes on an outside click. "capture"/"none" both
|
|
59605
|
-
// just absorb it without closing — for
|
|
59606
|
-
//
|
|
59607
|
-
//
|
|
59608
|
-
//
|
|
59609
|
-
// actually makes "capture"/"none" behave the same way here too.
|
|
60177
|
+
// just absorb it without closing — for a modal, showModal() already makes
|
|
60178
|
+
// the rest of the page inert, so there's nothing for a click to reach
|
|
60179
|
+
// either way; otherwise there's no native inert-ing, so the real backdrop
|
|
60180
|
+
// below is what makes "capture"/"none" behave the same way here too.
|
|
59610
60181
|
pointerInteractionOutsideEffect = "close",
|
|
59611
60182
|
// Whether there is a wall between the dialog and the page at all, asked
|
|
59612
60183
|
// before what a press on it does (pointerInteractionOutsideEffect) and
|
|
59613
|
-
// before how it is painted (backdropVariant below).
|
|
59614
|
-
//
|
|
59615
|
-
//
|
|
60184
|
+
// before how it is painted (backdropVariant below). It also picks the show
|
|
60185
|
+
// call for layer="top": a wall is what showModal() is for, and without one
|
|
60186
|
+
// the dialog goes to the same top layer through the Popover API instead.
|
|
59616
60187
|
backdrop = true,
|
|
59617
60188
|
// How loudly the backdrop says it is there — independent of what it
|
|
59618
60189
|
// *does* (that's pointerInteractionOutsideEffect above). "invisible" is a
|
|
@@ -59669,10 +60240,11 @@ const useDialogProps = props => {
|
|
|
59669
60240
|
anchor
|
|
59670
60241
|
});
|
|
59671
60242
|
const children = contentMounted ? childrenProp : null;
|
|
59672
|
-
|
|
59673
|
-
|
|
59674
|
-
|
|
59675
|
-
|
|
60243
|
+
// Where it is placed, and whether there is a wall — the two questions that
|
|
60244
|
+
// pick the show call (see this file's top comment).
|
|
60245
|
+
const isTopLayer = layer === "top";
|
|
60246
|
+
const isModal = isTopLayer && backdrop;
|
|
60247
|
+
const isTopLayerPopover = isTopLayer && !backdrop;
|
|
59676
60248
|
if (!backdrop && pointerInteractionOutsideEffect === "capture") {
|
|
59677
60249
|
console.warn(`Dialog: pointerInteractionOutsideEffect="capture" needs a backdrop. Absorbing a press is what a wall does, and backdrop={false} takes it away.`);
|
|
59678
60250
|
}
|
|
@@ -59693,7 +60265,7 @@ const useDialogProps = props => {
|
|
|
59693
60265
|
// container being that screen (the viewport, unless the app declared a
|
|
59694
60266
|
// narrower one) — and the positioned ancestor for a local one, where
|
|
59695
60267
|
// reading 3% of the screen gives an absurd gap inside a small box.
|
|
59696
|
-
|
|
60268
|
+
isTopLayer ? "3appw" : "3cqw");
|
|
59697
60269
|
// "expand || expandX", the shorthand semantics Popup used to apply before
|
|
59698
60270
|
// handing them over — the docked default only applies when neither was said
|
|
59699
60271
|
const expandXUnset = expand === undefined && expandXProp === undefined;
|
|
@@ -59891,14 +60463,14 @@ const useDialogProps = props => {
|
|
|
59891
60463
|
// from dialogEl.parentElement, which for DialogLocal is the
|
|
59892
60464
|
// .navi_dialog_clip_wrapper (itself position: absolute) rather than the
|
|
59893
60465
|
// real, meaningful ancestor beyond it.
|
|
59894
|
-
const positionedAncestor =
|
|
60466
|
+
const positionedAncestor = isTopLayer ? document.documentElement : getPositionedParent(dialogEl.parentElement /* dialogEl is inside the clip_wrapper */);
|
|
59895
60467
|
const [cleanup, addCleanup] = createPubSub(true);
|
|
59896
60468
|
const anchorElement = resolveAnchorElement(e);
|
|
59897
60469
|
// Kept for the closing, which has no opening event to read it back from:
|
|
59898
60470
|
// the box the dialog shrinks back into is the one it came out of.
|
|
59899
60471
|
anchorElementRef.current = anchorElement;
|
|
59900
60472
|
debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> openDialog`);
|
|
59901
|
-
if (!
|
|
60473
|
+
if (!isTopLayer) {
|
|
59902
60474
|
// see openLocalDialogCount's own comment
|
|
59903
60475
|
dialogEl.style.setProperty("--dialog-stack-order", openLocalDialogCount++);
|
|
59904
60476
|
}
|
|
@@ -59941,10 +60513,12 @@ const useDialogProps = props => {
|
|
|
59941
60513
|
}
|
|
59942
60514
|
if (isModal) {
|
|
59943
60515
|
dialogEl.showModal();
|
|
60516
|
+
} else if (isTopLayerPopover) {
|
|
60517
|
+
dialogEl.showPopover();
|
|
59944
60518
|
} else {
|
|
59945
60519
|
dialogEl.show();
|
|
59946
60520
|
}
|
|
59947
|
-
//
|
|
60521
|
+
// Whichever show call ran — see the backdrop's own [navi-hidden] CSS rule
|
|
59948
60522
|
// and popover.jsx's identical reasoning: showModal()/show() alone only
|
|
59949
60523
|
// wins over a stray, still-present [navi-hidden] { display: none }
|
|
59950
60524
|
// default when nothing else authored also sets display on dialogEl —
|
|
@@ -59952,24 +60526,23 @@ const useDialogProps = props => {
|
|
|
59952
60526
|
// property (e.g. Popup's own flex prop) defeats the UA stylesheet's own
|
|
59953
60527
|
// dialog:not([open]) default the same way it can for Popover.
|
|
59954
60528
|
dialogEl.removeAttribute("navi-hidden");
|
|
59955
|
-
if (isModal) ; else {
|
|
60529
|
+
if (isModal) ; else if (backdrop) {
|
|
59956
60530
|
addCleanup(trapFocusInside(dialogEl, {
|
|
59957
60531
|
debug: debugFocus,
|
|
59958
60532
|
boundaryElement: positionedAncestor,
|
|
59959
|
-
//
|
|
59960
|
-
//
|
|
59961
|
-
//
|
|
59962
|
-
// from it either, not just a Tab press.
|
|
60533
|
+
// The wall stops the keyboard too — a mousedown on some other
|
|
60534
|
+
// focusable element behind it must not steal the focus away either,
|
|
60535
|
+
// not just a Tab press.
|
|
59963
60536
|
pointerTrap: true
|
|
59964
60537
|
}));
|
|
59965
60538
|
}
|
|
59966
60539
|
if (scrollCapture) {
|
|
59967
|
-
// A modal dialog always has its own ::backdrop;
|
|
59968
|
-
//
|
|
60540
|
+
// A modal dialog always has its own ::backdrop; a local one has the
|
|
60541
|
+
// backdrop element when it renders one.
|
|
59969
60542
|
addCleanup(trapScrollInside(dialogEl, {
|
|
59970
60543
|
backdrop: isModal || backdropEl
|
|
59971
60544
|
}));
|
|
59972
|
-
} else if (!
|
|
60545
|
+
} else if (!isTopLayer) {
|
|
59973
60546
|
// A local dialog is confined to its positioned ancestor, and so is its
|
|
59974
60547
|
// backdrop (inset: 0 covers the scrollport, not the scrolled content):
|
|
59975
60548
|
// letting that ancestor scroll would slide the dialog away and reveal
|
|
@@ -59986,7 +60559,7 @@ const useDialogProps = props => {
|
|
|
59986
60559
|
// Positioning: dialogEl is already shown (display: flex, per this
|
|
59987
60560
|
// file's own [open] CSS) by this point, so its own dimensions are real
|
|
59988
60561
|
// — pickPositionRelativeTo's own no-anchor/docked mode (no `anchor`
|
|
59989
|
-
// argument at all) docks it against the viewport (layer="top"
|
|
60562
|
+
// argument at all) docks it against the viewport (layer="top")
|
|
59990
60563
|
// or its own positioned ancestor (layer="local", the same
|
|
59991
60564
|
// positionedAncestor computed above), same mechanism as Popover's own
|
|
59992
60565
|
// custom renderer. applyDialogPosition sets --container-position-remaining-height/-width
|
|
@@ -60018,7 +60591,7 @@ const useDialogProps = props => {
|
|
|
60018
60591
|
// box is what the caps must read, and nothing in CSS tracks it.
|
|
60019
60592
|
const applyDialogPosition = position => {
|
|
60020
60593
|
applyNewPosition(dialogEl, position);
|
|
60021
|
-
if (
|
|
60594
|
+
if (isTopLayer) {
|
|
60022
60595
|
dialogEl.style.removeProperty("--container-position-remaining-height");
|
|
60023
60596
|
dialogEl.style.removeProperty("--container-position-remaining-width");
|
|
60024
60597
|
}
|
|
@@ -60117,7 +60690,7 @@ const useDialogProps = props => {
|
|
|
60117
60690
|
|
|
60118
60691
|
// Reposition on the same triggers Popover's own visibleRectEffect
|
|
60119
60692
|
// already reacts to generically — window resize/scroll/visual-viewport
|
|
60120
|
-
// changes for layer="top"
|
|
60693
|
+
// changes for layer="top" (positionedAncestor is already
|
|
60121
60694
|
// document.documentElement there, see its own computation above;
|
|
60122
60695
|
// visibleRectEffect already debounces visualViewport resize by 100ms
|
|
60123
60696
|
// to avoid the mobile tap-to-tap-input keyboard flicker, so no
|
|
@@ -60215,15 +60788,19 @@ const useDialogProps = props => {
|
|
|
60215
60788
|
debugPopup(`"${closeEvent.type}" on ${getElementSignature(closeEvent.target)} -> closeDialog`);
|
|
60216
60789
|
clearTextSelectionInside(dialogEl);
|
|
60217
60790
|
dialogEl.setAttribute("aria-expanded", "false");
|
|
60218
|
-
if (!
|
|
60791
|
+
if (!isTopLayer) {
|
|
60219
60792
|
openLocalDialogCount = Math.max(0, openLocalDialogCount - 1);
|
|
60220
60793
|
dialogEl.style.removeProperty("--dialog-stack-order");
|
|
60221
60794
|
}
|
|
60222
60795
|
// See openEffect's own identical comment for why this is needed
|
|
60223
|
-
//
|
|
60796
|
+
// whichever show call ran, not just when a stray authored display
|
|
60224
60797
|
// property is actually present — harmless the rest of the time.
|
|
60225
60798
|
dialogEl.setAttribute("navi-hidden", "");
|
|
60226
|
-
|
|
60799
|
+
if (isTopLayerPopover) {
|
|
60800
|
+
dialogEl.hidePopover();
|
|
60801
|
+
} else {
|
|
60802
|
+
dialogEl.close();
|
|
60803
|
+
}
|
|
60227
60804
|
// Held at the size it has right now, for the whole way out. cleanup()
|
|
60228
60805
|
// below already stops the JS repositioning, but the size is CSS-driven
|
|
60229
60806
|
// (--x-dialog-max-height, and `height` outright under expandY) and
|
|
@@ -60250,9 +60827,8 @@ const useDialogProps = props => {
|
|
|
60250
60827
|
};
|
|
60251
60828
|
const onKeyDownShortcuts = createOnKeyDownForShortcuts({
|
|
60252
60829
|
escape: e => {
|
|
60253
|
-
// Only
|
|
60254
|
-
//
|
|
60255
|
-
// non-modal .show()'d one doesn't.
|
|
60830
|
+
// Only a modal <dialog> fires "cancel" on Escape natively (handled via
|
|
60831
|
+
// onCancel below); neither a .show()n nor a showPopover()n one does.
|
|
60256
60832
|
if (isModal || !openController.opened) {
|
|
60257
60833
|
return null;
|
|
60258
60834
|
}
|
|
@@ -60271,10 +60847,10 @@ const useDialogProps = props => {
|
|
|
60271
60847
|
// most fields are shared: renderer-specific bits (the outside-click
|
|
60272
60848
|
// handler below, in particular) are just assigned onto whichever of the
|
|
60273
60849
|
// two actually owns that concern for a given renderer, instead of one
|
|
60274
|
-
// object's own field branching internally
|
|
60275
|
-
//
|
|
60276
|
-
//
|
|
60277
|
-
//
|
|
60850
|
+
// object's own field branching internally. backdropProps only gets returned
|
|
60851
|
+
// (see the bottom of this function) for the one dialog whose wall is a real
|
|
60852
|
+
// element of ours: a modal's is native (::backdrop), and backdrop={false}
|
|
60853
|
+
// has none at all.
|
|
60278
60854
|
Object.assign(backdropProps, {
|
|
60279
60855
|
"ref": backdropRef,
|
|
60280
60856
|
// Out of flow like the popup it belongs to — see the content element's own
|
|
@@ -60290,14 +60866,14 @@ const useDialogProps = props => {
|
|
|
60290
60866
|
// and matching what a descendant relying on
|
|
60291
60867
|
// use_displayed_layout_effect.js's own aria-expanded-presence check
|
|
60292
60868
|
// needs — see popover.jsx's own identical prop for the full reasoning.
|
|
60293
|
-
"aria-expanded": openController.
|
|
60869
|
+
"aria-expanded": openController.openedInDom ? "true" : "false",
|
|
60294
60870
|
// Present from this very first render (recomputed fresh on every one
|
|
60295
60871
|
// from openController.opened, not a frozen mount-time constant) so
|
|
60296
60872
|
// there's no gap for the browser to ever paint this plain-div backdrop
|
|
60297
60873
|
// visible before anything has actually opened it — see popover.jsx's
|
|
60298
60874
|
// own identical prop for the full reasoning, and this file's own CSS
|
|
60299
60875
|
// for the rule it drives.
|
|
60300
|
-
"navi-hidden": openController.
|
|
60876
|
+
"navi-hidden": openController.openedInDom ? undefined : "",
|
|
60301
60877
|
"styleCSSVars": DIALOG_STYLE_CSS_VARS,
|
|
60302
60878
|
"animationDuration": rest.animationDuration,
|
|
60303
60879
|
"data-pointer-interaction-outside": pointerInteractionOutsideEffect,
|
|
@@ -60309,7 +60885,7 @@ const useDialogProps = props => {
|
|
|
60309
60885
|
tabIndex,
|
|
60310
60886
|
// See backdropProps' own identical prop above for the full reasoning
|
|
60311
60887
|
// (kept once, not repeated here).
|
|
60312
|
-
"aria-expanded": openController.
|
|
60888
|
+
"aria-expanded": openController.openedInDom ? "true" : "false",
|
|
60313
60889
|
// Present from the very first render (recomputed fresh from
|
|
60314
60890
|
// openController.opened every time, not a frozen mount-time constant —
|
|
60315
60891
|
// see popover.jsx's own identical prop for the full reasoning) so a
|
|
@@ -60317,28 +60893,28 @@ const useDialogProps = props => {
|
|
|
60317
60893
|
// can't silently defeat showModal()/close()'s native open/close — see
|
|
60318
60894
|
// this file's own CSS rule for dialogEl and the open/close steps below
|
|
60319
60895
|
// for how it's toggled.
|
|
60320
|
-
"navi-hidden": openController.
|
|
60896
|
+
"navi-hidden": openController.openedInDom ? undefined : "",
|
|
60321
60897
|
// Unlike Popover (which genuinely can't resolve "auto" until it
|
|
60322
60898
|
// measures against a real anchor), resolvedAnimation is already fully
|
|
60323
60899
|
// known synchronously here — a dialog never needs to flip anything
|
|
60324
60900
|
// after measuring (see this file's top comment) — so there's no reason
|
|
60325
60901
|
// to withhold the attribute for the auto case the way Popover has to.
|
|
60326
60902
|
"navi-animation": resolvedAnimation,
|
|
60327
|
-
// Only meaningful for
|
|
60328
|
-
//
|
|
60903
|
+
// Only meaningful for a modal's own native ::backdrop (see this file's
|
|
60904
|
+
// CSS for the "capture" glass effect) — a
|
|
60329
60905
|
// pseudo-element can't carry its own attributes, so this has to live on
|
|
60330
60906
|
// the originating .navi_dialog element instead, same reasoning as
|
|
60331
|
-
// navi-animation above. Harmless for
|
|
60332
|
-
//
|
|
60333
|
-
//
|
|
60907
|
+
// navi-animation above. Harmless for a local dialog too (its own real
|
|
60908
|
+
// backdrop element already gets the same attribute via backdropProps
|
|
60909
|
+
// above, which is what its own CSS actually keys off).
|
|
60334
60910
|
"data-pointer-interaction-outside": pointerInteractionOutsideEffect,
|
|
60335
|
-
// Only load-bearing for
|
|
60336
|
-
//
|
|
60337
|
-
//
|
|
60338
|
-
//
|
|
60911
|
+
// Only load-bearing for a modal's own native ::backdrop, same "a
|
|
60912
|
+
// pseudo-element can't carry attributes" reasoning as the prop just above
|
|
60913
|
+
// (and harmless for a local dialog, whose real backdrop element gets it
|
|
60914
|
+
// via backdropProps).
|
|
60339
60915
|
"data-backdrop-variant": backdropVariant,
|
|
60340
60916
|
// Read by the native ::backdrop, which inherits them from here (see this
|
|
60341
|
-
// file's CSS) —
|
|
60917
|
+
// file's CSS) — a local dialog's own backdrop element gets them via
|
|
60342
60918
|
// backdropProps above.
|
|
60343
60919
|
backdropColor,
|
|
60344
60920
|
backdropFilter,
|
|
@@ -60367,6 +60943,12 @@ const useDialogProps = props => {
|
|
|
60367
60943
|
// absolutely placed by its own code — so it says so once, here (group.jsx
|
|
60368
60944
|
// reads this attribute to tell a member from anything else on its line).
|
|
60369
60945
|
"navi-out-of-flow": "",
|
|
60946
|
+
// The top layer without a wall: showPopover() is what puts it there, and
|
|
60947
|
+
// it only accepts an element that declares itself a popover. "manual" so
|
|
60948
|
+
// the browser's own light dismiss stays out of it — what an outside press
|
|
60949
|
+
// does is pointerInteractionOutsideEffect's answer, given by the
|
|
60950
|
+
// document-level listener in openEffect above.
|
|
60951
|
+
"popover": isTopLayerPopover ? "manual" : undefined,
|
|
60370
60952
|
"baseClassName": "navi_dialog",
|
|
60371
60953
|
"pseudoClasses": DIALOG_PSEUDO_CLASSES,
|
|
60372
60954
|
// Distinguishes the two renderers for the CSS above (position: fixed
|
|
@@ -60416,8 +60998,8 @@ const useDialogProps = props => {
|
|
|
60416
60998
|
},
|
|
60417
60999
|
"onCancel": e => {
|
|
60418
61000
|
// The dialog's own "cancel" (Escape on a modal showModal() dialog —
|
|
60419
|
-
// the
|
|
60420
|
-
// above) fires on the dialog element itself. But a child
|
|
61001
|
+
// the Escape handling for the other two shows lives in
|
|
61002
|
+
// onKeyDownShortcuts above) fires on the dialog element itself. But a child
|
|
60421
61003
|
// <input type="file"> also fires a BUBBLING "cancel" when the user
|
|
60422
61004
|
// dismisses the file chooser (per the HTML spec), and it lands here
|
|
60423
61005
|
// too; only the dialog's own cancel means "close".
|
|
@@ -60431,12 +61013,12 @@ const useDialogProps = props => {
|
|
|
60431
61013
|
children
|
|
60432
61014
|
});
|
|
60433
61015
|
|
|
60434
|
-
// Outside-click handling for
|
|
60435
|
-
//
|
|
60436
|
-
//
|
|
60437
|
-
//
|
|
60438
|
-
//
|
|
60439
|
-
if (!
|
|
61016
|
+
// Outside-click handling for the wall we render ourselves. A modal's is a
|
|
61017
|
+
// plain document-level listener instead, set up in openEffect above (see
|
|
61018
|
+
// this file's top comment for why: neither a real backdrop element nor
|
|
61019
|
+
// dialogEl's own mousedown reliably fires for a native ::backdrop click),
|
|
61020
|
+
// and so is a wall-less dialog's.
|
|
61021
|
+
if (!isTopLayer && backdrop) {
|
|
60440
61022
|
backdropProps.onMouseDown = mouseDownEvent => {
|
|
60441
61023
|
if (mouseDownEvent.button !== 0) {
|
|
60442
61024
|
return;
|
|
@@ -60446,7 +61028,7 @@ const useDialogProps = props => {
|
|
|
60446
61028
|
if (openedDuringThisPress(openController)) {
|
|
60447
61029
|
return;
|
|
60448
61030
|
}
|
|
60449
|
-
// See the
|
|
61031
|
+
// See the document-level listener in openEffect above: a click inside
|
|
60450
61032
|
// another popup is a click on what is in front, not an outside click.
|
|
60451
61033
|
const dialogEl = ref.current;
|
|
60452
61034
|
const popupUnderPointer = mouseDownEvent.target.closest?.(`[navi-control="dialog"], [navi-control="popover"]`);
|
|
@@ -60463,7 +61045,7 @@ const useDialogProps = props => {
|
|
|
60463
61045
|
// the same behavior for Dialog.
|
|
60464
61046
|
};
|
|
60465
61047
|
}
|
|
60466
|
-
return [
|
|
61048
|
+
return [!isTopLayer && backdrop ? backdropProps : null, contentProps];
|
|
60467
61049
|
};
|
|
60468
61050
|
const DIALOG_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
|
|
60469
61051
|
|
|
@@ -61940,12 +62522,12 @@ const usePopoverProps = props => {
|
|
|
61940
62522
|
// commit (a plain prop is, no effect needed) — see
|
|
61941
62523
|
// use_displayed_layout_effect.js's own comments for why a descendant
|
|
61942
62524
|
// relying on aria-expanded's mere presence needs that.
|
|
61943
|
-
"aria-expanded": openController.
|
|
62525
|
+
"aria-expanded": openController.openedInDom ? "true" : "false",
|
|
61944
62526
|
// Read fresh on every render (not frozen at mount), so it stays
|
|
61945
62527
|
// correct even across a re-render that happens to occur while open —
|
|
61946
62528
|
// see contentProps' own identical prop just below for the full
|
|
61947
62529
|
// reasoning (kept once, not repeated here).
|
|
61948
|
-
"navi-hidden": openController.
|
|
62530
|
+
"navi-hidden": openController.openedInDom ? undefined : "",
|
|
61949
62531
|
"styleCSSVars": POPUP_STYLE_CSS_VARS,
|
|
61950
62532
|
"animationDuration": rest.animationDuration,
|
|
61951
62533
|
"data-pointer-interaction-outside": pointerInteractionOutsideEffect,
|
|
@@ -61994,7 +62576,7 @@ const usePopoverProps = props => {
|
|
|
61994
62576
|
"navi-animation": isAutoAnimation ? undefined : animation,
|
|
61995
62577
|
// See backdropProps' own identical prop above for the full reasoning
|
|
61996
62578
|
// (kept once, not repeated here).
|
|
61997
|
-
"aria-expanded": openController.
|
|
62579
|
+
"aria-expanded": openController.openedInDom ? "true" : "false",
|
|
61998
62580
|
// Only load-bearing for the custom renderer (a plain div has no native
|
|
61999
62581
|
// starting-hidden default) — present from this very first render so
|
|
62000
62582
|
// there's no gap for the browser to ever paint it visible before
|
|
@@ -62004,7 +62586,7 @@ const usePopoverProps = props => {
|
|
|
62004
62586
|
// the last render, so as long as this always reflects the *current*
|
|
62005
62587
|
// truth, it never fights the imperative
|
|
62006
62588
|
// removeAttribute/setAttribute openEffect/close do directly.
|
|
62007
|
-
"navi-hidden": openController.
|
|
62589
|
+
"navi-hidden": openController.openedInDom ? undefined : "",
|
|
62008
62590
|
"styleCSSVars": POPUP_STYLE_CSS_VARS,
|
|
62009
62591
|
...rest,
|
|
62010
62592
|
...autoFocusProps,
|
|
@@ -62188,22 +62770,18 @@ const css$C = /* css */`@layer navi {
|
|
|
62188
62770
|
* — without this, the exact same `<Popup>` usage would behave
|
|
62189
62771
|
* differently (close-on-outside-click or not) purely based on which mode
|
|
62190
62772
|
* the screen-size check happens to pick, which defeats the point of
|
|
62191
|
-
* having one shared API in the first place. Note this
|
|
62192
|
-
*
|
|
62193
|
-
*
|
|
62194
|
-
*
|
|
62195
|
-
* `dialog.jsx`'s own doc) — a popover's fully passive, click-through
|
|
62196
|
-
* backdrop has no dialog-mode equivalent. Whatever content `Popup` opens
|
|
62197
|
-
* is unavoidably *more* intrusive once it switches to dialog mode than
|
|
62198
|
-
* the exact same usage would be as a popover — worth keeping in mind for
|
|
62199
|
-
* anything that relies on `Popup` and can end up on a small screen.
|
|
62773
|
+
* having one shared API in the first place. Note this only says what a
|
|
62774
|
+
* press outside *does*; whether it reaches the page at all is `backdrop`
|
|
62775
|
+
* below, and `"none"`/`"capture"` describe a wall either way — the popup
|
|
62776
|
+
* absorbs the press without closing, dimmed or not.
|
|
62200
62777
|
* @param {boolean} [props.backdrop] - Whether anything is laid between the
|
|
62201
62778
|
* popup and the page at all: `false` lets a press outside both dismiss the
|
|
62202
|
-
* popup and reach whatever it landed on, in one gesture.
|
|
62203
|
-
*
|
|
62204
|
-
*
|
|
62205
|
-
*
|
|
62206
|
-
*
|
|
62779
|
+
* popup and reach whatever it landed on, in one gesture. Forwarded as-is and
|
|
62780
|
+
* honoured in either mode — a wall-less `Dialog` is shown through the
|
|
62781
|
+
* Popover API rather than `showModal()` — so which mode the screen-size
|
|
62782
|
+
* resolution picks says nothing about whether the page behind stays live.
|
|
62783
|
+
* It is also what makes a sheet docked to the bottom of a phone's screen
|
|
62784
|
+
* (`dockedOnSmallTouchScreen`) non-modal.
|
|
62207
62785
|
* @param {"auto"|"discrete"|"invisible"} [props.backdropVariant] - Forwarded
|
|
62208
62786
|
* as-is to whichever component renders (both understand it identically):
|
|
62209
62787
|
* how visible the backdrop is, independently of what an outside click
|
|
@@ -62274,9 +62852,6 @@ const Popup = props => {
|
|
|
62274
62852
|
// exact same <Popup> usage behaves identically regardless of which
|
|
62275
62853
|
// mode the automatic screen-size resolution happens to pick.
|
|
62276
62854
|
pointerInteractionOutsideEffect = "close",
|
|
62277
|
-
// Read here rather than left in ...rest so the dialog branch can drop it:
|
|
62278
|
-
// a top-layer dialog is modal and has no press left to let through, and
|
|
62279
|
-
// warns when asked (see Dialog's own backdrop prop).
|
|
62280
62855
|
backdrop,
|
|
62281
62856
|
// Popover-only (see this component's own doc) — destructured out so
|
|
62282
62857
|
// they're never part of ...rest, and therefore never forwarded to
|
|
@@ -62311,7 +62886,7 @@ const Popup = props => {
|
|
|
62311
62886
|
sizeFromAnchor: sizeFromAnchor,
|
|
62312
62887
|
maxWidth: maxWidth,
|
|
62313
62888
|
pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
|
|
62314
|
-
backdrop:
|
|
62889
|
+
backdrop: backdrop,
|
|
62315
62890
|
className: withPropsClassName("navi_popup", className),
|
|
62316
62891
|
expand: expand,
|
|
62317
62892
|
expandX: expandX,
|
|
@@ -62408,6 +62983,10 @@ const css$B = /* css */`.navi_picker {
|
|
|
62408
62983
|
}
|
|
62409
62984
|
}
|
|
62410
62985
|
}
|
|
62986
|
+
|
|
62987
|
+
&[data-dialog-size-from-anchor] .navi_dialog {
|
|
62988
|
+
--dialog-max-width: var(--picker-dialog-max-width, var(--anchor-width));
|
|
62989
|
+
}
|
|
62411
62990
|
}
|
|
62412
62991
|
|
|
62413
62992
|
&[data-popup-width-fit-content] {
|
|
@@ -63056,7 +63635,7 @@ const PickerCustom = props => {
|
|
|
63056
63635
|
// because that is where such a box says what it travels by — and
|
|
63057
63636
|
// asked at every press, since what the picker sits in is not the
|
|
63058
63637
|
// picker's to know at mount.
|
|
63059
|
-
if (!opensOnPress || interactionsDispute || isPressDisputedByDrag(e.target)) {
|
|
63638
|
+
if (!opensOnPress || interactionsDispute || isPressDisputedByDrag(e.target) || isPressDisputedByHold(ref.current)) {
|
|
63060
63639
|
return null;
|
|
63061
63640
|
}
|
|
63062
63641
|
return {
|
|
@@ -63106,6 +63685,13 @@ const PickerCustom = props => {
|
|
|
63106
63685
|
mode: mode
|
|
63107
63686
|
});
|
|
63108
63687
|
};
|
|
63688
|
+
|
|
63689
|
+
// A hold declared on something AROUND the picker (a card opened by
|
|
63690
|
+
// `openOn="longpress"`, holding this one in its drawing): the finger going
|
|
63691
|
+
// down may be the start of that hold, so this picker opens on the click — which
|
|
63692
|
+
// the hold, if it completes, swallows — rather than on the press. The picker's
|
|
63693
|
+
// own hold (its `openOn`) is not "around" it, and has already stepped back.
|
|
63694
|
+
const isPressDisputedByHold = pickerEl => Boolean(pickerEl?.parentElement?.closest(`[${LONGPRESS_ATTRIBUTE}]`));
|
|
63109
63695
|
const getPickerInput = pickerEl => {
|
|
63110
63696
|
return pickerEl.querySelector(".navi_picker_input");
|
|
63111
63697
|
};
|
|
@@ -63179,6 +63765,7 @@ const PickerContentInsidePopup = props => {
|
|
|
63179
63765
|
return jsx(Next, {
|
|
63180
63766
|
"aria-haspopup": isPopover ? "listbox" : "dialog",
|
|
63181
63767
|
"navi-popover-mode": isPopover ? popoverMode : undefined,
|
|
63768
|
+
"data-dialog-size-from-anchor": !isPopover && dialogSizeFromAnchor ? "" : undefined,
|
|
63182
63769
|
...rest,
|
|
63183
63770
|
// On popupProps already (see the picker's popup assembly); they mean
|
|
63184
63771
|
// nothing to the picker element.
|
|
@@ -63520,6 +64107,34 @@ const pickerUIIsNaviOwn = ui => {
|
|
|
63520
64107
|
return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
|
|
63521
64108
|
};
|
|
63522
64109
|
|
|
64110
|
+
/**
|
|
64111
|
+
* What the picker holds, read from inside its `ui` — for a drawing given as
|
|
64112
|
+
* an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
|
|
64113
|
+
* props to. `value` is the state the picker holds right now: the answer the
|
|
64114
|
+
* popup just closed on, before the server has said anything, and put back if
|
|
64115
|
+
* the action fails. `loading` is the run in flight; `interactive` is false
|
|
64116
|
+
* while the picker is disabled, read-only or busy.
|
|
64117
|
+
*
|
|
64118
|
+
* @returns {{ value: any, loading: boolean, interactive: boolean } | null}
|
|
64119
|
+
* null outside a picker.
|
|
64120
|
+
*/
|
|
64121
|
+
const usePickerState = () => {
|
|
64122
|
+
const context = useContext(PickerContext);
|
|
64123
|
+
if (!context) {
|
|
64124
|
+
return null;
|
|
64125
|
+
}
|
|
64126
|
+
const {
|
|
64127
|
+
value,
|
|
64128
|
+
loading,
|
|
64129
|
+
interactive
|
|
64130
|
+
} = context;
|
|
64131
|
+
return {
|
|
64132
|
+
value,
|
|
64133
|
+
loading,
|
|
64134
|
+
interactive
|
|
64135
|
+
};
|
|
64136
|
+
};
|
|
64137
|
+
|
|
63523
64138
|
const PickerNaviMinute = props => {
|
|
63524
64139
|
const Next = useNextResolver();
|
|
63525
64140
|
const {
|
|
@@ -71262,6 +71877,16 @@ const css$t = /* css */`@layer navi {
|
|
|
71262
71877
|
user-select: none;
|
|
71263
71878
|
}
|
|
71264
71879
|
|
|
71880
|
+
&[data-open-on] {
|
|
71881
|
+
& > .navi_picker_box > .navi_picker_input {
|
|
71882
|
+
pointer-events: none;
|
|
71883
|
+
}
|
|
71884
|
+
|
|
71885
|
+
& > .navi_picker_box > .navi_picker_value {
|
|
71886
|
+
pointer-events: auto;
|
|
71887
|
+
}
|
|
71888
|
+
}
|
|
71889
|
+
|
|
71265
71890
|
&[navi-ui-custom] {
|
|
71266
71891
|
& .navi_picker_input {
|
|
71267
71892
|
top: calc(-1 * (var(--picker-border-width) + var(--x-picker-press-padding-top)));
|
|
@@ -71590,12 +72215,15 @@ const PickerButton = props => {
|
|
|
71590
72215
|
// What the picker knows about itself, for the pieces it does not place: the
|
|
71591
72216
|
// drawings of its value (Picker.UI.*), and the affordances a caller may put
|
|
71592
72217
|
// in their own `ui` (Picker.Clear) as much as the ones it puts in its slot.
|
|
72218
|
+
// `ui={MyCard}`: a component the picker renders itself, told what it holds.
|
|
72219
|
+
const UIComponent = typeof ui === "function" ? ui : null;
|
|
71593
72220
|
const pickerContext = {
|
|
71594
72221
|
value,
|
|
71595
72222
|
placeholder,
|
|
71596
72223
|
maxLines,
|
|
71597
72224
|
id: inputProps.id,
|
|
71598
72225
|
interactive,
|
|
72226
|
+
loading,
|
|
71599
72227
|
clearConfirm
|
|
71600
72228
|
};
|
|
71601
72229
|
return (
|
|
@@ -71786,7 +72414,17 @@ const PickerButton = props => {
|
|
|
71786
72414
|
size: rightSlotIconSize,
|
|
71787
72415
|
lineOverflow: "allow",
|
|
71788
72416
|
children: rightSlotIcon === undefined ? jsx(ChevronDownSvg$1, {}) : rightSlotIcon
|
|
71789
|
-
}) : jsx(PickerDefaultUI, {}) :
|
|
72417
|
+
}) : jsx(PickerDefaultUI, {}) : UIComponent ?
|
|
72418
|
+
// Told what the picker HOLDS — the value it just closed
|
|
72419
|
+
// on, before the server has said anything — so a façade
|
|
72420
|
+
// shows the answer at once, and the wait with it. The
|
|
72421
|
+
// same state usePickerState() hands an element (see the
|
|
72422
|
+
// `ui` doc).
|
|
72423
|
+
jsx(UIComponent, {
|
|
72424
|
+
value: value,
|
|
72425
|
+
loading: loading,
|
|
72426
|
+
interactive: interactive
|
|
72427
|
+
}) : ui
|
|
71790
72428
|
})
|
|
71791
72429
|
})
|
|
71792
72430
|
}), hasRightSlot ? jsx("span", {
|
|
@@ -72175,7 +72813,7 @@ const PickerFirstResolver = props => {
|
|
|
72175
72813
|
* defaultValue?: any,
|
|
72176
72814
|
* name?: string,
|
|
72177
72815
|
* placeholder?: import("ignore:preact").ComponentChildren,
|
|
72178
|
-
* ui?: import("ignore:preact").ComponentChildren | "default",
|
|
72816
|
+
* ui?: import("ignore:preact").ComponentChildren | "default" | import("ignore:preact").ComponentType<{ value: any, loading: boolean, interactive: boolean }>,
|
|
72179
72817
|
* required?: boolean,
|
|
72180
72818
|
* min?: Date | string | number,
|
|
72181
72819
|
* max?: Date | string | number,
|
|
@@ -72259,17 +72897,22 @@ const PickerFirstResolver = props => {
|
|
|
72259
72897
|
* @param {"press"|"longpress"|"contextmenu"|string|string[]} [openOn="press"]
|
|
72260
72898
|
* What opens the popup. The press, by default — the way a native select
|
|
72261
72899
|
* opens. Any other name is an interaction navi detects (see
|
|
72262
|
-
* docs/interactions.md: `"longpress"`, `"contextmenu"`, `"
|
|
72900
|
+
* docs/interactions.md: `"longpress"`, `"contextmenu"`, `"double_click"`…), or
|
|
72263
72901
|
* a list of them — `["longpress", "contextmenu"]` is the hold on a phone and
|
|
72264
72902
|
* the right click at a desk. The picker declares it on itself, so it is
|
|
72265
72903
|
* arbitrated like any other gesture: a swipe on the same card takes the
|
|
72266
72904
|
* press a hold would have taken. A tap then opens nothing — the press stays
|
|
72267
|
-
* free for what the `ui` holds
|
|
72268
|
-
*
|
|
72269
|
-
*
|
|
72905
|
+
* free for what the `ui` holds: a link in the card navigates, a button in it
|
|
72906
|
+
* presses, a picker in it opens on its own click, with nothing to declare
|
|
72907
|
+
* (`selfInteractions` is for a drawing that DOES open on the press). That
|
|
72908
|
+
* is what lets a whole card be a picker without every touch on it opening
|
|
72909
|
+
* the sheet — and the keyboard keeps its ways in (Enter, Space, the
|
|
72910
|
+
* arrows). A hold declared INSIDE the card (`interactions={{ longpress }}`
|
|
72911
|
+
* on something it holds) answers before this one: the nearer hold takes the
|
|
72912
|
+
* press, the way a click is the innermost target's. Being a gesture, the open goes through
|
|
72270
72913
|
* the interaction gate as one: a `readOnly` picker refuses it and says so
|
|
72271
72914
|
* where the finger is, whatever `openWhileReadOnly` says.
|
|
72272
|
-
* @param {import("ignore:preact").ComponentChildren | "default"} [ui] What the
|
|
72915
|
+
* @param {import("ignore:preact").ComponentChildren | "default" | import("ignore:preact").ComponentType} [ui] What the
|
|
72273
72916
|
* trigger draws in place of the value's default rendering (a date, a list
|
|
72274
72917
|
* joined by commas…) — and then all it draws: `placeholder` is not shown
|
|
72275
72918
|
* next to it, and an empty value is not greyed as a placeholder either: what
|
|
@@ -72298,6 +72941,20 @@ const PickerFirstResolver = props => {
|
|
|
72298
72941
|
* beside it, out of the flow so the rest keeps its width — is drawn and
|
|
72299
72942
|
* opens nothing; `pressPadding` is how far the press area follows it out
|
|
72300
72943
|
* there.
|
|
72944
|
+
*
|
|
72945
|
+
* A drawing that follows what the picker HOLDS — the value the popup just
|
|
72946
|
+
* closed on, before the server has answered, the same state navi's own
|
|
72947
|
+
* default `ui` reads, rolled back if the action fails (`resetOnError`) —
|
|
72948
|
+
* reads it one of two ways. As a component, `ui={MyCard}`: the picker
|
|
72949
|
+
* renders it and hands it `value`, `loading` and `interactive` as props.
|
|
72950
|
+
* As an element, `ui={<MyCard game={game} />}`, with `usePickerState()`
|
|
72951
|
+
* inside it returning the same three — for a drawing that needs props of
|
|
72952
|
+
* its own as well. Either way a card shows the score the moment the sheet
|
|
72953
|
+
* leaves and takes it back on a refusal, with nothing held by the caller.
|
|
72954
|
+
* `loading` is the wait it wears meanwhile: navi draws its loading outline
|
|
72955
|
+
* on every trigger, but on a card-sized `variant="bare"` a two-pixel run
|
|
72956
|
+
* around the box says nothing — a big surface draws its own waiting state
|
|
72957
|
+
* from that flag, or from `[data-loading]` on the picker root in css.
|
|
72301
72958
|
* @param {boolean} [readOnly] Nothing in this picker can be changed — and it
|
|
72302
72959
|
* still opens, so what is in the popup can be read: everything in there is
|
|
72303
72960
|
* held read-only in turn, each control greying out and saying why on its own.
|
|
@@ -72313,7 +72970,10 @@ const PickerFirstResolver = props => {
|
|
|
72313
72970
|
* refuse the open as well, for a popup with nothing to read: a form of
|
|
72314
72971
|
* controls to fill in, a menu of gestures. Opening then only shows what is
|
|
72315
72972
|
* refused, so the picker says why on the trigger instead — where the
|
|
72316
|
-
* interaction happened.
|
|
72973
|
+
* interaction happened. A busy picker follows the same rule: it still
|
|
72974
|
+
* opens to be read while its action runs, and with `false` the press is
|
|
72975
|
+
* refused with the wait as its reason — what a card that is syncing wants
|
|
72976
|
+
* for the sheet that would write it again.
|
|
72317
72977
|
* @param {boolean|string} [error] Something went wrong around this picker (its
|
|
72318
72978
|
* content failed to load, its value could not be resolved…). Shown as a
|
|
72319
72979
|
* callout on the trigger, open or closed — the caller has nothing to place.
|
|
@@ -72408,7 +73068,10 @@ const PickerFirstResolver = props => {
|
|
|
72408
73068
|
* placed anywhere else. No right slot comes with it: a cross belongs inside
|
|
72409
73069
|
* that drawing, at the place and the size the drawing decides, so it is
|
|
72410
73070
|
* `<Picker.Clear />` the `ui` holds rather than `clearable` the picker
|
|
72411
|
-
* takes.
|
|
73071
|
+
* takes. What navi still draws on that box — the focus ring, the loading
|
|
73072
|
+
* outline — follows the picker's `borderRadius`, which stays the control's
|
|
73073
|
+
* unless told: a drawing with corners of its own states them on the picker
|
|
73074
|
+
* as well. `"headless"` draws nothing either, and is not the `ui`'s box but
|
|
72412
73075
|
* its parent's: it stretches to whatever positioned element holds it, for a
|
|
72413
73076
|
* picker put inside the thing that opens it (a button, a row) so that the
|
|
72414
73077
|
* popup hangs off that whole element. A picker opened from something it does
|
|
@@ -72499,16 +73162,18 @@ const PickerFirstResolver = props => {
|
|
|
72499
73162
|
* whose `ui` is a whole piece of the page wants: the popup is the browser
|
|
72500
73163
|
* morphing the trigger's box into what carries `data-grow` inside the popup
|
|
72501
73164
|
* (the popup itself when nothing does), and back on close. The trigger IS
|
|
72502
|
-
* the anchor, so nothing has to be named for it —
|
|
72503
|
-
* for a card that must keep its width on the way,
|
|
72504
|
-
*
|
|
73165
|
+
* the anchor, so nothing has to be named for it — `dialogSizeFromAnchor`
|
|
73166
|
+
* for a card that must keep its width on the way, left out for a drawing
|
|
73167
|
+
* that opens precisely to get bigger.
|
|
72505
73168
|
* @param {string} [animationDuration] The popup's own (`--popup-animation-duration`).
|
|
72506
|
-
* @param {boolean} [dialogSizeFromAnchor] Dialog mode: the dialog
|
|
72507
|
-
* trigger
|
|
72508
|
-
*
|
|
72509
|
-
*
|
|
72510
|
-
*
|
|
72511
|
-
*
|
|
73169
|
+
* @param {boolean} [dialogSizeFromAnchor] Dialog mode: the dialog is as wide
|
|
73170
|
+
* as the trigger — its box as a floor (Dialog's own `sizeFromAnchor`) and
|
|
73171
|
+
* as a ceiling, unless a `dialogMaxWidth` says otherwise. What keeps a card
|
|
73172
|
+
* its own width once lifted out of the page (`animation="growing"`, with
|
|
73173
|
+
* `data-grow` on the card inside the popup): it moves, and nothing else
|
|
73174
|
+
* about it changes. The ceiling is set here rather than through
|
|
73175
|
+
* `dialogMaxWidth="var(--anchor-width)"` because that variable lives on the
|
|
73176
|
+
* dialog element, and a prop on the picker is resolved on the picker.
|
|
72512
73177
|
* @param {"cancel"|"close"} [escapeEffect="cancel"] What Escape does to an open
|
|
72513
73178
|
* picker. "cancel" puts back the value the picker had at open, and a dialog
|
|
72514
73179
|
* picker also goes back in history — so anything written to the url while it
|
|
@@ -72536,8 +73201,10 @@ const PickerFirstResolver = props => {
|
|
|
72536
73201
|
* and the page at all. `false` lets a press outside both close the popup and
|
|
72537
73202
|
* reach whatever it landed on, in one gesture — for a picker opened over a
|
|
72538
73203
|
* page that stays as pressable as it looks (a plan, a map, a canvas), where a
|
|
72539
|
-
* wall would spend the first press on dismissing.
|
|
72540
|
-
*
|
|
73204
|
+
* wall would spend the first press on dismissing. Honoured in both modes: a
|
|
73205
|
+
* dialog with no wall is shown through the Popover API rather than
|
|
73206
|
+
* `showModal()`, so a picker docked as a bottom sheet on a phone still
|
|
73207
|
+
* leaves the plan behind it live.
|
|
72541
73208
|
* @param {"auto"|"discrete"|"invisible"} [backdropVariant="auto"] How visible the
|
|
72542
73209
|
* popup's backdrop is, independently of what a click outside does: `"auto"`
|
|
72543
73210
|
* is the paint `pointerInteractionOutsideEffect` implies, `"discrete"` a
|
|
@@ -84284,11 +84951,11 @@ const css = /* css */`.navi_side_panel {
|
|
|
84284
84951
|
* @param {boolean} [props.closeOnClickOutside=false] - `false` (default):
|
|
84285
84952
|
* maps to `pointerInteractionOutsideEffect="none"` — in popover mode, no
|
|
84286
84953
|
* backdrop at all, outside clicks pass straight through; in dialog mode,
|
|
84287
|
-
* the outside click is
|
|
84288
|
-
*
|
|
84289
|
-
*
|
|
84290
|
-
* panel on an outside click instead, and also enables trapping
|
|
84291
|
-
* navigation inside the panel (`focusCapture`) — closing on outside
|
|
84954
|
+
* the outside click is absorbed by the panel's own wall but changes
|
|
84955
|
+
* nothing. Pass `backdrop={false}` (forwarded to `Popup`) for a panel with
|
|
84956
|
+
* no wall in either mode, whose outside presses reach the page. `true`:
|
|
84957
|
+
* closes the panel on an outside click instead, and also enables trapping
|
|
84958
|
+
* Tab navigation inside the panel (`focusCapture`) — closing on outside
|
|
84292
84959
|
* interaction only makes sense paired with not letting focus silently
|
|
84293
84960
|
* leave the panel first.
|
|
84294
84961
|
* @param {boolean} [props.swipeToClose=true] - Pushing the panel back
|
|
@@ -84298,12 +84965,11 @@ const css = /* css */`.navi_side_panel {
|
|
|
84298
84965
|
* deliberately. See `swipe_to_close.js` for when the gesture is claimed.
|
|
84299
84966
|
* @param {"dialog"|"popover"} [props.mode] - Forwarded to `Popup` — forces
|
|
84300
84967
|
* one underlying renderer instead of its automatic screen-size
|
|
84301
|
-
* resolution. Note that if `Popup` ends up in dialog mode (small screen,
|
|
84302
|
-
*
|
|
84303
|
-
* `closeOnClickOutside`/`pointerInteractionOutsideEffect
|
|
84304
|
-
*
|
|
84305
|
-
*
|
|
84306
|
-
* popover's fully passive, click-through backdrop.
|
|
84968
|
+
* resolution. Note that if `Popup` ends up in dialog mode (small screen, or
|
|
84969
|
+
* forced here), the panel is modal unless it says `backdrop={false}`:
|
|
84970
|
+
* `closeOnClickOutside`/`pointerInteractionOutsideEffect` only say what a
|
|
84971
|
+
* press on the wall does, not whether there is one (see `dialog.jsx`'s own
|
|
84972
|
+
* doc).
|
|
84307
84973
|
* @param {import("ignore:preact").ComponentChildren} props.children - No built-in
|
|
84308
84974
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
84309
84975
|
* plain `<Button command="--navi-close">`), use `SidePanel.Head`'s own
|
|
@@ -84608,5 +85274,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
84608
85274
|
})
|
|
84609
85275
|
});
|
|
84610
85276
|
|
|
84611
|
-
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, usePopupMode, useRouteStatus, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useTransitionCover, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
85277
|
+
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 };
|
|
84612
85278
|
//# sourceMappingURL=jsenv_navi.js.map
|