@jsenv/navi 0.28.0 → 0.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +716 -277
- package/dist/jsenv_navi.js.map +72 -50
- package/dist/jsenv_navi_side_effects.js +1 -1
- package/dist/jsenv_navi_side_effects.js.map +1 -1
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* AI reading this file: read ../docs/AI_INSTRUCTIONS.md for context on
|
|
3
3
|
* using @jsenv/navi as intended.
|
|
4
4
|
*/
|
|
5
|
-
import { installImportMetaCssBuild, windowWidthSignal } from "./jsenv_navi_side_effects.js";
|
|
5
|
+
import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal } from "./jsenv_navi_side_effects.js";
|
|
6
6
|
import { isValidElement, createContext, h, toChildArray, render, Fragment, cloneElement } from "preact";
|
|
7
7
|
import { useErrorBoundary, useLayoutEffect, useEffect, useContext, useMemo, useRef, useState, useCallback, useId } from "preact/hooks";
|
|
8
8
|
import { jsxs, jsx, Fragment as Fragment$1 } from "preact/jsx-runtime";
|
|
@@ -6821,6 +6821,7 @@ const VISUAL_PROPS = {
|
|
|
6821
6821
|
scrollbarWidth: PASS_THROUGH,
|
|
6822
6822
|
scrollbarGutter: PASS_THROUGH,
|
|
6823
6823
|
scrollMarginBlock: PASS_THROUGH,
|
|
6824
|
+
scrollMarginInline: PASS_THROUGH,
|
|
6824
6825
|
scrollMargin: PASS_THROUGH,
|
|
6825
6826
|
};
|
|
6826
6827
|
const CONTENT_PROPS = {
|
|
@@ -7076,7 +7077,37 @@ const sizeSpacingKeySet = new Set(Object.keys(SIZE_MAP));
|
|
|
7076
7077
|
const isSizeSpacingKey = (key) => {
|
|
7077
7078
|
return sizeSpacingKeySet.has(key);
|
|
7078
7079
|
};
|
|
7080
|
+
// Viewport-relative units, resolved to pixels here because a JS consumer (popup
|
|
7081
|
+
// positioning) needs an actual number, not a length only CSS can evaluate.
|
|
7082
|
+
// "vvw"/"vvh" are navi's own: the *visual* viewport, which — unlike vw/dvw —
|
|
7083
|
+
// shrinks when the mobile virtual keyboard opens (see layout/responsive.js), so
|
|
7084
|
+
// they are what a popup meant to stay clear of the keyboard should use.
|
|
7085
|
+
const VIEWPORT_UNIT_SIGNALS = {
|
|
7086
|
+
vvw: visualViewportWidthSignal,
|
|
7087
|
+
vvh: visualViewportHeightSignal,
|
|
7088
|
+
vw: windowWidthSignal,
|
|
7089
|
+
vh: windowHeightSignal,
|
|
7090
|
+
dvw: windowWidthSignal,
|
|
7091
|
+
dvh: windowHeightSignal,
|
|
7092
|
+
};
|
|
7093
|
+
const VIEWPORT_LENGTH_REGEX = /^(-?\d+(?:\.\d+)?)(vvw|vvh|dvw|dvh|vw|vh)$/;
|
|
7094
|
+
const resolveViewportLength = (size) => {
|
|
7095
|
+
if (typeof size !== "string") {
|
|
7096
|
+
return null;
|
|
7097
|
+
}
|
|
7098
|
+
const match = VIEWPORT_LENGTH_REGEX.exec(size);
|
|
7099
|
+
if (!match) {
|
|
7100
|
+
return null;
|
|
7101
|
+
}
|
|
7102
|
+
const [, amount, unit] = match;
|
|
7103
|
+
return (parseFloat(amount) / 100) * VIEWPORT_UNIT_SIGNALS[unit].value;
|
|
7104
|
+
};
|
|
7105
|
+
|
|
7079
7106
|
const resolveSpacingSize = (size, element, property = "padding") => {
|
|
7107
|
+
const viewportLength = resolveViewportLength(size);
|
|
7108
|
+
if (viewportLength !== null) {
|
|
7109
|
+
return viewportLength;
|
|
7110
|
+
}
|
|
7080
7111
|
return normalizeStyle(SIZE_MAP[size] || size, property, "js", element);
|
|
7081
7112
|
};
|
|
7082
7113
|
|
|
@@ -21286,9 +21317,11 @@ const createControlInteraction = (
|
|
|
21286
21317
|
// Check managed controls — a non-interactable child blocks the parent,
|
|
21287
21318
|
// UNLESS the child's failing constraint has `ignoredByParents: true`
|
|
21288
21319
|
// (e.g. a disabled child inside a group should not prevent the group from acting).
|
|
21320
|
+
// Only the children that are reachable alongside the parent take part: a
|
|
21321
|
+
// picker's popup content is excluded, see getInteractionBlockingControls.
|
|
21289
21322
|
failingManagedInteraction = null;
|
|
21290
21323
|
if (!interactionFailedConstraintInfo) {
|
|
21291
|
-
for (const mc of controller.
|
|
21324
|
+
for (const mc of controller.getInteractionBlockingControls()) {
|
|
21292
21325
|
const mci = mc.rules.interaction;
|
|
21293
21326
|
if (!mci) {
|
|
21294
21327
|
continue;
|
|
@@ -21691,8 +21724,12 @@ const createDisplayedEvent = (ancestor) => {
|
|
|
21691
21724
|
*
|
|
21692
21725
|
* @param {import("preact/hooks").Ref<HTMLElement>} focusableElementRef
|
|
21693
21726
|
* Ref to the element to focus.
|
|
21694
|
-
* @param {boolean} autoFocus
|
|
21695
|
-
* When false the hook is a no-op.
|
|
21727
|
+
* @param {boolean|"fallback"|"restore"} autoFocus
|
|
21728
|
+
* When false the hook is a no-op. `"fallback"` claims focus only when nothing
|
|
21729
|
+
* more specific already did. `"restore"` never claims focus on open; it only
|
|
21730
|
+
* gets focus back from an ancestor that closed while it was focused (see
|
|
21731
|
+
* focus_transfer.js) — typically a text input that must not pop the mobile
|
|
21732
|
+
* keyboard open every time, but should stay where the user left it.
|
|
21696
21733
|
* @param {object} [options]
|
|
21697
21734
|
* @param {boolean} [options.preventScroll]
|
|
21698
21735
|
* Passed as `preventScroll` to `element.focus()`. Defaults to true to suppress
|
|
@@ -21715,6 +21752,11 @@ const useAutoFocus = (
|
|
|
21715
21752
|
if (!autoFocus) {
|
|
21716
21753
|
return () => {};
|
|
21717
21754
|
}
|
|
21755
|
+
if (autoFocus === "restore") {
|
|
21756
|
+
// "restore" never claims focus on its own; the only way it gets focus is
|
|
21757
|
+
// an ancestor reopening and handing it back (see focus_transfer.js).
|
|
21758
|
+
return () => {};
|
|
21759
|
+
}
|
|
21718
21760
|
const focusableElement = focusableElementRef.current;
|
|
21719
21761
|
if (!focusableElement) {
|
|
21720
21762
|
return () => {};
|
|
@@ -22750,6 +22792,7 @@ const useRenderScope = (init, update) => {
|
|
|
22750
22792
|
* props: Object;
|
|
22751
22793
|
* ref: Ref; // Used to dispatch DOM events
|
|
22752
22794
|
* getManagedControls(): UIStateController[]; // Returns controls whose validity is managed by this controller
|
|
22795
|
+
* getInteractionBlockingControls(): UIStateController[]; // Subset of the above whose busy state also blocks interacting with this controller
|
|
22753
22796
|
* }
|
|
22754
22797
|
* ```
|
|
22755
22798
|
*/
|
|
@@ -22867,6 +22910,10 @@ const useUIStateController = (
|
|
|
22867
22910
|
}
|
|
22868
22911
|
return [];
|
|
22869
22912
|
},
|
|
22913
|
+
// A facade child lives inside the control's own popup, so it is out of
|
|
22914
|
+
// reach until that popup opens. Letting it block interaction would make
|
|
22915
|
+
// a picker whose content is loading impossible to open at all.
|
|
22916
|
+
getInteractionBlockingControls: () => [],
|
|
22870
22917
|
onUIAction: (e, { skipCommand } = {}) => {
|
|
22871
22918
|
if (controlType === "button" && controller.controlHostProps.name) {
|
|
22872
22919
|
const buttonName = controller.controlHostProps.name;
|
|
@@ -23798,6 +23845,11 @@ const useUIGroupStateController = (
|
|
|
23798
23845
|
if (!cascadeValidationToChildren) return [];
|
|
23799
23846
|
return childUIStateControllerArray.slice();
|
|
23800
23847
|
},
|
|
23848
|
+
// Group children sit next to the group itself: a busy one really does
|
|
23849
|
+
// prevent the group from acting as a whole.
|
|
23850
|
+
getInteractionBlockingControls: () => {
|
|
23851
|
+
return controller.getManagedControls();
|
|
23852
|
+
},
|
|
23801
23853
|
subscribe: subscribeUIState,
|
|
23802
23854
|
};
|
|
23803
23855
|
const rules = createControlRules(controller, {
|
|
@@ -24033,6 +24085,13 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
24033
24085
|
}
|
|
24034
24086
|
return child.getManagedControls();
|
|
24035
24087
|
},
|
|
24088
|
+
getInteractionBlockingControls: () => {
|
|
24089
|
+
const child = firstChildControllerRef.current;
|
|
24090
|
+
if (!child) {
|
|
24091
|
+
return [];
|
|
24092
|
+
}
|
|
24093
|
+
return child.getInteractionBlockingControls();
|
|
24094
|
+
},
|
|
24036
24095
|
onChildUIAction: (child, e, { stateChanged, silent = false }) => {
|
|
24037
24096
|
if (!stateChanged) {
|
|
24038
24097
|
return;
|
|
@@ -24040,6 +24099,18 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
24040
24099
|
if (child !== firstChildControllerRef.current) {
|
|
24041
24100
|
return;
|
|
24042
24101
|
}
|
|
24102
|
+
if (
|
|
24103
|
+
silent &&
|
|
24104
|
+
child.uiState === undefined &&
|
|
24105
|
+
s.realUIStateController.uiState !== undefined
|
|
24106
|
+
) {
|
|
24107
|
+
// A silent sync means the child's own structure changed (children
|
|
24108
|
+
// mounted/unmounted), not that the user acted. A child that ends up
|
|
24109
|
+
// with no value there is one that currently *cannot* express one —
|
|
24110
|
+
// a <List loading> holds no items yet — which must not read as the
|
|
24111
|
+
// user clearing the picker, nor fire its uiAction.
|
|
24112
|
+
return;
|
|
24113
|
+
}
|
|
24043
24114
|
updatingRef.current = true;
|
|
24044
24115
|
// Use a different event type for silent (mount/unmount) syncs so that
|
|
24045
24116
|
// the picker's setUIState does not fire navi_change or action pipelines.
|
|
@@ -28646,6 +28717,7 @@ installImportMetaCssBuild(import.meta);const css$L = /* css */`
|
|
|
28646
28717
|
--link-text-decoration-hover: var(--link-text-decoration);
|
|
28647
28718
|
--link-cursor: pointer;
|
|
28648
28719
|
--link-loading-outline-size: 1px;
|
|
28720
|
+
--link-outline-width: 2px;
|
|
28649
28721
|
|
|
28650
28722
|
--link-current-indicator-size: 2px;
|
|
28651
28723
|
--link-current-indicator-spacing: 0;
|
|
@@ -28781,6 +28853,10 @@ installImportMetaCssBuild(import.meta);const css$L = /* css */`
|
|
|
28781
28853
|
--x-link-color: var(--link-color);
|
|
28782
28854
|
}
|
|
28783
28855
|
}
|
|
28856
|
+
&[data-anchor] {
|
|
28857
|
+
/* Usually better to have some spacing between the anchor and the scroll top */
|
|
28858
|
+
scroll-margin-block: calc(1em + var(--link-outline-width) + 1px);
|
|
28859
|
+
}
|
|
28784
28860
|
/* Hover */
|
|
28785
28861
|
&[data-hover] {
|
|
28786
28862
|
--x-link-background: var(--x-link-background-hover);
|
|
@@ -28788,7 +28864,7 @@ installImportMetaCssBuild(import.meta);const css$L = /* css */`
|
|
|
28788
28864
|
--x-link-text-decoration: var(--x-link-text-decoration-hover);
|
|
28789
28865
|
}
|
|
28790
28866
|
&[data-focus-visible] {
|
|
28791
|
-
outline-width:
|
|
28867
|
+
outline-width: var(--link-outline-width);
|
|
28792
28868
|
}
|
|
28793
28869
|
/* Pressed */
|
|
28794
28870
|
&[data-pressed] {
|
|
@@ -35947,37 +36023,62 @@ const renderSafe = (value) => {
|
|
|
35947
36023
|
|
|
35948
36024
|
const PickerContext = createContext();
|
|
35949
36025
|
|
|
35950
|
-
// The resolved popup mode of the surrounding Picker: "popover" or "dialog"
|
|
35951
|
-
// (frozen for the lifetime of an opening — see usePopupMode). Provided around the
|
|
35952
|
-
// picker's popup content so that content can render differently per mode.
|
|
35953
|
-
const PickerModeContext = createContext(undefined);
|
|
35954
|
-
|
|
35955
36026
|
/**
|
|
35956
|
-
*
|
|
35957
|
-
*
|
|
35958
|
-
*
|
|
35959
|
-
*
|
|
35960
|
-
*
|
|
36027
|
+
* Decides which element receives focus when a container (popover, dialog, …)
|
|
36028
|
+
* opens, and gives it back to where it came from when the container closes.
|
|
36029
|
+
*
|
|
36030
|
+
* The [navi-autofocus] attribute (written by use_auto_focus.js) tunes where
|
|
36031
|
+
* focus lands. Candidates are tried in this order:
|
|
36032
|
+
* 1. The element that held focus when the container was last closed, if it
|
|
36033
|
+
* opted into that with "fallback" or "restore"
|
|
36034
|
+
* 2. [navi-autofocus] with any other value ("" for a plain `autoFocus`)
|
|
36035
|
+
* 3. The first focusable element
|
|
36036
|
+
* 4. [navi-autofocus="fallback"], the container itself included
|
|
36037
|
+
* 5. The element focused before the container opened
|
|
36038
|
+
*
|
|
36039
|
+
* [navi-autofocus="restore"] appears in step 1 only: it never claims focus on
|
|
36040
|
+
* a fresh open, it only gets it back.
|
|
35961
36041
|
*/
|
|
35962
|
-
const usePickerMode = () => useContext(PickerModeContext);
|
|
35963
36042
|
|
|
35964
|
-
|
|
35965
|
-
|
|
35966
|
-
|
|
35967
|
-
|
|
35968
|
-
|
|
35969
|
-
|
|
35970
|
-
|
|
36043
|
+
// The element that held focus when a container closed is marked with
|
|
36044
|
+
// [navi-autofocus-last-focused], and its container with
|
|
36045
|
+
// [navi-autofocus-restore]. Both carry the same generated id: containers can
|
|
36046
|
+
// nest (a popover inside a dialog), so the id is what tells a reopening
|
|
36047
|
+
// container which mark among its descendants is its own.
|
|
36048
|
+
let restoreIdCounter = 0;
|
|
36049
|
+
|
|
36050
|
+
const isRestorableAutofocus = (el) => {
|
|
36051
|
+
const value = el.getAttribute("navi-autofocus");
|
|
36052
|
+
return value === "fallback" || value === "restore";
|
|
36053
|
+
};
|
|
36054
|
+
|
|
36055
|
+
const clearAutofocusRestore = (containerEl) => {
|
|
36056
|
+
const restoreId = containerEl.getAttribute("navi-autofocus-restore");
|
|
36057
|
+
if (restoreId === null) {
|
|
36058
|
+
return null;
|
|
36059
|
+
}
|
|
36060
|
+
containerEl.removeAttribute("navi-autofocus-restore");
|
|
36061
|
+
const selector = `[navi-autofocus-last-focused="${restoreId}"]`;
|
|
36062
|
+
const lastFocused = containerEl.matches(selector)
|
|
36063
|
+
? containerEl
|
|
36064
|
+
: containerEl.querySelector(selector);
|
|
36065
|
+
if (lastFocused) {
|
|
36066
|
+
lastFocused.removeAttribute("navi-autofocus-last-focused");
|
|
36067
|
+
}
|
|
36068
|
+
return lastFocused;
|
|
36069
|
+
};
|
|
36070
|
+
|
|
35971
36071
|
const markAutofocusRestoreOnClose = (containerEl) => {
|
|
36072
|
+
clearAutofocusRestore(containerEl);
|
|
35972
36073
|
const focused = document.activeElement;
|
|
35973
36074
|
if (
|
|
35974
36075
|
focused &&
|
|
35975
|
-
containerEl.contains(focused) &&
|
|
35976
|
-
focused
|
|
36076
|
+
(containerEl === focused || containerEl.contains(focused)) &&
|
|
36077
|
+
isRestorableAutofocus(focused)
|
|
35977
36078
|
) {
|
|
35978
|
-
|
|
35979
|
-
|
|
35980
|
-
|
|
36079
|
+
const restoreId = `${++restoreIdCounter}`;
|
|
36080
|
+
containerEl.setAttribute("navi-autofocus-restore", restoreId);
|
|
36081
|
+
focused.setAttribute("navi-autofocus-last-focused", restoreId);
|
|
35981
36082
|
}
|
|
35982
36083
|
};
|
|
35983
36084
|
|
|
@@ -35999,19 +36100,14 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
|
|
|
35999
36100
|
transferFocus: (transferEvent, containerEl) => {
|
|
36000
36101
|
let target;
|
|
36001
36102
|
let reason;
|
|
36002
|
-
|
|
36003
|
-
|
|
36004
|
-
|
|
36005
|
-
|
|
36006
|
-
);
|
|
36007
|
-
if (naviAutoFocusFallback) {
|
|
36008
|
-
reason = "navi-autofocus fallback (restore)";
|
|
36009
|
-
target = naviAutoFocusFallback;
|
|
36010
|
-
}
|
|
36103
|
+
const lastFocused = clearAutofocusRestore(containerEl);
|
|
36104
|
+
if (lastFocused) {
|
|
36105
|
+
reason = "element focused when closed (restore)";
|
|
36106
|
+
target = lastFocused;
|
|
36011
36107
|
}
|
|
36012
36108
|
if (!target) {
|
|
36013
36109
|
const naviAutoFocus = containerEl.querySelector(
|
|
36014
|
-
|
|
36110
|
+
`[navi-autofocus]:not([navi-autofocus="fallback"]):not([navi-autofocus="restore"])`,
|
|
36015
36111
|
);
|
|
36016
36112
|
if (naviAutoFocus) {
|
|
36017
36113
|
reason = "navi-autofocus";
|
|
@@ -36020,7 +36116,7 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
|
|
|
36020
36116
|
}
|
|
36021
36117
|
if (!target) {
|
|
36022
36118
|
const focusable = findFocusable(containerEl, {
|
|
36023
|
-
exclude:
|
|
36119
|
+
exclude: isRestorableAutofocus,
|
|
36024
36120
|
});
|
|
36025
36121
|
if (focusable) {
|
|
36026
36122
|
reason = "first focusable element";
|
|
@@ -36028,14 +36124,16 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
|
|
|
36028
36124
|
}
|
|
36029
36125
|
}
|
|
36030
36126
|
if (!target) {
|
|
36031
|
-
//
|
|
36032
|
-
//
|
|
36033
|
-
//
|
|
36034
|
-
|
|
36035
|
-
|
|
36036
|
-
|
|
36037
|
-
|
|
36038
|
-
|
|
36127
|
+
// A [navi-autofocus="fallback"] INSIDE the container (e.g. a search
|
|
36128
|
+
// input) wins over the container itself. The container is only the
|
|
36129
|
+
// fallback-of-the-fallback: a focusable popup root gets focus solely
|
|
36130
|
+
// when nothing inside it already carries the fallback. (matches() covers
|
|
36131
|
+
// the container-only case since querySelector searches descendants only.)
|
|
36132
|
+
const naviAutoFocusFallback =
|
|
36133
|
+
containerEl.querySelector(`[navi-autofocus="fallback"]`) ||
|
|
36134
|
+
(containerEl.matches(`[navi-autofocus="fallback"]`)
|
|
36135
|
+
? containerEl
|
|
36136
|
+
: null);
|
|
36039
36137
|
if (naviAutoFocusFallback) {
|
|
36040
36138
|
reason = "navi-autofocus fallback";
|
|
36041
36139
|
target = naviAutoFocusFallback;
|
|
@@ -36157,26 +36255,32 @@ const createOpenController = (
|
|
|
36157
36255
|
// open() ignores the request — no need to know *which* element triggers
|
|
36158
36256
|
// it. A bubble-phase listener (runs after everything else, once the click
|
|
36159
36257
|
// reaches document) clears the flag if nothing consumed it, meaning this
|
|
36160
|
-
// click never resulted in an open() call. A
|
|
36161
|
-
//
|
|
36162
|
-
//
|
|
36258
|
+
// click never resulted in an open() call. A timeout is a last-resort safety
|
|
36259
|
+
// net in case the click never reaches document at all (e.g. some ancestor
|
|
36260
|
+
// called stopPropagation()) — a *task*, never a microtask: a microtask
|
|
36261
|
+
// checkpoint runs between two listeners of the same trusted event dispatch,
|
|
36262
|
+
// so it would clear the flag before the bubble-phase handler this is meant
|
|
36263
|
+
// to block ever runs, which is precisely the case it exists for.
|
|
36163
36264
|
const armSuppressNextOpenRequest = () => {
|
|
36164
36265
|
disarmSuppressNextOpenRequest?.();
|
|
36266
|
+
let safetyTimeout = null;
|
|
36165
36267
|
const onCaptureClick = () => {
|
|
36166
36268
|
document.removeEventListener("click", onCaptureClick, {
|
|
36167
36269
|
capture: true,
|
|
36168
36270
|
});
|
|
36169
36271
|
suppressNextOpenRequest = true;
|
|
36170
36272
|
document.addEventListener("click", onBubbleClick);
|
|
36171
|
-
|
|
36273
|
+
safetyTimeout = setTimeout(() => {
|
|
36172
36274
|
suppressNextOpenRequest = false;
|
|
36173
36275
|
});
|
|
36174
36276
|
};
|
|
36175
36277
|
const onBubbleClick = () => {
|
|
36176
36278
|
document.removeEventListener("click", onBubbleClick);
|
|
36279
|
+
clearTimeout(safetyTimeout);
|
|
36177
36280
|
suppressNextOpenRequest = false;
|
|
36178
36281
|
};
|
|
36179
36282
|
disarmSuppressNextOpenRequest = () => {
|
|
36283
|
+
clearTimeout(safetyTimeout);
|
|
36180
36284
|
document.removeEventListener("click", onCaptureClick, {
|
|
36181
36285
|
capture: true,
|
|
36182
36286
|
});
|
|
@@ -36464,6 +36568,61 @@ const useOpenControllerByProps = (props) => {
|
|
|
36464
36568
|
return openController;
|
|
36465
36569
|
};
|
|
36466
36570
|
|
|
36571
|
+
/**
|
|
36572
|
+
* Where the "popover or dialog?" answer lives, for both the components that
|
|
36573
|
+
* decide it and the content that renders inside one.
|
|
36574
|
+
*
|
|
36575
|
+
* `useResolvedPopupMode` is the decision (screen size + maxWidth heuristic),
|
|
36576
|
+
* called by whatever renders the popup — `Popup` itself, or `picker_custom.jsx`
|
|
36577
|
+
* which needs the answer for its own mode-dependent history/ARIA handling on
|
|
36578
|
+
* top of picking a renderer. `usePopupMode` is the read side: any content
|
|
36579
|
+
* rendered inside a popup can call it to lay itself out differently in a
|
|
36580
|
+
* dropdown than in a full-screen modal.
|
|
36581
|
+
*/
|
|
36582
|
+
|
|
36583
|
+
const PopupModeContext = createContext(undefined);
|
|
36584
|
+
|
|
36585
|
+
/**
|
|
36586
|
+
* Read the mode of the popup this is rendered inside — a `Popup`, or a
|
|
36587
|
+
* `Picker`'s own popup. Returns undefined outside of any popup content.
|
|
36588
|
+
*
|
|
36589
|
+
* @returns {"popover" | "dialog" | undefined}
|
|
36590
|
+
*/
|
|
36591
|
+
const usePopupMode = () => useContext(PopupModeContext);
|
|
36592
|
+
|
|
36593
|
+
/**
|
|
36594
|
+
* Resolves which of Popover/Dialog a popup should be. Frozen for the component
|
|
36595
|
+
* instance's lifetime, so a screen resize never switches an already-mounted
|
|
36596
|
+
* popup from one to the other mid-session.
|
|
36597
|
+
*
|
|
36598
|
+
* @param {"dialog"|"popover"} [modeProp] - Forces one mode; `undefined` to
|
|
36599
|
+
* resolve automatically.
|
|
36600
|
+
* @param {string} [maxWidth] - A small enough value is treated as "compact",
|
|
36601
|
+
* staying a popover even on a small screen.
|
|
36602
|
+
* @returns {["dialog"|"popover", () => void]} The resolved mode, and a
|
|
36603
|
+
* `resetMode` function a caller can call (e.g. on close) to force the *next*
|
|
36604
|
+
* call to re-resolve from scratch instead of keeping the frozen value —
|
|
36605
|
+
* `Popup` itself never needs this (it has no notion of open/close of its
|
|
36606
|
+
* own), `picker_custom.jsx` does (re-evaluates screen size on every fresh
|
|
36607
|
+
* open).
|
|
36608
|
+
*/
|
|
36609
|
+
const useResolvedPopupMode = (modeProp, maxWidth) => {
|
|
36610
|
+
const defaultModeRef = useRef(null);
|
|
36611
|
+
if (defaultModeRef.current === null) {
|
|
36612
|
+
defaultModeRef.current = resolvePopupMode(modeProp, maxWidth);
|
|
36613
|
+
}
|
|
36614
|
+
const resetMode = () => {
|
|
36615
|
+
defaultModeRef.current = null;
|
|
36616
|
+
};
|
|
36617
|
+
return [defaultModeRef.current, resetMode];
|
|
36618
|
+
};
|
|
36619
|
+
const resolvePopupMode = (modeProp, maxWidth) => {
|
|
36620
|
+
const isSmallScreen = windowWidthSignal.peek() <= 600;
|
|
36621
|
+
const maxWidthPx = parseFloat(maxWidth);
|
|
36622
|
+
const isCompact = isFinite(maxWidthPx) && maxWidthPx < 150;
|
|
36623
|
+
return modeProp ?? (isSmallScreen && !isCompact ? "dialog" : "popover");
|
|
36624
|
+
};
|
|
36625
|
+
|
|
36467
36626
|
/**
|
|
36468
36627
|
* Entry/exit animation CSS shared by Popover and Dialog.
|
|
36469
36628
|
*
|
|
@@ -36561,7 +36720,8 @@ const popupCss = /* css */ `
|
|
|
36561
36720
|
this list contains (no shared transition-property to clobber, no
|
|
36562
36721
|
propertyName to filter). */
|
|
36563
36722
|
&[navi-animation] {
|
|
36564
|
-
transition-property:
|
|
36723
|
+
transition-property:
|
|
36724
|
+
display, overlay, opacity, translate, scale, box-shadow;
|
|
36565
36725
|
transition-duration:
|
|
36566
36726
|
var(--popup-animation-duration), var(--popup-animation-duration),
|
|
36567
36727
|
var(--popup-opacity-duration), var(--popup-translate-duration),
|
|
@@ -36924,15 +37084,24 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
36924
37084
|
const css$v = /* css */`
|
|
36925
37085
|
@layer navi {
|
|
36926
37086
|
.navi_dialog {
|
|
36927
|
-
/*
|
|
36928
|
-
|
|
36929
|
-
|
|
37087
|
+
/* Min gap between the dialog and the edges of its container. Written
|
|
37088
|
+
from the marginWithContainer prop (see below) — hence --x-, not a knob
|
|
37089
|
+
to set from CSS — so the size caps here and the placement can never
|
|
37090
|
+
disagree. The literal is only what a dialog painted before that ever
|
|
37091
|
+
runs falls back to. Not named "margin" because it isn't implemented
|
|
37092
|
+
with margins (those are needed for centering).
|
|
37093
|
+
|
|
37094
|
+
Capping the *size* here rather than only offsetting the position is
|
|
37095
|
+
what makes a centered dialog follow the mobile virtual keyboard for
|
|
37096
|
+
free: --navi-vvw/--navi-vvh track the visual viewport, so the browser
|
|
37097
|
+
reflows the dialog itself as the keyboard opens. */
|
|
37098
|
+
--x-dialog-viewport-spacing: 3vvw;
|
|
36930
37099
|
|
|
36931
37100
|
--dialog-maxmax-width: calc(
|
|
36932
|
-
var(--navi-vvw) - 2 * var(--dialog-viewport-spacing)
|
|
37101
|
+
var(--navi-vvw) - 2 * var(--x-dialog-viewport-spacing)
|
|
36933
37102
|
);
|
|
36934
37103
|
--dialog-maxmax-height: calc(
|
|
36935
|
-
var(--navi-vvh) - 2 * var(--dialog-viewport-spacing)
|
|
37104
|
+
var(--navi-vvh) - 2 * var(--x-dialog-viewport-spacing)
|
|
36936
37105
|
);
|
|
36937
37106
|
|
|
36938
37107
|
--dialog-border-radius: var(--navi-popup-border-radius);
|
|
@@ -37165,8 +37334,14 @@ const css$v = /* css */`
|
|
|
37165
37334
|
* `bottom-end`/`bottom-left`/`bottom-right`, `left`/`left-start`/
|
|
37166
37335
|
* `left-end`, or `center` — optionally wrapped in `inset(...)` (e.g.
|
|
37167
37336
|
* `inset(top)`) for the overlapping variant.
|
|
37168
|
-
* @param {string|number} [props.marginWithContainer=
|
|
37169
|
-
* between the dialog and the edges of its container
|
|
37337
|
+
* @param {string|number} [props.marginWithContainer="3vvw"] - Minimum gap kept
|
|
37338
|
+
* between the dialog and the edges of its container, whatever its
|
|
37339
|
+
* `positionArea`: it both caps the dialog's own size (via
|
|
37340
|
+
* `--x-dialog-viewport-spacing`, written from this prop) and offsets a docked
|
|
37341
|
+
* one from the edge it docks to. Accepts a spacing token ("s", "m"…), a
|
|
37342
|
+
* number of pixels, or a viewport length — "vvw"/"vvh" being the visual
|
|
37343
|
+
* viewport, which shrinks when the mobile keyboard opens. Pass 0 for a dialog
|
|
37344
|
+
* meant to sit flush (a side panel).
|
|
37170
37345
|
* @param {"close"|"cancel"|"capture"|"none"} [props.pointerInteractionOutsideEffect="close"]
|
|
37171
37346
|
* - `"close"` closes the dialog on an outside click. `"capture"`/`"none"`
|
|
37172
37347
|
* both just absorb the click without closing (visually dimmed backdrop vs.
|
|
@@ -37197,9 +37372,10 @@ const css$v = /* css */`
|
|
|
37197
37372
|
* @param {number} [props.tabIndex=-1] - Set on the dialog element itself so
|
|
37198
37373
|
* `autoFocus="fallback"` below has somewhere to land when the dialog has
|
|
37199
37374
|
* no other focusable descendant of its own.
|
|
37200
|
-
* @param {boolean|"fallback"} [props.autoFocus="fallback"] - See
|
|
37201
|
-
* `
|
|
37202
|
-
* no other focusable descendant
|
|
37375
|
+
* @param {boolean|"fallback"|"restore"} [props.autoFocus="fallback"] - See
|
|
37376
|
+
* `focus_transfer.js` — `"fallback"` focuses the dialog itself if it has
|
|
37377
|
+
* no other focusable descendant, `"restore"` keeps it out of the opening
|
|
37378
|
+
* focus chain unless it held focus when the dialog closed.
|
|
37203
37379
|
* @param {boolean} [props.open] - Controlled open state.
|
|
37204
37380
|
* @param {boolean} [props.defaultOpen] - Uncontrolled, mount-only initial
|
|
37205
37381
|
* open state — plays no entrance animation (nothing was ever shown as
|
|
@@ -37312,7 +37488,10 @@ const useDialogProps = props => {
|
|
|
37312
37488
|
// Same grammar as Popover's own positionArea — see this file's top
|
|
37313
37489
|
// comment and popup_shared.js's parsePositionArea.
|
|
37314
37490
|
positionArea = "center",
|
|
37315
|
-
|
|
37491
|
+
// A dialog docked against an edge must keep the same gap its own size cap
|
|
37492
|
+
// already guarantees a centered one — so this drives both (see
|
|
37493
|
+
// --x-dialog-viewport-spacing above). Pass 0 to sit flush (side_panel.jsx).
|
|
37494
|
+
marginWithContainer = "3vvw",
|
|
37316
37495
|
// "close" (default) closes on an outside click. "capture"/"none" both
|
|
37317
37496
|
// just absorb it without closing — for the via-attribute renderer,
|
|
37318
37497
|
// showModal() already makes the rest of the page inert, so there's
|
|
@@ -37349,6 +37528,21 @@ const useDialogProps = props => {
|
|
|
37349
37528
|
const debugFocus = useDebugFocus();
|
|
37350
37529
|
const debugInteraction = useDebugInteraction();
|
|
37351
37530
|
const autoFocusProps = useAutoFocus(ref, autoFocus);
|
|
37531
|
+
// positionDialog lives in openEffect's closure — created once, when the
|
|
37532
|
+
// dialog opens. Reading the placement props through a ref instead of that
|
|
37533
|
+
// closure is what lets a change while open take effect on the spot (see the
|
|
37534
|
+
// reposition effect below) rather than only on the next opening.
|
|
37535
|
+
const positionPropsRef = useRef(null);
|
|
37536
|
+
positionPropsRef.current = {
|
|
37537
|
+
positionArea,
|
|
37538
|
+
marginWithContainer
|
|
37539
|
+
};
|
|
37540
|
+
const repositionRef = useRef(null);
|
|
37541
|
+
useEffect(() => {
|
|
37542
|
+
repositionRef.current?.(new CustomEvent("position_props_change", {
|
|
37543
|
+
detail: {}
|
|
37544
|
+
}));
|
|
37545
|
+
}, [positionArea, marginWithContainer]);
|
|
37352
37546
|
const positionAreaParseResult = parsePositionArea(positionArea);
|
|
37353
37547
|
if (!positionAreaParseResult) {
|
|
37354
37548
|
console.warn(`Dialog: invalid positionArea="${positionArea}"`);
|
|
@@ -37492,10 +37686,27 @@ const useDialogProps = props => {
|
|
|
37492
37686
|
// custom renderer. applyNewPosition sets --container-position-remaining-height/-width
|
|
37493
37687
|
// from the result, same as popover.jsx.
|
|
37494
37688
|
const positionDialog = triggerEvent => {
|
|
37689
|
+
const {
|
|
37690
|
+
positionArea,
|
|
37691
|
+
marginWithContainer
|
|
37692
|
+
} = positionPropsRef.current;
|
|
37693
|
+
let marginWithContainerInPixels = resolveSpacingSize(marginWithContainer);
|
|
37694
|
+
if (typeof marginWithContainerInPixels !== "number") {
|
|
37695
|
+
// A value only CSS could evaluate (a spacing token resolving to a var(),
|
|
37696
|
+
// a percentage…) — the placement below needs a real number, and letting
|
|
37697
|
+
// it through would put the dialog at NaN.
|
|
37698
|
+
console.warn(`Dialog: marginWithContainer="${marginWithContainer}" cannot be resolved to pixels. Use a number or a viewport length ("3vvw", "2vvh").`);
|
|
37699
|
+
marginWithContainerInPixels = 0;
|
|
37700
|
+
}
|
|
37701
|
+
// The size caps read the same gap in CSS as the placement below applies
|
|
37702
|
+
// in pixels, so a docked dialog and a centered one keep the same
|
|
37703
|
+
// distance from the edges. Written resolved (not as the raw prop) so a
|
|
37704
|
+
// spacing token stays valid inside the caps' own calc().
|
|
37705
|
+
dialogEl.style.setProperty("--x-dialog-viewport-spacing", `${marginWithContainerInPixels}px`);
|
|
37495
37706
|
const pickOptions = {
|
|
37496
37707
|
positionArea,
|
|
37497
37708
|
container: positionedAncestor,
|
|
37498
|
-
marginWithContainer:
|
|
37709
|
+
marginWithContainer: marginWithContainerInPixels,
|
|
37499
37710
|
event: triggerEvent
|
|
37500
37711
|
};
|
|
37501
37712
|
let position = pickPositionRelativeTo(dialogEl, null, pickOptions);
|
|
@@ -37541,7 +37752,19 @@ const useDialogProps = props => {
|
|
|
37541
37752
|
skipElementResize: true
|
|
37542
37753
|
});
|
|
37543
37754
|
rectEffect.observeSize(dialogEl);
|
|
37755
|
+
// Exposed for the placement-props effect below, which needs to re-place an
|
|
37756
|
+
// already-open dialog.
|
|
37757
|
+
repositionRef.current = repositionEvent => {
|
|
37758
|
+
// data-position-*-current pins an open dialog to the side it first
|
|
37759
|
+
// resolved to, so a resize never makes it jump (pickPositionRelativeTo
|
|
37760
|
+
// reads it back and prefers it over the requested area). A new placement
|
|
37761
|
+
// request is precisely the case where that memory must not win.
|
|
37762
|
+
dialogEl.removeAttribute("data-position-x-current");
|
|
37763
|
+
dialogEl.removeAttribute("data-position-y-current");
|
|
37764
|
+
positionDialog(repositionEvent);
|
|
37765
|
+
};
|
|
37544
37766
|
addCleanup(() => {
|
|
37767
|
+
repositionRef.current = null;
|
|
37545
37768
|
rectEffect.disconnect();
|
|
37546
37769
|
});
|
|
37547
37770
|
// A descendant anchored to something inside this dialog (a Callout, a
|
|
@@ -37833,7 +38056,10 @@ let openLocalPopoverCount = 0;
|
|
|
37833
38056
|
const css$u = /* css */`
|
|
37834
38057
|
@layer navi {
|
|
37835
38058
|
.navi_popover {
|
|
37836
|
-
|
|
38059
|
+
/* soft: user-configurable preferred max-height. Kept as a *default*
|
|
38060
|
+
rather than a value so an outer component can bridge its own prop into
|
|
38061
|
+
--popover-max-height without having to restate 300px (see picker). */
|
|
38062
|
+
--popover-max-height-default: 300px;
|
|
37837
38063
|
--popover-maxmax-height: calc(0.95 * var(--navi-vvh));
|
|
37838
38064
|
--popover-maxmax-width: calc(0.95 * var(--navi-vvw));
|
|
37839
38065
|
|
|
@@ -37884,7 +38110,7 @@ const css$u = /* css */`
|
|
|
37884
38110
|
var(--popover-maxmax-width)
|
|
37885
38111
|
);
|
|
37886
38112
|
--x-popover-max-height: min(
|
|
37887
|
-
var(--popover-max-height),
|
|
38113
|
+
var(--popover-max-height, var(--popover-max-height-default)),
|
|
37888
38114
|
var(--container-position-remaining-height, var(--popover-maxmax-height)),
|
|
37889
38115
|
var(--popover-maxmax-height)
|
|
37890
38116
|
);
|
|
@@ -38135,9 +38361,10 @@ const css$u = /* css */`
|
|
|
38135
38361
|
* @param {number} [props.tabIndex=-1] - Set on the popover element itself
|
|
38136
38362
|
* so `autoFocus="fallback"` below has somewhere to land when the popover
|
|
38137
38363
|
* has no other focusable descendant of its own.
|
|
38138
|
-
* @param {boolean|"fallback"} [props.autoFocus="fallback"] - See
|
|
38139
|
-
* `
|
|
38140
|
-
* no other focusable descendant
|
|
38364
|
+
* @param {boolean|"fallback"|"restore"} [props.autoFocus="fallback"] - See
|
|
38365
|
+
* `focus_transfer.js` — `"fallback"` focuses the popover itself if it has
|
|
38366
|
+
* no other focusable descendant, `"restore"` keeps it out of the opening
|
|
38367
|
+
* focus chain unless it held focus when the popover closed.
|
|
38141
38368
|
* @param {boolean} [props.open] - Controlled open state.
|
|
38142
38369
|
* @param {boolean} [props.defaultOpen] - Uncontrolled, mount-only initial
|
|
38143
38370
|
* open state — plays no entrance animation (nothing was ever shown as
|
|
@@ -38313,6 +38540,24 @@ const usePopoverProps = props => {
|
|
|
38313
38540
|
// (see resolveAutoAnimationKind).
|
|
38314
38541
|
const isAutoAnimation = animation === true || animation === "auto";
|
|
38315
38542
|
const hasBackdrop = pointerInteractionOutsideEffect !== "none";
|
|
38543
|
+
// positionPopover lives in openEffect's closure — created once, when the
|
|
38544
|
+
// popover opens. Reading the placement props through a ref instead of that
|
|
38545
|
+
// closure is what lets a change while open take effect on the spot (see the
|
|
38546
|
+
// reposition effect below) rather than only on the next opening.
|
|
38547
|
+
const positionPropsRef = useRef(null);
|
|
38548
|
+
positionPropsRef.current = {
|
|
38549
|
+
positionArea,
|
|
38550
|
+
positionAreaFixed,
|
|
38551
|
+
positionAreaWhenAnchorIsInvalid,
|
|
38552
|
+
marginWithAnchor,
|
|
38553
|
+
marginWithContainer
|
|
38554
|
+
};
|
|
38555
|
+
const repositionRef = useRef(null);
|
|
38556
|
+
useEffect(() => {
|
|
38557
|
+
repositionRef.current?.(new CustomEvent("position_props_change", {
|
|
38558
|
+
detail: {}
|
|
38559
|
+
}));
|
|
38560
|
+
}, [positionArea, positionAreaFixed, positionAreaWhenAnchorIsInvalid, marginWithAnchor, marginWithContainer]);
|
|
38316
38561
|
// The custom renderer's own starting-hidden state is a stylesheet default
|
|
38317
38562
|
// now (&:not([popover]) { display: none } on .navi_popover/
|
|
38318
38563
|
// .navi_popover_backdrop above) rather than set here imperatively — a
|
|
@@ -38509,6 +38754,13 @@ const usePopoverProps = props => {
|
|
|
38509
38754
|
// via-attribute renderer (see its own computation above).
|
|
38510
38755
|
const effectiveAnchor = hasAnchorElement ? anchorElement : positionedAncestor;
|
|
38511
38756
|
const positionPopover = positionEvent => {
|
|
38757
|
+
const {
|
|
38758
|
+
positionArea,
|
|
38759
|
+
positionAreaFixed,
|
|
38760
|
+
positionAreaWhenAnchorIsInvalid,
|
|
38761
|
+
marginWithAnchor,
|
|
38762
|
+
marginWithContainer
|
|
38763
|
+
} = positionPropsRef.current;
|
|
38512
38764
|
let position;
|
|
38513
38765
|
if (hasAnchorElement) {
|
|
38514
38766
|
const {
|
|
@@ -38676,6 +38928,21 @@ const usePopoverProps = props => {
|
|
|
38676
38928
|
// while open (e.g. an expand/collapse toggle inside it) — not just when
|
|
38677
38929
|
// the anchor itself moves/resizes/re-anchors.
|
|
38678
38930
|
rectEffect.observeSize(popoverEl);
|
|
38931
|
+
// Exposed for the placement-props effect above, which needs to re-place an
|
|
38932
|
+
// already-open popover.
|
|
38933
|
+
repositionRef.current = repositionEvent => {
|
|
38934
|
+
// data-position-*-current pins an open popover to the side it first
|
|
38935
|
+
// resolved to, so scrolling or a content resize never makes it jump
|
|
38936
|
+
// (pickPositionRelativeTo reads it back and prefers it over the
|
|
38937
|
+
// requested area). A new placement request is precisely the case where
|
|
38938
|
+
// that memory must not win — drop it before re-resolving.
|
|
38939
|
+
popoverEl.removeAttribute("data-position-x-current");
|
|
38940
|
+
popoverEl.removeAttribute("data-position-y-current");
|
|
38941
|
+
positionPopover(repositionEvent);
|
|
38942
|
+
};
|
|
38943
|
+
addCleanup(() => {
|
|
38944
|
+
repositionRef.current = null;
|
|
38945
|
+
});
|
|
38679
38946
|
// A descendant anchored to something inside this popover (a Callout, a
|
|
38680
38947
|
// further-nested Popover) needing to know about this popover's own
|
|
38681
38948
|
// left/top repositioning transition — not just that the target changed
|
|
@@ -39002,12 +39269,12 @@ const resolvePositionAreaAndAnimationKind = ({
|
|
|
39002
39269
|
installImportMetaCssBuild(import.meta);/**
|
|
39003
39270
|
* A lightweight version of picker_custom.jsx's own Popover/Dialog switch —
|
|
39004
39271
|
* no picker concepts (value/action tracking, keyboard letter/arrow-to-open
|
|
39005
|
-
* shortcuts, history-driven expanded state
|
|
39272
|
+
* shortcuts, history-driven expanded state):
|
|
39006
39273
|
* just picks between rendering a Popover or a Dialog and applies the shared
|
|
39007
39274
|
* "popup box" look (padding, background, border-radius, box-shadow) to
|
|
39008
39275
|
* whichever one it renders.
|
|
39009
39276
|
*
|
|
39010
|
-
* Mode resolution (`
|
|
39277
|
+
* Mode resolution (`useResolvedPopupMode`, popup_mode.jsx) is shared with picker_custom.jsx,
|
|
39011
39278
|
* not just mirrored — the picker needs the resolved mode itself (for its own
|
|
39012
39279
|
* mode-dependent history/ARIA handling), not just to pick which of Popover/
|
|
39013
39280
|
* Dialog to render, so it calls the same hook directly instead of
|
|
@@ -39150,7 +39417,12 @@ const Popup = props => {
|
|
|
39150
39417
|
positionAreaFixed,
|
|
39151
39418
|
...rest
|
|
39152
39419
|
} = props;
|
|
39153
|
-
const [mode] =
|
|
39420
|
+
const [mode] = useResolvedPopupMode(modeProp, maxWidth);
|
|
39421
|
+
// So the content can lay itself out per mode — see usePopupMode.
|
|
39422
|
+
const childrenWithMode = jsx(PopupModeContext.Provider, {
|
|
39423
|
+
value: mode,
|
|
39424
|
+
children: children
|
|
39425
|
+
});
|
|
39154
39426
|
if (mode === "dialog") {
|
|
39155
39427
|
const expandXResolved = expand || expandX;
|
|
39156
39428
|
const expandYResolved = expand || expandY;
|
|
@@ -39161,7 +39433,7 @@ const Popup = props => {
|
|
|
39161
39433
|
className: withPropsClassName("navi_popup", className),
|
|
39162
39434
|
"data-expand-x": expandXResolved ? "" : undefined,
|
|
39163
39435
|
"data-expand-y": expandYResolved ? "" : undefined,
|
|
39164
|
-
children:
|
|
39436
|
+
children: childrenWithMode
|
|
39165
39437
|
});
|
|
39166
39438
|
}
|
|
39167
39439
|
return jsx(Popover, {
|
|
@@ -39173,54 +39445,10 @@ const Popup = props => {
|
|
|
39173
39445
|
focusCapture: focusCapture,
|
|
39174
39446
|
positionAreaFixed: positionAreaFixed,
|
|
39175
39447
|
className: withPropsClassName("navi_popup", className),
|
|
39176
|
-
children:
|
|
39448
|
+
children: childrenWithMode
|
|
39177
39449
|
});
|
|
39178
39450
|
};
|
|
39179
39451
|
|
|
39180
|
-
/**
|
|
39181
|
-
* Frozen for the component instance's lifetime — mirrors `Popup`'s own
|
|
39182
|
-
* mode-resolution timing (a screen resize while already mounted doesn't
|
|
39183
|
-
* switch between Popover and Dialog mid-session).
|
|
39184
|
-
*
|
|
39185
|
-
* @param {"dialog"|"popover"} [modeProp]
|
|
39186
|
-
* @param {string} [maxWidth]
|
|
39187
|
-
* @returns {["dialog"|"popover", () => void]} The resolved mode, and a
|
|
39188
|
-
* `resetMode` function a caller can call (e.g. on close) to force the
|
|
39189
|
-
* *next* call to re-resolve from scratch instead of keeping the frozen
|
|
39190
|
-
* value — `Popup` itself never needs this (it has no notion of
|
|
39191
|
-
* open/close of its own), `picker_custom.jsx` does (re-evaluates screen
|
|
39192
|
-
* size on every fresh open).
|
|
39193
|
-
*/
|
|
39194
|
-
const usePopupMode = (modeProp, maxWidth) => {
|
|
39195
|
-
const defaultModeRef = useRef(null);
|
|
39196
|
-
if (defaultModeRef.current === null) {
|
|
39197
|
-
defaultModeRef.current = resolvePopupMode(modeProp, maxWidth);
|
|
39198
|
-
}
|
|
39199
|
-
const resetMode = () => {
|
|
39200
|
-
defaultModeRef.current = null;
|
|
39201
|
-
};
|
|
39202
|
-
return [defaultModeRef.current, resetMode];
|
|
39203
|
-
};
|
|
39204
|
-
/**
|
|
39205
|
-
* Same small-screen/`maxWidth`-compact heuristic `Popup` uses internally,
|
|
39206
|
-
* exported so `picker_custom.jsx` (which needs the resolved mode itself,
|
|
39207
|
-
* for its own mode-dependent history/ARIA handling — not just to pick which
|
|
39208
|
-
* of Popover/Dialog to render, the way `Popup` only ever needs it) doesn't
|
|
39209
|
-
* have to duplicate it.
|
|
39210
|
-
*
|
|
39211
|
-
* @param {"dialog"|"popover"} [modeProp] - Forces one mode; `undefined` to
|
|
39212
|
-
* resolve automatically.
|
|
39213
|
-
* @param {string} [maxWidth] - A small enough value is treated as
|
|
39214
|
-
* "compact", staying a popover even on a small screen.
|
|
39215
|
-
* @returns {"dialog"|"popover"}
|
|
39216
|
-
*/
|
|
39217
|
-
const resolvePopupMode = (modeProp, maxWidth) => {
|
|
39218
|
-
const isSmallScreen = windowWidthSignal.peek() <= 600;
|
|
39219
|
-
const maxWidthPx = parseFloat(maxWidth);
|
|
39220
|
-
const isCompact = isFinite(maxWidthPx) && maxWidthPx < 150;
|
|
39221
|
-
return modeProp ?? (isSmallScreen && !isCompact ? "dialog" : "popover");
|
|
39222
|
-
};
|
|
39223
|
-
|
|
39224
39452
|
installImportMetaCssBuild(import.meta);const css$s = /* css */`
|
|
39225
39453
|
.navi_picker {
|
|
39226
39454
|
/* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
|
|
@@ -39228,8 +39456,7 @@ installImportMetaCssBuild(import.meta);const css$s = /* css */`
|
|
|
39228
39456
|
themselves — nothing to redefine here. Only the picker's own look
|
|
39229
39457
|
(border color/radius/width, background) needs bridging into the vars
|
|
39230
39458
|
Popover/Dialog actually consume, plus a couple of genuinely
|
|
39231
|
-
picker-specific bits below (anchor-width min-width, the
|
|
39232
|
-
the nested list). */
|
|
39459
|
+
picker-specific bits below (anchor-width min-width, the nested list). */
|
|
39233
39460
|
|
|
39234
39461
|
/* popover */
|
|
39235
39462
|
&[aria-haspopup="listbox"] {
|
|
@@ -39240,52 +39467,17 @@ installImportMetaCssBuild(import.meta);const css$s = /* css */`
|
|
|
39240
39467
|
--popover-background-color: var(--picker-background-color);
|
|
39241
39468
|
--popover-outline-width: var(--picker-outline-width);
|
|
39242
39469
|
--popover-outline-color: var(--picker-outline-color);
|
|
39470
|
+
/* No fallback on purpose: when the picker's own popoverMaxHeight prop
|
|
39471
|
+
is unset this declaration is invalid at computed-value time, which
|
|
39472
|
+
leaves --popover-max-height unset and lets the popover fall back to
|
|
39473
|
+
--popover-max-height-default. */
|
|
39474
|
+
--popover-max-height: var(--picker-popover-max-height);
|
|
39243
39475
|
|
|
39244
39476
|
/* At least as wide as the trigger — unless popupWidthFitContent, then
|
|
39245
39477
|
let the content (e.g. a Wheel) size the popover (see picker.jsx). */
|
|
39246
39478
|
min-width: var(--picker-popover-min-width, var(--anchor-width, 0px));
|
|
39247
39479
|
cursor: default; /* Reset pointer cursor within the select */
|
|
39248
39480
|
|
|
39249
|
-
/* The anchor placeholder is a non-interactive visual clone of the
|
|
39250
|
-
trigger. It makes the popover wrap both the trigger area and the list
|
|
39251
|
-
under a single border/shadow. CSS order places it before the list
|
|
39252
|
-
when the popover is below the trigger, and after when above. */
|
|
39253
|
-
.navi_picker_anchor_clone {
|
|
39254
|
-
display: flex;
|
|
39255
|
-
/* To make clone same height as original we need to force it because context can impact height */
|
|
39256
|
-
/* Like siblings with a bigger height in a flex container */
|
|
39257
|
-
/* We subtract the border sizes as anchor-height includes borders in the dimensions */
|
|
39258
|
-
min-height: var(--anchor-inner-height);
|
|
39259
|
-
/* Mirror the trigger's padding so the clone looks identical */
|
|
39260
|
-
padding-top: var(--x-picker-padding-top);
|
|
39261
|
-
padding-right: var(--x-picker-padding-right);
|
|
39262
|
-
padding-bottom: var(--x-picker-padding-bottom);
|
|
39263
|
-
padding-left: var(--x-picker-padding-left);
|
|
39264
|
-
flex-shrink: 0;
|
|
39265
|
-
flex-direction: column;
|
|
39266
|
-
justify-content: center;
|
|
39267
|
-
gap: var(--navi-s);
|
|
39268
|
-
order: -1; /* before the list — popover is below the trigger */
|
|
39269
|
-
background: var(--x-picker-background-color);
|
|
39270
|
-
border-bottom: var(--picker-border-width) solid
|
|
39271
|
-
var(--x-picker-border-color);
|
|
39272
|
-
|
|
39273
|
-
&:hover {
|
|
39274
|
-
--x-picker-background-color: var(--picker-background-color-hover);
|
|
39275
|
-
--x-picker-border-color: var(--picker-border-color-hover);
|
|
39276
|
-
}
|
|
39277
|
-
}
|
|
39278
|
-
|
|
39279
|
-
&[data-position-y-current="top"],
|
|
39280
|
-
&[data-position-y-current="inset-bottom"] {
|
|
39281
|
-
.navi_picker_anchor_clone {
|
|
39282
|
-
order: 1; /* after the list — popover is above the trigger */
|
|
39283
|
-
border-top: var(--picker-border-width) solid
|
|
39284
|
-
var(--x-picker-border-color);
|
|
39285
|
-
border-bottom: none;
|
|
39286
|
-
}
|
|
39287
|
-
}
|
|
39288
|
-
|
|
39289
39481
|
/* The list scrolls inside the popover */
|
|
39290
39482
|
.navi_list_container {
|
|
39291
39483
|
width: 100%;
|
|
@@ -39299,16 +39491,15 @@ installImportMetaCssBuild(import.meta);const css$s = /* css */`
|
|
|
39299
39491
|
}
|
|
39300
39492
|
|
|
39301
39493
|
&[aria-expanded="true"] {
|
|
39302
|
-
&[navi-popover-mode="overlay"]
|
|
39303
|
-
&[navi-popover-mode="attached"] {
|
|
39494
|
+
&[navi-popover-mode="overlay"] {
|
|
39304
39495
|
/* When sizes uses float AND the border uses border-radius it's possible it's possible to see some pixels
|
|
39305
39496
|
of the underlying select borders. We hide them to ensure this cannot happen. */
|
|
39306
39497
|
border-color: transparent;
|
|
39307
39498
|
}
|
|
39308
39499
|
|
|
39309
39500
|
/* Popover itself has no opinion on its content's own layout (plain
|
|
39310
|
-
div, block by default) — the picker's content
|
|
39311
|
-
|
|
39501
|
+
div, block by default) — the picker's content needs to stack
|
|
39502
|
+
vertically. */
|
|
39312
39503
|
.navi_popover {
|
|
39313
39504
|
display: flex;
|
|
39314
39505
|
flex-direction: column;
|
|
@@ -39456,11 +39647,11 @@ const PickerCustom = props => {
|
|
|
39456
39647
|
const controlId = useContext(ControlIdContext);
|
|
39457
39648
|
props.id = props.id || controlId || idDefault;
|
|
39458
39649
|
// Same small-screen/maxWidth-compact heuristic Popup itself uses (see
|
|
39459
|
-
//
|
|
39650
|
+
// popup_mode.jsx's own useResolvedPopupMode) — frozen for the lifetime of an opening
|
|
39460
39651
|
// (computed when closed, stable while open, so a screen resize mid-session
|
|
39461
39652
|
// doesn't switch between Popover and Dialog), with resetMode called from
|
|
39462
39653
|
// this picker's own onClose below to re-evaluate on the *next* open.
|
|
39463
|
-
const [mode, resetMode] =
|
|
39654
|
+
const [mode, resetMode] = useResolvedPopupMode(modeProp, props.maxWidth);
|
|
39464
39655
|
const pickerProps = {
|
|
39465
39656
|
...props
|
|
39466
39657
|
};
|
|
@@ -39801,6 +39992,7 @@ const PickerContentInsidePopup = props => {
|
|
|
39801
39992
|
// defaulting the now-correctly-named prop to `true` would be a real,
|
|
39802
39993
|
// unintended behavior change riding along with the rename.
|
|
39803
39994
|
focusCapture,
|
|
39995
|
+
positionArea,
|
|
39804
39996
|
popoverMode = "nearby",
|
|
39805
39997
|
popoverSpacing = popoverMode === "nearby" ? 5 : 0,
|
|
39806
39998
|
marginWithContainer = 10,
|
|
@@ -39846,11 +40038,11 @@ const PickerContentInsidePopup = props => {
|
|
|
39846
40038
|
}
|
|
39847
40039
|
});
|
|
39848
40040
|
},
|
|
39849
|
-
children:
|
|
40041
|
+
children: jsx(Popup, {
|
|
39850
40042
|
...popupProps,
|
|
39851
40043
|
mode: mode,
|
|
39852
40044
|
animation: animation,
|
|
39853
|
-
positionArea: isPopover ? popoverMode === "nearby" ? "bottom-start" : "inset(top-left)" :
|
|
40045
|
+
positionArea: isPopover ? positionArea ?? (popoverMode === "nearby" ? "bottom-start" : "inset(top-left)") : positionArea,
|
|
39854
40046
|
marginWithAnchor: isPopover ? popoverSpacing : undefined,
|
|
39855
40047
|
marginWithContainer: isPopover ? marginWithContainer : undefined,
|
|
39856
40048
|
scrollCapture: scrollCapture === "dialog" ? !isPopover : scrollCapture === "popover" ? isPopover : scrollCapture,
|
|
@@ -39858,21 +40050,10 @@ const PickerContentInsidePopup = props => {
|
|
|
39858
40050
|
focusCapture: isPopover ? focusCapture : undefined,
|
|
39859
40051
|
expandX: !isPopover ? expandX : undefined,
|
|
39860
40052
|
expandY: !isPopover ? expandY : undefined,
|
|
39861
|
-
children:
|
|
39862
|
-
className: "navi_picker_anchor_clone",
|
|
39863
|
-
onMouseDown: e => {
|
|
39864
|
-
if (e.button !== 0) {
|
|
39865
|
-
return;
|
|
39866
|
-
}
|
|
39867
|
-
popupProps.openController.requestClose(e, {
|
|
39868
|
-
isCancel: true
|
|
39869
|
-
});
|
|
39870
|
-
},
|
|
39871
|
-
children: props.trigger
|
|
39872
|
-
}) : null, jsx(PickerModeContext.Provider, {
|
|
40053
|
+
children: jsx(PopupModeContext.Provider, {
|
|
39873
40054
|
value: mode,
|
|
39874
40055
|
children: children
|
|
39875
|
-
})
|
|
40056
|
+
})
|
|
39876
40057
|
})
|
|
39877
40058
|
});
|
|
39878
40059
|
};
|
|
@@ -40512,6 +40693,84 @@ const toDate = (value, parseString) => {
|
|
|
40512
40693
|
return null;
|
|
40513
40694
|
};
|
|
40514
40695
|
|
|
40696
|
+
const LoadingDotsSvg = () => {
|
|
40697
|
+
return jsxs("svg", {
|
|
40698
|
+
viewBox: "0 0 200 200",
|
|
40699
|
+
width: "100%",
|
|
40700
|
+
height: "100%",
|
|
40701
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
40702
|
+
children: [jsx("rect", {
|
|
40703
|
+
fill: "currentColor",
|
|
40704
|
+
stroke: "currentColor",
|
|
40705
|
+
"stroke-width": "15",
|
|
40706
|
+
width: "30",
|
|
40707
|
+
height: "30",
|
|
40708
|
+
x: "25",
|
|
40709
|
+
y: "85",
|
|
40710
|
+
children: jsx("animate", {
|
|
40711
|
+
attributeName: "opacity",
|
|
40712
|
+
calcMode: "spline",
|
|
40713
|
+
dur: "2",
|
|
40714
|
+
values: "1;0;1;",
|
|
40715
|
+
keySplines: ".5 0 .5 1;.5 0 .5 1",
|
|
40716
|
+
repeatCount: "indefinite",
|
|
40717
|
+
begin: "-.4"
|
|
40718
|
+
})
|
|
40719
|
+
}), jsx("rect", {
|
|
40720
|
+
fill: "currentColor",
|
|
40721
|
+
stroke: "currentColor",
|
|
40722
|
+
"stroke-width": "15",
|
|
40723
|
+
width: "30",
|
|
40724
|
+
height: "30",
|
|
40725
|
+
x: "85",
|
|
40726
|
+
y: "85",
|
|
40727
|
+
children: jsx("animate", {
|
|
40728
|
+
attributeName: "opacity",
|
|
40729
|
+
calcMode: "spline",
|
|
40730
|
+
dur: "2",
|
|
40731
|
+
values: "1;0;1;",
|
|
40732
|
+
keySplines: ".5 0 .5 1;.5 0 .5 1",
|
|
40733
|
+
repeatCount: "indefinite",
|
|
40734
|
+
begin: "-.2"
|
|
40735
|
+
})
|
|
40736
|
+
}), jsx("rect", {
|
|
40737
|
+
fill: "currentColor",
|
|
40738
|
+
stroke: "currentColor",
|
|
40739
|
+
"stroke-width": "15",
|
|
40740
|
+
width: "30",
|
|
40741
|
+
height: "30",
|
|
40742
|
+
x: "145",
|
|
40743
|
+
y: "85",
|
|
40744
|
+
children: jsx("animate", {
|
|
40745
|
+
attributeName: "opacity",
|
|
40746
|
+
calcMode: "spline",
|
|
40747
|
+
dur: "2",
|
|
40748
|
+
values: "1;0;1;",
|
|
40749
|
+
keySplines: ".5 0 .5 1;.5 0 .5 1",
|
|
40750
|
+
repeatCount: "indefinite",
|
|
40751
|
+
begin: "0"
|
|
40752
|
+
})
|
|
40753
|
+
})]
|
|
40754
|
+
});
|
|
40755
|
+
};
|
|
40756
|
+
|
|
40757
|
+
const LoadingIndicator = ({
|
|
40758
|
+
variant = "circle",
|
|
40759
|
+
...props
|
|
40760
|
+
}) => {
|
|
40761
|
+
if (variant === "dots") {
|
|
40762
|
+
return jsx(Icon, {
|
|
40763
|
+
...props,
|
|
40764
|
+
children: jsx(LoadingDotsSvg, {})
|
|
40765
|
+
});
|
|
40766
|
+
}
|
|
40767
|
+
return jsx(Icon, {
|
|
40768
|
+
circle: true,
|
|
40769
|
+
...props,
|
|
40770
|
+
children: jsx(LoadingIndicatorFluid, {})
|
|
40771
|
+
});
|
|
40772
|
+
};
|
|
40773
|
+
|
|
40515
40774
|
installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
40516
40775
|
@layer navi {
|
|
40517
40776
|
.navi_separator {
|
|
@@ -41266,6 +41525,11 @@ const ListSelectable = props => {
|
|
|
41266
41525
|
"navi-has-selected-background": selectedIndicator === "backgroundColor" ? "" : undefined,
|
|
41267
41526
|
...listControlRootProps,
|
|
41268
41527
|
...listControlProps,
|
|
41528
|
+
// "loading" is a control prop, so useControlgroupProps consumes it (into
|
|
41529
|
+
// aria-busy / the :-navi-loading pseudo state) and it does not survive
|
|
41530
|
+
// into the props below. ListUI needs it too — it is what makes the list
|
|
41531
|
+
// render skeleton rows instead of its (not yet known) items.
|
|
41532
|
+
loading: props.loading,
|
|
41269
41533
|
name: undefined,
|
|
41270
41534
|
value: undefined,
|
|
41271
41535
|
defaultValue: undefined,
|
|
@@ -41789,6 +42053,16 @@ const css$p = /* css */`
|
|
|
41789
42053
|
}
|
|
41790
42054
|
&[data-expand-y] {
|
|
41791
42055
|
--list-max-height: none;
|
|
42056
|
+
|
|
42057
|
+
/* expandY grows the container to fill its parent (flex-grow, applied by
|
|
42058
|
+
Box). The scroll container must then fill that grown height and take
|
|
42059
|
+
over the internal scroll — flex:1 fills it, min-height:0 lets it shrink
|
|
42060
|
+
below its content so overflow:auto scrolls instead of the content
|
|
42061
|
+
pushing past the container (which overflow:hidden would just clip). */
|
|
42062
|
+
.navi_list_scroll_container {
|
|
42063
|
+
min-height: 0;
|
|
42064
|
+
flex: 1;
|
|
42065
|
+
}
|
|
41792
42066
|
}
|
|
41793
42067
|
&[navi-nothing-to-display] {
|
|
41794
42068
|
display: none;
|
|
@@ -41891,7 +42165,11 @@ const css$p = /* css */`
|
|
|
41891
42165
|
scroll-margin-bottom: var(--x-list-scroll-spacing-bottom);
|
|
41892
42166
|
scroll-margin-left: var(--x-list-scroll-spacing-left);
|
|
41893
42167
|
|
|
41894
|
-
|
|
42168
|
+
/* The "invisible_and_inert" search no-match mode keeps items in the DOM
|
|
42169
|
+
(to preserve layout) but hides them — it sets BOTH aria-hidden and inert.
|
|
42170
|
+
Scope to that pair so the presentation placeholders that are only
|
|
42171
|
+
aria-hidden (skeleton rows, the loader) stay visible. */
|
|
42172
|
+
&[aria-hidden="true"][inert] {
|
|
41895
42173
|
opacity: 0;
|
|
41896
42174
|
}
|
|
41897
42175
|
|
|
@@ -42000,6 +42278,39 @@ const css$p = /* css */`
|
|
|
42000
42278
|
user-select: none;
|
|
42001
42279
|
}
|
|
42002
42280
|
}
|
|
42281
|
+
/* Loading placeholders (see List's loading / loadingIndicator / skeletonTemplate).
|
|
42282
|
+
A skeleton row reuses <Text loading> for the shimmer bar; the loader row
|
|
42283
|
+
centers a spinner. */
|
|
42284
|
+
.navi_list_item_skeleton {
|
|
42285
|
+
pointer-events: none;
|
|
42286
|
+
}
|
|
42287
|
+
.navi_list_loader {
|
|
42288
|
+
display: flex;
|
|
42289
|
+
padding: 12px;
|
|
42290
|
+
align-items: center;
|
|
42291
|
+
justify-content: center;
|
|
42292
|
+
color: light-dark(#888, #aaa);
|
|
42293
|
+
}
|
|
42294
|
+
/* Error state (List error prop): an inline callout describing why the list
|
|
42295
|
+
failed to load, shown in place of the items. */
|
|
42296
|
+
.navi_list_error {
|
|
42297
|
+
display: flex;
|
|
42298
|
+
margin: 8px;
|
|
42299
|
+
padding: 10px 12px;
|
|
42300
|
+
align-items: flex-start;
|
|
42301
|
+
gap: 8px;
|
|
42302
|
+
color: light-dark(#b91c1c, #fca5a5);
|
|
42303
|
+
font-size: 0.9em;
|
|
42304
|
+
line-height: 1.4;
|
|
42305
|
+
background: light-dark(#fef2f2, rgba(127, 29, 29, 0.25));
|
|
42306
|
+
border: 1px solid light-dark(#fecaca, rgba(248, 113, 113, 0.4));
|
|
42307
|
+
border-radius: 6px;
|
|
42308
|
+
}
|
|
42309
|
+
.navi_list_error_icon {
|
|
42310
|
+
flex: none;
|
|
42311
|
+
font-size: 1em;
|
|
42312
|
+
line-height: 1.4;
|
|
42313
|
+
}
|
|
42003
42314
|
[navi-virtual-filler="after"] {
|
|
42004
42315
|
/* for some reason preact ends up puttin this element before the list items in some scenarios
|
|
42005
42316
|
I've noticed that removing the ItemIndexToScrollOnMountRefContext.Provider
|
|
@@ -42090,6 +42401,11 @@ const ListUI = props => {
|
|
|
42090
42401
|
columns,
|
|
42091
42402
|
searchText,
|
|
42092
42403
|
searchNoMatchMode = "remove",
|
|
42404
|
+
loading,
|
|
42405
|
+
loadingIndicator = "skeleton",
|
|
42406
|
+
loadingSkeletonCount = 3,
|
|
42407
|
+
skeletonTemplate,
|
|
42408
|
+
error,
|
|
42093
42409
|
horizontal,
|
|
42094
42410
|
spacing,
|
|
42095
42411
|
...rest
|
|
@@ -42161,9 +42477,63 @@ const ListUI = props => {
|
|
|
42161
42477
|
const noMatchCount = tracker.noMatchCountSignal.value;
|
|
42162
42478
|
const itemCount = tracker.countSignal.value;
|
|
42163
42479
|
const allNoMatch = noMatchCount > 0 && noMatchCount === itemCount;
|
|
42480
|
+
const searching = Boolean(searchText);
|
|
42164
42481
|
const fallbackDisabled = fallback !== undefined && !fallback;
|
|
42165
42482
|
const searchFallbackDisabled = searchFallback !== undefined && !searchFallback;
|
|
42166
|
-
|
|
42483
|
+
// No item is visible when the list is empty (filtering may happen outside the
|
|
42484
|
+
// list, dropping itemCount to 0) or when a search removed them all — only the
|
|
42485
|
+
// "remove" mode empties the view; "muted"/"below"/… keep items on screen.
|
|
42486
|
+
const noVisibleItems = itemCount === 0 || allNoMatch && searchNoMatchMode === "remove";
|
|
42487
|
+
// Which fallback message actually renders (mirrors SearchFallback / Fallback
|
|
42488
|
+
// below): during a search an empty/no-match result is a "no match" state (the
|
|
42489
|
+
// search fallback), otherwise an empty list is the "empty" state.
|
|
42490
|
+
const searchFallbackShown = (allNoMatch || searching && itemCount === 0) && !searchFallbackDisabled;
|
|
42491
|
+
const emptyFallbackShown = !searching && itemCount === 0 && !fallbackDisabled;
|
|
42492
|
+
// Hide the whole list — border included — when there is genuinely nothing to
|
|
42493
|
+
// show: no visible items AND no fallback message. Never while loading or in
|
|
42494
|
+
// error (the placeholder / error message ARE the content to display).
|
|
42495
|
+
const nothingToDisplay = !loading && !error && noVisibleItems && !searchFallbackShown && !emptyFallbackShown;
|
|
42496
|
+
|
|
42497
|
+
// Placeholder content replaces the real children: an error message when the
|
|
42498
|
+
// load failed (takes precedence), otherwise — while loading — skeleton rows
|
|
42499
|
+
// (default, count loadingSkeletonCount, look skeletonTemplate) or a single
|
|
42500
|
+
// centered loader when loadingIndicator="loader".
|
|
42501
|
+
let content = children;
|
|
42502
|
+
if (error) {
|
|
42503
|
+
content = jsxs(ListItem, {
|
|
42504
|
+
role: "presentation",
|
|
42505
|
+
baseClassName: "navi_list_item navi_list_error",
|
|
42506
|
+
children: [jsx("span", {
|
|
42507
|
+
className: "navi_list_error_icon",
|
|
42508
|
+
"aria-hidden": "true",
|
|
42509
|
+
children: "\u26A0"
|
|
42510
|
+
}), jsx("span", {
|
|
42511
|
+
children: error === true ? "Something went wrong." : error
|
|
42512
|
+
})]
|
|
42513
|
+
});
|
|
42514
|
+
} else if (loading) {
|
|
42515
|
+
if (loadingIndicator === "loader") {
|
|
42516
|
+
content = jsx(ListItem, {
|
|
42517
|
+
role: "presentation",
|
|
42518
|
+
"aria-hidden": "true",
|
|
42519
|
+
baseClassName: "navi_list_item navi_list_loader",
|
|
42520
|
+
children: jsx(LoadingIndicator, {})
|
|
42521
|
+
});
|
|
42522
|
+
} else {
|
|
42523
|
+
const template = skeletonTemplate ?? jsx(ListItem, {
|
|
42524
|
+
skeleton: true
|
|
42525
|
+
});
|
|
42526
|
+
const skeletons = [];
|
|
42527
|
+
let skeletonIndex = 0;
|
|
42528
|
+
while (skeletonIndex < loadingSkeletonCount) {
|
|
42529
|
+
skeletons.push(cloneElement(template, {
|
|
42530
|
+
key: `navi-list-skeleton-${skeletonIndex}`
|
|
42531
|
+
}));
|
|
42532
|
+
skeletonIndex++;
|
|
42533
|
+
}
|
|
42534
|
+
content = skeletons;
|
|
42535
|
+
}
|
|
42536
|
+
}
|
|
42167
42537
|
return jsx(Box, {
|
|
42168
42538
|
...rest,
|
|
42169
42539
|
ref: ref,
|
|
@@ -42177,6 +42547,8 @@ const ListUI = props => {
|
|
|
42177
42547
|
expand: expand,
|
|
42178
42548
|
"navi-zero-match": allNoMatch ? "" : undefined,
|
|
42179
42549
|
"navi-nothing-to-display": nothingToDisplay ? "" : undefined,
|
|
42550
|
+
"navi-loading": loading ? "" : undefined,
|
|
42551
|
+
"navi-error": error ? "" : undefined,
|
|
42180
42552
|
styleCSSVars: LIST_STYLE_CSS_VARS,
|
|
42181
42553
|
pseudoClasses: LIST_PSEUDO_CLASSES,
|
|
42182
42554
|
hasChildUsingForwardedProps: true,
|
|
@@ -42198,6 +42570,9 @@ const ListUI = props => {
|
|
|
42198
42570
|
role: role,
|
|
42199
42571
|
fallback: fallback,
|
|
42200
42572
|
searchFallback: searchFallback,
|
|
42573
|
+
searching: searching,
|
|
42574
|
+
loading: loading,
|
|
42575
|
+
error: error,
|
|
42201
42576
|
searchNoMatchMode: searchNoMatchMode,
|
|
42202
42577
|
separator: separator,
|
|
42203
42578
|
expandX: expandX || expand,
|
|
@@ -42208,7 +42583,7 @@ const ListUI = props => {
|
|
|
42208
42583
|
renderWindow: renderWindow,
|
|
42209
42584
|
virtualItemSizeSignal: virtualItemSizeSignal,
|
|
42210
42585
|
pendingScrollRef: pendingScrollRef,
|
|
42211
|
-
children:
|
|
42586
|
+
children: content
|
|
42212
42587
|
})
|
|
42213
42588
|
});
|
|
42214
42589
|
};
|
|
@@ -42238,6 +42613,11 @@ const ListFirstResolver = props => {
|
|
|
42238
42613
|
* searchFallback?: import("ignore:preact").ComponentChildren,
|
|
42239
42614
|
* searchText?: string,
|
|
42240
42615
|
* searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted" | "below",
|
|
42616
|
+
* loading?: boolean,
|
|
42617
|
+
* loadingIndicator?: "skeleton" | "loader",
|
|
42618
|
+
* loadingSkeletonCount?: number,
|
|
42619
|
+
* skeletonTemplate?: import("ignore:preact").ComponentChildren,
|
|
42620
|
+
* error?: boolean | import("ignore:preact").ComponentChildren,
|
|
42241
42621
|
* separator?: boolean | import("ignore:preact").ComponentChildren,
|
|
42242
42622
|
* lockSize?: boolean,
|
|
42243
42623
|
* horizontal?: boolean,
|
|
@@ -42255,6 +42635,9 @@ const ListContent = ({
|
|
|
42255
42635
|
role,
|
|
42256
42636
|
fallback,
|
|
42257
42637
|
searchFallback,
|
|
42638
|
+
searching,
|
|
42639
|
+
loading,
|
|
42640
|
+
error,
|
|
42258
42641
|
searchNoMatchMode,
|
|
42259
42642
|
separator,
|
|
42260
42643
|
expandX,
|
|
@@ -42274,6 +42657,9 @@ const ListContent = ({
|
|
|
42274
42657
|
role: role,
|
|
42275
42658
|
fallback: fallback,
|
|
42276
42659
|
searchFallback: searchFallback,
|
|
42660
|
+
searching: searching,
|
|
42661
|
+
loading: loading,
|
|
42662
|
+
error: error,
|
|
42277
42663
|
searchNoMatchMode: searchNoMatchMode,
|
|
42278
42664
|
separator: separator === true ? jsx(Separator, {
|
|
42279
42665
|
margin: "0"
|
|
@@ -42772,6 +43158,9 @@ const UnorderedList = ({
|
|
|
42772
43158
|
virtualItemSizeSignal,
|
|
42773
43159
|
fallback,
|
|
42774
43160
|
searchFallback,
|
|
43161
|
+
searching,
|
|
43162
|
+
loading,
|
|
43163
|
+
error,
|
|
42775
43164
|
searchNoMatchMode,
|
|
42776
43165
|
separator,
|
|
42777
43166
|
horizontal,
|
|
@@ -42780,6 +43169,9 @@ const UnorderedList = ({
|
|
|
42780
43169
|
children,
|
|
42781
43170
|
...rest
|
|
42782
43171
|
}) => {
|
|
43172
|
+
// No empty/no-match message while loading or in error — the placeholder /
|
|
43173
|
+
// error message is the content, even though no items are tracked yet.
|
|
43174
|
+
const suppressFallback = loading || Boolean(error);
|
|
42783
43175
|
return jsxs(Box, {
|
|
42784
43176
|
as: "ul",
|
|
42785
43177
|
flex: columns ? undefined : horizontal ? "x" : "y",
|
|
@@ -42791,11 +43183,13 @@ const UnorderedList = ({
|
|
|
42791
43183
|
children: [jsx(BeforeFiller, {
|
|
42792
43184
|
virtualItemSizeSignal: virtualItemSizeSignal,
|
|
42793
43185
|
renderWindowStart: renderWindow.start
|
|
42794
|
-
}), jsx(SearchFallback, {
|
|
43186
|
+
}), !suppressFallback && jsx(SearchFallback, {
|
|
42795
43187
|
searchFallback: searchFallback,
|
|
43188
|
+
searching: searching,
|
|
42796
43189
|
tracker: tracker
|
|
42797
|
-
}), jsx(Fallback, {
|
|
43190
|
+
}), !suppressFallback && jsx(Fallback, {
|
|
42798
43191
|
fallback: fallback,
|
|
43192
|
+
searching: searching,
|
|
42799
43193
|
tracker: tracker
|
|
42800
43194
|
}), jsx(SearchNoMatchModeContext.Provider, {
|
|
42801
43195
|
value: searchNoMatchMode,
|
|
@@ -42820,16 +43214,18 @@ const UnorderedList = ({
|
|
|
42820
43214
|
});
|
|
42821
43215
|
};
|
|
42822
43216
|
|
|
42823
|
-
//
|
|
42824
|
-
//
|
|
42825
|
-
//
|
|
43217
|
+
// The "no match" message. Shown when a search left nothing to display: either
|
|
43218
|
+
// every matchable item has match=false (in-list filtering), or the list is empty
|
|
43219
|
+
// during an active search (filtering done outside the list, so itemCount is 0).
|
|
42826
43220
|
const SearchFallback = ({
|
|
42827
43221
|
tracker,
|
|
42828
|
-
searchFallback
|
|
43222
|
+
searchFallback,
|
|
43223
|
+
searching
|
|
42829
43224
|
}) => {
|
|
42830
43225
|
const itemCount = tracker.countSignal.value;
|
|
42831
43226
|
const noMatchCount = tracker.noMatchCountSignal.value;
|
|
42832
|
-
const
|
|
43227
|
+
const allNoMatch = noMatchCount > 0 && noMatchCount === itemCount;
|
|
43228
|
+
const showMatchFallback = allNoMatch || searching && itemCount === 0;
|
|
42833
43229
|
if (searchFallback === undefined) {
|
|
42834
43230
|
searchFallback = naviI18n("list.no_match");
|
|
42835
43231
|
}
|
|
@@ -42848,15 +43244,22 @@ const SearchFallback = ({
|
|
|
42848
43244
|
children: searchFallback
|
|
42849
43245
|
});
|
|
42850
43246
|
};
|
|
43247
|
+
// The "empty list" message. Not shown during a search — an empty search result
|
|
43248
|
+
// is a "no match" state (SearchFallback), not an empty-list state.
|
|
42851
43249
|
const Fallback = ({
|
|
42852
43250
|
tracker,
|
|
42853
|
-
fallback
|
|
43251
|
+
fallback,
|
|
43252
|
+
searching
|
|
42854
43253
|
}) => {
|
|
42855
43254
|
const itemCount = tracker.countSignal.value;
|
|
42856
|
-
const showFallback = itemCount === 0;
|
|
43255
|
+
const showFallback = itemCount === 0 && !searching;
|
|
42857
43256
|
if (fallback === undefined) {
|
|
42858
43257
|
fallback = naviI18n("list.empty");
|
|
42859
43258
|
}
|
|
43259
|
+
if (!fallback) {
|
|
43260
|
+
// explicitely disabled by user (<List fallback={false|null|''}>)
|
|
43261
|
+
return null;
|
|
43262
|
+
}
|
|
42860
43263
|
if (!showFallback) {
|
|
42861
43264
|
return null;
|
|
42862
43265
|
}
|
|
@@ -42970,11 +43373,57 @@ const ListItemPresentation = props => {
|
|
|
42970
43373
|
...columnsOverrideProps
|
|
42971
43374
|
});
|
|
42972
43375
|
};
|
|
43376
|
+
// A <List.Item skeleton> — a non-interactive placeholder row shown while a list
|
|
43377
|
+
// is loading. It is presentation-only (not tracked, not selectable, aria-hidden)
|
|
43378
|
+
// and reuses <Text loading> for the shimmer. Box layout props (padding, spacing…)
|
|
43379
|
+
// pass through so a skeletonTemplate can match the real items' metrics; and when
|
|
43380
|
+
// children are provided they render as-is, so a template can reproduce a
|
|
43381
|
+
// multi-part item (e.g. title + subtitle) out of several <Text loading> bars.
|
|
43382
|
+
const ListItemSkeletonResolver = props => {
|
|
43383
|
+
const Next = useNextResolver();
|
|
43384
|
+
if (props.skeleton) {
|
|
43385
|
+
return jsx(ListItemSkeleton, {
|
|
43386
|
+
...props
|
|
43387
|
+
});
|
|
43388
|
+
}
|
|
43389
|
+
return jsx(Next, {
|
|
43390
|
+
...props
|
|
43391
|
+
});
|
|
43392
|
+
};
|
|
43393
|
+
const ListItemSkeleton = props => {
|
|
43394
|
+
// Without vertical padding the bars of consecutive rows touch and read as one
|
|
43395
|
+
// block; "s" is enough air for them to be seen as separate rows.
|
|
43396
|
+
// eslint-disable-next-line no-unused-vars
|
|
43397
|
+
const {
|
|
43398
|
+
skeleton,
|
|
43399
|
+
children,
|
|
43400
|
+
paddingY = "s",
|
|
43401
|
+
...rest
|
|
43402
|
+
} = props;
|
|
43403
|
+
const columnsOverrideProps = useListItemColumnsOverrideProps(rest.style);
|
|
43404
|
+
return jsx(Box, {
|
|
43405
|
+
as: "li",
|
|
43406
|
+
role: "presentation",
|
|
43407
|
+
"aria-hidden": "true",
|
|
43408
|
+
paddingY: paddingY,
|
|
43409
|
+
...rest,
|
|
43410
|
+
...columnsOverrideProps,
|
|
43411
|
+
baseClassName: "navi_list_item navi_list_item_skeleton",
|
|
43412
|
+
children: children ?? jsx(Text, {
|
|
43413
|
+
loading: true
|
|
43414
|
+
})
|
|
43415
|
+
});
|
|
43416
|
+
};
|
|
42973
43417
|
const ListItemUI = props => {
|
|
42974
|
-
|
|
43418
|
+
// A stable id/index only matters when the item's identity must survive
|
|
43419
|
+
// reordering — i.e. it is selectable (selected/pointed state) or participates
|
|
43420
|
+
// in a matching system (search reorders items). A purely presentational,
|
|
43421
|
+
// static list doesn't need either, so don't nag about them there.
|
|
43422
|
+
const identityMatters = props.selectable || Boolean(props.matchInfo) || props.value !== undefined;
|
|
43423
|
+
if (identityMatters && props.id === undefined) {
|
|
42975
43424
|
console.warn("ListItem is missing an explicit id prop. Provide a stable id so pointed/selected state survives search reordering.");
|
|
42976
43425
|
}
|
|
42977
|
-
if (props.index === undefined) {
|
|
43426
|
+
if (identityMatters && props.index === undefined) {
|
|
42978
43427
|
console.warn("ListItem is missing an explicit index prop. Provide an index so item ordering is stable regardless of render order.");
|
|
42979
43428
|
}
|
|
42980
43429
|
const idDefault = useId();
|
|
@@ -42987,6 +43436,13 @@ const ListItemUI = props => {
|
|
|
42987
43436
|
// (e.g. useSearchText's getItemMatchInfo(item): { match, matchScore,
|
|
42988
43437
|
// matchRanges }), so there is exactly one way to wire it up.
|
|
42989
43438
|
const matchInfo = props.matchInfo;
|
|
43439
|
+
// Expose match on the tracked item: the tracker counts non-matching items via
|
|
43440
|
+
// `item.match === false` (drives noMatchCount → allNoMatch → the searchFallback
|
|
43441
|
+
// / hide-when-empty behavior). Without this a matchInfo-based search would
|
|
43442
|
+
// filter items out but never register them as "no match".
|
|
43443
|
+
if (matchInfo) {
|
|
43444
|
+
props.match = matchInfo.match;
|
|
43445
|
+
}
|
|
42990
43446
|
// Derive filtered/hidden/muted from matchInfo.match + searchNoMatchMode context.
|
|
42991
43447
|
if (matchInfo?.match === false) {
|
|
42992
43448
|
if (searchNoMatchMode === "remove") {
|
|
@@ -43160,6 +43616,10 @@ const LIST_ITEM_PSEUDO_ELEMENTS = ["::highlight"];
|
|
|
43160
43616
|
* selectable — when true, the item participates in selection (radio or checkbox
|
|
43161
43617
|
* depending on whether the parent List has `multiple`). Requires
|
|
43162
43618
|
* `value` and typically a <SelectableInput /> child.
|
|
43619
|
+
* skeleton — render a non-interactive placeholder row (a shimmering bar)
|
|
43620
|
+
* instead of a real item. Used as the List `skeletonTemplate`
|
|
43621
|
+
* while `loading`; Box layout props (padding…) pass through so the
|
|
43622
|
+
* placeholder can match the real items' metrics.
|
|
43163
43623
|
* value — the JS value emitted by the list's action/uiAction when this item
|
|
43164
43624
|
* is selected. Can be any type (string, number, object…).
|
|
43165
43625
|
* selected — controlled selected state. Pass `selected === value` (single) or
|
|
@@ -43186,7 +43646,7 @@ const LIST_ITEM_PSEUDO_ELEMENTS = ["::highlight"];
|
|
|
43186
43646
|
* CSS Highlight API.
|
|
43187
43647
|
* ...rest — forwarded to the rendered <li> element
|
|
43188
43648
|
*/
|
|
43189
|
-
const ListItem = createComponentResolver([ListItemFirstResolver, ListItemSelectableResolver, ListItemHeaderOrFooterResolver, ListItemPresentationResolver, ListItemUI]);
|
|
43649
|
+
const ListItem = createComponentResolver([ListItemFirstResolver, ListItemSkeletonResolver, ListItemSelectableResolver, ListItemHeaderOrFooterResolver, ListItemPresentationResolver, ListItemUI]);
|
|
43190
43650
|
List.Item = ListItem;
|
|
43191
43651
|
|
|
43192
43652
|
/**
|
|
@@ -44291,7 +44751,8 @@ const PickerButton = props => {
|
|
|
44291
44751
|
// --anchor-width). Set true when the CONTENT should dictate the popover width
|
|
44292
44752
|
// (e.g. a Wheel) instead of being stretched to the trigger — see
|
|
44293
44753
|
// picker_custom.jsx.
|
|
44294
|
-
popupWidthFitContent
|
|
44754
|
+
popupWidthFitContent,
|
|
44755
|
+
error
|
|
44295
44756
|
} = props;
|
|
44296
44757
|
const isSingleLine = maxLines === 1;
|
|
44297
44758
|
const inputRef = useRef(null);
|
|
@@ -44308,6 +44769,7 @@ const PickerButton = props => {
|
|
|
44308
44769
|
children
|
|
44309
44770
|
} = inputProps;
|
|
44310
44771
|
const loading = basePseudoState[":-navi-loading"];
|
|
44772
|
+
usePickerErrorCallout(uiStateController, error);
|
|
44311
44773
|
return jsxs(Box, {
|
|
44312
44774
|
as: "div",
|
|
44313
44775
|
ref: ref,
|
|
@@ -44327,6 +44789,7 @@ const PickerButton = props => {
|
|
|
44327
44789
|
ui: undefined,
|
|
44328
44790
|
maxLines: undefined,
|
|
44329
44791
|
popupWidthFitContent: undefined,
|
|
44792
|
+
error: undefined,
|
|
44330
44793
|
dayLabel: undefined
|
|
44331
44794
|
// This wrapper will receive keyboard event bubbling from the picker popup content
|
|
44332
44795
|
// we re-dispatch on the input (to get escape to close for instance)
|
|
@@ -44451,6 +44914,33 @@ const PickerButton = props => {
|
|
|
44451
44914
|
const isWithinPickerContent = (el, pickerEl) => {
|
|
44452
44915
|
return pickerEl.querySelector(".navi_picker_content")?.contains(el);
|
|
44453
44916
|
};
|
|
44917
|
+
const PICKER_ERROR_TOKEN = createOpenToken();
|
|
44918
|
+
// The `error` prop rides the control's own callout — the same surface already
|
|
44919
|
+
// used for failing constraints — so a caller never has to decide where to put
|
|
44920
|
+
// the message. It shows whether the popup is open or closed, and dismissing it
|
|
44921
|
+
// discards that error: only a new `error` value raises another one.
|
|
44922
|
+
const usePickerErrorCallout = (uiStateController, error) => {
|
|
44923
|
+
useEffect(() => {
|
|
44924
|
+
const {
|
|
44925
|
+
callout
|
|
44926
|
+
} = uiStateController.rules;
|
|
44927
|
+
const closeEvent = new CustomEvent("picker_error_cleared", {
|
|
44928
|
+
detail: {}
|
|
44929
|
+
});
|
|
44930
|
+
if (!error) {
|
|
44931
|
+
callout.removeOpenToken(PICKER_ERROR_TOKEN, closeEvent);
|
|
44932
|
+
return undefined;
|
|
44933
|
+
}
|
|
44934
|
+
callout.addOpenToken(PICKER_ERROR_TOKEN, {
|
|
44935
|
+
message: error === true ? "Something went wrong." : error,
|
|
44936
|
+
status: "error",
|
|
44937
|
+
skipFocus: true
|
|
44938
|
+
});
|
|
44939
|
+
return () => {
|
|
44940
|
+
callout.removeOpenToken(PICKER_ERROR_TOKEN, closeEvent);
|
|
44941
|
+
};
|
|
44942
|
+
}, [error]);
|
|
44943
|
+
};
|
|
44454
44944
|
const PickerInput = props => {
|
|
44455
44945
|
const {
|
|
44456
44946
|
ui,
|
|
@@ -44577,10 +45067,14 @@ const PickerFirstResolver = props => {
|
|
|
44577
45067
|
* step?: string | number,
|
|
44578
45068
|
* disabled?: boolean,
|
|
44579
45069
|
* readOnly?: boolean,
|
|
45070
|
+
* error?: boolean | string,
|
|
44580
45071
|
* uiAction?: (value: any, event: Event) => void,
|
|
44581
45072
|
* action?: (value: any, event: Event) => void,
|
|
44582
45073
|
* children?: import("ignore:preact").ComponentChildren,
|
|
44583
45074
|
* mode?: "popover" | "dialog",
|
|
45075
|
+
* popoverMode?: "nearby" | "overlay",
|
|
45076
|
+
* positionArea?: string,
|
|
45077
|
+
* popupWidthFitContent?: boolean,
|
|
44584
45078
|
* variant?: "icon" | "headless",
|
|
44585
45079
|
* icon?: import("ignore:preact").ComponentChildren,
|
|
44586
45080
|
* maxLines?: number,
|
|
@@ -44593,6 +45087,24 @@ const PickerFirstResolver = props => {
|
|
|
44593
45087
|
* ref?: import("ignore:preact").RefObject<HTMLElement>,
|
|
44594
45088
|
* [key: string]: any,
|
|
44595
45089
|
* }>}
|
|
45090
|
+
* @param {boolean|string} [error] Something went wrong around this picker (its
|
|
45091
|
+
* content failed to load, its value could not be resolved…). Shown as a
|
|
45092
|
+
* callout on the trigger, open or closed — the caller has nothing to place.
|
|
45093
|
+
* Dismissing it discards that error; a new `error` value raises another one.
|
|
45094
|
+
* @param {"nearby"|"overlay"} [popoverMode="nearby"] "overlay" lays the popover
|
|
45095
|
+
* over the trigger, "nearby" leaves a small gap below it.
|
|
45096
|
+
* @param {string} [positionArea] Where the popup goes — relative to the trigger
|
|
45097
|
+
* in popover mode, relative to the viewport in dialog mode. Same grammar as
|
|
45098
|
+
* Popover/Dialog's own `positionArea` ("top", "right-end", "inset(top-left)",
|
|
45099
|
+
* …). Defaults to "bottom-start" in popover mode ("inset(top-left)" when
|
|
45100
|
+
* popoverMode is "overlay"), and to Dialog's own "center" in dialog mode. A
|
|
45101
|
+
* popover still flips to the opposite side on its own when there isn't
|
|
45102
|
+
* enough room.
|
|
45103
|
+
* @param {boolean} [popupWidthFitContent] By default the popup is at least as
|
|
45104
|
+
* wide as the trigger. Set this to let the content size it instead, so a
|
|
45105
|
+
* popup narrower than the trigger stays narrow.
|
|
45106
|
+
* @param {number|string} [popoverMaxHeight] Soft cap on the popover's height
|
|
45107
|
+
* (default 300px). The popover shrinks below it when space is tight.
|
|
44596
45108
|
*/
|
|
44597
45109
|
const Picker = createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
44598
45110
|
Picker.UI = PickerDefaultUI;
|
|
@@ -50764,67 +51276,6 @@ const Address = ({
|
|
|
50764
51276
|
});
|
|
50765
51277
|
};
|
|
50766
51278
|
|
|
50767
|
-
const LoadingDotsSvg = () => {
|
|
50768
|
-
return jsxs("svg", {
|
|
50769
|
-
viewBox: "0 0 200 200",
|
|
50770
|
-
width: "100%",
|
|
50771
|
-
height: "100%",
|
|
50772
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
50773
|
-
children: [jsx("rect", {
|
|
50774
|
-
fill: "currentColor",
|
|
50775
|
-
stroke: "currentColor",
|
|
50776
|
-
"stroke-width": "15",
|
|
50777
|
-
width: "30",
|
|
50778
|
-
height: "30",
|
|
50779
|
-
x: "25",
|
|
50780
|
-
y: "85",
|
|
50781
|
-
children: jsx("animate", {
|
|
50782
|
-
attributeName: "opacity",
|
|
50783
|
-
calcMode: "spline",
|
|
50784
|
-
dur: "2",
|
|
50785
|
-
values: "1;0;1;",
|
|
50786
|
-
keySplines: ".5 0 .5 1;.5 0 .5 1",
|
|
50787
|
-
repeatCount: "indefinite",
|
|
50788
|
-
begin: "-.4"
|
|
50789
|
-
})
|
|
50790
|
-
}), jsx("rect", {
|
|
50791
|
-
fill: "currentColor",
|
|
50792
|
-
stroke: "currentColor",
|
|
50793
|
-
"stroke-width": "15",
|
|
50794
|
-
width: "30",
|
|
50795
|
-
height: "30",
|
|
50796
|
-
x: "85",
|
|
50797
|
-
y: "85",
|
|
50798
|
-
children: jsx("animate", {
|
|
50799
|
-
attributeName: "opacity",
|
|
50800
|
-
calcMode: "spline",
|
|
50801
|
-
dur: "2",
|
|
50802
|
-
values: "1;0;1;",
|
|
50803
|
-
keySplines: ".5 0 .5 1;.5 0 .5 1",
|
|
50804
|
-
repeatCount: "indefinite",
|
|
50805
|
-
begin: "-.2"
|
|
50806
|
-
})
|
|
50807
|
-
}), jsx("rect", {
|
|
50808
|
-
fill: "currentColor",
|
|
50809
|
-
stroke: "currentColor",
|
|
50810
|
-
"stroke-width": "15",
|
|
50811
|
-
width: "30",
|
|
50812
|
-
height: "30",
|
|
50813
|
-
x: "145",
|
|
50814
|
-
y: "85",
|
|
50815
|
-
children: jsx("animate", {
|
|
50816
|
-
attributeName: "opacity",
|
|
50817
|
-
calcMode: "spline",
|
|
50818
|
-
dur: "2",
|
|
50819
|
-
values: "1;0;1;",
|
|
50820
|
-
keySplines: ".5 0 .5 1;.5 0 .5 1",
|
|
50821
|
-
repeatCount: "indefinite",
|
|
50822
|
-
begin: "0"
|
|
50823
|
-
})
|
|
50824
|
-
})]
|
|
50825
|
-
});
|
|
50826
|
-
};
|
|
50827
|
-
|
|
50828
51279
|
const formatNumber = (value, { lang = languagesSignal.value } = {}) => {
|
|
50829
51280
|
return new Intl.NumberFormat(lang).format(value);
|
|
50830
51281
|
};
|
|
@@ -52229,23 +52680,6 @@ const Image = ({
|
|
|
52229
52680
|
});
|
|
52230
52681
|
};
|
|
52231
52682
|
|
|
52232
|
-
const LoadingIndicator = ({
|
|
52233
|
-
variant = "circle",
|
|
52234
|
-
...props
|
|
52235
|
-
}) => {
|
|
52236
|
-
if (variant === "dots") {
|
|
52237
|
-
return jsx(Icon, {
|
|
52238
|
-
...props,
|
|
52239
|
-
children: jsx(LoadingDotsSvg, {})
|
|
52240
|
-
});
|
|
52241
|
-
}
|
|
52242
|
-
return jsx(Icon, {
|
|
52243
|
-
circle: true,
|
|
52244
|
-
...props,
|
|
52245
|
-
children: jsx(LoadingIndicatorFluid, {})
|
|
52246
|
-
});
|
|
52247
|
-
};
|
|
52248
|
-
|
|
52249
52683
|
const Svg = props => {
|
|
52250
52684
|
return jsx(Box, {
|
|
52251
52685
|
...props,
|
|
@@ -52744,7 +53178,12 @@ const SidePanel = ({
|
|
|
52744
53178
|
onClose: onClose,
|
|
52745
53179
|
layer: layer,
|
|
52746
53180
|
anchorCustomEventDetail: "ignore",
|
|
52747
|
-
positionArea: side
|
|
53181
|
+
positionArea: side
|
|
53182
|
+
// A side panel is flush against the edge it slides in from — none of
|
|
53183
|
+
// Dialog's own default gap with the container.
|
|
53184
|
+
,
|
|
53185
|
+
|
|
53186
|
+
marginWithContainer: 0,
|
|
52748
53187
|
animation: animation === true ? `slide-from-${side}` : animation,
|
|
52749
53188
|
pointerInteractionOutsideEffect: closeOnClickOutside ? "close" : "none",
|
|
52750
53189
|
focusCapture: closeOnClickOutside,
|
|
@@ -52960,5 +53399,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
52960
53399
|
})
|
|
52961
53400
|
});
|
|
52962
53401
|
|
|
52963
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, Details, Dialog, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns,
|
|
53402
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, Details, Dialog, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
|
|
52964
53403
|
//# sourceMappingURL=jsenv_navi.js.map
|