@jsenv/navi 0.29.230 → 0.29.232
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 +385 -101
- package/dist/dev/jsenv_navi.js.map +16 -14
- 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 +360 -98
- package/dist/jsenv_navi.js.map +16 -14
- 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/typography.md +18 -0
- package/package.json +2 -2
package/dist/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
|
}
|
|
@@ -2849,6 +2849,9 @@ ${lines.join("\n")}`,
|
|
|
2849
2849
|
const willPrerunSet = new Set();
|
|
2850
2850
|
const willRunSet = new Set();
|
|
2851
2851
|
const willPromoteSet = new Set(); // prerun -> run requested
|
|
2852
|
+
// Asked for, already under way: the request adds no work, and answers with
|
|
2853
|
+
// the run that is already carrying it (see join_active_runs below).
|
|
2854
|
+
const willJoinSet = new Set();
|
|
2852
2855
|
const staysRunningSet = new Set();
|
|
2853
2856
|
const staysAbortedSet = new Set();
|
|
2854
2857
|
const staysFailedSet = new Set();
|
|
@@ -2900,8 +2903,11 @@ ${lines.join("\n")}`,
|
|
|
2900
2903
|
} else {
|
|
2901
2904
|
willRunSet.add(action);
|
|
2902
2905
|
}
|
|
2906
|
+
} else {
|
|
2907
|
+
// The request performs nothing, and still has to be answered: what
|
|
2908
|
+
// the caller asked for is the answer, not the running.
|
|
2909
|
+
willJoinSet.add(action);
|
|
2903
2910
|
}
|
|
2904
|
-
// Otherwise, ignore the request (action stays as-is)
|
|
2905
2911
|
} else if (isPrerun) {
|
|
2906
2912
|
willPrerunSet.add(action);
|
|
2907
2913
|
} else {
|
|
@@ -2928,6 +2934,7 @@ ${lines.join("\n")}`,
|
|
|
2928
2934
|
// Special case: action was prerun but not yet requested to run
|
|
2929
2935
|
// Just promote it to "run requested" without rerunning
|
|
2930
2936
|
willPromoteSet.add(actionToRun);
|
|
2937
|
+
willJoinSet.add(actionToRun);
|
|
2931
2938
|
continue;
|
|
2932
2939
|
}
|
|
2933
2940
|
handleActionRequest(actionToRun, "run");
|
|
@@ -3074,6 +3081,27 @@ ${lines.join("\n")}`);
|
|
|
3074
3081
|
actionToPromote.isPrerunSignal.value = false;
|
|
3075
3082
|
}
|
|
3076
3083
|
}
|
|
3084
|
+
|
|
3085
|
+
// A run asked for an action that is already running, or that already holds
|
|
3086
|
+
// the answer, performs nothing — and answers all the same, with the run
|
|
3087
|
+
// that IS carrying it. `await action.run()` then means the same thing to
|
|
3088
|
+
// every caller: the second one waits for the first one's request and reads
|
|
3089
|
+
// the same data, instead of being handed `undefined` before the network has
|
|
3090
|
+
// said anything. See the run() JSDoc.
|
|
3091
|
+
{
|
|
3092
|
+
for (const actionToJoin of willJoinSet) {
|
|
3093
|
+
const runningPromise = actionPromiseMap.get(actionToJoin);
|
|
3094
|
+
if (runningPromise) {
|
|
3095
|
+
hasAsync = true;
|
|
3096
|
+
resultArray.push({ type: "async", promise: runningPromise });
|
|
3097
|
+
} else {
|
|
3098
|
+
resultArray.push({
|
|
3099
|
+
type: "sync",
|
|
3100
|
+
result: actionToJoin.dataSignal.peek(),
|
|
3101
|
+
});
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3077
3105
|
if (DEBUG$2) {
|
|
3078
3106
|
console.groupEnd();
|
|
3079
3107
|
}
|
|
@@ -3228,10 +3256,15 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3228
3256
|
return dispatchSingleAction(action, "prerun", options);
|
|
3229
3257
|
};
|
|
3230
3258
|
/**
|
|
3231
|
-
* Requests the action's data
|
|
3232
|
-
*
|
|
3233
|
-
*
|
|
3234
|
-
*
|
|
3259
|
+
* Requests the action's data, and resolves with it.
|
|
3260
|
+
*
|
|
3261
|
+
* An action that is already RUNNING or COMPLETED already has it, so the
|
|
3262
|
+
* request starts nothing — it JOINS: the caller waits for the run that is
|
|
3263
|
+
* already carrying the question and reads the same answer. Which is what
|
|
3264
|
+
* makes `await action.run()` say one thing to every caller, whether it is
|
|
3265
|
+
* the one that asked first or the one that arrived while the request was
|
|
3266
|
+
* out. Use `rerun()` to force a fresh run ("refresh", "check now", any
|
|
3267
|
+
* explicit user intent to go back to the network).
|
|
3235
3268
|
*/
|
|
3236
3269
|
const run = (options) => {
|
|
3237
3270
|
action.debug(`${action}.run(${stringifyForDisplay(options)})`);
|
|
@@ -5749,7 +5782,7 @@ installImportMetaCssBuild(import.meta);
|
|
|
5749
5782
|
* (see calloutTemplate in callout.js).
|
|
5750
5783
|
*/
|
|
5751
5784
|
|
|
5752
|
-
const css$
|
|
5785
|
+
const css$14 = /* css */`.navi_callout_status_icon {
|
|
5753
5786
|
--x-callout-status-icon-color: var(--navi-callout-neutral-color);
|
|
5754
5787
|
box-sizing: border-box;
|
|
5755
5788
|
aspect-ratio: 1;
|
|
@@ -5820,7 +5853,7 @@ const CalloutStatusIcon = ({
|
|
|
5820
5853
|
status = "info",
|
|
5821
5854
|
shape = "square"
|
|
5822
5855
|
}) => {
|
|
5823
|
-
import.meta.css = [css$
|
|
5856
|
+
import.meta.css = [css$14, "@jsenv/navi/src/control/rules/callout/callout_status_icon.jsx"];
|
|
5824
5857
|
return jsx("span", {
|
|
5825
5858
|
className: "navi_callout_status_icon",
|
|
5826
5859
|
"data-status": status === "none" ? undefined : status,
|
|
@@ -5842,7 +5875,7 @@ installImportMetaCssBuild(import.meta);
|
|
|
5842
5875
|
// Unique for the page's lifetime: a caller may write the id in a commandfor.
|
|
5843
5876
|
let calloutCount = 0;
|
|
5844
5877
|
|
|
5845
|
-
const css$
|
|
5878
|
+
const css$13 = /* css */ `
|
|
5846
5879
|
@layer navi {
|
|
5847
5880
|
.navi_callout {
|
|
5848
5881
|
/* A callout is parented to what it explains, so it inherits from it — and
|
|
@@ -6190,7 +6223,7 @@ const openCallout = (
|
|
|
6190
6223
|
debug = () => {},
|
|
6191
6224
|
} = {},
|
|
6192
6225
|
) => {
|
|
6193
|
-
import.meta.css = [css$
|
|
6226
|
+
import.meta.css = [css$13, "@jsenv/navi/src/control/rules/callout/callout.js"];
|
|
6194
6227
|
if (debug === true) {
|
|
6195
6228
|
debug = (e, ...args) => console.debug(`"${e.type}" -> `, ...args);
|
|
6196
6229
|
}
|
|
@@ -18238,8 +18271,12 @@ const VISUAL_PROPS = {
|
|
|
18238
18271
|
outlineWidth: PASS_THROUGH,
|
|
18239
18272
|
boxDecorationBreak: PASS_THROUGH,
|
|
18240
18273
|
boxShadow: PASS_THROUGH,
|
|
18241
|
-
background:
|
|
18242
|
-
|
|
18274
|
+
background: (value) => {
|
|
18275
|
+
return { background: resolveSurfaceKeyword(value) };
|
|
18276
|
+
},
|
|
18277
|
+
backgroundColor: (value) => {
|
|
18278
|
+
return { backgroundColor: resolveSurfaceKeyword(value) };
|
|
18279
|
+
},
|
|
18243
18280
|
backgroundImage: PASS_THROUGH,
|
|
18244
18281
|
backgroundSize: PASS_THROUGH,
|
|
18245
18282
|
border: PASS_THROUGH,
|
|
@@ -18604,6 +18641,18 @@ const resolveColorKeyword = (value) => {
|
|
|
18604
18641
|
return COLOR_KEYWORD_MAP[value] || value;
|
|
18605
18642
|
};
|
|
18606
18643
|
|
|
18644
|
+
// The two planes an app paints: the paper its content sits on, and the frame
|
|
18645
|
+
// around it (see navi_css_vars.js). Named rather than derived at each call
|
|
18646
|
+
// site, so a top bar and a side menu are the same plane without agreeing on a
|
|
18647
|
+
// formula.
|
|
18648
|
+
const SURFACE_KEYWORD_MAP = {
|
|
18649
|
+
surface: "var(--navi-surface-color)",
|
|
18650
|
+
chrome: "var(--navi-chrome-color)",
|
|
18651
|
+
};
|
|
18652
|
+
const resolveSurfaceKeyword = (value) => {
|
|
18653
|
+
return SURFACE_KEYWORD_MAP[value] || value;
|
|
18654
|
+
};
|
|
18655
|
+
|
|
18607
18656
|
const DEFAULT_DISPLAY_BY_TAG_NAME = {
|
|
18608
18657
|
"inline": new Set([
|
|
18609
18658
|
"a",
|
|
@@ -20505,6 +20554,16 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
20505
20554
|
* children?: import("ignore:preact").ComponentChildren,
|
|
20506
20555
|
* [key: string]: any,
|
|
20507
20556
|
* }>}
|
|
20557
|
+
* @param {string} [background] Any CSS background, plus two keywords for the
|
|
20558
|
+
* planes an app paints: `"surface"` is the paper content sits on,
|
|
20559
|
+
* `"chrome"` the frame around a screen — a top bar, a side menu, a toolbar.
|
|
20560
|
+
* Both follow the theme, so a bar stays a bar in dark mode
|
|
20561
|
+
* (`--navi-surface-color` / `--navi-chrome-color`). `backgroundColor` takes
|
|
20562
|
+
* the same two.
|
|
20563
|
+
* @param {string} [color] Any CSS color, plus the five ink keywords —
|
|
20564
|
+
* `"primary"`, `"secondary"`, `"emphasis"`, `"discrete"`, `"hint"` — which
|
|
20565
|
+
* say how loud a text is rather than which color it is (see
|
|
20566
|
+
* docs/typography.md).
|
|
20508
20567
|
* @param {object} [interactions] What this box answers, by interaction name
|
|
20509
20568
|
* (see docs/interactions.md). A plain box has no wiring of its own: it does
|
|
20510
20569
|
* nothing with `action` (that is a control's prop), so a click on it is
|
|
@@ -22408,6 +22467,20 @@ const debug$2 = (...args) => {
|
|
|
22408
22467
|
// Base URL management
|
|
22409
22468
|
let baseFileUrl;
|
|
22410
22469
|
let baseUrl;
|
|
22470
|
+
/**
|
|
22471
|
+
* Where this document's addresses start, for a document that is not at the
|
|
22472
|
+
* root: a panel served from "/admin/admin.html" declares
|
|
22473
|
+
* `setBaseUrl("/admin/admin.html")` and then writes its routes as "/places",
|
|
22474
|
+
* "/places/:placeId". The url "/admin/places/42" matches, and `buildUrl` puts
|
|
22475
|
+
* the prefix back.
|
|
22476
|
+
*
|
|
22477
|
+
* Call it in the routes module, above the `route()` calls it governs — they
|
|
22478
|
+
* read the base url as they are created, so an entry point calling it after
|
|
22479
|
+
* importing them is too late. The server must serve the document for every
|
|
22480
|
+
* address below it (see docs/navigation.md).
|
|
22481
|
+
*
|
|
22482
|
+
* @param {string} value - the document's own url, absolute or relative.
|
|
22483
|
+
*/
|
|
22411
22484
|
const setBaseUrl = (value) => {
|
|
22412
22485
|
baseFileUrl = new URL(
|
|
22413
22486
|
value,
|
|
@@ -28120,7 +28193,7 @@ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
|
|
|
28120
28193
|
// the root pictures must NOT move (they carry the whole viewport, blank bands
|
|
28121
28194
|
// included).
|
|
28122
28195
|
|
|
28123
|
-
const css$
|
|
28196
|
+
const css$12 = /* css */`:root[data-navi-route-transition] [data-navi-route-transition-area] {
|
|
28124
28197
|
view-transition-name: navi-route-transition;
|
|
28125
28198
|
}
|
|
28126
28199
|
|
|
@@ -28441,7 +28514,7 @@ const RouteTransitionArea = ({
|
|
|
28441
28514
|
children,
|
|
28442
28515
|
...rest
|
|
28443
28516
|
}) => {
|
|
28444
|
-
import.meta.css = [css$
|
|
28517
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
28445
28518
|
installTransitionWindowCss();
|
|
28446
28519
|
const props = {
|
|
28447
28520
|
...rest,
|
|
@@ -28511,7 +28584,7 @@ const RouteTransitionArea = ({
|
|
|
28511
28584
|
* @returns {() => void} remove this relation.
|
|
28512
28585
|
*/
|
|
28513
28586
|
const defineRouteTransition = (from, to, transition) => {
|
|
28514
|
-
import.meta.css = [css$
|
|
28587
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
28515
28588
|
installTransitionWindowCss();
|
|
28516
28589
|
if (!to) {
|
|
28517
28590
|
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.`);
|
|
@@ -28552,7 +28625,7 @@ const defineRouteTransition = (from, to, transition) => {
|
|
|
28552
28625
|
* @returns {() => void} remove this default.
|
|
28553
28626
|
*/
|
|
28554
28627
|
const defineRouteDefaultTransition = transition => {
|
|
28555
|
-
import.meta.css = [css$
|
|
28628
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
28556
28629
|
installTransitionWindowCss();
|
|
28557
28630
|
const value = normalizeTransition(transition);
|
|
28558
28631
|
defaultTransition = value;
|
|
@@ -29374,7 +29447,7 @@ const DRAGGED_ATTRIBUTE = "data-navi-route-travel-dragged";
|
|
|
29374
29447
|
const TURNED_ATTRIBUTE = "data-navi-route-travel-turned";
|
|
29375
29448
|
// The name the box wears while it travels, and only then (see nameForTravel).
|
|
29376
29449
|
const TRAVEL_NAME = "navi-route-travel";
|
|
29377
|
-
const css$
|
|
29450
|
+
const css$11 = /* css */`.navi_route_travel {
|
|
29378
29451
|
position: relative;
|
|
29379
29452
|
|
|
29380
29453
|
&[data-travel-by-drag="x"] {
|
|
@@ -29663,7 +29736,7 @@ const RouteTravel = ({
|
|
|
29663
29736
|
children,
|
|
29664
29737
|
...rest
|
|
29665
29738
|
}) => {
|
|
29666
|
-
import.meta.css = [css$
|
|
29739
|
+
import.meta.css = [css$11, "@jsenv/navi/src/nav/route_travel.jsx"];
|
|
29667
29740
|
installTransitionWindowCss();
|
|
29668
29741
|
const elementRef = useRef();
|
|
29669
29742
|
const gestureRef = useRef(null);
|
|
@@ -39227,7 +39300,7 @@ const setupNetworkMonitoring = () => {
|
|
|
39227
39300
|
setupNetworkMonitoring();
|
|
39228
39301
|
|
|
39229
39302
|
installImportMetaCssBuild(import.meta);
|
|
39230
|
-
const css
|
|
39303
|
+
const css$10 = /* css */`.navi_loading_indicator_fluid_container {
|
|
39231
39304
|
border-radius: inherit;
|
|
39232
39305
|
opacity: 1;
|
|
39233
39306
|
width: 100%;
|
|
@@ -39258,7 +39331,7 @@ const LoadingIndicatorFluid = ({
|
|
|
39258
39331
|
visuallyHidden,
|
|
39259
39332
|
...rest
|
|
39260
39333
|
}) => {
|
|
39261
|
-
import.meta.css = [css
|
|
39334
|
+
import.meta.css = [css$10, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
|
|
39262
39335
|
const ref = useRef(null);
|
|
39263
39336
|
// The container dimensions can be deduced from the ref itself as the indicator is absolute inset 0
|
|
39264
39337
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
@@ -39464,7 +39537,7 @@ const LoadingRectangleSvg = ({
|
|
|
39464
39537
|
};
|
|
39465
39538
|
|
|
39466
39539
|
installImportMetaCssBuild(import.meta);
|
|
39467
|
-
const css
|
|
39540
|
+
const css$$ = /* css */`.navi_loading_outline_wrapper {
|
|
39468
39541
|
top: max(var(--loading-outline-min-inset, -100vh),
|
|
39469
39542
|
var(--loading-rectangle-top, 0px));
|
|
39470
39543
|
right: max(var(--loading-outline-min-inset, -100vh),
|
|
@@ -39485,7 +39558,7 @@ const css$_ = /* css */`.navi_loading_outline_wrapper {
|
|
|
39485
39558
|
}
|
|
39486
39559
|
`;
|
|
39487
39560
|
const LoadingOutline = props => {
|
|
39488
|
-
import.meta.css = [css
|
|
39561
|
+
import.meta.css = [css$$, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
|
|
39489
39562
|
if (props.containerRef) {
|
|
39490
39563
|
const container = props.containerRef.current;
|
|
39491
39564
|
if (!container) {
|
|
@@ -39620,7 +39693,7 @@ const LoadingOutlineWithPortal = props => {
|
|
|
39620
39693
|
};
|
|
39621
39694
|
|
|
39622
39695
|
installImportMetaCssBuild(import.meta);
|
|
39623
|
-
const css$
|
|
39696
|
+
const css$_ = /* css */`.navi_text_anchor {
|
|
39624
39697
|
vertical-align: baseline;
|
|
39625
39698
|
user-select: none;
|
|
39626
39699
|
overflow: hidden;
|
|
@@ -39658,7 +39731,7 @@ const TextAnchor = ({
|
|
|
39658
39731
|
textSize,
|
|
39659
39732
|
lineLayout
|
|
39660
39733
|
}) => {
|
|
39661
|
-
import.meta.css = [css$
|
|
39734
|
+
import.meta.css = [css$_, "@jsenv/navi/src/text/text_anchor.jsx"];
|
|
39662
39735
|
const anchorRef = useRef();
|
|
39663
39736
|
|
|
39664
39737
|
// Plain useLayoutEffect would also fire while an ancestor dialog/popover
|
|
@@ -39886,7 +39959,7 @@ const selectByTextStrings = (element, range, startText, endText) => {
|
|
|
39886
39959
|
};
|
|
39887
39960
|
|
|
39888
39961
|
installImportMetaCssBuild(import.meta);
|
|
39889
|
-
const css$
|
|
39962
|
+
const css$Z = /* css */`@layer navi {
|
|
39890
39963
|
.navi_text {
|
|
39891
39964
|
&[data-skeleton] {
|
|
39892
39965
|
border-radius: .2em;
|
|
@@ -40455,7 +40528,7 @@ const TextShrinkWrap = props => {
|
|
|
40455
40528
|
});
|
|
40456
40529
|
};
|
|
40457
40530
|
const TextUI = props => {
|
|
40458
|
-
import.meta.css = [css$
|
|
40531
|
+
import.meta.css = [css$Z, "@jsenv/navi/src/text/text.jsx"];
|
|
40459
40532
|
let {
|
|
40460
40533
|
ref,
|
|
40461
40534
|
spacing,
|
|
@@ -40679,7 +40752,7 @@ const Icon = ({
|
|
|
40679
40752
|
fillLine,
|
|
40680
40753
|
...props
|
|
40681
40754
|
}) => {
|
|
40682
|
-
import.meta.css = [css$
|
|
40755
|
+
import.meta.css = [css$Z, "@jsenv/navi/src/text/text.jsx"];
|
|
40683
40756
|
const innerChildren = href ? jsx("svg", {
|
|
40684
40757
|
width: "100%",
|
|
40685
40758
|
height: "100%",
|
|
@@ -40869,7 +40942,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
40869
40942
|
};
|
|
40870
40943
|
|
|
40871
40944
|
installImportMetaCssBuild(import.meta);
|
|
40872
|
-
const css$
|
|
40945
|
+
const css$Y = /* css */`@layer navi {
|
|
40873
40946
|
.navi_link {
|
|
40874
40947
|
--link-border-radius: unset;
|
|
40875
40948
|
--link-outline-color: var(--navi-focus-outline-color);
|
|
@@ -41327,7 +41400,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
41327
41400
|
* @param {boolean} [props.readOnly]
|
|
41328
41401
|
*/
|
|
41329
41402
|
const Link = props => {
|
|
41330
|
-
import.meta.css = [css$
|
|
41403
|
+
import.meta.css = [css$Y, "@jsenv/navi/src/nav/link/link.jsx"];
|
|
41331
41404
|
if (props.route) {
|
|
41332
41405
|
return jsx(LinkWithRoute, {
|
|
41333
41406
|
...props
|
|
@@ -41886,7 +41959,7 @@ const rememberProgressAnimation = (navElement, travel) => {
|
|
|
41886
41959
|
travel.progressAnimation = animation;
|
|
41887
41960
|
travel.progressFrom = animation ? parseFloat(animation.effect.getKeyframes()[0]["--slide-travel-progress"]) || 0 : 0;
|
|
41888
41961
|
};
|
|
41889
|
-
const css$
|
|
41962
|
+
const css$X = /* css */`@layer navi {
|
|
41890
41963
|
.navi_nav {
|
|
41891
41964
|
--nav-border: none;
|
|
41892
41965
|
--nav-padding: 0px;
|
|
@@ -42150,7 +42223,7 @@ const Nav = ({
|
|
|
42150
42223
|
slideContainer,
|
|
42151
42224
|
...props
|
|
42152
42225
|
}) => {
|
|
42153
|
-
import.meta.css = [css$
|
|
42226
|
+
import.meta.css = [css$X, "@jsenv/navi/src/nav/link/nav.jsx"];
|
|
42154
42227
|
const defaultRef = useRef();
|
|
42155
42228
|
props.ref = props.ref || defaultRef;
|
|
42156
42229
|
const navRef = props.ref;
|
|
@@ -43026,7 +43099,7 @@ const buildBinderCenterlinePathBelow = ({
|
|
|
43026
43099
|
};
|
|
43027
43100
|
|
|
43028
43101
|
installImportMetaCssBuild(import.meta);
|
|
43029
|
-
const css$
|
|
43102
|
+
const css$W = /* css */`@layer navi {
|
|
43030
43103
|
.navi_binder {
|
|
43031
43104
|
--binder-border-width: var(--navi-control-border-width);
|
|
43032
43105
|
--binder-border-radius: var(--navi-control-border-radius);
|
|
@@ -43311,7 +43384,7 @@ const Binder = ({
|
|
|
43311
43384
|
pagePadding,
|
|
43312
43385
|
...props
|
|
43313
43386
|
}) => {
|
|
43314
|
-
import.meta.css = [css$
|
|
43387
|
+
import.meta.css = [css$W, "@jsenv/navi/src/nav/binder/binder.jsx"];
|
|
43315
43388
|
const items = toChildArray(children).map((child, index) => {
|
|
43316
43389
|
const {
|
|
43317
43390
|
value: itemValue,
|
|
@@ -43758,7 +43831,7 @@ const writeSpace = (area) => {
|
|
|
43758
43831
|
};
|
|
43759
43832
|
|
|
43760
43833
|
installImportMetaCssBuild(import.meta);
|
|
43761
|
-
const css$
|
|
43834
|
+
const css$V = /* css */`@layer navi {
|
|
43762
43835
|
:root {
|
|
43763
43836
|
--navi-fixed-bar-width: 56px;
|
|
43764
43837
|
--navi-fixed-bar-height: 56px;
|
|
@@ -43878,7 +43951,7 @@ const FixedBar = ({
|
|
|
43878
43951
|
border = true,
|
|
43879
43952
|
...props
|
|
43880
43953
|
}) => {
|
|
43881
|
-
import.meta.css = [css$
|
|
43954
|
+
import.meta.css = [css$V, "@jsenv/navi/src/layout/fixed_bar/fixed_bar.jsx"];
|
|
43882
43955
|
const defaultRef = useRef();
|
|
43883
43956
|
props.ref = props.ref || defaultRef;
|
|
43884
43957
|
// Said with the width the border rule reads rather than with an attribute of
|
|
@@ -43972,7 +44045,7 @@ const FixedBar = ({
|
|
|
43972
44045
|
// Subpixel layout rounds rectangles up on boxes that fit exactly.
|
|
43973
44046
|
const OVERFLOW_TOLERANCE = 1;
|
|
43974
44047
|
|
|
43975
|
-
const css$
|
|
44048
|
+
const css$U = /* css */ `
|
|
43976
44049
|
[data-navi-overflow-x] {
|
|
43977
44050
|
outline: 2px dashed #e74c3c;
|
|
43978
44051
|
outline-offset: -2px;
|
|
@@ -43996,7 +44069,7 @@ const detectHorizontalOverflow = ({
|
|
|
43996
44069
|
let styleEl = null;
|
|
43997
44070
|
if (highlight) {
|
|
43998
44071
|
styleEl = document.createElement("style");
|
|
43999
|
-
styleEl.textContent = css$
|
|
44072
|
+
styleEl.textContent = css$U;
|
|
44000
44073
|
document.head.appendChild(styleEl);
|
|
44001
44074
|
}
|
|
44002
44075
|
|
|
@@ -44152,7 +44225,7 @@ const useFocusGroup = (
|
|
|
44152
44225
|
|
|
44153
44226
|
installImportMetaCssBuild(import.meta);
|
|
44154
44227
|
const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
|
|
44155
|
-
const css$
|
|
44228
|
+
const css$T = /* css */`.navi_summary_marker {
|
|
44156
44229
|
flex-shrink: 0;
|
|
44157
44230
|
width: 1em;
|
|
44158
44231
|
height: 1em;
|
|
@@ -44244,7 +44317,7 @@ const SummaryMarker = ({
|
|
|
44244
44317
|
loading,
|
|
44245
44318
|
openDirection = "down"
|
|
44246
44319
|
}) => {
|
|
44247
|
-
import.meta.css = [css$
|
|
44320
|
+
import.meta.css = [css$T, "@jsenv/navi/src/control/details/summary_marker.jsx"];
|
|
44248
44321
|
const showLoading = useDebounceTrue(loading, 300);
|
|
44249
44322
|
return jsx("span", {
|
|
44250
44323
|
className: "navi_summary_marker",
|
|
@@ -44290,7 +44363,7 @@ const SummaryMarker = ({
|
|
|
44290
44363
|
};
|
|
44291
44364
|
|
|
44292
44365
|
installImportMetaCssBuild(import.meta);
|
|
44293
|
-
const css$
|
|
44366
|
+
const css$S = /* css */`.navi_details {
|
|
44294
44367
|
z-index: 1;
|
|
44295
44368
|
flex-direction: column;
|
|
44296
44369
|
flex-shrink: 0;
|
|
@@ -44335,7 +44408,7 @@ const Details = props => {
|
|
|
44335
44408
|
return details;
|
|
44336
44409
|
};
|
|
44337
44410
|
const DetailsField = props => {
|
|
44338
|
-
import.meta.css = [css$
|
|
44411
|
+
import.meta.css = [css$S, "@jsenv/navi/src/control/details/details.jsx"];
|
|
44339
44412
|
const {
|
|
44340
44413
|
ref,
|
|
44341
44414
|
persists,
|
|
@@ -46080,7 +46153,7 @@ const usePopupContentMount = (
|
|
|
46080
46153
|
};
|
|
46081
46154
|
|
|
46082
46155
|
installImportMetaCssBuild(import.meta);
|
|
46083
|
-
const css$
|
|
46156
|
+
const css$R = /* css */`.navi_expandable {
|
|
46084
46157
|
flex-direction: column;
|
|
46085
46158
|
flex-shrink: 0;
|
|
46086
46159
|
display: flex;
|
|
@@ -46310,7 +46383,7 @@ const useExpandableContext = partName => {
|
|
|
46310
46383
|
* builds the content ahead of the click.
|
|
46311
46384
|
*/
|
|
46312
46385
|
const Expandable = props => {
|
|
46313
|
-
import.meta.css = [css$
|
|
46386
|
+
import.meta.css = [css$R, "@jsenv/navi/src/control/expandable/expandable.jsx"];
|
|
46314
46387
|
/* The open props are read from `props` by the open controller below; they
|
|
46315
46388
|
are named here only to keep them out of `rest`, and so out of the DOM. */
|
|
46316
46389
|
/* eslint-disable no-unused-vars */
|
|
@@ -46997,46 +47070,6 @@ const shallowDiffers = (a, b) => {
|
|
|
46997
47070
|
return false;
|
|
46998
47071
|
};
|
|
46999
47072
|
|
|
47000
|
-
const ButtonRouteResolver = props => {
|
|
47001
|
-
const Next = useNextResolver();
|
|
47002
|
-
if (props.route) {
|
|
47003
|
-
return jsx(ButtonWithRoute, {
|
|
47004
|
-
...props
|
|
47005
|
-
});
|
|
47006
|
-
}
|
|
47007
|
-
return jsx(Next, {
|
|
47008
|
-
...props
|
|
47009
|
-
});
|
|
47010
|
-
};
|
|
47011
|
-
const ButtonWithRoute = props => {
|
|
47012
|
-
const Next = useNextResolver();
|
|
47013
|
-
const {
|
|
47014
|
-
route,
|
|
47015
|
-
routeParams,
|
|
47016
|
-
children,
|
|
47017
|
-
pseudoState,
|
|
47018
|
-
...rest
|
|
47019
|
-
} = props;
|
|
47020
|
-
const url = route.buildUrl(routeParams);
|
|
47021
|
-
const {
|
|
47022
|
-
matching
|
|
47023
|
-
} = useRouteStatus(route);
|
|
47024
|
-
const paramsAreMatching = route.matchesParams(routeParams);
|
|
47025
|
-
const linkMatching = matching && paramsAreMatching;
|
|
47026
|
-
|
|
47027
|
-
// Merged into whatever the caller already holds: a button can be forced into
|
|
47028
|
-
// a state for a demo and still learn its own current-ness from its route.
|
|
47029
|
-
return jsx(Next, {
|
|
47030
|
-
href: url,
|
|
47031
|
-
pseudoState: {
|
|
47032
|
-
...pseudoState,
|
|
47033
|
-
":-navi-href-current": linkMatching
|
|
47034
|
-
},
|
|
47035
|
-
...rest,
|
|
47036
|
-
children: children || route.buildRelativeUrl(routeParams)
|
|
47037
|
-
});
|
|
47038
|
-
};
|
|
47039
|
-
|
|
47040
47073
|
const LIGHT_ACCENT_ATTRIBUTE = "data-accent-light";
|
|
47041
47074
|
const VERY_LIGHT_ACCENT_ATTRIBUTE = "data-accent-very-light";
|
|
47042
47075
|
const DARK_CONTRAST_ATTRIBUTE = "data-accent-needs-dark-fg";
|
|
@@ -47137,7 +47170,7 @@ const useAccentColorAttributes = (
|
|
|
47137
47170
|
};
|
|
47138
47171
|
|
|
47139
47172
|
installImportMetaCssBuild(import.meta);
|
|
47140
|
-
const css$
|
|
47173
|
+
const css$Q = /* css */`@layer navi {
|
|
47141
47174
|
.navi_button {
|
|
47142
47175
|
--button-border-radius: var(--navi-control-border-radius);
|
|
47143
47176
|
--button-border-width: var(--navi-control-border-width);
|
|
@@ -47438,7 +47471,7 @@ a.navi_button {
|
|
|
47438
47471
|
}
|
|
47439
47472
|
`;
|
|
47440
47473
|
const ButtonUI = props => {
|
|
47441
|
-
import.meta.css = [css$
|
|
47474
|
+
import.meta.css = [css$Q, "@jsenv/navi/src/control/input/button_ui.jsx"];
|
|
47442
47475
|
const {
|
|
47443
47476
|
ref,
|
|
47444
47477
|
// href/link
|
|
@@ -47653,6 +47686,186 @@ const ButtonShadow = () => {
|
|
|
47653
47686
|
};
|
|
47654
47687
|
markAsOutsideTextFlow(ButtonShadow);
|
|
47655
47688
|
|
|
47689
|
+
installImportMetaCssBuild(import.meta);
|
|
47690
|
+
const css$P = /* css */`.navi_button_confirm {
|
|
47691
|
+
align-items: center;
|
|
47692
|
+
gap: var(--navi-s);
|
|
47693
|
+
flex-wrap: wrap;
|
|
47694
|
+
display: inline-flex;
|
|
47695
|
+
}
|
|
47696
|
+
`;
|
|
47697
|
+
const ButtonConfirmResolver = props => {
|
|
47698
|
+
const Next = useNextResolver();
|
|
47699
|
+
if (!props.confirm) {
|
|
47700
|
+
return jsx(Next, {
|
|
47701
|
+
...props
|
|
47702
|
+
});
|
|
47703
|
+
}
|
|
47704
|
+
return jsx(ButtonConfirm, {
|
|
47705
|
+
...props,
|
|
47706
|
+
Next: Next
|
|
47707
|
+
});
|
|
47708
|
+
};
|
|
47709
|
+
const ButtonConfirm = ({
|
|
47710
|
+
Next,
|
|
47711
|
+
confirm,
|
|
47712
|
+
confirmLabel = naviI18n("button.confirm"),
|
|
47713
|
+
cancelLabel = naviI18n("button.cancel"),
|
|
47714
|
+
confirmTestId,
|
|
47715
|
+
cancelTestId,
|
|
47716
|
+
...props
|
|
47717
|
+
}) => {
|
|
47718
|
+
import.meta.css = [css$P, "@jsenv/navi/src/control/input/button_confirm.jsx"];
|
|
47719
|
+
const [asking, setAsking] = useState(false);
|
|
47720
|
+
const askingRef = useRef(null);
|
|
47721
|
+
const cancelRef = useRef(null);
|
|
47722
|
+
// Whether going back to the resting label owes the focus a place to land: a
|
|
47723
|
+
// press on "Annuler" or an Escape does, a focus that already left does not —
|
|
47724
|
+
// it is elsewhere by then, and pulling it back would take it from whatever
|
|
47725
|
+
// the person moved to.
|
|
47726
|
+
const restoreFocusRef = useRef(false);
|
|
47727
|
+
const questionId = `${useId()}_question`;
|
|
47728
|
+
const cancel = restoreFocus => {
|
|
47729
|
+
restoreFocusRef.current = restoreFocus;
|
|
47730
|
+
setAsking(false);
|
|
47731
|
+
};
|
|
47732
|
+
useLayoutEffect(() => {
|
|
47733
|
+
if (asking) {
|
|
47734
|
+
// The press was aimed at this button, and the button that answers now is
|
|
47735
|
+
// where it should land: navi's own ladder inside the question, which
|
|
47736
|
+
// finds "Confirmer" (first in the row) unless the caller marked
|
|
47737
|
+
// something else.
|
|
47738
|
+
const found = findFocusTarget(askingRef.current, {
|
|
47739
|
+
restoreMayClaim: true
|
|
47740
|
+
});
|
|
47741
|
+
if (found) {
|
|
47742
|
+
moveFocusTo(found.target);
|
|
47743
|
+
}
|
|
47744
|
+
return;
|
|
47745
|
+
}
|
|
47746
|
+
if (restoreFocusRef.current) {
|
|
47747
|
+
restoreFocusRef.current = false;
|
|
47748
|
+
// The answer may have taken the button with it — a row deleting itself
|
|
47749
|
+
// is the whole point of asking — and there is then nothing to give the
|
|
47750
|
+
// focus back to.
|
|
47751
|
+
const buttonEl = props.ref.current;
|
|
47752
|
+
if (buttonEl) {
|
|
47753
|
+
moveFocusTo(buttonEl);
|
|
47754
|
+
}
|
|
47755
|
+
}
|
|
47756
|
+
}, [asking]);
|
|
47757
|
+
if (!asking) {
|
|
47758
|
+
return jsx(Next, {
|
|
47759
|
+
...props,
|
|
47760
|
+
// The first press is the question, not the act — so nothing that acts
|
|
47761
|
+
// is handed to the resolvers below. `type` with them: a submit button
|
|
47762
|
+
// would be given `--navi-send` back.
|
|
47763
|
+
type: "button",
|
|
47764
|
+
action: undefined,
|
|
47765
|
+
command: undefined,
|
|
47766
|
+
href: undefined,
|
|
47767
|
+
route: undefined,
|
|
47768
|
+
onClick: () => {
|
|
47769
|
+
setAsking(true);
|
|
47770
|
+
}
|
|
47771
|
+
});
|
|
47772
|
+
}
|
|
47773
|
+
const {
|
|
47774
|
+
action,
|
|
47775
|
+
onActionEnd,
|
|
47776
|
+
onClick
|
|
47777
|
+
} = props;
|
|
47778
|
+
return jsxs("span", {
|
|
47779
|
+
className: "navi_button_confirm",
|
|
47780
|
+
ref: askingRef,
|
|
47781
|
+
onKeyDown: e => {
|
|
47782
|
+
if (e.key === "Escape") {
|
|
47783
|
+
cancel(true);
|
|
47784
|
+
}
|
|
47785
|
+
},
|
|
47786
|
+
onFocusOut: e => {
|
|
47787
|
+
if (!askingRef.current.contains(e.relatedTarget)) {
|
|
47788
|
+
cancel(false);
|
|
47789
|
+
}
|
|
47790
|
+
},
|
|
47791
|
+
children: [jsx("span", {
|
|
47792
|
+
id: questionId,
|
|
47793
|
+
children: confirm === true ? naviI18n("confirm.message") : confirm
|
|
47794
|
+
}), jsx(Next, {
|
|
47795
|
+
...props,
|
|
47796
|
+
"aria-describedby": questionId
|
|
47797
|
+
// The button confirming is the button that was pressed: it keeps the
|
|
47798
|
+
// caller's own testid unless the question names one of its own.
|
|
47799
|
+
,
|
|
47800
|
+
"data-testid": confirmTestId || props["data-testid"],
|
|
47801
|
+
onClick: e => {
|
|
47802
|
+
onClick?.(e);
|
|
47803
|
+
if (!action) {
|
|
47804
|
+
// A command runs on the press itself: there is nothing to wait for,
|
|
47805
|
+
// the question is answered as soon as it is pressed.
|
|
47806
|
+
cancel(true);
|
|
47807
|
+
}
|
|
47808
|
+
}
|
|
47809
|
+
// A run that failed keeps the question up, with its callout on the
|
|
47810
|
+
// button that raised it: the retry is then one press away.
|
|
47811
|
+
,
|
|
47812
|
+
onActionEnd: (data, e) => {
|
|
47813
|
+
onActionEnd?.(data, e);
|
|
47814
|
+
cancel(true);
|
|
47815
|
+
},
|
|
47816
|
+
children: confirmLabel
|
|
47817
|
+
}), jsx(ButtonUI, {
|
|
47818
|
+
ref: cancelRef,
|
|
47819
|
+
type: "button",
|
|
47820
|
+
"data-testid": cancelTestId,
|
|
47821
|
+
onClick: () => {
|
|
47822
|
+
cancel(true);
|
|
47823
|
+
},
|
|
47824
|
+
children: cancelLabel
|
|
47825
|
+
})]
|
|
47826
|
+
});
|
|
47827
|
+
};
|
|
47828
|
+
|
|
47829
|
+
const ButtonRouteResolver = props => {
|
|
47830
|
+
const Next = useNextResolver();
|
|
47831
|
+
if (props.route) {
|
|
47832
|
+
return jsx(ButtonWithRoute, {
|
|
47833
|
+
...props
|
|
47834
|
+
});
|
|
47835
|
+
}
|
|
47836
|
+
return jsx(Next, {
|
|
47837
|
+
...props
|
|
47838
|
+
});
|
|
47839
|
+
};
|
|
47840
|
+
const ButtonWithRoute = props => {
|
|
47841
|
+
const Next = useNextResolver();
|
|
47842
|
+
const {
|
|
47843
|
+
route,
|
|
47844
|
+
routeParams,
|
|
47845
|
+
children,
|
|
47846
|
+
pseudoState,
|
|
47847
|
+
...rest
|
|
47848
|
+
} = props;
|
|
47849
|
+
const url = route.buildUrl(routeParams);
|
|
47850
|
+
const {
|
|
47851
|
+
matching
|
|
47852
|
+
} = useRouteStatus(route);
|
|
47853
|
+
const paramsAreMatching = route.matchesParams(routeParams);
|
|
47854
|
+
const linkMatching = matching && paramsAreMatching;
|
|
47855
|
+
|
|
47856
|
+
// Merged into whatever the caller already holds: a button can be forced into
|
|
47857
|
+
// a state for a demo and still learn its own current-ness from its route.
|
|
47858
|
+
return jsx(Next, {
|
|
47859
|
+
href: url,
|
|
47860
|
+
pseudoState: {
|
|
47861
|
+
...pseudoState,
|
|
47862
|
+
":-navi-href-current": linkMatching
|
|
47863
|
+
},
|
|
47864
|
+
...rest,
|
|
47865
|
+
children: children || route.buildRelativeUrl(routeParams)
|
|
47866
|
+
});
|
|
47867
|
+
};
|
|
47868
|
+
|
|
47656
47869
|
const ButtonFirstResolver = props => {
|
|
47657
47870
|
const Next = useNextResolver();
|
|
47658
47871
|
const defaultRef = useRef(null);
|
|
@@ -47814,6 +48027,20 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
47814
48027
|
* more: the work may already have happened on the other side (see
|
|
47815
48028
|
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
47816
48029
|
* where the screen can be re-opened on what is actually there.
|
|
48030
|
+
* @param {boolean|import("ignore:preact").ComponentChildren} [confirm] Ask before
|
|
48031
|
+
* doing it, in the place the button stands: the first press replaces the
|
|
48032
|
+
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
48033
|
+
* does what the button was for. `true` asks navi's default question. The
|
|
48034
|
+
* press stays under the finger and there is nothing to dismiss — "Annuler",
|
|
48035
|
+
* Escape, or the focus leaving puts the button back. Use `<Picker
|
|
48036
|
+
* type="confirm">` instead when the question is long enough to want a popup,
|
|
48037
|
+
* or when the row has no space for it.
|
|
48038
|
+
* @param {import("ignore:preact").ComponentChildren} [confirmLabel] Label of the
|
|
48039
|
+
* second press.
|
|
48040
|
+
* @param {import("ignore:preact").ComponentChildren} [cancelLabel] Label of the way
|
|
48041
|
+
* out of the question.
|
|
48042
|
+
* @param {string} [confirmTestId] `data-testid` of the second press.
|
|
48043
|
+
* @param {string} [cancelTestId] `data-testid` of the way out.
|
|
47817
48044
|
* @param {string} [contentDisplay] The display of the frame the button draws
|
|
47818
48045
|
* around its children. It follows the button's own by default — its display
|
|
47819
48046
|
* and, a display alone saying nothing about direction, the rest of its flow
|
|
@@ -47828,7 +48055,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
47828
48055
|
* (it wears it itself) and loses the shrink under the finger, which has
|
|
47829
48056
|
* nothing left to scale but the interactive area itself.
|
|
47830
48057
|
*/
|
|
47831
|
-
const Button = createComponentResolver([ButtonFirstResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonUI]);
|
|
48058
|
+
const Button = createComponentResolver([ButtonFirstResolver, ButtonConfirmResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonUI]);
|
|
47832
48059
|
|
|
47833
48060
|
installImportMetaCssBuild(import.meta);
|
|
47834
48061
|
const css$O = /* css */`.navi_control_swap {
|
|
@@ -55820,13 +56047,7 @@ const TimeDate = ({
|
|
|
55820
56047
|
})
|
|
55821
56048
|
});
|
|
55822
56049
|
}
|
|
55823
|
-
const date = toDate(children
|
|
55824
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
55825
|
-
const d = new Date(`${value}T00:00:00`);
|
|
55826
|
-
return isNaN(d.getTime()) ? null : d;
|
|
55827
|
-
}
|
|
55828
|
-
return null;
|
|
55829
|
-
});
|
|
56050
|
+
const date = toDate(children);
|
|
55830
56051
|
if (!date) {
|
|
55831
56052
|
return jsx(TimeText, {
|
|
55832
56053
|
...props,
|
|
@@ -72269,6 +72490,26 @@ const css$r = /* css */`.navi_checkbox_group {
|
|
|
72269
72490
|
`;
|
|
72270
72491
|
|
|
72271
72492
|
/**
|
|
72493
|
+
* A set of checkboxes answering one question, holding the array of the values
|
|
72494
|
+
* that are checked.
|
|
72495
|
+
*
|
|
72496
|
+
* It renders a `<fieldset>` and hands its `name` down, so the boxes inside are
|
|
72497
|
+
* plain `<Input type="checkbox" value="…">` with nothing else to declare; each
|
|
72498
|
+
* value that is checked appears in the array, in the order the boxes are
|
|
72499
|
+
* written. What it holds is said the way every control says it — `value` /
|
|
72500
|
+
* `defaultValue` (an array), or a bound `signal` — and inside a `<Form>` the
|
|
72501
|
+
* group is one entry of the submitted params, an array.
|
|
72502
|
+
*
|
|
72503
|
+
* ```jsx
|
|
72504
|
+
* <CheckboxGroup name="permissions" defaultValue={["places.moderate"]} action={save}>
|
|
72505
|
+
* <legend>Permissions</legend>
|
|
72506
|
+
* <Field as="label"><Input type="checkbox" value="places.moderate" /> Modérer les lieux</Field>
|
|
72507
|
+
* <Field as="label"><Input type="checkbox" value="games.list" /> Lister les parties</Field>
|
|
72508
|
+
* </CheckboxGroup>
|
|
72509
|
+
* ```
|
|
72510
|
+
*
|
|
72511
|
+
* See `<RadioGroup>` for the same shape holding a single value.
|
|
72512
|
+
*
|
|
72272
72513
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
72273
72514
|
* maxLength?: number,
|
|
72274
72515
|
* maxLengthGuard?: number,
|
|
@@ -72990,6 +73231,27 @@ const css$p = /* css */`.navi_radio_group {
|
|
|
72990
73231
|
}
|
|
72991
73232
|
}
|
|
72992
73233
|
`;
|
|
73234
|
+
|
|
73235
|
+
/**
|
|
73236
|
+
* A set of radios answering one question, holding the value of the one that is
|
|
73237
|
+
* checked.
|
|
73238
|
+
*
|
|
73239
|
+
* It renders a `<fieldset>` and hands its `name` down, so the radios inside are
|
|
73240
|
+
* plain `<Input type="radio" value="…">` with nothing else to declare — sharing
|
|
73241
|
+
* a name is what makes them exclusive. What it holds is said the way every
|
|
73242
|
+
* control says it — `value` / `defaultValue`, or a bound `signal` — and inside a
|
|
73243
|
+
* `<Form>` the group is one entry of the submitted params.
|
|
73244
|
+
*
|
|
73245
|
+
* ```jsx
|
|
73246
|
+
* <RadioGroup name="role" defaultValue="viewer" action={save}>
|
|
73247
|
+
* <legend>Rôle</legend>
|
|
73248
|
+
* <Field as="label"><Input type="radio" value="viewer" /> Lecteur</Field>
|
|
73249
|
+
* <Field as="label"><Input type="radio" value="admin" /> Administrateur</Field>
|
|
73250
|
+
* </RadioGroup>
|
|
73251
|
+
* ```
|
|
73252
|
+
*
|
|
73253
|
+
* See `<CheckboxGroup>` for the same shape holding an array.
|
|
73254
|
+
*/
|
|
72993
73255
|
const RadioGroup = props => {
|
|
72994
73256
|
const refDefault = useRef(null);
|
|
72995
73257
|
props.ref = props.ref || refDefault;
|