@jsenv/navi 0.29.132 → 0.29.134

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.
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, coarsePointerSignal, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
6
6
  export { disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
7
- import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, initFocusGroup, scrollIntoViewScoped, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, isTouchDrivenEvent, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, getScrollContainer, canScroll, measureWidestChildRow, performTabNavigation, wheelGestureIsTakenFrom, releaseWheelGesture, claimWheelGesture, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
7
+ import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, initFocusGroup, scrollIntoViewScoped, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, isTouchDrivenEvent, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, canScroll, measureWidestChildRow, performTabNavigation, wheelGestureIsTakenFrom, releaseWheelGesture, claimWheelGesture, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
8
8
  export { clickIsSuppressed, contrastColor, findEvent, startDragTo } from "@jsenv/dom";
9
9
  import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
10
10
  import { isValidElement, h, Fragment, createContext, render, toChildArray, options, cloneElement } from "preact";
@@ -2229,6 +2229,17 @@ naviI18n.addAll({
2229
2229
  pt: "meia-noite",
2230
2230
  nl: "middernacht",
2231
2231
  },
2232
+ // What <TimeRange> writes between the two bounds of a span — "8h–10h",
2233
+ // "11 mai – 14 mai". An en dash, the mark for a span, not a hyphen.
2234
+ "time.range_separator": {
2235
+ en: "–",
2236
+ fr: "–",
2237
+ de: "–",
2238
+ es: "–",
2239
+ it: "–",
2240
+ pt: "–",
2241
+ nl: "–",
2242
+ },
2232
2243
  // Compact duration unit symbols used in "1h30", "45min", "2d", etc.
2233
2244
  "time.duration.year_symbol": {
2234
2245
  en: "y",
@@ -6081,10 +6092,11 @@ const css$11 = /* css */`
6081
6092
  right where it's set) — same reasoning as Popover's identical
6082
6093
  attribute (popover.jsx's own top comment has the full case). */
6083
6094
  position: fixed;
6084
- /* Popover resets */
6085
- inset: auto;
6086
- top: 0;
6087
- left: 0;
6095
+ /* Popover resets. Laid out at its containing block's own origin and moved
6096
+ from there by a translate (applyNewPosition), never by left/top — see
6097
+ applyNewPosition's own doc for why the placement may not go through
6098
+ left/top themselves. */
6099
+ inset: 0 auto auto 0;
6088
6100
  /* For some reason callout could end up behing elements when it's redisplayed in a dialog
6089
6101
  (behind button relatively positioned in dialog footer while callout is appended into dialog body)
6090
6102
  To ensure ti goes above we put a z-index: 1, I hope it won't bite use in the future */
@@ -6108,10 +6120,9 @@ const css$11 = /* css */`
6108
6120
  border: none;
6109
6121
  outline: none; /* programmatic focus may land here briefly before being redirected to close button */
6110
6122
  opacity: 0;
6111
- /* Positioned with plain left/top (applyNewPosition, visible_rect.js) —
6112
- left/top are NOT transitioned here, applyNewPosition drives that
6113
- itself via the Web Animations API instead of CSS, same mechanism
6114
- Popover/Dialog use. */
6123
+ /* opacity only: the placement lives in the translate property, which
6124
+ applyNewPosition (visible_rect.js) owns and animates itself through the
6125
+ Web Animations API, same mechanism Popover/Dialog use. */
6115
6126
  transition: opacity 0.2s ease-in-out;
6116
6127
  cursor: initial; /* Do not inherit element cursor, inside the element but should use regular cursor */
6117
6128
  pointer-events: auto; /* Must be interactive to be closabled (overrid list item pointer-events none for instance) */
@@ -8082,27 +8093,35 @@ const formatTime = (date, lang) => {
8082
8093
  * "compact" uses our own notation that omits the minute symbol when hours are present.
8083
8094
  *
8084
8095
  * @param {number} minutes
8085
- * @param {{ lang?: string, format?: "long"|"short"|"narrow"|"compact", clockStyle?: boolean, forceUnit?: boolean }} [options]
8096
+ * @param {{ lang?: string, format?: "long"|"short"|"narrow"|"compact", clockStyle?: boolean, pad?: boolean, precision?: "hour"|"minute", forceUnit?: boolean }} [options]
8086
8097
  * @param {boolean} [options.forceUnit=false] - Keep the value in minutes
8087
8098
  * however big it gets ("2160 minutes" instead of "1 jour et 12 heures").
8088
8099
  * Past 24 hours the default promotes to days, which reads better but hides
8089
8100
  * the unit the caller works in.
8090
8101
  * @param {boolean} [options.clockStyle=false] - Set this when `minutes`
8091
8102
  * represents a time-of-day rather than a real duration (used by
8092
- * `<Time type="time">`, see time.jsx's own TimeTime) affects two
8093
- * things at once, both consequences of a clock's "0" being a meaningful
8094
- * hour rather than "no hours":
8095
- * - a zero-hours component is normally dropped entirely (a real 5-minute
8096
- * duration should print as "5 minutes", not "0 hours 5 minutes"); this
8097
- * keeps it instead (e.g. "0 h et 5 min"/"0h 5min"/"00h05") so midnight
8098
- * doesn't collapse to something indistinguishable from an actual
8099
- * 5-minute duration.
8100
- * - `format: "compact"` also zero-pads a single-digit hour to 2 digits
8101
- * (e.g. "5h30" → "05h30") and keeps a zero-valued minute (e.g. "10h" →
8102
- * "10h00"), so it reads closer to a "05:30"/"10:00" clock. The other
8103
- * formats spell out their units, so "10 heures"/"10h" reads fine there
8104
- * and only "compact" needs the clock shape.
8103
+ * `<Time type="time">`, see time.jsx's own TimeTime). A clock's "0" is a
8104
+ * meaningful hour rather than "no hours": a zero-hours component is
8105
+ * normally dropped entirely (a real 5-minute duration should print as
8106
+ * "5 minutes", not "0 hours 5 minutes"); this keeps it instead (e.g.
8107
+ * "0 h et 5 min"/"0h 5min"/"00h05") so midnight doesn't collapse to
8108
+ * something indistinguishable from an actual 5-minute duration.
8105
8109
  * Must not be set for plain duration formatting.
8110
+ * @param {boolean} [options.pad=true] - Zero-pad the hour to 2 digits
8111
+ * ("08h30" rather than "8h30"). `clockStyle` + `format: "compact"` only.
8112
+ * @param {"hour"|"minute"} [options.precision="minute"] - Whether a zero
8113
+ * minute is written: `"minute"` keeps it ("10h00"), `"hour"` drops it
8114
+ * ("10h"). `clockStyle` + `format: "compact"` only.
8115
+ *
8116
+ * These last two are the clock's two independent shape choices, and only
8117
+ * `format: "compact"` has to make them — the spelled-out formats put their
8118
+ * units in words, so "10 heures"/"10 h"/"10h" already reads as a time of
8119
+ * day whatever the padding, and they always write the hour bare and drop a
8120
+ * zero minute. Padded + minute ("08h00") is the column shape, where every
8121
+ * row occupies the same width; bare + hour ("8h", "8h30") is the shape a
8122
+ * person speaks. Bare + minute ("8h00") only ever makes sense next to a
8123
+ * partner that has minutes of its own — see `<TimeRange>`, which is the
8124
+ * only thing that asks for it.
8106
8125
  *
8107
8126
  * @example
8108
8127
  * formatMinuteDuration(90, { lang: "fr" }) // "1 heure 30 minutes" (long, default)
@@ -8113,6 +8132,9 @@ const formatTime = (date, lang) => {
8113
8132
  * formatMinuteDuration(5, { lang: "fr", format: "narrow", clockStyle: true }) // "0h 5min"
8114
8133
  * formatMinuteDuration(330, { lang: "fr", format: "compact", clockStyle: true }) // "05h30"
8115
8134
  * formatMinuteDuration(600, { lang: "fr", format: "compact", clockStyle: true }) // "10h00"
8135
+ * formatMinuteDuration(600, { lang: "fr", format: "compact", clockStyle: true, pad: false }) // "10h00"
8136
+ * formatMinuteDuration(600, { lang: "fr", format: "compact", clockStyle: true, pad: false, precision: "hour" }) // "10h"
8137
+ * formatMinuteDuration(510, { lang: "fr", format: "compact", clockStyle: true, pad: false, precision: "hour" }) // "8h30"
8116
8138
  * formatMinuteDuration(2160, { lang: "fr" }) // "1 jour et 12 heures"
8117
8139
  * formatMinuteDuration(2160, { lang: "fr", forceUnit: true }) // "2 160 minutes"
8118
8140
  */
@@ -8122,13 +8144,15 @@ const formatMinuteDuration = (
8122
8144
  lang = languagesSignal.value,
8123
8145
  format = "long",
8124
8146
  clockStyle = false,
8147
+ pad = true,
8148
+ precision = "minute",
8125
8149
  forceUnit = false,
8126
8150
  } = {},
8127
8151
  ) => {
8128
8152
  if (minutes < 0) {
8129
8153
  // the d/h/m split below only holds for a positive value; formatting the
8130
8154
  // magnitude and putting the sign back is the only reading that works
8131
- return `-${formatMinuteDuration(-minutes, { lang, format, clockStyle, forceUnit })}`;
8155
+ return `-${formatMinuteDuration(-minutes, { lang, format, clockStyle, pad, precision, forceUnit })}`;
8132
8156
  }
8133
8157
  if (forceUnit || (minutes === 0 && !clockStyle)) {
8134
8158
  // a zero has nothing to promote to, and rendering it as an empty string
@@ -8162,16 +8186,19 @@ const formatMinuteDuration = (
8162
8186
  const hSym = naviI18n("time.duration.hour_symbol", undefined, { lang });
8163
8187
  const mSym = naviI18n("time.duration.minute_symbol", undefined, { lang });
8164
8188
  const dStr = d > 0 ? `${formatCompactNumber(d, lang)}${dSym}` : "";
8165
- const hStr = clockStyle
8166
- ? String(h).padStart(2, "0")
8167
- : formatCompactNumber(h, lang);
8189
+ const hStr =
8190
+ clockStyle && pad
8191
+ ? String(h).padStart(2, "0")
8192
+ : formatCompactNumber(h, lang);
8168
8193
  if (d === 0 && h === 0 && !clockStyle) {
8169
8194
  return `${m}${mSym}`;
8170
8195
  }
8171
8196
  if (m === 0) {
8172
8197
  if (clockStyle) {
8173
- // "10h00" on a clock, "2h" for a real 2 hours duration
8174
- return `${hStr}${hSym}00`;
8198
+ // "10h00" on a clock, "2h" for a real 2 hours duration — except at
8199
+ // precision "hour", where a clock drops the zero minute too ("10h"),
8200
+ // the way one says it out loud
8201
+ return precision === "minute" ? `${hStr}${hSym}00` : `${hStr}${hSym}`;
8175
8202
  }
8176
8203
  return h === 0 ? dStr : `${dStr}${hStr}${hSym}`;
8177
8204
  }
@@ -25350,6 +25377,43 @@ const updateDocumentState = (value) => {
25350
25377
  documentStateSignal.value = value;
25351
25378
  };
25352
25379
 
25380
+ /**
25381
+ * What the history entry being written ends up holding.
25382
+ *
25383
+ * `state`:
25384
+ * - `undefined` — a neutral navigation: a link click, a `replaceUrl` writing a
25385
+ * search param. A **replace** stays on the entry the document is already on,
25386
+ * so what that entry holds stays with it. A **push** opens a NEW entry, and
25387
+ * what was written for the one being left — an open dialog, an expanded
25388
+ * picker (see `useNavState`) — describes that entry alone: carried forward it
25389
+ * would reopen on the next screen, and on the one after that, until something
25390
+ * mounting the same id opens out of nowhere.
25391
+ * - `null` — an explicit reset.
25392
+ * - an object — built by the caller (`enter()`/`leave()` copy the current state
25393
+ * themselves), taken as given.
25394
+ *
25395
+ * `sharedState` always wins: it describes the document, not the entry.
25396
+ *
25397
+ * The push/replace split is the Navigation API's own rule for a `navigate()`
25398
+ * carrying no state — which is why via_navigation.js gets it from the browser
25399
+ * and via_history.js has to spell it out to say the same thing.
25400
+ */
25401
+ const resolveEffectiveDocumentState = (
25402
+ state,
25403
+ { navigationType, currentState, sharedState },
25404
+ ) => {
25405
+ if (state === undefined) {
25406
+ if (navigationType === "push") {
25407
+ return sharedState;
25408
+ }
25409
+ return { ...(currentState || {}), ...sharedState };
25410
+ }
25411
+ if (state === null) {
25412
+ return sharedState;
25413
+ }
25414
+ return { ...state, ...sharedState };
25415
+ };
25416
+
25353
25417
  /**
25354
25418
  * The document's rendering, held for the one frame a view transition needs.
25355
25419
  *
@@ -25482,6 +25546,13 @@ const takeoverRoutingRenderingHold = () => {
25482
25546
  * something still loading. Its content is not there at the moment it is put
25483
25547
  * back, so a position beyond what has arrived is clamped as before. Only the
25484
25548
  * page knows when it is whole.
25549
+ *
25550
+ * WHEN a page is arrived at is not decided here either. A document navigation
25551
+ * lands where its kind says (see via_history.js), and one scrollport can be
25552
+ * shared by pages the browser is never told apart: a row of tabs replaces the
25553
+ * url under the same document, so the arrival — and the deafness the swap
25554
+ * needs, see suspendScrollRecording — is asked for by the row itself (see
25555
+ * route_travel.jsx).
25485
25556
  */
25486
25557
 
25487
25558
 
@@ -25519,6 +25590,29 @@ const storePositions = () => {
25519
25590
  }
25520
25591
  };
25521
25592
 
25593
+ // The document is one scrollport for every page put in it, so a page swapped
25594
+ // under it for a shorter one is an offset the browser CLAMPS — and a clamp is
25595
+ // a scroll event like any other. It is not the reader scrolling, and by the
25596
+ // time it fires the url is already the arriving page's: written down, it is
25597
+ // that page's own position that the page being left destroys.
25598
+ //
25599
+ // Only whoever swaps the page knows when that is happening, so the deafness is
25600
+ // asked for from there and lasts exactly as long as the swap. Counted rather
25601
+ // than flagged: two swaps overlap — a travel relaying into the next one under
25602
+ // the same finger, a travel being undone while it plays.
25603
+ let suspendCount = 0;
25604
+ const suspendScrollRecording = () => {
25605
+ suspendCount++;
25606
+ let resumed = false;
25607
+ return () => {
25608
+ if (resumed) {
25609
+ return;
25610
+ }
25611
+ resumed = true;
25612
+ suspendCount--;
25613
+ };
25614
+ };
25615
+
25522
25616
  let installed = false;
25523
25617
  const installScrollRestoration = () => {
25524
25618
  if (installed) {
@@ -25536,6 +25630,9 @@ const installScrollRestoration = () => {
25536
25630
  window.addEventListener(
25537
25631
  "scroll",
25538
25632
  () => {
25633
+ if (suspendCount) {
25634
+ return;
25635
+ }
25539
25636
  positionByUrl.set(window.location.href, {
25540
25637
  x: window.scrollX,
25541
25638
  y: window.scrollY,
@@ -25558,12 +25655,43 @@ const installScrollRestoration = () => {
25558
25655
 
25559
25656
  // Nothing to put back is not the same as putting back the top: a page arrived
25560
25657
  // at for the first time is startAtTop's business, and this must not step on it.
25658
+ // Whether there was anything, for a caller who has an answer of its own for the
25659
+ // page that has never been read.
25561
25660
  const restoreScrollPosition = (url) => {
25562
25661
  const position = positionByUrl.get(new URL(url, window.location.href).href);
25563
25662
  if (!position) {
25564
- return;
25663
+ return false;
25565
25664
  }
25566
25665
  scrollTo(position);
25666
+ return true;
25667
+ };
25668
+
25669
+ // A page one arrives at for the first time starts at its top. Only a document
25670
+ // navigation does that on its own: a pushState creates its entry with whatever
25671
+ // scroll happened to be there, so without this the page opens at the offset of
25672
+ // the one before it — and that borrowed offset is what is then remembered FOR
25673
+ // it, and handed back on the way forward.
25674
+ //
25675
+ // The document, because the document is the scrollport in the common case. An
25676
+ // app that scrolls an element of its own scrolls it itself.
25677
+ const startAtTop = (url) => {
25678
+ // A fragment names where to land, and the browser is the one that finds it.
25679
+ if (new URL(url, window.location.href).hash) {
25680
+ return;
25681
+ }
25682
+ window.scrollTo({ top: 0, left: 0, behavior: "instant" });
25683
+ };
25684
+
25685
+ // An arrival at a page whose scrollport is already showing another one: the
25686
+ // tabs of a row share the document, and the offset on it is whichever tab was
25687
+ // last read. Where this one was read, and its top when it never was — leaving
25688
+ // the offset alone would seat the reader wherever the neighbour happened to
25689
+ // be, so here "nothing recorded" and "stay" are not the same thing.
25690
+ const arriveAtScrollPosition = (url) => {
25691
+ if (restoreScrollPosition(url)) {
25692
+ return;
25693
+ }
25694
+ startAtTop(url);
25567
25695
  };
25568
25696
 
25569
25697
  const scrollTo = ({ x, y }) => {
@@ -25907,28 +26035,14 @@ const setupBrowserIntegrationViaHistory = ({
25907
26035
 
25908
26036
  if (navigationType === "push" || navigationType === "replace") {
25909
26037
  markUrlAsVisited(url);
25910
- // undefined inherit current state (link click, neutral navigation)
25911
- // null → explicit reset (no nav-state keys carried over)
25912
- // {...} → explicit state from enter()/leave(), already built from currentState
25913
- // When state is given it's responsability of the caller to ensure it inherits document state (or not, you want it 99% of the time)
25914
- let effectiveState;
25915
- const sharedState = {
25916
- jsenv_visited_urls: Array.from(visitedUrlSet),
25917
- [NAV_DEPTH_STATE_KEY]: getNavDepth(),
25918
- };
25919
- if (state === undefined) {
25920
- effectiveState = {
25921
- ...(getDocumentState() || {}),
25922
- ...sharedState,
25923
- };
25924
- } else if (state === null) {
25925
- effectiveState = sharedState;
25926
- } else if (state) {
25927
- effectiveState = {
25928
- ...state,
25929
- ...sharedState,
25930
- };
25931
- }
26038
+ const effectiveState = resolveEffectiveDocumentState(state, {
26039
+ navigationType,
26040
+ currentState: getDocumentState(),
26041
+ sharedState: {
26042
+ jsenv_visited_urls: Array.from(visitedUrlSet),
26043
+ [NAV_DEPTH_STATE_KEY]: getNavDepth(),
26044
+ },
26045
+ });
25932
26046
  if (navigationType === "push") {
25933
26047
  window.history.pushState(effectiveState, null, url);
25934
26048
  } else {
@@ -25977,12 +26091,21 @@ const setupBrowserIntegrationViaHistory = ({
25977
26091
  isVisited,
25978
26092
  state,
25979
26093
  });
26094
+ // Where the document lands, said by what kind of arrival this is. Both are
26095
+ // waited for, and for the same two reasons: the page has to be there to be
26096
+ // scrolled, and a picture taken before it would be of a page at its top
26097
+ // (see rendering_hold.js, which is where the waiting happens). After the
26098
+ // history has been written too, so the entry being left keeps the offset
26099
+ // it is at.
26100
+ //
26101
+ // A replace gets neither: it is the same place said differently — a param
26102
+ // settling, a state written — and moving the reader for it would throw
26103
+ // them out of a page they never left. The one replace that IS an arrival
26104
+ // is a row of tabs travelling, and the row says so for itself (see
26105
+ // route_travel.jsx).
25980
26106
  if (navigationType === "push") {
25981
26107
  whenRenderingResumes(() => startAtTop(url));
25982
26108
  } else if (navigationType === "traverse") {
25983
- // Where this entry was left. Waited for like the reset above, and for
25984
- // the same two reasons: the page has to be there to be scrolled, and a
25985
- // picture taken before it would be of a page at its top.
25986
26109
  whenRenderingResumes(() => restoreScrollPosition(url));
25987
26110
  }
25988
26111
  executeWithCleanup(
@@ -26177,39 +26300,6 @@ const setupBrowserIntegrationViaHistory = ({
26177
26300
  };
26178
26301
  };
26179
26302
 
26180
- // A page one arrives at for the first time starts at its top. Only a document
26181
- // navigation does that on its own: a pushState creates its entry with whatever
26182
- // scroll happened to be there, so without this the new page opens at the offset
26183
- // of the one before it — and worse, that borrowed offset is what the browser
26184
- // then remembers FOR that entry, and hands back on the way forward.
26185
- //
26186
- // Push only. A traverse is the browser's business and it is already right: it
26187
- // keeps a position per entry and restores it. A replace is not an arrival —
26188
- // it is the same place, said differently (a tab row travelling, see
26189
- // route_travel.jsx), and resetting there would throw the reader out of a page
26190
- // they never left.
26191
- //
26192
- // After the routes have been told, and after the picture of the page being
26193
- // left has been taken — that ordering is the whole subtlety. The routes
26194
- // changing is what sets a movement off, and a movement measures the box it is
26195
- // leaving as it stands; put the document back to its top any earlier and the
26196
- // picture is of a page at its first line, which the reader was not at. The
26197
- // browser paints what the new offset shows and nothing else, so what is kept
26198
- // of the page being left is the band it had already painted, and the movement
26199
- // carries a fragment (see rendering_hold.js, which is where the waiting
26200
- // happens). After pushState too, so the entry being left keeps the offset it
26201
- // is at.
26202
- //
26203
- // The document, because the document is the scrollport in the common case. An
26204
- // app that scrolls an element of its own scrolls it itself.
26205
- const startAtTop = (url) => {
26206
- // A fragment names where to land, and the browser is the one that finds it.
26207
- if (new URL(url, window.location.href).hash) {
26208
- return;
26209
- }
26210
- window.scrollTo({ top: 0, left: 0, behavior: "instant" });
26211
- };
26212
-
26213
26303
  let updateRoutes;
26214
26304
 
26215
26305
  const applyActions = (params) => {
@@ -26996,8 +27086,38 @@ const releaseTransitionDestination = (owner) => {
26996
27086
  * the page arriving is in the DOM and the transition has not started playing.
26997
27087
  * Everything DERIVED from these numbers — the band a fixed bar covers, how far
26998
27088
  * a page travels — is derived in CSS, so the application's own numbers (the
26999
- * room its bars give back, see layout/safe_area.js) take part in it.
27089
+ * room its bars give back, see layout/safe_area.js; what covers the box from
27090
+ * inside the document, --navi-transition-cover-* below) take part in it.
27091
+ */
27092
+
27093
+ /**
27094
+ * What covers the box from INSIDE the document: a sticky row of tabs above the
27095
+ * pages, a header pinned to the top of a scroller. Declared at zero and
27096
+ * written by whoever covers it, exactly as a fixed bar publishes the room it
27097
+ * gives back (layout/safe_area.js).
27098
+ *
27099
+ * A slot rather than a measurement, and a slot navi cannot fill itself: the
27100
+ * safe area answers for what is pinned to the WINDOW's edges, and a sticky row
27101
+ * is none of those — it lives in the document, below the bars, and covers the
27102
+ * top of the box exactly as a fixed bar covers the top of the screen. The
27103
+ * pictures of a transition are drawn in the top layer, where no z-index of the
27104
+ * document reaches them, so what the document paints over the box has to be
27105
+ * counted here or it is the pictures that paint over it.
27106
+ *
27107
+ * Each one is a distance inward from the band the safe area already leaves
27108
+ * free, so a row states its own height and nothing else: what the bars above
27109
+ * it take is already counted.
27000
27110
  */
27111
+ const TRANSITION_WINDOW_CSS = /* css */ `
27112
+ @layer navi {
27113
+ :root {
27114
+ --navi-transition-cover-top: 0px;
27115
+ --navi-transition-cover-right: 0px;
27116
+ --navi-transition-cover-bottom: 0px;
27117
+ --navi-transition-cover-left: 0px;
27118
+ }
27119
+ }
27120
+ `;
27001
27121
 
27002
27122
  const WINDOW_TOP_PROPERTY = "--navi-transition-window-top";
27003
27123
  const WINDOW_LEFT_PROPERTY = "--navi-transition-window-left";
@@ -27144,6 +27264,8 @@ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
27144
27264
  // included).
27145
27265
 
27146
27266
  const css$10 = /* css */`
27267
+ ${TRANSITION_WINDOW_CSS}
27268
+
27147
27269
  /* The marked region is a picture of its own for the length of a transition of
27148
27270
  OURS, and only then — the name is what makes the pages a picture the
27149
27271
  movement below can carry.
@@ -27228,39 +27350,46 @@ const css$10 = /* css */`
27228
27350
  height: var(--navi-transition-window-height);
27229
27351
  animation-name: none;
27230
27352
 
27231
- /* Cut at the safe area, on top of being cut at the area's own box. The
27232
- pictures are drawn in the top layer, so they cover a fixed bar as
27233
- easily as anything else — and the area runs UNDER the bars by design:
27234
- that is what a fixed bar is for, and what the room it gives back is
27235
- for. An area taller than the screen therefore ends below the bottom
27236
- bar, and a scrolled one starts above the top bar, so the movement
27237
- would be watched painting over them for its whole length.
27238
-
27239
- The band left free is the app's own safe area (see
27240
- layout/safe_area.js) every kind of furniture at once, not the bars
27241
- alone, and read rather than asked for, so one that grows, shrinks or
27242
- unmounts mid-transition is followed without anything being told. What
27243
- the window cannot know is only where it itself stands, and that is the
27244
- measured half. */
27353
+ /* Cut at what covers the area, on top of being cut at the area's own
27354
+ box. The pictures are drawn in the top layer, so they cover a fixed bar
27355
+ as easily as anything else — and the area runs UNDER the bars by
27356
+ design: that is what a fixed bar is for, and what the room it gives
27357
+ back is for. An area taller than the screen therefore ends below the
27358
+ bottom bar, and a scrolled one starts above the top bar, so the
27359
+ movement would be watched painting over them for its whole length.
27360
+
27361
+ Two bands are left free, and they answer for two different things: the
27362
+ app's own safe area (layout/safe_area.js), everything pinned to the
27363
+ WINDOW's edges, and --navi-transition-cover-* (transition_window.js),
27364
+ what covers the area from inside the document — a sticky header above
27365
+ the pages covers the top of the area exactly as a fixed bar covers the
27366
+ top of the screen. Both are read rather than asked for, so one that
27367
+ grows, shrinks or unmounts mid-transition is followed without anything
27368
+ being told. What the window cannot know is only where it itself stands,
27369
+ and that is the measured half. */
27245
27370
  --navi-route-transition-clip-top: max(
27246
27371
  0px,
27247
- var(--navi-safe-area-inset-top) - var(--navi-transition-window-top)
27372
+ var(--navi-safe-area-inset-top) +
27373
+ var(--navi-transition-cover-top) - var(--navi-transition-window-top)
27248
27374
  );
27249
27375
  --navi-route-transition-clip-left: max(
27250
27376
  0px,
27251
- var(--navi-safe-area-inset-left) - var(--navi-transition-window-left)
27377
+ var(--navi-safe-area-inset-left) +
27378
+ var(--navi-transition-cover-left) - var(--navi-transition-window-left)
27252
27379
  );
27253
27380
  --navi-route-transition-clip-bottom: max(
27254
27381
  0px,
27255
27382
  var(--navi-transition-window-top) +
27256
27383
  var(--navi-transition-window-height) +
27257
- var(--navi-safe-area-inset-bottom) - 100dvh
27384
+ var(--navi-safe-area-inset-bottom) +
27385
+ var(--navi-transition-cover-bottom) - 100dvh
27258
27386
  );
27259
27387
  --navi-route-transition-clip-right: max(
27260
27388
  0px,
27261
27389
  var(--navi-transition-window-left) +
27262
27390
  var(--navi-transition-window-width) +
27263
- var(--navi-safe-area-inset-right) - 100dvw
27391
+ var(--navi-safe-area-inset-right) +
27392
+ var(--navi-transition-cover-right) - 100dvw
27264
27393
  );
27265
27394
  clip-path: inset(
27266
27395
  var(--navi-route-transition-clip-top)
@@ -28278,6 +28407,8 @@ const TURNED_ATTRIBUTE = "data-navi-route-travel-turned";
28278
28407
  // The name the box wears while it travels, and only then (see nameForTravel).
28279
28408
  const TRAVEL_NAME = "navi-route-travel";
28280
28409
  const css$$ = /* css */`
28410
+ ${TRANSITION_WINDOW_CSS}
28411
+
28281
28412
  /* The name that makes the page inside this box a picture of its own during a
28282
28413
  transition — rather than part of the one big picture the document takes, so
28283
28414
  the two pages can move past each other while everything else stays where it
@@ -28396,38 +28527,46 @@ const css$$ = /* css */`
28396
28527
  route to the next. */
28397
28528
  height: var(--navi-transition-window-height);
28398
28529
 
28399
- /* Cut at the safe area, on top of being cut at the box. The pictures are
28400
- drawn in the top layer, so they cover a fixed bar as easily as anything
28401
- else — and the box they travel in runs UNDER the bars by design: that
28402
- is what a fixed bar is for, and what the room it gives back is for. A
28403
- box scrolled by so much as a pixel therefore starts above the top bar
28404
- and ends below the bottom one, and the travel would be watched painting
28405
- over both for its whole length.
28406
-
28407
- The band left free is the app's own safe area (see layout/safe_area.js)
28408
- every kind of furniture at once, not the bars alone, and read rather
28409
- than asked for, so one that grows, shrinks or unmounts mid-travel is
28410
- followed without anything being told. What the group cannot know is
28411
- only where it itself stands, and that is the measured half. */
28530
+ /* Cut at what covers the box, on top of being cut at the box. The
28531
+ pictures are drawn in the top layer, so they cover a fixed bar as
28532
+ easily as anything else — and the box they travel in runs UNDER the
28533
+ bars by design: that is what a fixed bar is for, and what the room it
28534
+ gives back is for. A box scrolled by so much as a pixel therefore
28535
+ starts above the top bar and ends below the bottom one, and the travel
28536
+ would be watched painting over both for its whole length.
28537
+
28538
+ Two bands are left free, and they answer for two different things: the
28539
+ app's own safe area (layout/safe_area.js), everything pinned to the
28540
+ WINDOW's edges, and --navi-transition-cover-* (transition_window.js),
28541
+ what covers the box from inside the document a sticky row of tabs
28542
+ above the pages covers the top of the box exactly as a fixed bar covers
28543
+ the top of the screen. Both are read rather than asked for, so one that
28544
+ grows, shrinks or unmounts mid-travel is followed without anything
28545
+ being told. What the group cannot know is only where it itself stands,
28546
+ and that is the measured half. */
28412
28547
  --navi-route-travel-clip-top: max(
28413
28548
  0px,
28414
- var(--navi-safe-area-inset-top) - var(--navi-transition-window-top)
28549
+ var(--navi-safe-area-inset-top) +
28550
+ var(--navi-transition-cover-top) - var(--navi-transition-window-top)
28415
28551
  );
28416
28552
  --navi-route-travel-clip-left: max(
28417
28553
  0px,
28418
- var(--navi-safe-area-inset-left) - var(--navi-transition-window-left)
28554
+ var(--navi-safe-area-inset-left) +
28555
+ var(--navi-transition-cover-left) - var(--navi-transition-window-left)
28419
28556
  );
28420
28557
  --navi-route-travel-clip-bottom: max(
28421
28558
  0px,
28422
28559
  var(--navi-transition-window-top) +
28423
28560
  var(--navi-transition-window-height) +
28424
- var(--navi-safe-area-inset-bottom) - 100dvh
28561
+ var(--navi-safe-area-inset-bottom) +
28562
+ var(--navi-transition-cover-bottom) - 100dvh
28425
28563
  );
28426
28564
  --navi-route-travel-clip-right: max(
28427
28565
  0px,
28428
28566
  var(--navi-transition-window-left) +
28429
28567
  var(--navi-transition-window-width) +
28430
- var(--navi-safe-area-inset-right) - 100dvw
28568
+ var(--navi-safe-area-inset-right) +
28569
+ var(--navi-transition-cover-right) - 100dvw
28431
28570
  );
28432
28571
  clip-path: inset(
28433
28572
  var(--navi-route-travel-clip-top) var(--navi-route-travel-clip-right)
@@ -28736,6 +28875,19 @@ const RouteTravel = ({
28736
28875
  ended: false
28737
28876
  };
28738
28877
  travelRef.current = travel;
28878
+ // Whether the document's offset is this row's business at all (see
28879
+ // pagesScrollTheDocument), asked once and before anything is swapped.
28880
+ travel.scrollsDocument = pagesScrollTheDocument(elementRef.current);
28881
+ if (travel.scrollsDocument) {
28882
+ // One of the tabs is about to be swapped out from under that scrollport,
28883
+ // and whatever the browser does with the offset meanwhile — clamping it
28884
+ // to a page shorter than the trip — is not the reader scrolling. The url
28885
+ // it would be written against is already the arriving tab's, so recorded
28886
+ // it is that tab's own position that the tab being left destroys. Deaf
28887
+ // for the whole travel, which is exactly as long as the offset belongs
28888
+ // to nobody (see endTravel).
28889
+ travel.resumeScrollRecording = suspendScrollRecording();
28890
+ }
28739
28891
  // Taken before the picture is: the browser reads the name off the DOM as it
28740
28892
  // stands when the transition starts, and this box is only a picture of its
28741
28893
  // own for as long as it is the one travelling. Where it travels to is said
@@ -28778,8 +28930,24 @@ const RouteTravel = ({
28778
28930
  await change();
28779
28931
  }
28780
28932
  }, renderWait);
28933
+ // Where the arriving tab was left, or its top when it has never been
28934
+ // read. A travel is an arrival — the tab pressed is another route, and
28935
+ // the replace it navigates by is the only thing about it that says
28936
+ // otherwise — and this is the one place that knows it. Read from the
28937
+ // travel rather than from the page it set off for: a travel aimed
28938
+ // somewhere else while it waited lands where it is aimed now.
28939
+ if (travel.scrollsDocument) {
28940
+ const {
28941
+ route,
28942
+ params
28943
+ } = travel.page;
28944
+ arriveAtScrollPosition(route.buildUrl(params));
28945
+ }
28781
28946
  // The page arriving is in the DOM and the transition has not started
28782
- // playing: the one moment both boxes can be known.
28947
+ // playing: the one moment both boxes can be known. After the scroll,
28948
+ // which is what the box is measured through: the two states are at the
28949
+ // same place in the layout without being at the same place in the window
28950
+ // (see transition_window.js).
28783
28951
  holdTransitionWindow(travel, elementRef.current, rectBefore);
28784
28952
  });
28785
28953
  travel.viewTransition = viewTransition;
@@ -29000,6 +29168,25 @@ const RouteTravel = ({
29000
29168
  }
29001
29169
  travel.viewTransition.skipTransition();
29002
29170
  } finally {
29171
+ if (travel.scrollsDocument) {
29172
+ // The page coming back was read somewhere else, and the offset
29173
+ // currently on the document is the one the page that came in put
29174
+ // there. Given back once the page is really back — the render is
29175
+ // what makes the document tall enough to hold that offset again —
29176
+ // and the recording stays deaf until then, over the clamp the swap
29177
+ // back makes on the way. Its own deafness rather than the travel's:
29178
+ // the travel ends here, and this outlives it by a render.
29179
+ const resumeScrollRecording = suspendScrollRecording();
29180
+ const stopWatchingRender = observeRouteRender(() => {
29181
+ stopWatchingRender();
29182
+ const {
29183
+ route,
29184
+ params
29185
+ } = travel.fromPage;
29186
+ arriveAtScrollPosition(route.buildUrl(params));
29187
+ resumeScrollRecording();
29188
+ });
29189
+ }
29003
29190
  releaseRendering();
29004
29191
  // A travel ENDS, whatever happened on the way back: put the state back,
29005
29192
  // fail to drop the picture, be interrupted by something else — the one
@@ -29085,6 +29272,11 @@ const RouteTravel = ({
29085
29272
  travel.ended = true;
29086
29273
  travel.dropHold?.();
29087
29274
  travel.dropHold = null;
29275
+ // The offset on the scrollport belongs to a page again — the one that
29276
+ // arrived, or the one put back. A travel that never got as far as saying
29277
+ // which still has to give the recording back; the gesture's own
29278
+ // pseudo-travel never took it (see noPicture).
29279
+ travel.resumeScrollRecording?.();
29088
29280
  // Its own hold, always — whether or not this travel is still the current
29089
29281
  // one. Nobody else will lift it.
29090
29282
  releaseHold(travel);
@@ -29620,6 +29812,32 @@ const whilePageRenders = async (page, change, wait = armRouteRenderWait()) => {
29620
29812
  }
29621
29813
  };
29622
29814
 
29815
+ // Whether the document's offset belongs to the tabs. The pages of a row scroll
29816
+ // the document when nothing between the box and the viewport is a scroll
29817
+ // container: the tab on screen is then what makes the document tall, and the
29818
+ // offset on it is that tab's — it has to be given back with the tab, and the
29819
+ // browser's clamping of it while pages are swapped has to be ignored.
29820
+ //
29821
+ // A box that lives inside a scroller of its own — a frame in an article, a
29822
+ // panel beside other content — shares nothing with the document: the offset
29823
+ // there is the surrounding page's, the reader never left it, and a travel has
29824
+ // no business moving it. Each of its pages brings its own scrollport, which
29825
+ // goes away with the page and has nothing to restore.
29826
+ //
29827
+ // includeHidden, because an `overflow: hidden` ancestor is one of those
29828
+ // scrollers: it shows no scrollbar and still holds an offset of its own, and
29829
+ // what is under it never reaches the document. A wrapper that merely clips
29830
+ // (`overflow: clip`) holds nothing and is walked straight through — see
29831
+ // is_scrollable.js.
29832
+ const pagesScrollTheDocument = element => {
29833
+ const scrollContainer = getScrollContainer(element, {
29834
+ includeHidden: true
29835
+ });
29836
+ // html and body are one answer: whichever of them the walk stops on, what
29837
+ // scrolls is the viewport, which is what window.scrollTo moves.
29838
+ return scrollContainer === document.documentElement || scrollContainer === document.body;
29839
+ };
29840
+
29623
29841
  // A page of the row: a route, and the params that say which of its tabs when
29624
29842
  // several of them share it. Written as a bare route by a caller whose tabs are
29625
29843
  // routes of their own — which is the same page with nothing to tell apart.
@@ -52935,6 +53153,8 @@ const TimeTime = ({
52935
53153
  children,
52936
53154
  lang = languagesSignal.value,
52937
53155
  format = "long",
53156
+ pad = true,
53157
+ precision = pad ? "minute" : "hour",
52938
53158
  ...props
52939
53159
  }) => {
52940
53160
  if (children === undefined) {
@@ -52943,13 +53163,7 @@ const TimeTime = ({
52943
53163
  children: "--:--"
52944
53164
  });
52945
53165
  }
52946
- const date = toDate(children, value => {
52947
- if (/^\d{2}:\d{2}(?::\d{2})?$/.test(value)) {
52948
- const d = new Date(`1970-01-01T${value}`);
52949
- return isNaN(d.getTime()) ? null : d;
52950
- }
52951
- return null;
52952
- });
53166
+ const date = toTimeOfDay(children);
52953
53167
  // toDate turns a non-finite number into an Invalid Date, which is an object
52954
53168
  if (!date || isNaN(date.getTime())) {
52955
53169
  return jsx(TimeText, {
@@ -52986,7 +53200,9 @@ const TimeTime = ({
52986
53200
  text = formatMinuteDuration(totalMinutes, {
52987
53201
  lang,
52988
53202
  format,
52989
- clockStyle: true
53203
+ clockStyle: true,
53204
+ pad,
53205
+ precision
52990
53206
  });
52991
53207
  } else if (format !== "long") {
52992
53208
  // short/narrow/compact: keep the "0 h"/"0h" hour part instead of
@@ -52996,7 +53212,9 @@ const TimeTime = ({
52996
53212
  text = formatMinuteDuration(totalMinutes, {
52997
53213
  lang,
52998
53214
  format,
52999
- clockStyle: true
53215
+ clockStyle: true,
53216
+ pad,
53217
+ precision
53000
53218
  });
53001
53219
  } else {
53002
53220
  const midnightWord = naviI18n("time.midnight", undefined, {
@@ -53287,6 +53505,111 @@ const TimeText = props => {
53287
53505
  ...props
53288
53506
  });
53289
53507
  };
53508
+
53509
+ /**
53510
+ * Displays a span between two instants — an opening slot, an availability
53511
+ * window — as the two `<time>` elements `<Time>` would render, around a
53512
+ * separator.
53513
+ *
53514
+ * On top of writing the separator, it makes the two bounds agree on how
53515
+ * precisely they are written, which is a property of the pair and of nothing
53516
+ * else: with `type="time" format="compact" pad={false}`, "08:00"–"10:00" reads
53517
+ * "8h–10h", but "11:30"–"14:00" reads "11h30–14h00" and not "11h30–14h", where
53518
+ * the eye stops on the difference of shape before it reads the hours. Any bound
53519
+ * with minutes gives minutes to both, zero included. The padded clock
53520
+ * (`pad` left at its default) already writes every bound at the same width, and
53521
+ * the spelled-out formats say their units in words, so neither needs the rule
53522
+ * and neither is touched by it.
53523
+ *
53524
+ * @param {Date|number|string} from
53525
+ * The start of the span, in whatever `<Time>` accepts for this `type`.
53526
+ * @param {Date|number|string} to
53527
+ * The end of the span. An undefined bound renders `<Time>`'s own placeholder.
53528
+ * @param {"date"|"month"|"datetime"|"time"|"hour"|"minute"|"second"} [type="time"]
53529
+ * Passed to both bounds. Only `"time"` gets the shared-precision rule; the
53530
+ * other types are written one after the other, with nothing factored out (a
53531
+ * date span reads "11 mai – 14 mai", never "du 11 au 14 mai").
53532
+ * @param {"hour"|"minute"} [precision]
53533
+ * Writes both bounds at this precision instead of the one the pair calls for
53534
+ * — `"minute"` to keep a zero minute on both ("8h00–10h00"), `"hour"` to drop
53535
+ * it on both ("8h–11h30", which is the shape the rule exists to avoid: set it
53536
+ * only when you mean it).
53537
+ * @param {string} [separator]
53538
+ * What goes between the two bounds. Defaults to the `"time.range_separator"`
53539
+ * navi text (an en dash), tightened against both bounds in `format="compact"`
53540
+ * — where the span is one short token — and spaced out otherwise.
53541
+ *
53542
+ * Every other prop is forwarded to both bounds; see `<Time>`.
53543
+ */
53544
+ const TimeRange = ({
53545
+ from,
53546
+ to,
53547
+ type = "time",
53548
+ format = "long",
53549
+ lang = languagesSignal.value,
53550
+ pad = true,
53551
+ precision,
53552
+ separator = naviI18n("time.range_separator", undefined, {
53553
+ lang
53554
+ }),
53555
+ ...props
53556
+ }) => {
53557
+ const boundProps = {
53558
+ type,
53559
+ format,
53560
+ lang
53561
+ };
53562
+ if (type === "time") {
53563
+ boundProps.pad = pad;
53564
+ boundProps.precision = precision ?? resolvePairPrecision(from, to, {
53565
+ format,
53566
+ pad
53567
+ });
53568
+ }
53569
+ // compact writes the whole span as one short token ("8h–10h"): nothing
53570
+ // around the separator, and no break inside it. The other formats are
53571
+ // phrases — they get room around the separator, and may wrap there.
53572
+ const tight = format === "compact";
53573
+ return jsxs(Text, {
53574
+ noWrap: tight,
53575
+ ...props,
53576
+ children: [jsx(Time, {
53577
+ ...boundProps,
53578
+ children: from
53579
+ }), tight ? separator : ` ${separator} `, jsx(Time, {
53580
+ ...boundProps,
53581
+ children: to
53582
+ })]
53583
+ });
53584
+ };
53585
+
53586
+ // The two bounds of a span are written to the same precision, decided by the
53587
+ // pair: "8h–10h" as long as neither has minutes, "11h30–14h00" as soon as one
53588
+ // of them does. Only ever a question for the unpadded compact clock — the
53589
+ // padded one always writes "08h00", and the spelled-out formats name their
53590
+ // units, leaving no shape for the eye to trip on.
53591
+ const resolvePairPrecision = (from, to, {
53592
+ format,
53593
+ pad
53594
+ }) => {
53595
+ if (pad || format !== "compact") {
53596
+ return "minute";
53597
+ }
53598
+ const hasMinutes = value => {
53599
+ const date = toTimeOfDay(value);
53600
+ return Boolean(date) && !isNaN(date.getTime()) && date.getMinutes() !== 0;
53601
+ };
53602
+ return hasMinutes(from) || hasMinutes(to) ? "minute" : "hour";
53603
+ };
53604
+ const toTimeOfDay = value => {
53605
+ return toDate(value, string => {
53606
+ if (/^\d{2}:\d{2}(?::\d{2})?$/.test(string)) {
53607
+ const d = new Date(`1970-01-01T${string}`);
53608
+ return isNaN(d.getTime()) ? null : d;
53609
+ }
53610
+ return null;
53611
+ });
53612
+ };
53290
53613
  const toDate = (value, parseString) => {
53291
53614
  if (value instanceof Date) {
53292
53615
  return value;
@@ -53768,7 +54091,7 @@ const createOpenController = (
53768
54091
  // Last: the close effects above are what starts the exit transition the
53769
54092
  // content must outlive (see popup_content_mount.js).
53770
54093
  controller.unmountContent?.();
53771
- controller.onOpenedChange?.(false);
54094
+ controller.onOpenedChange?.(false, closeEvent);
53772
54095
  };
53773
54096
  const controller = {
53774
54097
  opened: false,
@@ -53788,9 +54111,10 @@ const createOpenController = (
53788
54111
  // away on close (`unmountWhenClosed`). Called from performClose above.
53789
54112
  unmountContent: null,
53790
54113
  // Told whenever `opened` actually changes, whatever asked for it — an
53791
- // interaction, a command, a prop. What lets a `signal` prop reflect the
53792
- // popup's real state (see useOpenPropsEffectOnOpenController), called once
53793
- // the open/close has fully happened rather than mid-sequence.
54114
+ // interaction, a command, a prop — with the event that asked. What lets a
54115
+ // `signal` prop reflect the popup's real state, and a `navState` prop write
54116
+ // it into the history entry (see useOpenPropsEffectOnOpenController);
54117
+ // called once the open/close has fully happened rather than mid-sequence.
53794
54118
  onOpenedChange: null,
53795
54119
  open: (e, detail) => {
53796
54120
  if (controller.opened || !controller.openEffect) {
@@ -53894,7 +54218,7 @@ const createOpenController = (
53894
54218
  openEffectReturnValue?.(closeEvent);
53895
54219
  };
53896
54220
  closeHandlers = openHandler(requestOpenEvent) || null;
53897
- controller.onOpenedChange?.(true);
54221
+ controller.onOpenedChange?.(true, requestOpenEvent);
53898
54222
  },
53899
54223
  requestClose: (
53900
54224
  e = new CustomEvent("programmatic", { detail: {} }),
@@ -54014,33 +54338,83 @@ const scheduleMountOpen = (run) => {
54014
54338
  });
54015
54339
  };
54016
54340
 
54341
+ // Where the popup's open state is kept, when it is kept anywhere: `navState`
54342
+ // resolved to the `{ id, type }` useNavState wants.
54343
+ //
54344
+ // `true` takes the popup's own id — a popup a `--navi-open` command can name is
54345
+ // a popup that already has a stable one, and that id is what identifies its
54346
+ // open state too.
54347
+ const NO_NAV_STATE = { id: undefined, type: "replace" };
54348
+ const resolveNavStateProp = (navState, popupId) => {
54349
+ if (!navState) {
54350
+ return NO_NAV_STATE;
54351
+ }
54352
+ if (navState === true) {
54353
+ return { id: popupId, type: "replace" };
54354
+ }
54355
+ if (typeof navState === "string") {
54356
+ return { id: navState, type: "replace" };
54357
+ }
54358
+ return { id: navState.id || popupId, type: navState.type || "replace" };
54359
+ };
54360
+
54017
54361
  /**
54018
- * Keeps an open controller in sync with a plain `open`/`defaultOpen` pair
54019
- * shared between `useOpenControllerByProps` below (Dialog/Popover driving
54020
- * their own controller) and `picker_custom.jsx` (which derives its own
54021
- * boolean from history state instead of a literal `open` prop, but needs
54022
- * the exact same skip-if-already-matching / open-or-requestClose control
54023
- * flow, via a small `{ open, requestClose, opened }` adapter around its own
54024
- * `requestOpen`/`requestClose` wrappers).
54362
+ * Keeps an open controller in sync with where the caller says the popup should
54363
+ * be: an `open`/`defaultOpen` pair, a `signal`, or a `navState` — the open
54364
+ * state written into the history entry, so a screen left and come back to finds
54365
+ * its popup as it was.
54366
+ *
54367
+ * Shared between `useOpenControllerByProps` below (Dialog/Popover driving their
54368
+ * own controller) and `picker_custom.jsx` (which owns its controller but wants
54369
+ * the same skip-if-already-matching / open-or-requestClose control flow).
54025
54370
  *
54026
54371
  * @param {{ open: (e: Event, detail?: object) => void, requestClose: (e: Event, detail?: object) => void, opened: boolean }} openController
54027
- * @param {{ open?: boolean|"interaction", defaultOpen?: boolean|"interaction", signal?: import("@preact/signals").Signal<boolean> }} props
54372
+ * @param {{ id?: string, open?: boolean|"interaction", defaultOpen?: boolean|"interaction", signal?: import("@preact/signals").Signal<boolean>, navState?: boolean|string|{id?: string, type?: "push"|"replace"} }} props
54028
54373
  */
54029
54374
  const useOpenPropsEffectOnOpenController = (openController, props) => {
54030
- const { signal, defaultOpen } = props;
54031
- // What the caller holds, however they hold it: an `open` they re-render
54032
- // themselves, or a `signal` this hook also writes (see onOpenedChange below).
54033
- // Reading .value during render is what subscribes the popup to it.
54034
- const open = signal ? signal.value : props.open;
54375
+ const { signal, defaultOpen, navState } = props;
54376
+ const { id: navStateId, type: navStateType } = resolveNavStateProp(
54377
+ navState,
54378
+ props.id,
54379
+ );
54380
+ // Called unconditionally (it answers with no-ops for an absent id), like
54381
+ // every other hook here.
54382
+ const [navStateValue, enterNavState, leaveNavState] = useNavState(
54383
+ navStateId,
54384
+ { type: navStateType },
54385
+ );
54386
+ // What the caller holds, however they hold it: the history entry when there
54387
+ // is a `navState`, an `open` they re-render themselves, or a `signal` this
54388
+ // hook also writes (see onOpenedChange below). Reading .value during render
54389
+ // is what subscribes the popup to a signal; reading the document state is
54390
+ // what subscribes it to the history entry, back button included.
54391
+ const open = navStateId
54392
+ ? Boolean(navStateValue)
54393
+ : signal
54394
+ ? signal.value
54395
+ : props.open;
54035
54396
  // Assigned on every render, like openEffect, so it always closes over the
54036
54397
  // latest prop: a popup that opens or closes on its own (Escape, backdrop, a
54037
- // --navi-close command) writes what happened into the signal, so whoever
54038
- // holds it always reads where the popup is.
54039
- openController.onOpenedChange = signal
54040
- ? (opened) => {
54041
- signal.value = opened;
54042
- }
54043
- : null;
54398
+ // --navi-close command) writes what happened where the caller keeps it, so
54399
+ // whoever holds it always reads where the popup is.
54400
+ openController.onOpenedChange =
54401
+ navStateId || signal
54402
+ ? (opened, event) => {
54403
+ if (navStateId) {
54404
+ if (opened) {
54405
+ enterNavState();
54406
+ } else {
54407
+ // Under type "push" a cancel goes back rather than rewriting the
54408
+ // entry, so everything else written to the url while the popup
54409
+ // was open goes back with it (see useNavState's own leave()).
54410
+ leaveNavState({ isBack: Boolean(event?.detail?.isCancel) });
54411
+ }
54412
+ }
54413
+ if (signal) {
54414
+ signal.value = opened;
54415
+ }
54416
+ }
54417
+ : null;
54044
54418
  // Tracks whether the effect below has ever run before — only the very
54045
54419
  // first run gets the "mount already open" treatment (`open` truthy from
54046
54420
  // the start, or the uncontrolled, mount-only `defaultOpen`); every
@@ -54093,12 +54467,15 @@ const useOpenPropsEffectOnOpenController = (openController, props) => {
54093
54467
  { isCancel: true },
54094
54468
  );
54095
54469
  }
54470
+ // The request can be refused (a busy form denying the close): the popup
54471
+ // then stays where it was, and whoever holds the open state is told so —
54472
+ // otherwise it would keep saying "closed" about a popup still open.
54096
54473
  if (signal) {
54097
- // The request can be refused (a busy form denying the close): the popup
54098
- // then stays where it was, and the signal is told so — otherwise it
54099
- // would keep saying "closed" about a popup still open.
54100
54474
  signal.value = openController.opened;
54101
54475
  }
54476
+ if (navStateId && openController.opened) {
54477
+ enterNavState();
54478
+ }
54102
54479
  }, [open]);
54103
54480
  };
54104
54481
 
@@ -54358,7 +54735,14 @@ const usePopupContentMount = (
54358
54735
  *
54359
54736
  * `animation="slide-from-*"` (anchorReference/point mode only): a real
54360
54737
  * translate-based entrance, 8 directions (cardinal + 4 diagonals), each
54361
- * 100%-of-own-size. Popover always resolves `animation="auto"`/`"sliding"`
54738
+ * 100%-of-own-size. It travels through `transform`, not through the
54739
+ * `translate` property, which belongs to the popup's own placement
54740
+ * (applyNewPosition in visible_rect.js — see its doc for why the placement is
54741
+ * a transform at all, and why it has to be the outermost one: the individual
54742
+ * transform properties apply translate, then rotate, then scale, then
54743
+ * `transform`, so both the travel here and the `scale` below compose *under* a
54744
+ * placement that stays where it was put).
54745
+ * Popover always resolves `animation="auto"`/`"sliding"`
54362
54746
  * to one of these concretely in JS (see popover.jsx's
54363
54747
  * `resolveDirectionValue`), so there's no bare `animation="sliding"`
54364
54748
  * selector here at all — a point/corner has no anchor edge to grow out of,
@@ -54399,14 +54783,15 @@ const popupCss = /* css */ `
54399
54783
 
54400
54784
  .navi_popover,
54401
54785
  .navi_dialog {
54402
- /* left/top are deliberately absent from this list — applyNewPosition
54403
- (visible_rect.js) drives that transition itself via the Web
54404
- Animations API instead of CSS, so it stays independent of whatever
54405
- this list contains (no shared transition-property to clobber, no
54406
- propertyName to filter). */
54786
+ /* The translate property is deliberately absent from this list — it
54787
+ carries where the popup stands, and applyNewPosition (visible_rect.js)
54788
+ owns it and drives its own transition through the Web Animations API
54789
+ instead of CSS, so it stays independent of whatever this list contains
54790
+ (no shared transition-property to clobber, no propertyName to filter).
54791
+ What moves here is transform, which composes under it. */
54407
54792
  &[navi-animation] {
54408
54793
  transition-property:
54409
- display, overlay, opacity, translate, scale, box-shadow;
54794
+ display, overlay, opacity, transform, scale, box-shadow;
54410
54795
  transition-duration:
54411
54796
  var(--popup-animation-duration), var(--popup-animation-duration),
54412
54797
  var(--popup-opacity-duration), var(--popup-translate-duration),
@@ -54435,7 +54820,7 @@ const popupCss = /* css */ `
54435
54820
  centered, no direction involved. */
54436
54821
  &[navi-animation="scaling"] {
54437
54822
  opacity: 1;
54438
- translate: 0 0;
54823
+ transform: translate(0px, 0px);
54439
54824
  scale: 1;
54440
54825
  &[aria-expanded="false"] {
54441
54826
  opacity: 0;
@@ -54487,14 +54872,16 @@ const popupCss = /* css */ `
54487
54872
  &[navi-animation="slide-from-bottom-left"],
54488
54873
  &[navi-animation="slide-from-bottom-right"] {
54489
54874
  opacity: 1;
54490
- translate: 0 0;
54875
+ transform: translate(0px, 0px);
54491
54876
 
54492
54877
  /* No fade: the travel is the whole effect. Fading it out on top would
54493
54878
  make the popup disappear before it has finished leaving, which reads as
54494
54879
  two things happening rather than one movement. */
54495
54880
  &[aria-expanded="false"] {
54496
- translate: calc(var(--x-popup-slide-x, 0) * 100%)
54497
- calc(var(--x-popup-slide-y, -1) * 100%);
54881
+ transform: translate(
54882
+ calc(var(--x-popup-slide-x, 0) * 100%),
54883
+ calc(var(--x-popup-slide-y, -1) * 100%)
54884
+ );
54498
54885
  }
54499
54886
  }
54500
54887
  }
@@ -54686,14 +55073,17 @@ const createSwipeToClose = (side, { grip } = {}) => {
54686
55073
  }
54687
55074
  }
54688
55075
 
54689
- // Where the panel stands, written on it directly: the gesture reports a
54690
- // distance in screen coordinates, which is exactly what a translate takes.
55076
+ // How far the panel has been pulled, written on it directly: the gesture
55077
+ // reports a distance in screen coordinates, which is exactly what a
55078
+ // translate takes. It goes through `transform` because the `translate`
55079
+ // property carries where the panel *stands* (applyNewPosition in
55080
+ // visible_rect.js, which owns it) — the pull composes under the placement
55081
+ // instead of replacing it.
54691
55082
  const paint = (distance) => {
54692
- panelEl.style.translate =
54693
- axis === "x" ? `${distance}px 0px` : `0px ${distance}px`;
55083
+ panelEl.style.transform = translateOf(axis, distance);
54694
55084
  };
54695
55085
  const restore = () => {
54696
- panelEl.style.translate = "";
55086
+ panelEl.style.transform = "";
54697
55087
  panelEl.style.transitionProperty = "";
54698
55088
  panelEl.style.userSelect = "";
54699
55089
  };
@@ -54705,8 +55095,8 @@ const createSwipeToClose = (side, { grip } = {}) => {
54705
55095
  paint(to);
54706
55096
  const animation = panelEl.animate(
54707
55097
  [
54708
- { translate: translateOf(axis, from) },
54709
- { translate: translateOf(axis, to) },
55098
+ { transform: translateOf(axis, from) },
55099
+ { transform: translateOf(axis, to) },
54710
55100
  ],
54711
55101
  {
54712
55102
  duration: (covered / sizeOf(panelEl, axis)) * TRAVEL_DURATION,
@@ -54776,7 +55166,9 @@ const sizeOf = (element, axis) => {
54776
55166
  return axis === "x" ? rect.width : rect.height;
54777
55167
  };
54778
55168
  const translateOf = (axis, distance) =>
54779
- axis === "x" ? `${distance}px 0px` : `0px ${distance}px`;
55169
+ axis === "x"
55170
+ ? `translate(${distance}px, 0px)`
55171
+ : `translate(0px, ${distance}px)`;
54780
55172
 
54781
55173
  const PopupClose = ({
54782
55174
  label,
@@ -54973,11 +55365,17 @@ const css$E = /* css */`
54973
55365
  containing block is genuinely its nearest positioned ancestor,
54974
55366
  regardless of positionArea. See the [data-layer="top"] rule below for
54975
55367
  why the via-attribute renderer overrides this. Position is always
54976
- JS-driven (pickPositionRelativeTo sets top/left directly, see
54977
- useDialogProps below) — no CSS alignment/inset math here at all,
54978
- unlike an earlier version of this file. */
55368
+ JS-driven (pickPositionRelativeTo, see useDialogProps below) — no CSS
55369
+ alignment/inset math here at all. */
54979
55370
  position: absolute;
54980
- inset: unset;
55371
+ /* Laid out at its containing block's own origin and moved from there by a
55372
+ translate (applyNewPosition), never by left/top: a shrink-to-fit box
55373
+ placed with left is only ever as wide as what is left of the container
55374
+ to its right, and that width is what decides where it gets placed — see
55375
+ applyNewPosition's own doc. right/bottom stay auto: an inset there would
55376
+ over-constrain the box against the UA's margin: auto and re-center
55377
+ it. */
55378
+ inset: 0 auto auto 0;
54981
55379
  /* Custom renderer only — see openLocalDialogCount above */
54982
55380
  z-index: calc(var(--navi-z-index-popup) + var(--dialog-stack-order, 0));
54983
55381
  min-width: min(
@@ -55073,11 +55471,12 @@ const css$E = /* css */`
55073
55471
  &[data-flush-bottom][data-flush-left] {
55074
55472
  border-bottom-left-radius: 0;
55075
55473
  }
55076
- /* left/top are NOT transitioned here applyNewPosition (visible_rect.js)
55077
- drives that itself via the Web Animations API instead of CSS, so it
55078
- stays independent from navi-animation's own opacity/scale/display
55079
- transition list below (no shared transition-property to clobber, no
55080
- propertyName to filter). */
55474
+ /* The placement is a translate, so the translate property is spoken for
55475
+ here (see applyNewPosition in visible_rect.js, which owns it and animates
55476
+ it itself through the Web Animations API rather than through this file's
55477
+ transitions no shared transition-property to clobber, no propertyName
55478
+ to filter). An entrance animation moves the dialog through scale and
55479
+ transform instead, which compose under it: see popup_css.js. */
55081
55480
 
55082
55481
  &::backdrop {
55083
55482
  background: var(--navi-backdrop-close-background);
@@ -55157,7 +55556,7 @@ const css$E = /* css */`
55157
55556
  containing block is the viewport rather than any positioned
55158
55557
  ancestor. Not left to the native :modal UA stylesheet's own default
55159
55558
  (also position: fixed, but with its own margin/inset assumptions) so
55160
- that JS-set top/left (see useDialogProps below) always wins
55559
+ that the JS-driven placement (see useDialogProps below) always wins
55161
55560
  cleanly. */
55162
55561
  &[data-layer="top"] {
55163
55562
  position: fixed;
@@ -55442,6 +55841,16 @@ const css$E = /* css */`
55442
55841
  * actually closes — not preventable (see `open_controller.js`'s own
55443
55842
  * `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
55444
55843
  * veto a close instead).
55844
+ * @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
55845
+ * Keeps the open state in the history entry, so a screen left and come back
55846
+ * to finds this popup as it was — open, and without an entrance playing: it
55847
+ * was already open when the page reappeared. `true` stores it under the
55848
+ * popup's own `id`; a string names the key instead.
55849
+ * `{ type: "push" }` also makes the opening a history entry of its own, so
55850
+ * the back button closes the popup rather than leaving the screen — and a
55851
+ * cancel (Escape) goes back, taking whatever was written to the url while it
55852
+ * was open with it. The state belongs to the entry that wrote it: a
55853
+ * navigation that stacks a new entry does not carry it along.
55445
55854
  * @param {object} [props.openController] - Advanced: an externally-owned
55446
55855
  * open controller (see `open_controller.js`) for a caller that wants to
55447
55856
  * drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
@@ -55509,6 +55918,7 @@ const UncontrolledDialog = props => {
55509
55918
  open: undefined,
55510
55919
  signal: undefined,
55511
55920
  defaultOpen: undefined,
55921
+ navState: undefined,
55512
55922
  onClose: undefined,
55513
55923
  openController: openController,
55514
55924
  onnavi_request_open: e => {
@@ -56088,8 +56498,8 @@ const useDialogProps = props => {
56088
56498
  rectEffect.disconnect();
56089
56499
  });
56090
56500
  // A descendant anchored to something inside this dialog (a Callout, a
56091
- // nested Popover) needing to know about this dialog's own left/top
56092
- // repositioning transition — not just that the target changed
56501
+ // nested Popover) needing to know about this dialog's own repositioning
56502
+ // transition — not just that the target changed
56093
56503
  // (navi_position_change above), but that a real, currently-playing
56094
56504
  // transition is moving it right now — is handled generically by
56095
56505
  // applyNewPosition itself (see its own notifyPositionTransition), since
@@ -56536,7 +56946,14 @@ const css$D = /* css */`
56536
56946
  whether it has a real anchor — this file's top comment has the full
56537
56947
  reasoning. */
56538
56948
  position: absolute;
56539
- inset: unset;
56949
+ /* Laid out at its containing block's own origin and moved from there by a
56950
+ translate (applyNewPosition), never by left/top: a shrink-to-fit box
56951
+ placed with left is only ever as wide as what is left of the container
56952
+ to its right, and that width is what decides which side it gets placed
56953
+ on — see applyNewPosition's own doc. right/bottom stay auto: an inset
56954
+ there would over-constrain the box against the UA's margin: auto and
56955
+ re-center it. */
56956
+ inset: 0 auto auto 0;
56540
56957
  /* Custom renderer only: --popover-stack-order is set to
56541
56958
  openLocalPopoverCount on every open (see openEffect below) so the
56542
56959
  most-recently-opened local popover always outranks an earlier one,
@@ -56568,35 +56985,13 @@ const css$D = /* css */`
56568
56985
  (see box.jsx), which is what makes it scroll — and what a
56569
56986
  header/footer/body inside it then rearranges. */
56570
56987
  overscroll-behavior: none;
56571
- /* left/top are NOT transitioned here applyNewPosition (visible_rect.js)
56572
- drives that itself via the Web Animations API instead of CSS, so it
56573
- stays independent from navi-animation's own opacity/scale/display
56574
- transition list below (no shared transition-property to clobber, no
56575
- propertyName to filter). */
56576
- /* overflow is not declared here: the popover carries [data-scrollable]
56577
- (see box.jsx), which is what makes it scroll — and what a
56578
- header/footer/body inside it then rearranges. */
56579
- overscroll-behavior: none;
56580
-
56581
- /* overflow is not declared here: the popover carries [data-scrollable]
56582
- (see box.jsx), which is what makes it scroll — and what a
56583
- header/footer/body inside it then rearranges. */
56584
- overscroll-behavior: none;
56585
- /* left/top are NOT transitioned here — applyNewPosition (visible_rect.js)
56586
- drives that itself via the Web Animations API instead of CSS, so it
56587
- stays independent from navi-animation's own opacity/scale/display
56588
- transition list below (no shared transition-property to clobber, no
56589
- propertyName to filter). */
56590
- /* overflow is not declared here: the popover carries [data-scrollable]
56591
- (see box.jsx), which is what makes it scroll — and what a
56592
- header/footer/body inside it then rearranges. */
56593
- overscroll-behavior: none;
56988
+ /* The placement is a translate, so the translate property is spoken for
56989
+ here (see applyNewPosition in visible_rect.js, which owns it and animates
56990
+ it itself through the Web Animations API rather than through this file's
56991
+ transitions no shared transition-property to clobber, no propertyName
56992
+ to filter). An entrance animation moves the popover through scale and
56993
+ transform instead, which compose under it: see popup_css.js. */
56594
56994
 
56595
- /* left/top are NOT transitioned here — applyNewPosition (visible_rect.js)
56596
- drives that itself via the Web Animations API instead of CSS, so it
56597
- stays independent from navi-animation's own opacity/scale/display
56598
- transition list below (no shared transition-property to clobber, no
56599
- propertyName to filter). */
56600
56995
  /* The via-attribute renderer starts hidden for free (native UA default
56601
56996
  for any [popover] element, same as <dialog> without [open]) — the
56602
56997
  custom renderer is a plain div with no such native default, so
@@ -56900,6 +57295,16 @@ const css$D = /* css */`
56900
57295
  * actually closes — not preventable (see `open_controller.js`'s own
56901
57296
  * `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
56902
57297
  * veto a close instead).
57298
+ * @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
57299
+ * Keeps the open state in the history entry, so a screen left and come back
57300
+ * to finds this popup as it was — open, and without an entrance playing: it
57301
+ * was already open when the page reappeared. `true` stores it under the
57302
+ * popup's own `id`; a string names the key instead.
57303
+ * `{ type: "push" }` also makes the opening a history entry of its own, so
57304
+ * the back button closes the popup rather than leaving the screen — and a
57305
+ * cancel (Escape) goes back, taking whatever was written to the url while it
57306
+ * was open with it. The state belongs to the entry that wrote it: a
57307
+ * navigation that stacks a new entry does not carry it along.
56903
57308
  * @param {object} [props.openController] - Advanced: an externally-owned
56904
57309
  * open controller (see `open_controller.js`) for a caller that wants to
56905
57310
  * drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
@@ -56965,6 +57370,7 @@ const UncontrolledPopover = props => {
56965
57370
  open: undefined,
56966
57371
  signal: undefined,
56967
57372
  defaultOpen: undefined,
57373
+ navState: undefined,
56968
57374
  onClose: undefined,
56969
57375
  openController: openController,
56970
57376
  onnavi_request_open: e => {
@@ -57580,7 +57986,7 @@ const usePopoverProps = props => {
57580
57986
  });
57581
57987
  // A descendant anchored to something inside this popover (a Callout, a
57582
57988
  // further-nested Popover) needing to know about this popover's own
57583
- // left/top repositioning transition — not just that the target changed
57989
+ // repositioning transition — not just that the target changed
57584
57990
  // (navi_position_change above), but that a real, currently-playing
57585
57991
  // transition is moving it right now — is handled generically by
57586
57992
  // applyNewPosition itself (see its own notifyPositionTransition in
@@ -80607,5 +81013,5 @@ const UserSvg = () => jsx("svg", {
80607
81013
  })
80608
81014
  });
80609
81015
 
80610
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, 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, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isOfflineError, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, 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, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
81016
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, 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, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isOfflineError, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, 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, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
80611
81017
  //# sourceMappingURL=jsenv_navi.js.map