@jsenv/navi 0.29.229 → 0.29.231
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 +521 -239
- package/dist/dev/jsenv_navi.js.map +17 -15
- package/dist/dev/jsenv_navi_side_effects.js +3 -0
- package/dist/dev/jsenv_navi_side_effects.js.map +2 -2
- package/dist/jsenv_navi.js +496 -236
- package/dist/jsenv_navi.js.map +17 -15
- package/dist/jsenv_navi_side_effects.js +3 -0
- package/dist/jsenv_navi_side_effects.js.map +2 -2
- package/docs/actions.md +27 -0
- package/docs/css_architecture.md +20 -0
- package/docs/form_changed.md +12 -3
- package/docs/i18n.md +9 -0
- package/docs/navigation.md +81 -0
- package/docs/resource.md +5 -0
- package/docs/scroll.md +9 -3
- package/docs/typography.md +18 -0
- package/package.json +2 -2
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -80,7 +80,7 @@ installImportMetaCssBuild(import.meta);
|
|
|
80
80
|
* any of these, and a number is the last resort, not the first tool.
|
|
81
81
|
*/
|
|
82
82
|
|
|
83
|
-
const css$
|
|
83
|
+
const css$17 = /* css */ `@layer navi {
|
|
84
84
|
:root {
|
|
85
85
|
--navi-z-index-control-hovered: 1;
|
|
86
86
|
--navi-z-index-control-focused: 2;
|
|
@@ -92,7 +92,7 @@ const css$16 = /* css */ `@layer navi {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
`;
|
|
95
|
-
import.meta.css = [css$
|
|
95
|
+
import.meta.css = [css$17, "@jsenv/navi/src/navi_z_indexes.js"];
|
|
96
96
|
|
|
97
97
|
const addIntoArray = (array, ...valuesToAdd) => {
|
|
98
98
|
if (valuesToAdd.length === 1) {
|
|
@@ -328,7 +328,7 @@ installImportMetaCssBuild(import.meta);
|
|
|
328
328
|
|
|
329
329
|
const URL_TARGET_ATTRIBUTE = "data-url-target";
|
|
330
330
|
|
|
331
|
-
const css$
|
|
331
|
+
const css$16 = /* css */ `@layer navi {
|
|
332
332
|
[data-url-target] {
|
|
333
333
|
animation: navi_url_target var(--navi-url-target-duration, 2s)
|
|
334
334
|
ease-out;
|
|
@@ -346,7 +346,7 @@ const css$15 = /* css */ `@layer navi {
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
`;
|
|
349
|
-
import.meta.css = [css$
|
|
349
|
+
import.meta.css = [css$16, "@jsenv/navi/src/nav/url_target/url_target.js"];
|
|
350
350
|
|
|
351
351
|
let urlTargetOptions = {
|
|
352
352
|
block: "start",
|
|
@@ -1683,7 +1683,7 @@ const useActionStatus = (action) => {
|
|
|
1683
1683
|
};
|
|
1684
1684
|
|
|
1685
1685
|
installImportMetaCssBuild(import.meta);
|
|
1686
|
-
const css$
|
|
1686
|
+
const css$15 = /* css */`.action_error {
|
|
1687
1687
|
background: #fdd;
|
|
1688
1688
|
border: 1px solid red;
|
|
1689
1689
|
margin-top: 0;
|
|
@@ -1707,7 +1707,7 @@ const ActionRenderer = ({
|
|
|
1707
1707
|
children,
|
|
1708
1708
|
disabled
|
|
1709
1709
|
}) => {
|
|
1710
|
-
import.meta.css = [css$
|
|
1710
|
+
import.meta.css = [css$15, "@jsenv/navi/src/action/action_renderer.jsx"];
|
|
1711
1711
|
if (action === undefined) {
|
|
1712
1712
|
throw new Error("ActionRenderer requires an action to render, but none was provided.");
|
|
1713
1713
|
}
|
|
@@ -4854,6 +4854,25 @@ const warnSignalCollision = (props, controlType, stateProp) => {
|
|
|
4854
4854
|
}
|
|
4855
4855
|
};
|
|
4856
4856
|
|
|
4857
|
+
/**
|
|
4858
|
+
* A signal handed to a prop that holds a plain value is the `signal` binding
|
|
4859
|
+
* written in the wrong place. Nothing throws: the signal object becomes the
|
|
4860
|
+
* value, and a value that cannot be written to the DOM is replaced by a
|
|
4861
|
+
* `window.__navi_js('…')` reference string — which is then what the field
|
|
4862
|
+
* shows on screen (see toDomValue in controller_registry.js).
|
|
4863
|
+
*/
|
|
4864
|
+
const warnSignalAsState = (props, controlType, stateProps) => {
|
|
4865
|
+
for (const stateProp of stateProps) {
|
|
4866
|
+
if (isSignal(props[stateProp])) {
|
|
4867
|
+
console.warn(
|
|
4868
|
+
`[navi] "${controlType}" got a signal as "${stateProp}". ` +
|
|
4869
|
+
`A signal binds a control through "signal" — did you mean signal={...}? ` +
|
|
4870
|
+
`As "${stateProp}" it is taken for the value itself.`,
|
|
4871
|
+
);
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
};
|
|
4875
|
+
|
|
4857
4876
|
// In-memory registry of all mounted ui state controllers keyed by their id.
|
|
4858
4877
|
// Allows direct controller access without dispatching DOM events — used by external
|
|
4859
4878
|
// callers (e.g. selectable_list) to call setUIState by id instead of via the DOM.
|
|
@@ -5864,7 +5883,7 @@ installImportMetaCssBuild(import.meta);
|
|
|
5864
5883
|
* (see calloutTemplate in callout.js).
|
|
5865
5884
|
*/
|
|
5866
5885
|
|
|
5867
|
-
const css$
|
|
5886
|
+
const css$14 = /* css */`.navi_callout_status_icon {
|
|
5868
5887
|
--x-callout-status-icon-color: var(--navi-callout-neutral-color);
|
|
5869
5888
|
box-sizing: border-box;
|
|
5870
5889
|
aspect-ratio: 1;
|
|
@@ -5935,7 +5954,7 @@ const CalloutStatusIcon = ({
|
|
|
5935
5954
|
status = "info",
|
|
5936
5955
|
shape = "square"
|
|
5937
5956
|
}) => {
|
|
5938
|
-
import.meta.css = [css$
|
|
5957
|
+
import.meta.css = [css$14, "@jsenv/navi/src/control/rules/callout/callout_status_icon.jsx"];
|
|
5939
5958
|
return jsx("span", {
|
|
5940
5959
|
className: "navi_callout_status_icon",
|
|
5941
5960
|
"data-status": status === "none" ? undefined : status,
|
|
@@ -5957,7 +5976,7 @@ installImportMetaCssBuild(import.meta);
|
|
|
5957
5976
|
// Unique for the page's lifetime: a caller may write the id in a commandfor.
|
|
5958
5977
|
let calloutCount = 0;
|
|
5959
5978
|
|
|
5960
|
-
const css$
|
|
5979
|
+
const css$13 = /* css */ `
|
|
5961
5980
|
@layer navi {
|
|
5962
5981
|
.navi_callout {
|
|
5963
5982
|
/* A callout is parented to what it explains, so it inherits from it — and
|
|
@@ -6305,7 +6324,7 @@ const openCallout = (
|
|
|
6305
6324
|
debug = () => {},
|
|
6306
6325
|
} = {},
|
|
6307
6326
|
) => {
|
|
6308
|
-
import.meta.css = [css$
|
|
6327
|
+
import.meta.css = [css$13, "@jsenv/navi/src/control/rules/callout/callout.js"];
|
|
6309
6328
|
if (debug === true) {
|
|
6310
6329
|
debug = (e, ...args) => console.debug(`"${e.type}" -> `, ...args);
|
|
6311
6330
|
}
|
|
@@ -18515,8 +18534,12 @@ const VISUAL_PROPS = {
|
|
|
18515
18534
|
outlineWidth: PASS_THROUGH,
|
|
18516
18535
|
boxDecorationBreak: PASS_THROUGH,
|
|
18517
18536
|
boxShadow: PASS_THROUGH,
|
|
18518
|
-
background:
|
|
18519
|
-
|
|
18537
|
+
background: (value) => {
|
|
18538
|
+
return { background: resolveSurfaceKeyword(value) };
|
|
18539
|
+
},
|
|
18540
|
+
backgroundColor: (value) => {
|
|
18541
|
+
return { backgroundColor: resolveSurfaceKeyword(value) };
|
|
18542
|
+
},
|
|
18520
18543
|
backgroundImage: PASS_THROUGH,
|
|
18521
18544
|
backgroundSize: PASS_THROUGH,
|
|
18522
18545
|
border: PASS_THROUGH,
|
|
@@ -18881,6 +18904,18 @@ const resolveColorKeyword = (value) => {
|
|
|
18881
18904
|
return COLOR_KEYWORD_MAP[value] || value;
|
|
18882
18905
|
};
|
|
18883
18906
|
|
|
18907
|
+
// The two planes an app paints: the paper its content sits on, and the frame
|
|
18908
|
+
// around it (see navi_css_vars.js). Named rather than derived at each call
|
|
18909
|
+
// site, so a top bar and a side menu are the same plane without agreeing on a
|
|
18910
|
+
// formula.
|
|
18911
|
+
const SURFACE_KEYWORD_MAP = {
|
|
18912
|
+
surface: "var(--navi-surface-color)",
|
|
18913
|
+
chrome: "var(--navi-chrome-color)",
|
|
18914
|
+
};
|
|
18915
|
+
const resolveSurfaceKeyword = (value) => {
|
|
18916
|
+
return SURFACE_KEYWORD_MAP[value] || value;
|
|
18917
|
+
};
|
|
18918
|
+
|
|
18884
18919
|
const DEFAULT_DISPLAY_BY_TAG_NAME = {
|
|
18885
18920
|
"inline": new Set([
|
|
18886
18921
|
"a",
|
|
@@ -20810,6 +20845,16 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
20810
20845
|
* children?: import("ignore:preact").ComponentChildren,
|
|
20811
20846
|
* [key: string]: any,
|
|
20812
20847
|
* }>}
|
|
20848
|
+
* @param {string} [background] Any CSS background, plus two keywords for the
|
|
20849
|
+
* planes an app paints: `"surface"` is the paper content sits on,
|
|
20850
|
+
* `"chrome"` the frame around a screen — a top bar, a side menu, a toolbar.
|
|
20851
|
+
* Both follow the theme, so a bar stays a bar in dark mode
|
|
20852
|
+
* (`--navi-surface-color` / `--navi-chrome-color`). `backgroundColor` takes
|
|
20853
|
+
* the same two.
|
|
20854
|
+
* @param {string} [color] Any CSS color, plus the five ink keywords —
|
|
20855
|
+
* `"primary"`, `"secondary"`, `"emphasis"`, `"discrete"`, `"hint"` — which
|
|
20856
|
+
* say how loud a text is rather than which color it is (see
|
|
20857
|
+
* docs/typography.md).
|
|
20813
20858
|
* @param {object} [interactions] What this box answers, by interaction name
|
|
20814
20859
|
* (see docs/interactions.md). A plain box has no wiring of its own: it does
|
|
20815
20860
|
* nothing with `action` (that is a control's prop), so a click on it is
|
|
@@ -22721,6 +22766,20 @@ const debug$2 = (...args) => {
|
|
|
22721
22766
|
// Base URL management
|
|
22722
22767
|
let baseFileUrl;
|
|
22723
22768
|
let baseUrl;
|
|
22769
|
+
/**
|
|
22770
|
+
* Where this document's addresses start, for a document that is not at the
|
|
22771
|
+
* root: a panel served from "/admin/admin.html" declares
|
|
22772
|
+
* `setBaseUrl("/admin/admin.html")` and then writes its routes as "/places",
|
|
22773
|
+
* "/places/:placeId". The url "/admin/places/42" matches, and `buildUrl` puts
|
|
22774
|
+
* the prefix back.
|
|
22775
|
+
*
|
|
22776
|
+
* Call it in the routes module, above the `route()` calls it governs — they
|
|
22777
|
+
* read the base url as they are created, so an entry point calling it after
|
|
22778
|
+
* importing them is too late. The server must serve the document for every
|
|
22779
|
+
* address below it (see docs/navigation.md).
|
|
22780
|
+
*
|
|
22781
|
+
* @param {string} value - the document's own url, absolute or relative.
|
|
22782
|
+
*/
|
|
22724
22783
|
const setBaseUrl = (value) => {
|
|
22725
22784
|
baseFileUrl = new URL(
|
|
22726
22785
|
value,
|
|
@@ -28563,7 +28622,7 @@ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
|
|
|
28563
28622
|
// the root pictures must NOT move (they carry the whole viewport, blank bands
|
|
28564
28623
|
// included).
|
|
28565
28624
|
|
|
28566
|
-
const css$
|
|
28625
|
+
const css$12 = /* css */`:root[data-navi-route-transition] [data-navi-route-transition-area] {
|
|
28567
28626
|
view-transition-name: navi-route-transition;
|
|
28568
28627
|
}
|
|
28569
28628
|
|
|
@@ -28884,7 +28943,7 @@ const RouteTransitionArea = ({
|
|
|
28884
28943
|
children,
|
|
28885
28944
|
...rest
|
|
28886
28945
|
}) => {
|
|
28887
|
-
import.meta.css = [css$
|
|
28946
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
28888
28947
|
installTransitionWindowCss();
|
|
28889
28948
|
const props = {
|
|
28890
28949
|
...rest,
|
|
@@ -28954,7 +29013,7 @@ const RouteTransitionArea = ({
|
|
|
28954
29013
|
* @returns {() => void} remove this relation.
|
|
28955
29014
|
*/
|
|
28956
29015
|
const defineRouteTransition = (from, to, transition) => {
|
|
28957
|
-
import.meta.css = [css$
|
|
29016
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
28958
29017
|
installTransitionWindowCss();
|
|
28959
29018
|
if (!to) {
|
|
28960
29019
|
throw new TypeError(`defineRouteTransition needs a destination: "to" is ${to}. The page reached from anywhere is written defineRouteTransition(null, THAT_PAGE, ...) — there is no relation the other way round, a page LEFT for anywhere being the back half of that one.`);
|
|
@@ -28995,7 +29054,7 @@ const defineRouteTransition = (from, to, transition) => {
|
|
|
28995
29054
|
* @returns {() => void} remove this default.
|
|
28996
29055
|
*/
|
|
28997
29056
|
const defineRouteDefaultTransition = transition => {
|
|
28998
|
-
import.meta.css = [css$
|
|
29057
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
28999
29058
|
installTransitionWindowCss();
|
|
29000
29059
|
const value = normalizeTransition(transition);
|
|
29001
29060
|
defaultTransition = value;
|
|
@@ -29817,7 +29876,7 @@ const DRAGGED_ATTRIBUTE = "data-navi-route-travel-dragged";
|
|
|
29817
29876
|
const TURNED_ATTRIBUTE = "data-navi-route-travel-turned";
|
|
29818
29877
|
// The name the box wears while it travels, and only then (see nameForTravel).
|
|
29819
29878
|
const TRAVEL_NAME = "navi-route-travel";
|
|
29820
|
-
const css$
|
|
29879
|
+
const css$11 = /* css */`.navi_route_travel {
|
|
29821
29880
|
position: relative;
|
|
29822
29881
|
|
|
29823
29882
|
&[data-travel-by-drag="x"] {
|
|
@@ -30106,7 +30165,7 @@ const RouteTravel = ({
|
|
|
30106
30165
|
children,
|
|
30107
30166
|
...rest
|
|
30108
30167
|
}) => {
|
|
30109
|
-
import.meta.css = [css$
|
|
30168
|
+
import.meta.css = [css$11, "@jsenv/navi/src/nav/route_travel.jsx"];
|
|
30110
30169
|
installTransitionWindowCss();
|
|
30111
30170
|
const elementRef = useRef();
|
|
30112
30171
|
const gestureRef = useRef(null);
|
|
@@ -37154,9 +37213,11 @@ const useControlProps = (props, {
|
|
|
37154
37213
|
* props, which is how `signal` came to be forgotten.
|
|
37155
37214
|
*/
|
|
37156
37215
|
const isControlValueGivenByProps = props => Object.hasOwn(props, "value") || Object.hasOwn(props, "defaultValue") || Object.hasOwn(props, "signal");
|
|
37216
|
+
const CONTROL_STATE_PROPS = ["value", "defaultValue", "checked", "defaultChecked", "open", "defaultOpen"];
|
|
37157
37217
|
const createControlInfo = (props, {
|
|
37158
37218
|
controlType
|
|
37159
37219
|
}) => {
|
|
37220
|
+
warnSignalAsState(props, controlType, CONTROL_STATE_PROPS);
|
|
37160
37221
|
let statePropName;
|
|
37161
37222
|
let defaultStatePropName;
|
|
37162
37223
|
let stateInitial;
|
|
@@ -39792,7 +39853,7 @@ const setupNetworkMonitoring = () => {
|
|
|
39792
39853
|
setupNetworkMonitoring();
|
|
39793
39854
|
|
|
39794
39855
|
installImportMetaCssBuild(import.meta);
|
|
39795
|
-
const css
|
|
39856
|
+
const css$10 = /* css */`.navi_loading_indicator_fluid_container {
|
|
39796
39857
|
border-radius: inherit;
|
|
39797
39858
|
opacity: 1;
|
|
39798
39859
|
width: 100%;
|
|
@@ -39823,7 +39884,7 @@ const LoadingIndicatorFluid = ({
|
|
|
39823
39884
|
visuallyHidden,
|
|
39824
39885
|
...rest
|
|
39825
39886
|
}) => {
|
|
39826
|
-
import.meta.css = [css
|
|
39887
|
+
import.meta.css = [css$10, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
|
|
39827
39888
|
const ref = useRef(null);
|
|
39828
39889
|
// The container dimensions can be deduced from the ref itself as the indicator is absolute inset 0
|
|
39829
39890
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
@@ -40029,7 +40090,7 @@ const LoadingRectangleSvg = ({
|
|
|
40029
40090
|
};
|
|
40030
40091
|
|
|
40031
40092
|
installImportMetaCssBuild(import.meta);
|
|
40032
|
-
const css
|
|
40093
|
+
const css$$ = /* css */`.navi_loading_outline_wrapper {
|
|
40033
40094
|
top: max(var(--loading-outline-min-inset, -100vh),
|
|
40034
40095
|
var(--loading-rectangle-top, 0px));
|
|
40035
40096
|
right: max(var(--loading-outline-min-inset, -100vh),
|
|
@@ -40050,7 +40111,7 @@ const css$_ = /* css */`.navi_loading_outline_wrapper {
|
|
|
40050
40111
|
}
|
|
40051
40112
|
`;
|
|
40052
40113
|
const LoadingOutline = props => {
|
|
40053
|
-
import.meta.css = [css
|
|
40114
|
+
import.meta.css = [css$$, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
|
|
40054
40115
|
if (props.containerRef) {
|
|
40055
40116
|
const container = props.containerRef.current;
|
|
40056
40117
|
if (!container) {
|
|
@@ -40185,7 +40246,7 @@ const LoadingOutlineWithPortal = props => {
|
|
|
40185
40246
|
};
|
|
40186
40247
|
|
|
40187
40248
|
installImportMetaCssBuild(import.meta);
|
|
40188
|
-
const css$
|
|
40249
|
+
const css$_ = /* css */`.navi_text_anchor {
|
|
40189
40250
|
vertical-align: baseline;
|
|
40190
40251
|
user-select: none;
|
|
40191
40252
|
overflow: hidden;
|
|
@@ -40223,7 +40284,7 @@ const TextAnchor = ({
|
|
|
40223
40284
|
textSize,
|
|
40224
40285
|
lineLayout
|
|
40225
40286
|
}) => {
|
|
40226
|
-
import.meta.css = [css$
|
|
40287
|
+
import.meta.css = [css$_, "@jsenv/navi/src/text/text_anchor.jsx"];
|
|
40227
40288
|
const anchorRef = useRef();
|
|
40228
40289
|
|
|
40229
40290
|
// Plain useLayoutEffect would also fire while an ancestor dialog/popover
|
|
@@ -40451,7 +40512,7 @@ const selectByTextStrings = (element, range, startText, endText) => {
|
|
|
40451
40512
|
};
|
|
40452
40513
|
|
|
40453
40514
|
installImportMetaCssBuild(import.meta);
|
|
40454
|
-
const css$
|
|
40515
|
+
const css$Z = /* css */`@layer navi {
|
|
40455
40516
|
.navi_text {
|
|
40456
40517
|
&[data-skeleton] {
|
|
40457
40518
|
border-radius: .2em;
|
|
@@ -41020,7 +41081,7 @@ const TextShrinkWrap = props => {
|
|
|
41020
41081
|
});
|
|
41021
41082
|
};
|
|
41022
41083
|
const TextUI = props => {
|
|
41023
|
-
import.meta.css = [css$
|
|
41084
|
+
import.meta.css = [css$Z, "@jsenv/navi/src/text/text.jsx"];
|
|
41024
41085
|
let {
|
|
41025
41086
|
ref,
|
|
41026
41087
|
spacing,
|
|
@@ -41244,7 +41305,7 @@ const Icon = ({
|
|
|
41244
41305
|
fillLine,
|
|
41245
41306
|
...props
|
|
41246
41307
|
}) => {
|
|
41247
|
-
import.meta.css = [css$
|
|
41308
|
+
import.meta.css = [css$Z, "@jsenv/navi/src/text/text.jsx"];
|
|
41248
41309
|
const innerChildren = href ? jsx("svg", {
|
|
41249
41310
|
width: "100%",
|
|
41250
41311
|
height: "100%",
|
|
@@ -41434,7 +41495,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
41434
41495
|
};
|
|
41435
41496
|
|
|
41436
41497
|
installImportMetaCssBuild(import.meta);
|
|
41437
|
-
const css$
|
|
41498
|
+
const css$Y = /* css */`@layer navi {
|
|
41438
41499
|
.navi_link {
|
|
41439
41500
|
--link-border-radius: unset;
|
|
41440
41501
|
--link-outline-color: var(--navi-focus-outline-color);
|
|
@@ -41892,7 +41953,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
41892
41953
|
* @param {boolean} [props.readOnly]
|
|
41893
41954
|
*/
|
|
41894
41955
|
const Link = props => {
|
|
41895
|
-
import.meta.css = [css$
|
|
41956
|
+
import.meta.css = [css$Y, "@jsenv/navi/src/nav/link/link.jsx"];
|
|
41896
41957
|
if (props.route) {
|
|
41897
41958
|
return jsx(LinkWithRoute, {
|
|
41898
41959
|
...props
|
|
@@ -42460,7 +42521,7 @@ const rememberProgressAnimation = (navElement, travel) => {
|
|
|
42460
42521
|
travel.progressAnimation = animation;
|
|
42461
42522
|
travel.progressFrom = animation ? parseFloat(animation.effect.getKeyframes()[0]["--slide-travel-progress"]) || 0 : 0;
|
|
42462
42523
|
};
|
|
42463
|
-
const css$
|
|
42524
|
+
const css$X = /* css */`@layer navi {
|
|
42464
42525
|
.navi_nav {
|
|
42465
42526
|
--nav-border: none;
|
|
42466
42527
|
--nav-padding: 0px;
|
|
@@ -42724,7 +42785,7 @@ const Nav = ({
|
|
|
42724
42785
|
slideContainer,
|
|
42725
42786
|
...props
|
|
42726
42787
|
}) => {
|
|
42727
|
-
import.meta.css = [css$
|
|
42788
|
+
import.meta.css = [css$X, "@jsenv/navi/src/nav/link/nav.jsx"];
|
|
42728
42789
|
const defaultRef = useRef();
|
|
42729
42790
|
props.ref = props.ref || defaultRef;
|
|
42730
42791
|
const navRef = props.ref;
|
|
@@ -43600,7 +43661,7 @@ const buildBinderCenterlinePathBelow = ({
|
|
|
43600
43661
|
};
|
|
43601
43662
|
|
|
43602
43663
|
installImportMetaCssBuild(import.meta);
|
|
43603
|
-
const css$
|
|
43664
|
+
const css$W = /* css */`@layer navi {
|
|
43604
43665
|
.navi_binder {
|
|
43605
43666
|
--binder-border-width: var(--navi-control-border-width);
|
|
43606
43667
|
--binder-border-radius: var(--navi-control-border-radius);
|
|
@@ -43885,7 +43946,7 @@ const Binder = ({
|
|
|
43885
43946
|
pagePadding,
|
|
43886
43947
|
...props
|
|
43887
43948
|
}) => {
|
|
43888
|
-
import.meta.css = [css$
|
|
43949
|
+
import.meta.css = [css$W, "@jsenv/navi/src/nav/binder/binder.jsx"];
|
|
43889
43950
|
const items = toChildArray(children).map((child, index) => {
|
|
43890
43951
|
const {
|
|
43891
43952
|
value: itemValue,
|
|
@@ -44332,7 +44393,7 @@ const writeSpace = (area) => {
|
|
|
44332
44393
|
};
|
|
44333
44394
|
|
|
44334
44395
|
installImportMetaCssBuild(import.meta);
|
|
44335
|
-
const css$
|
|
44396
|
+
const css$V = /* css */`@layer navi {
|
|
44336
44397
|
:root {
|
|
44337
44398
|
--navi-fixed-bar-width: 56px;
|
|
44338
44399
|
--navi-fixed-bar-height: 56px;
|
|
@@ -44452,7 +44513,7 @@ const FixedBar = ({
|
|
|
44452
44513
|
border = true,
|
|
44453
44514
|
...props
|
|
44454
44515
|
}) => {
|
|
44455
|
-
import.meta.css = [css$
|
|
44516
|
+
import.meta.css = [css$V, "@jsenv/navi/src/layout/fixed_bar/fixed_bar.jsx"];
|
|
44456
44517
|
const defaultRef = useRef();
|
|
44457
44518
|
props.ref = props.ref || defaultRef;
|
|
44458
44519
|
// Said with the width the border rule reads rather than with an attribute of
|
|
@@ -44546,7 +44607,7 @@ const FixedBar = ({
|
|
|
44546
44607
|
// Subpixel layout rounds rectangles up on boxes that fit exactly.
|
|
44547
44608
|
const OVERFLOW_TOLERANCE = 1;
|
|
44548
44609
|
|
|
44549
|
-
const css$
|
|
44610
|
+
const css$U = /* css */ `
|
|
44550
44611
|
[data-navi-overflow-x] {
|
|
44551
44612
|
outline: 2px dashed #e74c3c;
|
|
44552
44613
|
outline-offset: -2px;
|
|
@@ -44570,7 +44631,7 @@ const detectHorizontalOverflow = ({
|
|
|
44570
44631
|
let styleEl = null;
|
|
44571
44632
|
if (highlight) {
|
|
44572
44633
|
styleEl = document.createElement("style");
|
|
44573
|
-
styleEl.textContent = css$
|
|
44634
|
+
styleEl.textContent = css$U;
|
|
44574
44635
|
document.head.appendChild(styleEl);
|
|
44575
44636
|
}
|
|
44576
44637
|
|
|
@@ -44726,7 +44787,7 @@ const useFocusGroup = (
|
|
|
44726
44787
|
|
|
44727
44788
|
installImportMetaCssBuild(import.meta);
|
|
44728
44789
|
const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
|
|
44729
|
-
const css$
|
|
44790
|
+
const css$T = /* css */`.navi_summary_marker {
|
|
44730
44791
|
flex-shrink: 0;
|
|
44731
44792
|
width: 1em;
|
|
44732
44793
|
height: 1em;
|
|
@@ -44818,7 +44879,7 @@ const SummaryMarker = ({
|
|
|
44818
44879
|
loading,
|
|
44819
44880
|
openDirection = "down"
|
|
44820
44881
|
}) => {
|
|
44821
|
-
import.meta.css = [css$
|
|
44882
|
+
import.meta.css = [css$T, "@jsenv/navi/src/control/details/summary_marker.jsx"];
|
|
44822
44883
|
const showLoading = useDebounceTrue(loading, 300);
|
|
44823
44884
|
return jsx("span", {
|
|
44824
44885
|
className: "navi_summary_marker",
|
|
@@ -44864,7 +44925,7 @@ const SummaryMarker = ({
|
|
|
44864
44925
|
};
|
|
44865
44926
|
|
|
44866
44927
|
installImportMetaCssBuild(import.meta);
|
|
44867
|
-
const css$
|
|
44928
|
+
const css$S = /* css */`.navi_details {
|
|
44868
44929
|
z-index: 1;
|
|
44869
44930
|
flex-direction: column;
|
|
44870
44931
|
flex-shrink: 0;
|
|
@@ -44909,7 +44970,7 @@ const Details = props => {
|
|
|
44909
44970
|
return details;
|
|
44910
44971
|
};
|
|
44911
44972
|
const DetailsField = props => {
|
|
44912
|
-
import.meta.css = [css$
|
|
44973
|
+
import.meta.css = [css$S, "@jsenv/navi/src/control/details/details.jsx"];
|
|
44913
44974
|
const {
|
|
44914
44975
|
ref,
|
|
44915
44976
|
persists,
|
|
@@ -45825,6 +45886,7 @@ const useOpenPropsEffectOnOpenController = (
|
|
|
45825
45886
|
name = "popup",
|
|
45826
45887
|
) => {
|
|
45827
45888
|
const { signal, value, defaultOpen, navState } = props;
|
|
45889
|
+
warnSignalAsState(props, name, ["open", "defaultOpen"]);
|
|
45828
45890
|
const { id: navStateId, type: navStateType } = resolveNavStateProp(
|
|
45829
45891
|
navState,
|
|
45830
45892
|
props.id,
|
|
@@ -46709,7 +46771,7 @@ const usePopupContentMount = (
|
|
|
46709
46771
|
};
|
|
46710
46772
|
|
|
46711
46773
|
installImportMetaCssBuild(import.meta);
|
|
46712
|
-
const css$
|
|
46774
|
+
const css$R = /* css */`.navi_expandable {
|
|
46713
46775
|
flex-direction: column;
|
|
46714
46776
|
flex-shrink: 0;
|
|
46715
46777
|
display: flex;
|
|
@@ -46939,7 +47001,7 @@ const useExpandableContext = partName => {
|
|
|
46939
47001
|
* builds the content ahead of the click.
|
|
46940
47002
|
*/
|
|
46941
47003
|
const Expandable = props => {
|
|
46942
|
-
import.meta.css = [css$
|
|
47004
|
+
import.meta.css = [css$R, "@jsenv/navi/src/control/expandable/expandable.jsx"];
|
|
46943
47005
|
/* The open props are read from `props` by the open controller below; they
|
|
46944
47006
|
are named here only to keep them out of `rest`, and so out of the DOM. */
|
|
46945
47007
|
/* eslint-disable no-unused-vars */
|
|
@@ -47626,49 +47688,6 @@ const shallowDiffers = (a, b) => {
|
|
|
47626
47688
|
return false;
|
|
47627
47689
|
};
|
|
47628
47690
|
|
|
47629
|
-
const ButtonRouteResolver = props => {
|
|
47630
|
-
const Next = useNextResolver();
|
|
47631
|
-
if (props.route) {
|
|
47632
|
-
return jsx(ButtonWithRoute, {
|
|
47633
|
-
...props
|
|
47634
|
-
});
|
|
47635
|
-
}
|
|
47636
|
-
return jsx(Next, {
|
|
47637
|
-
...props
|
|
47638
|
-
});
|
|
47639
|
-
};
|
|
47640
|
-
const ButtonWithRoute = props => {
|
|
47641
|
-
const Next = useNextResolver();
|
|
47642
|
-
const {
|
|
47643
|
-
route,
|
|
47644
|
-
routeParams,
|
|
47645
|
-
children,
|
|
47646
|
-
pseudoState,
|
|
47647
|
-
...rest
|
|
47648
|
-
} = props;
|
|
47649
|
-
{
|
|
47650
|
-
assertRoute(route);
|
|
47651
|
-
}
|
|
47652
|
-
const url = route.buildUrl(routeParams);
|
|
47653
|
-
const {
|
|
47654
|
-
matching
|
|
47655
|
-
} = useRouteStatus(route);
|
|
47656
|
-
const paramsAreMatching = route.matchesParams(routeParams);
|
|
47657
|
-
const linkMatching = matching && paramsAreMatching;
|
|
47658
|
-
|
|
47659
|
-
// Merged into whatever the caller already holds: a button can be forced into
|
|
47660
|
-
// a state for a demo and still learn its own current-ness from its route.
|
|
47661
|
-
return jsx(Next, {
|
|
47662
|
-
href: url,
|
|
47663
|
-
pseudoState: {
|
|
47664
|
-
...pseudoState,
|
|
47665
|
-
":-navi-href-current": linkMatching
|
|
47666
|
-
},
|
|
47667
|
-
...rest,
|
|
47668
|
-
children: children || route.buildRelativeUrl(routeParams)
|
|
47669
|
-
});
|
|
47670
|
-
};
|
|
47671
|
-
|
|
47672
47691
|
const LIGHT_ACCENT_ATTRIBUTE = "data-accent-light";
|
|
47673
47692
|
const VERY_LIGHT_ACCENT_ATTRIBUTE = "data-accent-very-light";
|
|
47674
47693
|
const DARK_CONTRAST_ATTRIBUTE = "data-accent-needs-dark-fg";
|
|
@@ -47769,7 +47788,7 @@ const useAccentColorAttributes = (
|
|
|
47769
47788
|
};
|
|
47770
47789
|
|
|
47771
47790
|
installImportMetaCssBuild(import.meta);
|
|
47772
|
-
const css$
|
|
47791
|
+
const css$Q = /* css */`@layer navi {
|
|
47773
47792
|
.navi_button {
|
|
47774
47793
|
--button-border-radius: var(--navi-control-border-radius);
|
|
47775
47794
|
--button-border-width: var(--navi-control-border-width);
|
|
@@ -48070,7 +48089,7 @@ a.navi_button {
|
|
|
48070
48089
|
}
|
|
48071
48090
|
`;
|
|
48072
48091
|
const ButtonUI = props => {
|
|
48073
|
-
import.meta.css = [css$
|
|
48092
|
+
import.meta.css = [css$Q, "@jsenv/navi/src/control/input/button_ui.jsx"];
|
|
48074
48093
|
const {
|
|
48075
48094
|
ref,
|
|
48076
48095
|
// href/link
|
|
@@ -48285,6 +48304,189 @@ const ButtonShadow = () => {
|
|
|
48285
48304
|
};
|
|
48286
48305
|
markAsOutsideTextFlow(ButtonShadow);
|
|
48287
48306
|
|
|
48307
|
+
installImportMetaCssBuild(import.meta);
|
|
48308
|
+
const css$P = /* css */`.navi_button_confirm {
|
|
48309
|
+
align-items: center;
|
|
48310
|
+
gap: var(--navi-s);
|
|
48311
|
+
flex-wrap: wrap;
|
|
48312
|
+
display: inline-flex;
|
|
48313
|
+
}
|
|
48314
|
+
`;
|
|
48315
|
+
const ButtonConfirmResolver = props => {
|
|
48316
|
+
const Next = useNextResolver();
|
|
48317
|
+
if (!props.confirm) {
|
|
48318
|
+
return jsx(Next, {
|
|
48319
|
+
...props
|
|
48320
|
+
});
|
|
48321
|
+
}
|
|
48322
|
+
return jsx(ButtonConfirm, {
|
|
48323
|
+
...props,
|
|
48324
|
+
Next: Next
|
|
48325
|
+
});
|
|
48326
|
+
};
|
|
48327
|
+
const ButtonConfirm = ({
|
|
48328
|
+
Next,
|
|
48329
|
+
confirm,
|
|
48330
|
+
confirmLabel = naviI18n("button.confirm"),
|
|
48331
|
+
cancelLabel = naviI18n("button.cancel"),
|
|
48332
|
+
confirmTestId,
|
|
48333
|
+
cancelTestId,
|
|
48334
|
+
...props
|
|
48335
|
+
}) => {
|
|
48336
|
+
import.meta.css = [css$P, "@jsenv/navi/src/control/input/button_confirm.jsx"];
|
|
48337
|
+
const [asking, setAsking] = useState(false);
|
|
48338
|
+
const askingRef = useRef(null);
|
|
48339
|
+
const cancelRef = useRef(null);
|
|
48340
|
+
// Whether going back to the resting label owes the focus a place to land: a
|
|
48341
|
+
// press on "Annuler" or an Escape does, a focus that already left does not —
|
|
48342
|
+
// it is elsewhere by then, and pulling it back would take it from whatever
|
|
48343
|
+
// the person moved to.
|
|
48344
|
+
const restoreFocusRef = useRef(false);
|
|
48345
|
+
const questionId = `${useId()}_question`;
|
|
48346
|
+
const cancel = restoreFocus => {
|
|
48347
|
+
restoreFocusRef.current = restoreFocus;
|
|
48348
|
+
setAsking(false);
|
|
48349
|
+
};
|
|
48350
|
+
useLayoutEffect(() => {
|
|
48351
|
+
if (asking) {
|
|
48352
|
+
// The press was aimed at this button, and the button that answers now is
|
|
48353
|
+
// where it should land: navi's own ladder inside the question, which
|
|
48354
|
+
// finds "Confirmer" (first in the row) unless the caller marked
|
|
48355
|
+
// something else.
|
|
48356
|
+
const found = findFocusTarget(askingRef.current, {
|
|
48357
|
+
restoreMayClaim: true
|
|
48358
|
+
});
|
|
48359
|
+
if (found) {
|
|
48360
|
+
moveFocusTo(found.target);
|
|
48361
|
+
}
|
|
48362
|
+
return;
|
|
48363
|
+
}
|
|
48364
|
+
if (restoreFocusRef.current) {
|
|
48365
|
+
restoreFocusRef.current = false;
|
|
48366
|
+
// The answer may have taken the button with it — a row deleting itself
|
|
48367
|
+
// is the whole point of asking — and there is then nothing to give the
|
|
48368
|
+
// focus back to.
|
|
48369
|
+
const buttonEl = props.ref.current;
|
|
48370
|
+
if (buttonEl) {
|
|
48371
|
+
moveFocusTo(buttonEl);
|
|
48372
|
+
}
|
|
48373
|
+
}
|
|
48374
|
+
}, [asking]);
|
|
48375
|
+
if (!asking) {
|
|
48376
|
+
return jsx(Next, {
|
|
48377
|
+
...props,
|
|
48378
|
+
// The first press is the question, not the act — so nothing that acts
|
|
48379
|
+
// is handed to the resolvers below. `type` with them: a submit button
|
|
48380
|
+
// would be given `--navi-send` back.
|
|
48381
|
+
type: "button",
|
|
48382
|
+
action: undefined,
|
|
48383
|
+
command: undefined,
|
|
48384
|
+
href: undefined,
|
|
48385
|
+
route: undefined,
|
|
48386
|
+
onClick: () => {
|
|
48387
|
+
setAsking(true);
|
|
48388
|
+
}
|
|
48389
|
+
});
|
|
48390
|
+
}
|
|
48391
|
+
const {
|
|
48392
|
+
action,
|
|
48393
|
+
onActionEnd,
|
|
48394
|
+
onClick
|
|
48395
|
+
} = props;
|
|
48396
|
+
return jsxs("span", {
|
|
48397
|
+
className: "navi_button_confirm",
|
|
48398
|
+
ref: askingRef,
|
|
48399
|
+
onKeyDown: e => {
|
|
48400
|
+
if (e.key === "Escape") {
|
|
48401
|
+
cancel(true);
|
|
48402
|
+
}
|
|
48403
|
+
},
|
|
48404
|
+
onFocusOut: e => {
|
|
48405
|
+
if (!askingRef.current.contains(e.relatedTarget)) {
|
|
48406
|
+
cancel(false);
|
|
48407
|
+
}
|
|
48408
|
+
},
|
|
48409
|
+
children: [jsx("span", {
|
|
48410
|
+
id: questionId,
|
|
48411
|
+
children: confirm === true ? naviI18n("confirm.message") : confirm
|
|
48412
|
+
}), jsx(Next, {
|
|
48413
|
+
...props,
|
|
48414
|
+
"aria-describedby": questionId
|
|
48415
|
+
// The button confirming is the button that was pressed: it keeps the
|
|
48416
|
+
// caller's own testid unless the question names one of its own.
|
|
48417
|
+
,
|
|
48418
|
+
"data-testid": confirmTestId || props["data-testid"],
|
|
48419
|
+
onClick: e => {
|
|
48420
|
+
onClick?.(e);
|
|
48421
|
+
if (!action) {
|
|
48422
|
+
// A command runs on the press itself: there is nothing to wait for,
|
|
48423
|
+
// the question is answered as soon as it is pressed.
|
|
48424
|
+
cancel(true);
|
|
48425
|
+
}
|
|
48426
|
+
}
|
|
48427
|
+
// A run that failed keeps the question up, with its callout on the
|
|
48428
|
+
// button that raised it: the retry is then one press away.
|
|
48429
|
+
,
|
|
48430
|
+
onActionEnd: (data, e) => {
|
|
48431
|
+
onActionEnd?.(data, e);
|
|
48432
|
+
cancel(true);
|
|
48433
|
+
},
|
|
48434
|
+
children: confirmLabel
|
|
48435
|
+
}), jsx(ButtonUI, {
|
|
48436
|
+
ref: cancelRef,
|
|
48437
|
+
type: "button",
|
|
48438
|
+
"data-testid": cancelTestId,
|
|
48439
|
+
onClick: () => {
|
|
48440
|
+
cancel(true);
|
|
48441
|
+
},
|
|
48442
|
+
children: cancelLabel
|
|
48443
|
+
})]
|
|
48444
|
+
});
|
|
48445
|
+
};
|
|
48446
|
+
|
|
48447
|
+
const ButtonRouteResolver = props => {
|
|
48448
|
+
const Next = useNextResolver();
|
|
48449
|
+
if (props.route) {
|
|
48450
|
+
return jsx(ButtonWithRoute, {
|
|
48451
|
+
...props
|
|
48452
|
+
});
|
|
48453
|
+
}
|
|
48454
|
+
return jsx(Next, {
|
|
48455
|
+
...props
|
|
48456
|
+
});
|
|
48457
|
+
};
|
|
48458
|
+
const ButtonWithRoute = props => {
|
|
48459
|
+
const Next = useNextResolver();
|
|
48460
|
+
const {
|
|
48461
|
+
route,
|
|
48462
|
+
routeParams,
|
|
48463
|
+
children,
|
|
48464
|
+
pseudoState,
|
|
48465
|
+
...rest
|
|
48466
|
+
} = props;
|
|
48467
|
+
{
|
|
48468
|
+
assertRoute(route);
|
|
48469
|
+
}
|
|
48470
|
+
const url = route.buildUrl(routeParams);
|
|
48471
|
+
const {
|
|
48472
|
+
matching
|
|
48473
|
+
} = useRouteStatus(route);
|
|
48474
|
+
const paramsAreMatching = route.matchesParams(routeParams);
|
|
48475
|
+
const linkMatching = matching && paramsAreMatching;
|
|
48476
|
+
|
|
48477
|
+
// Merged into whatever the caller already holds: a button can be forced into
|
|
48478
|
+
// a state for a demo and still learn its own current-ness from its route.
|
|
48479
|
+
return jsx(Next, {
|
|
48480
|
+
href: url,
|
|
48481
|
+
pseudoState: {
|
|
48482
|
+
...pseudoState,
|
|
48483
|
+
":-navi-href-current": linkMatching
|
|
48484
|
+
},
|
|
48485
|
+
...rest,
|
|
48486
|
+
children: children || route.buildRelativeUrl(routeParams)
|
|
48487
|
+
});
|
|
48488
|
+
};
|
|
48489
|
+
|
|
48288
48490
|
const ButtonFirstResolver = props => {
|
|
48289
48491
|
const Next = useNextResolver();
|
|
48290
48492
|
const defaultRef = useRef(null);
|
|
@@ -48446,6 +48648,20 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48446
48648
|
* more: the work may already have happened on the other side (see
|
|
48447
48649
|
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
48448
48650
|
* where the screen can be re-opened on what is actually there.
|
|
48651
|
+
* @param {boolean|import("ignore:preact").ComponentChildren} [confirm] Ask before
|
|
48652
|
+
* doing it, in the place the button stands: the first press replaces the
|
|
48653
|
+
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
48654
|
+
* does what the button was for. `true` asks navi's default question. The
|
|
48655
|
+
* press stays under the finger and there is nothing to dismiss — "Annuler",
|
|
48656
|
+
* Escape, or the focus leaving puts the button back. Use `<Picker
|
|
48657
|
+
* type="confirm">` instead when the question is long enough to want a popup,
|
|
48658
|
+
* or when the row has no space for it.
|
|
48659
|
+
* @param {import("ignore:preact").ComponentChildren} [confirmLabel] Label of the
|
|
48660
|
+
* second press.
|
|
48661
|
+
* @param {import("ignore:preact").ComponentChildren} [cancelLabel] Label of the way
|
|
48662
|
+
* out of the question.
|
|
48663
|
+
* @param {string} [confirmTestId] `data-testid` of the second press.
|
|
48664
|
+
* @param {string} [cancelTestId] `data-testid` of the way out.
|
|
48449
48665
|
* @param {string} [contentDisplay] The display of the frame the button draws
|
|
48450
48666
|
* around its children. It follows the button's own by default — its display
|
|
48451
48667
|
* and, a display alone saying nothing about direction, the rest of its flow
|
|
@@ -48460,7 +48676,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48460
48676
|
* (it wears it itself) and loses the shrink under the finger, which has
|
|
48461
48677
|
* nothing left to scale but the interactive area itself.
|
|
48462
48678
|
*/
|
|
48463
|
-
const Button = createComponentResolver([ButtonFirstResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonUI]);
|
|
48679
|
+
const Button = createComponentResolver([ButtonFirstResolver, ButtonConfirmResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonUI]);
|
|
48464
48680
|
|
|
48465
48681
|
installImportMetaCssBuild(import.meta);
|
|
48466
48682
|
const css$O = /* css */`.navi_control_swap {
|
|
@@ -56505,13 +56721,7 @@ const TimeDate = ({
|
|
|
56505
56721
|
})
|
|
56506
56722
|
});
|
|
56507
56723
|
}
|
|
56508
|
-
const date = toDate(children
|
|
56509
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
56510
|
-
const d = new Date(`${value}T00:00:00`);
|
|
56511
|
-
return isNaN(d.getTime()) ? null : d;
|
|
56512
|
-
}
|
|
56513
|
-
return null;
|
|
56514
|
-
});
|
|
56724
|
+
const date = toDate(children);
|
|
56515
56725
|
if (!date) {
|
|
56516
56726
|
return jsx(TimeText, {
|
|
56517
56727
|
...props,
|
|
@@ -64129,158 +64339,163 @@ const ListSelectable = props => {
|
|
|
64129
64339
|
}
|
|
64130
64340
|
setCurrentId(initialEl.id);
|
|
64131
64341
|
}, []);
|
|
64132
|
-
|
|
64133
|
-
|
|
64134
|
-
|
|
64135
|
-
|
|
64136
|
-
|
|
64137
|
-
|
|
64138
|
-
|
|
64139
|
-
|
|
64140
|
-
|
|
64141
|
-
|
|
64142
|
-
|
|
64143
|
-
|
|
64144
|
-
|
|
64145
|
-
|
|
64146
|
-
|
|
64147
|
-
|
|
64148
|
-
|
|
64149
|
-
|
|
64150
|
-
|
|
64151
|
-
|
|
64152
|
-
|
|
64153
|
-
|
|
64154
|
-
|
|
64155
|
-
|
|
64156
|
-
|
|
64157
|
-
|
|
64158
|
-
|
|
64159
|
-
|
|
64160
|
-
|
|
64161
|
-
|
|
64162
|
-
const {
|
|
64163
|
-
id
|
|
64164
|
-
} = e.detail;
|
|
64165
|
-
if (id === undefined) {
|
|
64166
|
-
return;
|
|
64167
|
-
}
|
|
64168
|
-
const inputId = `${id}_input`;
|
|
64169
|
-
const childController = uiGroupStateController.findChildById(inputId);
|
|
64170
|
-
if (!childController) {
|
|
64171
|
-
return;
|
|
64172
|
-
}
|
|
64173
|
-
const list = ref.current;
|
|
64174
|
-
dispatchRequestInteraction(list, {
|
|
64175
|
-
event: e,
|
|
64176
|
-
name: "select",
|
|
64177
|
-
prevented: () => e.preventDefault(),
|
|
64178
|
-
// tell the requester that we don't want to select this item
|
|
64179
|
-
// Asked of the item too, not only of the list: an item can be the one
|
|
64180
|
-
// refusing (the list already holds all it accepts, see maxLength), and
|
|
64181
|
-
// it is the one that then says why.
|
|
64182
|
-
allowed: () => {
|
|
64183
|
-
dispatchRequestInteraction(childController.ref.current, {
|
|
64184
|
-
event: e,
|
|
64185
|
-
name: "select",
|
|
64186
|
-
prevented: () => e.preventDefault(),
|
|
64187
|
-
allowed: () => childController.setUIState(childController.value, e)
|
|
64188
|
-
});
|
|
64342
|
+
|
|
64343
|
+
// The group's contexts stand around the whole list, never between the <ul>
|
|
64344
|
+
// and the rows: the walk that gives the rows their places is over the <ul>'s
|
|
64345
|
+
// children, and a component sitting there is one child — every row would then
|
|
64346
|
+
// stand in a single slot, and reordering them (a search) would move nothing.
|
|
64347
|
+
const listVnode = jsx(ControlgroupChildrenWrapper, {
|
|
64348
|
+
...childrenWrapperProps,
|
|
64349
|
+
children: jsx(Next, {
|
|
64350
|
+
"navi-selectable": "",
|
|
64351
|
+
...listControlRootProps,
|
|
64352
|
+
...listControlProps,
|
|
64353
|
+
// "loading" is a control prop, so useControlgroupProps consumes it (into
|
|
64354
|
+
// aria-busy / the :-navi-loading pseudo state) and it does not survive
|
|
64355
|
+
// into the props below. ListUI needs it too — it is what makes the list
|
|
64356
|
+
// render skeleton rows instead of its (not yet known) items.
|
|
64357
|
+
loading: props.loading,
|
|
64358
|
+
name: undefined,
|
|
64359
|
+
value: undefined,
|
|
64360
|
+
defaultValue: undefined,
|
|
64361
|
+
selectable: undefined,
|
|
64362
|
+
multiple: undefined,
|
|
64363
|
+
deselectable: undefined,
|
|
64364
|
+
focusGroupDirection: undefined,
|
|
64365
|
+
focusGroupWrap: undefined
|
|
64366
|
+
// Track focus inside the list: whichever item gets focus becomes current.
|
|
64367
|
+
,
|
|
64368
|
+
onFocusIn: e => {
|
|
64369
|
+
const realInput = e.target.closest("[navi-selectable-real-input]");
|
|
64370
|
+
if (!realInput) {
|
|
64371
|
+
return;
|
|
64189
64372
|
}
|
|
64190
|
-
|
|
64191
|
-
|
|
64192
|
-
|
|
64193
|
-
|
|
64194
|
-
|
|
64195
|
-
|
|
64196
|
-
|
|
64197
|
-
|
|
64198
|
-
|
|
64199
|
-
|
|
64200
|
-
|
|
64201
|
-
|
|
64202
|
-
|
|
64203
|
-
|
|
64204
|
-
|
|
64205
|
-
|
|
64206
|
-
|
|
64207
|
-
|
|
64208
|
-
|
|
64209
|
-
|
|
64210
|
-
|
|
64211
|
-
|
|
64212
|
-
|
|
64213
|
-
|
|
64214
|
-
|
|
64215
|
-
|
|
64216
|
-
|
|
64217
|
-
|
|
64218
|
-
|
|
64219
|
-
|
|
64220
|
-
|
|
64221
|
-
|
|
64222
|
-
|
|
64223
|
-
|
|
64224
|
-
|
|
64373
|
+
const itemEl = realInput.closest("[navi-list-item-real]");
|
|
64374
|
+
if (itemEl && itemEl.id) {
|
|
64375
|
+
setCurrentId(itemEl.id, e);
|
|
64376
|
+
}
|
|
64377
|
+
},
|
|
64378
|
+
onnavi_request_select: e => {
|
|
64379
|
+
const {
|
|
64380
|
+
id
|
|
64381
|
+
} = e.detail;
|
|
64382
|
+
if (id === undefined) {
|
|
64383
|
+
return;
|
|
64384
|
+
}
|
|
64385
|
+
const inputId = `${id}_input`;
|
|
64386
|
+
const childController = uiGroupStateController.findChildById(inputId);
|
|
64387
|
+
if (!childController) {
|
|
64388
|
+
return;
|
|
64389
|
+
}
|
|
64390
|
+
const list = ref.current;
|
|
64391
|
+
dispatchRequestInteraction(list, {
|
|
64392
|
+
event: e,
|
|
64393
|
+
name: "select",
|
|
64394
|
+
prevented: () => e.preventDefault(),
|
|
64395
|
+
// tell the requester that we don't want to select this item
|
|
64396
|
+
// Asked of the item too, not only of the list: an item can be the one
|
|
64397
|
+
// refusing (the list already holds all it accepts, see maxLength), and
|
|
64398
|
+
// it is the one that then says why.
|
|
64399
|
+
allowed: () => {
|
|
64400
|
+
dispatchRequestInteraction(childController.ref.current, {
|
|
64401
|
+
event: e,
|
|
64402
|
+
name: "select",
|
|
64403
|
+
prevented: () => e.preventDefault(),
|
|
64404
|
+
allowed: () => childController.setUIState(childController.value, e)
|
|
64405
|
+
});
|
|
64406
|
+
}
|
|
64407
|
+
});
|
|
64408
|
+
},
|
|
64409
|
+
onnavi_request_unselect: e => {
|
|
64410
|
+
const {
|
|
64411
|
+
id
|
|
64412
|
+
} = e.detail;
|
|
64413
|
+
if (id === undefined) {
|
|
64414
|
+
return;
|
|
64415
|
+
}
|
|
64416
|
+
const inputId = `${id}_input`;
|
|
64417
|
+
const childController = uiGroupStateController.findChildById(inputId);
|
|
64418
|
+
if (!childController) {
|
|
64419
|
+
return;
|
|
64420
|
+
}
|
|
64421
|
+
const list = ref.current;
|
|
64422
|
+
dispatchRequestInteraction(list, {
|
|
64423
|
+
event: e,
|
|
64424
|
+
name: "unselect",
|
|
64425
|
+
prevented: () => e.preventDefault(),
|
|
64426
|
+
// tell the requester that we don't want to unselect this item
|
|
64427
|
+
allowed: () => childController.setUIState(undefined, e)
|
|
64428
|
+
});
|
|
64225
64429
|
}
|
|
64226
|
-
|
|
64227
|
-
|
|
64228
|
-
|
|
64229
|
-
|
|
64230
|
-
|
|
64231
|
-
|
|
64232
|
-
|
|
64233
|
-
|
|
64234
|
-
|
|
64235
|
-
|
|
64236
|
-
|
|
64237
|
-
|
|
64430
|
+
// "previous"/"next", not "up"/"down": a list is a line of items whichever
|
|
64431
|
+
// way it is laid out, and a horizontal one walks sideways. The keys that
|
|
64432
|
+
// drive it (arrows, Home/End) map onto that here, and so do the commands
|
|
64433
|
+
// (--navi-previous / --navi-next / --navi-first / --navi-last).
|
|
64434
|
+
,
|
|
64435
|
+
onnavi_request_nav: e => {
|
|
64436
|
+
const {
|
|
64437
|
+
goal
|
|
64438
|
+
} = e.detail;
|
|
64439
|
+
const navigableEls = getNavigableElements();
|
|
64440
|
+
if (navigableEls.length === 0) {
|
|
64441
|
+
return;
|
|
64442
|
+
}
|
|
64443
|
+
const currentId = currentIdRef.current;
|
|
64444
|
+
let currentIndex = -1;
|
|
64445
|
+
if (currentId) {
|
|
64446
|
+
currentIndex = navigableEls.findIndex(el => el.id === currentId);
|
|
64447
|
+
}
|
|
64448
|
+
let targetEl;
|
|
64449
|
+
if (goal === "first") {
|
|
64238
64450
|
targetEl = navigableEls[0];
|
|
64239
|
-
} else if (
|
|
64240
|
-
targetEl = navigableEls[currentIndex + 1];
|
|
64241
|
-
} else {
|
|
64451
|
+
} else if (goal === "last") {
|
|
64242
64452
|
targetEl = navigableEls[navigableEls.length - 1];
|
|
64453
|
+
} else if (goal === "next") {
|
|
64454
|
+
if (currentIndex === -1) {
|
|
64455
|
+
targetEl = navigableEls[0];
|
|
64456
|
+
} else if (currentIndex < navigableEls.length - 1) {
|
|
64457
|
+
targetEl = navigableEls[currentIndex + 1];
|
|
64458
|
+
} else {
|
|
64459
|
+
targetEl = navigableEls[navigableEls.length - 1];
|
|
64460
|
+
}
|
|
64461
|
+
} else if (goal === "previous") {
|
|
64462
|
+
if (currentIndex === -1) {
|
|
64463
|
+
targetEl = navigableEls[0];
|
|
64464
|
+
} else if (currentIndex > 0) {
|
|
64465
|
+
targetEl = navigableEls[currentIndex - 1];
|
|
64466
|
+
} else {
|
|
64467
|
+
targetEl = navigableEls[0];
|
|
64468
|
+
}
|
|
64243
64469
|
}
|
|
64244
|
-
|
|
64245
|
-
|
|
64246
|
-
targetEl = navigableEls[0];
|
|
64247
|
-
} else if (currentIndex > 0) {
|
|
64248
|
-
targetEl = navigableEls[currentIndex - 1];
|
|
64249
|
-
} else {
|
|
64250
|
-
targetEl = navigableEls[0];
|
|
64470
|
+
if (!targetEl) {
|
|
64471
|
+
return;
|
|
64251
64472
|
}
|
|
64252
|
-
|
|
64253
|
-
|
|
64254
|
-
|
|
64255
|
-
|
|
64256
|
-
|
|
64257
|
-
|
|
64258
|
-
|
|
64259
|
-
|
|
64260
|
-
|
|
64261
|
-
|
|
64262
|
-
|
|
64263
|
-
|
|
64264
|
-
|
|
64265
|
-
|
|
64266
|
-
|
|
64267
|
-
|
|
64268
|
-
|
|
64269
|
-
|
|
64270
|
-
|
|
64271
|
-
|
|
64473
|
+
setCurrentId(targetEl.id, e);
|
|
64474
|
+
dispatchCustomEvent(ref.current, "navi_request_scroll", {
|
|
64475
|
+
event: e,
|
|
64476
|
+
id: targetEl.id
|
|
64477
|
+
});
|
|
64478
|
+
},
|
|
64479
|
+
onnavi_request_activate: e => {
|
|
64480
|
+
const currentId = currentIdRef.current;
|
|
64481
|
+
if (!currentId) {
|
|
64482
|
+
return;
|
|
64483
|
+
}
|
|
64484
|
+
if (multiple || deselectable) {
|
|
64485
|
+
const inputId = `${currentId}_input`;
|
|
64486
|
+
const childController = uiGroupStateController.findChildById(inputId);
|
|
64487
|
+
const isSelected = childController && childController.uiState;
|
|
64488
|
+
dispatchCustomEvent(ref.current, isSelected ? "navi_request_unselect" : "navi_request_select", {
|
|
64489
|
+
event: e,
|
|
64490
|
+
id: currentId
|
|
64491
|
+
});
|
|
64492
|
+
return;
|
|
64493
|
+
}
|
|
64494
|
+
dispatchCustomEvent(ref.current, "navi_request_select", {
|
|
64272
64495
|
event: e,
|
|
64273
64496
|
id: currentId
|
|
64274
64497
|
});
|
|
64275
|
-
|
|
64276
|
-
}
|
|
64277
|
-
dispatchCustomEvent(ref.current, "navi_request_select", {
|
|
64278
|
-
event: e,
|
|
64279
|
-
id: currentId
|
|
64280
|
-
});
|
|
64281
|
-
},
|
|
64282
|
-
children: jsx(ControlgroupChildrenWrapper, {
|
|
64283
|
-
...childrenWrapperProps,
|
|
64498
|
+
},
|
|
64284
64499
|
children: props.children
|
|
64285
64500
|
})
|
|
64286
64501
|
});
|
|
@@ -65907,6 +66122,10 @@ const useListScrollSync = ({
|
|
|
65907
66122
|
const block = blockRequested || (event.type === "navi_displayed" ? "center" : "nearest");
|
|
65908
66123
|
const scrollToItemCall = `${getElementSignature(itemEl)}.scrollIntoView({ block: "${block}", container: "nearest" })`;
|
|
65909
66124
|
debugScroll(`${trigger} -> ${scrollToItemCall}`);
|
|
66125
|
+
// The list is going somewhere on purpose, so there is no view to hold
|
|
66126
|
+
// still any more: an anchor captured before this drop it, or it would
|
|
66127
|
+
// put the list back where it was the moment the rows move under it.
|
|
66128
|
+
anchorRef.current = null;
|
|
65910
66129
|
scrollIntoViewScoped(itemEl, {
|
|
65911
66130
|
container: getScroller(),
|
|
65912
66131
|
block
|
|
@@ -68004,6 +68223,28 @@ const createListVirtual = () => {
|
|
|
68004
68223
|
} else {
|
|
68005
68224
|
ownerIdsBySlot.set(slotId, [ownerId]);
|
|
68006
68225
|
}
|
|
68226
|
+
warnIfEveryRowInOneSlot(slotId);
|
|
68227
|
+
};
|
|
68228
|
+
// Rows that all stand in the same slot keep the order they first mounted in:
|
|
68229
|
+
// the walk is over the children the list is given, and a component holding
|
|
68230
|
+
// them is one child however many rows it renders. Everything about a place
|
|
68231
|
+
// then stops following what the caller writes — a search reordering the rows
|
|
68232
|
+
// moves nothing. Said once, and only for the shape that can be nothing else:
|
|
68233
|
+
// the list's whole content is one child, and several rows came out of it.
|
|
68234
|
+
let everyRowInOneSlotWarned = false;
|
|
68235
|
+
const warnIfEveryRowInOneSlot = slotId => {
|
|
68236
|
+
if (everyRowInOneSlotWarned) {
|
|
68237
|
+
return;
|
|
68238
|
+
}
|
|
68239
|
+
const rootSlotIds = slotIdsByParent.get(null);
|
|
68240
|
+
if (!rootSlotIds || rootSlotIds.length !== 1 || rootSlotIds[0] !== slotId) {
|
|
68241
|
+
return;
|
|
68242
|
+
}
|
|
68243
|
+
if (ownerIdsBySlot.get(slotId).length < 2) {
|
|
68244
|
+
return;
|
|
68245
|
+
}
|
|
68246
|
+
everyRowInOneSlotWarned = true;
|
|
68247
|
+
console.warn(`List: every row stands in the same slot, so they keep the order they first rendered in — reordering them (a search, a sort) will not move them. The list's rows must be its own children: give it the rows (or a <List.Items>), not a component rendering them.`);
|
|
68007
68248
|
};
|
|
68008
68249
|
const removeFromSlot = (slotId, ownerId) => {
|
|
68009
68250
|
const ownerIds = ownerIdsBySlot.get(slotId);
|
|
@@ -73460,6 +73701,26 @@ const css$r = /* css */`.navi_checkbox_group {
|
|
|
73460
73701
|
`;
|
|
73461
73702
|
|
|
73462
73703
|
/**
|
|
73704
|
+
* A set of checkboxes answering one question, holding the array of the values
|
|
73705
|
+
* that are checked.
|
|
73706
|
+
*
|
|
73707
|
+
* It renders a `<fieldset>` and hands its `name` down, so the boxes inside are
|
|
73708
|
+
* plain `<Input type="checkbox" value="…">` with nothing else to declare; each
|
|
73709
|
+
* value that is checked appears in the array, in the order the boxes are
|
|
73710
|
+
* written. What it holds is said the way every control says it — `value` /
|
|
73711
|
+
* `defaultValue` (an array), or a bound `signal` — and inside a `<Form>` the
|
|
73712
|
+
* group is one entry of the submitted params, an array.
|
|
73713
|
+
*
|
|
73714
|
+
* ```jsx
|
|
73715
|
+
* <CheckboxGroup name="permissions" defaultValue={["places.moderate"]} action={save}>
|
|
73716
|
+
* <legend>Permissions</legend>
|
|
73717
|
+
* <Field as="label"><Input type="checkbox" value="places.moderate" /> Modérer les lieux</Field>
|
|
73718
|
+
* <Field as="label"><Input type="checkbox" value="games.list" /> Lister les parties</Field>
|
|
73719
|
+
* </CheckboxGroup>
|
|
73720
|
+
* ```
|
|
73721
|
+
*
|
|
73722
|
+
* See `<RadioGroup>` for the same shape holding a single value.
|
|
73723
|
+
*
|
|
73463
73724
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
73464
73725
|
* maxLength?: number,
|
|
73465
73726
|
* maxLengthGuard?: number,
|
|
@@ -74181,6 +74442,27 @@ const css$p = /* css */`.navi_radio_group {
|
|
|
74181
74442
|
}
|
|
74182
74443
|
}
|
|
74183
74444
|
`;
|
|
74445
|
+
|
|
74446
|
+
/**
|
|
74447
|
+
* A set of radios answering one question, holding the value of the one that is
|
|
74448
|
+
* checked.
|
|
74449
|
+
*
|
|
74450
|
+
* It renders a `<fieldset>` and hands its `name` down, so the radios inside are
|
|
74451
|
+
* plain `<Input type="radio" value="…">` with nothing else to declare — sharing
|
|
74452
|
+
* a name is what makes them exclusive. What it holds is said the way every
|
|
74453
|
+
* control says it — `value` / `defaultValue`, or a bound `signal` — and inside a
|
|
74454
|
+
* `<Form>` the group is one entry of the submitted params.
|
|
74455
|
+
*
|
|
74456
|
+
* ```jsx
|
|
74457
|
+
* <RadioGroup name="role" defaultValue="viewer" action={save}>
|
|
74458
|
+
* <legend>Rôle</legend>
|
|
74459
|
+
* <Field as="label"><Input type="radio" value="viewer" /> Lecteur</Field>
|
|
74460
|
+
* <Field as="label"><Input type="radio" value="admin" /> Administrateur</Field>
|
|
74461
|
+
* </RadioGroup>
|
|
74462
|
+
* ```
|
|
74463
|
+
*
|
|
74464
|
+
* See `<CheckboxGroup>` for the same shape holding an array.
|
|
74465
|
+
*/
|
|
74184
74466
|
const RadioGroup = props => {
|
|
74185
74467
|
const refDefault = useRef(null);
|
|
74186
74468
|
props.ref = props.ref || refDefault;
|