@jsenv/navi 0.29.316 → 0.29.318
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 +620 -244
- package/dist/dev/jsenv_navi.js.map +18 -14
- package/dist/jsenv_navi.js +514 -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.
|
|
@@ -64329,25 +64504,44 @@ const PickerCalloutPopup = ({
|
|
|
64329
64504
|
onnavi_request_confirm?.(e);
|
|
64330
64505
|
};
|
|
64331
64506
|
calloutElement.addEventListener("navi_request_confirm", forwardConfirm);
|
|
64507
|
+
// Said once the content is in the shown callout: what the content defers
|
|
64508
|
+
// until it is displayed (useDisplayedLayoutEffect, watching this attribute)
|
|
64509
|
+
// then measures it where it is drawn. Same call stack as the open, like
|
|
64510
|
+
// Popover's own — see observeAncestorOpenState in @jsenv/dom.
|
|
64511
|
+
dock.setAttribute("aria-expanded", "true");
|
|
64332
64512
|
return closeEvent => {
|
|
64333
64513
|
calloutElement.removeEventListener("navi_request_confirm", forwardConfirm);
|
|
64334
64514
|
calloutManager.removeOpenToken(PICKER_CALLOUT_CONTENT_TOKEN, closeEvent);
|
|
64515
|
+
dock.appendChild(host);
|
|
64516
|
+
dock.setAttribute("aria-expanded", "false");
|
|
64335
64517
|
};
|
|
64336
64518
|
};
|
|
64337
64519
|
return (
|
|
64338
64520
|
// What the picker addresses (aria-controls, the request events it
|
|
64339
64521
|
// forwards); the callout itself lives where the callout manager puts it.
|
|
64522
|
+
// aria-expanded here, on the dock rather than on the content element: the
|
|
64523
|
+
// openable ancestor the content finds at mount (the only time it looks),
|
|
64524
|
+
// and one that is no ancestor of the content once the callout holds it —
|
|
64525
|
+
// so a --navi-close said in there still resolves to the callout element,
|
|
64526
|
+
// the closest [aria-expanded] from the button. The open effect keeps it
|
|
64527
|
+
// current; preact leaves it alone, the prop never changes.
|
|
64340
64528
|
jsx(Box, {
|
|
64341
64529
|
as: "span",
|
|
64342
64530
|
ref: ref,
|
|
64343
64531
|
id: id,
|
|
64532
|
+
className: "navi_picker_callout_dock",
|
|
64533
|
+
"aria-expanded": "false",
|
|
64344
64534
|
style: {
|
|
64345
64535
|
display: "contents"
|
|
64346
64536
|
},
|
|
64347
64537
|
onnavi_request_open: onnavi_request_open,
|
|
64348
64538
|
onnavi_request_close: onnavi_request_close,
|
|
64349
64539
|
onnavi_request_confirm: onnavi_request_confirm,
|
|
64350
|
-
children:
|
|
64540
|
+
children: jsx("div", {
|
|
64541
|
+
ref: hostRef,
|
|
64542
|
+
"data-picker-content": "",
|
|
64543
|
+
children: children
|
|
64544
|
+
})
|
|
64351
64545
|
})
|
|
64352
64546
|
);
|
|
64353
64547
|
};
|
|
@@ -64656,6 +64850,43 @@ document.addEventListener(
|
|
|
64656
64850
|
{ capture: true, passive: true },
|
|
64657
64851
|
);
|
|
64658
64852
|
|
|
64853
|
+
/**
|
|
64854
|
+
* Calls back once the browser has painted what is committed now.
|
|
64855
|
+
*
|
|
64856
|
+
* The callbacks of the next frame run before that frame paints, so the call is
|
|
64857
|
+
* made from a task queued from inside one — the same way preact schedules
|
|
64858
|
+
* useEffect. Unlike an effect, nothing runs this early: preact flushes a
|
|
64859
|
+
* component's pending effects as soon as that component renders again, and a
|
|
64860
|
+
* re-render before the paint is exactly what a caller waiting for the paint
|
|
64861
|
+
* has to survive. A frame not coming at all (a background tab) still answers,
|
|
64862
|
+
* late, through the timeout.
|
|
64863
|
+
*
|
|
64864
|
+
* @returns {() => void} cancel
|
|
64865
|
+
*/
|
|
64866
|
+
const FRAME_TIMEOUT_MS = 100;
|
|
64867
|
+
|
|
64868
|
+
const afterPaint = (callback) => {
|
|
64869
|
+
let called = false;
|
|
64870
|
+
let timeoutId;
|
|
64871
|
+
let frameId;
|
|
64872
|
+
const onFrame = () => {
|
|
64873
|
+
if (called) {
|
|
64874
|
+
return;
|
|
64875
|
+
}
|
|
64876
|
+
called = true;
|
|
64877
|
+
clearTimeout(timeoutId);
|
|
64878
|
+
cancelAnimationFrame(frameId);
|
|
64879
|
+
timeoutId = setTimeout(callback, 0);
|
|
64880
|
+
};
|
|
64881
|
+
timeoutId = setTimeout(onFrame, FRAME_TIMEOUT_MS);
|
|
64882
|
+
frameId = requestAnimationFrame(onFrame);
|
|
64883
|
+
return () => {
|
|
64884
|
+
called = true;
|
|
64885
|
+
clearTimeout(timeoutId);
|
|
64886
|
+
cancelAnimationFrame(frameId);
|
|
64887
|
+
};
|
|
64888
|
+
};
|
|
64889
|
+
|
|
64659
64890
|
const LoadingDotsSvg = () => {
|
|
64660
64891
|
return jsxs("svg", {
|
|
64661
64892
|
viewBox: "0 0 200 200",
|
|
@@ -66615,6 +66846,32 @@ const css$x = /* css */`@layer navi {
|
|
|
66615
66846
|
middle of the padding. On the scroll box the scrollbar stays against the
|
|
66616
66847
|
border and the padding is what separates the rows from it. */
|
|
66617
66848
|
const LIST_PADDING_PROP_SET = new Set(["padding", "paddingX", "paddingY", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft"]);
|
|
66849
|
+
|
|
66850
|
+
// Accepts a string too (renderBudget="50" from an HTML attribute): the
|
|
66851
|
+
// arithmetic on the budget (renderBudget / 2, start + renderBudget) would
|
|
66852
|
+
// silently misbehave on a raw string ("+" concatenates).
|
|
66853
|
+
const toRenderBudgetNumber = value => {
|
|
66854
|
+
if (typeof value === "string") {
|
|
66855
|
+
const parsed = Number(value);
|
|
66856
|
+
return Number.isFinite(parsed) ? parsed : RENDER_BUDGET_DEFAULT;
|
|
66857
|
+
}
|
|
66858
|
+
if (typeof value === "number") {
|
|
66859
|
+
return value;
|
|
66860
|
+
}
|
|
66861
|
+
return RENDER_BUDGET_DEFAULT;
|
|
66862
|
+
};
|
|
66863
|
+
const resolveRenderBudget = renderBudget => {
|
|
66864
|
+
if (renderBudget && typeof renderBudget === "object") {
|
|
66865
|
+
return {
|
|
66866
|
+
initial: renderBudget.initial === undefined ? undefined : toRenderBudgetNumber(renderBudget.initial),
|
|
66867
|
+
after: toRenderBudgetNumber(renderBudget.after)
|
|
66868
|
+
};
|
|
66869
|
+
}
|
|
66870
|
+
return {
|
|
66871
|
+
initial: undefined,
|
|
66872
|
+
after: toRenderBudgetNumber(renderBudget)
|
|
66873
|
+
};
|
|
66874
|
+
};
|
|
66618
66875
|
const ListUI = props => {
|
|
66619
66876
|
import.meta.css = [css$x, "@jsenv/navi/src/control/list/list.jsx"];
|
|
66620
66877
|
const {
|
|
@@ -66663,18 +66920,34 @@ const ListUI = props => {
|
|
|
66663
66920
|
delete rest[name];
|
|
66664
66921
|
}
|
|
66665
66922
|
}
|
|
66666
|
-
//
|
|
66667
|
-
//
|
|
66668
|
-
//
|
|
66669
|
-
//
|
|
66670
|
-
|
|
66671
|
-
|
|
66672
|
-
|
|
66673
|
-
|
|
66674
|
-
|
|
66923
|
+
// `renderBudget` is a number, or `{ initial, after }`: the window of the
|
|
66924
|
+
// first commit, until the browser has painted it, and the window from then
|
|
66925
|
+
// on. A list opening inside a popup draws in the click that opens it, and
|
|
66926
|
+
// the browser paints nothing before that render ends: rows below the fold
|
|
66927
|
+
// cost the same as rows on screen there, and are drawn to be seen one frame
|
|
66928
|
+
// later just as well. `after` takes over after the paint (see afterPaint for
|
|
66929
|
+
// why not an effect), and the floor of 30 is its: it protects the scrolling
|
|
66930
|
+
// window, not a picture nobody has seen yet.
|
|
66931
|
+
const {
|
|
66932
|
+
initial: initialRenderBudget,
|
|
66933
|
+
after: renderBudgetAfterPaint
|
|
66934
|
+
} = resolveRenderBudget(renderBudgetProp);
|
|
66935
|
+
let renderBudget = renderBudgetAfterPaint;
|
|
66675
66936
|
if (renderBudget < 30 && !renderBudgetSkipCheck) {
|
|
66676
66937
|
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
66938
|
}
|
|
66939
|
+
const [firstPaintPending, setFirstPaintPending] = useState(initialRenderBudget !== undefined);
|
|
66940
|
+
useLayoutEffect(() => {
|
|
66941
|
+
if (!firstPaintPending) {
|
|
66942
|
+
return undefined;
|
|
66943
|
+
}
|
|
66944
|
+
return afterPaint(() => {
|
|
66945
|
+
setFirstPaintPending(false);
|
|
66946
|
+
});
|
|
66947
|
+
}, []);
|
|
66948
|
+
if (firstPaintPending) {
|
|
66949
|
+
renderBudget = initialRenderBudget;
|
|
66950
|
+
}
|
|
66678
66951
|
|
|
66679
66952
|
// lockSize: capture the container's dimensions on first render so filtering
|
|
66680
66953
|
// cannot collapse the layout. Measurement happens on the initial (unfiltered)
|
|
@@ -66711,7 +66984,10 @@ const ListUI = props => {
|
|
|
66711
66984
|
onListVisibleItemsChange?.(tracker.visibleItemsSignal.peek());
|
|
66712
66985
|
}
|
|
66713
66986
|
});
|
|
66714
|
-
|
|
66987
|
+
// What the runs ask for and stand for: the steady budget, whatever the
|
|
66988
|
+
// window of the first paint draws — a run asking for the rows of the first
|
|
66989
|
+
// picture and then for the rest is two round trips for one opening.
|
|
66990
|
+
virtual.renderBudget = renderBudgetAfterPaint;
|
|
66715
66991
|
virtual.scrolled = scrolled ?? defaultScrolled;
|
|
66716
66992
|
const {
|
|
66717
66993
|
virtualItemSizeSignal,
|
|
@@ -67060,7 +67336,10 @@ const useListScrollSync = ({
|
|
|
67060
67336
|
horizontal
|
|
67061
67337
|
}) => {
|
|
67062
67338
|
const debugScroll = useDebugScroll();
|
|
67063
|
-
const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal
|
|
67339
|
+
const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal, {
|
|
67340
|
+
virtual,
|
|
67341
|
+
renderBudget
|
|
67342
|
+
});
|
|
67064
67343
|
const getScroller = () => getScrollerEl(ref.current, scroller, horizontal);
|
|
67065
67344
|
const getListEl = () => ref.current.querySelector(".navi_list");
|
|
67066
67345
|
// Which box scrolls is measured (see getScrollerEl), so the answer holds
|
|
@@ -67116,6 +67395,40 @@ const useListScrollSync = ({
|
|
|
67116
67395
|
});
|
|
67117
67396
|
const renderWindowRef = useRef(null);
|
|
67118
67397
|
renderWindowRef.current = renderWindow;
|
|
67398
|
+
// The window is as wide as the budget says, whatever the state holds: a
|
|
67399
|
+
// budget that changes (the first paint's giving way to the scrolling one)
|
|
67400
|
+
// re-frames the window where it stands, in the render itself, the way
|
|
67401
|
+
// holdWindow below moves it — nothing else would, the scroll listener only
|
|
67402
|
+
// moves a window the user is about to leave. Derived on every render and
|
|
67403
|
+
// not written once: the state keeps the width it had, and the next render
|
|
67404
|
+
// copies it back into the ref above. The same object is handed out for as
|
|
67405
|
+
// long as the numbers hold, so the rows are not told about a window that
|
|
67406
|
+
// did not move.
|
|
67407
|
+
const framedWindowRef = useRef(null);
|
|
67408
|
+
{
|
|
67409
|
+
const {
|
|
67410
|
+
start,
|
|
67411
|
+
end
|
|
67412
|
+
} = renderWindowRef.current;
|
|
67413
|
+
const total = virtual.totalSignal.peek();
|
|
67414
|
+
let framedStart = start;
|
|
67415
|
+
let framedEnd = start + renderBudget;
|
|
67416
|
+
if (total > 0 && framedEnd > total) {
|
|
67417
|
+
framedEnd = total;
|
|
67418
|
+
framedStart = total - renderBudget < 0 ? 0 : total - renderBudget;
|
|
67419
|
+
}
|
|
67420
|
+
if (framedStart !== start || framedEnd !== end) {
|
|
67421
|
+
const framed = framedWindowRef.current;
|
|
67422
|
+
if (framed && framed.start === framedStart && framed.end === framedEnd) {
|
|
67423
|
+
renderWindowRef.current = framed;
|
|
67424
|
+
} else {
|
|
67425
|
+
framedWindowRef.current = renderWindowRef.current = {
|
|
67426
|
+
start: framedStart,
|
|
67427
|
+
end: framedEnd
|
|
67428
|
+
};
|
|
67429
|
+
}
|
|
67430
|
+
}
|
|
67431
|
+
}
|
|
67119
67432
|
const updateRenderWindow = (newStart, newEnd, reason) => {
|
|
67120
67433
|
const {
|
|
67121
67434
|
start,
|
|
@@ -68429,7 +68742,10 @@ const measureItemSize = (listEl, horizontal) => {
|
|
|
68429
68742
|
fromSkeletons
|
|
68430
68743
|
};
|
|
68431
68744
|
};
|
|
68432
|
-
const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal
|
|
68745
|
+
const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal, {
|
|
68746
|
+
virtual,
|
|
68747
|
+
renderBudget
|
|
68748
|
+
}) => {
|
|
68433
68749
|
const virtualSizeSignalRef = useRef(null);
|
|
68434
68750
|
if (!virtualSizeSignalRef.current) {
|
|
68435
68751
|
virtualSizeSignalRef.current = signal(virtualItemSizeProp);
|
|
@@ -68482,8 +68798,12 @@ const useVirtualItemSizeSignal = (ref, virtualItemSizeProp = 0, horizontal) => {
|
|
|
68482
68798
|
// as the rows it was measured on. Written from a layout effect it would
|
|
68483
68799
|
// resize them one commit later — after the scroll anchoring of that commit
|
|
68484
68800
|
// had already run, which is exactly the jump anchoring exists to prevent.
|
|
68801
|
+
// And only while some rows are held off screen: the fillers are what the
|
|
68802
|
+
// size is for, and a list drawing every row it has would pay a layout on
|
|
68803
|
+
// each of its renders for a number nothing reads.
|
|
68485
68804
|
const sizeAlreadyKnown = virtualSizeSignal.peek() !== 0;
|
|
68486
|
-
|
|
68805
|
+
const rowsHeldOffScreen = virtual.totalSignal.peek() > renderBudget;
|
|
68806
|
+
if (!virtualItemSizeProp && sizeAlreadyKnown && rowsHeldOffScreen && ref.current) {
|
|
68487
68807
|
const listEl = ref.current.querySelector(".navi_list");
|
|
68488
68808
|
const measure = listEl ? measureItemSize(listEl, horizontal) : null;
|
|
68489
68809
|
if (measure) {
|
|
@@ -68648,12 +68968,15 @@ const Fallback = ({
|
|
|
68648
68968
|
children: fallback
|
|
68649
68969
|
});
|
|
68650
68970
|
};
|
|
68971
|
+
// Reads the row size itself: it is what the size is for, and a run holding
|
|
68972
|
+
// every row it draws must not be redrawn — every row of it — because the size
|
|
68973
|
+
// settled after the first commit.
|
|
68651
68974
|
const VirtualFiller = ({
|
|
68652
68975
|
edge,
|
|
68653
|
-
itemCount
|
|
68654
|
-
virtualItemSize
|
|
68976
|
+
itemCount
|
|
68655
68977
|
}) => {
|
|
68656
|
-
const
|
|
68978
|
+
const virtual = useContext(ListVirtualContext);
|
|
68979
|
+
const sizeToFill = itemCount * virtual.virtualItemSizeSignal.value;
|
|
68657
68980
|
if (!sizeToFill) {
|
|
68658
68981
|
return null;
|
|
68659
68982
|
}
|
|
@@ -69707,6 +70030,21 @@ const ListItems = ({
|
|
|
69707
70030
|
const virtual = useContext(ListVirtualContext);
|
|
69708
70031
|
const slotId = useContext(ListSlotContext);
|
|
69709
70032
|
const renderWindow = useContext(RenderWindowContext);
|
|
70033
|
+
// The vnode drawn for a row, kept by item: a run rendering again (its window
|
|
70034
|
+
// moving, its first paint's budget giving way to the full one) hands preact
|
|
70035
|
+
// the same vnode for a row that has not changed, and preact leaves that
|
|
70036
|
+
// row's whole subtree alone. Only for a `renderItem` that is the same
|
|
70037
|
+
// function as last time — a new one may close over new state — and for a
|
|
70038
|
+
// row at the same index, in the same refreshing state: everything the
|
|
70039
|
+
// function is given.
|
|
70040
|
+
const rowVnodesRef = useRef(null);
|
|
70041
|
+
if (!rowVnodesRef.current || rowVnodesRef.current.renderItem !== renderItem) {
|
|
70042
|
+
rowVnodesRef.current = {
|
|
70043
|
+
renderItem,
|
|
70044
|
+
byItem: new Map()
|
|
70045
|
+
};
|
|
70046
|
+
}
|
|
70047
|
+
const rowVnodesByItem = rowVnodesRef.current.byItem;
|
|
69710
70048
|
const separator = useContext(SeparatorContext);
|
|
69711
70049
|
const store = useItemStore({
|
|
69712
70050
|
items,
|
|
@@ -69717,16 +70055,26 @@ const ListItems = ({
|
|
|
69717
70055
|
});
|
|
69718
70056
|
const renderRowSkeleton = renderSkeleton === undefined ? virtual.renderSkeleton : renderSkeleton;
|
|
69719
70057
|
// 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
|
|
70058
|
+
// and the rows drawn stop short of where the scroll says they are. Read
|
|
70059
|
+
// where a row is actually missing, and not before: the size settles after
|
|
70060
|
+
// the first commit, and a run holding every row it draws would otherwise be
|
|
70061
|
+
// redrawn whole by a number it has no use for.
|
|
70062
|
+
let skeletonRow = null;
|
|
70063
|
+
const getSkeletonRow = () => {
|
|
70064
|
+
if (skeletonRow) {
|
|
70065
|
+
return skeletonRow;
|
|
70066
|
+
}
|
|
70067
|
+
skeletonRow = {};
|
|
70068
|
+
const virtualItemSize = virtual.virtualItemSizeSignal.value;
|
|
70069
|
+
if (virtualItemSize) {
|
|
70070
|
+
if (virtual.horizontal) {
|
|
70071
|
+
skeletonRow.rowMinWidth = `${virtualItemSize}px`;
|
|
70072
|
+
} else {
|
|
70073
|
+
skeletonRow.rowMinHeight = `${virtualItemSize}px`;
|
|
70074
|
+
}
|
|
69728
70075
|
}
|
|
69729
|
-
|
|
70076
|
+
return skeletonRow;
|
|
70077
|
+
};
|
|
69730
70078
|
const runStart = virtual.take(ownerId, store.rowCount, slotId);
|
|
69731
70079
|
const runEnd = runStart + store.rowCount;
|
|
69732
70080
|
// The two ways to count the same row. The list numbers its rows from its own
|
|
@@ -69895,8 +70243,7 @@ const ListItems = ({
|
|
|
69895
70243
|
if (windowFrom > runStart) {
|
|
69896
70244
|
rows.push(jsx(VirtualFiller, {
|
|
69897
70245
|
edge: "before",
|
|
69898
|
-
itemCount: windowFrom - runStart
|
|
69899
|
-
virtualItemSize: virtualItemSize
|
|
70246
|
+
itemCount: windowFrom - runStart
|
|
69900
70247
|
}, "navi-list-filler-before"));
|
|
69901
70248
|
}
|
|
69902
70249
|
const renderItemState = {
|
|
@@ -69910,7 +70257,7 @@ const ListItems = ({
|
|
|
69910
70257
|
rows.push(jsx("li", {
|
|
69911
70258
|
className: "navi_list_failed_rows",
|
|
69912
70259
|
style: {
|
|
69913
|
-
"--size-to-fill": `${failedRowCount *
|
|
70260
|
+
"--size-to-fill": `${failedRowCount * virtual.virtualItemSizeSignal.value}px`
|
|
69914
70261
|
},
|
|
69915
70262
|
children: renderError ? renderError({
|
|
69916
70263
|
error: store.failure.error,
|
|
@@ -69929,7 +70276,17 @@ const ListItems = ({
|
|
|
69929
70276
|
const key = item === undefined ? `${ownerId}_skeleton_${rowIndex}` : idOf(item, rowIndex);
|
|
69930
70277
|
let rowVnode;
|
|
69931
70278
|
if (item !== undefined) {
|
|
69932
|
-
|
|
70279
|
+
const rowVnodeKept = rowVnodesByItem.get(item);
|
|
70280
|
+
if (rowVnodeKept && rowVnodeKept.rowIndex === rowIndex && rowVnodeKept.refreshing === renderItemState.refreshing) {
|
|
70281
|
+
rowVnode = rowVnodeKept.vnode;
|
|
70282
|
+
} else {
|
|
70283
|
+
rowVnode = renderItem(item, rowIndex, renderItemState);
|
|
70284
|
+
rowVnodesByItem.set(item, {
|
|
70285
|
+
vnode: rowVnode,
|
|
70286
|
+
rowIndex,
|
|
70287
|
+
refreshing: renderItemState.refreshing
|
|
70288
|
+
});
|
|
70289
|
+
}
|
|
69933
70290
|
} else if (renderRowSkeleton === false) {
|
|
69934
70291
|
// The row must still take its room: without it the rows below would
|
|
69935
70292
|
// climb up and slide back down as the answer arrives.
|
|
@@ -69960,7 +70317,7 @@ const ListItems = ({
|
|
|
69960
70317
|
value: item === undefined ? {
|
|
69961
70318
|
id: key,
|
|
69962
70319
|
index: rowIndex,
|
|
69963
|
-
...
|
|
70320
|
+
...getSkeletonRow()
|
|
69964
70321
|
} : {
|
|
69965
70322
|
id: key,
|
|
69966
70323
|
index: rowIndex,
|
|
@@ -69975,8 +70332,7 @@ const ListItems = ({
|
|
|
69975
70332
|
if (runEnd > windowTo) {
|
|
69976
70333
|
rows.push(jsx(VirtualFiller, {
|
|
69977
70334
|
edge: "after",
|
|
69978
|
-
itemCount: runEnd - windowTo
|
|
69979
|
-
virtualItemSize: virtualItemSize
|
|
70335
|
+
itemCount: runEnd - windowTo
|
|
69980
70336
|
}, "navi-list-filler-after"));
|
|
69981
70337
|
}
|
|
69982
70338
|
return rows;
|
|
@@ -70742,6 +71098,25 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
70742
71098
|
* starts. Keep it to come back to it
|
|
70743
71099
|
* later through `scrolled`/`defaultScrolled` — an index would not do, since
|
|
70744
71100
|
* rows get inserted while a list is being read.
|
|
71101
|
+
* @param {number|{initial: number, after: number}} [props.renderBudget=100]
|
|
71102
|
+
* How many rows of a `<List.Items>` run are in the DOM at once: the render
|
|
71103
|
+
* window, which slides as the user scrolls while fillers hold the room of
|
|
71104
|
+
* the rows outside it. Rows declared one by one as `<List.Item>` children
|
|
71105
|
+
* are all drawn, whatever this says — a list with more than a few dozen rows
|
|
71106
|
+
* gives them to a run (see docs/scroll.md, "Many rows"). Below 30 the list
|
|
71107
|
+
* warns: a window shorter than a tall screen shows blank fillers.
|
|
71108
|
+
*
|
|
71109
|
+
* `{ initial, after }` for a list drawn in the click that opens it (a popup):
|
|
71110
|
+
* `initial` rows in the commit the browser paints first — about what a phone
|
|
71111
|
+
* screen shows — and `after` from the paint on, where the floor of 30
|
|
71112
|
+
* applies. The runs ask their source for `after` rows from the start, so the
|
|
71113
|
+
* first picture costs no second request.
|
|
71114
|
+
* @param {number} [props.virtualItemSize]
|
|
71115
|
+
* The size of one row along the scroll axis, in px, when every row has the
|
|
71116
|
+
* same: what the fillers are sized with and what a scroll position is
|
|
71117
|
+
* estimated from. Left out, the list measures its rows — once when it
|
|
71118
|
+
* mounts, again when a popup around it opens, and after each commit while
|
|
71119
|
+
* rows are held off screen. Given, it never measures.
|
|
70745
71120
|
* @param {"self"|"parent"|"document"|Element|{current: Element}} [props.scroller="self"]
|
|
70746
71121
|
* Which box scrolls. `"self"` gives the list a scroll box of its own;
|
|
70747
71122
|
* `"parent"` makes it virtualize against the scrollable ancestor it lives in
|
|
@@ -72120,6 +72495,7 @@ const css$t = /* css */`@layer navi {
|
|
|
72120
72495
|
--picker-icon-color-disabled: var(--picker-icon-color-readonly);
|
|
72121
72496
|
--picker-align-x-default: flex-start;
|
|
72122
72497
|
--picker-align-y-default: center;
|
|
72498
|
+
--picker-text-align-default: initial;
|
|
72123
72499
|
}
|
|
72124
72500
|
}
|
|
72125
72501
|
|
|
@@ -72258,7 +72634,7 @@ const css$t = /* css */`@layer navi {
|
|
|
72258
72634
|
}
|
|
72259
72635
|
|
|
72260
72636
|
&[navi-single-line] {
|
|
72261
|
-
& .navi_picker_right_slot {
|
|
72637
|
+
& > .navi_picker_box > .navi_picker_right_slot {
|
|
72262
72638
|
align-self: var(--x-picker-align-y);
|
|
72263
72639
|
}
|
|
72264
72640
|
}
|
|
@@ -72295,7 +72671,7 @@ const css$t = /* css */`@layer navi {
|
|
|
72295
72671
|
}
|
|
72296
72672
|
|
|
72297
72673
|
&[navi-ui-custom] {
|
|
72298
|
-
& .navi_picker_input {
|
|
72674
|
+
& > .navi_picker_box > .navi_picker_input {
|
|
72299
72675
|
top: calc(-1 * (var(--picker-border-width) + var(--x-picker-press-padding-top)));
|
|
72300
72676
|
right: calc(-1 *
|
|
72301
72677
|
(var(--picker-border-width) + var(--x-picker-press-padding-right)));
|
|
@@ -72358,10 +72734,10 @@ const css$t = /* css */`@layer navi {
|
|
|
72358
72734
|
--x-picker-cursor: pointer;
|
|
72359
72735
|
}
|
|
72360
72736
|
|
|
72361
|
-
&[data-focus-within]:has(.navi_picker_input[data-focus-visible]), &[data-focus-visible] {
|
|
72737
|
+
&[data-focus-within]:has( > .navi_picker_box > .navi_picker_input[data-focus-visible]), &[data-focus-visible] {
|
|
72362
72738
|
--x-picker-border-color: transparent;
|
|
72363
72739
|
|
|
72364
|
-
& .navi_picker_box {
|
|
72740
|
+
& > .navi_picker_box {
|
|
72365
72741
|
outline-style: solid;
|
|
72366
72742
|
}
|
|
72367
72743
|
}
|
|
@@ -72419,7 +72795,7 @@ const css$t = /* css */`@layer navi {
|
|
|
72419
72795
|
--picker-background-color-disabled: var(--picker-background-color);
|
|
72420
72796
|
--picker-icon-color: currentColor;
|
|
72421
72797
|
|
|
72422
|
-
& .navi_picker_box {
|
|
72798
|
+
& > .navi_picker_box {
|
|
72423
72799
|
z-index: -1;
|
|
72424
72800
|
position: absolute;
|
|
72425
72801
|
inset: 0;
|
|
@@ -72444,11 +72820,11 @@ const css$t = /* css */`@layer navi {
|
|
|
72444
72820
|
font-family: inherit;
|
|
72445
72821
|
line-height: inherit;
|
|
72446
72822
|
|
|
72447
|
-
& .navi_picker_box {
|
|
72823
|
+
& > .navi_picker_box {
|
|
72448
72824
|
min-height: 0;
|
|
72449
72825
|
}
|
|
72450
72826
|
|
|
72451
|
-
& .navi_picker_value[data-picker-facade] {
|
|
72827
|
+
& > .navi_picker_box > .navi_picker_value[data-picker-facade] {
|
|
72452
72828
|
align-items: var(--x-picker-align-y);
|
|
72453
72829
|
display: flex;
|
|
72454
72830
|
}
|
|
@@ -72499,11 +72875,11 @@ const css$t = /* css */`@layer navi {
|
|
|
72499
72875
|
--picker-padding-y-default: 0;
|
|
72500
72876
|
--picker-align-x-default: center;
|
|
72501
72877
|
|
|
72502
|
-
& .navi_picker_box {
|
|
72878
|
+
& > .navi_picker_box {
|
|
72503
72879
|
min-height: 0;
|
|
72504
72880
|
}
|
|
72505
72881
|
|
|
72506
|
-
& .navi_picker_value {
|
|
72882
|
+
& > .navi_picker_box > .navi_picker_value {
|
|
72507
72883
|
flex-grow: 0;
|
|
72508
72884
|
align-items: center;
|
|
72509
72885
|
display: inline-flex;
|