@jsenv/navi 0.29.74 → 0.29.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +325 -53
- package/dist/jsenv_navi.js.map +14 -10
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
6
|
export { coarsePointerSignal, disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
|
|
7
|
-
import { elementIsFocusable, createPubSub, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createIterableWeakSet, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, mergeTwoStyles, normalizeStyles, resolveCSSSize, hasCSSSizeUnit, resolveOklchLightness, contrastColor, closestOpenableAncestor, isAncestorOpen, observeAncestorOpenState, getAncestorOpenType, clickIsSuppressed, scrollRoomTowards, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, findBefore, findAfter, initFocusGroup,
|
|
7
|
+
import { elementIsFocusable, createPubSub, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createIterableWeakSet, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, mergeTwoStyles, normalizeStyles, resolveCSSSize, hasCSSSizeUnit, resolveOklchLightness, contrastColor, closestOpenableAncestor, isAncestorOpen, observeAncestorOpenState, getAncestorOpenType, clickIsSuppressed, isTouchDrivenEvent, scrollIntoViewScoped, scrollRoomTowards, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, findBefore, findAfter, initFocusGroup, getScrollContainer, canScroll, measureWidestChildRow, performTabNavigation, wheelGestureIsTakenFrom, releaseWheelGesture, claimWheelGesture, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, stringifyStyle as stringifyStyle$1 } from "@jsenv/dom";
|
|
8
8
|
export { clickIsSuppressed, contrastColor, findEvent, startDragTo } from "@jsenv/dom";
|
|
9
9
|
import { signal, computed, effect, batch, untracked, useSignal } from "@preact/signals";
|
|
10
10
|
import { createContext, isValidElement, h, Fragment, render, toChildArray, options, cloneElement } from "preact";
|
|
@@ -19345,10 +19345,48 @@ import.meta.css = [/* css */`
|
|
|
19345
19345
|
outline-offset: calc(-1 * var(--navi-focus-outline-width));
|
|
19346
19346
|
overflow: auto;
|
|
19347
19347
|
|
|
19348
|
+
/* The same reading as the header's corners above, on all four: a body
|
|
19349
|
+
follows the corners of the box it is drawn in — which is also what
|
|
19350
|
+
it clips its content to, the overflow just above. */
|
|
19351
|
+
border-top-left-radius: inherit;
|
|
19352
|
+
border-top-right-radius: inherit;
|
|
19353
|
+
border-bottom-right-radius: inherit;
|
|
19354
|
+
border-bottom-left-radius: inherit;
|
|
19355
|
+
|
|
19348
19356
|
&:focus-visible {
|
|
19349
19357
|
outline-style: solid;
|
|
19350
19358
|
}
|
|
19351
19359
|
}
|
|
19360
|
+
|
|
19361
|
+
/* A corner a header or a footer covers is not the body's to follow:
|
|
19362
|
+
what the body meets there is their flat separator line, not a curve,
|
|
19363
|
+
and a radius against it shows the box through the gap it opens. */
|
|
19364
|
+
> [data-header] ~ [data-body] {
|
|
19365
|
+
border-top-left-radius: 0;
|
|
19366
|
+
border-top-right-radius: 0;
|
|
19367
|
+
}
|
|
19368
|
+
> [data-body]:has(~ [data-footer]) {
|
|
19369
|
+
border-bottom-right-radius: 0;
|
|
19370
|
+
border-bottom-left-radius: 0;
|
|
19371
|
+
}
|
|
19372
|
+
|
|
19373
|
+
/* A body with no padding of its own holds content running edge to edge:
|
|
19374
|
+
whatever sits at one of its ends is drawn ON the corner the body just
|
|
19375
|
+
resolved, so a radius of its own there carves a notch out of it. The
|
|
19376
|
+
body already clips to that corner, which makes "none" the right radius
|
|
19377
|
+
for what lands on it — square, and the body draws the curve. The ask
|
|
19378
|
+
travels down as a corner claim (see group.jsx) so a navi control
|
|
19379
|
+
answers it wherever it sits inside. */
|
|
19380
|
+
> [data-body][data-body-flush] {
|
|
19381
|
+
> :first-child {
|
|
19382
|
+
--x-corner-top-left-radius: 0;
|
|
19383
|
+
--x-corner-top-right-radius: 0;
|
|
19384
|
+
}
|
|
19385
|
+
> :last-child {
|
|
19386
|
+
--x-corner-bottom-right-radius: 0;
|
|
19387
|
+
--x-corner-bottom-left-radius: 0;
|
|
19388
|
+
}
|
|
19389
|
+
}
|
|
19352
19390
|
}
|
|
19353
19391
|
}
|
|
19354
19392
|
|
|
@@ -19551,6 +19589,11 @@ const Box = props => {
|
|
|
19551
19589
|
}
|
|
19552
19590
|
if (body) {
|
|
19553
19591
|
rest["data-body"] = "";
|
|
19592
|
+
// Padding is what decides whether the content reaches the body's own
|
|
19593
|
+
// corners — see the corner claims in this file's CSS.
|
|
19594
|
+
if (!PADDING_PROP_NAMES.some(name => isNonZeroSpacing(rest[name]))) {
|
|
19595
|
+
rest["data-body-flush"] = "";
|
|
19596
|
+
}
|
|
19554
19597
|
}
|
|
19555
19598
|
const defaultDisplay = getDefaultDisplay(TagName);
|
|
19556
19599
|
// Read the parent flow early so we can use it when display="inherit" is requested.
|
|
@@ -20015,6 +20058,16 @@ const shouldInjectSeparatorBetween = (left, right) => {
|
|
|
20015
20058
|
}
|
|
20016
20059
|
return true;
|
|
20017
20060
|
};
|
|
20061
|
+
const PADDING_PROP_NAMES = ["padding", "paddingX", "paddingY", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft"];
|
|
20062
|
+
const isNonZeroSpacing = value => {
|
|
20063
|
+
if (value === undefined || value === null || value === false) {
|
|
20064
|
+
return false;
|
|
20065
|
+
}
|
|
20066
|
+
if (value === 0 || value === "0" || value === "none") {
|
|
20067
|
+
return false;
|
|
20068
|
+
}
|
|
20069
|
+
return true;
|
|
20070
|
+
};
|
|
20018
20071
|
|
|
20019
20072
|
const useDebounceTrue = (value, delay = 300) => {
|
|
20020
20073
|
const [debouncedTrue, setDebouncedTrue] = useState(false);
|
|
@@ -28546,7 +28599,21 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
|
|
|
28546
28599
|
focusedElement,
|
|
28547
28600
|
focusVisible,
|
|
28548
28601
|
|
|
28549
|
-
|
|
28602
|
+
/**
|
|
28603
|
+
* Moves the focus into `containerEl`, on the element the ladder above
|
|
28604
|
+
* picks.
|
|
28605
|
+
*
|
|
28606
|
+
* `getDelay(target)` — asked once the target is known, answers how many
|
|
28607
|
+
* milliseconds to wait before actually focusing it. The ladder is what
|
|
28608
|
+
* decides WHO gets the focus and it may only run once (it consumes the
|
|
28609
|
+
* autofocus-restore mark), so a caller with a policy about WHEN cannot
|
|
28610
|
+
* resolve the target itself to make up its mind: it is handed the answer
|
|
28611
|
+
* instead. Returns a cancel function when it did delay, so a container
|
|
28612
|
+
* closing before the delay is up takes back a focus it never gave;
|
|
28613
|
+
* undefined when it focused straight away and there is nothing to take
|
|
28614
|
+
* back.
|
|
28615
|
+
*/
|
|
28616
|
+
transferFocus: (transferEvent, containerEl, { getDelay } = {}) => {
|
|
28550
28617
|
let target;
|
|
28551
28618
|
let reason;
|
|
28552
28619
|
const lastFocused = clearAutofocusRestore(containerEl);
|
|
@@ -28574,24 +28641,39 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
|
|
|
28574
28641
|
}
|
|
28575
28642
|
}
|
|
28576
28643
|
if (!target) {
|
|
28577
|
-
return;
|
|
28644
|
+
return undefined;
|
|
28578
28645
|
}
|
|
28579
28646
|
// The modality speaks for the transfer, but an editable target outranks
|
|
28580
28647
|
// it: it draws its ring on any focus (see isMatchingFocusVisible), so
|
|
28581
28648
|
// the native :focus-visible is told the same.
|
|
28582
28649
|
const targetFocusVisible = focusVisible || isEditableTarget(target);
|
|
28650
|
+
const giveFocus = () => {
|
|
28651
|
+
debugFocus(
|
|
28652
|
+
transferEvent,
|
|
28653
|
+
`Moving focus to ${getElementSignature(target)}.focus({ preventScroll: true, focusVisible: ${targetFocusVisible} }) (reason: ${reason})`,
|
|
28654
|
+
);
|
|
28655
|
+
target.focus({
|
|
28656
|
+
preventScroll: true,
|
|
28657
|
+
focusVisible: targetFocusVisible,
|
|
28658
|
+
});
|
|
28659
|
+
if (target.hasAttribute("navi-autofocus-select")) {
|
|
28660
|
+
target.select();
|
|
28661
|
+
target.scrollLeft = 0;
|
|
28662
|
+
}
|
|
28663
|
+
};
|
|
28664
|
+
const delay = getDelay?.(target) || 0;
|
|
28665
|
+
if (!delay) {
|
|
28666
|
+
giveFocus();
|
|
28667
|
+
return undefined;
|
|
28668
|
+
}
|
|
28583
28669
|
debugFocus(
|
|
28584
28670
|
transferEvent,
|
|
28585
|
-
`
|
|
28671
|
+
`Delaying focus to ${getElementSignature(target)} by ${delay}ms`,
|
|
28586
28672
|
);
|
|
28587
|
-
|
|
28588
|
-
|
|
28589
|
-
|
|
28590
|
-
}
|
|
28591
|
-
if (target.hasAttribute("navi-autofocus-select")) {
|
|
28592
|
-
target.select();
|
|
28593
|
-
target.scrollLeft = 0;
|
|
28594
|
-
}
|
|
28673
|
+
const timeout = setTimeout(giveFocus, delay);
|
|
28674
|
+
return () => {
|
|
28675
|
+
clearTimeout(timeout);
|
|
28676
|
+
};
|
|
28595
28677
|
},
|
|
28596
28678
|
|
|
28597
28679
|
restoreFocus: (restoreEvent) => {
|
|
@@ -28633,6 +28715,21 @@ const getFocusedBeforeTransfer = (e) => {
|
|
|
28633
28715
|
return document.activeElement;
|
|
28634
28716
|
};
|
|
28635
28717
|
|
|
28718
|
+
// How long a popup waits before handing the focus to a field, when giving it
|
|
28719
|
+
// is what raises the on-screen keyboard.
|
|
28720
|
+
//
|
|
28721
|
+
// The focus is normally given as early as possible. But a popup places itself
|
|
28722
|
+
// against the viewport, and on a phone the keyboard takes a third of that
|
|
28723
|
+
// viewport away the moment a field receives focus — so the two landing in the
|
|
28724
|
+
// same tick means the popup is still arriving when the room under it changes,
|
|
28725
|
+
// and it re-places itself mid-entrance. Waiting lets it settle first, and the
|
|
28726
|
+
// keyboard then shrinks a box that has stopped moving.
|
|
28727
|
+
//
|
|
28728
|
+
// Long enough to outlast an entrance transition rather than merely reaching
|
|
28729
|
+
// the next frame: what has to be over is the popup MOVING, not one paint of
|
|
28730
|
+
// it.
|
|
28731
|
+
const FOCUS_DELAY_ON_KEYBOARD_MS = 250;
|
|
28732
|
+
|
|
28636
28733
|
/**
|
|
28637
28734
|
* Owns open/close decision-making for a popup (Dialog or Popover): guards
|
|
28638
28735
|
* against duplicate requests and notifies the popup owner's own reactions.
|
|
@@ -28871,8 +28968,32 @@ const createOpenController = (
|
|
|
28871
28968
|
// once mousedown.preventDefault() has kept focus from landing
|
|
28872
28969
|
// anywhere yet.
|
|
28873
28970
|
|
|
28874
|
-
|
|
28971
|
+
// Two conditions, and both are about THIS opening rather than about
|
|
28972
|
+
// the device:
|
|
28973
|
+
// - the interaction: only a finger raises a virtual keyboard, and a
|
|
28974
|
+
// hybrid tablet answers "coarse" to every device-level signal
|
|
28975
|
+
// whichever of its two inputs was just used — the open event still
|
|
28976
|
+
// remembers which one it was. An opening with no pointer in it at
|
|
28977
|
+
// all (a keyboard shortcut, defaultOpen, an app calling open()) is
|
|
28978
|
+
// not one either.
|
|
28979
|
+
// - the target: focusing a button raises nothing, so there is nothing
|
|
28980
|
+
// to wait for and the focus stays immediate. Only a field the
|
|
28981
|
+
// keyboard comes up for is worth delaying — which is why the
|
|
28982
|
+
// decision is taken on the resolved target, inside transferFocus.
|
|
28983
|
+
const openedByTouch = Boolean(
|
|
28984
|
+
findEvent(requestOpenEvent, isTouchDrivenEvent),
|
|
28985
|
+
);
|
|
28986
|
+
const cancelPendingFocus = focusTransfer.transferFocus(e, el, {
|
|
28987
|
+
getDelay: (target) =>
|
|
28988
|
+
openedByTouch && isEditableTarget(target)
|
|
28989
|
+
? FOCUS_DELAY_ON_KEYBOARD_MS
|
|
28990
|
+
: 0,
|
|
28991
|
+
});
|
|
28875
28992
|
return (closeEvent) => {
|
|
28993
|
+
// Closed before the delay was up: the focus was never given, so it
|
|
28994
|
+
// must not be given now — to a field inside a popup on its way out,
|
|
28995
|
+
// raising the keyboard as it goes.
|
|
28996
|
+
cancelPendingFocus?.();
|
|
28876
28997
|
markAutofocusRestoreOnClose(el, closeEvent, focusedAtClose);
|
|
28877
28998
|
const focusoutEvent = findEvent(closeEvent, "focusout");
|
|
28878
28999
|
if (focusoutEvent) {
|
|
@@ -29141,6 +29262,60 @@ const flushSyncRendering = (fn) => {
|
|
|
29141
29262
|
*/
|
|
29142
29263
|
|
|
29143
29264
|
|
|
29265
|
+
/**
|
|
29266
|
+
* Whether a visibleRectEffect delivery is one that can have taken height away
|
|
29267
|
+
* from a popup, and so pushed whatever holds focus out of sight:
|
|
29268
|
+
* - "resize": the window/visual viewport settled — which is also how the
|
|
29269
|
+
* on-screen keyboard arrives, overlay or not (window_size.js);
|
|
29270
|
+
* - ELEMENT_SIZE_CHANGE: the popup's own box measured different;
|
|
29271
|
+
* - "focusin": the focus-settled re-measure, for the room that changes with
|
|
29272
|
+
* nothing announcing it (subscribeFocusSettled in window_size.js).
|
|
29273
|
+
*
|
|
29274
|
+
* Everything else is a scroll of one kind or another, where nothing got
|
|
29275
|
+
* smaller and scrolling the focused element back would fight the very gesture
|
|
29276
|
+
* that fired it.
|
|
29277
|
+
*/
|
|
29278
|
+
const mayHaveHiddenFocus = (event) => {
|
|
29279
|
+
const type = event?.type;
|
|
29280
|
+
return (
|
|
29281
|
+
type === "resize" || type === ELEMENT_SIZE_CHANGE || type === "focusin"
|
|
29282
|
+
);
|
|
29283
|
+
};
|
|
29284
|
+
|
|
29285
|
+
/**
|
|
29286
|
+
* Scrolls whatever holds focus inside `popupEl` back into view, if the popup
|
|
29287
|
+
* getting shorter has pushed it out.
|
|
29288
|
+
*
|
|
29289
|
+
* The case this exists for: a field low in the scrolling body of a popup that
|
|
29290
|
+
* also has a footer (box.jsx — with a body, the body is the only thing that
|
|
29291
|
+
* scrolls and the footer is a sibling sitting right under it). Focusing the
|
|
29292
|
+
* field makes the browser scroll it into view, which it does against the
|
|
29293
|
+
* popup's height AT THAT MOMENT; the on-screen keyboard then opens and takes
|
|
29294
|
+
* that height away. The body shrinks, its scrollTop does not move, so the
|
|
29295
|
+
* content slides down relative to the shorter scrollport and the field ends up
|
|
29296
|
+
* past its bottom edge — visually, swallowed by the footer. The browser does
|
|
29297
|
+
* not redo a scroll-into-view it already answered, so this does.
|
|
29298
|
+
*
|
|
29299
|
+
* Scoped to the field's own scroll container (never the page): a popup traps
|
|
29300
|
+
* scrolling precisely so the document underneath cannot move, and a plain
|
|
29301
|
+
* scrollIntoView walks past a container whose scrollbar isn't visible — see
|
|
29302
|
+
* scrollIntoViewScoped's own doc.
|
|
29303
|
+
*
|
|
29304
|
+
* "nearest": the smallest scroll that makes it visible, and none at all when
|
|
29305
|
+
* it already is — so this is free to call on every resize, and never fights
|
|
29306
|
+
* where the user had scrolled to.
|
|
29307
|
+
*/
|
|
29308
|
+
const keepFocusedElementVisible = (popupEl) => {
|
|
29309
|
+
const { activeElement } = document;
|
|
29310
|
+
if (!activeElement || activeElement === popupEl) {
|
|
29311
|
+
return;
|
|
29312
|
+
}
|
|
29313
|
+
if (!popupEl.contains(activeElement)) {
|
|
29314
|
+
return;
|
|
29315
|
+
}
|
|
29316
|
+
scrollIntoViewScoped(activeElement, { block: "nearest" });
|
|
29317
|
+
};
|
|
29318
|
+
|
|
29144
29319
|
/**
|
|
29145
29320
|
* Calls `onSettled` once `el`'s current CSS transition is over — via
|
|
29146
29321
|
* `transitionend`, with a safety `setTimeout` fallback matching the longest
|
|
@@ -30990,6 +31165,11 @@ const useDialogProps = props => {
|
|
|
30990
31165
|
// handled generically by applyNewPosition itself (dispatches
|
|
30991
31166
|
// navi_position_change on every call) — nothing to do here.
|
|
30992
31167
|
};
|
|
31168
|
+
// Cleared here rather than on close, where the box is deliberately left
|
|
31169
|
+
// frozen at the size it was closing at (see the closing function's own
|
|
31170
|
+
// comment): this opening has its own content to be measured against, and
|
|
31171
|
+
// measuring it inside last time's box would answer with last time's size.
|
|
31172
|
+
unfreezeSize(dialogEl);
|
|
30993
31173
|
positionDialog();
|
|
30994
31174
|
if (sizing === "frozen") {
|
|
30995
31175
|
// After positionDialog: the caps it writes
|
|
@@ -31012,6 +31192,12 @@ const useDialogProps = props => {
|
|
|
31012
31192
|
event
|
|
31013
31193
|
}) => {
|
|
31014
31194
|
positionDialog(event);
|
|
31195
|
+
// Only for what can have taken height away from the dialog — a
|
|
31196
|
+
// scroll never does, and re-scrolling on one would fight the finger
|
|
31197
|
+
// that caused it. See keepFocusedElementVisible's own doc.
|
|
31198
|
+
if (mayHaveHiddenFocus(event)) {
|
|
31199
|
+
keepFocusedElementVisible(dialogEl);
|
|
31200
|
+
}
|
|
31015
31201
|
}, {
|
|
31016
31202
|
event: e,
|
|
31017
31203
|
skipElementResize: true
|
|
@@ -31064,6 +31250,12 @@ const useDialogProps = props => {
|
|
|
31064
31250
|
}
|
|
31065
31251
|
const hasCssTransitionAnimation = Boolean(resolvedAnimation);
|
|
31066
31252
|
const cancelOpenInteractionSuppression = !silent && hasCssTransitionAnimation ? suppressPointerEventsDuringTransition(dialogEl) : null;
|
|
31253
|
+
// Handing the focus to a field is what raises the on-screen keyboard, and
|
|
31254
|
+
// the keyboard takes away the very room this dialog was just placed
|
|
31255
|
+
// against — so on a touch-driven opening the transfer waits for the
|
|
31256
|
+
// entrance to be over. Decided by transferFocusOnOpen, the only place that
|
|
31257
|
+
// knows WHICH element is about to be focused (open_controller.js and its
|
|
31258
|
+
// FOCUS_DELAY_ON_KEYBOARD_MS).
|
|
31067
31259
|
const restoreFocus = openController.transferFocusOnOpen(dialogEl);
|
|
31068
31260
|
|
|
31069
31261
|
// isModal outside-click detection (see this file's top comment for why
|
|
@@ -31129,9 +31321,16 @@ const useDialogProps = props => {
|
|
|
31129
31321
|
// property is actually present — harmless the rest of the time.
|
|
31130
31322
|
dialogEl.setAttribute("navi-hidden", "");
|
|
31131
31323
|
dialogEl.close();
|
|
31132
|
-
//
|
|
31133
|
-
//
|
|
31134
|
-
|
|
31324
|
+
// Held at the size it has right now, for the whole way out. cleanup()
|
|
31325
|
+
// below already stops the JS repositioning, but the size is CSS-driven
|
|
31326
|
+
// (--x-dialog-max-height, and `height` outright under expandY) and
|
|
31327
|
+
// keeps following the visual viewport on its own — so a dialog closed
|
|
31328
|
+
// while the keyboard is up grows back to fill the room the keyboard is
|
|
31329
|
+
// giving back, WHILE fading out. Coherent, and still wrong to watch: a
|
|
31330
|
+
// box being dismissed has nothing left to adapt to, and the growth
|
|
31331
|
+
// reads as something happening at the exact moment nothing should. The
|
|
31332
|
+
// next opening clears it (see openEffect's own unfreezeSize).
|
|
31333
|
+
freezeSize(dialogEl);
|
|
31135
31334
|
cancelOpenInteractionSuppression?.();
|
|
31136
31335
|
if (hasCssTransitionAnimation) {
|
|
31137
31336
|
suppressPointerEventsDuringTransition(dialogEl);
|
|
@@ -32443,6 +32642,12 @@ const usePopoverProps = props => {
|
|
|
32443
32642
|
}
|
|
32444
32643
|
popoverEl.removeAttribute("data-anchor-out-of-view");
|
|
32445
32644
|
positionPopover(event);
|
|
32645
|
+
// Same as Dialog's own — a popup with a scrolling body and a footer
|
|
32646
|
+
// swallows the field it just got shorter around. See
|
|
32647
|
+
// keepFocusedElementVisible's own doc in popup_shared.js.
|
|
32648
|
+
if (mayHaveHiddenFocus(event)) {
|
|
32649
|
+
keepFocusedElementVisible(popoverEl);
|
|
32650
|
+
}
|
|
32446
32651
|
}, {
|
|
32447
32652
|
event: e,
|
|
32448
32653
|
// it's ok for the popover to become unsync with the anchor size
|
|
@@ -38830,24 +39035,34 @@ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
|
|
|
38830
39035
|
// included).
|
|
38831
39036
|
|
|
38832
39037
|
const css$T = /* css */`
|
|
38833
|
-
/* The marked region is a picture of its own
|
|
38834
|
-
|
|
38835
|
-
|
|
38836
|
-
|
|
39038
|
+
/* The marked region is a picture of its own for the length of a transition of
|
|
39039
|
+
OURS, and only then — the name is what makes the pages a picture the
|
|
39040
|
+
movement below can carry.
|
|
39041
|
+
|
|
39042
|
+
Named outside that, it would be a picture during every view transition the
|
|
39043
|
+
APPLICATION starts — two rows swapping, a list changing — and a page is
|
|
39044
|
+
several screens tall: its picture is the whole element, drawn in the top
|
|
39045
|
+
layer from wherever the element starts, so it paints over the fixed bars
|
|
39046
|
+
and past the bottom of the screen for the length of a movement that has
|
|
39047
|
+
nothing to do with the pages. Unnamed, it stays part of the document's own
|
|
39048
|
+
picture, where the browser cuts it at the viewport like everything else. */
|
|
39049
|
+
:root[data-navi-route-transition] [data-navi-route-transition-area] {
|
|
38837
39050
|
view-transition-name: navi-route-transition;
|
|
38838
39051
|
}
|
|
38839
39052
|
|
|
38840
|
-
/* A named descendant — a
|
|
38841
|
-
|
|
38842
|
-
|
|
38843
|
-
pages
|
|
38844
|
-
|
|
38845
|
-
|
|
38846
|
-
|
|
38847
|
-
|
|
39053
|
+
/* A named descendant — a thumbnail named for a morph, a row named for a
|
|
39054
|
+
reorder gesture — is a hole in the area's picture and a group of its own.
|
|
39055
|
+
Nested groups keep that group inside the area's, which is what cuts it at
|
|
39056
|
+
the pages' edge instead of letting it paint across the screen. What it does
|
|
39057
|
+
NOT do is make it travel: the movement is carried by the area's two
|
|
39058
|
+
pictures, and a group is not one of them, so a named descendant stands
|
|
39059
|
+
where it was captured while the pages slide under it. A morph wants exactly
|
|
39060
|
+
that; a component that names its parts for changes of its own does not, and
|
|
39061
|
+
drops its names for the length of the movement (see list.jsx). A browser
|
|
39062
|
+
without nested groups is warned instead (see
|
|
38848
39063
|
warnAboutNamesEscapingArea). */
|
|
38849
39064
|
@supports (view-transition-group: contain) {
|
|
38850
|
-
[data-navi-route-transition-area] {
|
|
39065
|
+
:root[data-navi-route-transition] [data-navi-route-transition-area] {
|
|
38851
39066
|
view-transition-group: contain;
|
|
38852
39067
|
}
|
|
38853
39068
|
}
|
|
@@ -45736,9 +45951,12 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
45736
45951
|
* along the bar it adds to the padding asked for. Note that every
|
|
45737
45952
|
* `env(safe-area-inset-*)` is 0 unless the page asks for it:
|
|
45738
45953
|
* `<meta name="viewport" content="…, viewport-fit=cover">`.
|
|
45739
|
-
* 4. **Its hairline is
|
|
45740
|
-
*
|
|
45741
|
-
*
|
|
45954
|
+
* 4. **Its hairline is part of its box.** The line covers the content just as
|
|
45955
|
+
* the bar does, so the room given back has to include it — a line drawn
|
|
45956
|
+
* outside the box (a box-shadow, an outline) is a line the content scrolls
|
|
45957
|
+
* under, and a line a page transition paints over. It is a real border,
|
|
45958
|
+
* added to the size asked for exactly like the notch inset is, so the
|
|
45959
|
+
* content still gets the size the prop names.
|
|
45742
45960
|
*/
|
|
45743
45961
|
const css$M = /* css */`
|
|
45744
45962
|
@layer navi {
|
|
@@ -45798,39 +46016,50 @@ const css$M = /* css */`
|
|
|
45798
46016
|
}
|
|
45799
46017
|
|
|
45800
46018
|
/* Across the bar, the inset of the edge it is pinned to is padding AND is
|
|
45801
|
-
added to the size
|
|
45802
|
-
|
|
46019
|
+
added to the size, and the hairline on the content side is added the
|
|
46020
|
+
same way: the background then runs under the notch, the line stands
|
|
46021
|
+
clear of the content, and the content keeps the whole width/height asked
|
|
46022
|
+
for. */
|
|
45803
46023
|
&[data-area="top"] {
|
|
45804
46024
|
top: var(--navi-app-inset-top);
|
|
45805
|
-
height: calc(
|
|
46025
|
+
height: calc(
|
|
46026
|
+
var(--navi-fixed-bar-height) + env(safe-area-inset-top) +
|
|
46027
|
+
var(--navi-fixed-bar-border-width)
|
|
46028
|
+
);
|
|
45806
46029
|
padding-top: env(safe-area-inset-top);
|
|
45807
|
-
|
|
46030
|
+
border-bottom: var(--navi-fixed-bar-border-width) solid
|
|
45808
46031
|
var(--navi-fixed-bar-border-color);
|
|
45809
46032
|
}
|
|
45810
46033
|
&[data-area="bottom"] {
|
|
45811
46034
|
bottom: var(--navi-app-inset-bottom);
|
|
45812
|
-
height: calc(
|
|
46035
|
+
height: calc(
|
|
46036
|
+
var(--navi-fixed-bar-height) + env(safe-area-inset-bottom) +
|
|
46037
|
+
var(--navi-fixed-bar-border-width)
|
|
46038
|
+
);
|
|
45813
46039
|
padding-bottom: env(safe-area-inset-bottom);
|
|
45814
|
-
|
|
46040
|
+
border-top: var(--navi-fixed-bar-border-width) solid
|
|
45815
46041
|
var(--navi-fixed-bar-border-color);
|
|
45816
46042
|
}
|
|
45817
46043
|
&[data-area="left"] {
|
|
45818
46044
|
left: var(--navi-app-inset-left);
|
|
45819
|
-
width: calc(
|
|
46045
|
+
width: calc(
|
|
46046
|
+
var(--navi-fixed-bar-width) + env(safe-area-inset-left) +
|
|
46047
|
+
var(--navi-fixed-bar-border-width)
|
|
46048
|
+
);
|
|
45820
46049
|
padding-left: env(safe-area-inset-left);
|
|
45821
|
-
|
|
46050
|
+
border-right: var(--navi-fixed-bar-border-width) solid
|
|
45822
46051
|
var(--navi-fixed-bar-border-color);
|
|
45823
46052
|
}
|
|
45824
46053
|
&[data-area="right"] {
|
|
45825
46054
|
right: var(--navi-app-inset-right);
|
|
45826
|
-
width: calc(
|
|
46055
|
+
width: calc(
|
|
46056
|
+
var(--navi-fixed-bar-width) + env(safe-area-inset-right) +
|
|
46057
|
+
var(--navi-fixed-bar-border-width)
|
|
46058
|
+
);
|
|
45827
46059
|
padding-right: env(safe-area-inset-right);
|
|
45828
|
-
|
|
46060
|
+
border-left: var(--navi-fixed-bar-border-width) solid
|
|
45829
46061
|
var(--navi-fixed-bar-border-color);
|
|
45830
46062
|
}
|
|
45831
|
-
&[data-border="none"] {
|
|
45832
|
-
box-shadow: none;
|
|
45833
|
-
}
|
|
45834
46063
|
}
|
|
45835
46064
|
`;
|
|
45836
46065
|
const FixedBarStyleCSSVars = {
|
|
@@ -45864,9 +46093,10 @@ const FixedBarStyleCSSVars = {
|
|
|
45864
46093
|
* @param {string|number} [props.width] - …and for one on a side. The safe-area
|
|
45865
46094
|
* inset is NOT part of it: it is added on top, so the content keeps the size
|
|
45866
46095
|
* asked for.
|
|
45867
|
-
* @param {boolean} [props.border=true] - The hairline on the content side.
|
|
45868
|
-
*
|
|
45869
|
-
* `borderWidth`/`borderColor`, or
|
|
46096
|
+
* @param {boolean} [props.border=true] - The hairline on the content side. It
|
|
46097
|
+
* is added to the size rather than taken out of it, and counts in the room
|
|
46098
|
+
* the bar gives back; give it a `borderWidth`/`borderColor`, or
|
|
46099
|
+
* `border={false}` for none.
|
|
45870
46100
|
* @param {string|number} [props.maxWidth] - Keeps the bar lined up with a
|
|
45871
46101
|
* content column narrower than the window (it stays centered).
|
|
45872
46102
|
*/
|
|
@@ -45879,9 +46109,16 @@ const FixedBar = ({
|
|
|
45879
46109
|
import.meta.css = [css$M, "@jsenv/navi/src/layout/fixed_bar/fixed_bar.jsx"];
|
|
45880
46110
|
const defaultRef = useRef();
|
|
45881
46111
|
props.ref = props.ref || defaultRef;
|
|
46112
|
+
// Said with the width the border rule reads rather than with an attribute of
|
|
46113
|
+
// its own: the width is what the size calc adds, so a line asked away here
|
|
46114
|
+
// is a line that takes no room either.
|
|
46115
|
+
if (!border) {
|
|
46116
|
+
props.borderWidth = "0px";
|
|
46117
|
+
}
|
|
45882
46118
|
// Whichever of width/height crosses the edge the bar sits on is what the
|
|
45883
46119
|
// content has to be given back — and the bar's border box already IS that:
|
|
45884
|
-
// the size it was given
|
|
46120
|
+
// the size it was given, the inset of that edge, and the hairline standing
|
|
46121
|
+
// between it and the content. Measured rather than
|
|
45885
46122
|
// rebuilt as a calc() expression, so a size coming from anywhere — a prop, a
|
|
45886
46123
|
// theme variable, the content itself — is reserved just the same, and each
|
|
45887
46124
|
// `env()` inset stays the browser's business alone.
|
|
@@ -45928,7 +46165,6 @@ const FixedBar = ({
|
|
|
45928
46165
|
return jsx(Box, {
|
|
45929
46166
|
baseClassName: "navi_fixed_bar",
|
|
45930
46167
|
"data-area": area,
|
|
45931
|
-
"data-border": border ? undefined : "none",
|
|
45932
46168
|
...props,
|
|
45933
46169
|
styleCSSVars: FixedBarStyleCSSVars,
|
|
45934
46170
|
children: children
|
|
@@ -57051,7 +57287,26 @@ const css$w = /* css */`
|
|
|
57051
57287
|
flex-direction: column;
|
|
57052
57288
|
background-color: var(--x-list-background-color);
|
|
57053
57289
|
border: var(--x-list-border-width) solid var(--x-list-border-color);
|
|
57054
|
-
|
|
57290
|
+
/* Squared from the outside, corner by corner: whoever draws the surface
|
|
57291
|
+
the list is laid on says which corners are the list's to draw (a popup's
|
|
57292
|
+
body does, see box.jsx), and each corner falls back to the list's own
|
|
57293
|
+
radius when nothing asks for anything. */
|
|
57294
|
+
border-top-left-radius: var(
|
|
57295
|
+
--x-corner-top-left-radius,
|
|
57296
|
+
var(--x-list-border-radius)
|
|
57297
|
+
);
|
|
57298
|
+
border-top-right-radius: var(
|
|
57299
|
+
--x-corner-top-right-radius,
|
|
57300
|
+
var(--x-list-border-radius)
|
|
57301
|
+
);
|
|
57302
|
+
border-bottom-right-radius: var(
|
|
57303
|
+
--x-corner-bottom-right-radius,
|
|
57304
|
+
var(--x-list-border-radius)
|
|
57305
|
+
);
|
|
57306
|
+
border-bottom-left-radius: var(
|
|
57307
|
+
--x-corner-bottom-left-radius,
|
|
57308
|
+
var(--x-list-border-radius)
|
|
57309
|
+
);
|
|
57055
57310
|
|
|
57056
57311
|
transition: opacity 0.2s ease;
|
|
57057
57312
|
/* overflow:hidden is required on the container (not the inner scroll element)
|
|
@@ -57060,6 +57315,13 @@ const css$w = /* css */`
|
|
|
57060
57315
|
overflow: hidden;
|
|
57061
57316
|
|
|
57062
57317
|
.navi_list_scroll_container {
|
|
57318
|
+
/* The ask stops here: this element is inside the list's frame, so a row
|
|
57319
|
+
or a control it holds is not at the surface's corner. */
|
|
57320
|
+
--x-corner-top-left-radius: initial;
|
|
57321
|
+
--x-corner-top-right-radius: initial;
|
|
57322
|
+
--x-corner-bottom-right-radius: initial;
|
|
57323
|
+
--x-corner-bottom-left-radius: initial;
|
|
57324
|
+
|
|
57063
57325
|
width: inherit;
|
|
57064
57326
|
min-width: inherit;
|
|
57065
57327
|
max-width: var(--list-max-width, inherit);
|
|
@@ -57533,9 +57795,19 @@ const css$w = /* css */`
|
|
|
57533
57795
|
without being contained animate across the page (the pictures live in the
|
|
57534
57796
|
top layer, where no overflow of the document reaches them), which is worse
|
|
57535
57797
|
than not animating at all. So a browser with no nested groups gets no name
|
|
57536
|
-
either, and the change simply happens.
|
|
57798
|
+
either, and the change simply happens.
|
|
57799
|
+
|
|
57800
|
+
Named for a change of the list's own, and for that alone: while the PAGES
|
|
57801
|
+
are the ones moving — a route transition, a route travel — the list is part
|
|
57802
|
+
of what travels, and a picture of its own is precisely what does not
|
|
57803
|
+
travel. A page is carried by its own picture; anything named inside it is
|
|
57804
|
+
lifted out of that picture into a group of its own, which stays where it
|
|
57805
|
+
was captured while the page slides away under it. So the names are dropped
|
|
57806
|
+
for the length of such a movement and the list crosses the screen with the
|
|
57807
|
+
page, as a block. */
|
|
57537
57808
|
@supports (view-transition-group: contain) {
|
|
57538
|
-
|
|
57809
|
+
:root:not([data-navi-route-transition], [data-navi-route-travel])
|
|
57810
|
+
.navi_list_container[data-item-transition] {
|
|
57539
57811
|
/* The list needs a name to be a group at all; which name does not matter,
|
|
57540
57812
|
only that no other element in the document carries it. */
|
|
57541
57813
|
view-transition-name: match-element;
|