@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/dev/jsenv_navi.js
CHANGED
|
@@ -3678,7 +3678,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3678
3678
|
toString: () => actionCallSourceSignal.peek(),
|
|
3679
3679
|
meta,
|
|
3680
3680
|
debug: (...args) => {
|
|
3681
|
-
if (!meta.debug && !DEBUG$2) {
|
|
3681
|
+
if ((!meta.debug && !DEBUG$2)) {
|
|
3682
3682
|
return;
|
|
3683
3683
|
}
|
|
3684
3684
|
console.debug(...args);
|
|
@@ -12922,68 +12922,153 @@ const useResetErrorBoundary = () => {
|
|
|
12922
12922
|
return resetErrorBoundary;
|
|
12923
12923
|
};
|
|
12924
12924
|
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
|
|
12925
|
+
/*
|
|
12926
|
+
* Everything dev-only in this module hangs off one `import.meta.dev` test at
|
|
12927
|
+
* the binding level (`import.meta.dev ? naviDebug.x : noop`), not inside the
|
|
12928
|
+
* function bodies. Rollup follows a binding to the function it names, but
|
|
12929
|
+
* not an early `return noop` inside a body: the first shape lets it drop every
|
|
12930
|
+
* `debugX("…")` call site of the production build, the second keeps them all.
|
|
12931
|
+
*/
|
|
12932
|
+
|
|
12932
12933
|
const debugNoop = () => {};
|
|
12933
|
-
const
|
|
12934
|
-
const
|
|
12935
|
-
const
|
|
12936
|
-
const
|
|
12937
|
-
const
|
|
12938
|
-
const
|
|
12939
|
-
const
|
|
12940
|
-
const
|
|
12934
|
+
const createNaviDebug = () => {
|
|
12935
|
+
const DebugCommandContext = createContext(false);
|
|
12936
|
+
const DebugInteractionContext = createContext(false);
|
|
12937
|
+
const DebugFocusContext = createContext(false);
|
|
12938
|
+
const DebugScrollContext = createContext(false);
|
|
12939
|
+
const DebugPopupContext = createContext(false);
|
|
12940
|
+
const DebugActionContext = createContext(false);
|
|
12941
|
+
const DebugUIStateContext = createContext(false);
|
|
12942
|
+
const eventGroupLogger = createEventGroupLogger();
|
|
12943
|
+
const debugCommandDefault = eventGroupLogger.createCategory("[command]", "#8e44ad");
|
|
12944
|
+
const debugInteractionDefault = eventGroupLogger.createCategory("[interaction]", "#2980b9");
|
|
12945
|
+
const debugActionDefault = eventGroupLogger.createCategory("[action]", "#e67e22");
|
|
12946
|
+
const debugPopupDefault = eventGroupLogger.createCategory("[popup]", "#27ae60");
|
|
12947
|
+
const debugUIStateDefault = eventGroupLogger.createCategory("[uistate]", "#7f8c8d");
|
|
12948
|
+
const debugFocusDefault = eventGroupLogger.createCategory("[focus]", "#2980b9");
|
|
12949
|
+
const debugScrollDefault = eventGroupLogger.createCategory("[scroll]", "#2980b9");
|
|
12950
|
+
const useDebugCommand = () => {
|
|
12951
|
+
const debug = useContext(DebugCommandContext);
|
|
12952
|
+
return debug || debugNoop;
|
|
12953
|
+
};
|
|
12954
|
+
const useDebugInteraction = () => {
|
|
12955
|
+
const debug = useContext(DebugInteractionContext);
|
|
12956
|
+
return debug || debugNoop;
|
|
12957
|
+
};
|
|
12958
|
+
const useDebugFocus = () => {
|
|
12959
|
+
const debug = useContext(DebugFocusContext);
|
|
12960
|
+
return debug || debugNoop;
|
|
12961
|
+
};
|
|
12962
|
+
const useDebugScroll = () => {
|
|
12963
|
+
const debug = useContext(DebugScrollContext);
|
|
12964
|
+
return debug || debugNoop;
|
|
12965
|
+
};
|
|
12966
|
+
const useDebugPopup = () => {
|
|
12967
|
+
const debug = useContext(DebugPopupContext);
|
|
12968
|
+
return debug || debugNoop;
|
|
12969
|
+
};
|
|
12970
|
+
const useDebugAction = () => {
|
|
12971
|
+
const debug = useContext(DebugActionContext);
|
|
12972
|
+
return debug || debugNoop;
|
|
12973
|
+
};
|
|
12974
|
+
const useDebugUIState = () => {
|
|
12975
|
+
const debug = useContext(DebugUIStateContext);
|
|
12976
|
+
return debug || debugNoop;
|
|
12977
|
+
};
|
|
12978
|
+
const NaviDebug = ({
|
|
12979
|
+
debugAll,
|
|
12980
|
+
debugCommand = debugAll,
|
|
12981
|
+
debugInteraction = debugAll,
|
|
12982
|
+
debugFocus = debugAll,
|
|
12983
|
+
debugScroll = debugAll,
|
|
12984
|
+
debugPopup = debugAll,
|
|
12985
|
+
debugAction = debugAll,
|
|
12986
|
+
debugUIState = debugAll,
|
|
12987
|
+
children
|
|
12988
|
+
}) => {
|
|
12989
|
+
if (debugCommand === true) {
|
|
12990
|
+
debugCommand = debugCommandDefault;
|
|
12991
|
+
}
|
|
12992
|
+
if (debugInteraction === true) {
|
|
12993
|
+
debugInteraction = debugInteractionDefault;
|
|
12994
|
+
}
|
|
12995
|
+
if (debugFocus === true || debugInteraction && debugFocus === undefined) {
|
|
12996
|
+
debugFocus = debugFocusDefault;
|
|
12997
|
+
}
|
|
12998
|
+
if (debugScroll === true || debugInteraction && debugScroll === undefined) {
|
|
12999
|
+
debugScroll = debugScrollDefault;
|
|
13000
|
+
}
|
|
13001
|
+
if (debugPopup === true || debugInteraction && debugPopup === undefined) {
|
|
13002
|
+
debugPopup = debugPopupDefault;
|
|
13003
|
+
}
|
|
13004
|
+
if (debugAction === true) {
|
|
13005
|
+
debugAction = debugActionDefault;
|
|
13006
|
+
}
|
|
13007
|
+
if (debugUIState === true) {
|
|
13008
|
+
debugUIState = debugUIStateDefault;
|
|
13009
|
+
}
|
|
13010
|
+
return jsx(DebugCommandContext.Provider, {
|
|
13011
|
+
value: debugCommand,
|
|
13012
|
+
children: jsx(DebugInteractionContext.Provider, {
|
|
13013
|
+
value: debugInteraction,
|
|
13014
|
+
children: jsx(DebugFocusContext.Provider, {
|
|
13015
|
+
value: debugFocus,
|
|
13016
|
+
children: jsx(DebugScrollContext.Provider, {
|
|
13017
|
+
value: debugScroll,
|
|
13018
|
+
children: jsx(DebugPopupContext.Provider, {
|
|
13019
|
+
value: debugPopup,
|
|
13020
|
+
children: jsx(DebugActionContext.Provider, {
|
|
13021
|
+
value: debugAction,
|
|
13022
|
+
children: jsx(DebugUIStateContext.Provider, {
|
|
13023
|
+
value: debugUIState,
|
|
13024
|
+
children: children
|
|
13025
|
+
})
|
|
13026
|
+
})
|
|
13027
|
+
})
|
|
13028
|
+
})
|
|
13029
|
+
})
|
|
13030
|
+
})
|
|
13031
|
+
});
|
|
13032
|
+
};
|
|
13033
|
+
return {
|
|
13034
|
+
useDebugCommand,
|
|
13035
|
+
useDebugInteraction,
|
|
13036
|
+
useDebugFocus,
|
|
13037
|
+
useDebugScroll,
|
|
13038
|
+
useDebugPopup,
|
|
13039
|
+
useDebugAction,
|
|
13040
|
+
useDebugUIState,
|
|
13041
|
+
NaviDebug
|
|
13042
|
+
};
|
|
13043
|
+
};
|
|
13044
|
+
const naviDebug = createNaviDebug() ;
|
|
12941
13045
|
|
|
12942
13046
|
// The hooks below expose one concern's logger to components inside <NaviDebug>.
|
|
12943
13047
|
// Each returns the logger function enabled for that concern, or a no-op when the
|
|
12944
13048
|
// concern is off — so call sites can `const debug = useDebugX()` unconditionally.
|
|
12945
13049
|
// The logger is called as `debug(message, …)` or, to group a side effect under
|
|
12946
13050
|
// the native event that caused it, `debug(event, message, …)`.
|
|
13051
|
+
// In production every hook is the no-op returner and the bundler drops the
|
|
13052
|
+
// call sites along with their messages.
|
|
12947
13053
|
|
|
12948
13054
|
/** Logger for navi command dispatch (`--navi-*`), or a no-op when disabled. */
|
|
12949
|
-
const useDebugCommand =
|
|
12950
|
-
const debug = useContext(DebugCommandContext);
|
|
12951
|
-
return debug || debugNoop;
|
|
12952
|
-
};
|
|
13055
|
+
const useDebugCommand = naviDebug.useDebugCommand ;
|
|
12953
13056
|
/** Logger for gated interactions (click/scroll/select/…), or a no-op. */
|
|
12954
|
-
const useDebugInteraction =
|
|
12955
|
-
const debug = useContext(DebugInteractionContext);
|
|
12956
|
-
return debug || debugNoop;
|
|
12957
|
-
};
|
|
13057
|
+
const useDebugInteraction = naviDebug.useDebugInteraction ;
|
|
12958
13058
|
/** Logger for focus moves and focus-visible decisions, or a no-op. */
|
|
12959
|
-
const useDebugFocus =
|
|
12960
|
-
const debug = useContext(DebugFocusContext);
|
|
12961
|
-
return debug || debugNoop;
|
|
12962
|
-
};
|
|
13059
|
+
const useDebugFocus = naviDebug.useDebugFocus ;
|
|
12963
13060
|
/**
|
|
12964
13061
|
* Logger for virtual scroll / wheel motion (drag, momentum, glide) and for what
|
|
12965
13062
|
* a virtualized list does about it — the render window moving, and the rows the
|
|
12966
13063
|
* run asks for or decides not to ask for. Or a no-op.
|
|
12967
13064
|
*/
|
|
12968
|
-
const useDebugScroll =
|
|
12969
|
-
const debug = useContext(DebugScrollContext);
|
|
12970
|
-
return debug || debugNoop;
|
|
12971
|
-
};
|
|
13065
|
+
const useDebugScroll = naviDebug.useDebugScroll ;
|
|
12972
13066
|
/** Logger for popover/dialog open/close/positioning, or a no-op. */
|
|
12973
|
-
const useDebugPopup =
|
|
12974
|
-
const debug = useContext(DebugPopupContext);
|
|
12975
|
-
return debug || debugNoop;
|
|
12976
|
-
};
|
|
13067
|
+
const useDebugPopup = naviDebug.useDebugPopup ;
|
|
12977
13068
|
/** Logger for the action lifecycle (request → run → end), or a no-op. */
|
|
12978
|
-
const useDebugAction =
|
|
12979
|
-
const debug = useContext(DebugActionContext);
|
|
12980
|
-
return debug || debugNoop;
|
|
12981
|
-
};
|
|
13069
|
+
const useDebugAction = naviDebug.useDebugAction ;
|
|
12982
13070
|
/** Logger for UI-state transitions, validation and synthetic events, or a no-op. */
|
|
12983
|
-
const useDebugUIState =
|
|
12984
|
-
const debug = useContext(DebugUIStateContext);
|
|
12985
|
-
return debug || debugNoop;
|
|
12986
|
-
};
|
|
13071
|
+
const useDebugUIState = naviDebug.useDebugUIState ;
|
|
12987
13072
|
|
|
12988
13073
|
/**
|
|
12989
13074
|
* Turns on navi's color-coded console logging for everything rendered inside it.
|
|
@@ -13002,6 +13087,8 @@ const useDebugUIState = () => {
|
|
|
13002
13087
|
* `debugScroll` and `debugPopup` unless those are set explicitly, since they
|
|
13003
13088
|
* describe the same interaction.
|
|
13004
13089
|
*
|
|
13090
|
+
* Dev-only: the production build of navi renders the children and logs nothing.
|
|
13091
|
+
*
|
|
13005
13092
|
* @param {object} props
|
|
13006
13093
|
* @param {boolean|Function} [props.debugAll] - Default for every concern below.
|
|
13007
13094
|
* @param {boolean|Function} [props.debugCommand] - navi command dispatch (`--navi-*`).
|
|
@@ -13027,61 +13114,12 @@ const useDebugUIState = () => {
|
|
|
13027
13114
|
* <Wheel>…</Wheel>
|
|
13028
13115
|
* </NaviDebug>
|
|
13029
13116
|
*/
|
|
13030
|
-
const NaviDebug =
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
debugPopup = debugAll,
|
|
13037
|
-
debugAction = debugAll,
|
|
13038
|
-
debugUIState = debugAll,
|
|
13039
|
-
children
|
|
13040
|
-
}) => {
|
|
13041
|
-
if (debugCommand === true) {
|
|
13042
|
-
debugCommand = debugCommandDefault;
|
|
13043
|
-
}
|
|
13044
|
-
if (debugInteraction === true) {
|
|
13045
|
-
debugInteraction = debugInteractionDefault;
|
|
13046
|
-
}
|
|
13047
|
-
if (debugFocus === true || debugInteraction && debugFocus === undefined) {
|
|
13048
|
-
debugFocus = debugFocusDefault;
|
|
13049
|
-
}
|
|
13050
|
-
if (debugScroll === true || debugInteraction && debugScroll === undefined) {
|
|
13051
|
-
debugScroll = debugScrollDefault;
|
|
13052
|
-
}
|
|
13053
|
-
if (debugPopup === true || debugInteraction && debugPopup === undefined) {
|
|
13054
|
-
debugPopup = debugPopupDefault;
|
|
13055
|
-
}
|
|
13056
|
-
if (debugAction === true) {
|
|
13057
|
-
debugAction = debugActionDefault;
|
|
13058
|
-
}
|
|
13059
|
-
if (debugUIState === true) {
|
|
13060
|
-
debugUIState = debugUIStateDefault;
|
|
13061
|
-
}
|
|
13062
|
-
return jsx(DebugCommandContext.Provider, {
|
|
13063
|
-
value: debugCommand,
|
|
13064
|
-
children: jsx(DebugInteractionContext.Provider, {
|
|
13065
|
-
value: debugInteraction,
|
|
13066
|
-
children: jsx(DebugFocusContext.Provider, {
|
|
13067
|
-
value: debugFocus,
|
|
13068
|
-
children: jsx(DebugScrollContext.Provider, {
|
|
13069
|
-
value: debugScroll,
|
|
13070
|
-
children: jsx(DebugPopupContext.Provider, {
|
|
13071
|
-
value: debugPopup,
|
|
13072
|
-
children: jsx(DebugActionContext.Provider, {
|
|
13073
|
-
value: debugAction,
|
|
13074
|
-
children: jsx(DebugUIStateContext.Provider, {
|
|
13075
|
-
value: debugUIState,
|
|
13076
|
-
children: children
|
|
13077
|
-
})
|
|
13078
|
-
})
|
|
13079
|
-
})
|
|
13080
|
-
})
|
|
13081
|
-
})
|
|
13082
|
-
})
|
|
13083
|
-
});
|
|
13084
|
-
};
|
|
13117
|
+
const NaviDebug = naviDebug.NaviDebug ;
|
|
13118
|
+
|
|
13119
|
+
// For a caller whose message costs something to build (a line of numbers
|
|
13120
|
+
// formatted on every placement): nothing to build for a logger that logs nothing.
|
|
13121
|
+
// Always true in production, so the bundler drops the building too.
|
|
13122
|
+
const isDebugNoop = debug => debug === debugNoop ;
|
|
13085
13123
|
|
|
13086
13124
|
const actionErrorWeakMap = new WeakMap();
|
|
13087
13125
|
const NAVI_ACTION_ERROR_CONSTRAINT = {
|
|
@@ -22328,17 +22366,19 @@ const createUITransitionController = (
|
|
|
22328
22366
|
pauseBreakpoints = [],
|
|
22329
22367
|
} = {},
|
|
22330
22368
|
) => {
|
|
22331
|
-
const
|
|
22332
|
-
|
|
22333
|
-
|
|
22334
|
-
};
|
|
22335
|
-
const hasDebugLogs = debugConfig.size;
|
|
22369
|
+
const hasDebugDetection =
|
|
22370
|
+
root.hasAttribute("data-debug-detection");
|
|
22371
|
+
const hasDebugLogs = root.hasAttribute("data-debug-size");
|
|
22336
22372
|
const debugDetection = (message) => {
|
|
22337
|
-
if (!
|
|
22373
|
+
if (!hasDebugDetection) {
|
|
22374
|
+
return;
|
|
22375
|
+
}
|
|
22338
22376
|
console.debug(`[detection]`, message);
|
|
22339
22377
|
};
|
|
22340
22378
|
const debugSize = (message) => {
|
|
22341
|
-
if (!
|
|
22379
|
+
if (!hasDebugLogs) {
|
|
22380
|
+
return;
|
|
22381
|
+
}
|
|
22342
22382
|
console.debug(`[size]`, message);
|
|
22343
22383
|
};
|
|
22344
22384
|
|
|
@@ -23188,7 +23228,8 @@ const useUITransitionContentId = value => {
|
|
|
23188
23228
|
|
|
23189
23229
|
|
|
23190
23230
|
const DEBUG$1 =
|
|
23191
|
-
typeof process === "object"
|
|
23231
|
+
typeof process === "object" &&
|
|
23232
|
+
process.env.DEBUG === "true";
|
|
23192
23233
|
const debug$2 = (...args) => {
|
|
23193
23234
|
if (DEBUG$1) {
|
|
23194
23235
|
console.debug(...args);
|
|
@@ -25615,9 +25656,30 @@ const route = (
|
|
|
25615
25656
|
});
|
|
25616
25657
|
// methods
|
|
25617
25658
|
registerSetup(({ routeSet, getUrl }) => {
|
|
25659
|
+
// One computed per set of params. Building a url reads the route family's
|
|
25660
|
+
// signals (see readSignalForUrlBuild) and verifies the candidate against
|
|
25661
|
+
// every pattern of the family; a list of a hundred links to one route
|
|
25662
|
+
// would do that a hundred times on each of its renders. A computed does it
|
|
25663
|
+
// once, again only when a signal it read changes — and a component reading
|
|
25664
|
+
// it subscribes to exactly those, which is what reading the signals during
|
|
25665
|
+
// render gave it.
|
|
25666
|
+
const relativeUrlByParamsKey = new Map();
|
|
25618
25667
|
route.buildRelativeUrl = (params) => {
|
|
25619
|
-
|
|
25620
|
-
|
|
25668
|
+
const paramsKey = getParamsCacheKey(params);
|
|
25669
|
+
if (paramsKey === null) {
|
|
25670
|
+
return routePattern.buildMostPreciseUrl(params);
|
|
25671
|
+
}
|
|
25672
|
+
let relativeUrlComputed = relativeUrlByParamsKey.get(paramsKey);
|
|
25673
|
+
if (!relativeUrlComputed) {
|
|
25674
|
+
if (relativeUrlByParamsKey.size >= PARAMS_CACHE_MAX) {
|
|
25675
|
+
relativeUrlByParamsKey.clear();
|
|
25676
|
+
}
|
|
25677
|
+
relativeUrlComputed = computed(() =>
|
|
25678
|
+
routePattern.buildMostPreciseUrl(params),
|
|
25679
|
+
);
|
|
25680
|
+
relativeUrlByParamsKey.set(paramsKey, relativeUrlComputed);
|
|
25681
|
+
}
|
|
25682
|
+
return relativeUrlComputed.value;
|
|
25621
25683
|
};
|
|
25622
25684
|
route.buildUrl = (params) => {
|
|
25623
25685
|
const routeRelativeUrl = route.buildRelativeUrl(params);
|
|
@@ -26413,6 +26475,36 @@ const setOnAllRouteReady = (callback) => {
|
|
|
26413
26475
|
onAllRouteReady = callback;
|
|
26414
26476
|
};
|
|
26415
26477
|
|
|
26478
|
+
// Params are cached by content, and only params whose content a string can
|
|
26479
|
+
// stand for: anything else (an object, a function) builds without the cache.
|
|
26480
|
+
// A key set to undefined is content too — it says "without this one", which
|
|
26481
|
+
// a params object not naming it does not say (see buildIntendedState).
|
|
26482
|
+
const PARAMS_CACHE_MAX = 1000;
|
|
26483
|
+
const getParamsCacheKey = (params) => {
|
|
26484
|
+
if (params === undefined || params === null) {
|
|
26485
|
+
return "";
|
|
26486
|
+
}
|
|
26487
|
+
if (typeof params !== "object") {
|
|
26488
|
+
return null;
|
|
26489
|
+
}
|
|
26490
|
+
const keys = Object.keys(params).sort();
|
|
26491
|
+
let key = "";
|
|
26492
|
+
for (const name of keys) {
|
|
26493
|
+
const value = params[name];
|
|
26494
|
+
const type = typeof value;
|
|
26495
|
+
if (
|
|
26496
|
+
value !== null &&
|
|
26497
|
+
type !== "string" &&
|
|
26498
|
+
type !== "number" &&
|
|
26499
|
+
type !== "boolean"
|
|
26500
|
+
) {
|
|
26501
|
+
return null;
|
|
26502
|
+
}
|
|
26503
|
+
key += `${name}=${type}:${value}\n`;
|
|
26504
|
+
}
|
|
26505
|
+
return key;
|
|
26506
|
+
};
|
|
26507
|
+
|
|
26416
26508
|
/**
|
|
26417
26509
|
* Is there an entry of THIS document behind the current one? And ahead of it?
|
|
26418
26510
|
*
|
|
@@ -42736,6 +42828,73 @@ const LoadingOutlineWithPortal = props => {
|
|
|
42736
42828
|
});
|
|
42737
42829
|
};
|
|
42738
42830
|
|
|
42831
|
+
/**
|
|
42832
|
+
* Reads of the layout gathered across effects, so that the browser brings its
|
|
42833
|
+
* styles and layout up to date once for all of them rather than once per
|
|
42834
|
+
* element.
|
|
42835
|
+
*
|
|
42836
|
+
* A layout effect that reads (a computed style, a rect) and then writes (an
|
|
42837
|
+
* attribute, a style) is fine on its own. A hundred of them in one commit are
|
|
42838
|
+
* not: every write dirties the tree, and the next element's read forces the
|
|
42839
|
+
* browser to recompute it — a style recalculation per element, each one over
|
|
42840
|
+
* everything dirtied since the last. Most of a long mount's time goes there,
|
|
42841
|
+
* not in the effects themselves.
|
|
42842
|
+
*
|
|
42843
|
+
* So an effect hands its read over instead of running it. Reads run together
|
|
42844
|
+
* in a microtask — still before the paint, so nothing shows uncorrected — and
|
|
42845
|
+
* each returns the write that depends on it, run after every read of the
|
|
42846
|
+
* round. A write that needs to read again returns that read: it joins the next
|
|
42847
|
+
* round, reads first, writes after, and so on until nothing is left.
|
|
42848
|
+
*
|
|
42849
|
+
* The cancel returned is for an element unmounted before its round: a write
|
|
42850
|
+
* on a node that is gone is at best wasted.
|
|
42851
|
+
*
|
|
42852
|
+
* @param {() => (undefined | (() => undefined | Function))} read
|
|
42853
|
+
* @returns {() => void} cancel
|
|
42854
|
+
*/
|
|
42855
|
+
const scheduleLayoutRead = (read) => {
|
|
42856
|
+
const entry = { read, cancelled: false };
|
|
42857
|
+
round.push(entry);
|
|
42858
|
+
if (!flushScheduled) {
|
|
42859
|
+
flushScheduled = true;
|
|
42860
|
+
queueMicrotask(flush);
|
|
42861
|
+
}
|
|
42862
|
+
return () => {
|
|
42863
|
+
entry.cancelled = true;
|
|
42864
|
+
};
|
|
42865
|
+
};
|
|
42866
|
+
|
|
42867
|
+
let round = [];
|
|
42868
|
+
let flushScheduled = false;
|
|
42869
|
+
|
|
42870
|
+
const flush = () => {
|
|
42871
|
+
flushScheduled = false;
|
|
42872
|
+
while (round.length > 0) {
|
|
42873
|
+
const entries = round;
|
|
42874
|
+
round = [];
|
|
42875
|
+
const writes = [];
|
|
42876
|
+
for (const entry of entries) {
|
|
42877
|
+
if (entry.cancelled) {
|
|
42878
|
+
continue;
|
|
42879
|
+
}
|
|
42880
|
+
const write = entry.read();
|
|
42881
|
+
if (typeof write === "function") {
|
|
42882
|
+
writes.push({ entry, write });
|
|
42883
|
+
}
|
|
42884
|
+
}
|
|
42885
|
+
for (const { entry, write } of writes) {
|
|
42886
|
+
if (entry.cancelled) {
|
|
42887
|
+
continue;
|
|
42888
|
+
}
|
|
42889
|
+
const readAgain = write();
|
|
42890
|
+
if (typeof readAgain === "function") {
|
|
42891
|
+
entry.read = readAgain;
|
|
42892
|
+
round.push(entry);
|
|
42893
|
+
}
|
|
42894
|
+
}
|
|
42895
|
+
}
|
|
42896
|
+
};
|
|
42897
|
+
|
|
42739
42898
|
installImportMetaCssBuild(import.meta);
|
|
42740
42899
|
const css$_ = /* css */`.navi_text_anchor {
|
|
42741
42900
|
vertical-align: baseline;
|
|
@@ -42777,6 +42936,16 @@ const TextAnchor = ({
|
|
|
42777
42936
|
}) => {
|
|
42778
42937
|
import.meta.css = [css$_, "@jsenv/navi/src/text/text_anchor.jsx"];
|
|
42779
42938
|
const anchorRef = useRef();
|
|
42939
|
+
// The correction reads the layout, with the other reads of the commit (see
|
|
42940
|
+
// layout_batch.js) — an anchor per icon, each measured between two writes,
|
|
42941
|
+
// is a style recalculation per icon. Cancelled by a newer correction or by
|
|
42942
|
+
// the unmount: a write on a node that is gone is wasted.
|
|
42943
|
+
const cancelReadRef = useRef(null);
|
|
42944
|
+
useLayoutEffect(() => {
|
|
42945
|
+
return () => {
|
|
42946
|
+
cancelReadRef.current?.();
|
|
42947
|
+
};
|
|
42948
|
+
}, []);
|
|
42780
42949
|
|
|
42781
42950
|
// Plain useLayoutEffect would also fire while an ancestor dialog/popover
|
|
42782
42951
|
// (e.g. a closed SidePanel) is still display:none — every rect involved
|
|
@@ -42805,23 +42974,36 @@ const TextAnchor = ({
|
|
|
42805
42974
|
if (!anchorEl || !childEl) {
|
|
42806
42975
|
return;
|
|
42807
42976
|
}
|
|
42808
|
-
|
|
42809
|
-
|
|
42810
|
-
|
|
42811
|
-
|
|
42812
|
-
|
|
42813
|
-
|
|
42814
|
-
|
|
42815
|
-
|
|
42816
|
-
|
|
42817
|
-
|
|
42818
|
-
|
|
42819
|
-
|
|
42820
|
-
|
|
42821
|
-
|
|
42822
|
-
|
|
42977
|
+
cancelReadRef.current?.();
|
|
42978
|
+
cancelReadRef.current = scheduleLayoutRead(() => {
|
|
42979
|
+
// Only correct when the anchor lives in an inline formatting context.
|
|
42980
|
+
// If the parent is a flex/grid container, inline layout rules don't
|
|
42981
|
+
// apply and our font-metrics model is invalid.
|
|
42982
|
+
const parentDisplay = getComputedStyle(anchorEl.parentElement).display;
|
|
42983
|
+
if (parentDisplay !== "inline" && parentDisplay !== "inline-block" && parentDisplay !== "block") {
|
|
42984
|
+
return () => {
|
|
42985
|
+
// we must hide the anchor otherwise it would affect layout without
|
|
42986
|
+
// providing any benefit (would trigger flex gap for instance)
|
|
42987
|
+
anchorEl.setAttribute("hidden", "");
|
|
42988
|
+
setTopOffset(childEl, 0);
|
|
42989
|
+
};
|
|
42990
|
+
}
|
|
42991
|
+
return () => {
|
|
42992
|
+
// The anchor has to be in the flow to be measured: shown here, read
|
|
42993
|
+
// in the next round, with the other anchors shown by this one.
|
|
42994
|
+
anchorEl.removeAttribute("hidden");
|
|
42995
|
+
return () => {
|
|
42996
|
+
const topOffset = computeTopOffset({
|
|
42997
|
+
anchorEl,
|
|
42998
|
+
childEl,
|
|
42999
|
+
textAnchor
|
|
43000
|
+
});
|
|
43001
|
+
return () => {
|
|
43002
|
+
setTopOffset(childEl, topOffset);
|
|
43003
|
+
};
|
|
43004
|
+
};
|
|
43005
|
+
};
|
|
42823
43006
|
});
|
|
42824
|
-
setTopOffset(childEl, topOffset);
|
|
42825
43007
|
}, [textAnchor, textKey, textSize, lineLayout?.size, lineLayout?.verticalAlign]);
|
|
42826
43008
|
return jsxs(Fragment, {
|
|
42827
43009
|
children: [children, jsx("span", {
|
|
@@ -48501,53 +48683,35 @@ const useNextResolver = () => useContext(NextResolverContext);
|
|
|
48501
48683
|
* // Then inside a component render:
|
|
48502
48684
|
* renderButton(props)
|
|
48503
48685
|
*
|
|
48504
|
-
*
|
|
48505
|
-
* the
|
|
48506
|
-
*
|
|
48507
|
-
*
|
|
48686
|
+
* Each position of the chain has a runner of its own, defined once: it renders
|
|
48687
|
+
* its resolver under a NextResolverContext holding the runner after it. A
|
|
48688
|
+
* resolver that re-renders on its own and renders <Next> therefore resumes at
|
|
48689
|
+
* its own position without any bookkeeping — the Next it was given is the one
|
|
48690
|
+
* made for it. A chain rendered by the hundred (a list's rows) pays two
|
|
48691
|
+
* components per position, the runner and the resolver, so nothing here is a
|
|
48692
|
+
* component that merely forwards.
|
|
48508
48693
|
*/
|
|
48509
48694
|
const createComponentResolver = (resolvers, {
|
|
48510
48695
|
pure
|
|
48511
48696
|
} = {}) => {
|
|
48512
|
-
const
|
|
48513
|
-
const
|
|
48514
|
-
|
|
48515
|
-
if (index >= resolvers.length) {
|
|
48516
|
-
return null;
|
|
48517
|
-
}
|
|
48697
|
+
const runners = [];
|
|
48698
|
+
const lastIndex = resolvers.length - 1;
|
|
48699
|
+
for (let index = 0; index < resolvers.length; index++) {
|
|
48518
48700
|
const Resolver = resolvers[index];
|
|
48519
|
-
const isLast = index ===
|
|
48520
|
-
|
|
48521
|
-
value: index + 1,
|
|
48522
|
-
children:
|
|
48523
|
-
value: null,
|
|
48524
|
-
children: jsx(Resolver, {
|
|
48525
|
-
...props
|
|
48526
|
-
})
|
|
48527
|
-
}) : jsx(Resolver, {
|
|
48701
|
+
const isLast = index === lastIndex;
|
|
48702
|
+
const Runner = props => jsx(NextResolverContext.Provider, {
|
|
48703
|
+
value: isLast ? null : runners[index + 1],
|
|
48704
|
+
children: jsx(Resolver, {
|
|
48528
48705
|
...props
|
|
48529
48706
|
})
|
|
48530
48707
|
});
|
|
48531
|
-
|
|
48532
|
-
|
|
48533
|
-
|
|
48534
|
-
|
|
48535
|
-
|
|
48536
|
-
// resumes from index+1 (already set by the Provider wrapping that resolver).
|
|
48537
|
-
const NextComponent = props => jsx(ChainRunner, {
|
|
48708
|
+
Runner.displayName = `${Resolver.displayName || Resolver.name}Runner`;
|
|
48709
|
+
runners.push(Runner);
|
|
48710
|
+
}
|
|
48711
|
+
const FirstRunner = runners[0];
|
|
48712
|
+
const renderComponent = props => jsx(FirstRunner, {
|
|
48538
48713
|
...props
|
|
48539
48714
|
});
|
|
48540
|
-
const renderComponent = props => {
|
|
48541
|
-
return jsx(NextResolverContext.Provider, {
|
|
48542
|
-
value: NextComponent,
|
|
48543
|
-
children: jsx(ResolverIndexContext.Provider, {
|
|
48544
|
-
value: 0,
|
|
48545
|
-
children: jsx(ChainRunner, {
|
|
48546
|
-
...props
|
|
48547
|
-
})
|
|
48548
|
-
})
|
|
48549
|
-
});
|
|
48550
|
-
};
|
|
48551
48715
|
if (!pure) {
|
|
48552
48716
|
return renderComponent;
|
|
48553
48717
|
}
|
|
@@ -48643,36 +48807,36 @@ const useAccentColorAttributes = (
|
|
|
48643
48807
|
return undefined;
|
|
48644
48808
|
}
|
|
48645
48809
|
}
|
|
48810
|
+
// Read with the other layout reads of the commit (see layout_batch.js): a
|
|
48811
|
+
// computed color per control, each read between two writes, is a style
|
|
48812
|
+
// recalculation per control.
|
|
48813
|
+
let cancelRead = null;
|
|
48646
48814
|
const updateAttributes = () => {
|
|
48647
|
-
|
|
48648
|
-
|
|
48649
|
-
|
|
48650
|
-
|
|
48651
|
-
|
|
48652
|
-
|
|
48653
|
-
|
|
48654
|
-
|
|
48655
|
-
|
|
48656
|
-
|
|
48657
|
-
|
|
48658
|
-
|
|
48659
|
-
|
|
48660
|
-
|
|
48661
|
-
|
|
48662
|
-
|
|
48663
|
-
|
|
48664
|
-
|
|
48665
|
-
|
|
48666
|
-
|
|
48667
|
-
|
|
48668
|
-
|
|
48669
|
-
|
|
48670
|
-
|
|
48671
|
-
|
|
48672
|
-
el.setAttribute(DARK_CONTRAST_ATTRIBUTE, "");
|
|
48673
|
-
} else {
|
|
48674
|
-
el.removeAttribute(DARK_CONTRAST_ATTRIBUTE);
|
|
48675
|
-
}
|
|
48815
|
+
cancelRead?.();
|
|
48816
|
+
cancelRead = scheduleLayoutRead(() => {
|
|
48817
|
+
const computedStyle = getComputedStyle(elementToCheck);
|
|
48818
|
+
const color = computedStyle[colorProperty];
|
|
48819
|
+
if (!color) {
|
|
48820
|
+
return () => {
|
|
48821
|
+
el.removeAttribute(LIGHT_ACCENT_ATTRIBUTE);
|
|
48822
|
+
el.removeAttribute(VERY_LIGHT_ACCENT_ATTRIBUTE);
|
|
48823
|
+
el.removeAttribute(DARK_CONTRAST_ATTRIBUTE);
|
|
48824
|
+
};
|
|
48825
|
+
}
|
|
48826
|
+
const luminance = resolveOklchLightness(color, el);
|
|
48827
|
+
const light =
|
|
48828
|
+
luminance !== null && luminance > LIGHT_LUMINANCE_THRESHOLD;
|
|
48829
|
+
const veryLight =
|
|
48830
|
+
luminance !== null && luminance > VERY_LIGHT_LUMINANCE_THRESHOLD;
|
|
48831
|
+
const needsDarkForeground =
|
|
48832
|
+
contrastColor(color, el, DARK_CONTRAST_LIGHTNESS_THRESHOLD) ===
|
|
48833
|
+
"black";
|
|
48834
|
+
return () => {
|
|
48835
|
+
el.toggleAttribute(LIGHT_ACCENT_ATTRIBUTE, light);
|
|
48836
|
+
el.toggleAttribute(VERY_LIGHT_ACCENT_ATTRIBUTE, veryLight);
|
|
48837
|
+
el.toggleAttribute(DARK_CONTRAST_ATTRIBUTE, needsDarkForeground);
|
|
48838
|
+
};
|
|
48839
|
+
});
|
|
48676
48840
|
};
|
|
48677
48841
|
updateAttributes();
|
|
48678
48842
|
const unsubscribeFromPseudoState = subscribeToPseudoState(
|
|
@@ -48680,6 +48844,7 @@ const useAccentColorAttributes = (
|
|
|
48680
48844
|
updateAttributes,
|
|
48681
48845
|
);
|
|
48682
48846
|
return () => {
|
|
48847
|
+
cancelRead?.();
|
|
48683
48848
|
unsubscribeFromPseudoState();
|
|
48684
48849
|
el.removeAttribute(LIGHT_ACCENT_ATTRIBUTE);
|
|
48685
48850
|
el.removeAttribute(VERY_LIGHT_ACCENT_ATTRIBUTE);
|
|
@@ -61006,6 +61171,9 @@ const useDialogProps = props => {
|
|
|
61006
61171
|
// strip), and the absence of a line after the keyboard arrives means the
|
|
61007
61172
|
// placement was never asked to run again.
|
|
61008
61173
|
const logPlacement = (triggerEvent, position) => {
|
|
61174
|
+
if (isDebugNoop(debugPopup)) {
|
|
61175
|
+
return;
|
|
61176
|
+
}
|
|
61009
61177
|
// The decided target, never the current rect: the placement animates, so
|
|
61010
61178
|
// a box read right after applying one is somewhere between the two and
|
|
61011
61179
|
// reads like a decision nobody took.
|
|
@@ -63307,7 +63475,11 @@ const Popup = props => {
|
|
|
63307
63475
|
Popup.Close = PopupClose;
|
|
63308
63476
|
|
|
63309
63477
|
installImportMetaCssBuild(import.meta);
|
|
63310
|
-
const css$B = /* css */`.
|
|
63478
|
+
const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
|
|
63479
|
+
display: none;
|
|
63480
|
+
}
|
|
63481
|
+
|
|
63482
|
+
.navi_popover[data-picker-popup] {
|
|
63311
63483
|
--popover-border-radius: var(--picker-border-radius);
|
|
63312
63484
|
--popover-border-width: var(--picker-border-width);
|
|
63313
63485
|
--popover-border-color: var(--x-picker-border-color);
|
|
@@ -64259,10 +64431,14 @@ const PICKER_CALLOUT_CONTENT_TOKEN = createOpenToken();
|
|
|
64259
64431
|
* closes the controller for real — the popup is already gone, there is no
|
|
64260
64432
|
* choice left to offer `requestClose`.
|
|
64261
64433
|
*
|
|
64262
|
-
* The content is rendered
|
|
64263
|
-
*
|
|
64264
|
-
*
|
|
64265
|
-
*
|
|
64434
|
+
* The content is rendered into an element this component owns, handed to the
|
|
64435
|
+
* callout as its message (a Node, appended as-is) and taken back when the
|
|
64436
|
+
* callout closes. Between two opens it is docked, hidden, in the span below:
|
|
64437
|
+
* in the document the whole time, the way a popup's `mount="always"` keeps its
|
|
64438
|
+
* content — so what it holds survives a close, and what it measures of itself
|
|
64439
|
+
* at mount (a computed color, a light-dark() pair) resolves against a real
|
|
64440
|
+
* ancestry. An element with no document has no computed style, and a badge
|
|
64441
|
+
* reading its own background there would see nothing at all. The element
|
|
64266
64442
|
* carries data-picker-content: the callout is appended inside the picker root,
|
|
64267
64443
|
* and a press in there must read as inside the popup, not on the trigger.
|
|
64268
64444
|
*/
|
|
@@ -64282,23 +64458,22 @@ const PickerCalloutPopup = ({
|
|
|
64282
64458
|
children
|
|
64283
64459
|
}) => {
|
|
64284
64460
|
const hostRef = useRef(null);
|
|
64285
|
-
if (!hostRef.current) {
|
|
64286
|
-
const host = document.createElement("div");
|
|
64287
|
-
host.setAttribute("data-picker-content", "");
|
|
64288
|
-
hostRef.current = host;
|
|
64289
|
-
}
|
|
64290
64461
|
// Reassigned on every render, like Popover's own, so it closes over the
|
|
64291
64462
|
// latest props.
|
|
64292
64463
|
openController.getElement = () => pickerRef.current;
|
|
64293
64464
|
openController.openEffect = openEvent => {
|
|
64294
64465
|
const pickerEl = pickerRef.current;
|
|
64466
|
+
const host = hostRef.current;
|
|
64467
|
+
// Where the content sits while the callout is closed (the span below);
|
|
64468
|
+
// the callout takes the element out of it and the cleanup puts it back.
|
|
64469
|
+
const dock = host.parentNode;
|
|
64295
64470
|
const calloutManager = getPickerInput(pickerEl).__uiStateController__.rules.callout;
|
|
64296
64471
|
// Only an anchor the caller named: left unsaid, the manager anchors on the
|
|
64297
64472
|
// picker's own input — which is where the data-callout-* attributes a
|
|
64298
64473
|
// caller puts on the picker land, and where the callout reads them.
|
|
64299
64474
|
const anchorElement = anchor === pickerRef ? undefined : anchor && "current" in anchor ? anchor.current : anchor;
|
|
64300
64475
|
calloutManager.addOpenToken(PICKER_CALLOUT_CONTENT_TOKEN, {
|
|
64301
|
-
message:
|
|
64476
|
+
message: host,
|
|
64302
64477
|
// The popup a `popupTestId` names is this callout: it is the surface the
|
|
64303
64478
|
// picker opens, drawn by navi, so it is the one thing the caller cannot
|
|
64304
64479
|
// name from its own children.
|
|
@@ -64332,6 +64507,7 @@ const PickerCalloutPopup = ({
|
|
|
64332
64507
|
return closeEvent => {
|
|
64333
64508
|
calloutElement.removeEventListener("navi_request_confirm", forwardConfirm);
|
|
64334
64509
|
calloutManager.removeOpenToken(PICKER_CALLOUT_CONTENT_TOKEN, closeEvent);
|
|
64510
|
+
dock.appendChild(host);
|
|
64335
64511
|
};
|
|
64336
64512
|
};
|
|
64337
64513
|
return (
|
|
@@ -64341,13 +64517,18 @@ const PickerCalloutPopup = ({
|
|
|
64341
64517
|
as: "span",
|
|
64342
64518
|
ref: ref,
|
|
64343
64519
|
id: id,
|
|
64520
|
+
className: "navi_picker_callout_dock",
|
|
64344
64521
|
style: {
|
|
64345
64522
|
display: "contents"
|
|
64346
64523
|
},
|
|
64347
64524
|
onnavi_request_open: onnavi_request_open,
|
|
64348
64525
|
onnavi_request_close: onnavi_request_close,
|
|
64349
64526
|
onnavi_request_confirm: onnavi_request_confirm,
|
|
64350
|
-
children:
|
|
64527
|
+
children: jsx("div", {
|
|
64528
|
+
ref: hostRef,
|
|
64529
|
+
"data-picker-content": "",
|
|
64530
|
+
children: children
|
|
64531
|
+
})
|
|
64351
64532
|
})
|
|
64352
64533
|
);
|
|
64353
64534
|
};
|
|
@@ -64656,6 +64837,43 @@ document.addEventListener(
|
|
|
64656
64837
|
{ capture: true, passive: true },
|
|
64657
64838
|
);
|
|
64658
64839
|
|
|
64840
|
+
/**
|
|
64841
|
+
* Calls back once the browser has painted what is committed now.
|
|
64842
|
+
*
|
|
64843
|
+
* The callbacks of the next frame run before that frame paints, so the call is
|
|
64844
|
+
* made from a task queued from inside one — the same way preact schedules
|
|
64845
|
+
* useEffect. Unlike an effect, nothing runs this early: preact flushes a
|
|
64846
|
+
* component's pending effects as soon as that component renders again, and a
|
|
64847
|
+
* re-render before the paint is exactly what a caller waiting for the paint
|
|
64848
|
+
* has to survive. A frame not coming at all (a background tab) still answers,
|
|
64849
|
+
* late, through the timeout.
|
|
64850
|
+
*
|
|
64851
|
+
* @returns {() => void} cancel
|
|
64852
|
+
*/
|
|
64853
|
+
const FRAME_TIMEOUT_MS = 100;
|
|
64854
|
+
|
|
64855
|
+
const afterPaint = (callback) => {
|
|
64856
|
+
let called = false;
|
|
64857
|
+
let timeoutId;
|
|
64858
|
+
let frameId;
|
|
64859
|
+
const onFrame = () => {
|
|
64860
|
+
if (called) {
|
|
64861
|
+
return;
|
|
64862
|
+
}
|
|
64863
|
+
called = true;
|
|
64864
|
+
clearTimeout(timeoutId);
|
|
64865
|
+
cancelAnimationFrame(frameId);
|
|
64866
|
+
timeoutId = setTimeout(callback, 0);
|
|
64867
|
+
};
|
|
64868
|
+
timeoutId = setTimeout(onFrame, FRAME_TIMEOUT_MS);
|
|
64869
|
+
frameId = requestAnimationFrame(onFrame);
|
|
64870
|
+
return () => {
|
|
64871
|
+
called = true;
|
|
64872
|
+
clearTimeout(timeoutId);
|
|
64873
|
+
cancelAnimationFrame(frameId);
|
|
64874
|
+
};
|
|
64875
|
+
};
|
|
64876
|
+
|
|
64659
64877
|
const LoadingDotsSvg = () => {
|
|
64660
64878
|
return jsxs("svg", {
|
|
64661
64879
|
viewBox: "0 0 200 200",
|
|
@@ -66615,6 +66833,32 @@ const css$x = /* css */`@layer navi {
|
|
|
66615
66833
|
middle of the padding. On the scroll box the scrollbar stays against the
|
|
66616
66834
|
border and the padding is what separates the rows from it. */
|
|
66617
66835
|
const LIST_PADDING_PROP_SET = new Set(["padding", "paddingX", "paddingY", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft"]);
|
|
66836
|
+
|
|
66837
|
+
// Accepts a string too (renderBudget="50" from an HTML attribute): the
|
|
66838
|
+
// arithmetic on the budget (renderBudget / 2, start + renderBudget) would
|
|
66839
|
+
// silently misbehave on a raw string ("+" concatenates).
|
|
66840
|
+
const toRenderBudgetNumber = value => {
|
|
66841
|
+
if (typeof value === "string") {
|
|
66842
|
+
const parsed = Number(value);
|
|
66843
|
+
return Number.isFinite(parsed) ? parsed : RENDER_BUDGET_DEFAULT;
|
|
66844
|
+
}
|
|
66845
|
+
if (typeof value === "number") {
|
|
66846
|
+
return value;
|
|
66847
|
+
}
|
|
66848
|
+
return RENDER_BUDGET_DEFAULT;
|
|
66849
|
+
};
|
|
66850
|
+
const resolveRenderBudget = renderBudget => {
|
|
66851
|
+
if (renderBudget && typeof renderBudget === "object") {
|
|
66852
|
+
return {
|
|
66853
|
+
initial: renderBudget.initial === undefined ? undefined : toRenderBudgetNumber(renderBudget.initial),
|
|
66854
|
+
after: toRenderBudgetNumber(renderBudget.after)
|
|
66855
|
+
};
|
|
66856
|
+
}
|
|
66857
|
+
return {
|
|
66858
|
+
initial: undefined,
|
|
66859
|
+
after: toRenderBudgetNumber(renderBudget)
|
|
66860
|
+
};
|
|
66861
|
+
};
|
|
66618
66862
|
const ListUI = props => {
|
|
66619
66863
|
import.meta.css = [css$x, "@jsenv/navi/src/control/list/list.jsx"];
|
|
66620
66864
|
const {
|
|
@@ -66663,18 +66907,34 @@ const ListUI = props => {
|
|
|
66663
66907
|
delete rest[name];
|
|
66664
66908
|
}
|
|
66665
66909
|
}
|
|
66666
|
-
//
|
|
66667
|
-
//
|
|
66668
|
-
//
|
|
66669
|
-
//
|
|
66670
|
-
|
|
66671
|
-
|
|
66672
|
-
|
|
66673
|
-
|
|
66674
|
-
|
|
66910
|
+
// `renderBudget` is a number, or `{ initial, after }`: the window of the
|
|
66911
|
+
// first commit, until the browser has painted it, and the window from then
|
|
66912
|
+
// on. A list opening inside a popup draws in the click that opens it, and
|
|
66913
|
+
// the browser paints nothing before that render ends: rows below the fold
|
|
66914
|
+
// cost the same as rows on screen there, and are drawn to be seen one frame
|
|
66915
|
+
// later just as well. `after` takes over after the paint (see afterPaint for
|
|
66916
|
+
// why not an effect), and the floor of 30 is its: it protects the scrolling
|
|
66917
|
+
// window, not a picture nobody has seen yet.
|
|
66918
|
+
const {
|
|
66919
|
+
initial: initialRenderBudget,
|
|
66920
|
+
after: renderBudgetAfterPaint
|
|
66921
|
+
} = resolveRenderBudget(renderBudgetProp);
|
|
66922
|
+
let renderBudget = renderBudgetAfterPaint;
|
|
66675
66923
|
if (renderBudget < 30 && !renderBudgetSkipCheck) {
|
|
66676
66924
|
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}).`);
|
|
66677
66925
|
}
|
|
66926
|
+
const [firstPaintPending, setFirstPaintPending] = useState(initialRenderBudget !== undefined);
|
|
66927
|
+
useLayoutEffect(() => {
|
|
66928
|
+
if (!firstPaintPending) {
|
|
66929
|
+
return undefined;
|
|
66930
|
+
}
|
|
66931
|
+
return afterPaint(() => {
|
|
66932
|
+
setFirstPaintPending(false);
|
|
66933
|
+
});
|
|
66934
|
+
}, []);
|
|
66935
|
+
if (firstPaintPending) {
|
|
66936
|
+
renderBudget = initialRenderBudget;
|
|
66937
|
+
}
|
|
66678
66938
|
|
|
66679
66939
|
// lockSize: capture the container's dimensions on first render so filtering
|
|
66680
66940
|
// cannot collapse the layout. Measurement happens on the initial (unfiltered)
|
|
@@ -66711,7 +66971,10 @@ const ListUI = props => {
|
|
|
66711
66971
|
onListVisibleItemsChange?.(tracker.visibleItemsSignal.peek());
|
|
66712
66972
|
}
|
|
66713
66973
|
});
|
|
66714
|
-
|
|
66974
|
+
// What the runs ask for and stand for: the steady budget, whatever the
|
|
66975
|
+
// window of the first paint draws — a run asking for the rows of the first
|
|
66976
|
+
// picture and then for the rest is two round trips for one opening.
|
|
66977
|
+
virtual.renderBudget = renderBudgetAfterPaint;
|
|
66715
66978
|
virtual.scrolled = scrolled ?? defaultScrolled;
|
|
66716
66979
|
const {
|
|
66717
66980
|
virtualItemSizeSignal,
|
|
@@ -67060,7 +67323,10 @@ const useListScrollSync = ({
|
|
|
67060
67323
|
horizontal
|
|
67061
67324
|
}) => {
|
|
67062
67325
|
const debugScroll = useDebugScroll();
|
|
67063
|
-
const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal
|
|
67326
|
+
const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal, {
|
|
67327
|
+
virtual,
|
|
67328
|
+
renderBudget
|
|
67329
|
+
});
|
|
67064
67330
|
const getScroller = () => getScrollerEl(ref.current, scroller, horizontal);
|
|
67065
67331
|
const getListEl = () => ref.current.querySelector(".navi_list");
|
|
67066
67332
|
// Which box scrolls is measured (see getScrollerEl), so the answer holds
|
|
@@ -67116,6 +67382,28 @@ const useListScrollSync = ({
|
|
|
67116
67382
|
});
|
|
67117
67383
|
const renderWindowRef = useRef(null);
|
|
67118
67384
|
renderWindowRef.current = renderWindow;
|
|
67385
|
+
// A budget that changes (the first paint's giving way to the scrolling one)
|
|
67386
|
+
// re-frames the window where it stands, in this very render — the way
|
|
67387
|
+
// holdWindow below moves it: nothing else would, the scroll listener only
|
|
67388
|
+
// moves a window the user is about to leave.
|
|
67389
|
+
const renderBudgetRef = useRef(renderBudget);
|
|
67390
|
+
if (renderBudgetRef.current !== renderBudget) {
|
|
67391
|
+
renderBudgetRef.current = renderBudget;
|
|
67392
|
+
const {
|
|
67393
|
+
start
|
|
67394
|
+
} = renderWindowRef.current;
|
|
67395
|
+
const total = virtual.totalSignal.peek();
|
|
67396
|
+
let newStart = start;
|
|
67397
|
+
let newEnd = start + renderBudget;
|
|
67398
|
+
if (total > 0 && newEnd > total) {
|
|
67399
|
+
newEnd = total;
|
|
67400
|
+
newStart = total - renderBudget < 0 ? 0 : total - renderBudget;
|
|
67401
|
+
}
|
|
67402
|
+
renderWindowRef.current = {
|
|
67403
|
+
start: newStart,
|
|
67404
|
+
end: newEnd
|
|
67405
|
+
};
|
|
67406
|
+
}
|
|
67119
67407
|
const updateRenderWindow = (newStart, newEnd, reason) => {
|
|
67120
67408
|
const {
|
|
67121
67409
|
start,
|
|
@@ -68429,7 +68717,10 @@ const measureItemSize = (listEl, horizontal) => {
|
|
|
68429
68717
|
fromSkeletons
|
|
68430
68718
|
};
|
|
68431
68719
|
};
|
|
68432
|
-
const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal
|
|
68720
|
+
const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal, {
|
|
68721
|
+
virtual,
|
|
68722
|
+
renderBudget
|
|
68723
|
+
}) => {
|
|
68433
68724
|
const virtualSizeSignalRef = useRef(null);
|
|
68434
68725
|
if (!virtualSizeSignalRef.current) {
|
|
68435
68726
|
virtualSizeSignalRef.current = signal(virtualItemSizeProp);
|
|
@@ -68482,8 +68773,12 @@ const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal) => {
|
|
|
68482
68773
|
// as the rows it was measured on. Written from a layout effect it would
|
|
68483
68774
|
// resize them one commit later — after the scroll anchoring of that commit
|
|
68484
68775
|
// had already run, which is exactly the jump anchoring exists to prevent.
|
|
68776
|
+
// And only while some rows are held off screen: the fillers are what the
|
|
68777
|
+
// size is for, and a list drawing every row it has would pay a layout on
|
|
68778
|
+
// each of its renders for a number nothing reads.
|
|
68485
68779
|
const sizeAlreadyKnown = virtualSizeSignal.peek() !== 0;
|
|
68486
|
-
|
|
68780
|
+
const rowsHeldOffScreen = virtual.totalSignal.peek() > renderBudget;
|
|
68781
|
+
if (!virtualItemSizeProp && sizeAlreadyKnown && rowsHeldOffScreen && ref.current) {
|
|
68487
68782
|
const listEl = ref.current.querySelector(".navi_list");
|
|
68488
68783
|
const measure = listEl ? measureItemSize(listEl, horizontal) : null;
|
|
68489
68784
|
if (measure) {
|
|
@@ -68648,12 +68943,15 @@ const Fallback = ({
|
|
|
68648
68943
|
children: fallback
|
|
68649
68944
|
});
|
|
68650
68945
|
};
|
|
68946
|
+
// Reads the row size itself: it is what the size is for, and a run holding
|
|
68947
|
+
// every row it draws must not be redrawn — every row of it — because the size
|
|
68948
|
+
// settled after the first commit.
|
|
68651
68949
|
const VirtualFiller = ({
|
|
68652
68950
|
edge,
|
|
68653
|
-
itemCount
|
|
68654
|
-
virtualItemSize
|
|
68951
|
+
itemCount
|
|
68655
68952
|
}) => {
|
|
68656
|
-
const
|
|
68953
|
+
const virtual = useContext(ListVirtualContext);
|
|
68954
|
+
const sizeToFill = itemCount * virtual.virtualItemSizeSignal.value;
|
|
68657
68955
|
if (!sizeToFill) {
|
|
68658
68956
|
return null;
|
|
68659
68957
|
}
|
|
@@ -69707,6 +70005,21 @@ const ListItems = ({
|
|
|
69707
70005
|
const virtual = useContext(ListVirtualContext);
|
|
69708
70006
|
const slotId = useContext(ListSlotContext);
|
|
69709
70007
|
const renderWindow = useContext(RenderWindowContext);
|
|
70008
|
+
// The vnode drawn for a row, kept by item: a run rendering again (its window
|
|
70009
|
+
// moving, its first paint's budget giving way to the full one) hands preact
|
|
70010
|
+
// the same vnode for a row that has not changed, and preact leaves that
|
|
70011
|
+
// row's whole subtree alone. Only for a `renderItem` that is the same
|
|
70012
|
+
// function as last time — a new one may close over new state — and for a
|
|
70013
|
+
// row at the same index, in the same refreshing state: everything the
|
|
70014
|
+
// function is given.
|
|
70015
|
+
const rowVnodesRef = useRef(null);
|
|
70016
|
+
if (!rowVnodesRef.current || rowVnodesRef.current.renderItem !== renderItem) {
|
|
70017
|
+
rowVnodesRef.current = {
|
|
70018
|
+
renderItem,
|
|
70019
|
+
byItem: new Map()
|
|
70020
|
+
};
|
|
70021
|
+
}
|
|
70022
|
+
const rowVnodesByItem = rowVnodesRef.current.byItem;
|
|
69710
70023
|
const separator = useContext(SeparatorContext);
|
|
69711
70024
|
const store = useItemStore({
|
|
69712
70025
|
items,
|
|
@@ -69717,16 +70030,26 @@ const ListItems = ({
|
|
|
69717
70030
|
});
|
|
69718
70031
|
const renderRowSkeleton = renderSkeleton === undefined ? virtual.renderSkeleton : renderSkeleton;
|
|
69719
70032
|
// A row on its way takes the room the list reserves for it: anything else
|
|
69720
|
-
// and the rows drawn stop short of where the scroll says they are.
|
|
69721
|
-
|
|
69722
|
-
|
|
69723
|
-
|
|
69724
|
-
|
|
69725
|
-
|
|
69726
|
-
|
|
69727
|
-
skeletonRow
|
|
70033
|
+
// and the rows drawn stop short of where the scroll says they are. Read
|
|
70034
|
+
// where a row is actually missing, and not before: the size settles after
|
|
70035
|
+
// the first commit, and a run holding every row it draws would otherwise be
|
|
70036
|
+
// redrawn whole by a number it has no use for.
|
|
70037
|
+
let skeletonRow = null;
|
|
70038
|
+
const getSkeletonRow = () => {
|
|
70039
|
+
if (skeletonRow) {
|
|
70040
|
+
return skeletonRow;
|
|
70041
|
+
}
|
|
70042
|
+
skeletonRow = {};
|
|
70043
|
+
const virtualItemSize = virtual.virtualItemSizeSignal.value;
|
|
70044
|
+
if (virtualItemSize) {
|
|
70045
|
+
if (virtual.horizontal) {
|
|
70046
|
+
skeletonRow.rowMinWidth = `${virtualItemSize}px`;
|
|
70047
|
+
} else {
|
|
70048
|
+
skeletonRow.rowMinHeight = `${virtualItemSize}px`;
|
|
70049
|
+
}
|
|
69728
70050
|
}
|
|
69729
|
-
|
|
70051
|
+
return skeletonRow;
|
|
70052
|
+
};
|
|
69730
70053
|
const runStart = virtual.take(ownerId, store.rowCount, slotId);
|
|
69731
70054
|
const runEnd = runStart + store.rowCount;
|
|
69732
70055
|
// The two ways to count the same row. The list numbers its rows from its own
|
|
@@ -69895,8 +70218,7 @@ const ListItems = ({
|
|
|
69895
70218
|
if (windowFrom > runStart) {
|
|
69896
70219
|
rows.push(jsx(VirtualFiller, {
|
|
69897
70220
|
edge: "before",
|
|
69898
|
-
itemCount: windowFrom - runStart
|
|
69899
|
-
virtualItemSize: virtualItemSize
|
|
70221
|
+
itemCount: windowFrom - runStart
|
|
69900
70222
|
}, "navi-list-filler-before"));
|
|
69901
70223
|
}
|
|
69902
70224
|
const renderItemState = {
|
|
@@ -69910,7 +70232,7 @@ const ListItems = ({
|
|
|
69910
70232
|
rows.push(jsx("li", {
|
|
69911
70233
|
className: "navi_list_failed_rows",
|
|
69912
70234
|
style: {
|
|
69913
|
-
"--size-to-fill": `${failedRowCount *
|
|
70235
|
+
"--size-to-fill": `${failedRowCount * virtual.virtualItemSizeSignal.value}px`
|
|
69914
70236
|
},
|
|
69915
70237
|
children: renderError ? renderError({
|
|
69916
70238
|
error: store.failure.error,
|
|
@@ -69929,7 +70251,17 @@ const ListItems = ({
|
|
|
69929
70251
|
const key = item === undefined ? `${ownerId}_skeleton_${rowIndex}` : idOf(item, rowIndex);
|
|
69930
70252
|
let rowVnode;
|
|
69931
70253
|
if (item !== undefined) {
|
|
69932
|
-
|
|
70254
|
+
const rowVnodeKept = rowVnodesByItem.get(item);
|
|
70255
|
+
if (rowVnodeKept && rowVnodeKept.rowIndex === rowIndex && rowVnodeKept.refreshing === renderItemState.refreshing) {
|
|
70256
|
+
rowVnode = rowVnodeKept.vnode;
|
|
70257
|
+
} else {
|
|
70258
|
+
rowVnode = renderItem(item, rowIndex, renderItemState);
|
|
70259
|
+
rowVnodesByItem.set(item, {
|
|
70260
|
+
vnode: rowVnode,
|
|
70261
|
+
rowIndex,
|
|
70262
|
+
refreshing: renderItemState.refreshing
|
|
70263
|
+
});
|
|
70264
|
+
}
|
|
69933
70265
|
} else if (renderRowSkeleton === false) {
|
|
69934
70266
|
// The row must still take its room: without it the rows below would
|
|
69935
70267
|
// climb up and slide back down as the answer arrives.
|
|
@@ -69960,7 +70292,7 @@ const ListItems = ({
|
|
|
69960
70292
|
value: item === undefined ? {
|
|
69961
70293
|
id: key,
|
|
69962
70294
|
index: rowIndex,
|
|
69963
|
-
...
|
|
70295
|
+
...getSkeletonRow()
|
|
69964
70296
|
} : {
|
|
69965
70297
|
id: key,
|
|
69966
70298
|
index: rowIndex,
|
|
@@ -69975,8 +70307,7 @@ const ListItems = ({
|
|
|
69975
70307
|
if (runEnd > windowTo) {
|
|
69976
70308
|
rows.push(jsx(VirtualFiller, {
|
|
69977
70309
|
edge: "after",
|
|
69978
|
-
itemCount: runEnd - windowTo
|
|
69979
|
-
virtualItemSize: virtualItemSize
|
|
70310
|
+
itemCount: runEnd - windowTo
|
|
69980
70311
|
}, "navi-list-filler-after"));
|
|
69981
70312
|
}
|
|
69982
70313
|
return rows;
|
|
@@ -70742,6 +71073,25 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
70742
71073
|
* starts. Keep it to come back to it
|
|
70743
71074
|
* later through `scrolled`/`defaultScrolled` — an index would not do, since
|
|
70744
71075
|
* rows get inserted while a list is being read.
|
|
71076
|
+
* @param {number|{initial: number, after: number}} [props.renderBudget=100]
|
|
71077
|
+
* How many rows of a `<List.Items>` run are in the DOM at once: the render
|
|
71078
|
+
* window, which slides as the user scrolls while fillers hold the room of
|
|
71079
|
+
* the rows outside it. Rows declared one by one as `<List.Item>` children
|
|
71080
|
+
* are all drawn, whatever this says — a list with more than a few dozen rows
|
|
71081
|
+
* gives them to a run (see docs/scroll.md, "Many rows"). Below 30 the list
|
|
71082
|
+
* warns: a window shorter than a tall screen shows blank fillers.
|
|
71083
|
+
*
|
|
71084
|
+
* `{ initial, after }` for a list drawn in the click that opens it (a popup):
|
|
71085
|
+
* `initial` rows in the commit the browser paints first — about what a phone
|
|
71086
|
+
* screen shows — and `after` from the paint on, where the floor of 30
|
|
71087
|
+
* applies. The runs ask their source for `after` rows from the start, so the
|
|
71088
|
+
* first picture costs no second request.
|
|
71089
|
+
* @param {number} [props.virtualItemSize]
|
|
71090
|
+
* The size of one row along the scroll axis, in px, when every row has the
|
|
71091
|
+
* same: what the fillers are sized with and what a scroll position is
|
|
71092
|
+
* estimated from. Left out, the list measures its rows — once when it
|
|
71093
|
+
* mounts, again when a popup around it opens, and after each commit while
|
|
71094
|
+
* rows are held off screen. Given, it never measures.
|
|
70745
71095
|
* @param {"self"|"parent"|"document"|Element|{current: Element}} [props.scroller="self"]
|
|
70746
71096
|
* Which box scrolls. `"self"` gives the list a scroll box of its own;
|
|
70747
71097
|
* `"parent"` makes it virtualize against the scrollable ancestor it lives in
|
|
@@ -72120,6 +72470,7 @@ const css$t = /* css */`@layer navi {
|
|
|
72120
72470
|
--picker-icon-color-disabled: var(--picker-icon-color-readonly);
|
|
72121
72471
|
--picker-align-x-default: flex-start;
|
|
72122
72472
|
--picker-align-y-default: center;
|
|
72473
|
+
--picker-text-align-default: initial;
|
|
72123
72474
|
}
|
|
72124
72475
|
}
|
|
72125
72476
|
|
|
@@ -72258,7 +72609,7 @@ const css$t = /* css */`@layer navi {
|
|
|
72258
72609
|
}
|
|
72259
72610
|
|
|
72260
72611
|
&[navi-single-line] {
|
|
72261
|
-
& .navi_picker_right_slot {
|
|
72612
|
+
& > .navi_picker_box > .navi_picker_right_slot {
|
|
72262
72613
|
align-self: var(--x-picker-align-y);
|
|
72263
72614
|
}
|
|
72264
72615
|
}
|
|
@@ -72295,7 +72646,7 @@ const css$t = /* css */`@layer navi {
|
|
|
72295
72646
|
}
|
|
72296
72647
|
|
|
72297
72648
|
&[navi-ui-custom] {
|
|
72298
|
-
& .navi_picker_input {
|
|
72649
|
+
& > .navi_picker_box > .navi_picker_input {
|
|
72299
72650
|
top: calc(-1 * (var(--picker-border-width) + var(--x-picker-press-padding-top)));
|
|
72300
72651
|
right: calc(-1 *
|
|
72301
72652
|
(var(--picker-border-width) + var(--x-picker-press-padding-right)));
|
|
@@ -72358,10 +72709,10 @@ const css$t = /* css */`@layer navi {
|
|
|
72358
72709
|
--x-picker-cursor: pointer;
|
|
72359
72710
|
}
|
|
72360
72711
|
|
|
72361
|
-
&[data-focus-within]:has(.navi_picker_input[data-focus-visible]), &[data-focus-visible] {
|
|
72712
|
+
&[data-focus-within]:has( > .navi_picker_box > .navi_picker_input[data-focus-visible]), &[data-focus-visible] {
|
|
72362
72713
|
--x-picker-border-color: transparent;
|
|
72363
72714
|
|
|
72364
|
-
& .navi_picker_box {
|
|
72715
|
+
& > .navi_picker_box {
|
|
72365
72716
|
outline-style: solid;
|
|
72366
72717
|
}
|
|
72367
72718
|
}
|
|
@@ -72419,7 +72770,7 @@ const css$t = /* css */`@layer navi {
|
|
|
72419
72770
|
--picker-background-color-disabled: var(--picker-background-color);
|
|
72420
72771
|
--picker-icon-color: currentColor;
|
|
72421
72772
|
|
|
72422
|
-
& .navi_picker_box {
|
|
72773
|
+
& > .navi_picker_box {
|
|
72423
72774
|
z-index: -1;
|
|
72424
72775
|
position: absolute;
|
|
72425
72776
|
inset: 0;
|
|
@@ -72444,11 +72795,11 @@ const css$t = /* css */`@layer navi {
|
|
|
72444
72795
|
font-family: inherit;
|
|
72445
72796
|
line-height: inherit;
|
|
72446
72797
|
|
|
72447
|
-
& .navi_picker_box {
|
|
72798
|
+
& > .navi_picker_box {
|
|
72448
72799
|
min-height: 0;
|
|
72449
72800
|
}
|
|
72450
72801
|
|
|
72451
|
-
& .navi_picker_value[data-picker-facade] {
|
|
72802
|
+
& > .navi_picker_box > .navi_picker_value[data-picker-facade] {
|
|
72452
72803
|
align-items: var(--x-picker-align-y);
|
|
72453
72804
|
display: flex;
|
|
72454
72805
|
}
|
|
@@ -72499,11 +72850,11 @@ const css$t = /* css */`@layer navi {
|
|
|
72499
72850
|
--picker-padding-y-default: 0;
|
|
72500
72851
|
--picker-align-x-default: center;
|
|
72501
72852
|
|
|
72502
|
-
& .navi_picker_box {
|
|
72853
|
+
& > .navi_picker_box {
|
|
72503
72854
|
min-height: 0;
|
|
72504
72855
|
}
|
|
72505
72856
|
|
|
72506
|
-
& .navi_picker_value {
|
|
72857
|
+
& > .navi_picker_box > .navi_picker_value {
|
|
72507
72858
|
flex-grow: 0;
|
|
72508
72859
|
align-items: center;
|
|
72509
72860
|
display: inline-flex;
|