@jsenv/navi 0.29.45 → 0.29.46
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 +268 -69
- package/dist/jsenv_navi.js.map +30 -19
- package/dist/jsenv_navi_side_effects.js +26 -3
- package/dist/jsenv_navi_side_effects.js.map +2 -2
- package/docs/AI_INSTRUCTIONS.md +9 -0
- package/docs/actions.md +22 -0
- package/docs/control_value.md +6 -0
- package/docs/create_and_edit.md +468 -0
- package/docs/form_changed.md +2 -0
- package/docs/navigation.md +15 -0
- package/docs/scroll.md +14 -5
- package/docs/z_index.md +21 -8
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* AI reading this file: read ../docs/AI_INSTRUCTIONS.md for context on
|
|
3
3
|
* using @jsenv/navi as intended.
|
|
4
4
|
*/
|
|
5
|
-
import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth,
|
|
5
|
+
import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
|
+
export { coarsePointerSignal } from "./jsenv_navi_side_effects.js";
|
|
6
7
|
import { elementIsFocusable, createPubSub, dispatchInternalCustomEvent, dispatchCustomEvent, getElementSignature, findEvent, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, 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, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, findBefore, findAfter, initFocusGroup, scrollIntoViewScoped, getScrollContainer, canScroll, measureWidestChildRow, performTabNavigation, wheelGestureIsTakenFrom, releaseWheelGesture, claimWheelGesture, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, stringifyStyle as stringifyStyle$1 } from "@jsenv/dom";
|
|
7
8
|
export { contrastColor, findEvent, startDragTo } from "@jsenv/dom";
|
|
8
9
|
import { signal, computed, effect, batch, useSignal } from "@preact/signals";
|
|
@@ -49,17 +50,28 @@ const css$10 = /* css */`
|
|
|
49
50
|
--navi-z-index-control-focused: 2;
|
|
50
51
|
|
|
51
52
|
/* Kept stuck while something scrolls under it: a list header, the head
|
|
52
|
-
and foot of a side panel, a table's sticky cells
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
sticky part
|
|
61
|
-
it
|
|
62
|
-
|
|
53
|
+
and foot of a side panel, a table's sticky cells, the header and
|
|
54
|
+
footer of any scrolling Box. Above raised controls — a control
|
|
55
|
+
scrolling past must go under the header that pins the column it
|
|
56
|
+
belongs to, never over it.
|
|
57
|
+
|
|
58
|
+
A sticky element is a positioned one, so it already wins against
|
|
59
|
+
everything in the flow — the band is what it takes to also win against
|
|
60
|
+
what the page positioned itself, which loses to DOM order otherwise
|
|
61
|
+
(a sticky part is written before what scrolls under it). Box applies
|
|
62
|
+
it by default, isolated, and lets a call site write auto back:
|
|
63
|
+
--box-header-z-index / --box-footer-z-index.
|
|
64
|
+
|
|
65
|
+
"While stuck" is the condition the name states, and it costs something
|
|
66
|
+
to ignore: a sticky part at rest is a block in the flow with nothing
|
|
67
|
+
passing under it, and the band there is what slices whatever a
|
|
68
|
+
neighbouring row lets out of its box. CSS cannot express the
|
|
69
|
+
condition — an element cannot read its own stuck state — so it takes
|
|
70
|
+
measuring, which List does (it marks its parts with a navi-stuck
|
|
71
|
+
attribute against its own scroller and applies the band only there,
|
|
72
|
+
see --list-*-z-index in list.jsx) and Box does not: a generic
|
|
73
|
+
scrolling area does not know what it was given to scroll, and dropping
|
|
74
|
+
to auto there loses to a single position: relative. */
|
|
63
75
|
--navi-z-index-sticky: 10;
|
|
64
76
|
|
|
65
77
|
/* Pinned to the viewport, over the whole page: FixedBar. A decade of its
|
|
@@ -19030,14 +19042,24 @@ import.meta.css = [/* css */`
|
|
|
19030
19042
|
|
|
19031
19043
|
[data-scrollable] {
|
|
19032
19044
|
overflow: var(--x-scrollable-overflow, auto);
|
|
19045
|
+
--box-header-z-index: var(--navi-z-index-sticky);
|
|
19046
|
+
--box-footer-z-index: var(--navi-z-index-sticky);
|
|
19047
|
+
/* The band stays inside this box: without a stacking context here, "in
|
|
19048
|
+
front of my body" would be read as "in front of everything on the page"
|
|
19049
|
+
and a header would reach past a bar or a popup — which is exactly what
|
|
19050
|
+
the decades in navi_z_indexes.js are there to prevent. See
|
|
19051
|
+
docs/z_index.md. */
|
|
19052
|
+
isolation: isolate;
|
|
19033
19053
|
|
|
19034
19054
|
&[data-scrollable-overflow="scroll"] {
|
|
19035
19055
|
--x-scrollable-overflow: scroll;
|
|
19036
19056
|
}
|
|
19037
19057
|
|
|
19038
|
-
/* box-shadow
|
|
19039
|
-
|
|
19040
|
-
|
|
19058
|
+
/* A real border and not a box-shadow: a shadow is drawn outside the box, so
|
|
19059
|
+
it lands on top of whatever comes next in the painting order and loses to
|
|
19060
|
+
it — a body painting its own background over the line that was meant to
|
|
19061
|
+
separate them. The border belongs to the part itself and is always
|
|
19062
|
+
visible; the pixel it adds shifts nothing, these parts never shrink. */
|
|
19041
19063
|
/* The corners are the container's, not the part's: a header sitting at the
|
|
19042
19064
|
top of a rounded box has to follow that curve or it paints square over
|
|
19043
19065
|
it (a dark header in a rounded popup is where this shows). inherit and
|
|
@@ -19045,18 +19067,18 @@ import.meta.css = [/* css */`
|
|
|
19045
19067
|
> [data-header] {
|
|
19046
19068
|
position: sticky;
|
|
19047
19069
|
top: 0;
|
|
19048
|
-
z-index:
|
|
19070
|
+
z-index: var(--box-header-z-index);
|
|
19071
|
+
border-bottom: 1px solid var(--navi-separator-color-default);
|
|
19049
19072
|
border-top-left-radius: inherit;
|
|
19050
19073
|
border-top-right-radius: inherit;
|
|
19051
|
-
box-shadow: 0 1px 0 var(--navi-separator-color-default);
|
|
19052
19074
|
}
|
|
19053
19075
|
> [data-footer] {
|
|
19054
19076
|
position: sticky;
|
|
19055
19077
|
bottom: 0;
|
|
19056
|
-
z-index:
|
|
19078
|
+
z-index: var(--box-footer-z-index);
|
|
19079
|
+
border-top: 1px solid var(--navi-separator-color-default);
|
|
19057
19080
|
border-bottom-right-radius: inherit;
|
|
19058
19081
|
border-bottom-left-radius: inherit;
|
|
19059
|
-
box-shadow: 0 -1px 0 var(--navi-separator-color-default);
|
|
19060
19082
|
}
|
|
19061
19083
|
|
|
19062
19084
|
&:has(> [data-body]) {
|
|
@@ -19070,8 +19092,11 @@ import.meta.css = [/* css */`
|
|
|
19070
19092
|
|
|
19071
19093
|
> [data-header],
|
|
19072
19094
|
> [data-footer] {
|
|
19095
|
+
/* Nothing scrolls under them here — the body does that, next to them —
|
|
19096
|
+
so they are back to being blocks in the flow, and stacking is not
|
|
19097
|
+
their business anymore. */
|
|
19073
19098
|
position: static;
|
|
19074
|
-
z-index:
|
|
19099
|
+
z-index: auto;
|
|
19075
19100
|
flex-shrink: 0;
|
|
19076
19101
|
}
|
|
19077
19102
|
|
|
@@ -25441,6 +25466,13 @@ const useControlProps = (props, {
|
|
|
25441
25466
|
const onButtonInteractionAllowed = e => {
|
|
25442
25467
|
triggerUIAction(e);
|
|
25443
25468
|
const control = ref.current;
|
|
25469
|
+
if (!control) {
|
|
25470
|
+
// What the button just did took the button away: a command that
|
|
25471
|
+
// navigates, a popup closing over it. There is no control left to
|
|
25472
|
+
// ask for an action, and nothing is lost by not asking — what the
|
|
25473
|
+
// press was for has already happened.
|
|
25474
|
+
return;
|
|
25475
|
+
}
|
|
25444
25476
|
tryActionAfterInteractionAllowed(control, {
|
|
25445
25477
|
event: e,
|
|
25446
25478
|
action: boundAction,
|
|
@@ -29169,17 +29201,19 @@ const css$V = /* css */`
|
|
|
29169
29201
|
* shown via the non-modal `.show()` instead, staying in normal document
|
|
29170
29202
|
* flow inside its own positioned ancestor — confined to (and clipped by)
|
|
29171
29203
|
* that container instead of the whole viewport.
|
|
29172
|
-
* @param {boolean} [props.
|
|
29173
|
-
* (docked flush to the bottom edge, full width)
|
|
29174
|
-
* and leaves it alone otherwise. For a dialog meant to be interacted
|
|
29175
|
-
* rather than merely read:
|
|
29176
|
-
* the screen and a centered box ends up both cramped and out of thumb
|
|
29177
|
-
*
|
|
29178
|
-
*
|
|
29179
|
-
*
|
|
29180
|
-
*
|
|
29181
|
-
*
|
|
29182
|
-
*
|
|
29204
|
+
* @param {boolean} [props.dockedOnSmallTouchScreen] - Turns the dialog into a
|
|
29205
|
+
* bottom sheet (docked flush to the bottom edge, full width) on a small touch
|
|
29206
|
+
* screen, and leaves it alone otherwise. For a dialog meant to be interacted
|
|
29207
|
+
* with rather than merely read: on a phone the keyboard owns the bottom of
|
|
29208
|
+
* the screen and a centered box ends up both cramped and out of thumb reach,
|
|
29209
|
+
* while under a mouse the centered box is already the right shape. Both
|
|
29210
|
+
* halves of the name matter (`smallTouchScreenSignal`): touch alone would
|
|
29211
|
+
* dock a big touch screen — a tablet, a kiosk panel — a whole screen away
|
|
29212
|
+
* from where the finger just tapped, and size alone would dock a narrow
|
|
29213
|
+
* desktop window, which is still a mouse. It supplies defaults for
|
|
29214
|
+
* `positionArea`, `marginWithContainer`, `expandX` and `scrollCapture`, so
|
|
29215
|
+
* any of them can still be pinned explicitly. Re-resolves live as the pointer
|
|
29216
|
+
* type or the window size changes.
|
|
29183
29217
|
* @param {string} [props.positionArea="center"] - Where to dock the dialog
|
|
29184
29218
|
* within its container (the viewport for `layer="top"`, the positioned
|
|
29185
29219
|
* ancestor for `layer="local"`) — Dialog is never anchored to a real
|
|
@@ -29192,8 +29226,8 @@ const css$V = /* css */`
|
|
|
29192
29226
|
* `inset(top)`) for the overlapping variant.
|
|
29193
29227
|
* @param {boolean} [props.expand] - Shorthand for both `expandX` and `expandY`.
|
|
29194
29228
|
* @param {boolean} [props.expandX] - Stretches the dialog to the full width its
|
|
29195
|
-
* container allows (`--dialog-maxmax-width`). Set by
|
|
29196
|
-
* touch
|
|
29229
|
+
* container allows (`--dialog-maxmax-width`). Set by
|
|
29230
|
+
* `dockedOnSmallTouchScreen` on a small touch screen.
|
|
29197
29231
|
* @param {boolean} [props.expandY] - Same, vertically
|
|
29198
29232
|
* (`--dialog-maxmax-height`).
|
|
29199
29233
|
* @param {string|number} [props.marginWithContainer="3appw"] - Minimum gap kept
|
|
@@ -29223,7 +29257,7 @@ const css$V = /* css */`
|
|
|
29223
29257
|
* A `layer="local"` dialog always locks its own positioned ancestor's
|
|
29224
29258
|
* scroll while open (its backdrop only covers the scrollport, so scrolling
|
|
29225
29259
|
* there would reveal uncovered content); this prop extends the lock to the
|
|
29226
|
-
* whole page. Defaults to `true` for a dialog docked by `
|
|
29260
|
+
* whole page. Defaults to `true` for a dialog docked by `dockedOnSmallTouchScreen`.
|
|
29227
29261
|
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`} [props.animation]
|
|
29228
29262
|
* - `true`/`"auto"` resolves to `"scaling"` for a centered `positionArea`,
|
|
29229
29263
|
* or a concrete `"slide-from-*"` direction otherwise. Any other explicit
|
|
@@ -29406,10 +29440,10 @@ const DialogLocal = props => {
|
|
|
29406
29440
|
* contentProps]` — `backdropProps` is `null` for the via-attribute renderer
|
|
29407
29441
|
* (its own backdrop is native, not a real element).
|
|
29408
29442
|
*/
|
|
29409
|
-
// What a dialog turns into
|
|
29410
|
-
// the dialog in the zone a
|
|
29411
|
-
//
|
|
29412
|
-
//
|
|
29443
|
+
// What a dialog turns into on a small touch screen. "bottom" is not a taste:
|
|
29444
|
+
// it puts the dialog in the zone a phone is actually operated from — where the
|
|
29445
|
+
// thumbs rest and where the virtual keyboard comes up — instead of the middle
|
|
29446
|
+
// of the screen, which is the farthest point from both.
|
|
29413
29447
|
// Only defaults: an explicitly passed prop still wins, so the docked shape can
|
|
29414
29448
|
// be adjusted one axis at a time instead of being all-or-nothing.
|
|
29415
29449
|
const DOCKED = {
|
|
@@ -29447,7 +29481,7 @@ const useDialogProps = props => {
|
|
|
29447
29481
|
// .show() instead, staying in normal document flow, position: absolute
|
|
29448
29482
|
// relative to its own positioned ancestor. See this file's top comment.
|
|
29449
29483
|
layer = "top",
|
|
29450
|
-
|
|
29484
|
+
dockedOnSmallTouchScreen,
|
|
29451
29485
|
// Same grammar as Popover's own positionArea — see this file's top
|
|
29452
29486
|
// comment and popup_shared.js's parsePositionArea.
|
|
29453
29487
|
positionArea: positionAreaProp,
|
|
@@ -29500,9 +29534,10 @@ const useDialogProps = props => {
|
|
|
29500
29534
|
});
|
|
29501
29535
|
const isModal = layer === "top";
|
|
29502
29536
|
const ref = props.ref;
|
|
29503
|
-
// Only touch changes anything:
|
|
29504
|
-
//
|
|
29505
|
-
|
|
29537
|
+
// Only a small touch screen changes anything: on a mouse — and on a touch
|
|
29538
|
+
// screen too big to reach the bottom edge of — a dialog already wants to be
|
|
29539
|
+
// the centered box it is by default, so there is nothing to resolve.
|
|
29540
|
+
const isDocked = dockedOnSmallTouchScreen && smallTouchScreenSignal.value;
|
|
29506
29541
|
const positionArea = positionAreaProp ?? (isDocked ? DOCKED.positionArea : "center");
|
|
29507
29542
|
const marginWithContainer = marginWithContainerProp ?? (isDocked ? DOCKED.marginWithContainer :
|
|
29508
29543
|
// A share of whatever holds the dialog: the app's own screen for a
|
|
@@ -31645,8 +31680,8 @@ const css$T = /* css */`
|
|
|
31645
31680
|
* @property {"close"|"cancel"|"capture"|"none"} [pointerInteractionOutsideEffect]
|
|
31646
31681
|
* - What a click outside does. `"capture"`/`"none"` force an explicit answer
|
|
31647
31682
|
* by refusing to treat a click elsewhere as one.
|
|
31648
|
-
* @property {boolean} [
|
|
31649
|
-
* into a bottom sheet
|
|
31683
|
+
* @property {boolean} [dockedOnSmallTouchScreen] - `"dialog"` mode only: turn
|
|
31684
|
+
* the popup into a bottom sheet on a small touch screen.
|
|
31650
31685
|
* @property {(params: { message: import("ignore:preact").ComponentChildren }) => import("ignore:preact").ComponentChildren} [renderContent]
|
|
31651
31686
|
* - Replaces the popup body — the question and the two buttons — for every
|
|
31652
31687
|
* confirmation at once. The per-button `confirmPopupContent` prop is the same
|
|
@@ -31664,7 +31699,7 @@ const confirmPopupOptions = {
|
|
|
31664
31699
|
animationDuration: undefined,
|
|
31665
31700
|
positionArea: undefined,
|
|
31666
31701
|
pointerInteractionOutsideEffect: "close",
|
|
31667
|
-
|
|
31702
|
+
dockedOnSmallTouchScreen: false,
|
|
31668
31703
|
renderContent: undefined
|
|
31669
31704
|
};
|
|
31670
31705
|
|
|
@@ -31746,7 +31781,7 @@ const ConfirmPopup = ({
|
|
|
31746
31781
|
animationDuration,
|
|
31747
31782
|
positionArea,
|
|
31748
31783
|
pointerInteractionOutsideEffect,
|
|
31749
|
-
|
|
31784
|
+
dockedOnSmallTouchScreen,
|
|
31750
31785
|
renderContent
|
|
31751
31786
|
} = confirmPopupOptions;
|
|
31752
31787
|
|
|
@@ -31782,7 +31817,7 @@ const ConfirmPopup = ({
|
|
|
31782
31817
|
if (mode === "dialog") {
|
|
31783
31818
|
return jsx(Dialog, {
|
|
31784
31819
|
className: "navi_confirm_popup",
|
|
31785
|
-
|
|
31820
|
+
dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
|
|
31786
31821
|
...popupProps,
|
|
31787
31822
|
children: body
|
|
31788
31823
|
});
|
|
@@ -32037,6 +32072,16 @@ const debounceSignal = (
|
|
|
32037
32072
|
* The action will not fire while the user is actively changing filters; it fires once
|
|
32038
32073
|
* they pause for half a second.
|
|
32039
32074
|
*/
|
|
32075
|
+
// The run is not awaited here, and a rejection nobody waits for is an unhandled
|
|
32076
|
+
// one — in dev, an error overlay thrown over a page that is already saying what
|
|
32077
|
+
// went wrong. Nothing is lost by dropping it: the failure is held by the action
|
|
32078
|
+
// itself, and whoever reads it (useAsyncData, <Button action>) is what shows it.
|
|
32079
|
+
const runUnwatched = (result) => {
|
|
32080
|
+
if (result && typeof result.catch === "function") {
|
|
32081
|
+
result.catch(() => {});
|
|
32082
|
+
}
|
|
32083
|
+
};
|
|
32084
|
+
|
|
32040
32085
|
const actionRunEffect = (
|
|
32041
32086
|
action,
|
|
32042
32087
|
deriveActionParamsFromSignals,
|
|
@@ -32086,7 +32131,7 @@ const actionRunEffect = (
|
|
|
32086
32131
|
// falsy params, don't run
|
|
32087
32132
|
return;
|
|
32088
32133
|
}
|
|
32089
|
-
actionTarget.run({ reason: "truthy params first run" });
|
|
32134
|
+
runUnwatched(actionTarget.run({ reason: "truthy params first run" }));
|
|
32090
32135
|
return;
|
|
32091
32136
|
}
|
|
32092
32137
|
|
|
@@ -32103,16 +32148,20 @@ const actionRunEffect = (
|
|
|
32103
32148
|
}
|
|
32104
32149
|
if (!actionTargetPrevious.params) {
|
|
32105
32150
|
// coming from falsy-params state: action may already be cached, avoid unnecessary rerun
|
|
32106
|
-
|
|
32151
|
+
runUnwatched(
|
|
32152
|
+
actionTarget.run({ reason: "params restored from falsy state" }),
|
|
32153
|
+
);
|
|
32107
32154
|
} else {
|
|
32108
|
-
actionTarget.rerun({ reason: "params modified" });
|
|
32155
|
+
runUnwatched(actionTarget.rerun({ reason: "params modified" }));
|
|
32109
32156
|
}
|
|
32110
32157
|
}
|
|
32111
32158
|
},
|
|
32112
32159
|
...options,
|
|
32113
32160
|
});
|
|
32114
32161
|
if (actionParamsSignal.peek()) {
|
|
32115
|
-
|
|
32162
|
+
runUnwatched(
|
|
32163
|
+
actionRunnedByThisEffect.run({ reason: "initial truthy params" }),
|
|
32164
|
+
);
|
|
32116
32165
|
}
|
|
32117
32166
|
return actionRunnedByThisEffect;
|
|
32118
32167
|
};
|
|
@@ -35236,6 +35285,24 @@ const TYPE_CONVERTERS = {
|
|
|
35236
35285
|
},
|
|
35237
35286
|
};
|
|
35238
35287
|
|
|
35288
|
+
/**
|
|
35289
|
+
* A container has put its page on screen — or as much of it as it can.
|
|
35290
|
+
*
|
|
35291
|
+
* A route matching is a signal changing, and the page it selects reaches the
|
|
35292
|
+
* DOM only once Preact has rendered — an unknown number of passes later, in an
|
|
35293
|
+
* unknown number of microtasks. Anyone who needs the page as it IS rather than
|
|
35294
|
+
* as it has been decided (a travel about to have its picture taken by the
|
|
35295
|
+
* browser, see route_travel.jsx) waits for this instead of counting.
|
|
35296
|
+
*
|
|
35297
|
+
* A page waiting on data is announced too, by the boundary showing its loading
|
|
35298
|
+
* state (see Loading in use_async_data.jsx): what the container could put on
|
|
35299
|
+
* screen is what the browser is about to take a picture of, and a page that
|
|
35300
|
+
* cannot render yet would otherwise be waited on until the transition dies of
|
|
35301
|
+
* it. It lives in a module of its own for that: the async layer says it as much
|
|
35302
|
+
* as the router does, and neither can import the other.
|
|
35303
|
+
*/
|
|
35304
|
+
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
35305
|
+
|
|
35239
35306
|
const promiseStateWeakMap = new WeakMap();
|
|
35240
35307
|
const usePromiseAsyncData = (
|
|
35241
35308
|
promise,
|
|
@@ -35290,7 +35357,8 @@ const useForceRender = () => {
|
|
|
35290
35357
|
|
|
35291
35358
|
const useAsyncData = (promiseOrAction, {
|
|
35292
35359
|
loading = "delegate",
|
|
35293
|
-
error = "delegate"
|
|
35360
|
+
error = "delegate",
|
|
35361
|
+
onLoad
|
|
35294
35362
|
} = {}) => {
|
|
35295
35363
|
const isAction = Boolean(promiseOrAction && promiseOrAction.isAction);
|
|
35296
35364
|
if (loading === true) {
|
|
@@ -35302,7 +35370,8 @@ const useAsyncData = (promiseOrAction, {
|
|
|
35302
35370
|
if (isAction) {
|
|
35303
35371
|
return useActionAsyncData(promiseOrAction, {
|
|
35304
35372
|
loadingEffect: loading,
|
|
35305
|
-
errorEffect: error
|
|
35373
|
+
errorEffect: error,
|
|
35374
|
+
onLoad
|
|
35306
35375
|
});
|
|
35307
35376
|
}
|
|
35308
35377
|
return usePromiseAsyncData(promiseOrAction, {
|
|
@@ -35319,12 +35388,14 @@ const dismissedActionWeakSet = new WeakSet();
|
|
|
35319
35388
|
const dismissedActionPendingPromiseWeakMap = new WeakMap();
|
|
35320
35389
|
const useActionAsyncData = (action, {
|
|
35321
35390
|
loadingEffect,
|
|
35322
|
-
errorEffect
|
|
35391
|
+
errorEffect,
|
|
35392
|
+
onLoad
|
|
35323
35393
|
}) => {
|
|
35324
35394
|
const loadingRef = useContext(LoadingContext);
|
|
35325
35395
|
if (!loadingRef) {
|
|
35326
35396
|
throw new Error("Missing <Loading>");
|
|
35327
35397
|
}
|
|
35398
|
+
useOnLoad(action, onLoad);
|
|
35328
35399
|
|
|
35329
35400
|
// Use peek() instead of .value to avoid subscribing this component to the signal.
|
|
35330
35401
|
// Reading .value would make Preact re-render the component reactively when the state
|
|
@@ -35445,6 +35516,41 @@ const useActionAsyncData = (action, {
|
|
|
35445
35516
|
throw pendingPromise;
|
|
35446
35517
|
};
|
|
35447
35518
|
|
|
35519
|
+
// What a screen does with the data once, when it becomes known (see onLoad in
|
|
35520
|
+
// the JSDoc above). Kept apart because the two questions it answers are not the
|
|
35521
|
+
// ones the hook around it answers: WHEN — a layout effect, so a form taking its
|
|
35522
|
+
// reference in the same tick sees what was written; and HOW OFTEN — once per set
|
|
35523
|
+
// of params, which is the action's own answer to "is this another thing or the
|
|
35524
|
+
// same one again".
|
|
35525
|
+
const NOTHING_SEEDED = Symbol("nothing_seeded");
|
|
35526
|
+
const useOnLoad = (action, onLoad) => {
|
|
35527
|
+
const onLoadRef = useRef(onLoad);
|
|
35528
|
+
onLoadRef.current = onLoad;
|
|
35529
|
+
const paramsSeededRef = useRef(NOTHING_SEEDED);
|
|
35530
|
+
useLayoutEffect(() => {
|
|
35531
|
+
const callback = onLoadRef.current;
|
|
35532
|
+
if (!callback) {
|
|
35533
|
+
return;
|
|
35534
|
+
}
|
|
35535
|
+
if (action.runningStateSignal.peek() !== COMPLETED) {
|
|
35536
|
+
return;
|
|
35537
|
+
}
|
|
35538
|
+
const data = action.dataSignal.peek();
|
|
35539
|
+
if (data === undefined) {
|
|
35540
|
+
return;
|
|
35541
|
+
}
|
|
35542
|
+
const params = action.paramsSignal.peek();
|
|
35543
|
+
const paramsSeeded = paramsSeededRef.current;
|
|
35544
|
+
if (paramsSeeded !== NOTHING_SEEDED && compareTwoJsValues(params, paramsSeeded)) {
|
|
35545
|
+
return;
|
|
35546
|
+
}
|
|
35547
|
+
paramsSeededRef.current = params;
|
|
35548
|
+
callback(data, {
|
|
35549
|
+
params
|
|
35550
|
+
});
|
|
35551
|
+
});
|
|
35552
|
+
};
|
|
35553
|
+
|
|
35448
35554
|
// ─── Loading ──────────────────────────────────────────────────────────────────
|
|
35449
35555
|
// Wraps Suspense. Provides LoadingContext so useAction can write the suspension
|
|
35450
35556
|
// reason. LoadingFallback reads that reason and subscribes to the action so it
|
|
@@ -35484,6 +35590,14 @@ const LoadingFallback = ({
|
|
|
35484
35590
|
setTick(n => n + 1);
|
|
35485
35591
|
});
|
|
35486
35592
|
}, [action]);
|
|
35593
|
+
// A page that suspends never gets to say it is on screen — its own effects
|
|
35594
|
+
// are held with it — so this says it for it: what the document shows of the
|
|
35595
|
+
// page arriving is this. Anyone waiting for the page to be there before
|
|
35596
|
+
// moving (a travel about to have its picture taken, see route_travel.jsx)
|
|
35597
|
+
// would otherwise wait for a render that cannot happen until the data does.
|
|
35598
|
+
useLayoutEffect(() => {
|
|
35599
|
+
publishRouteRender();
|
|
35600
|
+
});
|
|
35487
35601
|
if (loadingRef.current.reason !== "loading") {
|
|
35488
35602
|
return null;
|
|
35489
35603
|
}
|
|
@@ -36916,7 +37030,6 @@ const Head = ({
|
|
|
36916
37030
|
* ```
|
|
36917
37031
|
*/
|
|
36918
37032
|
|
|
36919
|
-
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
36920
37033
|
|
|
36921
37034
|
/**
|
|
36922
37035
|
* Keep every container showing the page it is showing, whatever the routes say.
|
|
@@ -37204,6 +37317,10 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
37204
37317
|
const CAN_KEEP_PICTURE = Boolean(document.startViewTransition && !document.startViewTransition.isPolyfill);
|
|
37205
37318
|
const startViewTransition = ensureDocumentStartViewTransition();
|
|
37206
37319
|
const TRAVEL_ATTRIBUTE = "data-navi-route-travel";
|
|
37320
|
+
// Which way the pages move, said on the document: the pictures of a transition
|
|
37321
|
+
// hang off the root, not off the box that travels, so the box's own `axis` has
|
|
37322
|
+
// to be lent to the document for the length of the travel.
|
|
37323
|
+
const TRAVEL_AXIS_ATTRIBUTE = "data-navi-route-travel-axis";
|
|
37207
37324
|
// While a finger holds the travel: the pictures stand still and go exactly
|
|
37208
37325
|
// where it says (see the CSS, and scrubTravel).
|
|
37209
37326
|
const HOLD_ATTRIBUTE = "data-navi-route-travel-held";
|
|
@@ -37397,6 +37514,28 @@ const css$R = /* css */`
|
|
|
37397
37514
|
}
|
|
37398
37515
|
}
|
|
37399
37516
|
|
|
37517
|
+
/* The same four movements, along the axis the pages are laid out on: the
|
|
37518
|
+
start of a column is its top, so going forward there is the page rising and
|
|
37519
|
+
the next one coming up from below. */
|
|
37520
|
+
:root[${TRAVEL_AXIS_ATTRIBUTE}="y"] {
|
|
37521
|
+
&[${TRAVEL_ATTRIBUTE}="forward"] {
|
|
37522
|
+
&::view-transition-old(navi-route-travel) {
|
|
37523
|
+
animation-name: navi-route-travel-leave-towards-top;
|
|
37524
|
+
}
|
|
37525
|
+
&::view-transition-new(navi-route-travel) {
|
|
37526
|
+
animation-name: navi-route-travel-enter-from-bottom;
|
|
37527
|
+
}
|
|
37528
|
+
}
|
|
37529
|
+
&[${TRAVEL_ATTRIBUTE}="back"] {
|
|
37530
|
+
&::view-transition-old(navi-route-travel) {
|
|
37531
|
+
animation-name: navi-route-travel-leave-towards-bottom;
|
|
37532
|
+
}
|
|
37533
|
+
&::view-transition-new(navi-route-travel) {
|
|
37534
|
+
animation-name: navi-route-travel-enter-from-top;
|
|
37535
|
+
}
|
|
37536
|
+
}
|
|
37537
|
+
}
|
|
37538
|
+
|
|
37400
37539
|
@keyframes navi-route-travel-leave-towards-start {
|
|
37401
37540
|
from {
|
|
37402
37541
|
translate: 0 0;
|
|
@@ -37429,6 +37568,38 @@ const css$R = /* css */`
|
|
|
37429
37568
|
translate: 0 0;
|
|
37430
37569
|
}
|
|
37431
37570
|
}
|
|
37571
|
+
@keyframes navi-route-travel-leave-towards-top {
|
|
37572
|
+
from {
|
|
37573
|
+
translate: 0 0;
|
|
37574
|
+
}
|
|
37575
|
+
to {
|
|
37576
|
+
translate: 0 -100%;
|
|
37577
|
+
}
|
|
37578
|
+
}
|
|
37579
|
+
@keyframes navi-route-travel-enter-from-bottom {
|
|
37580
|
+
from {
|
|
37581
|
+
translate: 0 100%;
|
|
37582
|
+
}
|
|
37583
|
+
to {
|
|
37584
|
+
translate: 0 0;
|
|
37585
|
+
}
|
|
37586
|
+
}
|
|
37587
|
+
@keyframes navi-route-travel-leave-towards-bottom {
|
|
37588
|
+
from {
|
|
37589
|
+
translate: 0 0;
|
|
37590
|
+
}
|
|
37591
|
+
to {
|
|
37592
|
+
translate: 0 100%;
|
|
37593
|
+
}
|
|
37594
|
+
}
|
|
37595
|
+
@keyframes navi-route-travel-enter-from-top {
|
|
37596
|
+
from {
|
|
37597
|
+
translate: 0 -100%;
|
|
37598
|
+
}
|
|
37599
|
+
to {
|
|
37600
|
+
translate: 0 0;
|
|
37601
|
+
}
|
|
37602
|
+
}
|
|
37432
37603
|
`;
|
|
37433
37604
|
|
|
37434
37605
|
/**
|
|
@@ -37565,6 +37736,7 @@ const RouteTravel = ({
|
|
|
37565
37736
|
// own for as long as it is the one travelling.
|
|
37566
37737
|
nameForTravel(elementRef.current);
|
|
37567
37738
|
document.documentElement.setAttribute(TRAVEL_ATTRIBUTE, direction);
|
|
37739
|
+
document.documentElement.setAttribute(TRAVEL_AXIS_ATTRIBUTE, axis);
|
|
37568
37740
|
if (scrub) {
|
|
37569
37741
|
holdPictures(travel);
|
|
37570
37742
|
document.documentElement.setAttribute(DRAGGED_ATTRIBUTE, "");
|
|
@@ -37578,14 +37750,21 @@ const RouteTravel = ({
|
|
|
37578
37750
|
const releaseRendering = renderingHeldForRouting || holdRendering();
|
|
37579
37751
|
renderingHeldForRouting = null;
|
|
37580
37752
|
// The picture the browser is about to take must be of the page that was
|
|
37581
|
-
// asked for, and a route matching is not yet a page rendered.
|
|
37753
|
+
// asked for, and a route matching is not yet a page rendered. Watched from
|
|
37754
|
+
// here rather than from inside the callback below: the browser calls that
|
|
37755
|
+
// callback a frame later, and a navigation that has already been decided
|
|
37756
|
+
// (what follows a send, a command) renders its page in between. A wait
|
|
37757
|
+
// armed then waits for something that has already happened — until the
|
|
37758
|
+
// browser gives up on the transition, leaving the page it was leaving on
|
|
37759
|
+
// screen and an error nobody asked for.
|
|
37760
|
+
const renderWait = armRouteRenderWait();
|
|
37582
37761
|
const viewTransition = startViewTransition(async () => {
|
|
37583
37762
|
await whilePageRenders(page, async () => {
|
|
37584
37763
|
releaseRendering();
|
|
37585
37764
|
if (change) {
|
|
37586
37765
|
await change();
|
|
37587
37766
|
}
|
|
37588
|
-
});
|
|
37767
|
+
}, renderWait);
|
|
37589
37768
|
// The page arriving is in the DOM and the transition has not started
|
|
37590
37769
|
// playing: the one moment both boxes can be known.
|
|
37591
37770
|
holdTravelHeight(elementRef.current, heightBefore);
|
|
@@ -37610,6 +37789,7 @@ const RouteTravel = ({
|
|
|
37610
37789
|
viewTransition.finished.catch(() => {
|
|
37611
37790
|
// A transition that fails before it ever calls back leaves the page held:
|
|
37612
37791
|
// whoever asked for the hold gives it back, here as everywhere else.
|
|
37792
|
+
renderWait.stop();
|
|
37613
37793
|
releaseRendering();
|
|
37614
37794
|
endTravel(travel);
|
|
37615
37795
|
});
|
|
@@ -37913,6 +38093,7 @@ const RouteTravel = ({
|
|
|
37913
38093
|
// rather than pick.
|
|
37914
38094
|
unnameAfterTravel(elementRef.current);
|
|
37915
38095
|
document.documentElement.removeAttribute(TRAVEL_ATTRIBUTE);
|
|
38096
|
+
document.documentElement.removeAttribute(TRAVEL_AXIS_ATTRIBUTE);
|
|
37916
38097
|
document.documentElement.removeAttribute(DRAGGED_ATTRIBUTE);
|
|
37917
38098
|
document.documentElement.removeAttribute(TURNED_ATTRIBUTE);
|
|
37918
38099
|
releaseTravelHeight();
|
|
@@ -38475,20 +38656,30 @@ const scrubTravel = (travel, ratio) => {
|
|
|
38475
38656
|
// inside the callback of a view transition: the browser has stopped rendering
|
|
38476
38657
|
// and is waiting on this very promise to take its picture, so a wait that never
|
|
38477
38658
|
// ends is a page frozen under a transition that never became ready.
|
|
38478
|
-
|
|
38659
|
+
// Listening starts before the change, or a render landing while the change is
|
|
38660
|
+
// being awaited is a render nobody heard. Armed apart from the wait itself
|
|
38661
|
+
// because the two do not always happen at the same moment: a view transition
|
|
38662
|
+
// calls its update callback a frame after it is started, and the render can
|
|
38663
|
+
// land in that gap — see beginTravel, which arms this the moment the travel is
|
|
38664
|
+
// decided and hands it over.
|
|
38665
|
+
const armRouteRenderWait = () => {
|
|
38479
38666
|
let stopListening;
|
|
38480
38667
|
const rendered = new Promise(resolve => {
|
|
38481
|
-
// Listened for before the change, or a render landing while the change is
|
|
38482
|
-
// being awaited is a render nobody heard.
|
|
38483
38668
|
stopListening = observeRouteRender(resolve);
|
|
38484
38669
|
});
|
|
38670
|
+
return {
|
|
38671
|
+
rendered,
|
|
38672
|
+
stop: () => stopListening()
|
|
38673
|
+
};
|
|
38674
|
+
};
|
|
38675
|
+
const whilePageRenders = async (page, change, wait = armRouteRenderWait()) => {
|
|
38485
38676
|
try {
|
|
38486
38677
|
await change();
|
|
38487
38678
|
if (pageIsCurrent(page)) {
|
|
38488
|
-
await rendered;
|
|
38679
|
+
await wait.rendered;
|
|
38489
38680
|
}
|
|
38490
38681
|
} finally {
|
|
38491
|
-
|
|
38682
|
+
wait.stop();
|
|
38492
38683
|
}
|
|
38493
38684
|
};
|
|
38494
38685
|
|
|
@@ -38536,12 +38727,20 @@ const pageIsCurrent = ({
|
|
|
38536
38727
|
}
|
|
38537
38728
|
return params ? route.matchesParams(params) : true;
|
|
38538
38729
|
};
|
|
38539
|
-
//
|
|
38540
|
-
//
|
|
38730
|
+
// The FIRST page that answers, as with the branches of a <Route>: several
|
|
38731
|
+
// routes match at once — a literal one and the parameterized one it is a case of
|
|
38732
|
+
// ("/games/new" is also a "/games/:gameId"), a section and the page inside it —
|
|
38733
|
+
// and the row has to be on the page the router is showing, which is the first
|
|
38734
|
+
// one written that matches.
|
|
38735
|
+
//
|
|
38736
|
+
// Every page is read all the same, never only up to the one that answers yes: a
|
|
38737
|
+
// page that is not the current one today is the one that must wake the reader
|
|
38738
|
+
// tomorrow.
|
|
38541
38739
|
const currentPageIndex = pages => {
|
|
38542
38740
|
let currentIndex = -1;
|
|
38543
38741
|
for (let i = 0; i < pages.length; i++) {
|
|
38544
|
-
|
|
38742
|
+
const isCurrent = pageIsCurrent(pages[i]);
|
|
38743
|
+
if (isCurrent && currentIndex === -1) {
|
|
38545
38744
|
currentIndex = i;
|
|
38546
38745
|
}
|
|
38547
38746
|
}
|
|
@@ -52361,7 +52560,7 @@ const PickerContentInsidePopup = props => {
|
|
|
52361
52560
|
// above: those exist because "expand" already means something on the picker
|
|
52362
52561
|
// itself, and this one does not. Popover ignores it, same as Dialog ignores
|
|
52363
52562
|
// marginWithAnchor.
|
|
52364
|
-
|
|
52563
|
+
dockedOnSmallTouchScreen,
|
|
52365
52564
|
animation,
|
|
52366
52565
|
...rest
|
|
52367
52566
|
} = props;
|
|
@@ -52409,7 +52608,7 @@ const PickerContentInsidePopup = props => {
|
|
|
52409
52608
|
expand: isPopover ? undefined : dialogExpand,
|
|
52410
52609
|
expandX: isPopover ? undefined : dialogExpandX,
|
|
52411
52610
|
expandY: isPopover ? undefined : dialogExpandY,
|
|
52412
|
-
|
|
52611
|
+
dockedOnSmallTouchScreen: isPopover ? undefined : dockedOnSmallTouchScreen,
|
|
52413
52612
|
children: jsx(PopupModeContext.Provider, {
|
|
52414
52613
|
value: mode,
|
|
52415
52614
|
children: children
|
|
@@ -54087,7 +54286,7 @@ const css$v = /* css */`
|
|
|
54087
54286
|
|
|
54088
54287
|
/* Same reasoning, for the corners: a dialog squares off whatever corner
|
|
54089
54288
|
lands on its container's own (see the data-flush-* rules in dialog.jsx —
|
|
54090
|
-
a bottom sheet from
|
|
54289
|
+
a bottom sheet from dockedOnSmallTouchScreen squares its two bottom ones). A list
|
|
54091
54290
|
drawn right against that corner has to square the same one, otherwise its
|
|
54092
54291
|
own radius carves a notch out of the popup's square corner. Direct child
|
|
54093
54292
|
only: any deeper and the list is presumably inset from the popup's edge,
|
|
@@ -71079,5 +71278,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
71079
71278
|
})
|
|
71080
71279
|
});
|
|
71081
71280
|
|
|
71082
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeSpin, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership,
|
|
71281
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeSpin, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
71083
71282
|
//# sourceMappingURL=jsenv_navi.js.map
|