@jsenv/navi 0.29.324 → 0.29.326
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/README.md +1 -1
- package/dist/dev/jsenv_navi.js +247 -125
- package/dist/dev/jsenv_navi.js.map +11 -11
- package/dist/jsenv_navi.js +181 -129
- package/dist/jsenv_navi.js.map +11 -11
- package/docs/AI_INSTRUCTIONS.md +3 -2
- package/docs/dialog_shape.md +38 -18
- package/docs/drag_to_travel.md +3 -3
- package/docs/popup_backdrop.md +9 -7
- package/docs/popup_open.md +51 -16
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, coarsePointerSignal, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
6
|
export { disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
|
|
7
|
-
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, isTouchDrivenEvent, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
7
|
+
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, createInternalCustomEvent, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, isTouchDrivenEvent, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
8
8
|
export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
|
|
9
9
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
10
10
|
import { isValidElement, createContext, render, h, toChildArray, options, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
|
|
@@ -3092,6 +3092,15 @@ const resolveActionProxies = (actionSet) => {
|
|
|
3092
3092
|
|
|
3093
3093
|
const NO_PARAMS = { __no_params__: true };
|
|
3094
3094
|
const mergeActionParams = (currentParams, newParams) => {
|
|
3095
|
+
if (newParams === null) {
|
|
3096
|
+
// `null` is a question that cannot be asked: a callback destructuring its
|
|
3097
|
+
// params throws on it. It is what a computed says when it has not got
|
|
3098
|
+
// enough to ask with, and "nothing to ask" is said with `undefined`
|
|
3099
|
+
// everywhere below (and by the run guards reading paramsSignal), so it
|
|
3100
|
+
// becomes that here, at the one door params coming from a binding go
|
|
3101
|
+
// through.
|
|
3102
|
+
newParams = undefined;
|
|
3103
|
+
}
|
|
3095
3104
|
// The order of these two checks is load-bearing. Checking `undefined` first
|
|
3096
3105
|
// looks symmetric — "no new params, keep whatever is there, NO_PARAMS
|
|
3097
3106
|
// included" — and breaks routing: merge(NO_PARAMS, undefined) must yield
|
|
@@ -3349,7 +3358,10 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3349
3358
|
*
|
|
3350
3359
|
* @param {any} newParamsOrSignal - params, or a signal holding them (the
|
|
3351
3360
|
* result then retargets itself as the signal changes), or an object whose
|
|
3352
|
-
* values may be signals.
|
|
3361
|
+
* values may be signals. A signal with nothing to ask yet — a question
|
|
3362
|
+
* computed from a form still missing a field — holds `undefined` or
|
|
3363
|
+
* `null`: the instance then has no params, which is what
|
|
3364
|
+
* `useAsyncData(action, { run: true })` reads to start nothing.
|
|
3353
3365
|
* @param {object} [options]
|
|
3354
3366
|
* @param {number} [options.debounce] - milliseconds the params must stay
|
|
3355
3367
|
* stable before the instance follows them. What it buys is a screen that
|
|
@@ -12616,6 +12628,12 @@ const useExecuteAction = (
|
|
|
12616
12628
|
// whose command sent — is stored as the callout display target, so the
|
|
12617
12629
|
// message appears on whoever asked rather than on the whole control.
|
|
12618
12630
|
const element = elementRef.current;
|
|
12631
|
+
if (!element) {
|
|
12632
|
+
// The control left the page while its run was out (its own answer can
|
|
12633
|
+
// unmount it, see the outcome events below): there is no box to hang
|
|
12634
|
+
// the refusal on. onActionError is still told, by the run's side effect.
|
|
12635
|
+
return;
|
|
12636
|
+
}
|
|
12619
12637
|
let target = requester;
|
|
12620
12638
|
// A requester that is no longer on the page is not a place to show
|
|
12621
12639
|
// anything: the clear cross leaves with the value it optimistically
|
|
@@ -12718,12 +12736,20 @@ const useExecuteAction = (
|
|
|
12718
12736
|
// Either three callbacks, one per outcome, or a single one for "however
|
|
12719
12737
|
// this ends" — it receives `{ aborted, reason }`, `{ error }` or
|
|
12720
12738
|
// `{ data }`, so a caller that only needs to know the action settled
|
|
12721
|
-
// (and whether it worked) does not have to register three.
|
|
12739
|
+
// (and whether it worked) does not have to register three. Each also
|
|
12740
|
+
// receives the outcome event (navi_action_abort/error/end) as its
|
|
12741
|
+
// second argument.
|
|
12722
12742
|
const addSideEffect = (sideEffect) => {
|
|
12723
12743
|
if (typeof sideEffect === "function") {
|
|
12724
|
-
addAbortCallback((reason) =>
|
|
12725
|
-
|
|
12726
|
-
|
|
12744
|
+
addAbortCallback((reason, outcomeEvent) =>
|
|
12745
|
+
sideEffect({ aborted: true, reason }, outcomeEvent),
|
|
12746
|
+
);
|
|
12747
|
+
addErrorCallback((error, outcomeEvent) =>
|
|
12748
|
+
sideEffect({ error }, outcomeEvent),
|
|
12749
|
+
);
|
|
12750
|
+
addCompleteCallback((data, outcomeEvent) =>
|
|
12751
|
+
sideEffect({ data }, outcomeEvent),
|
|
12752
|
+
);
|
|
12727
12753
|
return;
|
|
12728
12754
|
}
|
|
12729
12755
|
const { abort, error, complete } = sideEffect;
|
|
@@ -12731,56 +12757,6 @@ const useExecuteAction = (
|
|
|
12731
12757
|
addErrorCallback(error);
|
|
12732
12758
|
addCompleteCallback(complete);
|
|
12733
12759
|
};
|
|
12734
|
-
addSideEffect({
|
|
12735
|
-
abort: (reason) => {
|
|
12736
|
-
const element = elementRef.current;
|
|
12737
|
-
if (
|
|
12738
|
-
// at this stage the action side effect might have removed the <element> from the DOM
|
|
12739
|
-
// (in theory no because action side effect are batched to happen after)
|
|
12740
|
-
// but other side effects might do this
|
|
12741
|
-
element
|
|
12742
|
-
) {
|
|
12743
|
-
dispatchInternalCustomEvent(element, "navi_action_abort", {
|
|
12744
|
-
...sharedActionEventDetail,
|
|
12745
|
-
reason,
|
|
12746
|
-
});
|
|
12747
|
-
}
|
|
12748
|
-
},
|
|
12749
|
-
error: (error) => {
|
|
12750
|
-
if (errorEffect === "show_validation_message") {
|
|
12751
|
-
addErrorMessage(error, { requester });
|
|
12752
|
-
} else if (errorEffect === "throw") {
|
|
12753
|
-
setError(error);
|
|
12754
|
-
}
|
|
12755
|
-
|
|
12756
|
-
const element = elementRef.current;
|
|
12757
|
-
if (
|
|
12758
|
-
// at this stage the action side effect might have removed the <element> from the DOM
|
|
12759
|
-
// (in theory no because action side effect are batched to happen after)
|
|
12760
|
-
// but other side effects might do this
|
|
12761
|
-
element
|
|
12762
|
-
) {
|
|
12763
|
-
dispatchInternalCustomEvent(element, "navi_action_error", {
|
|
12764
|
-
...sharedActionEventDetail,
|
|
12765
|
-
error,
|
|
12766
|
-
});
|
|
12767
|
-
}
|
|
12768
|
-
},
|
|
12769
|
-
complete: (data) => {
|
|
12770
|
-
const element = elementRef.current;
|
|
12771
|
-
if (
|
|
12772
|
-
// at this stage the action side effect might have removed the <element> from the DOM
|
|
12773
|
-
// (in theory no because action side effect are batched to happen after)
|
|
12774
|
-
// but other side effects might do this
|
|
12775
|
-
element
|
|
12776
|
-
) {
|
|
12777
|
-
dispatchInternalCustomEvent(element, "navi_action_end", {
|
|
12778
|
-
...sharedActionEventDetail,
|
|
12779
|
-
data,
|
|
12780
|
-
});
|
|
12781
|
-
}
|
|
12782
|
-
},
|
|
12783
|
-
});
|
|
12784
12760
|
|
|
12785
12761
|
const actionStartEventDetail = {
|
|
12786
12762
|
...sharedActionEventDetail,
|
|
@@ -12792,13 +12768,50 @@ const useExecuteAction = (
|
|
|
12792
12768
|
actionStartEventDetail,
|
|
12793
12769
|
);
|
|
12794
12770
|
|
|
12771
|
+
// The outcome is told twice: to the element, when there still is one,
|
|
12772
|
+
// and to the side effects registered at navi_action_start, always. A
|
|
12773
|
+
// run can outlive its control — the run's own answer, written to a
|
|
12774
|
+
// store the parent reads to decide what it draws, unmounts the control
|
|
12775
|
+
// one microtask before the run settles — and the element is then no
|
|
12776
|
+
// place to dispatch to: Preact drops a detached element's listeners.
|
|
12777
|
+
// The side effects are the run's, not the element's, so the event
|
|
12778
|
+
// reaches them either way.
|
|
12779
|
+
const tellOutcome = (outcomeEventName, outcomeEventDetail, trigger) => {
|
|
12780
|
+
const outcomeEvent = createInternalCustomEvent(outcomeEventName, {
|
|
12781
|
+
...sharedActionEventDetail,
|
|
12782
|
+
...outcomeEventDetail,
|
|
12783
|
+
});
|
|
12784
|
+
const element = elementRef.current;
|
|
12785
|
+
if (element) {
|
|
12786
|
+
element.dispatchEvent(outcomeEvent);
|
|
12787
|
+
}
|
|
12788
|
+
trigger(outcomeEvent);
|
|
12789
|
+
};
|
|
12790
|
+
|
|
12795
12791
|
const runAction = () => {
|
|
12796
12792
|
return action[method]({
|
|
12797
12793
|
event: actionEvent,
|
|
12798
12794
|
reason: `"${event.type}" event on ${getElementSignature(event.target)}`,
|
|
12799
|
-
onAbort:
|
|
12800
|
-
|
|
12801
|
-
|
|
12795
|
+
onAbort: (reason) => {
|
|
12796
|
+
tellOutcome("navi_action_abort", { reason }, (outcomeEvent) => {
|
|
12797
|
+
triggerAbort(reason, outcomeEvent);
|
|
12798
|
+
});
|
|
12799
|
+
},
|
|
12800
|
+
onError: (error) => {
|
|
12801
|
+
if (errorEffect === "show_validation_message") {
|
|
12802
|
+
addErrorMessage(error, { requester });
|
|
12803
|
+
} else if (errorEffect === "throw") {
|
|
12804
|
+
setError(error);
|
|
12805
|
+
}
|
|
12806
|
+
tellOutcome("navi_action_error", { error }, (outcomeEvent) => {
|
|
12807
|
+
triggerError(error, outcomeEvent);
|
|
12808
|
+
});
|
|
12809
|
+
},
|
|
12810
|
+
onComplete: (data) => {
|
|
12811
|
+
tellOutcome("navi_action_end", { data }, (outcomeEvent) => {
|
|
12812
|
+
triggerComplete(data, outcomeEvent);
|
|
12813
|
+
});
|
|
12814
|
+
},
|
|
12802
12815
|
});
|
|
12803
12816
|
};
|
|
12804
12817
|
|
|
@@ -16508,6 +16521,11 @@ const generateSignalId = () => {
|
|
|
16508
16521
|
* as the user moved. `"push"` is for a state whose values ARE places one came
|
|
16509
16522
|
* from (the photo one is looking at in a gallery). Whatever is said here, one
|
|
16510
16523
|
* write can say otherwise: `signal.set(value, { history })`.
|
|
16524
|
+
* Either way every write reaches the address, synchronously, and browsers
|
|
16525
|
+
* refuse an address written too often (Safari: 100 writes per 10 s, then a
|
|
16526
|
+
* SecurityError). A value one DRAGS is written at 60 Hz: keep it in the
|
|
16527
|
+
* gesture while the finger is down and write the state once, on release —
|
|
16528
|
+
* an address is not a recording of a gesture.
|
|
16511
16529
|
* @param {boolean} [options.debug=false] - Enable debug logging for this signal's operations
|
|
16512
16530
|
* @returns {import("@preact/signals").Signal} A signal that can be synchronized with a source signal and/or persisted in localStorage. The signal includes a `validity` property for validation state.
|
|
16513
16531
|
*
|
|
@@ -26543,11 +26561,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
26543
26561
|
[NAV_DEPTH_STATE_KEY]: getNavDepth(),
|
|
26544
26562
|
},
|
|
26545
26563
|
});
|
|
26546
|
-
|
|
26547
|
-
window.history.pushState(effectiveState, null, url);
|
|
26548
|
-
} else {
|
|
26549
|
-
window.history.replaceState(effectiveState, null, url);
|
|
26550
|
-
}
|
|
26564
|
+
writeHistoryEntry(navigationType, effectiveState, url);
|
|
26551
26565
|
rememberEntryIsOfThisDocument();
|
|
26552
26566
|
updateDocumentUrl(url);
|
|
26553
26567
|
updateDocumentState(effectiveState);
|
|
@@ -26567,14 +26581,14 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
26567
26581
|
[NAV_DEPTH_STATE_KEY]: getNavDepth(),
|
|
26568
26582
|
},
|
|
26569
26583
|
});
|
|
26570
|
-
|
|
26584
|
+
writeHistoryEntry("replace", state, url);
|
|
26571
26585
|
rememberEntryIsOfThisDocument();
|
|
26572
26586
|
} else if (redirected) {
|
|
26573
26587
|
// The entry the browser is on names an address that only sends
|
|
26574
26588
|
// elsewhere — a cold load on it, or a back into it. Written over where
|
|
26575
26589
|
// it stands (the entry keeps its place in the stack, hence its state
|
|
26576
26590
|
// and the depth in it): pressing back must not walk into it again.
|
|
26577
|
-
|
|
26591
|
+
writeHistoryEntry("replace", state, url);
|
|
26578
26592
|
rememberEntryIsOfThisDocument();
|
|
26579
26593
|
}
|
|
26580
26594
|
updateDocumentUrl(url);
|
|
@@ -26827,7 +26841,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
26827
26841
|
// The entry itself has to lose them too, not just the document state:
|
|
26828
26842
|
// getDocumentState() reads the entry back, and every state write copies
|
|
26829
26843
|
// what it finds there onto the next one.
|
|
26830
|
-
|
|
26844
|
+
writeHistoryEntry("replace", state, url);
|
|
26831
26845
|
}
|
|
26832
26846
|
handleRoutingTask(url, {
|
|
26833
26847
|
reason: "routing initialization",
|
|
@@ -26849,11 +26863,25 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
26849
26863
|
visitedUrlsSignal,
|
|
26850
26864
|
};
|
|
26851
26865
|
};
|
|
26866
|
+
const ADDRESS_WRITE_ADVICE = `An address is not a recording of a gesture: a value dragged per frame belongs to the gesture, and the state bound to the url is written once, on release.`;
|
|
26867
|
+
const writeHistoryEntry = (navigationType, state, url) => {
|
|
26868
|
+
try {
|
|
26869
|
+
if (navigationType === "push") {
|
|
26870
|
+
window.history.pushState(state, null, url);
|
|
26871
|
+
} else {
|
|
26872
|
+
window.history.replaceState(state, null, url);
|
|
26873
|
+
}
|
|
26874
|
+
} catch (e) {
|
|
26875
|
+
if (e.name !== "SecurityError") {
|
|
26876
|
+
throw e;
|
|
26877
|
+
}
|
|
26878
|
+
throw new Error(
|
|
26879
|
+
`The browser refused to write the address ${url}: ${e.message}. ${ADDRESS_WRITE_ADVICE}`,
|
|
26880
|
+
{ cause: e },
|
|
26881
|
+
);
|
|
26882
|
+
}
|
|
26883
|
+
};
|
|
26852
26884
|
|
|
26853
|
-
// Read before the history is written, in both places that ask: a push or a
|
|
26854
|
-
// replace onto the url already displayed. Never a traverse — the browser has
|
|
26855
|
-
// already moved window.location.href when a popstate is handled, so the
|
|
26856
|
-
// comparison would say "same" about every back and forward.
|
|
26857
26885
|
const isStateOnlyNavigation = (
|
|
26858
26886
|
url,
|
|
26859
26887
|
{ navigationType },
|
|
@@ -39868,10 +39896,24 @@ const useInteractiveProps = (props, {
|
|
|
39868
39896
|
// aborted run, whose promise is awaited to completion (see
|
|
39869
39897
|
// performRun in actions.js) — which is exactly what "the server is
|
|
39870
39898
|
// done with it" means, and therefore when the queued request may go.
|
|
39871
|
-
e.detail.addSideEffect(outcome => {
|
|
39899
|
+
e.detail.addSideEffect((outcome, outcomeEvent) => {
|
|
39872
39900
|
uiStateController.actionInFlight = false;
|
|
39873
39901
|
uiStateController.runningActionSignal.value = null;
|
|
39874
39902
|
uiStateController.parallelGuard?.release(uiStateController);
|
|
39903
|
+
// The outcome callbacks are told from here, the run's side effect,
|
|
39904
|
+
// and not from the onnavi_action_* handlers below: the run can
|
|
39905
|
+
// outlive this control (its own answer, written to a store the
|
|
39906
|
+
// parent reads, unmounts it one microtask before it settles), and
|
|
39907
|
+
// whoever wears the run — a parent's `loading` following these,
|
|
39908
|
+
// see 12_picker_card_demo.html — must hear it end all the same.
|
|
39909
|
+
// The handlers below keep what needs the element.
|
|
39910
|
+
if (outcome.aborted) {
|
|
39911
|
+
onActionAborted?.(outcomeEvent);
|
|
39912
|
+
} else if ("error" in outcome) {
|
|
39913
|
+
onActionError?.(outcome.error, outcomeEvent);
|
|
39914
|
+
} else {
|
|
39915
|
+
onActionEnd?.(outcome.data, outcomeEvent);
|
|
39916
|
+
}
|
|
39875
39917
|
const queuedEvent = uiStateController.queuedActionAllowedEvent;
|
|
39876
39918
|
if (!queuedEvent) {
|
|
39877
39919
|
syncOptimisticAttribute(false);
|
|
@@ -39911,7 +39953,6 @@ const useInteractiveProps = (props, {
|
|
|
39911
39953
|
if (resetOnAbort && !superseded) {
|
|
39912
39954
|
dispatchRequestResetUIState(e.currentTarget, e);
|
|
39913
39955
|
}
|
|
39914
|
-
onActionAborted?.(e);
|
|
39915
39956
|
},
|
|
39916
39957
|
onnavi_action_error: e => {
|
|
39917
39958
|
const {
|
|
@@ -39920,7 +39961,6 @@ const useInteractiveProps = (props, {
|
|
|
39920
39961
|
if (resetOnError) {
|
|
39921
39962
|
dispatchRequestResetUIState(e.currentTarget, e);
|
|
39922
39963
|
}
|
|
39923
|
-
onActionError?.(error, e);
|
|
39924
39964
|
uiStateController.onActionError(e);
|
|
39925
39965
|
},
|
|
39926
39966
|
onnavi_action_end: e => {
|
|
@@ -39928,7 +39968,6 @@ const useInteractiveProps = (props, {
|
|
|
39928
39968
|
data
|
|
39929
39969
|
} = e.detail;
|
|
39930
39970
|
debugAction(e, `action end with data: ${JSON.stringify(data)}`);
|
|
39931
|
-
onActionEnd?.(data, e);
|
|
39932
39971
|
controlRootProps.onnavi_action_end?.(e);
|
|
39933
39972
|
uiStateController.onActionEnd(e);
|
|
39934
39973
|
|
|
@@ -59316,34 +59355,44 @@ const css$E = /* css */`
|
|
|
59316
59355
|
* `showModal()`'d (native focus trap, `Escape`-to-cancel, hardware
|
|
59317
59356
|
* back-button dismissal, rest-of-document made inert), without one it is
|
|
59318
59357
|
* shown through the Popover API and the page behind stays live.
|
|
59319
|
-
* @param {boolean} [props.dockedOnSmallTouchScreen] - Turns
|
|
59320
|
-
*
|
|
59321
|
-
* screen, and leaves it alone otherwise
|
|
59322
|
-
*
|
|
59323
|
-
* the
|
|
59324
|
-
*
|
|
59325
|
-
*
|
|
59326
|
-
*
|
|
59327
|
-
*
|
|
59328
|
-
*
|
|
59329
|
-
*
|
|
59330
|
-
*
|
|
59358
|
+
* @param {boolean|"top"|"bottom"} [props.dockedOnSmallTouchScreen] - Turns
|
|
59359
|
+
* the dialog into a sheet (docked flush to one edge, full width) on a small
|
|
59360
|
+
* touch screen, and leaves it alone otherwise: under a mouse the centered box
|
|
59361
|
+
* is already the right shape, while on a phone a centered box ends up cramped
|
|
59362
|
+
* between the keyboard and the top of the screen. The value says which edge
|
|
59363
|
+
* the sheet rests on. `true` (or `"top"`) rests it on the top edge, out of
|
|
59364
|
+
* the virtual keyboard's way: a field tapped in the sheet raises the keyboard
|
|
59365
|
+
* below it, and the question and the field stay where the finger found them.
|
|
59366
|
+
* `"bottom"` rests it on the bottom edge, where the thumbs are — for a sheet
|
|
59367
|
+
* that is read and tapped (a list, a couple of buttons, a confirmation) and
|
|
59368
|
+
* holds nothing that raises a keyboard; one that does is reflowed into the
|
|
59369
|
+
* strip left above the keyboard at every keystroke. The edge is said here
|
|
59370
|
+
* rather than through `positionArea`, which places both shapes:
|
|
59371
|
+
* `positionArea="bottom"` would also pin the centered box to the bottom of a
|
|
59372
|
+
* desktop window. Both halves of the name matter (`smallTouchScreenSignal`):
|
|
59373
|
+
* touch alone would dock a big touch screen — a tablet, a kiosk panel — a
|
|
59374
|
+
* whole screen away from where the finger just tapped, and size alone would
|
|
59375
|
+
* dock a narrow desktop window, which is still a mouse. It supplies defaults
|
|
59376
|
+
* for `positionArea`, `marginWithContainer`, `expandX` and `scrollCapture`,
|
|
59377
|
+
* so any of them can still be pinned explicitly — including `expandX={false}`,
|
|
59331
59378
|
* which opts the docked dialog out of the full-width stretch and leaves it a
|
|
59332
|
-
* floating box
|
|
59379
|
+
* floating box against its edge. It also withdraws `maxWidth` while docked: a
|
|
59333
59380
|
* sheet is container-wide by definition, and a `maxWidth` is an answer about
|
|
59334
59381
|
* the *centered* shape, so the two can be stated together (`maxWidth="16rem"
|
|
59335
59382
|
* dockedOnSmallTouchScreen`) and each applies where it means something.
|
|
59336
59383
|
* `minWidth` needs no such rule — its floor is below the full width — and
|
|
59337
59384
|
* `maxHeight`/`minHeight` keep applying, a sheet being content-tall.
|
|
59338
|
-
* Ignored entirely when `expandY`
|
|
59339
|
-
*
|
|
59340
|
-
*
|
|
59341
|
-
*
|
|
59342
|
-
*
|
|
59343
|
-
*
|
|
59344
|
-
*
|
|
59345
|
-
*
|
|
59346
|
-
*
|
|
59385
|
+
* Ignored entirely when `expandY` (or `expand`) is set: a dialog already
|
|
59386
|
+
* filling the height touches both edges, so docking could only take away the
|
|
59387
|
+
* shape the caller asked for. Re-resolves live as the pointer type or the
|
|
59388
|
+
* window size changes. A sheet is also pushed back through the edge it rests
|
|
59389
|
+
* on to close it (down for a bottom sheet, up for a top one), held by its
|
|
59390
|
+
* header (a `Box` with the `header` prop) and by anything else carrying
|
|
59391
|
+
* `data-swipe-grip`. The rest of the sheet is left to what it holds, so a
|
|
59392
|
+
* board something is dragged across keeps its own gestures. See
|
|
59393
|
+
* `swipe_to_close.js`. In dev, a `"bottom"` sheet found holding a field that
|
|
59394
|
+
* raises the keyboard is warned about at open, on any screen — the mistake
|
|
59395
|
+
* itself only shows on a phone.
|
|
59347
59396
|
* @param {string} [props.positionArea="center"] - Where to dock the dialog
|
|
59348
59397
|
* within its container (the viewport for `layer="top"`, the positioned
|
|
59349
59398
|
* ancestor for `layer="local"`) — Dialog is never anchored to a real
|
|
@@ -59709,28 +59758,28 @@ const DialogLocal = props => {
|
|
|
59709
59758
|
* own to render: a modal's is the native `::backdrop`, and `backdrop={false}`
|
|
59710
59759
|
* has none at all.
|
|
59711
59760
|
*/
|
|
59712
|
-
// What a dialog turns into on a small touch screen
|
|
59713
|
-
//
|
|
59714
|
-
//
|
|
59715
|
-
//
|
|
59761
|
+
// What a dialog turns into on a small touch screen: a sheet flush against one
|
|
59762
|
+
// edge of the screen. Which edge is the caller's word (the prop's value, see
|
|
59763
|
+
// its JSDoc), the top one unless told "bottom": the cheap mistake must be the
|
|
59764
|
+
// default one — a sheet docked top by mistake is a little farther from the
|
|
59765
|
+
// thumb, a sheet docked bottom by mistake fights the virtual keyboard, which
|
|
59766
|
+
// rises from that very edge, on every keystroke.
|
|
59716
59767
|
// Only defaults: an explicitly passed prop still wins, so the docked shape can
|
|
59717
59768
|
// be adjusted one axis at a time instead of being all-or-nothing.
|
|
59718
59769
|
const DOCKED = {
|
|
59719
|
-
positionArea: "bottom",
|
|
59720
59770
|
marginWithContainer: 0,
|
|
59721
59771
|
expandX: true,
|
|
59722
|
-
// A sheet
|
|
59723
|
-
//
|
|
59724
|
-
// reasoning as "bottom" above, applied to the gesture instead of the shape.
|
|
59772
|
+
// A sheet is dragged with a thumb, and a drag that runs past its own edge
|
|
59773
|
+
// must not land on the page behind it.
|
|
59725
59774
|
scrollCapture: true
|
|
59726
59775
|
};
|
|
59727
59776
|
|
|
59728
|
-
// Where a
|
|
59729
|
-
// with `header`, plus anything the application marked as one more.
|
|
59730
|
-
// else in the sheet is content the finger came to operate — a board
|
|
59731
|
-
// dragged across, a list, a map — and a press there belongs to it. A
|
|
59732
|
-
// no header and nothing marked is not pushed
|
|
59733
|
-
// own controls, by the backdrop and by Escape.
|
|
59777
|
+
// Where a sheet is held to push it back through its edge: the strip a Box
|
|
59778
|
+
// declares with `header`, plus anything the application marked as one more.
|
|
59779
|
+
// Everything else in the sheet is content the finger came to operate — a board
|
|
59780
|
+
// a piece is dragged across, a list, a map — and a press there belongs to it. A
|
|
59781
|
+
// sheet with no header and nothing marked is not pushed back at all; it is
|
|
59782
|
+
// closed by its own controls, by the backdrop and by Escape.
|
|
59734
59783
|
const DOCKED_SWIPE_GRIP = "[data-header],[data-swipe-grip]";
|
|
59735
59784
|
const useDialogProps = props => {
|
|
59736
59785
|
const backdropProps = {};
|
|
@@ -59836,12 +59885,12 @@ const useDialogProps = props => {
|
|
|
59836
59885
|
// Only a small touch screen changes anything: on a mouse — and on a touch
|
|
59837
59886
|
// screen too big to reach the bottom edge of — a dialog already wants to be
|
|
59838
59887
|
// the centered box it is by default, so there is nothing to resolve.
|
|
59839
|
-
// expandY cancels the docking outright:
|
|
59840
|
-
//
|
|
59841
|
-
//
|
|
59842
|
-
// caller asked for (and arm a swipe-down on something that never rose).
|
|
59888
|
+
// expandY cancels the docking outright: a dialog already filling the height
|
|
59889
|
+
// touches both edges — all docking could still do is take away the shape
|
|
59890
|
+
// the caller asked for (and arm a swipe on something that never rose).
|
|
59843
59891
|
const isDocked = dockedOnSmallTouchScreen && smallTouchScreenSignal.value && !expandY;
|
|
59844
|
-
const
|
|
59892
|
+
const dockedEdge = dockedOnSmallTouchScreen === "bottom" ? "bottom" : "top";
|
|
59893
|
+
const positionArea = positionAreaProp ?? (isDocked ? dockedEdge : "center");
|
|
59845
59894
|
const marginWithContainer = marginWithContainerProp ?? (isDocked ? DOCKED.marginWithContainer :
|
|
59846
59895
|
// A share of whatever holds the dialog: the app's own screen for a
|
|
59847
59896
|
// top-layer one — where appw is exactly "3% of the container", the
|
|
@@ -59903,12 +59952,12 @@ const useDialogProps = props => {
|
|
|
59903
59952
|
y: "center",
|
|
59904
59953
|
x: "center"
|
|
59905
59954
|
};
|
|
59906
|
-
// Pushing the sheet back
|
|
59907
|
-
//
|
|
59908
|
-
// actually resting on
|
|
59909
|
-
//
|
|
59910
|
-
const
|
|
59911
|
-
const onSwipePointerDown =
|
|
59955
|
+
// Pushing the sheet back through the edge it rests on closes it — the way
|
|
59956
|
+
// it came in, and the way the thumb on that edge already pushes. Only a sheet
|
|
59957
|
+
// actually resting on an edge: anywhere else the gesture would send the
|
|
59958
|
+
// dialog somewhere it never came from.
|
|
59959
|
+
const swipeToCloseSide = isDocked && (parsedPositionArea.y === "top" || parsedPositionArea.y === "bottom") ? parsedPositionArea.y : null;
|
|
59960
|
+
const onSwipePointerDown = swipeToCloseSide ? createSwipeToClose(swipeToCloseSide, {
|
|
59912
59961
|
grip: DOCKED_SWIPE_GRIP
|
|
59913
59962
|
}) : null;
|
|
59914
59963
|
// A corner sitting exactly on the container's own corner must not be
|
|
@@ -60540,12 +60589,12 @@ const useDialogProps = props => {
|
|
|
60540
60589
|
"data-flush-right": flushEdges.right ? "" : undefined,
|
|
60541
60590
|
"data-flush-bottom": flushEdges.bottom ? "" : undefined,
|
|
60542
60591
|
"data-flush-left": flushEdges.left ? "" : undefined,
|
|
60543
|
-
"data-swipe-to-close":
|
|
60592
|
+
"data-swipe-to-close": swipeToCloseSide ?? undefined,
|
|
60544
60593
|
// The axis the sheet travels on when it is pushed back, said to the shared
|
|
60545
60594
|
// gesture layer: it is what a box travelling inside the sheet reads to know
|
|
60546
60595
|
// this axis is already walked (see @jsenv/dom's drag_to_travel).
|
|
60547
|
-
"data-drag-travel":
|
|
60548
|
-
"data-travel-by-drag":
|
|
60596
|
+
"data-drag-travel": swipeToCloseSide ? SWIPE_AXIS_BY_SIDE[swipeToCloseSide] : undefined,
|
|
60597
|
+
"data-travel-by-drag": swipeToCloseSide ? SWIPE_AXIS_BY_SIDE[swipeToCloseSide] : undefined,
|
|
60549
60598
|
"onPointerDown": e => {
|
|
60550
60599
|
rest.onPointerDown?.(e);
|
|
60551
60600
|
onSwipePointerDown?.(e);
|
|
@@ -62339,8 +62388,8 @@ const css$C = /* css */`@layer navi {
|
|
|
62339
62388
|
* @param {string} [props.positionAreaFixed] - **Popover-only**, same guard.
|
|
62340
62389
|
* @param {string} [props.positionAreaWhenAnchorIsInvalid] - **Popover-only**,
|
|
62341
62390
|
* same guard.
|
|
62342
|
-
* @param {boolean} [props.dockedOnSmallTouchScreen] -
|
|
62343
|
-
* popover is never a
|
|
62391
|
+
* @param {boolean|"top"|"bottom"} [props.dockedOnSmallTouchScreen] -
|
|
62392
|
+
* **Dialog-only** (a popover is never a sheet), destructured out for the same reason
|
|
62344
62393
|
* turned around: it must not land on the popover element as a stray DOM
|
|
62345
62394
|
* attribute when the screen-size resolution picks `mode="popover"`. Where
|
|
62346
62395
|
* that resolution lands is what decides whether it applies at all, so a
|
|
@@ -62368,7 +62417,7 @@ const css$C = /* css */`@layer navi {
|
|
|
62368
62417
|
* honoured in either mode — a wall-less `Dialog` is shown through the
|
|
62369
62418
|
* Popover API rather than `showModal()` — so which mode the screen-size
|
|
62370
62419
|
* resolution picks says nothing about whether the page behind stays live.
|
|
62371
|
-
* It is also what makes a sheet docked to
|
|
62420
|
+
* It is also what makes a sheet docked to an edge of a phone's screen
|
|
62372
62421
|
* (`dockedOnSmallTouchScreen`) non-modal.
|
|
62373
62422
|
* @param {"auto"|"discrete"|"invisible"} [props.backdropVariant] - Forwarded
|
|
62374
62423
|
* as-is to whichever component renders (both understand it identically):
|
|
@@ -74973,7 +75022,7 @@ const css$n = /* css */`.navi_split_button {
|
|
|
74973
75022
|
* dialogExpand?: boolean,
|
|
74974
75023
|
* dialogExpandX?: boolean,
|
|
74975
75024
|
* dialogExpandY?: boolean,
|
|
74976
|
-
* dockedOnSmallTouchScreen?: boolean,
|
|
75025
|
+
* dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
|
|
74977
75026
|
* marginWithContainer?: number | string,
|
|
74978
75027
|
* backdrop?: boolean,
|
|
74979
75028
|
* backdropVariant?: "auto" | "discrete" | "invisible",
|
|
@@ -84401,6 +84450,9 @@ const createSlot = (SlotRenderer = Box) => {
|
|
|
84401
84450
|
slotPropsSignal.value = props;
|
|
84402
84451
|
useLayoutEffect(() => {
|
|
84403
84452
|
return () => {
|
|
84453
|
+
// Within one diff preact mounts the newcomer before unmounting the
|
|
84454
|
+
// leaver, so the leaver may no longer be the filler by the time it
|
|
84455
|
+
// is cleaned up.
|
|
84404
84456
|
if (filler !== fillerRef) {
|
|
84405
84457
|
return;
|
|
84406
84458
|
}
|