@jsenv/navi 0.29.316 → 0.29.317
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/jsenv_navi.js +595 -244
- package/dist/dev/jsenv_navi.js.map +18 -14
- package/dist/jsenv_navi.js +489 -552
- package/dist/jsenv_navi.js.map +18 -15
- package/docs/AI_INSTRUCTIONS.md +4 -2
- package/docs/scroll.md +90 -0
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
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, markDragSource, refuseDragTo, startDragTo, installPanZoom,
|
|
7
|
+
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, refuseDragTo, startDragTo, installPanZoom, 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, Fragment as Fragment$1 } from "preact";
|
|
11
11
|
import { useErrorBoundary, useLayoutEffect, useContext, useCallback, useRef, useState, useEffect, useMemo, useId } from "preact/hooks";
|
|
12
|
-
import { humanizeI18n,
|
|
12
|
+
import { humanizeI18n, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatTime, formatMinuteDuration, formatSecondDuration, formatHourDuration, formatTimeRelative, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
|
|
13
13
|
export { createI18n, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText } from "@jsenv/humanize";
|
|
14
14
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
15
15
|
import { TYPE_RULE, durationContainsNaN, compareTwoDurations, durationToSeconds, DISPLAYABLE_RULE, MAX_LINE_BREAKS_RULE, NO_EMOJI_RULE, SINGLE_SPACE_RULE, createValidity, resolveCharClass, getCharClassMessageKey, compileCharClassAnchored, compileCharClass, CHAR_CLASS_PRESETS, parseDuration, durationToISOString } from "@jsenv/validity";
|
|
@@ -191,9 +191,7 @@ const executeWithCleanup = (fn, cleanup) => {
|
|
|
191
191
|
}
|
|
192
192
|
};
|
|
193
193
|
|
|
194
|
-
let DEBUG$3 = false;
|
|
195
194
|
const enableDebugOnDocumentLoading = () => {
|
|
196
|
-
DEBUG$3 = true;
|
|
197
195
|
};
|
|
198
196
|
|
|
199
197
|
const windowIsLoadingSignal = signal(true);
|
|
@@ -213,20 +211,9 @@ const [
|
|
|
213
211
|
removeFromDocumentLoadingRouteArraySignal,
|
|
214
212
|
] = arraySignal([]);
|
|
215
213
|
const routingWhile = (fn, routeNames = []) => {
|
|
216
|
-
if (DEBUG$3 && routeNames.length > 0) {
|
|
217
|
-
console.debug(`routingWhile: Adding routes to loading state:`, routeNames);
|
|
218
|
-
}
|
|
219
214
|
addToDocumentLoadingRouteArraySignal(...routeNames);
|
|
220
215
|
return executeWithCleanup(fn, () => {
|
|
221
216
|
removeFromDocumentLoadingRouteArraySignal(...routeNames);
|
|
222
|
-
if (DEBUG$3 && routeNames.length > 0) {
|
|
223
|
-
console.debug(
|
|
224
|
-
`routingWhile: Removed routes from loading state:`,
|
|
225
|
-
routeNames,
|
|
226
|
-
"state after removing:",
|
|
227
|
-
documentLoadingRouteArraySignal.peek(),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
217
|
});
|
|
231
218
|
};
|
|
232
219
|
|
|
@@ -236,23 +223,9 @@ const [
|
|
|
236
223
|
removeFromDocumentLoadingActionArraySignal,
|
|
237
224
|
] = arraySignal([]);
|
|
238
225
|
const workingWhile = (fn, actionNames = []) => {
|
|
239
|
-
if (DEBUG$3 && actionNames.length > 0) {
|
|
240
|
-
console.debug(
|
|
241
|
-
`workingWhile: Adding actions to loading state:`,
|
|
242
|
-
actionNames,
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
226
|
addToDocumentLoadingActionArraySignal(...actionNames);
|
|
246
227
|
return executeWithCleanup(fn, () => {
|
|
247
228
|
removeFromDocumentLoadingActionArraySignal(...actionNames);
|
|
248
|
-
if (DEBUG$3 && actionNames.length > 0) {
|
|
249
|
-
console.debug(
|
|
250
|
-
`routingWhile: Removed action from loading state:`,
|
|
251
|
-
actionNames,
|
|
252
|
-
"start after removing:",
|
|
253
|
-
documentLoadingActionArraySignal.peek(),
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
229
|
});
|
|
257
230
|
};
|
|
258
231
|
|
|
@@ -2612,10 +2585,7 @@ const setActionPrivateProperties = (action, properties) => {
|
|
|
2612
2585
|
// Params waiting to settle exist only under a debounced binding; every other
|
|
2613
2586
|
// action answers "no" so nobody has to check whether it can be asked.
|
|
2614
2587
|
const NOT_SETTLING_SIGNAL = signal(false);
|
|
2615
|
-
|
|
2616
|
-
let DEBUG$2 = false;
|
|
2617
2588
|
const enableDebugActions = () => {
|
|
2618
|
-
DEBUG$2 = true;
|
|
2619
2589
|
};
|
|
2620
2590
|
|
|
2621
2591
|
let dispatchActions = (params) => {
|
|
@@ -2759,17 +2729,6 @@ const createKeptAnswerRegistry = ({ ms, max }) => {
|
|
|
2759
2729
|
};
|
|
2760
2730
|
};
|
|
2761
2731
|
|
|
2762
|
-
const formatActionSet = (actionSet, prefix = "") => {
|
|
2763
|
-
let message = prefix;
|
|
2764
|
-
for (const action of actionSet) {
|
|
2765
|
-
message += "\n";
|
|
2766
|
-
message += prefixFirstAndIndentRemainingLines(String(action), {
|
|
2767
|
-
prefix: " -",
|
|
2768
|
-
});
|
|
2769
|
-
}
|
|
2770
|
-
return message;
|
|
2771
|
-
};
|
|
2772
|
-
|
|
2773
2732
|
const actionAbortMap = new Map();
|
|
2774
2733
|
const actionPromiseMap = new Map();
|
|
2775
2734
|
const activationWeakSet = createIterableWeakSet("activation");
|
|
@@ -2851,24 +2810,6 @@ const updateActions = ({
|
|
|
2851
2810
|
|
|
2852
2811
|
const { runningSet, settledSet } = getActivationInfo();
|
|
2853
2812
|
|
|
2854
|
-
if (DEBUG$2) {
|
|
2855
|
-
let argSource = `reason: ${JSON.stringify(reason)}`;
|
|
2856
|
-
if (isReplace) {
|
|
2857
|
-
argSource += `, isReplace: true`;
|
|
2858
|
-
}
|
|
2859
|
-
console.group(`updateActions({ ${argSource} })`);
|
|
2860
|
-
const lines = [
|
|
2861
|
-
...(prerunSet.size ? [formatActionSet(prerunSet, "- prerun:")] : []),
|
|
2862
|
-
...(runSet.size ? [formatActionSet(runSet, "- run:")] : []),
|
|
2863
|
-
...(rerunSet.size ? [formatActionSet(rerunSet, "- rerun:")] : []),
|
|
2864
|
-
...(resetSet.size ? [formatActionSet(resetSet, "- reset:")] : []),
|
|
2865
|
-
];
|
|
2866
|
-
console.debug(
|
|
2867
|
-
`requested operations:
|
|
2868
|
-
${lines.join("\n")}`,
|
|
2869
|
-
);
|
|
2870
|
-
}
|
|
2871
|
-
|
|
2872
2813
|
// Internal sets that track what operations will actually be performed
|
|
2873
2814
|
const willResetSet = new Set();
|
|
2874
2815
|
const willPrerunSet = new Set();
|
|
@@ -2995,34 +2936,6 @@ ${lines.join("\n")}`,
|
|
|
2995
2936
|
}
|
|
2996
2937
|
}
|
|
2997
2938
|
}
|
|
2998
|
-
if (DEBUG$2) {
|
|
2999
|
-
const lines = [
|
|
3000
|
-
...(willResetSet.size
|
|
3001
|
-
? [formatActionSet(willResetSet, "- will reset:")]
|
|
3002
|
-
: []),
|
|
3003
|
-
...(willPrerunSet.size
|
|
3004
|
-
? [formatActionSet(willPrerunSet, "- will prerun:")]
|
|
3005
|
-
: []),
|
|
3006
|
-
...(willPromoteSet.size
|
|
3007
|
-
? [formatActionSet(willPromoteSet, "- will promote:")]
|
|
3008
|
-
: []),
|
|
3009
|
-
...(willRunSet.size ? [formatActionSet(willRunSet, "- will run:")] : []),
|
|
3010
|
-
...(staysRunningSet.size
|
|
3011
|
-
? [formatActionSet(staysRunningSet, "- stays running:")]
|
|
3012
|
-
: []),
|
|
3013
|
-
...(staysAbortedSet.size
|
|
3014
|
-
? [formatActionSet(staysAbortedSet, "- stays aborted:")]
|
|
3015
|
-
: []),
|
|
3016
|
-
...(staysFailedSet.size
|
|
3017
|
-
? [formatActionSet(staysFailedSet, "- stays failed:")]
|
|
3018
|
-
: []),
|
|
3019
|
-
...(staysCompletedSet.size
|
|
3020
|
-
? [formatActionSet(staysCompletedSet, "- stays completed:")]
|
|
3021
|
-
: []),
|
|
3022
|
-
];
|
|
3023
|
-
console.debug(`operations that will be performed:
|
|
3024
|
-
${lines.join("\n")}`);
|
|
3025
|
-
}
|
|
3026
2939
|
|
|
3027
2940
|
// Step 4: Execute resets
|
|
3028
2941
|
{
|
|
@@ -3127,9 +3040,6 @@ ${lines.join("\n")}`);
|
|
|
3127
3040
|
}
|
|
3128
3041
|
}
|
|
3129
3042
|
}
|
|
3130
|
-
if (DEBUG$2) {
|
|
3131
|
-
console.groupEnd();
|
|
3132
|
-
}
|
|
3133
3043
|
|
|
3134
3044
|
// Calculate requestedResult based on the execution results
|
|
3135
3045
|
let requestedResult;
|
|
@@ -3625,10 +3535,9 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3625
3535
|
toString: () => actionCallSourceSignal.peek(),
|
|
3626
3536
|
meta,
|
|
3627
3537
|
debug: (...args) => {
|
|
3628
|
-
|
|
3538
|
+
{
|
|
3629
3539
|
return;
|
|
3630
3540
|
}
|
|
3631
|
-
console.debug(...args);
|
|
3632
3541
|
},
|
|
3633
3542
|
|
|
3634
3543
|
paramsSignal,
|
|
@@ -3697,9 +3606,6 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3697
3606
|
if (isPrerun && (globalAbortSignal.aborted || abortSignal.aborted)) {
|
|
3698
3607
|
prerunProtectionRegistry.unprotect(action);
|
|
3699
3608
|
}
|
|
3700
|
-
if (DEBUG$2) {
|
|
3701
|
-
console.log(`"${action}" aborted (reason: ${abortReason})`);
|
|
3702
|
-
}
|
|
3703
3609
|
};
|
|
3704
3610
|
|
|
3705
3611
|
const onAbortFromSpecific = () => {
|
|
@@ -3791,9 +3697,6 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3791
3697
|
if (keptAnswerRegistry) {
|
|
3792
3698
|
keptAnswerRegistry.keep(action);
|
|
3793
3699
|
}
|
|
3794
|
-
if (DEBUG$2) {
|
|
3795
|
-
console.log(`"${action}": completed`);
|
|
3796
|
-
}
|
|
3797
3700
|
const data = dataSignal.peek();
|
|
3798
3701
|
return data;
|
|
3799
3702
|
};
|
|
@@ -3818,9 +3721,6 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3818
3721
|
onAbort?.(error, { event, action, args });
|
|
3819
3722
|
return error;
|
|
3820
3723
|
}
|
|
3821
|
-
if (DEBUG$2) {
|
|
3822
|
-
console.log(`"${action}": failed (error: ${error})`);
|
|
3823
|
-
}
|
|
3824
3724
|
error.action = action;
|
|
3825
3725
|
batch(() => {
|
|
3826
3726
|
errorSignal.value = error;
|
|
@@ -3887,9 +3787,6 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3887
3787
|
|
|
3888
3788
|
const performReset = ({ reason, willRunOrPrerun }) => {
|
|
3889
3789
|
abort(reason);
|
|
3890
|
-
if (DEBUG$2) {
|
|
3891
|
-
console.log(`"${action}": resetting (reason: ${reason})`);
|
|
3892
|
-
}
|
|
3893
3790
|
|
|
3894
3791
|
prerunProtectionRegistry.unprotect(action);
|
|
3895
3792
|
if (keptAnswerRegistry) {
|
|
@@ -9457,14 +9354,13 @@ const onRequestInteraction = (
|
|
|
9457
9354
|
} = requestInteractionCustomEvent.detail;
|
|
9458
9355
|
|
|
9459
9356
|
const onPrevented = (reason) => {
|
|
9460
|
-
debugInteraction(event, `"${name}" prevented (${reason})`);
|
|
9461
9357
|
requestInteractionCustomEvent.preventDefault();
|
|
9462
9358
|
prevented?.();
|
|
9463
9359
|
always?.();
|
|
9464
9360
|
};
|
|
9465
9361
|
|
|
9466
9362
|
if (event.defaultPrevented) {
|
|
9467
|
-
onPrevented(
|
|
9363
|
+
onPrevented();
|
|
9468
9364
|
return false;
|
|
9469
9365
|
}
|
|
9470
9366
|
|
|
@@ -9487,10 +9383,6 @@ const onRequestInteraction = (
|
|
|
9487
9383
|
// Unless that element is the one asking: what it took the press to decide is
|
|
9488
9384
|
// arriving here, and it is this control's to answer.
|
|
9489
9385
|
if (isAimedAtSelfInteractionsBelow(event, controlHost, requester)) {
|
|
9490
|
-
debugInteraction(
|
|
9491
|
-
event,
|
|
9492
|
-
`"${name}" is for a self-interactions element below`,
|
|
9493
|
-
);
|
|
9494
9386
|
requestInteractionCustomEvent.preventDefault();
|
|
9495
9387
|
return false;
|
|
9496
9388
|
}
|
|
@@ -9505,7 +9397,7 @@ const onRequestInteraction = (
|
|
|
9505
9397
|
const failedInfo =
|
|
9506
9398
|
ci.interactionFailedConstraintInfo ??
|
|
9507
9399
|
ci.failingManagedInteraction?.interactionFailedConstraintInfo;
|
|
9508
|
-
|
|
9400
|
+
failedInfo
|
|
9509
9401
|
? `failing interaction constraint "${failedInfo.name}"`
|
|
9510
9402
|
: "not interactable";
|
|
9511
9403
|
// A refusal is explained to whoever asked, and an automatic follow-up
|
|
@@ -9518,13 +9410,11 @@ const onRequestInteraction = (
|
|
|
9518
9410
|
if (!automatic) {
|
|
9519
9411
|
ci.reportInteractivity({ event });
|
|
9520
9412
|
}
|
|
9521
|
-
onPrevented(
|
|
9413
|
+
onPrevented();
|
|
9522
9414
|
return false;
|
|
9523
9415
|
}
|
|
9524
9416
|
}
|
|
9525
9417
|
}
|
|
9526
|
-
|
|
9527
|
-
debugInteraction(event, `"${name}" allowed`);
|
|
9528
9418
|
allowed?.();
|
|
9529
9419
|
always?.();
|
|
9530
9420
|
// The click served this control; it must not serve a second time whatever
|
|
@@ -12554,68 +12444,46 @@ const useResetErrorBoundary = () => {
|
|
|
12554
12444
|
return resetErrorBoundary;
|
|
12555
12445
|
};
|
|
12556
12446
|
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12447
|
+
/*
|
|
12448
|
+
* Everything dev-only in this module hangs off one `import.meta.dev` test at
|
|
12449
|
+
* the binding level (`import.meta.dev ? naviDebug.x : noop`), not inside the
|
|
12450
|
+
* function bodies. Rollup follows a binding to the function it names, but
|
|
12451
|
+
* not an early `return noop` inside a body: the first shape lets it drop every
|
|
12452
|
+
* `debugX("…")` call site of the production build, the second keeps them all.
|
|
12453
|
+
*/
|
|
12454
|
+
|
|
12564
12455
|
const debugNoop = () => {};
|
|
12565
|
-
const
|
|
12566
|
-
const
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
const debugPopupDefault = eventGroupLogger.createCategory("[popup]", "#27ae60");
|
|
12570
|
-
const debugUIStateDefault = eventGroupLogger.createCategory("[uistate]", "#7f8c8d");
|
|
12571
|
-
const debugFocusDefault = eventGroupLogger.createCategory("[focus]", "#2980b9");
|
|
12572
|
-
const debugScrollDefault = eventGroupLogger.createCategory("[scroll]", "#2980b9");
|
|
12456
|
+
const useDebugNoop = () => debugNoop;
|
|
12457
|
+
const NaviDebugNoop = ({
|
|
12458
|
+
children
|
|
12459
|
+
}) => children;
|
|
12573
12460
|
|
|
12574
12461
|
// The hooks below expose one concern's logger to components inside <NaviDebug>.
|
|
12575
12462
|
// Each returns the logger function enabled for that concern, or a no-op when the
|
|
12576
12463
|
// concern is off — so call sites can `const debug = useDebugX()` unconditionally.
|
|
12577
12464
|
// The logger is called as `debug(message, …)` or, to group a side effect under
|
|
12578
12465
|
// the native event that caused it, `debug(event, message, …)`.
|
|
12466
|
+
// In production every hook is the no-op returner and the bundler drops the
|
|
12467
|
+
// call sites along with their messages.
|
|
12579
12468
|
|
|
12580
12469
|
/** Logger for navi command dispatch (`--navi-*`), or a no-op when disabled. */
|
|
12581
|
-
const useDebugCommand =
|
|
12582
|
-
const debug = useContext(DebugCommandContext);
|
|
12583
|
-
return debug || debugNoop;
|
|
12584
|
-
};
|
|
12470
|
+
const useDebugCommand = useDebugNoop;
|
|
12585
12471
|
/** Logger for gated interactions (click/scroll/select/…), or a no-op. */
|
|
12586
|
-
const useDebugInteraction =
|
|
12587
|
-
const debug = useContext(DebugInteractionContext);
|
|
12588
|
-
return debug || debugNoop;
|
|
12589
|
-
};
|
|
12472
|
+
const useDebugInteraction = useDebugNoop;
|
|
12590
12473
|
/** Logger for focus moves and focus-visible decisions, or a no-op. */
|
|
12591
|
-
const useDebugFocus =
|
|
12592
|
-
const debug = useContext(DebugFocusContext);
|
|
12593
|
-
return debug || debugNoop;
|
|
12594
|
-
};
|
|
12474
|
+
const useDebugFocus = useDebugNoop;
|
|
12595
12475
|
/**
|
|
12596
12476
|
* Logger for virtual scroll / wheel motion (drag, momentum, glide) and for what
|
|
12597
12477
|
* a virtualized list does about it — the render window moving, and the rows the
|
|
12598
12478
|
* run asks for or decides not to ask for. Or a no-op.
|
|
12599
12479
|
*/
|
|
12600
|
-
const useDebugScroll =
|
|
12601
|
-
const debug = useContext(DebugScrollContext);
|
|
12602
|
-
return debug || debugNoop;
|
|
12603
|
-
};
|
|
12480
|
+
const useDebugScroll = useDebugNoop;
|
|
12604
12481
|
/** Logger for popover/dialog open/close/positioning, or a no-op. */
|
|
12605
|
-
const useDebugPopup =
|
|
12606
|
-
const debug = useContext(DebugPopupContext);
|
|
12607
|
-
return debug || debugNoop;
|
|
12608
|
-
};
|
|
12482
|
+
const useDebugPopup = useDebugNoop;
|
|
12609
12483
|
/** Logger for the action lifecycle (request → run → end), or a no-op. */
|
|
12610
|
-
const useDebugAction =
|
|
12611
|
-
const debug = useContext(DebugActionContext);
|
|
12612
|
-
return debug || debugNoop;
|
|
12613
|
-
};
|
|
12484
|
+
const useDebugAction = useDebugNoop;
|
|
12614
12485
|
/** Logger for UI-state transitions, validation and synthetic events, or a no-op. */
|
|
12615
|
-
const useDebugUIState =
|
|
12616
|
-
const debug = useContext(DebugUIStateContext);
|
|
12617
|
-
return debug || debugNoop;
|
|
12618
|
-
};
|
|
12486
|
+
const useDebugUIState = useDebugNoop;
|
|
12619
12487
|
|
|
12620
12488
|
/**
|
|
12621
12489
|
* Turns on navi's color-coded console logging for everything rendered inside it.
|
|
@@ -12634,6 +12502,8 @@ const useDebugUIState = () => {
|
|
|
12634
12502
|
* `debugScroll` and `debugPopup` unless those are set explicitly, since they
|
|
12635
12503
|
* describe the same interaction.
|
|
12636
12504
|
*
|
|
12505
|
+
* Dev-only: the production build of navi renders the children and logs nothing.
|
|
12506
|
+
*
|
|
12637
12507
|
* @param {object} props
|
|
12638
12508
|
* @param {boolean|Function} [props.debugAll] - Default for every concern below.
|
|
12639
12509
|
* @param {boolean|Function} [props.debugCommand] - navi command dispatch (`--navi-*`).
|
|
@@ -12659,61 +12529,7 @@ const useDebugUIState = () => {
|
|
|
12659
12529
|
* <Wheel>…</Wheel>
|
|
12660
12530
|
* </NaviDebug>
|
|
12661
12531
|
*/
|
|
12662
|
-
const NaviDebug =
|
|
12663
|
-
debugAll,
|
|
12664
|
-
debugCommand = debugAll,
|
|
12665
|
-
debugInteraction = debugAll,
|
|
12666
|
-
debugFocus = debugAll,
|
|
12667
|
-
debugScroll = debugAll,
|
|
12668
|
-
debugPopup = debugAll,
|
|
12669
|
-
debugAction = debugAll,
|
|
12670
|
-
debugUIState = debugAll,
|
|
12671
|
-
children
|
|
12672
|
-
}) => {
|
|
12673
|
-
if (debugCommand === true) {
|
|
12674
|
-
debugCommand = debugCommandDefault;
|
|
12675
|
-
}
|
|
12676
|
-
if (debugInteraction === true) {
|
|
12677
|
-
debugInteraction = debugInteractionDefault;
|
|
12678
|
-
}
|
|
12679
|
-
if (debugFocus === true || debugInteraction && debugFocus === undefined) {
|
|
12680
|
-
debugFocus = debugFocusDefault;
|
|
12681
|
-
}
|
|
12682
|
-
if (debugScroll === true || debugInteraction && debugScroll === undefined) {
|
|
12683
|
-
debugScroll = debugScrollDefault;
|
|
12684
|
-
}
|
|
12685
|
-
if (debugPopup === true || debugInteraction && debugPopup === undefined) {
|
|
12686
|
-
debugPopup = debugPopupDefault;
|
|
12687
|
-
}
|
|
12688
|
-
if (debugAction === true) {
|
|
12689
|
-
debugAction = debugActionDefault;
|
|
12690
|
-
}
|
|
12691
|
-
if (debugUIState === true) {
|
|
12692
|
-
debugUIState = debugUIStateDefault;
|
|
12693
|
-
}
|
|
12694
|
-
return jsx(DebugCommandContext.Provider, {
|
|
12695
|
-
value: debugCommand,
|
|
12696
|
-
children: jsx(DebugInteractionContext.Provider, {
|
|
12697
|
-
value: debugInteraction,
|
|
12698
|
-
children: jsx(DebugFocusContext.Provider, {
|
|
12699
|
-
value: debugFocus,
|
|
12700
|
-
children: jsx(DebugScrollContext.Provider, {
|
|
12701
|
-
value: debugScroll,
|
|
12702
|
-
children: jsx(DebugPopupContext.Provider, {
|
|
12703
|
-
value: debugPopup,
|
|
12704
|
-
children: jsx(DebugActionContext.Provider, {
|
|
12705
|
-
value: debugAction,
|
|
12706
|
-
children: jsx(DebugUIStateContext.Provider, {
|
|
12707
|
-
value: debugUIState,
|
|
12708
|
-
children: children
|
|
12709
|
-
})
|
|
12710
|
-
})
|
|
12711
|
-
})
|
|
12712
|
-
})
|
|
12713
|
-
})
|
|
12714
|
-
})
|
|
12715
|
-
});
|
|
12716
|
-
};
|
|
12532
|
+
const NaviDebug = NaviDebugNoop;
|
|
12717
12533
|
|
|
12718
12534
|
const actionErrorWeakMap = new WeakMap();
|
|
12719
12535
|
const NAVI_ACTION_ERROR_CONSTRAINT = {
|
|
@@ -12755,7 +12571,6 @@ const useExecuteAction = (
|
|
|
12755
12571
|
errorMapping,
|
|
12756
12572
|
} = {},
|
|
12757
12573
|
) => {
|
|
12758
|
-
const debugAction = useDebugAction();
|
|
12759
12574
|
|
|
12760
12575
|
// see https://medium.com/trabe/catching-asynchronous-errors-in-react-using-error-boundaries-5e8a5fd7b971
|
|
12761
12576
|
// and https://codepen.io/dmail/pen/XJJqeGp?editors=0010
|
|
@@ -12854,7 +12669,6 @@ const useExecuteAction = (
|
|
|
12854
12669
|
event: actionEvent,
|
|
12855
12670
|
method,
|
|
12856
12671
|
};
|
|
12857
|
-
debugAction(event, "executing action, requested by", requester);
|
|
12858
12672
|
|
|
12859
12673
|
if (resetErrorBoundary) {
|
|
12860
12674
|
resetErrorBoundary();
|
|
@@ -21917,18 +21731,10 @@ const createUITransitionController = (
|
|
|
21917
21731
|
pauseBreakpoints = [],
|
|
21918
21732
|
} = {},
|
|
21919
21733
|
) => {
|
|
21920
|
-
const debugConfig = {
|
|
21921
|
-
detection: root.hasAttribute("data-debug-detection"),
|
|
21922
|
-
size: root.hasAttribute("data-debug-size"),
|
|
21923
|
-
};
|
|
21924
|
-
const hasDebugLogs = debugConfig.size;
|
|
21925
|
-
const debugDetection = (message) => {
|
|
21926
|
-
if (!debugConfig.detection) return;
|
|
21927
|
-
console.debug(`[detection]`, message);
|
|
21928
|
-
};
|
|
21929
21734
|
const debugSize = (message) => {
|
|
21930
|
-
|
|
21931
|
-
|
|
21735
|
+
{
|
|
21736
|
+
return;
|
|
21737
|
+
}
|
|
21932
21738
|
};
|
|
21933
21739
|
|
|
21934
21740
|
const activeGroup = root.querySelector(".ui_transition_active_group");
|
|
@@ -22134,8 +21940,6 @@ const createUITransitionController = (
|
|
|
22134
21940
|
};
|
|
22135
21941
|
|
|
22136
21942
|
updateAlignment();
|
|
22137
|
-
|
|
22138
|
-
let transitionType = "none";
|
|
22139
21943
|
const groupTransitionOptions = {
|
|
22140
21944
|
// debugBreakpoints: [0.25],
|
|
22141
21945
|
pauseBreakpoints,
|
|
@@ -22171,9 +21975,6 @@ const createUITransitionController = (
|
|
|
22171
21975
|
const fromHeight = previousTargetSlotConfiguration.height || 0;
|
|
22172
21976
|
const toWidth = targetSlotConfiguration.width || 0;
|
|
22173
21977
|
const toHeight = targetSlotConfiguration.height || 0;
|
|
22174
|
-
debugSize(
|
|
22175
|
-
`transition from [${fromWidth}x${fromHeight}] to [${toWidth}x${toHeight}]`,
|
|
22176
|
-
);
|
|
22177
21978
|
const restoreOverflow = preventIntermediateScrollbar(root, {
|
|
22178
21979
|
fromWidth,
|
|
22179
21980
|
fromHeight,
|
|
@@ -22192,7 +21993,6 @@ const createUITransitionController = (
|
|
|
22192
21993
|
}
|
|
22193
21994
|
},
|
|
22194
21995
|
onRestore: () => {
|
|
22195
|
-
debugSize(`Restored overflow after transition`);
|
|
22196
21996
|
},
|
|
22197
21997
|
});
|
|
22198
21998
|
|
|
@@ -22239,9 +22039,6 @@ const createUITransitionController = (
|
|
|
22239
22039
|
toHeight,
|
|
22240
22040
|
alignY,
|
|
22241
22041
|
);
|
|
22242
|
-
debugSize(
|
|
22243
|
-
`Positions in container: from [${fromLeft},${fromTop}] ${fromWidth}x${fromHeight} to [${targetLeft},${targetTop}] ${toWidth}x${toHeight}`,
|
|
22244
|
-
);
|
|
22245
22042
|
// Get border-radius values
|
|
22246
22043
|
const fromBorderRadius =
|
|
22247
22044
|
previousTargetSlotConfiguration.borderRadius || 0;
|
|
@@ -22350,9 +22147,6 @@ const createUITransitionController = (
|
|
|
22350
22147
|
onFinish: () => {
|
|
22351
22148
|
moveConfigurationIntoSlot(UNSET, previousTargetSlot);
|
|
22352
22149
|
moveConfigurationIntoSlot(UNSET, previousOutgoingSlot);
|
|
22353
|
-
if (hasDebugLogs) {
|
|
22354
|
-
console.groupEnd();
|
|
22355
|
-
}
|
|
22356
22150
|
},
|
|
22357
22151
|
});
|
|
22358
22152
|
transition.play();
|
|
@@ -22371,10 +22165,6 @@ const createUITransitionController = (
|
|
|
22371
22165
|
const transition = transitionController.update(transitions, {
|
|
22372
22166
|
onFinish: () => {
|
|
22373
22167
|
moveConfigurationIntoSlot(UNSET, outgoingSlot);
|
|
22374
|
-
|
|
22375
|
-
if (hasDebugLogs) {
|
|
22376
|
-
console.groupEnd();
|
|
22377
|
-
}
|
|
22378
22168
|
},
|
|
22379
22169
|
});
|
|
22380
22170
|
transition.play();
|
|
@@ -22393,9 +22183,6 @@ const createUITransitionController = (
|
|
|
22393
22183
|
onFinish: () => {
|
|
22394
22184
|
moveConfigurationIntoSlot(UNSET, previousTargetSlot);
|
|
22395
22185
|
moveConfigurationIntoSlot(UNSET, previousOutgoingSlot);
|
|
22396
|
-
if (hasDebugLogs) {
|
|
22397
|
-
console.groupEnd();
|
|
22398
|
-
}
|
|
22399
22186
|
},
|
|
22400
22187
|
});
|
|
22401
22188
|
transition.play();
|
|
@@ -22437,26 +22224,11 @@ const createUITransitionController = (
|
|
|
22437
22224
|
}
|
|
22438
22225
|
const fromConfiguration = targetSlotConfiguration;
|
|
22439
22226
|
const toConfiguration = detectConfiguration(targetSlot);
|
|
22440
|
-
if (hasDebugLogs) {
|
|
22441
|
-
console.group(`targetSlotEffect()`);
|
|
22442
|
-
console.debug(`reasons:`);
|
|
22443
|
-
console.debug(`- ${reasons.join("\n- ")}`);
|
|
22444
|
-
}
|
|
22445
22227
|
if (isSameConfiguration(fromConfiguration, toConfiguration)) {
|
|
22446
|
-
debugDetection(
|
|
22447
|
-
`already in desired state (${toConfiguration}) -> early return`,
|
|
22448
|
-
);
|
|
22449
|
-
if (hasDebugLogs) {
|
|
22450
|
-
console.groupEnd();
|
|
22451
|
-
}
|
|
22452
22228
|
return;
|
|
22453
22229
|
}
|
|
22454
|
-
|
|
22455
|
-
|
|
22456
|
-
transitionType = `${fromConfigType}_to_${toConfigType}`;
|
|
22457
|
-
debugDetection(
|
|
22458
|
-
`Prepare "${transitionType}" transition (${fromConfiguration} -> ${toConfiguration})`,
|
|
22459
|
-
);
|
|
22230
|
+
fromConfiguration.type;
|
|
22231
|
+
toConfiguration.type;
|
|
22460
22232
|
// content_to_empty / content_phase_to_empty
|
|
22461
22233
|
if (toConfiguration.isEmpty) {
|
|
22462
22234
|
applyToEmptyTransition();
|
|
@@ -22529,7 +22301,7 @@ const createUITransitionController = (
|
|
|
22529
22301
|
if (reasonParts.length === 0) {
|
|
22530
22302
|
return;
|
|
22531
22303
|
}
|
|
22532
|
-
targetSlotEffect(
|
|
22304
|
+
targetSlotEffect();
|
|
22533
22305
|
});
|
|
22534
22306
|
mutationObserver.observe(targetSlot, {
|
|
22535
22307
|
childList: true,
|
|
@@ -22775,13 +22547,7 @@ const useUITransitionContentId = value => {
|
|
|
22775
22547
|
* Replaces URLPattern with a simpler, more predictable approach
|
|
22776
22548
|
*/
|
|
22777
22549
|
|
|
22778
|
-
|
|
22779
|
-
const DEBUG$1 =
|
|
22780
|
-
typeof process === "object" ? process.env.DEBUG === "true" : false;
|
|
22781
22550
|
const debug$2 = (...args) => {
|
|
22782
|
-
if (DEBUG$1) {
|
|
22783
|
-
console.debug(...args);
|
|
22784
|
-
}
|
|
22785
22551
|
};
|
|
22786
22552
|
|
|
22787
22553
|
// Base URL management
|
|
@@ -22891,9 +22657,6 @@ const createRoutePattern = (
|
|
|
22891
22657
|
queryConnectionMap,
|
|
22892
22658
|
paramConstraintMap,
|
|
22893
22659
|
});
|
|
22894
|
-
|
|
22895
|
-
debug$2(`[CustomPattern] Created pattern:`, parsedPattern);
|
|
22896
|
-
debug$2(`[CustomPattern] Signal connections:`, connections);
|
|
22897
22660
|
debug$2(`[CustomPattern] Path connections:`, pathConnectionMap.size);
|
|
22898
22661
|
debug$2(`[CustomPattern] Query connections:`, queryConnectionMap.size);
|
|
22899
22662
|
|
|
@@ -22915,11 +22678,6 @@ const createRoutePattern = (
|
|
|
22915
22678
|
patternObj: patternObject,
|
|
22916
22679
|
});
|
|
22917
22680
|
|
|
22918
|
-
debug$2(
|
|
22919
|
-
`[CustomPattern] Matching "${url}" against "${cleanPattern}":`,
|
|
22920
|
-
result,
|
|
22921
|
-
);
|
|
22922
|
-
|
|
22923
22681
|
return result;
|
|
22924
22682
|
};
|
|
22925
22683
|
|
|
@@ -24507,10 +24265,7 @@ const buildHierarchicalQueryParams = (
|
|
|
24507
24265
|
currentParent = currentParent.parent;
|
|
24508
24266
|
}
|
|
24509
24267
|
}
|
|
24510
|
-
|
|
24511
|
-
debug$2(`Building params for ${originalPattern}`);
|
|
24512
24268
|
debug$2(`parsedPattern:`, parsedPattern.original);
|
|
24513
|
-
debug$2(`params:`, params);
|
|
24514
24269
|
debug$2(
|
|
24515
24270
|
`ancestorPatterns:`,
|
|
24516
24271
|
ancestorPatterns.map((p) => p.urlPatternRaw),
|
|
@@ -24947,7 +24702,6 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
24947
24702
|
for (const routePattern of routePatternSet) {
|
|
24948
24703
|
calculatePatternDepth(routePattern);
|
|
24949
24704
|
}
|
|
24950
|
-
debug$2("Pattern registry updated");
|
|
24951
24705
|
};
|
|
24952
24706
|
// Store the most specific parent (closest parent in hierarchy)
|
|
24953
24707
|
const getPathSegmentCount = (pattern) => {
|
|
@@ -25184,9 +24938,30 @@ const route = (
|
|
|
25184
24938
|
});
|
|
25185
24939
|
// methods
|
|
25186
24940
|
registerSetup(({ routeSet, getUrl }) => {
|
|
24941
|
+
// One computed per set of params. Building a url reads the route family's
|
|
24942
|
+
// signals (see readSignalForUrlBuild) and verifies the candidate against
|
|
24943
|
+
// every pattern of the family; a list of a hundred links to one route
|
|
24944
|
+
// would do that a hundred times on each of its renders. A computed does it
|
|
24945
|
+
// once, again only when a signal it read changes — and a component reading
|
|
24946
|
+
// it subscribes to exactly those, which is what reading the signals during
|
|
24947
|
+
// render gave it.
|
|
24948
|
+
const relativeUrlByParamsKey = new Map();
|
|
25187
24949
|
route.buildRelativeUrl = (params) => {
|
|
25188
|
-
|
|
25189
|
-
|
|
24950
|
+
const paramsKey = getParamsCacheKey(params);
|
|
24951
|
+
if (paramsKey === null) {
|
|
24952
|
+
return routePattern.buildMostPreciseUrl(params);
|
|
24953
|
+
}
|
|
24954
|
+
let relativeUrlComputed = relativeUrlByParamsKey.get(paramsKey);
|
|
24955
|
+
if (!relativeUrlComputed) {
|
|
24956
|
+
if (relativeUrlByParamsKey.size >= PARAMS_CACHE_MAX) {
|
|
24957
|
+
relativeUrlByParamsKey.clear();
|
|
24958
|
+
}
|
|
24959
|
+
relativeUrlComputed = computed(() =>
|
|
24960
|
+
routePattern.buildMostPreciseUrl(params),
|
|
24961
|
+
);
|
|
24962
|
+
relativeUrlByParamsKey.set(paramsKey, relativeUrlComputed);
|
|
24963
|
+
}
|
|
24964
|
+
return relativeUrlComputed.value;
|
|
25190
24965
|
};
|
|
25191
24966
|
route.buildUrl = (params) => {
|
|
25192
24967
|
const routeRelativeUrl = route.buildRelativeUrl(params);
|
|
@@ -25950,6 +25725,36 @@ const setOnAllRouteReady = (callback) => {
|
|
|
25950
25725
|
onAllRouteReady = callback;
|
|
25951
25726
|
};
|
|
25952
25727
|
|
|
25728
|
+
// Params are cached by content, and only params whose content a string can
|
|
25729
|
+
// stand for: anything else (an object, a function) builds without the cache.
|
|
25730
|
+
// A key set to undefined is content too — it says "without this one", which
|
|
25731
|
+
// a params object not naming it does not say (see buildIntendedState).
|
|
25732
|
+
const PARAMS_CACHE_MAX = 1000;
|
|
25733
|
+
const getParamsCacheKey = (params) => {
|
|
25734
|
+
if (params === undefined || params === null) {
|
|
25735
|
+
return "";
|
|
25736
|
+
}
|
|
25737
|
+
if (typeof params !== "object") {
|
|
25738
|
+
return null;
|
|
25739
|
+
}
|
|
25740
|
+
const keys = Object.keys(params).sort();
|
|
25741
|
+
let key = "";
|
|
25742
|
+
for (const name of keys) {
|
|
25743
|
+
const value = params[name];
|
|
25744
|
+
const type = typeof value;
|
|
25745
|
+
if (
|
|
25746
|
+
value !== null &&
|
|
25747
|
+
type !== "string" &&
|
|
25748
|
+
type !== "number" &&
|
|
25749
|
+
type !== "boolean"
|
|
25750
|
+
) {
|
|
25751
|
+
return null;
|
|
25752
|
+
}
|
|
25753
|
+
key += `${name}=${type}:${value}\n`;
|
|
25754
|
+
}
|
|
25755
|
+
return key;
|
|
25756
|
+
};
|
|
25757
|
+
|
|
25953
25758
|
/**
|
|
25954
25759
|
* Is there an entry of THIS document behind the current one? And ahead of it?
|
|
25955
25760
|
*
|
|
@@ -35130,9 +34935,7 @@ const useAutoFocus = (
|
|
|
35130
34935
|
return () => {};
|
|
35131
34936
|
}
|
|
35132
34937
|
const focusDebugCall = `${getElementSignature(focusableElement)}.focus({ preventScroll: ${preventScroll} })`;
|
|
35133
|
-
if (ancestorType === "document") {
|
|
35134
|
-
debugFocus(e, `[autofocus] document -> ${focusDebugCall}`);
|
|
35135
|
-
} else {
|
|
34938
|
+
if (ancestorType === "document") ; else {
|
|
35136
34939
|
debugFocus(
|
|
35137
34940
|
e,
|
|
35138
34941
|
`[autofocus] "${ancestorType}" opened ${getElementSignature(ancestor)} -> ${focusDebugCall}`,
|
|
@@ -35868,10 +35671,6 @@ const useUIStateController = (
|
|
|
35868
35671
|
[buttonName]: controller.uiState,
|
|
35869
35672
|
};
|
|
35870
35673
|
parentController.syncInternalState(mergedState);
|
|
35871
|
-
debugUIState(
|
|
35872
|
-
`merging button state into parent control group:`,
|
|
35873
|
-
mergedState,
|
|
35874
|
-
);
|
|
35875
35674
|
// The sender's value is part of the answer, and whoever mirrors
|
|
35876
35675
|
// this group — a picker, through its façade — reads the group's
|
|
35877
35676
|
// state, not its children's: told the way a mount sync is
|
|
@@ -35890,7 +35689,6 @@ const useUIStateController = (
|
|
|
35890
35689
|
writeBoundSignal(currentUIState);
|
|
35891
35690
|
s.uiActionInternal?.(currentUIState, e);
|
|
35892
35691
|
if (s.uiAction) {
|
|
35893
|
-
debugUIState(`calling uiAction for ${controlType}`, currentUIState);
|
|
35894
35692
|
s.uiAction(currentUIState, e);
|
|
35895
35693
|
}
|
|
35896
35694
|
if (skipCommand) ; else {
|
|
@@ -35898,9 +35696,6 @@ const useUIStateController = (
|
|
|
35898
35696
|
if (command) {
|
|
35899
35697
|
const element = controller.ref.current;
|
|
35900
35698
|
if (element) {
|
|
35901
|
-
debugUIState(
|
|
35902
|
-
`triggering command "${command}" for "${controlType}"`,
|
|
35903
|
-
);
|
|
35904
35699
|
// What the press means may not be due yet: a button with an
|
|
35905
35700
|
// action of its own runs the work first and lets its command
|
|
35906
35701
|
// follow only once that work succeeded (see control_hooks).
|
|
@@ -36185,10 +35980,6 @@ const useUIStateController = (
|
|
|
36185
35980
|
if (!existingInputEvent) {
|
|
36186
35981
|
if (el.tagName === "INPUT") {
|
|
36187
35982
|
if (el.type === "radio" || el.type === "checkbox") {
|
|
36188
|
-
debugUIState(
|
|
36189
|
-
e,
|
|
36190
|
-
"dispatching synthetic input event without data for checkbox/radio",
|
|
36191
|
-
);
|
|
36192
35983
|
dispatchSyntheticInput(
|
|
36193
35984
|
el,
|
|
36194
35985
|
new Event("input", { bubbles: true }),
|
|
@@ -36196,10 +35987,6 @@ const useUIStateController = (
|
|
|
36196
35987
|
);
|
|
36197
35988
|
syntheticInputFired = true;
|
|
36198
35989
|
} else {
|
|
36199
|
-
debugUIState(
|
|
36200
|
-
e,
|
|
36201
|
-
`dispatching synthetic input event with data "${newUIState}" for input`,
|
|
36202
|
-
);
|
|
36203
35990
|
dispatchSyntheticInput(
|
|
36204
35991
|
el,
|
|
36205
35992
|
new InputEvent("input", {
|
|
@@ -36213,10 +36000,6 @@ const useUIStateController = (
|
|
|
36213
36000
|
syntheticInputFired = true;
|
|
36214
36001
|
}
|
|
36215
36002
|
} else if (el.tagName === "SELECT") {
|
|
36216
|
-
debugUIState(
|
|
36217
|
-
e,
|
|
36218
|
-
`dispatching synthetic input event for select "${newUIState}"`,
|
|
36219
|
-
);
|
|
36220
36003
|
// A plain Event, not an InputEvent: that is what the browser
|
|
36221
36004
|
// itself fires on a select, and input_effect reads the value off
|
|
36222
36005
|
// the element anyway.
|
|
@@ -36273,12 +36056,10 @@ const useUIStateController = (
|
|
|
36273
36056
|
controller.state = controller.uiState;
|
|
36274
36057
|
},
|
|
36275
36058
|
onActionEnd: (e) => {
|
|
36276
|
-
debugUIState(`"${controlType}" actionEnd called`);
|
|
36277
36059
|
acknowledgeOwnAction(controller);
|
|
36278
36060
|
controller.rules.validation.syncValidity(e);
|
|
36279
36061
|
},
|
|
36280
36062
|
onActionError: (e) => {
|
|
36281
|
-
debugUIState(`"${controlType}" actionError called`);
|
|
36282
36063
|
controller.rules.validation.syncValidity(e, { report: true });
|
|
36283
36064
|
},
|
|
36284
36065
|
subscribe: subscribeUIState,
|
|
@@ -36917,9 +36698,6 @@ const useUIGroupStateController = (
|
|
|
36917
36698
|
const scope = useRenderScope(
|
|
36918
36699
|
// ── init: runs once on mount ───────────────────────────────────────────
|
|
36919
36700
|
(s) => {
|
|
36920
|
-
debugUIGroup(
|
|
36921
|
-
`Creating "${controlType}" ui state controller (monitoring some descendants ui state(s))"`,
|
|
36922
|
-
);
|
|
36923
36701
|
const [publishUIState, subscribeUIState] = createPubSub();
|
|
36924
36702
|
const uiStateSignal = signal(stateInitial);
|
|
36925
36703
|
|
|
@@ -37424,9 +37202,6 @@ const useUIGroupStateController = (
|
|
|
37424
37202
|
childUIStateController,
|
|
37425
37203
|
);
|
|
37426
37204
|
if (index === -1) {
|
|
37427
|
-
debugUIGroup(
|
|
37428
|
-
`${controlType}.unregisterChild("${childControlType}") -> not found`,
|
|
37429
|
-
);
|
|
37430
37205
|
return;
|
|
37431
37206
|
}
|
|
37432
37207
|
childUIStateControllerArray.splice(index, 1);
|
|
@@ -38182,7 +37957,6 @@ const useControlProps = (props, {
|
|
|
38182
37957
|
uiActionInternal
|
|
38183
37958
|
}) => {
|
|
38184
37959
|
const debugUIState = useDebugUIState();
|
|
38185
|
-
const debugAction = useDebugAction();
|
|
38186
37960
|
|
|
38187
37961
|
// This control answers for itself: whatever group it sits in expects nothing
|
|
38188
37962
|
// from it and gives it nothing back. See useUIStateController, which is where
|
|
@@ -38805,7 +38579,6 @@ const useControlProps = (props, {
|
|
|
38805
38579
|
const lastActionValue = lastActionValueRef.current;
|
|
38806
38580
|
const valueSameAsLastAction = lastActionValue !== NO_ACTION_YET && compareTwoJsValues(currentValue, lastActionValue);
|
|
38807
38581
|
if (valueSameAsLastAction) {
|
|
38808
|
-
debugAction(e, `skipping action: value same as last action`);
|
|
38809
38582
|
return false;
|
|
38810
38583
|
}
|
|
38811
38584
|
}
|
|
@@ -38860,7 +38633,6 @@ const useControlProps = (props, {
|
|
|
38860
38633
|
intent,
|
|
38861
38634
|
bypassInteractivity,
|
|
38862
38635
|
prevented: () => {
|
|
38863
|
-
debugInteraction(e, `interaction not allowed`);
|
|
38864
38636
|
if (e.type === "keydown") {
|
|
38865
38637
|
e.preventDefault();
|
|
38866
38638
|
}
|
|
@@ -39842,7 +39614,6 @@ const useInteractiveProps = (props, {
|
|
|
39842
39614
|
// awaited before the queued request runs. See the abort section in
|
|
39843
39615
|
// docs/actions.md.
|
|
39844
39616
|
if (optimistic && uiStateController.actionInFlight) {
|
|
39845
|
-
debugAction(e, `queueing action (one already in flight)`);
|
|
39846
39617
|
uiStateController.queuedActionAllowedEvent = e;
|
|
39847
39618
|
// The instance captured at navi_action_start, NOT boundAction:
|
|
39848
39619
|
// boundAction is a proxy following the UI state signal, and the UI
|
|
@@ -39925,7 +39696,6 @@ const useInteractiveProps = (props, {
|
|
|
39925
39696
|
const {
|
|
39926
39697
|
error
|
|
39927
39698
|
} = e.detail;
|
|
39928
|
-
debugAction(e, `action error`, error);
|
|
39929
39699
|
if (resetOnError) {
|
|
39930
39700
|
dispatchRequestResetUIState(e.currentTarget, e);
|
|
39931
39701
|
}
|
|
@@ -42015,6 +41785,73 @@ const LoadingOutlineWithPortal = props => {
|
|
|
42015
41785
|
});
|
|
42016
41786
|
};
|
|
42017
41787
|
|
|
41788
|
+
/**
|
|
41789
|
+
* Reads of the layout gathered across effects, so that the browser brings its
|
|
41790
|
+
* styles and layout up to date once for all of them rather than once per
|
|
41791
|
+
* element.
|
|
41792
|
+
*
|
|
41793
|
+
* A layout effect that reads (a computed style, a rect) and then writes (an
|
|
41794
|
+
* attribute, a style) is fine on its own. A hundred of them in one commit are
|
|
41795
|
+
* not: every write dirties the tree, and the next element's read forces the
|
|
41796
|
+
* browser to recompute it — a style recalculation per element, each one over
|
|
41797
|
+
* everything dirtied since the last. Most of a long mount's time goes there,
|
|
41798
|
+
* not in the effects themselves.
|
|
41799
|
+
*
|
|
41800
|
+
* So an effect hands its read over instead of running it. Reads run together
|
|
41801
|
+
* in a microtask — still before the paint, so nothing shows uncorrected — and
|
|
41802
|
+
* each returns the write that depends on it, run after every read of the
|
|
41803
|
+
* round. A write that needs to read again returns that read: it joins the next
|
|
41804
|
+
* round, reads first, writes after, and so on until nothing is left.
|
|
41805
|
+
*
|
|
41806
|
+
* The cancel returned is for an element unmounted before its round: a write
|
|
41807
|
+
* on a node that is gone is at best wasted.
|
|
41808
|
+
*
|
|
41809
|
+
* @param {() => (undefined | (() => undefined | Function))} read
|
|
41810
|
+
* @returns {() => void} cancel
|
|
41811
|
+
*/
|
|
41812
|
+
const scheduleLayoutRead = (read) => {
|
|
41813
|
+
const entry = { read, cancelled: false };
|
|
41814
|
+
round.push(entry);
|
|
41815
|
+
if (!flushScheduled) {
|
|
41816
|
+
flushScheduled = true;
|
|
41817
|
+
queueMicrotask(flush);
|
|
41818
|
+
}
|
|
41819
|
+
return () => {
|
|
41820
|
+
entry.cancelled = true;
|
|
41821
|
+
};
|
|
41822
|
+
};
|
|
41823
|
+
|
|
41824
|
+
let round = [];
|
|
41825
|
+
let flushScheduled = false;
|
|
41826
|
+
|
|
41827
|
+
const flush = () => {
|
|
41828
|
+
flushScheduled = false;
|
|
41829
|
+
while (round.length > 0) {
|
|
41830
|
+
const entries = round;
|
|
41831
|
+
round = [];
|
|
41832
|
+
const writes = [];
|
|
41833
|
+
for (const entry of entries) {
|
|
41834
|
+
if (entry.cancelled) {
|
|
41835
|
+
continue;
|
|
41836
|
+
}
|
|
41837
|
+
const write = entry.read();
|
|
41838
|
+
if (typeof write === "function") {
|
|
41839
|
+
writes.push({ entry, write });
|
|
41840
|
+
}
|
|
41841
|
+
}
|
|
41842
|
+
for (const { entry, write } of writes) {
|
|
41843
|
+
if (entry.cancelled) {
|
|
41844
|
+
continue;
|
|
41845
|
+
}
|
|
41846
|
+
const readAgain = write();
|
|
41847
|
+
if (typeof readAgain === "function") {
|
|
41848
|
+
entry.read = readAgain;
|
|
41849
|
+
round.push(entry);
|
|
41850
|
+
}
|
|
41851
|
+
}
|
|
41852
|
+
}
|
|
41853
|
+
};
|
|
41854
|
+
|
|
42018
41855
|
installImportMetaCssBuild(import.meta);
|
|
42019
41856
|
const css$_ = /* css */`.navi_text_anchor {
|
|
42020
41857
|
vertical-align: baseline;
|
|
@@ -42056,6 +41893,16 @@ const TextAnchor = ({
|
|
|
42056
41893
|
}) => {
|
|
42057
41894
|
import.meta.css = [css$_, "@jsenv/navi/src/text/text_anchor.jsx"];
|
|
42058
41895
|
const anchorRef = useRef();
|
|
41896
|
+
// The correction reads the layout, with the other reads of the commit (see
|
|
41897
|
+
// layout_batch.js) — an anchor per icon, each measured between two writes,
|
|
41898
|
+
// is a style recalculation per icon. Cancelled by a newer correction or by
|
|
41899
|
+
// the unmount: a write on a node that is gone is wasted.
|
|
41900
|
+
const cancelReadRef = useRef(null);
|
|
41901
|
+
useLayoutEffect(() => {
|
|
41902
|
+
return () => {
|
|
41903
|
+
cancelReadRef.current?.();
|
|
41904
|
+
};
|
|
41905
|
+
}, []);
|
|
42059
41906
|
|
|
42060
41907
|
// Plain useLayoutEffect would also fire while an ancestor dialog/popover
|
|
42061
41908
|
// (e.g. a closed SidePanel) is still display:none — every rect involved
|
|
@@ -42084,23 +41931,36 @@ const TextAnchor = ({
|
|
|
42084
41931
|
if (!anchorEl || !childEl) {
|
|
42085
41932
|
return;
|
|
42086
41933
|
}
|
|
42087
|
-
|
|
42088
|
-
|
|
42089
|
-
|
|
42090
|
-
|
|
42091
|
-
|
|
42092
|
-
|
|
42093
|
-
|
|
42094
|
-
|
|
42095
|
-
|
|
42096
|
-
|
|
42097
|
-
|
|
42098
|
-
|
|
42099
|
-
|
|
42100
|
-
|
|
42101
|
-
|
|
41934
|
+
cancelReadRef.current?.();
|
|
41935
|
+
cancelReadRef.current = scheduleLayoutRead(() => {
|
|
41936
|
+
// Only correct when the anchor lives in an inline formatting context.
|
|
41937
|
+
// If the parent is a flex/grid container, inline layout rules don't
|
|
41938
|
+
// apply and our font-metrics model is invalid.
|
|
41939
|
+
const parentDisplay = getComputedStyle(anchorEl.parentElement).display;
|
|
41940
|
+
if (parentDisplay !== "inline" && parentDisplay !== "inline-block" && parentDisplay !== "block") {
|
|
41941
|
+
return () => {
|
|
41942
|
+
// we must hide the anchor otherwise it would affect layout without
|
|
41943
|
+
// providing any benefit (would trigger flex gap for instance)
|
|
41944
|
+
anchorEl.setAttribute("hidden", "");
|
|
41945
|
+
setTopOffset(childEl, 0);
|
|
41946
|
+
};
|
|
41947
|
+
}
|
|
41948
|
+
return () => {
|
|
41949
|
+
// The anchor has to be in the flow to be measured: shown here, read
|
|
41950
|
+
// in the next round, with the other anchors shown by this one.
|
|
41951
|
+
anchorEl.removeAttribute("hidden");
|
|
41952
|
+
return () => {
|
|
41953
|
+
const topOffset = computeTopOffset({
|
|
41954
|
+
anchorEl,
|
|
41955
|
+
childEl,
|
|
41956
|
+
textAnchor
|
|
41957
|
+
});
|
|
41958
|
+
return () => {
|
|
41959
|
+
setTopOffset(childEl, topOffset);
|
|
41960
|
+
};
|
|
41961
|
+
};
|
|
41962
|
+
};
|
|
42102
41963
|
});
|
|
42103
|
-
setTopOffset(childEl, topOffset);
|
|
42104
41964
|
}, [textAnchor, textKey, textSize, lineLayout?.size, lineLayout?.verticalAlign]);
|
|
42105
41965
|
return jsxs(Fragment, {
|
|
42106
41966
|
children: [children, jsx("span", {
|
|
@@ -47771,53 +47631,35 @@ const useNextResolver = () => useContext(NextResolverContext);
|
|
|
47771
47631
|
* // Then inside a component render:
|
|
47772
47632
|
* renderButton(props)
|
|
47773
47633
|
*
|
|
47774
|
-
*
|
|
47775
|
-
* the
|
|
47776
|
-
*
|
|
47777
|
-
*
|
|
47634
|
+
* Each position of the chain has a runner of its own, defined once: it renders
|
|
47635
|
+
* its resolver under a NextResolverContext holding the runner after it. A
|
|
47636
|
+
* resolver that re-renders on its own and renders <Next> therefore resumes at
|
|
47637
|
+
* its own position without any bookkeeping — the Next it was given is the one
|
|
47638
|
+
* made for it. A chain rendered by the hundred (a list's rows) pays two
|
|
47639
|
+
* components per position, the runner and the resolver, so nothing here is a
|
|
47640
|
+
* component that merely forwards.
|
|
47778
47641
|
*/
|
|
47779
47642
|
const createComponentResolver = (resolvers, {
|
|
47780
47643
|
pure
|
|
47781
47644
|
} = {}) => {
|
|
47782
|
-
const
|
|
47783
|
-
const
|
|
47784
|
-
|
|
47785
|
-
if (index >= resolvers.length) {
|
|
47786
|
-
return null;
|
|
47787
|
-
}
|
|
47645
|
+
const runners = [];
|
|
47646
|
+
const lastIndex = resolvers.length - 1;
|
|
47647
|
+
for (let index = 0; index < resolvers.length; index++) {
|
|
47788
47648
|
const Resolver = resolvers[index];
|
|
47789
|
-
const isLast = index ===
|
|
47790
|
-
|
|
47791
|
-
value: index + 1,
|
|
47792
|
-
children:
|
|
47793
|
-
value: null,
|
|
47794
|
-
children: jsx(Resolver, {
|
|
47795
|
-
...props
|
|
47796
|
-
})
|
|
47797
|
-
}) : jsx(Resolver, {
|
|
47649
|
+
const isLast = index === lastIndex;
|
|
47650
|
+
const Runner = props => jsx(NextResolverContext.Provider, {
|
|
47651
|
+
value: isLast ? null : runners[index + 1],
|
|
47652
|
+
children: jsx(Resolver, {
|
|
47798
47653
|
...props
|
|
47799
47654
|
})
|
|
47800
47655
|
});
|
|
47801
|
-
|
|
47802
|
-
|
|
47803
|
-
|
|
47804
|
-
|
|
47805
|
-
|
|
47806
|
-
// resumes from index+1 (already set by the Provider wrapping that resolver).
|
|
47807
|
-
const NextComponent = props => jsx(ChainRunner, {
|
|
47656
|
+
Runner.displayName = `${Resolver.displayName || Resolver.name}Runner`;
|
|
47657
|
+
runners.push(Runner);
|
|
47658
|
+
}
|
|
47659
|
+
const FirstRunner = runners[0];
|
|
47660
|
+
const renderComponent = props => jsx(FirstRunner, {
|
|
47808
47661
|
...props
|
|
47809
47662
|
});
|
|
47810
|
-
const renderComponent = props => {
|
|
47811
|
-
return jsx(NextResolverContext.Provider, {
|
|
47812
|
-
value: NextComponent,
|
|
47813
|
-
children: jsx(ResolverIndexContext.Provider, {
|
|
47814
|
-
value: 0,
|
|
47815
|
-
children: jsx(ChainRunner, {
|
|
47816
|
-
...props
|
|
47817
|
-
})
|
|
47818
|
-
})
|
|
47819
|
-
});
|
|
47820
|
-
};
|
|
47821
47663
|
if (!pure) {
|
|
47822
47664
|
return renderComponent;
|
|
47823
47665
|
}
|
|
@@ -47913,36 +47755,36 @@ const useAccentColorAttributes = (
|
|
|
47913
47755
|
return undefined;
|
|
47914
47756
|
}
|
|
47915
47757
|
}
|
|
47758
|
+
// Read with the other layout reads of the commit (see layout_batch.js): a
|
|
47759
|
+
// computed color per control, each read between two writes, is a style
|
|
47760
|
+
// recalculation per control.
|
|
47761
|
+
let cancelRead = null;
|
|
47916
47762
|
const updateAttributes = () => {
|
|
47917
|
-
|
|
47918
|
-
|
|
47919
|
-
|
|
47920
|
-
|
|
47921
|
-
|
|
47922
|
-
|
|
47923
|
-
|
|
47924
|
-
|
|
47925
|
-
|
|
47926
|
-
|
|
47927
|
-
|
|
47928
|
-
|
|
47929
|
-
|
|
47930
|
-
|
|
47931
|
-
|
|
47932
|
-
|
|
47933
|
-
|
|
47934
|
-
|
|
47935
|
-
|
|
47936
|
-
|
|
47937
|
-
|
|
47938
|
-
|
|
47939
|
-
|
|
47940
|
-
|
|
47941
|
-
|
|
47942
|
-
el.setAttribute(DARK_CONTRAST_ATTRIBUTE, "");
|
|
47943
|
-
} else {
|
|
47944
|
-
el.removeAttribute(DARK_CONTRAST_ATTRIBUTE);
|
|
47945
|
-
}
|
|
47763
|
+
cancelRead?.();
|
|
47764
|
+
cancelRead = scheduleLayoutRead(() => {
|
|
47765
|
+
const computedStyle = getComputedStyle(elementToCheck);
|
|
47766
|
+
const color = computedStyle[colorProperty];
|
|
47767
|
+
if (!color) {
|
|
47768
|
+
return () => {
|
|
47769
|
+
el.removeAttribute(LIGHT_ACCENT_ATTRIBUTE);
|
|
47770
|
+
el.removeAttribute(VERY_LIGHT_ACCENT_ATTRIBUTE);
|
|
47771
|
+
el.removeAttribute(DARK_CONTRAST_ATTRIBUTE);
|
|
47772
|
+
};
|
|
47773
|
+
}
|
|
47774
|
+
const luminance = resolveOklchLightness(color, el);
|
|
47775
|
+
const light =
|
|
47776
|
+
luminance !== null && luminance > LIGHT_LUMINANCE_THRESHOLD;
|
|
47777
|
+
const veryLight =
|
|
47778
|
+
luminance !== null && luminance > VERY_LIGHT_LUMINANCE_THRESHOLD;
|
|
47779
|
+
const needsDarkForeground =
|
|
47780
|
+
contrastColor(color, el, DARK_CONTRAST_LIGHTNESS_THRESHOLD) ===
|
|
47781
|
+
"black";
|
|
47782
|
+
return () => {
|
|
47783
|
+
el.toggleAttribute(LIGHT_ACCENT_ATTRIBUTE, light);
|
|
47784
|
+
el.toggleAttribute(VERY_LIGHT_ACCENT_ATTRIBUTE, veryLight);
|
|
47785
|
+
el.toggleAttribute(DARK_CONTRAST_ATTRIBUTE, needsDarkForeground);
|
|
47786
|
+
};
|
|
47787
|
+
});
|
|
47946
47788
|
};
|
|
47947
47789
|
updateAttributes();
|
|
47948
47790
|
const unsubscribeFromPseudoState = subscribeToPseudoState(
|
|
@@ -47950,6 +47792,7 @@ const useAccentColorAttributes = (
|
|
|
47950
47792
|
updateAttributes,
|
|
47951
47793
|
);
|
|
47952
47794
|
return () => {
|
|
47795
|
+
cancelRead?.();
|
|
47953
47796
|
unsubscribeFromPseudoState();
|
|
47954
47797
|
el.removeAttribute(LIGHT_ACCENT_ATTRIBUTE);
|
|
47955
47798
|
el.removeAttribute(VERY_LIGHT_ACCENT_ATTRIBUTE);
|
|
@@ -57468,26 +57311,11 @@ const useInputGroup = (ref) => {
|
|
|
57468
57311
|
const inputs = getInputs();
|
|
57469
57312
|
const idx = inputs.indexOf(active);
|
|
57470
57313
|
if (idx === -1) {
|
|
57471
|
-
debugFocus(
|
|
57472
|
-
e,
|
|
57473
|
-
"InputGroup ArrowRight on non group input → do nothing",
|
|
57474
|
-
);
|
|
57475
57314
|
return;
|
|
57476
57315
|
}
|
|
57477
57316
|
if (idx === inputs.length - 1) {
|
|
57478
|
-
debugFocus(
|
|
57479
|
-
e,
|
|
57480
|
-
"InputGroup ArrowRight at end of last input → do nothing",
|
|
57481
|
-
);
|
|
57482
57317
|
return;
|
|
57483
57318
|
}
|
|
57484
|
-
|
|
57485
|
-
debugFocus(
|
|
57486
|
-
e,
|
|
57487
|
-
"InputGroup ArrowRight at end of input[%d] → focus input[%d]",
|
|
57488
|
-
idx,
|
|
57489
|
-
idx + 1,
|
|
57490
|
-
);
|
|
57491
57319
|
e.preventDefault();
|
|
57492
57320
|
focusInput(inputs[idx + 1]);
|
|
57493
57321
|
return;
|
|
@@ -57506,12 +57334,6 @@ const useInputGroup = (ref) => {
|
|
|
57506
57334
|
if (idx === 0) {
|
|
57507
57335
|
return;
|
|
57508
57336
|
}
|
|
57509
|
-
debugFocus(
|
|
57510
|
-
e,
|
|
57511
|
-
"InputGroup ArrowLeft at start of input[%d] → focus input[%d]",
|
|
57512
|
-
idx,
|
|
57513
|
-
idx - 1,
|
|
57514
|
-
);
|
|
57515
57337
|
e.preventDefault();
|
|
57516
57338
|
focusInput(inputs[idx - 1]);
|
|
57517
57339
|
};
|
|
@@ -57534,12 +57356,6 @@ const useInputGroup = (ref) => {
|
|
|
57534
57356
|
return;
|
|
57535
57357
|
}
|
|
57536
57358
|
const nextInput = inputs[idx + 1];
|
|
57537
|
-
debugFocus(
|
|
57538
|
-
e,
|
|
57539
|
-
"InputGroup navi_input_full on input -> move to next input",
|
|
57540
|
-
input,
|
|
57541
|
-
nextInput,
|
|
57542
|
-
);
|
|
57543
57359
|
e.preventDefault();
|
|
57544
57360
|
focusInput(nextInput);
|
|
57545
57361
|
};
|
|
@@ -59549,14 +59365,12 @@ const Dialog = props => {
|
|
|
59549
59365
|
// by --navi-toggle/--navi-open/--navi-close commands, the `open` prop, or
|
|
59550
59366
|
// `defaultOpen`) rather than owned by a parent component.
|
|
59551
59367
|
const UncontrolledDialog = props => {
|
|
59552
|
-
const debugPopup = useDebugPopup();
|
|
59553
59368
|
// Resolved here rather than left to useDialogProps: the open handler below
|
|
59554
59369
|
// needs the dialog element to look at what it contains.
|
|
59555
59370
|
const defaultRef = useRef();
|
|
59556
59371
|
props.ref = props.ref || defaultRef;
|
|
59557
59372
|
const openController = useOpenController(openEvent => {
|
|
59558
59373
|
const dialogEl = props.ref.current;
|
|
59559
|
-
debugPopup(openEvent, `dialog opened`);
|
|
59560
59374
|
return {
|
|
59561
59375
|
onRequestClose: requestCloseEvent => {
|
|
59562
59376
|
// Whatever is inside must not be interrupted mid-action: a form that is
|
|
@@ -60197,38 +60011,6 @@ const useDialogProps = props => {
|
|
|
60197
60011
|
position = pickPositionRelativeTo(dialogEl, null, pickOptions);
|
|
60198
60012
|
applyDialogPosition(position);
|
|
60199
60013
|
}
|
|
60200
|
-
// A descendant's own visibleRectEffect (visible_rect.js — e.g. a
|
|
60201
|
-
// Callout anchored to something inside this Dialog) knowing to
|
|
60202
|
-
// recheck its own position whenever this dialog itself moves is
|
|
60203
|
-
// handled generically by applyNewPosition itself (dispatches
|
|
60204
|
-
// navi_position_change on every call) — nothing to do here.
|
|
60205
|
-
logPlacement(triggerEvent, position);
|
|
60206
|
-
};
|
|
60207
|
-
// The whole picture in one line: where the dialog ended up, and what it
|
|
60208
|
-
// believed about the room when it decided. Both are needed together — a box
|
|
60209
|
-
// that looks wrong on screen while these numbers are right means something
|
|
60210
|
-
// covers the screen that no API describes (a phone's autofill/suggestion
|
|
60211
|
-
// strip), and the absence of a line after the keyboard arrives means the
|
|
60212
|
-
// placement was never asked to run again.
|
|
60213
|
-
const logPlacement = (triggerEvent, position) => {
|
|
60214
|
-
// The decided target, never the current rect: the placement animates, so
|
|
60215
|
-
// a box read right after applying one is somewhere between the two and
|
|
60216
|
-
// reads like a decision nobody took.
|
|
60217
|
-
const {
|
|
60218
|
-
visualViewport
|
|
60219
|
-
} = window;
|
|
60220
|
-
// Reads nothing that brings the layout up to date — no getComputedStyle,
|
|
60221
|
-
// no window.innerWidth/innerHeight. That is the very thing this line is
|
|
60222
|
-
// here to observe (see getVisibleViewportRect in @jsenv/dom): a log that
|
|
60223
|
-
// forced it would hand the NEXT placement a fresh viewport and hide the
|
|
60224
|
-
// bug it was printed to show. Everything below is either already computed
|
|
60225
|
-
// (the pick's own numbers, "room" being the room it had) or free to read.
|
|
60226
|
-
const message = `placed on "${triggerEvent ? triggerEvent.type : "open"}": ${Math.round(position.width)}x${Math.round(position.height)} at ${Math.round(position.top)}->${Math.round(position.top + position.height)} | room ${Math.round(position.containerHeightAvailable)} | viewport ${Math.round(visualViewport.width)}x${Math.round(visualViewport.height)} +${Math.round(visualViewport.offsetLeft)},${Math.round(visualViewport.offsetTop)} | keyboard ${getVirtualKeyboardOverlayHeight()}`;
|
|
60227
|
-
if (triggerEvent) {
|
|
60228
|
-
debugPopup(triggerEvent, message);
|
|
60229
|
-
} else {
|
|
60230
|
-
debugPopup(message);
|
|
60231
|
-
}
|
|
60232
60014
|
};
|
|
60233
60015
|
// Cleared here rather than on close, where the box is deliberately left
|
|
60234
60016
|
// frozen at the size it was closing at (see the closing function's own
|
|
@@ -61197,7 +60979,6 @@ const Popover = props => {
|
|
|
61197
60979
|
// by --navi-toggle/--navi-open/--navi-close commands, or by the `open` prop)
|
|
61198
60980
|
// rather than owned by a parent component.
|
|
61199
60981
|
const UncontrolledPopover = props => {
|
|
61200
|
-
const debugPopup = useDebugPopup();
|
|
61201
60982
|
// Resolved here rather than in usePopoverProps: the open handler below needs
|
|
61202
60983
|
// the popover element to read what it holds.
|
|
61203
60984
|
const defaultRef = useRef();
|
|
@@ -61207,7 +60988,6 @@ const UncontrolledPopover = props => {
|
|
|
61207
60988
|
// about closing is that what it contains must not be interrupted mid-action.
|
|
61208
60989
|
const openController = useOpenController(openEvent => {
|
|
61209
60990
|
const popoverEl = props.ref.current;
|
|
61210
|
-
debugPopup(openEvent, `popover opened`);
|
|
61211
60991
|
return {
|
|
61212
60992
|
onRequestClose: requestCloseEvent => {
|
|
61213
60993
|
const controlsHolding = findControlsHoldingPopup(popoverEl);
|
|
@@ -61996,7 +61776,6 @@ const usePopoverProps = props => {
|
|
|
61996
61776
|
// The close callback openEffect returns — also inlined for the same
|
|
61997
61777
|
// reason: only ever built here.
|
|
61998
61778
|
return closeEvent => {
|
|
61999
|
-
debugPopup(closeEvent, `closePopover()`);
|
|
62000
61779
|
clearTextSelectionInside(popoverEl);
|
|
62001
61780
|
popoverEl.setAttribute("aria-expanded", "false");
|
|
62002
61781
|
// Set regardless of isTopLayer — see the open side's own identical
|
|
@@ -62506,7 +62285,11 @@ const Popup = props => {
|
|
|
62506
62285
|
Popup.Close = PopupClose;
|
|
62507
62286
|
|
|
62508
62287
|
installImportMetaCssBuild(import.meta);
|
|
62509
|
-
const css$B = /* css */`.
|
|
62288
|
+
const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
|
|
62289
|
+
display: none;
|
|
62290
|
+
}
|
|
62291
|
+
|
|
62292
|
+
.navi_popover[data-picker-popup] {
|
|
62510
62293
|
--popover-border-radius: var(--picker-border-radius);
|
|
62511
62294
|
--popover-border-width: var(--picker-border-width);
|
|
62512
62295
|
--popover-border-color: var(--x-picker-border-color);
|
|
@@ -62789,7 +62572,6 @@ const PickerCustom = props => {
|
|
|
62789
62572
|
}
|
|
62790
62573
|
// aria-expanded + open close + interactions to open close
|
|
62791
62574
|
{
|
|
62792
|
-
const debugFocus = useDebugFocus();
|
|
62793
62575
|
const debugPopup = useDebugPopup();
|
|
62794
62576
|
// In "dialog" mode with a stable, caller-provided id, enterExpanded()
|
|
62795
62577
|
// pushes a history entry so the back button closes it. Every other case
|
|
@@ -62824,7 +62606,6 @@ const PickerCustom = props => {
|
|
|
62824
62606
|
// ("yes, 2h15") — the same rule Form applies to an untouched field (see
|
|
62825
62607
|
// isUIStateHeld). Read at open, before anything inside can change it.
|
|
62826
62608
|
const heldAtOpen = isUIStateHeld(getPickerInput(ref.current)?.__uiStateController__);
|
|
62827
|
-
debugPopup(openEvent, `picker opened, store value at open`, valueAtOpen, heldAtOpen ? `(held)` : `(a suggestion, not an answer yet)`);
|
|
62828
62609
|
onOpen?.(openEvent);
|
|
62829
62610
|
return {
|
|
62830
62611
|
onRequestClose: requestCloseEvent => {
|
|
@@ -62892,18 +62673,7 @@ const PickerCustom = props => {
|
|
|
62892
62673
|
const inputEl = getPickerInput(ref.current);
|
|
62893
62674
|
const valueAtClose = getUIStateFromElement(inputEl);
|
|
62894
62675
|
const controller = inputEl?.__uiStateController__;
|
|
62895
|
-
if (controller?.controlHostProps.readOnly) {
|
|
62896
|
-
// Opened only to be read: what it shows stays the suggestion it
|
|
62897
|
-
// was. A look is not an answer, and the signal behind it is not
|
|
62898
|
-
// written by one.
|
|
62899
|
-
debugPopup(closeEvent, `picker is read-only -> nothing to commit`);
|
|
62900
|
-
} else if (valueAtOpen === undefined && compareTwoJsValues(valueAtClose, valueAtOpen)) {
|
|
62901
|
-
// Same third case onRequestClose steps around: nothing held,
|
|
62902
|
-
// nothing shown, nothing picked. There is no suggestion here to
|
|
62903
|
-
// turn into an answer.
|
|
62904
|
-
debugPopup(closeEvent, `picker showed nothing -> nothing to commit`);
|
|
62905
|
-
} else {
|
|
62906
|
-
debugPopup(closeEvent, `picker defined a suggestion -> commit it`);
|
|
62676
|
+
if (controller?.controlHostProps.readOnly) ; else if (valueAtOpen === undefined && compareTwoJsValues(valueAtClose, valueAtOpen)) ; else {
|
|
62907
62677
|
commitUIStateAsAnswer(controller, closeEvent);
|
|
62908
62678
|
}
|
|
62909
62679
|
}
|
|
@@ -63213,7 +62983,6 @@ const PickerCustom = props => {
|
|
|
63213
62983
|
name: "mousedown to open picker",
|
|
63214
62984
|
intent: "read",
|
|
63215
62985
|
allowed: () => {
|
|
63216
|
-
debugFocus(e, `prevent browser giving focus to button (mousedown.preventDefault())`);
|
|
63217
62986
|
requestOpen(e);
|
|
63218
62987
|
e.preventDefault(); // prevent browser trying to give focus to the select (popover will take focus)
|
|
63219
62988
|
}
|
|
@@ -63458,10 +63227,14 @@ const PICKER_CALLOUT_CONTENT_TOKEN = createOpenToken();
|
|
|
63458
63227
|
* closes the controller for real — the popup is already gone, there is no
|
|
63459
63228
|
* choice left to offer `requestClose`.
|
|
63460
63229
|
*
|
|
63461
|
-
* The content is rendered
|
|
63462
|
-
*
|
|
63463
|
-
*
|
|
63464
|
-
*
|
|
63230
|
+
* The content is rendered into an element this component owns, handed to the
|
|
63231
|
+
* callout as its message (a Node, appended as-is) and taken back when the
|
|
63232
|
+
* callout closes. Between two opens it is docked, hidden, in the span below:
|
|
63233
|
+
* in the document the whole time, the way a popup's `mount="always"` keeps its
|
|
63234
|
+
* content — so what it holds survives a close, and what it measures of itself
|
|
63235
|
+
* at mount (a computed color, a light-dark() pair) resolves against a real
|
|
63236
|
+
* ancestry. An element with no document has no computed style, and a badge
|
|
63237
|
+
* reading its own background there would see nothing at all. The element
|
|
63465
63238
|
* carries data-picker-content: the callout is appended inside the picker root,
|
|
63466
63239
|
* and a press in there must read as inside the popup, not on the trigger.
|
|
63467
63240
|
*/
|
|
@@ -63481,23 +63254,22 @@ const PickerCalloutPopup = ({
|
|
|
63481
63254
|
children
|
|
63482
63255
|
}) => {
|
|
63483
63256
|
const hostRef = useRef(null);
|
|
63484
|
-
if (!hostRef.current) {
|
|
63485
|
-
const host = document.createElement("div");
|
|
63486
|
-
host.setAttribute("data-picker-content", "");
|
|
63487
|
-
hostRef.current = host;
|
|
63488
|
-
}
|
|
63489
63257
|
// Reassigned on every render, like Popover's own, so it closes over the
|
|
63490
63258
|
// latest props.
|
|
63491
63259
|
openController.getElement = () => pickerRef.current;
|
|
63492
63260
|
openController.openEffect = openEvent => {
|
|
63493
63261
|
const pickerEl = pickerRef.current;
|
|
63262
|
+
const host = hostRef.current;
|
|
63263
|
+
// Where the content sits while the callout is closed (the span below);
|
|
63264
|
+
// the callout takes the element out of it and the cleanup puts it back.
|
|
63265
|
+
const dock = host.parentNode;
|
|
63494
63266
|
const calloutManager = getPickerInput(pickerEl).__uiStateController__.rules.callout;
|
|
63495
63267
|
// Only an anchor the caller named: left unsaid, the manager anchors on the
|
|
63496
63268
|
// picker's own input — which is where the data-callout-* attributes a
|
|
63497
63269
|
// caller puts on the picker land, and where the callout reads them.
|
|
63498
63270
|
const anchorElement = anchor === pickerRef ? undefined : anchor && "current" in anchor ? anchor.current : anchor;
|
|
63499
63271
|
calloutManager.addOpenToken(PICKER_CALLOUT_CONTENT_TOKEN, {
|
|
63500
|
-
message:
|
|
63272
|
+
message: host,
|
|
63501
63273
|
// The popup a `popupTestId` names is this callout: it is the surface the
|
|
63502
63274
|
// picker opens, drawn by navi, so it is the one thing the caller cannot
|
|
63503
63275
|
// name from its own children.
|
|
@@ -63531,6 +63303,7 @@ const PickerCalloutPopup = ({
|
|
|
63531
63303
|
return closeEvent => {
|
|
63532
63304
|
calloutElement.removeEventListener("navi_request_confirm", forwardConfirm);
|
|
63533
63305
|
calloutManager.removeOpenToken(PICKER_CALLOUT_CONTENT_TOKEN, closeEvent);
|
|
63306
|
+
dock.appendChild(host);
|
|
63534
63307
|
};
|
|
63535
63308
|
};
|
|
63536
63309
|
return (
|
|
@@ -63540,13 +63313,18 @@ const PickerCalloutPopup = ({
|
|
|
63540
63313
|
as: "span",
|
|
63541
63314
|
ref: ref,
|
|
63542
63315
|
id: id,
|
|
63316
|
+
className: "navi_picker_callout_dock",
|
|
63543
63317
|
style: {
|
|
63544
63318
|
display: "contents"
|
|
63545
63319
|
},
|
|
63546
63320
|
onnavi_request_open: onnavi_request_open,
|
|
63547
63321
|
onnavi_request_close: onnavi_request_close,
|
|
63548
63322
|
onnavi_request_confirm: onnavi_request_confirm,
|
|
63549
|
-
children:
|
|
63323
|
+
children: jsx("div", {
|
|
63324
|
+
ref: hostRef,
|
|
63325
|
+
"data-picker-content": "",
|
|
63326
|
+
children: children
|
|
63327
|
+
})
|
|
63550
63328
|
})
|
|
63551
63329
|
);
|
|
63552
63330
|
};
|
|
@@ -63855,6 +63633,43 @@ document.addEventListener(
|
|
|
63855
63633
|
{ capture: true, passive: true },
|
|
63856
63634
|
);
|
|
63857
63635
|
|
|
63636
|
+
/**
|
|
63637
|
+
* Calls back once the browser has painted what is committed now.
|
|
63638
|
+
*
|
|
63639
|
+
* The callbacks of the next frame run before that frame paints, so the call is
|
|
63640
|
+
* made from a task queued from inside one — the same way preact schedules
|
|
63641
|
+
* useEffect. Unlike an effect, nothing runs this early: preact flushes a
|
|
63642
|
+
* component's pending effects as soon as that component renders again, and a
|
|
63643
|
+
* re-render before the paint is exactly what a caller waiting for the paint
|
|
63644
|
+
* has to survive. A frame not coming at all (a background tab) still answers,
|
|
63645
|
+
* late, through the timeout.
|
|
63646
|
+
*
|
|
63647
|
+
* @returns {() => void} cancel
|
|
63648
|
+
*/
|
|
63649
|
+
const FRAME_TIMEOUT_MS = 100;
|
|
63650
|
+
|
|
63651
|
+
const afterPaint = (callback) => {
|
|
63652
|
+
let called = false;
|
|
63653
|
+
let timeoutId;
|
|
63654
|
+
let frameId;
|
|
63655
|
+
const onFrame = () => {
|
|
63656
|
+
if (called) {
|
|
63657
|
+
return;
|
|
63658
|
+
}
|
|
63659
|
+
called = true;
|
|
63660
|
+
clearTimeout(timeoutId);
|
|
63661
|
+
cancelAnimationFrame(frameId);
|
|
63662
|
+
timeoutId = setTimeout(callback, 0);
|
|
63663
|
+
};
|
|
63664
|
+
timeoutId = setTimeout(onFrame, FRAME_TIMEOUT_MS);
|
|
63665
|
+
frameId = requestAnimationFrame(onFrame);
|
|
63666
|
+
return () => {
|
|
63667
|
+
called = true;
|
|
63668
|
+
clearTimeout(timeoutId);
|
|
63669
|
+
cancelAnimationFrame(frameId);
|
|
63670
|
+
};
|
|
63671
|
+
};
|
|
63672
|
+
|
|
63858
63673
|
const LoadingDotsSvg = () => {
|
|
63859
63674
|
return jsxs("svg", {
|
|
63860
63675
|
viewBox: "0 0 200 200",
|
|
@@ -65814,6 +65629,32 @@ const css$x = /* css */`@layer navi {
|
|
|
65814
65629
|
middle of the padding. On the scroll box the scrollbar stays against the
|
|
65815
65630
|
border and the padding is what separates the rows from it. */
|
|
65816
65631
|
const LIST_PADDING_PROP_SET = new Set(["padding", "paddingX", "paddingY", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft"]);
|
|
65632
|
+
|
|
65633
|
+
// Accepts a string too (renderBudget="50" from an HTML attribute): the
|
|
65634
|
+
// arithmetic on the budget (renderBudget / 2, start + renderBudget) would
|
|
65635
|
+
// silently misbehave on a raw string ("+" concatenates).
|
|
65636
|
+
const toRenderBudgetNumber = value => {
|
|
65637
|
+
if (typeof value === "string") {
|
|
65638
|
+
const parsed = Number(value);
|
|
65639
|
+
return Number.isFinite(parsed) ? parsed : RENDER_BUDGET_DEFAULT;
|
|
65640
|
+
}
|
|
65641
|
+
if (typeof value === "number") {
|
|
65642
|
+
return value;
|
|
65643
|
+
}
|
|
65644
|
+
return RENDER_BUDGET_DEFAULT;
|
|
65645
|
+
};
|
|
65646
|
+
const resolveRenderBudget = renderBudget => {
|
|
65647
|
+
if (renderBudget && typeof renderBudget === "object") {
|
|
65648
|
+
return {
|
|
65649
|
+
initial: renderBudget.initial === undefined ? undefined : toRenderBudgetNumber(renderBudget.initial),
|
|
65650
|
+
after: toRenderBudgetNumber(renderBudget.after)
|
|
65651
|
+
};
|
|
65652
|
+
}
|
|
65653
|
+
return {
|
|
65654
|
+
initial: undefined,
|
|
65655
|
+
after: toRenderBudgetNumber(renderBudget)
|
|
65656
|
+
};
|
|
65657
|
+
};
|
|
65817
65658
|
const ListUI = props => {
|
|
65818
65659
|
import.meta.css = [css$x, "@jsenv/navi/src/control/list/list.jsx"];
|
|
65819
65660
|
const {
|
|
@@ -65862,18 +65703,34 @@ const ListUI = props => {
|
|
|
65862
65703
|
delete rest[name];
|
|
65863
65704
|
}
|
|
65864
65705
|
}
|
|
65865
|
-
//
|
|
65866
|
-
//
|
|
65867
|
-
//
|
|
65868
|
-
//
|
|
65869
|
-
|
|
65870
|
-
|
|
65871
|
-
|
|
65872
|
-
|
|
65873
|
-
|
|
65706
|
+
// `renderBudget` is a number, or `{ initial, after }`: the window of the
|
|
65707
|
+
// first commit, until the browser has painted it, and the window from then
|
|
65708
|
+
// on. A list opening inside a popup draws in the click that opens it, and
|
|
65709
|
+
// the browser paints nothing before that render ends: rows below the fold
|
|
65710
|
+
// cost the same as rows on screen there, and are drawn to be seen one frame
|
|
65711
|
+
// later just as well. `after` takes over after the paint (see afterPaint for
|
|
65712
|
+
// why not an effect), and the floor of 30 is its: it protects the scrolling
|
|
65713
|
+
// window, not a picture nobody has seen yet.
|
|
65714
|
+
const {
|
|
65715
|
+
initial: initialRenderBudget,
|
|
65716
|
+
after: renderBudgetAfterPaint
|
|
65717
|
+
} = resolveRenderBudget(renderBudgetProp);
|
|
65718
|
+
let renderBudget = renderBudgetAfterPaint;
|
|
65874
65719
|
if (renderBudget < 30 && !renderBudgetSkipCheck) {
|
|
65875
65720
|
console.warn(`List: renderBudget=${renderBudget} is too low. A renderBudget below 30 is not supported: on large screens or when the list grows, items outside the window would appear as blank space instead of rendered content. Use a value of at least 30, or omit the prop to use the default (${RENDER_BUDGET_DEFAULT}).`);
|
|
65876
65721
|
}
|
|
65722
|
+
const [firstPaintPending, setFirstPaintPending] = useState(initialRenderBudget !== undefined);
|
|
65723
|
+
useLayoutEffect(() => {
|
|
65724
|
+
if (!firstPaintPending) {
|
|
65725
|
+
return undefined;
|
|
65726
|
+
}
|
|
65727
|
+
return afterPaint(() => {
|
|
65728
|
+
setFirstPaintPending(false);
|
|
65729
|
+
});
|
|
65730
|
+
}, []);
|
|
65731
|
+
if (firstPaintPending) {
|
|
65732
|
+
renderBudget = initialRenderBudget;
|
|
65733
|
+
}
|
|
65877
65734
|
|
|
65878
65735
|
// lockSize: capture the container's dimensions on first render so filtering
|
|
65879
65736
|
// cannot collapse the layout. Measurement happens on the initial (unfiltered)
|
|
@@ -65910,7 +65767,10 @@ const ListUI = props => {
|
|
|
65910
65767
|
onListVisibleItemsChange?.(tracker.visibleItemsSignal.peek());
|
|
65911
65768
|
}
|
|
65912
65769
|
});
|
|
65913
|
-
|
|
65770
|
+
// What the runs ask for and stand for: the steady budget, whatever the
|
|
65771
|
+
// window of the first paint draws — a run asking for the rows of the first
|
|
65772
|
+
// picture and then for the rest is two round trips for one opening.
|
|
65773
|
+
virtual.renderBudget = renderBudgetAfterPaint;
|
|
65914
65774
|
virtual.scrolled = scrolled ?? defaultScrolled;
|
|
65915
65775
|
const {
|
|
65916
65776
|
virtualItemSizeSignal,
|
|
@@ -66259,7 +66119,10 @@ const useListScrollSync = ({
|
|
|
66259
66119
|
horizontal
|
|
66260
66120
|
}) => {
|
|
66261
66121
|
const debugScroll = useDebugScroll();
|
|
66262
|
-
const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal
|
|
66122
|
+
const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal, {
|
|
66123
|
+
virtual,
|
|
66124
|
+
renderBudget
|
|
66125
|
+
});
|
|
66263
66126
|
const getScroller = () => getScrollerEl(ref.current, scroller, horizontal);
|
|
66264
66127
|
const getListEl = () => ref.current.querySelector(".navi_list");
|
|
66265
66128
|
// Which box scrolls is measured (see getScrollerEl), so the answer holds
|
|
@@ -66315,6 +66178,28 @@ const useListScrollSync = ({
|
|
|
66315
66178
|
});
|
|
66316
66179
|
const renderWindowRef = useRef(null);
|
|
66317
66180
|
renderWindowRef.current = renderWindow;
|
|
66181
|
+
// A budget that changes (the first paint's giving way to the scrolling one)
|
|
66182
|
+
// re-frames the window where it stands, in this very render — the way
|
|
66183
|
+
// holdWindow below moves it: nothing else would, the scroll listener only
|
|
66184
|
+
// moves a window the user is about to leave.
|
|
66185
|
+
const renderBudgetRef = useRef(renderBudget);
|
|
66186
|
+
if (renderBudgetRef.current !== renderBudget) {
|
|
66187
|
+
renderBudgetRef.current = renderBudget;
|
|
66188
|
+
const {
|
|
66189
|
+
start
|
|
66190
|
+
} = renderWindowRef.current;
|
|
66191
|
+
const total = virtual.totalSignal.peek();
|
|
66192
|
+
let newStart = start;
|
|
66193
|
+
let newEnd = start + renderBudget;
|
|
66194
|
+
if (total > 0 && newEnd > total) {
|
|
66195
|
+
newEnd = total;
|
|
66196
|
+
newStart = total - renderBudget < 0 ? 0 : total - renderBudget;
|
|
66197
|
+
}
|
|
66198
|
+
renderWindowRef.current = {
|
|
66199
|
+
start: newStart,
|
|
66200
|
+
end: newEnd
|
|
66201
|
+
};
|
|
66202
|
+
}
|
|
66318
66203
|
const updateRenderWindow = (newStart, newEnd, reason) => {
|
|
66319
66204
|
const {
|
|
66320
66205
|
start,
|
|
@@ -66324,7 +66209,6 @@ const useListScrollSync = ({
|
|
|
66324
66209
|
return;
|
|
66325
66210
|
}
|
|
66326
66211
|
captureAnchor();
|
|
66327
|
-
debugScroll(`updateRenderWindow(${newStart}, ${newEnd}, "${reason}")`);
|
|
66328
66212
|
const renderWindow = {
|
|
66329
66213
|
start: newStart,
|
|
66330
66214
|
end: newEnd
|
|
@@ -66417,12 +66301,11 @@ const useListScrollSync = ({
|
|
|
66417
66301
|
return;
|
|
66418
66302
|
}
|
|
66419
66303
|
const scrollItemIntoView = itemEl => {
|
|
66420
|
-
|
|
66304
|
+
`"${event.type}" on ${getElementSignature(event.target)} (${reason})`;
|
|
66421
66305
|
// When we display the list we prefer to have selected item at the center
|
|
66422
66306
|
// otherwise, usually when focused by arrow nav, we want to keep it into view close to the nearest edge
|
|
66423
66307
|
const block = blockRequested || (event.type === "navi_displayed" ? "center" : "nearest");
|
|
66424
|
-
|
|
66425
|
-
debugScroll(`${trigger} -> ${scrollToItemCall}`);
|
|
66308
|
+
`${getElementSignature(itemEl)}.scrollIntoView({ block: "${block}", container: "nearest" })`;
|
|
66426
66309
|
// The list is going somewhere on purpose, so there is no view to hold
|
|
66427
66310
|
// still any more: an anchor captured before this drop it, or it would
|
|
66428
66311
|
// put the list back where it was the moment the rows move under it.
|
|
@@ -66458,7 +66341,7 @@ const useListScrollSync = ({
|
|
|
66458
66341
|
const half = Math.floor(renderBudget / 2);
|
|
66459
66342
|
const newStart = Math.max(0, index - half);
|
|
66460
66343
|
const newEnd = newStart + renderBudget;
|
|
66461
|
-
updateRenderWindow(newStart, newEnd
|
|
66344
|
+
updateRenderWindow(newStart, newEnd);
|
|
66462
66345
|
};
|
|
66463
66346
|
|
|
66464
66347
|
// Where the list must be: what the caller holds it at (`scrolled`), or where
|
|
@@ -66590,8 +66473,7 @@ const useListScrollSync = ({
|
|
|
66590
66473
|
return undefined;
|
|
66591
66474
|
}
|
|
66592
66475
|
savedScrollRef.current = null;
|
|
66593
|
-
|
|
66594
|
-
updateRenderWindow(savedScroll.renderWindow.start, savedScroll.renderWindow.end, "restore scroll window");
|
|
66476
|
+
updateRenderWindow(savedScroll.renderWindow.start, savedScroll.renderWindow.end);
|
|
66595
66477
|
// Tracked in a ref rather than cancelled via this effect's own cleanup:
|
|
66596
66478
|
// updateRenderWindow above triggers a re-render, which re-runs this
|
|
66597
66479
|
// effect (it has no dependency array — it needs to reactively poll
|
|
@@ -66933,7 +66815,7 @@ const useListScrollSync = ({
|
|
|
66933
66815
|
}
|
|
66934
66816
|
const endWanted = startWanted + windowSize;
|
|
66935
66817
|
if (startWanted !== start || endWanted !== end) {
|
|
66936
|
-
updateRenderWindow(startWanted, endWanted
|
|
66818
|
+
updateRenderWindow(startWanted, endWanted);
|
|
66937
66819
|
return;
|
|
66938
66820
|
}
|
|
66939
66821
|
}
|
|
@@ -67028,7 +66910,7 @@ const useListScrollSync = ({
|
|
|
67028
66910
|
if (newEnd === total) {
|
|
67029
66911
|
newStart = Math.max(0, total - renderBudget);
|
|
67030
66912
|
}
|
|
67031
|
-
updateRenderWindow(newStart, newEnd
|
|
66913
|
+
updateRenderWindow(newStart, newEnd);
|
|
67032
66914
|
};
|
|
67033
66915
|
// A page-level scroller does not emit "scroll" on the element itself
|
|
67034
66916
|
// (document.scrollingElement); the document does.
|
|
@@ -67579,7 +67461,10 @@ const measureItemSize = (listEl, horizontal) => {
|
|
|
67579
67461
|
fromSkeletons
|
|
67580
67462
|
};
|
|
67581
67463
|
};
|
|
67582
|
-
const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal
|
|
67464
|
+
const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal, {
|
|
67465
|
+
virtual,
|
|
67466
|
+
renderBudget
|
|
67467
|
+
}) => {
|
|
67583
67468
|
const virtualSizeSignalRef = useRef(null);
|
|
67584
67469
|
if (!virtualSizeSignalRef.current) {
|
|
67585
67470
|
virtualSizeSignalRef.current = signal(virtualItemSizeProp);
|
|
@@ -67632,8 +67517,12 @@ const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal) => {
|
|
|
67632
67517
|
// as the rows it was measured on. Written from a layout effect it would
|
|
67633
67518
|
// resize them one commit later — after the scroll anchoring of that commit
|
|
67634
67519
|
// had already run, which is exactly the jump anchoring exists to prevent.
|
|
67520
|
+
// And only while some rows are held off screen: the fillers are what the
|
|
67521
|
+
// size is for, and a list drawing every row it has would pay a layout on
|
|
67522
|
+
// each of its renders for a number nothing reads.
|
|
67635
67523
|
const sizeAlreadyKnown = virtualSizeSignal.peek() !== 0;
|
|
67636
|
-
|
|
67524
|
+
const rowsHeldOffScreen = virtual.totalSignal.peek() > renderBudget;
|
|
67525
|
+
if (!virtualItemSizeProp && sizeAlreadyKnown && rowsHeldOffScreen && ref.current) {
|
|
67637
67526
|
const listEl = ref.current.querySelector(".navi_list");
|
|
67638
67527
|
const measure = listEl ? measureItemSize(listEl, horizontal) : null;
|
|
67639
67528
|
if (measure) {
|
|
@@ -67798,12 +67687,15 @@ const Fallback = ({
|
|
|
67798
67687
|
children: fallback
|
|
67799
67688
|
});
|
|
67800
67689
|
};
|
|
67690
|
+
// Reads the row size itself: it is what the size is for, and a run holding
|
|
67691
|
+
// every row it draws must not be redrawn — every row of it — because the size
|
|
67692
|
+
// settled after the first commit.
|
|
67801
67693
|
const VirtualFiller = ({
|
|
67802
67694
|
edge,
|
|
67803
|
-
itemCount
|
|
67804
|
-
virtualItemSize
|
|
67695
|
+
itemCount
|
|
67805
67696
|
}) => {
|
|
67806
|
-
const
|
|
67697
|
+
const virtual = useContext(ListVirtualContext);
|
|
67698
|
+
const sizeToFill = itemCount * virtual.virtualItemSizeSignal.value;
|
|
67807
67699
|
if (!sizeToFill) {
|
|
67808
67700
|
return null;
|
|
67809
67701
|
}
|
|
@@ -68857,6 +68749,21 @@ const ListItems = ({
|
|
|
68857
68749
|
const virtual = useContext(ListVirtualContext);
|
|
68858
68750
|
const slotId = useContext(ListSlotContext);
|
|
68859
68751
|
const renderWindow = useContext(RenderWindowContext);
|
|
68752
|
+
// The vnode drawn for a row, kept by item: a run rendering again (its window
|
|
68753
|
+
// moving, its first paint's budget giving way to the full one) hands preact
|
|
68754
|
+
// the same vnode for a row that has not changed, and preact leaves that
|
|
68755
|
+
// row's whole subtree alone. Only for a `renderItem` that is the same
|
|
68756
|
+
// function as last time — a new one may close over new state — and for a
|
|
68757
|
+
// row at the same index, in the same refreshing state: everything the
|
|
68758
|
+
// function is given.
|
|
68759
|
+
const rowVnodesRef = useRef(null);
|
|
68760
|
+
if (!rowVnodesRef.current || rowVnodesRef.current.renderItem !== renderItem) {
|
|
68761
|
+
rowVnodesRef.current = {
|
|
68762
|
+
renderItem,
|
|
68763
|
+
byItem: new Map()
|
|
68764
|
+
};
|
|
68765
|
+
}
|
|
68766
|
+
const rowVnodesByItem = rowVnodesRef.current.byItem;
|
|
68860
68767
|
const separator = useContext(SeparatorContext);
|
|
68861
68768
|
const store = useItemStore({
|
|
68862
68769
|
items,
|
|
@@ -68867,16 +68774,26 @@ const ListItems = ({
|
|
|
68867
68774
|
});
|
|
68868
68775
|
const renderRowSkeleton = renderSkeleton === undefined ? virtual.renderSkeleton : renderSkeleton;
|
|
68869
68776
|
// A row on its way takes the room the list reserves for it: anything else
|
|
68870
|
-
// and the rows drawn stop short of where the scroll says they are.
|
|
68871
|
-
|
|
68872
|
-
|
|
68873
|
-
|
|
68874
|
-
|
|
68875
|
-
|
|
68876
|
-
|
|
68877
|
-
skeletonRow
|
|
68777
|
+
// and the rows drawn stop short of where the scroll says they are. Read
|
|
68778
|
+
// where a row is actually missing, and not before: the size settles after
|
|
68779
|
+
// the first commit, and a run holding every row it draws would otherwise be
|
|
68780
|
+
// redrawn whole by a number it has no use for.
|
|
68781
|
+
let skeletonRow = null;
|
|
68782
|
+
const getSkeletonRow = () => {
|
|
68783
|
+
if (skeletonRow) {
|
|
68784
|
+
return skeletonRow;
|
|
68785
|
+
}
|
|
68786
|
+
skeletonRow = {};
|
|
68787
|
+
const virtualItemSize = virtual.virtualItemSizeSignal.value;
|
|
68788
|
+
if (virtualItemSize) {
|
|
68789
|
+
if (virtual.horizontal) {
|
|
68790
|
+
skeletonRow.rowMinWidth = `${virtualItemSize}px`;
|
|
68791
|
+
} else {
|
|
68792
|
+
skeletonRow.rowMinHeight = `${virtualItemSize}px`;
|
|
68793
|
+
}
|
|
68878
68794
|
}
|
|
68879
|
-
|
|
68795
|
+
return skeletonRow;
|
|
68796
|
+
};
|
|
68880
68797
|
const runStart = virtual.take(ownerId, store.rowCount, slotId);
|
|
68881
68798
|
const runEnd = runStart + store.rowCount;
|
|
68882
68799
|
// The two ways to count the same row. The list numbers its rows from its own
|
|
@@ -69045,8 +68962,7 @@ const ListItems = ({
|
|
|
69045
68962
|
if (windowFrom > runStart) {
|
|
69046
68963
|
rows.push(jsx(VirtualFiller, {
|
|
69047
68964
|
edge: "before",
|
|
69048
|
-
itemCount: windowFrom - runStart
|
|
69049
|
-
virtualItemSize: virtualItemSize
|
|
68965
|
+
itemCount: windowFrom - runStart
|
|
69050
68966
|
}, "navi-list-filler-before"));
|
|
69051
68967
|
}
|
|
69052
68968
|
const renderItemState = {
|
|
@@ -69060,7 +68976,7 @@ const ListItems = ({
|
|
|
69060
68976
|
rows.push(jsx("li", {
|
|
69061
68977
|
className: "navi_list_failed_rows",
|
|
69062
68978
|
style: {
|
|
69063
|
-
"--size-to-fill": `${failedRowCount *
|
|
68979
|
+
"--size-to-fill": `${failedRowCount * virtual.virtualItemSizeSignal.value}px`
|
|
69064
68980
|
},
|
|
69065
68981
|
children: renderError ? renderError({
|
|
69066
68982
|
error: store.failure.error,
|
|
@@ -69079,7 +68995,17 @@ const ListItems = ({
|
|
|
69079
68995
|
const key = item === undefined ? `${ownerId}_skeleton_${rowIndex}` : idOf(item, rowIndex);
|
|
69080
68996
|
let rowVnode;
|
|
69081
68997
|
if (item !== undefined) {
|
|
69082
|
-
|
|
68998
|
+
const rowVnodeKept = rowVnodesByItem.get(item);
|
|
68999
|
+
if (rowVnodeKept && rowVnodeKept.rowIndex === rowIndex && rowVnodeKept.refreshing === renderItemState.refreshing) {
|
|
69000
|
+
rowVnode = rowVnodeKept.vnode;
|
|
69001
|
+
} else {
|
|
69002
|
+
rowVnode = renderItem(item, rowIndex, renderItemState);
|
|
69003
|
+
rowVnodesByItem.set(item, {
|
|
69004
|
+
vnode: rowVnode,
|
|
69005
|
+
rowIndex,
|
|
69006
|
+
refreshing: renderItemState.refreshing
|
|
69007
|
+
});
|
|
69008
|
+
}
|
|
69083
69009
|
} else if (renderRowSkeleton === false) {
|
|
69084
69010
|
// The row must still take its room: without it the rows below would
|
|
69085
69011
|
// climb up and slide back down as the answer arrives.
|
|
@@ -69110,7 +69036,7 @@ const ListItems = ({
|
|
|
69110
69036
|
value: item === undefined ? {
|
|
69111
69037
|
id: key,
|
|
69112
69038
|
index: rowIndex,
|
|
69113
|
-
...
|
|
69039
|
+
...getSkeletonRow()
|
|
69114
69040
|
} : {
|
|
69115
69041
|
id: key,
|
|
69116
69042
|
index: rowIndex,
|
|
@@ -69125,8 +69051,7 @@ const ListItems = ({
|
|
|
69125
69051
|
if (runEnd > windowTo) {
|
|
69126
69052
|
rows.push(jsx(VirtualFiller, {
|
|
69127
69053
|
edge: "after",
|
|
69128
|
-
itemCount: runEnd - windowTo
|
|
69129
|
-
virtualItemSize: virtualItemSize
|
|
69054
|
+
itemCount: runEnd - windowTo
|
|
69130
69055
|
}, "navi-list-filler-after"));
|
|
69131
69056
|
}
|
|
69132
69057
|
return rows;
|
|
@@ -69883,6 +69808,25 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
69883
69808
|
* starts. Keep it to come back to it
|
|
69884
69809
|
* later through `scrolled`/`defaultScrolled` — an index would not do, since
|
|
69885
69810
|
* rows get inserted while a list is being read.
|
|
69811
|
+
* @param {number|{initial: number, after: number}} [props.renderBudget=100]
|
|
69812
|
+
* How many rows of a `<List.Items>` run are in the DOM at once: the render
|
|
69813
|
+
* window, which slides as the user scrolls while fillers hold the room of
|
|
69814
|
+
* the rows outside it. Rows declared one by one as `<List.Item>` children
|
|
69815
|
+
* are all drawn, whatever this says — a list with more than a few dozen rows
|
|
69816
|
+
* gives them to a run (see docs/scroll.md, "Many rows"). Below 30 the list
|
|
69817
|
+
* warns: a window shorter than a tall screen shows blank fillers.
|
|
69818
|
+
*
|
|
69819
|
+
* `{ initial, after }` for a list drawn in the click that opens it (a popup):
|
|
69820
|
+
* `initial` rows in the commit the browser paints first — about what a phone
|
|
69821
|
+
* screen shows — and `after` from the paint on, where the floor of 30
|
|
69822
|
+
* applies. The runs ask their source for `after` rows from the start, so the
|
|
69823
|
+
* first picture costs no second request.
|
|
69824
|
+
* @param {number} [props.virtualItemSize]
|
|
69825
|
+
* The size of one row along the scroll axis, in px, when every row has the
|
|
69826
|
+
* same: what the fillers are sized with and what a scroll position is
|
|
69827
|
+
* estimated from. Left out, the list measures its rows — once when it
|
|
69828
|
+
* mounts, again when a popup around it opens, and after each commit while
|
|
69829
|
+
* rows are held off screen. Given, it never measures.
|
|
69886
69830
|
* @param {"self"|"parent"|"document"|Element|{current: Element}} [props.scroller="self"]
|
|
69887
69831
|
* Which box scrolls. `"self"` gives the list a scroll box of its own;
|
|
69888
69832
|
* `"parent"` makes it virtualize against the scrollable ancestor it lives in
|
|
@@ -71261,6 +71205,7 @@ const css$t = /* css */`@layer navi {
|
|
|
71261
71205
|
--picker-icon-color-disabled: var(--picker-icon-color-readonly);
|
|
71262
71206
|
--picker-align-x-default: flex-start;
|
|
71263
71207
|
--picker-align-y-default: center;
|
|
71208
|
+
--picker-text-align-default: initial;
|
|
71264
71209
|
}
|
|
71265
71210
|
}
|
|
71266
71211
|
|
|
@@ -71399,7 +71344,7 @@ const css$t = /* css */`@layer navi {
|
|
|
71399
71344
|
}
|
|
71400
71345
|
|
|
71401
71346
|
&[navi-single-line] {
|
|
71402
|
-
& .navi_picker_right_slot {
|
|
71347
|
+
& > .navi_picker_box > .navi_picker_right_slot {
|
|
71403
71348
|
align-self: var(--x-picker-align-y);
|
|
71404
71349
|
}
|
|
71405
71350
|
}
|
|
@@ -71436,7 +71381,7 @@ const css$t = /* css */`@layer navi {
|
|
|
71436
71381
|
}
|
|
71437
71382
|
|
|
71438
71383
|
&[navi-ui-custom] {
|
|
71439
|
-
& .navi_picker_input {
|
|
71384
|
+
& > .navi_picker_box > .navi_picker_input {
|
|
71440
71385
|
top: calc(-1 * (var(--picker-border-width) + var(--x-picker-press-padding-top)));
|
|
71441
71386
|
right: calc(-1 *
|
|
71442
71387
|
(var(--picker-border-width) + var(--x-picker-press-padding-right)));
|
|
@@ -71499,10 +71444,10 @@ const css$t = /* css */`@layer navi {
|
|
|
71499
71444
|
--x-picker-cursor: pointer;
|
|
71500
71445
|
}
|
|
71501
71446
|
|
|
71502
|
-
&[data-focus-within]:has(.navi_picker_input[data-focus-visible]), &[data-focus-visible] {
|
|
71447
|
+
&[data-focus-within]:has( > .navi_picker_box > .navi_picker_input[data-focus-visible]), &[data-focus-visible] {
|
|
71503
71448
|
--x-picker-border-color: transparent;
|
|
71504
71449
|
|
|
71505
|
-
& .navi_picker_box {
|
|
71450
|
+
& > .navi_picker_box {
|
|
71506
71451
|
outline-style: solid;
|
|
71507
71452
|
}
|
|
71508
71453
|
}
|
|
@@ -71560,7 +71505,7 @@ const css$t = /* css */`@layer navi {
|
|
|
71560
71505
|
--picker-background-color-disabled: var(--picker-background-color);
|
|
71561
71506
|
--picker-icon-color: currentColor;
|
|
71562
71507
|
|
|
71563
|
-
& .navi_picker_box {
|
|
71508
|
+
& > .navi_picker_box {
|
|
71564
71509
|
z-index: -1;
|
|
71565
71510
|
position: absolute;
|
|
71566
71511
|
inset: 0;
|
|
@@ -71585,11 +71530,11 @@ const css$t = /* css */`@layer navi {
|
|
|
71585
71530
|
font-family: inherit;
|
|
71586
71531
|
line-height: inherit;
|
|
71587
71532
|
|
|
71588
|
-
& .navi_picker_box {
|
|
71533
|
+
& > .navi_picker_box {
|
|
71589
71534
|
min-height: 0;
|
|
71590
71535
|
}
|
|
71591
71536
|
|
|
71592
|
-
& .navi_picker_value[data-picker-facade] {
|
|
71537
|
+
& > .navi_picker_box > .navi_picker_value[data-picker-facade] {
|
|
71593
71538
|
align-items: var(--x-picker-align-y);
|
|
71594
71539
|
display: flex;
|
|
71595
71540
|
}
|
|
@@ -71640,11 +71585,11 @@ const css$t = /* css */`@layer navi {
|
|
|
71640
71585
|
--picker-padding-y-default: 0;
|
|
71641
71586
|
--picker-align-x-default: center;
|
|
71642
71587
|
|
|
71643
|
-
& .navi_picker_box {
|
|
71588
|
+
& > .navi_picker_box {
|
|
71644
71589
|
min-height: 0;
|
|
71645
71590
|
}
|
|
71646
71591
|
|
|
71647
|
-
& .navi_picker_value {
|
|
71592
|
+
& > .navi_picker_box > .navi_picker_value {
|
|
71648
71593
|
flex-grow: 0;
|
|
71649
71594
|
align-items: center;
|
|
71650
71595
|
display: inline-flex;
|
|
@@ -76103,8 +76048,7 @@ const useWheelInteractions = ({
|
|
|
76103
76048
|
// A real drag begins: stop the glide and re-anchor to here so the wheel
|
|
76104
76049
|
// doesn't jump (startPos = the just-frozen position, startClient = now).
|
|
76105
76050
|
drag.moved = true;
|
|
76106
|
-
|
|
76107
|
-
debug(e, caught ? "catch: grabbed a moving wheel" : "drag: start");
|
|
76051
|
+
cancelAnim();
|
|
76108
76052
|
drag.startPos = posRef.current;
|
|
76109
76053
|
drag.startClient = client;
|
|
76110
76054
|
}
|
|
@@ -76749,11 +76693,6 @@ function WheelUI(props) {
|
|
|
76749
76693
|
// on the press settles the DOM up front, before the tap completes.
|
|
76750
76694
|
const commitIfAnimating = () => {
|
|
76751
76695
|
const animating = momentumRef.current !== null || glideRef.current !== null;
|
|
76752
|
-
// Logs on every press outside the wheel — tells us whether a tap on a dialog
|
|
76753
|
-
// button lands while the wheel is still coasting (animating=true) or after it
|
|
76754
|
-
// already settled (animating=false), which decides where the dropped click
|
|
76755
|
-
// comes from.
|
|
76756
|
-
debugScroll(`external press while wheel animating=${animating}`);
|
|
76757
76696
|
if (!animating) {
|
|
76758
76697
|
return;
|
|
76759
76698
|
}
|
|
@@ -76815,7 +76754,6 @@ function WheelUI(props) {
|
|
|
76815
76754
|
glideFromOutsideRef.current = fromOutside;
|
|
76816
76755
|
}
|
|
76817
76756
|
if (glideRef.current === null) {
|
|
76818
|
-
debugScroll("glide: start");
|
|
76819
76757
|
glideRef.current = requestAnimationFrame(() => glideStep(vp, performance.now()));
|
|
76820
76758
|
}
|
|
76821
76759
|
// else: the loop is already running and will chase the updated target — no
|
|
@@ -76830,7 +76768,6 @@ function WheelUI(props) {
|
|
|
76830
76768
|
const settle = (vp, velocity) => {
|
|
76831
76769
|
cancelAnim();
|
|
76832
76770
|
glideFromOutsideRef.current = false;
|
|
76833
|
-
debugScroll(`settle: momentum start (v=${velocity}px/ms)`);
|
|
76834
76771
|
// A drag fling: allow the full swipe velocity (see WHEEL_FLING_MAX_VELOCITY)
|
|
76835
76772
|
// so a hard swipe carries across the list instead of being clipped to a few
|
|
76836
76773
|
// rows. The mouse wheel never reaches here (it uses wheelSettle/glide).
|