@jsenv/navi 0.29.96 → 0.29.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +618 -238
- package/dist/jsenv_navi.js.map +34 -27
- package/docs/control_object.md +20 -1
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -38,7 +38,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
38
38
|
* any of these, and a number is the last resort, not the first tool.
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
const css$
|
|
41
|
+
const css$15 = /* css */`
|
|
42
42
|
@layer navi {
|
|
43
43
|
:root {
|
|
44
44
|
/* A control that overlaps its neighbours (the members of a Group share
|
|
@@ -92,7 +92,7 @@ const css$14 = /* css */`
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
`;
|
|
95
|
-
import.meta.css = [css$
|
|
95
|
+
import.meta.css = [css$15, "@jsenv/navi/src/navi_z_indexes.js"];
|
|
96
96
|
|
|
97
97
|
const addIntoArray = (array, ...valuesToAdd) => {
|
|
98
98
|
if (valuesToAdd.length === 1) {
|
|
@@ -361,7 +361,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
361
361
|
* the very first render and the browser does everything on its own.
|
|
362
362
|
*/
|
|
363
363
|
const URL_TARGET_ATTRIBUTE = "data-url-target";
|
|
364
|
-
const css$
|
|
364
|
+
const css$14 = /* css */`
|
|
365
365
|
@layer navi {
|
|
366
366
|
[${URL_TARGET_ATTRIBUTE}] {
|
|
367
367
|
animation: navi_url_target var(--navi-url-target-duration, 2000ms)
|
|
@@ -379,7 +379,7 @@ const css$13 = /* css */`
|
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
`;
|
|
382
|
-
import.meta.css = [css$
|
|
382
|
+
import.meta.css = [css$14, "@jsenv/navi/src/nav/url_target/url_target.js"];
|
|
383
383
|
let urlTargetOptions = {
|
|
384
384
|
block: "start",
|
|
385
385
|
behavior: "instant",
|
|
@@ -6403,7 +6403,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
6403
6403
|
* - Arrow automatically shows when pointing at a valid anchor element
|
|
6404
6404
|
* - Centers in viewport when no anchor element provided or anchor is too big
|
|
6405
6405
|
*/
|
|
6406
|
-
const css$
|
|
6406
|
+
const css$13 = /* css */`
|
|
6407
6407
|
@layer navi {
|
|
6408
6408
|
.navi_callout {
|
|
6409
6409
|
/* A callout is parented to what it explains, so it inherits from it — and
|
|
@@ -6646,7 +6646,7 @@ const openCallout = (message, {
|
|
|
6646
6646
|
skipFocus = false,
|
|
6647
6647
|
debug = () => {}
|
|
6648
6648
|
} = {}) => {
|
|
6649
|
-
import.meta.css = [css$
|
|
6649
|
+
import.meta.css = [css$13, "@jsenv/navi/src/control/rules/callout/callout.js"];
|
|
6650
6650
|
if (debug === true) {
|
|
6651
6651
|
debug = (e, ...args) => console.debug(`"${e.type}" -> `, ...args);
|
|
6652
6652
|
}
|
|
@@ -8012,12 +8012,20 @@ const isControlBusy = (field) => {
|
|
|
8012
8012
|
// An optimistic control stays interactive while its bound action runs:
|
|
8013
8013
|
// a new interaction is queued behind the run (see the action queue in
|
|
8014
8014
|
// control_hooks.jsx) rather than refused.
|
|
8015
|
-
if (
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8015
|
+
if (!field.optimistic && boundAction) {
|
|
8016
|
+
// The INSTANCE the proxy resolves to right now, not the proxy's own
|
|
8017
|
+
// signal: that one is a MIRROR, synced by an effect the settling batch
|
|
8018
|
+
// defers — read mid-batch (a state echo carrying the user's event back
|
|
8019
|
+
// down, an automatic follow-up), it still says RUNNING for an action
|
|
8020
|
+
// that is already over, and the gate would refuse — callout included —
|
|
8021
|
+
// for nothing. The resolved instance is the live truth: at that echo it
|
|
8022
|
+
// is the instance that just settled, already COMPLETED. And it IS the
|
|
8023
|
+
// running one whenever one runs — a non-optimistic control's state
|
|
8024
|
+
// cannot move mid-run, this very gate blocks it.
|
|
8025
|
+
const liveAction = boundAction.getCurrentAction?.() ?? boundAction;
|
|
8026
|
+
if (liveAction.runningStateSignal.value === RUNNING) {
|
|
8027
|
+
return true;
|
|
8028
|
+
}
|
|
8021
8029
|
}
|
|
8022
8030
|
if (field.loadingFromParent) {
|
|
8023
8031
|
const parent = field.parentUIStateController;
|
|
@@ -8940,10 +8948,12 @@ const REQUIRED_CONSTRAINT = {
|
|
|
8940
8948
|
};
|
|
8941
8949
|
}
|
|
8942
8950
|
|
|
8943
|
-
// checkbox_group controller: check aggregate uiState array
|
|
8951
|
+
// checkbox_group controller: check aggregate uiState array. An empty
|
|
8952
|
+
// selection aggregates to undefined, not to an empty array (see
|
|
8953
|
+
// GROUP_DEFAULTS.checkbox_group) — both mean "nothing checked" here.
|
|
8944
8954
|
if (controlType === "checkbox_group") {
|
|
8945
8955
|
const uiState = field.uiState;
|
|
8946
|
-
if (uiState.length > 0) {
|
|
8956
|
+
if (uiState !== undefined && uiState.length > 0) {
|
|
8947
8957
|
return null;
|
|
8948
8958
|
}
|
|
8949
8959
|
return {
|
|
@@ -18873,7 +18883,7 @@ const setupNetworkMonitoring = () => {
|
|
|
18873
18883
|
};
|
|
18874
18884
|
setupNetworkMonitoring();
|
|
18875
18885
|
|
|
18876
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
18886
|
+
installImportMetaCssBuild(import.meta);const css$12 = /* css */`
|
|
18877
18887
|
.navi_loading_indicator_fluid_container {
|
|
18878
18888
|
position: relative;
|
|
18879
18889
|
display: flex;
|
|
@@ -18905,7 +18915,7 @@ const LoadingIndicatorFluid = ({
|
|
|
18905
18915
|
visuallyHidden,
|
|
18906
18916
|
...rest
|
|
18907
18917
|
}) => {
|
|
18908
|
-
import.meta.css = [css$
|
|
18918
|
+
import.meta.css = [css$12, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
|
|
18909
18919
|
const ref = useRef(null);
|
|
18910
18920
|
// The container dimensions can be deduced from the ref itself as the indicator is absolute inset 0
|
|
18911
18921
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
@@ -19110,7 +19120,7 @@ const LoadingRectangleSvg = ({
|
|
|
19110
19120
|
});
|
|
19111
19121
|
};
|
|
19112
19122
|
|
|
19113
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
19123
|
+
installImportMetaCssBuild(import.meta);const css$11 = /* css */`
|
|
19114
19124
|
.navi_loading_outline_wrapper {
|
|
19115
19125
|
position: absolute;
|
|
19116
19126
|
/* Controls place the outline slightly outside their box, right on top of
|
|
@@ -19147,7 +19157,7 @@ installImportMetaCssBuild(import.meta);const css$10 = /* css */`
|
|
|
19147
19157
|
}
|
|
19148
19158
|
`;
|
|
19149
19159
|
const LoadingOutline = props => {
|
|
19150
|
-
import.meta.css = [css$
|
|
19160
|
+
import.meta.css = [css$11, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
|
|
19151
19161
|
if (props.containerRef) {
|
|
19152
19162
|
const container = props.containerRef.current;
|
|
19153
19163
|
if (!container) {
|
|
@@ -19437,7 +19447,7 @@ const selectByTextStrings = (element, range, startText, endText) => {
|
|
|
19437
19447
|
};
|
|
19438
19448
|
|
|
19439
19449
|
installImportMetaCssBuild(import.meta);// https://jsfiddle.net/v5xzJ/4/
|
|
19440
|
-
const css
|
|
19450
|
+
const css$10 = /* css */`
|
|
19441
19451
|
@layer navi {
|
|
19442
19452
|
.navi_text {
|
|
19443
19453
|
&[data-skeleton] {
|
|
@@ -19958,7 +19968,7 @@ const TextShrinkWrap = props => {
|
|
|
19958
19968
|
});
|
|
19959
19969
|
};
|
|
19960
19970
|
const TextUI = props => {
|
|
19961
|
-
import.meta.css = [css
|
|
19971
|
+
import.meta.css = [css$10, "@jsenv/navi/src/text/text.jsx"];
|
|
19962
19972
|
let {
|
|
19963
19973
|
ref,
|
|
19964
19974
|
spacing,
|
|
@@ -24717,6 +24727,16 @@ const useUIGroupStateController = (
|
|
|
24717
24727
|
// between them, from here on.
|
|
24718
24728
|
controller.stateGivenFromAbove = false;
|
|
24719
24729
|
if (resolvedDistributeChildStates) {
|
|
24730
|
+
if (compareTwoJsValues(groupUIState, controller.uiState)) {
|
|
24731
|
+
// The aggregate answered what the group already holds, which is
|
|
24732
|
+
// how such a group says "not yet, ask me again". Some gestures
|
|
24733
|
+
// cannot avoid an in-between — two people swapping seats means one
|
|
24734
|
+
// leaves before the other arrives — and the aggregate is where
|
|
24735
|
+
// that half-state is recognised. Placing the children from it
|
|
24736
|
+
// would undo the half of the gesture that has already landed, and
|
|
24737
|
+
// publishing it would hand a half-answer to the row above.
|
|
24738
|
+
return;
|
|
24739
|
+
}
|
|
24720
24740
|
// A group answering for all its children at once holds ONE answer
|
|
24721
24741
|
// its children are views OF, rather than a value that IS what they
|
|
24722
24742
|
// said: a list saying who plays and four seats saying who sits
|
|
@@ -27323,18 +27343,31 @@ const useInteractiveProps = (props, {
|
|
|
27323
27343
|
controlRootProps.onnavi_action_end?.(e);
|
|
27324
27344
|
uiStateController.onActionEnd(e);
|
|
27325
27345
|
|
|
27326
|
-
//
|
|
27327
|
-
//
|
|
27328
|
-
//
|
|
27346
|
+
// Auto-trigger the parent group's action after the leaf action
|
|
27347
|
+
// completes, for the groups that ARE one control made of parts: a
|
|
27348
|
+
// radio or checkbox group, a wheel group (an hour wheel settling is
|
|
27349
|
+
// the time settling). The parent has already aggregated the new
|
|
27350
|
+
// state by now, so uiStateSignal is correct. One level only: the
|
|
27351
|
+
// parent's own action end does not climb further unless that parent
|
|
27352
|
+
// is itself such a group.
|
|
27329
27353
|
const parentController = uiStateController.parentUIStateController;
|
|
27330
|
-
if (parentController && (parentController.controlType === "radio_group" || parentController.controlType === "checkbox_group")) {
|
|
27354
|
+
if (parentController && (parentController.controlType === "radio_group" || parentController.controlType === "checkbox_group" || parentController.controlType === "wheel_group")) {
|
|
27331
27355
|
const parentEl = parentController.ref.current;
|
|
27332
27356
|
if (parentEl) {
|
|
27333
|
-
const originalEvent = e.detail.eventChain[0];
|
|
27334
27357
|
dispatchRequestAction(parentEl, {
|
|
27335
|
-
event:
|
|
27358
|
+
event: e.detail.eventChain[0],
|
|
27336
27359
|
name: "auto_group_action",
|
|
27337
|
-
requester: e.detail.requester
|
|
27360
|
+
requester: e.detail.requester,
|
|
27361
|
+
// The interactivity gate is not re-asked: the user already
|
|
27362
|
+
// interacted — with the child, whose gate said yes — and this
|
|
27363
|
+
// follow-up is automatic. Asking again would also answer
|
|
27364
|
+
// wrong: this event is dispatched inside the batch() that
|
|
27365
|
+
// settles the child's action, where a bound action still
|
|
27366
|
+
// READS as running (its state is mirrored through a signal
|
|
27367
|
+
// effect the batch defers, see watchActionCompletion) — the
|
|
27368
|
+
// busy constraint would refuse the group for an action that
|
|
27369
|
+
// is already over. The validity gate still applies.
|
|
27370
|
+
bypassInteractivity: true
|
|
27338
27371
|
});
|
|
27339
27372
|
}
|
|
27340
27373
|
}
|
|
@@ -27396,7 +27429,7 @@ const getAssociatedLabels = element => {
|
|
|
27396
27429
|
return Array.from(element.labels);
|
|
27397
27430
|
};
|
|
27398
27431
|
|
|
27399
|
-
installImportMetaCssBuild(import.meta);const css
|
|
27432
|
+
installImportMetaCssBuild(import.meta);const css$$ = /* css */`
|
|
27400
27433
|
@layer navi {
|
|
27401
27434
|
.navi_button {
|
|
27402
27435
|
--button-border-radius: var(--navi-control-border-radius);
|
|
@@ -27827,7 +27860,7 @@ installImportMetaCssBuild(import.meta);const css$_ = /* css */`
|
|
|
27827
27860
|
}
|
|
27828
27861
|
`;
|
|
27829
27862
|
const ButtonUI = props => {
|
|
27830
|
-
import.meta.css = [css
|
|
27863
|
+
import.meta.css = [css$$, "@jsenv/navi/src/control/input/button_ui.jsx"];
|
|
27831
27864
|
const {
|
|
27832
27865
|
ref,
|
|
27833
27866
|
// href/link
|
|
@@ -29538,7 +29571,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
29538
29571
|
* reaches the real container.
|
|
29539
29572
|
*/
|
|
29540
29573
|
let openLocalDialogCount = 0;
|
|
29541
|
-
const css$
|
|
29574
|
+
const css$_ = /* css */`
|
|
29542
29575
|
@layer navi {
|
|
29543
29576
|
.navi_dialog {
|
|
29544
29577
|
/* Min gap between the dialog and the edges of its container. Written
|
|
@@ -30122,7 +30155,7 @@ const css$Z = /* css */`
|
|
|
30122
30155
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
30123
30156
|
*/
|
|
30124
30157
|
const Dialog = props => {
|
|
30125
|
-
import.meta.css = [css$
|
|
30158
|
+
import.meta.css = [css$_, "@jsenv/navi/src/layout/dialog.jsx"];
|
|
30126
30159
|
if (props.openController) {
|
|
30127
30160
|
return jsx(ControlledDialog, {
|
|
30128
30161
|
...props
|
|
@@ -31114,7 +31147,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
31114
31147
|
* and applied.
|
|
31115
31148
|
*/
|
|
31116
31149
|
let openLocalPopoverCount = 0;
|
|
31117
|
-
const css$
|
|
31150
|
+
const css$Z = /* css */`
|
|
31118
31151
|
@layer navi {
|
|
31119
31152
|
.navi_popover {
|
|
31120
31153
|
/* soft: user-configurable preferred max-height. Kept as a *default*
|
|
@@ -31570,7 +31603,7 @@ const css$Y = /* css */`
|
|
|
31570
31603
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
31571
31604
|
*/
|
|
31572
31605
|
const Popover = props => {
|
|
31573
|
-
import.meta.css = [css$
|
|
31606
|
+
import.meta.css = [css$Z, "@jsenv/navi/src/layout/popover.jsx"];
|
|
31574
31607
|
if (props.openController) {
|
|
31575
31608
|
return jsx(ControlledPopover, {
|
|
31576
31609
|
...props
|
|
@@ -32598,7 +32631,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
32598
32631
|
* event, and a caller replacing the body entirely then has one protocol to
|
|
32599
32632
|
* follow — `--navi-confirm` for yes, anything that closes for no.
|
|
32600
32633
|
*/
|
|
32601
|
-
const css$
|
|
32634
|
+
const css$Y = /* css */`
|
|
32602
32635
|
/* The width lives on the body rather than on the popup, so that custom
|
|
32603
32636
|
content (which replaces this body entirely) sizes itself instead of
|
|
32604
32637
|
inheriting a ceiling meant for a sentence-long question. */
|
|
@@ -32735,7 +32768,7 @@ const ConfirmPopup = ({
|
|
|
32735
32768
|
onAnswer,
|
|
32736
32769
|
onClosed
|
|
32737
32770
|
}) => {
|
|
32738
|
-
import.meta.css = [css$
|
|
32771
|
+
import.meta.css = [css$Y, "@jsenv/navi/src/action/confirm_popup.jsx"];
|
|
32739
32772
|
const {
|
|
32740
32773
|
mode,
|
|
32741
32774
|
confirmLabel,
|
|
@@ -32819,7 +32852,7 @@ const defaultBody = (message, {
|
|
|
32819
32852
|
});
|
|
32820
32853
|
};
|
|
32821
32854
|
|
|
32822
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
32855
|
+
installImportMetaCssBuild(import.meta);const css$X = /* css */`
|
|
32823
32856
|
.action_error {
|
|
32824
32857
|
margin-top: 0;
|
|
32825
32858
|
margin-bottom: 20px;
|
|
@@ -32844,7 +32877,7 @@ const ActionRenderer = ({
|
|
|
32844
32877
|
children,
|
|
32845
32878
|
disabled
|
|
32846
32879
|
}) => {
|
|
32847
|
-
import.meta.css = [css$
|
|
32880
|
+
import.meta.css = [css$X, "@jsenv/navi/src/action/action_renderer.jsx"];
|
|
32848
32881
|
if (action === undefined) {
|
|
32849
32882
|
throw new Error("ActionRenderer requires an action to render, but none was provided.");
|
|
32850
32883
|
}
|
|
@@ -38588,7 +38621,7 @@ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
|
|
|
38588
38621
|
// the root pictures must NOT move (they carry the whole viewport, blank bands
|
|
38589
38622
|
// included).
|
|
38590
38623
|
|
|
38591
|
-
const css$
|
|
38624
|
+
const css$W = /* css */`
|
|
38592
38625
|
/* The marked region is a picture of its own for the length of a transition of
|
|
38593
38626
|
OURS, and only then — the name is what makes the pages a picture the
|
|
38594
38627
|
movement below can carry.
|
|
@@ -39017,7 +39050,7 @@ const RouteTransitionArea = ({
|
|
|
39017
39050
|
children,
|
|
39018
39051
|
...rest
|
|
39019
39052
|
}) => {
|
|
39020
|
-
import.meta.css = [css$
|
|
39053
|
+
import.meta.css = [css$W, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
39021
39054
|
const props = {
|
|
39022
39055
|
...rest,
|
|
39023
39056
|
[TRANSITION_AREA_ATTRIBUTE]: ""
|
|
@@ -39074,7 +39107,7 @@ const RouteTransitionArea = ({
|
|
|
39074
39107
|
* @returns {() => void} remove this relation.
|
|
39075
39108
|
*/
|
|
39076
39109
|
const defineRouteTransition = (from, to, transition) => {
|
|
39077
|
-
import.meta.css = [css$
|
|
39110
|
+
import.meta.css = [css$W, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
39078
39111
|
const {
|
|
39079
39112
|
type,
|
|
39080
39113
|
duration
|
|
@@ -39110,7 +39143,7 @@ const defineRouteTransition = (from, to, transition) => {
|
|
|
39110
39143
|
* @returns {() => void} remove this default.
|
|
39111
39144
|
*/
|
|
39112
39145
|
const defineRouteDefaultTransition = transition => {
|
|
39113
|
-
import.meta.css = [css$
|
|
39146
|
+
import.meta.css = [css$W, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
39114
39147
|
const value = normalizeTransition(transition);
|
|
39115
39148
|
defaultTransition = value;
|
|
39116
39149
|
return () => {
|
|
@@ -39711,7 +39744,7 @@ const DRAGGED_ATTRIBUTE = "data-navi-route-travel-dragged";
|
|
|
39711
39744
|
const TURNED_ATTRIBUTE = "data-navi-route-travel-turned";
|
|
39712
39745
|
// The name the box wears while it travels, and only then (see nameForTravel).
|
|
39713
39746
|
const TRAVEL_NAME = "navi-route-travel";
|
|
39714
|
-
const css$
|
|
39747
|
+
const css$V = /* css */`
|
|
39715
39748
|
/* The name that makes the page inside this box a picture of its own during a
|
|
39716
39749
|
transition — rather than part of the one big picture the document takes, so
|
|
39717
39750
|
the two pages can move past each other while everything else stays where it
|
|
@@ -40101,7 +40134,7 @@ const RouteTravel = ({
|
|
|
40101
40134
|
children,
|
|
40102
40135
|
...rest
|
|
40103
40136
|
}) => {
|
|
40104
|
-
import.meta.css = [css$
|
|
40137
|
+
import.meta.css = [css$V, "@jsenv/navi/src/nav/route_travel.jsx"];
|
|
40105
40138
|
const elementRef = useRef();
|
|
40106
40139
|
const gestureRef = useRef(null);
|
|
40107
40140
|
// The travel in hand: the transition keeping the picture of the page being
|
|
@@ -42663,7 +42696,7 @@ const PhoneSvg = () => {
|
|
|
42663
42696
|
};
|
|
42664
42697
|
|
|
42665
42698
|
installImportMetaCssBuild(import.meta);// # TextAnchor — how it works
|
|
42666
|
-
const css$
|
|
42699
|
+
const css$U = /* css */`
|
|
42667
42700
|
.navi_text_anchor {
|
|
42668
42701
|
vertical-align: baseline;
|
|
42669
42702
|
user-select: none;
|
|
@@ -42698,7 +42731,7 @@ const TextAnchor = ({
|
|
|
42698
42731
|
textSize,
|
|
42699
42732
|
lineLayout
|
|
42700
42733
|
}) => {
|
|
42701
|
-
import.meta.css = [css$
|
|
42734
|
+
import.meta.css = [css$U, "@jsenv/navi/src/text/text_anchor.jsx"];
|
|
42702
42735
|
const anchorRef = useRef();
|
|
42703
42736
|
|
|
42704
42737
|
// Plain useLayoutEffect would also fire while an ancestor dialog/popover
|
|
@@ -42813,7 +42846,7 @@ const computeTopOffset = ({
|
|
|
42813
42846
|
};
|
|
42814
42847
|
const charTopCanvas = document.createElement("canvas");
|
|
42815
42848
|
|
|
42816
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
42849
|
+
installImportMetaCssBuild(import.meta);const css$T = /* css */`
|
|
42817
42850
|
@layer navi {
|
|
42818
42851
|
/* Ensure data attributes from box.jsx can win to update display */
|
|
42819
42852
|
.navi_icon {
|
|
@@ -42971,7 +43004,7 @@ const Icon = ({
|
|
|
42971
43004
|
fillLine,
|
|
42972
43005
|
...props
|
|
42973
43006
|
}) => {
|
|
42974
|
-
import.meta.css = [css$
|
|
43007
|
+
import.meta.css = [css$T, "@jsenv/navi/src/text/icon.jsx"];
|
|
42975
43008
|
const innerChildren = href ? jsx("svg", {
|
|
42976
43009
|
width: "100%",
|
|
42977
43010
|
height: "100%",
|
|
@@ -43133,7 +43166,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
43133
43166
|
});
|
|
43134
43167
|
};
|
|
43135
43168
|
|
|
43136
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
43169
|
+
installImportMetaCssBuild(import.meta);const css$S = /* css */`
|
|
43137
43170
|
@layer navi {
|
|
43138
43171
|
.navi_link {
|
|
43139
43172
|
--link-border-radius: unset;
|
|
@@ -43595,7 +43628,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
43595
43628
|
* @param {boolean} [props.readOnly]
|
|
43596
43629
|
*/
|
|
43597
43630
|
const Link = props => {
|
|
43598
|
-
import.meta.css = [css$
|
|
43631
|
+
import.meta.css = [css$S, "@jsenv/navi/src/nav/link/link.jsx"];
|
|
43599
43632
|
if (props.route) {
|
|
43600
43633
|
return jsx(LinkWithRoute, {
|
|
43601
43634
|
...props
|
|
@@ -43918,7 +43951,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
43918
43951
|
* https://dribbble.com/search/tabs
|
|
43919
43952
|
*/
|
|
43920
43953
|
let navCount = 0;
|
|
43921
|
-
const css$
|
|
43954
|
+
const css$R = /* css */`
|
|
43922
43955
|
@layer navi {
|
|
43923
43956
|
.navi_nav {
|
|
43924
43957
|
--nav-border: none;
|
|
@@ -44201,7 +44234,7 @@ const Nav = ({
|
|
|
44201
44234
|
slideContainer,
|
|
44202
44235
|
...props
|
|
44203
44236
|
}) => {
|
|
44204
|
-
import.meta.css = [css$
|
|
44237
|
+
import.meta.css = [css$R, "@jsenv/navi/src/nav/link/nav.jsx"];
|
|
44205
44238
|
const defaultRef = useRef();
|
|
44206
44239
|
props.ref = props.ref || defaultRef;
|
|
44207
44240
|
const navRef = props.ref;
|
|
@@ -44728,7 +44761,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
44728
44761
|
* Border width participates in layout (it is added to the tab and page
|
|
44729
44762
|
* padding): a thick border grows the binder rather than eating into the text.
|
|
44730
44763
|
*/
|
|
44731
|
-
const css$
|
|
44764
|
+
const css$Q = /* css */`
|
|
44732
44765
|
@layer navi {
|
|
44733
44766
|
.navi_binder {
|
|
44734
44767
|
--binder-border-width: var(--navi-control-border-width);
|
|
@@ -45041,7 +45074,7 @@ const Binder = ({
|
|
|
45041
45074
|
pagePadding,
|
|
45042
45075
|
...props
|
|
45043
45076
|
}) => {
|
|
45044
|
-
import.meta.css = [css$
|
|
45077
|
+
import.meta.css = [css$Q, "@jsenv/navi/src/nav/binder/binder.jsx"];
|
|
45045
45078
|
const items = toChildArray(children).map((child, index) => {
|
|
45046
45079
|
const {
|
|
45047
45080
|
value: itemValue,
|
|
@@ -45528,7 +45561,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
45528
45561
|
* added to the size asked for exactly like the notch inset is, so the
|
|
45529
45562
|
* content still gets the size the prop names.
|
|
45530
45563
|
*/
|
|
45531
|
-
const css$
|
|
45564
|
+
const css$P = /* css */`
|
|
45532
45565
|
@layer navi {
|
|
45533
45566
|
:root {
|
|
45534
45567
|
--navi-fixed-bar-width: 56px;
|
|
@@ -45676,7 +45709,7 @@ const FixedBar = ({
|
|
|
45676
45709
|
border = true,
|
|
45677
45710
|
...props
|
|
45678
45711
|
}) => {
|
|
45679
|
-
import.meta.css = [css$
|
|
45712
|
+
import.meta.css = [css$P, "@jsenv/navi/src/layout/fixed_bar/fixed_bar.jsx"];
|
|
45680
45713
|
const defaultRef = useRef();
|
|
45681
45714
|
props.ref = props.ref || defaultRef;
|
|
45682
45715
|
// Said with the width the border rule reads rather than with an attribute of
|
|
@@ -45770,7 +45803,7 @@ const FixedBar = ({
|
|
|
45770
45803
|
// Subpixel layout rounds rectangles up on boxes that fit exactly.
|
|
45771
45804
|
const OVERFLOW_TOLERANCE = 1;
|
|
45772
45805
|
|
|
45773
|
-
const css$
|
|
45806
|
+
const css$O = /* css */ `
|
|
45774
45807
|
[data-navi-overflow-x] {
|
|
45775
45808
|
outline: 2px dashed #e74c3c;
|
|
45776
45809
|
outline-offset: -2px;
|
|
@@ -45794,7 +45827,7 @@ const detectHorizontalOverflow = ({
|
|
|
45794
45827
|
let styleEl = null;
|
|
45795
45828
|
if (highlight) {
|
|
45796
45829
|
styleEl = document.createElement("style");
|
|
45797
|
-
styleEl.textContent = css$
|
|
45830
|
+
styleEl.textContent = css$O;
|
|
45798
45831
|
document.head.appendChild(styleEl);
|
|
45799
45832
|
}
|
|
45800
45833
|
|
|
@@ -45949,7 +45982,7 @@ const useFocusGroup = (
|
|
|
45949
45982
|
};
|
|
45950
45983
|
|
|
45951
45984
|
installImportMetaCssBuild(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
|
|
45952
|
-
const css$
|
|
45985
|
+
const css$N = /* css */`
|
|
45953
45986
|
.navi_summary_marker {
|
|
45954
45987
|
width: 1em;
|
|
45955
45988
|
height: 1em;
|
|
@@ -46039,7 +46072,7 @@ const SummaryMarker = ({
|
|
|
46039
46072
|
loading,
|
|
46040
46073
|
openDirection = "down"
|
|
46041
46074
|
}) => {
|
|
46042
|
-
import.meta.css = [css$
|
|
46075
|
+
import.meta.css = [css$N, "@jsenv/navi/src/control/details/summary_marker.jsx"];
|
|
46043
46076
|
const showLoading = useDebounceTrue(loading, 300);
|
|
46044
46077
|
return jsx("span", {
|
|
46045
46078
|
className: "navi_summary_marker",
|
|
@@ -46084,7 +46117,7 @@ const SummaryMarker = ({
|
|
|
46084
46117
|
});
|
|
46085
46118
|
};
|
|
46086
46119
|
|
|
46087
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
46120
|
+
installImportMetaCssBuild(import.meta);const css$M = /* css */`
|
|
46088
46121
|
.navi_details {
|
|
46089
46122
|
position: relative;
|
|
46090
46123
|
z-index: 1;
|
|
@@ -46130,7 +46163,7 @@ const Details = props => {
|
|
|
46130
46163
|
return details;
|
|
46131
46164
|
};
|
|
46132
46165
|
const DetailsField = props => {
|
|
46133
|
-
import.meta.css = [css$
|
|
46166
|
+
import.meta.css = [css$M, "@jsenv/navi/src/control/details/details.jsx"];
|
|
46134
46167
|
const {
|
|
46135
46168
|
ref,
|
|
46136
46169
|
persists,
|
|
@@ -46350,7 +46383,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
46350
46383
|
* UI when it comes first). Once settled open the clipping is released, so a
|
|
46351
46384
|
* popover or focus ring inside is not cut at the edges.
|
|
46352
46385
|
*/
|
|
46353
|
-
const css$
|
|
46386
|
+
const css$L = /* css */`
|
|
46354
46387
|
.navi_expandable {
|
|
46355
46388
|
position: relative;
|
|
46356
46389
|
display: flex;
|
|
@@ -46576,7 +46609,7 @@ const useExpandableContext = partName => {
|
|
|
46576
46609
|
* and rebuilds it from scratch on every expansion.
|
|
46577
46610
|
*/
|
|
46578
46611
|
const Expandable = props => {
|
|
46579
|
-
import.meta.css = [css$
|
|
46612
|
+
import.meta.css = [css$L, "@jsenv/navi/src/control/expandable/expandable.jsx"];
|
|
46580
46613
|
const {
|
|
46581
46614
|
ref,
|
|
46582
46615
|
ui,
|
|
@@ -47241,7 +47274,7 @@ const ControlGroup = props => {
|
|
|
47241
47274
|
};
|
|
47242
47275
|
const CONTROL_GROUP_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
|
|
47243
47276
|
|
|
47244
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
47277
|
+
installImportMetaCssBuild(import.meta);const css$K = /* css */`
|
|
47245
47278
|
@layer navi {
|
|
47246
47279
|
.navi_checkbox {
|
|
47247
47280
|
--switch-margin: 0; /* Useful to reserve space for outline */
|
|
@@ -47319,7 +47352,7 @@ installImportMetaCssBuild(import.meta);const css$J = /* css */`
|
|
|
47319
47352
|
}
|
|
47320
47353
|
`;
|
|
47321
47354
|
const SwitchUI = () => {
|
|
47322
|
-
import.meta.css = [css$
|
|
47355
|
+
import.meta.css = [css$K, "@jsenv/navi/src/control/input/switch_ui.jsx"];
|
|
47323
47356
|
return jsx(Box, {
|
|
47324
47357
|
className: "navi_switch",
|
|
47325
47358
|
as: "svg",
|
|
@@ -47361,7 +47394,7 @@ const useCheckableProps = (props, options) => {
|
|
|
47361
47394
|
return result;
|
|
47362
47395
|
};
|
|
47363
47396
|
|
|
47364
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
47397
|
+
installImportMetaCssBuild(import.meta);const css$J = /* css */`
|
|
47365
47398
|
@layer navi {
|
|
47366
47399
|
.navi_checkbox {
|
|
47367
47400
|
--border-radius: var(--navi-checkbox-border-radius);
|
|
@@ -47688,7 +47721,7 @@ const InputCheckboxHeadless = props => {
|
|
|
47688
47721
|
});
|
|
47689
47722
|
};
|
|
47690
47723
|
const InputCheckboxFieldInterface = props => {
|
|
47691
|
-
import.meta.css = [css$
|
|
47724
|
+
import.meta.css = [css$J, "@jsenv/navi/src/control/input/input_checkbox.jsx"];
|
|
47692
47725
|
const [checkboxRootProps, checkboxHostProps] = useCheckableProps(props);
|
|
47693
47726
|
const {
|
|
47694
47727
|
icon,
|
|
@@ -47810,7 +47843,7 @@ const CheckboxButtonStyleCSSVars = {
|
|
|
47810
47843
|
const CheckboxPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
|
|
47811
47844
|
const CheckboxPseudoElements = ["::-navi-loader", "::-navi-checkmark"];
|
|
47812
47845
|
|
|
47813
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
47846
|
+
installImportMetaCssBuild(import.meta);const css$I = /* css */`
|
|
47814
47847
|
@layer navi {
|
|
47815
47848
|
.navi_label {
|
|
47816
47849
|
--label-required-indicator-color: var(--navi-color-danger, #b42318);
|
|
@@ -47890,7 +47923,7 @@ installImportMetaCssBuild(import.meta);const css$H = /* css */`
|
|
|
47890
47923
|
* </Field>
|
|
47891
47924
|
*/
|
|
47892
47925
|
const Field = props => {
|
|
47893
|
-
import.meta.css = [css$
|
|
47926
|
+
import.meta.css = [css$I, "@jsenv/navi/src/control/field.jsx"];
|
|
47894
47927
|
const refDefault = useRef();
|
|
47895
47928
|
props.ref = props.ref || refDefault;
|
|
47896
47929
|
const {
|
|
@@ -47925,7 +47958,7 @@ const FieldCSSVars = {
|
|
|
47925
47958
|
spacingWithControl: "--spacing-with-control"
|
|
47926
47959
|
};
|
|
47927
47960
|
const FieldAsContainer = props => {
|
|
47928
|
-
import.meta.css = [css$
|
|
47961
|
+
import.meta.css = [css$I, "@jsenv/navi/src/control/field.jsx"];
|
|
47929
47962
|
const {
|
|
47930
47963
|
children
|
|
47931
47964
|
} = props;
|
|
@@ -47957,7 +47990,7 @@ const FieldAsContainer = props => {
|
|
|
47957
47990
|
};
|
|
47958
47991
|
const FIELD_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
|
|
47959
47992
|
const Label = props => {
|
|
47960
|
-
import.meta.css = [css$
|
|
47993
|
+
import.meta.css = [css$I, "@jsenv/navi/src/control/field.jsx"];
|
|
47961
47994
|
const {
|
|
47962
47995
|
children,
|
|
47963
47996
|
// Marks the label when its control is required. Takes what to show, or
|
|
@@ -48119,7 +48152,7 @@ const InputSlot = ({
|
|
|
48119
48152
|
});
|
|
48120
48153
|
};
|
|
48121
48154
|
|
|
48122
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
48155
|
+
installImportMetaCssBuild(import.meta);const css$H = /* css */`
|
|
48123
48156
|
@layer navi {
|
|
48124
48157
|
.navi_radio {
|
|
48125
48158
|
--margin: 3px 3px 3px 5px;
|
|
@@ -48482,7 +48515,7 @@ const InputRadioHeadless = props => {
|
|
|
48482
48515
|
};
|
|
48483
48516
|
const VARIANT_SET = new Set(["icon", "button", "radio"]);
|
|
48484
48517
|
const InputRadioFieldInterface = props => {
|
|
48485
|
-
import.meta.css = [css$
|
|
48518
|
+
import.meta.css = [css$H, "@jsenv/navi/src/control/input/input_radio.jsx"];
|
|
48486
48519
|
const [radioRootProps, radioHostProps] = useCheckableProps(props);
|
|
48487
48520
|
const {
|
|
48488
48521
|
icon,
|
|
@@ -48628,7 +48661,7 @@ const RadioButtonStyleCSSVars = {
|
|
|
48628
48661
|
const RadioPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
|
|
48629
48662
|
const RadioPseudoElements = ["::-navi-loader", "::-navi-radiomark"];
|
|
48630
48663
|
|
|
48631
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
48664
|
+
installImportMetaCssBuild(import.meta);const css$G = /* css */`
|
|
48632
48665
|
@layer navi {
|
|
48633
48666
|
.navi_input_range {
|
|
48634
48667
|
--border-radius: 6px;
|
|
@@ -48883,7 +48916,7 @@ const InputRange = props => {
|
|
|
48883
48916
|
});
|
|
48884
48917
|
};
|
|
48885
48918
|
const InputRangeFieldInterface = props => {
|
|
48886
|
-
import.meta.css = [css$
|
|
48919
|
+
import.meta.css = [css$G, "@jsenv/navi/src/control/input/input_range.jsx"];
|
|
48887
48920
|
const {
|
|
48888
48921
|
ref
|
|
48889
48922
|
} = props;
|
|
@@ -50991,7 +51024,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
50991
51024
|
* This means an editable thing MUST have a parent with position relative that wraps the content and the eventual editable input
|
|
50992
51025
|
*
|
|
50993
51026
|
*/
|
|
50994
|
-
const css$
|
|
51027
|
+
const css$F = /* css */`
|
|
50995
51028
|
.navi_editable_wrapper {
|
|
50996
51029
|
--inset-top: 0px;
|
|
50997
51030
|
--inset-right: 0px;
|
|
@@ -51040,7 +51073,7 @@ const useEditionController = () => {
|
|
|
51040
51073
|
};
|
|
51041
51074
|
};
|
|
51042
51075
|
const Editable = props => {
|
|
51043
|
-
import.meta.css = [css$
|
|
51076
|
+
import.meta.css = [css$F, "@jsenv/navi/src/control/edition/editable.jsx"];
|
|
51044
51077
|
let {
|
|
51045
51078
|
children,
|
|
51046
51079
|
action,
|
|
@@ -51589,7 +51622,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
51589
51622
|
* meet are drawn once instead of twice, and only the outer corners stay
|
|
51590
51623
|
* rounded. See docs/control_group.md.
|
|
51591
51624
|
*/
|
|
51592
|
-
const css$
|
|
51625
|
+
const css$E = /* css */`
|
|
51593
51626
|
.navi_group {
|
|
51594
51627
|
--group-border-width: var(--navi-control-border-width);
|
|
51595
51628
|
|
|
@@ -51705,7 +51738,7 @@ const Group = ({
|
|
|
51705
51738
|
vertical = row,
|
|
51706
51739
|
...props
|
|
51707
51740
|
}) => {
|
|
51708
|
-
import.meta.css = [css$
|
|
51741
|
+
import.meta.css = [css$E, "@jsenv/navi/src/control/group.jsx"];
|
|
51709
51742
|
return jsx(Box, {
|
|
51710
51743
|
baseClassName: "navi_group",
|
|
51711
51744
|
"data-vertical": vertical ? "" : undefined
|
|
@@ -51871,7 +51904,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
51871
51904
|
* So: nothing scrollable between the cap and the slides (a shared [data-body]
|
|
51872
51905
|
* around them IS a scroller, see box.jsx), and `overflow="auto"` on each Slide.
|
|
51873
51906
|
*/
|
|
51874
|
-
const css$
|
|
51907
|
+
const css$D = /* css */`
|
|
51875
51908
|
/* Where the picture stands relative to the slide that is current, in boxes
|
|
51876
51909
|
(see paintTravelProgress). Declared, so that it is a NUMBER the browser can
|
|
51877
51910
|
interpolate: the trait an indicator draws has to travel with the slides,
|
|
@@ -52365,7 +52398,7 @@ const SlideContainer = ({
|
|
|
52365
52398
|
children,
|
|
52366
52399
|
...rest
|
|
52367
52400
|
}) => {
|
|
52368
|
-
import.meta.css = [css$
|
|
52401
|
+
import.meta.css = [css$D, "@jsenv/navi/src/layout/slide_container.jsx"];
|
|
52369
52402
|
const debugFocus = useDebugFocus();
|
|
52370
52403
|
const trackRef = useRef();
|
|
52371
52404
|
// The box itself: it is what takes the keyboard when what is on screen holds
|
|
@@ -55210,7 +55243,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
55210
55243
|
* and only under its own `sizeFromAnchor`) pass through untouched via
|
|
55211
55244
|
* `...rest` to whichever of Popover/Dialog actually renders.
|
|
55212
55245
|
*/
|
|
55213
|
-
const css$
|
|
55246
|
+
const css$C = /* css */`
|
|
55214
55247
|
@layer navi {
|
|
55215
55248
|
.navi_popup {
|
|
55216
55249
|
--popup-border-radius: var(--navi-popup-border-radius);
|
|
@@ -55327,7 +55360,7 @@ const css$B = /* css */`
|
|
|
55327
55360
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
55328
55361
|
*/
|
|
55329
55362
|
const Popup = props => {
|
|
55330
|
-
import.meta.css = [css$
|
|
55363
|
+
import.meta.css = [css$C, "@jsenv/navi/src/layout/popup.jsx"];
|
|
55331
55364
|
const {
|
|
55332
55365
|
mode: modeProp,
|
|
55333
55366
|
maxWidth,
|
|
@@ -55388,7 +55421,7 @@ const Popup = props => {
|
|
|
55388
55421
|
});
|
|
55389
55422
|
};
|
|
55390
55423
|
|
|
55391
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
55424
|
+
installImportMetaCssBuild(import.meta);const css$B = /* css */`
|
|
55392
55425
|
.navi_picker {
|
|
55393
55426
|
/* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
|
|
55394
55427
|
overflow... are already handled correctly by Popup/Popover/Dialog
|
|
@@ -55516,7 +55549,7 @@ installImportMetaCssBuild(import.meta);const css$A = /* css */`
|
|
|
55516
55549
|
}
|
|
55517
55550
|
`;
|
|
55518
55551
|
const PickerCustomResolver = props => {
|
|
55519
|
-
import.meta.css = [css$
|
|
55552
|
+
import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
|
|
55520
55553
|
if (props.children === undefined) {
|
|
55521
55554
|
return jsx(PickerNative, {
|
|
55522
55555
|
...props
|
|
@@ -56310,7 +56343,7 @@ const LoadingIndicator = ({
|
|
|
56310
56343
|
});
|
|
56311
56344
|
};
|
|
56312
56345
|
|
|
56313
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
56346
|
+
installImportMetaCssBuild(import.meta);const css$A = /* css */`
|
|
56314
56347
|
@layer navi {
|
|
56315
56348
|
.navi_separator {
|
|
56316
56349
|
--size: 1px;
|
|
@@ -56388,7 +56421,7 @@ const Separator = ({
|
|
|
56388
56421
|
style,
|
|
56389
56422
|
...props
|
|
56390
56423
|
}) => {
|
|
56391
|
-
import.meta.css = [css$
|
|
56424
|
+
import.meta.css = [css$A, "@jsenv/navi/src/layout/separator.jsx"];
|
|
56392
56425
|
return jsx(Box, {
|
|
56393
56426
|
as: vertical ? "span" : "hr",
|
|
56394
56427
|
...props,
|
|
@@ -56881,7 +56914,7 @@ const ListItemFooter = props => {
|
|
|
56881
56914
|
});
|
|
56882
56915
|
};
|
|
56883
56916
|
|
|
56884
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
56917
|
+
installImportMetaCssBuild(import.meta);const css$z = /* css */`
|
|
56885
56918
|
@layer navi {
|
|
56886
56919
|
.navi_list_container[navi-selectable] {
|
|
56887
56920
|
/* Focus outline */
|
|
@@ -57093,7 +57126,7 @@ const ListSelectableResolver = props => {
|
|
|
57093
57126
|
};
|
|
57094
57127
|
const ListSelectable = props => {
|
|
57095
57128
|
const Next = useNextResolver();
|
|
57096
|
-
import.meta.css = [css$
|
|
57129
|
+
import.meta.css = [css$z, "@jsenv/navi/src/control/list/list_selectable.jsx"];
|
|
57097
57130
|
// we allow ourselves to auto-generate a name
|
|
57098
57131
|
const defaultName = useId();
|
|
57099
57132
|
props.name = props.name || `listbox_${defaultName}`;
|
|
@@ -57713,7 +57746,7 @@ const ListVirtualContext = createContext(null);
|
|
|
57713
57746
|
// that returning a component of one's own — instead of a bare <List.Item> —
|
|
57714
57747
|
// works the same way.
|
|
57715
57748
|
const ListRowContext = createContext(null);
|
|
57716
|
-
const css$
|
|
57749
|
+
const css$y = /* css */`
|
|
57717
57750
|
@layer navi {
|
|
57718
57751
|
.navi_list_container {
|
|
57719
57752
|
--list-outline-width: 1px;
|
|
@@ -58412,7 +58445,7 @@ const css$x = /* css */`
|
|
|
58412
58445
|
}
|
|
58413
58446
|
`;
|
|
58414
58447
|
const ListUI = props => {
|
|
58415
|
-
import.meta.css = [css$
|
|
58448
|
+
import.meta.css = [css$y, "@jsenv/navi/src/control/list/list.jsx"];
|
|
58416
58449
|
const {
|
|
58417
58450
|
ref,
|
|
58418
58451
|
renderBudget: renderBudgetProp = RENDER_BUDGET_DEFAULT,
|
|
@@ -62081,7 +62114,7 @@ const PickerPresetResolver = props => {
|
|
|
62081
62114
|
});
|
|
62082
62115
|
};
|
|
62083
62116
|
|
|
62084
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
62117
|
+
installImportMetaCssBuild(import.meta);const css$x = /* css */`
|
|
62085
62118
|
@layer navi {
|
|
62086
62119
|
}
|
|
62087
62120
|
.navi_badge {
|
|
@@ -62193,7 +62226,7 @@ const Badge = ({
|
|
|
62193
62226
|
className,
|
|
62194
62227
|
...props
|
|
62195
62228
|
}) => {
|
|
62196
|
-
import.meta.css = [css$
|
|
62229
|
+
import.meta.css = [css$x, "@jsenv/navi/src/text/badge.jsx"];
|
|
62197
62230
|
const defaultRef = useRef();
|
|
62198
62231
|
props.ref = props.ref || defaultRef;
|
|
62199
62232
|
const {
|
|
@@ -62245,7 +62278,7 @@ const BadgeButton = props => {
|
|
|
62245
62278
|
};
|
|
62246
62279
|
Badge.Button = BadgeButton;
|
|
62247
62280
|
|
|
62248
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
62281
|
+
installImportMetaCssBuild(import.meta);const css$w = /* css */`
|
|
62249
62282
|
@layer navi {
|
|
62250
62283
|
}
|
|
62251
62284
|
.navi_badge_list {
|
|
@@ -62270,7 +62303,7 @@ const BadgeList = ({
|
|
|
62270
62303
|
max,
|
|
62271
62304
|
...props
|
|
62272
62305
|
}) => {
|
|
62273
|
-
import.meta.css = [css$
|
|
62306
|
+
import.meta.css = [css$w, "@jsenv/navi/src/text/badge_list.jsx"];
|
|
62274
62307
|
const measureRef = useRef();
|
|
62275
62308
|
const visibleRef = useRef();
|
|
62276
62309
|
useLayoutEffect(() => {
|
|
@@ -62345,7 +62378,7 @@ const BadgeList = ({
|
|
|
62345
62378
|
});
|
|
62346
62379
|
};
|
|
62347
62380
|
|
|
62348
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
62381
|
+
installImportMetaCssBuild(import.meta);const css$v = /* css */`
|
|
62349
62382
|
.navi_color {
|
|
62350
62383
|
display: block;
|
|
62351
62384
|
aspect-ratio: 1/1;
|
|
@@ -62376,7 +62409,7 @@ const Color = ({
|
|
|
62376
62409
|
children,
|
|
62377
62410
|
...rest
|
|
62378
62411
|
}) => {
|
|
62379
|
-
import.meta.css = [css$
|
|
62412
|
+
import.meta.css = [css$v, "@jsenv/navi/src/text/color.jsx"];
|
|
62380
62413
|
const color = children || undefined;
|
|
62381
62414
|
return jsx(Box, {
|
|
62382
62415
|
as: "span",
|
|
@@ -62833,7 +62866,7 @@ const PickerFileUI = () => {
|
|
|
62833
62866
|
return String(value);
|
|
62834
62867
|
};
|
|
62835
62868
|
|
|
62836
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
62869
|
+
installImportMetaCssBuild(import.meta);const css$u = /* css */`
|
|
62837
62870
|
@layer navi {
|
|
62838
62871
|
.navi_picker {
|
|
62839
62872
|
--picker-border-radius: var(--navi-control-border-radius);
|
|
@@ -63222,7 +63255,7 @@ installImportMetaCssBuild(import.meta);const css$t = /* css */`
|
|
|
63222
63255
|
}
|
|
63223
63256
|
`;
|
|
63224
63257
|
const PickerButton = props => {
|
|
63225
|
-
import.meta.css = [css$
|
|
63258
|
+
import.meta.css = [css$u, "@jsenv/navi/src/control/picker/picker.jsx"];
|
|
63226
63259
|
if (typeof props.maxLines === "string") {
|
|
63227
63260
|
props.maxLines = parseInt(props.maxLines);
|
|
63228
63261
|
}
|
|
@@ -63671,7 +63704,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
63671
63704
|
* refuse it on purpose, which is what keeps the focus where the travel happens
|
|
63672
63705
|
* instead of moving it into a slide that is about to leave.
|
|
63673
63706
|
*/
|
|
63674
|
-
const css$
|
|
63707
|
+
const css$t = /* css */`
|
|
63675
63708
|
@layer navi {
|
|
63676
63709
|
.navi_picker_spin {
|
|
63677
63710
|
/* A picker one steps through is still a picker: what themes every picker
|
|
@@ -64140,7 +64173,7 @@ const Spin = ({
|
|
|
64140
64173
|
nextLabel,
|
|
64141
64174
|
...rest
|
|
64142
64175
|
}) => {
|
|
64143
|
-
import.meta.css = [css$
|
|
64176
|
+
import.meta.css = [css$t, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
|
|
64144
64177
|
const id = useId();
|
|
64145
64178
|
// What the group around it says, when there is one: how big the whole thing
|
|
64146
64179
|
// is written is said once, on the group, and every spin in it follows.
|
|
@@ -64676,7 +64709,7 @@ const renderValueDefault = value => String(value ?? "");
|
|
|
64676
64709
|
* are passed on to the spins sitting in them.
|
|
64677
64710
|
*/
|
|
64678
64711
|
const SpinGroup = props => {
|
|
64679
|
-
import.meta.css = [css$
|
|
64712
|
+
import.meta.css = [css$t, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
|
|
64680
64713
|
const {
|
|
64681
64714
|
size
|
|
64682
64715
|
} = props;
|
|
@@ -65189,7 +65222,7 @@ const TimeRangeSpin = ({
|
|
|
65189
65222
|
};
|
|
65190
65223
|
|
|
65191
65224
|
installImportMetaCssBuild(import.meta);// TOFIX: select in data then reset, it reset to red/blue instead of red/blue/green
|
|
65192
|
-
const css$
|
|
65225
|
+
const css$s = /* css */`
|
|
65193
65226
|
.navi_checkbox_group {
|
|
65194
65227
|
border-style: solid;
|
|
65195
65228
|
|
|
@@ -65230,7 +65263,7 @@ const CheckboxGroup = props => {
|
|
|
65230
65263
|
return checkboxGroup;
|
|
65231
65264
|
};
|
|
65232
65265
|
const CheckboxGroupInterface = props => {
|
|
65233
|
-
import.meta.css = [css$
|
|
65266
|
+
import.meta.css = [css$s, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
|
|
65234
65267
|
const {
|
|
65235
65268
|
ref
|
|
65236
65269
|
} = props;
|
|
@@ -65279,7 +65312,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
65279
65312
|
* shared sheet is registered here too — a page may render a Textarea without
|
|
65280
65313
|
* any Input.
|
|
65281
65314
|
*/
|
|
65282
|
-
const css$
|
|
65315
|
+
const css$r = /* css */`
|
|
65283
65316
|
.navi_input.navi_textarea {
|
|
65284
65317
|
.navi_control_input {
|
|
65285
65318
|
min-height: calc(var(--textarea-min-rows, 1.5) * 1lh);
|
|
@@ -65366,7 +65399,7 @@ const Textarea = ({
|
|
|
65366
65399
|
width = "35ch",
|
|
65367
65400
|
...props
|
|
65368
65401
|
}) => {
|
|
65369
|
-
import.meta.css = [inputCss + css$
|
|
65402
|
+
import.meta.css = [inputCss + css$r, "@jsenv/navi/src/control/input/textarea.jsx"];
|
|
65370
65403
|
const defaultRef = useRef(null);
|
|
65371
65404
|
props.ref = props.ref || defaultRef;
|
|
65372
65405
|
usePlaceholderHeight(props.ref, props.placeholder);
|
|
@@ -65440,7 +65473,7 @@ const TextareaCharCount = ({
|
|
|
65440
65473
|
maxLength,
|
|
65441
65474
|
...rest
|
|
65442
65475
|
}) => {
|
|
65443
|
-
import.meta.css = [css$
|
|
65476
|
+
import.meta.css = [css$r, "@jsenv/navi/src/control/input/textarea.jsx"];
|
|
65444
65477
|
const resolvedValue = signal ? signal.value : value;
|
|
65445
65478
|
const length = typeof resolvedValue === "string" ? resolvedValue.length : 0;
|
|
65446
65479
|
return jsx(Box, {
|
|
@@ -65625,7 +65658,7 @@ const formatIntlUnit = (unit, {
|
|
|
65625
65658
|
}
|
|
65626
65659
|
};
|
|
65627
65660
|
|
|
65628
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
65661
|
+
installImportMetaCssBuild(import.meta);const css$q = /* css */`
|
|
65629
65662
|
.navi_input_duration {
|
|
65630
65663
|
--duration-separator-spacing: 4px;
|
|
65631
65664
|
--loader-color: var(--navi-loader-color);
|
|
@@ -65692,7 +65725,7 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
|
|
|
65692
65725
|
* "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
|
|
65693
65726
|
*/
|
|
65694
65727
|
const InputDuration = props => {
|
|
65695
|
-
import.meta.css = [css$
|
|
65728
|
+
import.meta.css = [css$q, "@jsenv/navi/src/control/input/input_duration.jsx"];
|
|
65696
65729
|
const defaultRef = useRef();
|
|
65697
65730
|
props.ref = props.ref || defaultRef;
|
|
65698
65731
|
props.max = props.max || "23h59";
|
|
@@ -66194,7 +66227,7 @@ const InputDurationPart = ({
|
|
|
66194
66227
|
});
|
|
66195
66228
|
};
|
|
66196
66229
|
|
|
66197
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
66230
|
+
installImportMetaCssBuild(import.meta);const css$p = /* css */`
|
|
66198
66231
|
.navi_radio_group {
|
|
66199
66232
|
border-style: solid;
|
|
66200
66233
|
|
|
@@ -66214,7 +66247,7 @@ const RadioGroup = props => {
|
|
|
66214
66247
|
return radioGroup;
|
|
66215
66248
|
};
|
|
66216
66249
|
const RadioGroupInterface = props => {
|
|
66217
|
-
import.meta.css = [css$
|
|
66250
|
+
import.meta.css = [css$p, "@jsenv/navi/src/control/input/radio_group.jsx"];
|
|
66218
66251
|
const {
|
|
66219
66252
|
ref
|
|
66220
66253
|
} = props;
|
|
@@ -66272,7 +66305,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
66272
66305
|
* control is drawn — the list it opens stays the platform's own, which is the
|
|
66273
66306
|
* whole point of using a select.
|
|
66274
66307
|
*/
|
|
66275
|
-
const css$
|
|
66308
|
+
const css$o = /* css */`
|
|
66276
66309
|
.navi_input.navi_select {
|
|
66277
66310
|
.navi_control_input {
|
|
66278
66311
|
/* Room for the chevron, which sits over the padding rather than beside
|
|
@@ -66327,7 +66360,7 @@ const Select = ({
|
|
|
66327
66360
|
multiple,
|
|
66328
66361
|
...props
|
|
66329
66362
|
}) => {
|
|
66330
|
-
import.meta.css = [inputCss + css$
|
|
66363
|
+
import.meta.css = [inputCss + css$o, "@jsenv/navi/src/control/input/select.jsx"];
|
|
66331
66364
|
const defaultRef = useRef(null);
|
|
66332
66365
|
props.ref = props.ref || defaultRef;
|
|
66333
66366
|
seedDefaultValueFromSignal(props);
|
|
@@ -66395,7 +66428,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
66395
66428
|
* running: one loading outline around both, which is why each half is told
|
|
66396
66429
|
* `loadingOutline={false}`.
|
|
66397
66430
|
*/
|
|
66398
|
-
const css$
|
|
66431
|
+
const css$n = /* css */`
|
|
66399
66432
|
/* Around the pair rather than in it: the outline is drawn against this
|
|
66400
66433
|
element and follows its corners, and a Group counts its own children to
|
|
66401
66434
|
know which corners to square — an outline among them would be one of the
|
|
@@ -66507,7 +66540,7 @@ const css$m = /* css */`
|
|
|
66507
66540
|
* Anything else lands on the split button's own box.
|
|
66508
66541
|
*/
|
|
66509
66542
|
const SplitButton = props => {
|
|
66510
|
-
import.meta.css = [css$
|
|
66543
|
+
import.meta.css = [css$n, "@jsenv/navi/src/control/input/split_button.jsx"];
|
|
66511
66544
|
const {
|
|
66512
66545
|
options = [],
|
|
66513
66546
|
value,
|
|
@@ -67158,7 +67191,7 @@ installImportMetaCssBuild(import.meta);/*
|
|
|
67158
67191
|
* accessors (top/height vs left/width) chosen from `horizontal`, and the CSS has
|
|
67159
67192
|
* a [data-horizontal] variant.
|
|
67160
67193
|
*/
|
|
67161
|
-
const css$
|
|
67194
|
+
const css$m = /* css */`
|
|
67162
67195
|
.navi_wheel_container {
|
|
67163
67196
|
/* Row size and emphasis band are read together: the band is what a value
|
|
67164
67197
|
must cross to lose its emphasis, and the row is how far it has to travel
|
|
@@ -68280,7 +68313,7 @@ const useWheelKeyboard = ({
|
|
|
68280
68313
|
}, [isHorizontal, interactive, isLoop]);
|
|
68281
68314
|
};
|
|
68282
68315
|
function WheelUI(props) {
|
|
68283
|
-
import.meta.css = [css$
|
|
68316
|
+
import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
|
|
68284
68317
|
const {
|
|
68285
68318
|
ref,
|
|
68286
68319
|
visibleCount = 3,
|
|
@@ -68681,6 +68714,16 @@ function WheelUI(props) {
|
|
|
68681
68714
|
// uiAction; here we fire the action explicitly, once, on the settled value.
|
|
68682
68715
|
const input = inputRef.current;
|
|
68683
68716
|
if (input) {
|
|
68717
|
+
// Said out loud too, OUTSIDE the action pipeline: the action above goes
|
|
68718
|
+
// through the gates (validity included), and what listens to a settle
|
|
68719
|
+
// may be exactly the thing that RESTORES validity — a range's other
|
|
68720
|
+
// bound giving way (see TimeRangeWheel) cannot wait on a gate that
|
|
68721
|
+
// refuses invalid values. Bubbles, so a group holding this wheel hears
|
|
68722
|
+
// it without knowing where the wheel sits.
|
|
68723
|
+
dispatchPublicCustomEvent(input, "navi_wheel_settle", {
|
|
68724
|
+
value: trackedItemsRef.current[index].value,
|
|
68725
|
+
event: settleEvent
|
|
68726
|
+
});
|
|
68684
68727
|
dispatchRequestAction(input, {
|
|
68685
68728
|
event: settleEvent
|
|
68686
68729
|
});
|
|
@@ -69189,7 +69232,7 @@ Wheel.Item = WheelItem;
|
|
|
69189
69232
|
* @param {boolean} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
|
|
69190
69233
|
*/
|
|
69191
69234
|
const WheelGroup = props => {
|
|
69192
|
-
import.meta.css = [css$
|
|
69235
|
+
import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
|
|
69193
69236
|
// WheelGroup IS a control group: it aggregates its named wheels ("hours",
|
|
69194
69237
|
// "minutes"…) into one object value, so it can sit directly inside a Form or a
|
|
69195
69238
|
// Picker with no extra <ControlGroup> wrapper. The wheel-specific presentation
|
|
@@ -69220,7 +69263,11 @@ const WheelGroup = props => {
|
|
|
69220
69263
|
const [controlgroupRootProps, controlgroupProps, childrenWrapperProps] = useControlgroupProps(props, {
|
|
69221
69264
|
allowCapture: true,
|
|
69222
69265
|
wantRequesterButtonState: true,
|
|
69223
|
-
|
|
69266
|
+
// Its own type, not the generic "control_group": the group IS one
|
|
69267
|
+
// control made of parts, and a wheel settling runs the group's own
|
|
69268
|
+
// action — the way a radio checking runs its group's (the auto group
|
|
69269
|
+
// action in control_hooks keys on this type).
|
|
69270
|
+
controlType: "wheel_group",
|
|
69224
69271
|
stateType: "object",
|
|
69225
69272
|
cascadeValidationToChildren: true,
|
|
69226
69273
|
aggregateChildStates: props.aggregateChildStates,
|
|
@@ -69355,7 +69402,7 @@ const WheelColon = props => {
|
|
|
69355
69402
|
};
|
|
69356
69403
|
Wheel.Colon = WheelColon;
|
|
69357
69404
|
|
|
69358
|
-
|
|
69405
|
+
installImportMetaCssBuild(import.meta);/**
|
|
69359
69406
|
* A time of day, and a span between two of them, set by turning rather than by
|
|
69360
69407
|
* typing. A wheel only ever shows values that exist: there is no half-written
|
|
69361
69408
|
* hour to bound and correct under the fingers, which is what a time typed digit
|
|
@@ -69365,11 +69412,33 @@ Wheel.Colon = WheelColon;
|
|
|
69365
69412
|
* like `TimeSpin` — the two are interchangeable in a form. `TimeRangeWheel` is
|
|
69366
69413
|
* two of those and carries `{ start, end }`, with the rule such a pair always
|
|
69367
69414
|
* has: the end comes after the start. Here that rule is lived rather than
|
|
69368
|
-
* checked —
|
|
69369
|
-
* is always a span. The send-time constraint stays
|
|
69370
|
-
* cannot fix (a start so late the span no longer
|
|
69415
|
+
* checked — a bound settling pushes the other out of its way, so what the
|
|
69416
|
+
* wheels show at rest is always a span. The send-time constraint stays
|
|
69417
|
+
* underneath for what pushing cannot fix (a start so late the span no longer
|
|
69418
|
+
* fits in the day).
|
|
69371
69419
|
*/
|
|
69420
|
+
const css$l = /* css */`
|
|
69421
|
+
/* The words around a span's wheels ("De", "à"): one line box, the height
|
|
69422
|
+
of a wheel row (--wheel-item-height re-exposed, same value as
|
|
69423
|
+
.navi_wheel_container), centered against the wheels by the group. The
|
|
69424
|
+
box's strut — this element's own font — is what places the baseline,
|
|
69425
|
+
exactly where a wheel row places its numbers'; and because the content
|
|
69426
|
+
stays in inline flow, anything written INSIDE at another size still
|
|
69427
|
+
sits on that same baseline. Two things this depends on: the label is a
|
|
69428
|
+
<Text size={size}> so its em — and therefore this line-height — is the
|
|
69429
|
+
SAME em as the wheel rows' (a label left at the control font under
|
|
69430
|
+
bigger wheels computes a shorter row and its baseline drifts); and no
|
|
69431
|
+
flex centering of the content (centering re-centers a smaller glyph,
|
|
69432
|
+
baselines are not centers). */
|
|
69433
|
+
.navi_time_range_label {
|
|
69434
|
+
--wheel-item-height: round(1.8em, 1px);
|
|
69372
69435
|
|
|
69436
|
+
color: var(--wheel-color, light-dark(#111, #eee));
|
|
69437
|
+
line-height: var(--wheel-item-height);
|
|
69438
|
+
white-space: nowrap;
|
|
69439
|
+
user-select: none;
|
|
69440
|
+
}
|
|
69441
|
+
`;
|
|
69373
69442
|
const HOUR_COUNT = 24;
|
|
69374
69443
|
const MINUTES_PER_HOUR = 60;
|
|
69375
69444
|
const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
@@ -69528,6 +69597,7 @@ const TimeRangeWheel = ({
|
|
|
69528
69597
|
endTimeProps,
|
|
69529
69598
|
...rest
|
|
69530
69599
|
}) => {
|
|
69600
|
+
import.meta.css = [css$l, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
|
|
69531
69601
|
const startId = useId();
|
|
69532
69602
|
const startRef = useRef(null);
|
|
69533
69603
|
const endRef = useRef(null);
|
|
@@ -69539,10 +69609,15 @@ const TimeRangeWheel = ({
|
|
|
69539
69609
|
distributeChildUIState
|
|
69540
69610
|
} = useAnswered(placeholder, rest, aggregateSpan, distributeSpan);
|
|
69541
69611
|
|
|
69542
|
-
// What the pair does
|
|
69543
|
-
//
|
|
69544
|
-
//
|
|
69545
|
-
//
|
|
69612
|
+
// What the pair does once a bound SETTLES: the one that was moved stays
|
|
69613
|
+
// where it was put and the OTHER one gives way — a refusal at that point
|
|
69614
|
+
// would leave the person to undo what they just did. Settles, not while it
|
|
69615
|
+
// turns: a wheel under the finger holds a value nobody has chosen yet, and
|
|
69616
|
+
// the other bound jumping around mid-gesture answers a question that was
|
|
69617
|
+
// not asked. Wired on the wheel's settle EVENT (navi_wheel_settle), never
|
|
69618
|
+
// on `action`: an action goes through the gates, validity included, and
|
|
69619
|
+
// the moment this must run is precisely the moment the pair is INVALID —
|
|
69620
|
+
// an action-gated push would be refused by the very thing it fixes.
|
|
69546
69621
|
const keepBoundsApart = (movedSide, movedTime, e) => {
|
|
69547
69622
|
const movedMinutes = minutesFromTime(movedTime);
|
|
69548
69623
|
if (movedMinutes === null) {
|
|
@@ -69586,6 +69661,7 @@ const TimeRangeWheel = ({
|
|
|
69586
69661
|
value: answeredRef,
|
|
69587
69662
|
children: [startLabel === null ? null : jsx(Text, {
|
|
69588
69663
|
size: size,
|
|
69664
|
+
className: "navi_time_range_label",
|
|
69589
69665
|
children: startLabel
|
|
69590
69666
|
}), jsx(TimeWheel, {
|
|
69591
69667
|
id: startId,
|
|
@@ -69595,12 +69671,20 @@ const TimeRangeWheel = ({
|
|
|
69595
69671
|
hours: hours,
|
|
69596
69672
|
loop: loop,
|
|
69597
69673
|
size: size,
|
|
69598
|
-
placeholder: placeholder ? placeholder.start : undefined
|
|
69599
|
-
|
|
69674
|
+
placeholder: placeholder ? placeholder.start : undefined
|
|
69675
|
+
// e.detail.value is the settled WHEEL's own value (an hour, a
|
|
69676
|
+
// minute) — half a time. What the pair compares is this bound's
|
|
69677
|
+
// whole time, read off the element the listener sits on.
|
|
69678
|
+
,
|
|
69679
|
+
|
|
69680
|
+
onnavi_wheel_settle: e => {
|
|
69681
|
+
keepBoundsApart("start", getUIStateFromElement(e.currentTarget), e);
|
|
69682
|
+
},
|
|
69600
69683
|
...timeProps,
|
|
69601
69684
|
...startTimeProps
|
|
69602
69685
|
}), endLabel === null ? null : jsx(Text, {
|
|
69603
69686
|
size: size,
|
|
69687
|
+
className: "navi_time_range_label",
|
|
69604
69688
|
children: endLabel
|
|
69605
69689
|
}), jsx(TimeWheel, {
|
|
69606
69690
|
ref: endRef,
|
|
@@ -69610,11 +69694,14 @@ const TimeRangeWheel = ({
|
|
|
69610
69694
|
loop: loop,
|
|
69611
69695
|
size: size,
|
|
69612
69696
|
placeholder: placeholder ? placeholder.end : undefined,
|
|
69613
|
-
|
|
69697
|
+
onnavi_wheel_settle: e => {
|
|
69698
|
+
keepBoundsApart("end", getUIStateFromElement(e.currentTarget), e);
|
|
69699
|
+
}
|
|
69614
69700
|
// Which time it comes after, and how much room there must be between
|
|
69615
69701
|
// the two: said on the LATER of the two, so the answer is given where
|
|
69616
69702
|
// the time one would have to move is (see time_range_constraint.js).
|
|
69617
69703
|
,
|
|
69704
|
+
|
|
69618
69705
|
"data-time-after": startId,
|
|
69619
69706
|
"data-time-min-duration": minDuration,
|
|
69620
69707
|
...timeProps,
|
|
@@ -75247,18 +75334,34 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
75247
75334
|
* Two distinct facts, drawn separately — they usually agree, and everything
|
|
75248
75335
|
* this component says comes from the moments they do not:
|
|
75249
75336
|
*
|
|
75250
|
-
* - the PATH (
|
|
75251
|
-
*
|
|
75252
|
-
*
|
|
75253
|
-
*
|
|
75337
|
+
* - DONE and the PATH (the blue fill). The mental model, to keep in mind
|
|
75338
|
+
* when touching any of this: the circles are THINGS TO DO, and the path is
|
|
75339
|
+
* the progression along the linear walk from the first to the last — a
|
|
75340
|
+
* thing done is what lets the path advance. Answering a step fills its
|
|
75341
|
+
* dot AND the line onward, up to the NEXT dot: the invitation to go
|
|
75342
|
+
* there. If that next step is already done the path crosses it from
|
|
75343
|
+
* behind and carries on, and so forth — the fill covers the answered
|
|
75344
|
+
* prefix plus one segment of appetite. It stops at the edge of the first
|
|
75345
|
+
* dot not answered, which stays empty: the fill's one meaning is
|
|
75346
|
+
* "answered", and the first dot is NOT filled on arrival — it becomes so
|
|
75347
|
+
* by being answered. Past the fill the line is dashed, the road not
|
|
75348
|
+
* walked yet; steps answered out of order are filled dots standing alone,
|
|
75349
|
+
* dashed segments around them: the holes, readable at a glance.
|
|
75254
75350
|
* - the POSITION (`current`): the step being looked at, marked by a halo
|
|
75255
|
-
* around its dot and its label emphasized. It travels freely, so it can
|
|
75256
|
-
* AHEAD of the path
|
|
75257
|
-
* or BEHIND it (back on step 1 to change something already answered).
|
|
75351
|
+
* around its dot and its label emphasized. It travels freely, so it can
|
|
75352
|
+
* be AHEAD of the path or BEHIND it.
|
|
75258
75353
|
*
|
|
75259
75354
|
* Both move smoothly on change (a CSS transition each), so pressing a step
|
|
75260
75355
|
* or answering one is seen travelling rather than jumping.
|
|
75261
75356
|
*
|
|
75357
|
+
* The steps are the CHILDREN — <StepList.Item value="club">Club</StepList.Item>.
|
|
75358
|
+
* An Item renders nothing: it REGISTERS with the list as it renders (order
|
|
75359
|
+
* of rendering is the order of the steps), and the list draws everything —
|
|
75360
|
+
* which is what lets an Item come from anywhere: a .map(), a fragment, a
|
|
75361
|
+
* component of your own wrapping it. One caveat comes with reading the
|
|
75362
|
+
* children as they render: hand the list fresh Item vnodes on each render
|
|
75363
|
+
* (the usual JSX), not a memoized array a bailout would keep from rendering.
|
|
75364
|
+
*
|
|
75262
75365
|
* The dots are drawn in SVG, twice: a muted layer, and a filled layer
|
|
75263
75366
|
* clipped at the path's edge — the clip is what makes the path's progress a
|
|
75264
75367
|
* single sweep that fills the line and the dots it crosses in one movement.
|
|
@@ -75267,23 +75370,38 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
75267
75370
|
* surface. Colors are CSS custom properties (see the css below), overridden
|
|
75268
75371
|
* from outside for a dark band or a different accent.
|
|
75269
75372
|
*
|
|
75270
|
-
*
|
|
75271
|
-
*
|
|
75272
|
-
*
|
|
75273
|
-
*
|
|
75373
|
+
* `slideContainer` connects the list to a <SlideContainer> by id, both ways:
|
|
75374
|
+
* pressing a step travels there (--navi-go-to-slide), and the position is
|
|
75375
|
+
* READ off the container rather than said by a prop — including mid-travel:
|
|
75376
|
+
* the container paints --slide-travel-progress on this element (it is a
|
|
75377
|
+
* follower, same mechanism as <Nav slideContainer>), so the halo rides the
|
|
75378
|
+
* drag under the finger, in CSS alone. The path is not concerned: it moves
|
|
75379
|
+
* on answers, never on movement.
|
|
75274
75380
|
*/
|
|
75275
75381
|
const css$2 = /* css */`
|
|
75276
75382
|
.navi_step_list {
|
|
75277
|
-
/* The knobs
|
|
75278
|
-
|
|
75279
|
-
|
|
75280
|
-
|
|
75281
|
-
|
|
75282
|
-
|
|
75283
|
-
|
|
75284
|
-
|
|
75285
|
-
--step-list-
|
|
75286
|
-
--step-list-
|
|
75383
|
+
/* The knobs: one accent for everything filled — the dots and the line
|
|
75384
|
+
share it, because the fill has ONE meaning (answered) and a meaning
|
|
75385
|
+
does not change color. Muted is what is not answered, on-accent
|
|
75386
|
+
writes on filled dots. Said from OUTSIDE (any ancestor — a dark band,
|
|
75387
|
+
a themed app) on the plain names; resolved here through an
|
|
75388
|
+
indirection (--x-…, the way Button does), because a default written
|
|
75389
|
+
on the plain name on this very element would beat anything an
|
|
75390
|
+
ancestor says. */
|
|
75391
|
+
--x-step-list-accent: var(--step-list-accent, #4f8ef7);
|
|
75392
|
+
--x-step-list-on-accent: var(--step-list-on-accent, white);
|
|
75393
|
+
--x-step-list-muted: var(--step-list-muted, light-dark(#8a93a8, #8b99b8));
|
|
75394
|
+
--x-step-list-line: var(
|
|
75395
|
+
--step-list-line,
|
|
75396
|
+
light-dark(#c9d0dd, rgba(255, 255, 255, 0.35))
|
|
75397
|
+
);
|
|
75398
|
+
--x-step-list-current-color: var(
|
|
75399
|
+
--step-list-current-color,
|
|
75400
|
+
light-dark(#1c2433, white)
|
|
75401
|
+
);
|
|
75402
|
+
/* How long a movement takes — the path sweeping, the halo sliding. One
|
|
75403
|
+
number for all of them: they tell one story. */
|
|
75404
|
+
--x-step-list-duration: var(--step-list-duration, 300ms);
|
|
75287
75405
|
|
|
75288
75406
|
position: relative;
|
|
75289
75407
|
display: block;
|
|
@@ -75297,52 +75415,92 @@ const css$2 = /* css */`
|
|
|
75297
75415
|
}
|
|
75298
75416
|
/* The road not walked yet. */
|
|
75299
75417
|
.navi_step_list_rail line {
|
|
75300
|
-
stroke: var(--step-list-line);
|
|
75418
|
+
stroke: var(--x-step-list-line);
|
|
75301
75419
|
stroke-width: 2;
|
|
75302
75420
|
stroke-dasharray: 4 5;
|
|
75303
75421
|
}
|
|
75304
75422
|
.navi_step_list_rail circle {
|
|
75305
75423
|
fill: none;
|
|
75306
|
-
stroke: var(--step-list-muted);
|
|
75424
|
+
stroke: var(--x-step-list-muted);
|
|
75307
75425
|
stroke-width: 1.5;
|
|
75308
75426
|
}
|
|
75309
75427
|
.navi_step_list_rail text {
|
|
75310
75428
|
font-weight: 600;
|
|
75311
75429
|
font-size: 12px;
|
|
75312
|
-
fill: var(--step-list-muted);
|
|
75313
|
-
}
|
|
75314
|
-
/* The
|
|
75315
|
-
|
|
75316
|
-
|
|
75430
|
+
fill: var(--x-step-list-muted);
|
|
75431
|
+
}
|
|
75432
|
+
/* The current dot says so in the drawing itself, not only by its halo: on
|
|
75433
|
+
a dark band a muted number under a faint ring reads as nothing. Said in
|
|
75434
|
+
the base layer only (see renderRail) — a current dot the path has
|
|
75435
|
+
covered keeps the filled colors. */
|
|
75436
|
+
.navi_step_list_rail g[data-current] circle {
|
|
75437
|
+
stroke: var(--x-step-list-current-color);
|
|
75438
|
+
}
|
|
75439
|
+
.navi_step_list_rail g[data-current] text {
|
|
75440
|
+
fill: var(--x-step-list-current-color);
|
|
75441
|
+
}
|
|
75442
|
+
/* A step answered has its dot filled, wherever the path stands: answered
|
|
75443
|
+
out of order it stands alone, a filled dot between dashed segments.
|
|
75444
|
+
After the current rule on purpose: answered wins the drawing, the halo
|
|
75445
|
+
says current. */
|
|
75446
|
+
.navi_step_list_rail g[data-done] circle {
|
|
75447
|
+
fill: var(--x-step-list-accent);
|
|
75448
|
+
stroke: var(--x-step-list-accent);
|
|
75449
|
+
}
|
|
75450
|
+
.navi_step_list_rail g[data-done] text {
|
|
75451
|
+
fill: var(--x-step-list-on-accent);
|
|
75452
|
+
}
|
|
75453
|
+
/* The path: same drawing, filled, revealed up to the fill's edge — the
|
|
75454
|
+
answered prefix plus its segment of appetite (see the top comment). The
|
|
75455
|
+
clip is set inline (a width in px); transitioning it is what makes an
|
|
75456
|
+
answered step SWEEP its dot and the line onward rather than pop. */
|
|
75317
75457
|
.navi_step_list_rail_filled {
|
|
75318
|
-
transition: clip-path
|
|
75458
|
+
transition: clip-path var(--x-step-list-duration) ease;
|
|
75319
75459
|
}
|
|
75320
75460
|
.navi_step_list_rail_filled line {
|
|
75321
|
-
stroke: var(--step-list-
|
|
75461
|
+
stroke: var(--x-step-list-accent);
|
|
75322
75462
|
stroke-dasharray: none;
|
|
75323
75463
|
}
|
|
75324
75464
|
.navi_step_list_rail_filled circle {
|
|
75325
|
-
fill: var(--step-list-accent);
|
|
75326
|
-
stroke: var(--step-list-accent);
|
|
75465
|
+
fill: var(--x-step-list-accent);
|
|
75466
|
+
stroke: var(--x-step-list-accent);
|
|
75327
75467
|
}
|
|
75328
75468
|
.navi_step_list_rail_filled text {
|
|
75329
|
-
fill: var(--step-list-on-accent);
|
|
75469
|
+
fill: var(--x-step-list-on-accent);
|
|
75330
75470
|
}
|
|
75331
75471
|
/* The position: a halo around the dot being looked at. It slides from dot
|
|
75332
75472
|
to dot (transform, transitioned) — the g moves, the circle inside is
|
|
75333
75473
|
drawn at x=0. */
|
|
75334
75474
|
.navi_step_list_marker {
|
|
75335
|
-
transition: transform
|
|
75475
|
+
transition: transform var(--x-step-list-duration) ease;
|
|
75336
75476
|
}
|
|
75337
75477
|
.navi_step_list_marker circle {
|
|
75338
75478
|
fill: none;
|
|
75339
|
-
stroke: color-mix(in srgb, var(--step-list-accent) 65%, transparent);
|
|
75479
|
+
stroke: color-mix(in srgb, var(--x-step-list-accent) 65%, transparent);
|
|
75340
75480
|
stroke-width: 1.5;
|
|
75341
75481
|
}
|
|
75482
|
+
|
|
75483
|
+
/* Connected to slides: the movement is not this component's anymore. The
|
|
75484
|
+
container paints --slide-travel-progress here (this element follows it,
|
|
75485
|
+
see data-slide-container-follows) — an asked-for travel animates it, a
|
|
75486
|
+
finger drags it — and everything below is a calc() of that number, so
|
|
75487
|
+
the halo and the path move per frame in CSS alone.
|
|
75488
|
+
Position, in dots-x px: where the picture is right now. */
|
|
75489
|
+
.navi_step_list[data-slide-container-follows] {
|
|
75490
|
+
--step-list-position: calc(
|
|
75491
|
+
var(--step-list-pos-x, 0) + var(--slide-travel-progress) *
|
|
75492
|
+
var(--step-list-pos-dx, 0)
|
|
75493
|
+
);
|
|
75494
|
+
}
|
|
75495
|
+
.navi_step_list[data-slide-container-follows] .navi_step_list_marker {
|
|
75496
|
+
transform: translateX(calc(var(--step-list-position) * 1px));
|
|
75497
|
+
transition: none;
|
|
75498
|
+
}
|
|
75499
|
+
|
|
75342
75500
|
/* One press target per step, covering the dot AND the label under it. The
|
|
75343
75501
|
feedback is NOT the whole surface: a rectangle would say the whole band
|
|
75344
75502
|
is a button, when the affordance is the dot — so hover and focus land on
|
|
75345
|
-
a circle drawn over the dot
|
|
75503
|
+
a circle drawn over the dot, plus the label brightening.
|
|
75346
75504
|
--step-dot-x anchors both on the dot, wherever the dot sits in the slot:
|
|
75347
75505
|
the first and last slots are asymmetric (cut at the container's edge,
|
|
75348
75506
|
see the geometry in the component). */
|
|
@@ -75352,9 +75510,9 @@ const css$2 = /* css */`
|
|
|
75352
75510
|
box-sizing: border-box;
|
|
75353
75511
|
height: 100%;
|
|
75354
75512
|
}
|
|
75355
|
-
/* Doubled selector: the
|
|
75356
|
-
|
|
75357
|
-
what makes these values the ones read. */
|
|
75513
|
+
/* Doubled selector: the button's own state formulas (a readonly color
|
|
75514
|
+
mixed at the variant level) are declared in navi's stylesheet, injected
|
|
75515
|
+
after this one — specificity is what makes these values the ones read. */
|
|
75358
75516
|
.navi_step_list .navi_step_list_step {
|
|
75359
75517
|
position: relative;
|
|
75360
75518
|
display: block;
|
|
@@ -75363,15 +75521,19 @@ const css$2 = /* css */`
|
|
|
75363
75521
|
padding: 0;
|
|
75364
75522
|
font-size: 12px;
|
|
75365
75523
|
outline: none;
|
|
75366
|
-
--button-color: var(--step-list-muted);
|
|
75367
|
-
--button-color-readonly: var(--step-list-muted);
|
|
75368
|
-
|
|
75369
|
-
|
|
75370
|
-
|
|
75524
|
+
--button-color: var(--x-step-list-muted);
|
|
75525
|
+
--button-color-readonly: var(--x-step-list-muted);
|
|
75526
|
+
/* The button's own focus ring, silenced: it would outline the whole
|
|
75527
|
+
press surface, and the ring this list draws is the one around the dot
|
|
75528
|
+
(see below) — two rings read as a mistake. Width rather than style,
|
|
75529
|
+
because the dot sets its own style in full. */
|
|
75530
|
+
--button-outline-width: 0px;
|
|
75371
75531
|
}
|
|
75372
75532
|
/* Centered on the dot: same vertical middle as the rail (top 0, height 34,
|
|
75373
|
-
cy 17).
|
|
75374
|
-
|
|
75533
|
+
cy 17). A real element rather than a ::before, because it is also what a
|
|
75534
|
+
callout anchors to (data-callout-anchor needs a selector) — a message
|
|
75535
|
+
about a step points at its CIRCLE, not at the press surface. */
|
|
75536
|
+
.navi_step_list_dot {
|
|
75375
75537
|
position: absolute;
|
|
75376
75538
|
top: 17px;
|
|
75377
75539
|
left: var(--step-dot-x);
|
|
@@ -75379,18 +75541,18 @@ const css$2 = /* css */`
|
|
|
75379
75541
|
height: 30px;
|
|
75380
75542
|
border-radius: 50%;
|
|
75381
75543
|
translate: -50% -50%;
|
|
75382
|
-
|
|
75544
|
+
pointer-events: none;
|
|
75383
75545
|
}
|
|
75384
|
-
.navi_step_list_step:hover
|
|
75385
|
-
.navi_step_list_step[data-hover]
|
|
75386
|
-
background: color-mix(in srgb, var(--step-list-accent) 15%, transparent);
|
|
75546
|
+
.navi_step_list_step:hover .navi_step_list_dot,
|
|
75547
|
+
.navi_step_list_step[data-hover] .navi_step_list_dot {
|
|
75548
|
+
background: color-mix(in srgb, var(--x-step-list-accent) 15%, transparent);
|
|
75387
75549
|
}
|
|
75388
75550
|
.navi_step_list .navi_step_list_step:hover,
|
|
75389
75551
|
.navi_step_list .navi_step_list_step[data-hover] {
|
|
75390
|
-
--button-color: var(--step-list-current-color);
|
|
75552
|
+
--button-color: var(--x-step-list-current-color);
|
|
75391
75553
|
}
|
|
75392
|
-
.navi_step_list_step:focus-visible
|
|
75393
|
-
.navi_step_list_step[data-focus-visible]
|
|
75554
|
+
.navi_step_list_step:focus-visible .navi_step_list_dot,
|
|
75555
|
+
.navi_step_list_step[data-focus-visible] .navi_step_list_dot {
|
|
75394
75556
|
outline-width: var(--navi-focus-outline-width);
|
|
75395
75557
|
outline-style: solid;
|
|
75396
75558
|
outline-color: var(--navi-focus-outline-color);
|
|
@@ -75405,8 +75567,8 @@ const css$2 = /* css */`
|
|
|
75405
75567
|
}
|
|
75406
75568
|
.navi_step_list .navi_step_list_step[data-current] {
|
|
75407
75569
|
font-weight: 600;
|
|
75408
|
-
--button-color: var(--step-list-current-color);
|
|
75409
|
-
--button-color-readonly: var(--step-list-current-color);
|
|
75570
|
+
--button-color: var(--x-step-list-current-color);
|
|
75571
|
+
--button-color-readonly: var(--x-step-list-current-color);
|
|
75410
75572
|
}
|
|
75411
75573
|
`;
|
|
75412
75574
|
const RAIL_H = 34;
|
|
@@ -75418,26 +75580,50 @@ const EDGE_INSET = 30;
|
|
|
75418
75580
|
// halo of a current dot not to sit on the line.
|
|
75419
75581
|
const LINE_GAP = 5;
|
|
75420
75582
|
|
|
75583
|
+
// What the Items say to the list holding them (see Step): where to write
|
|
75584
|
+
// themselves down. Null outside any list — a Step alone renders nothing and
|
|
75585
|
+
// registers nowhere.
|
|
75586
|
+
const StepListContext = createContext(null);
|
|
75587
|
+
|
|
75421
75588
|
/**
|
|
75422
75589
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
75423
75590
|
* current?: string,
|
|
75424
|
-
*
|
|
75425
|
-
*
|
|
75591
|
+
* slideContainer?: string,
|
|
75592
|
+
* travelByClick?: boolean,
|
|
75593
|
+
* travelByKeyboard?: boolean,
|
|
75594
|
+
* duration?: string,
|
|
75426
75595
|
* [key: string]: any,
|
|
75427
75596
|
* }>}
|
|
75428
75597
|
* @param {string} [current] - the step being looked at: its dot gets the
|
|
75429
75598
|
* halo, its label the emphasis. Omit for "nowhere" — a confirmation
|
|
75430
|
-
* screen after the walk, say.
|
|
75431
|
-
*
|
|
75432
|
-
*
|
|
75433
|
-
*
|
|
75434
|
-
*
|
|
75435
|
-
*
|
|
75599
|
+
* screen after the walk, say. With `slideContainer` the position is read
|
|
75600
|
+
* off the container instead, and this prop is ignored.
|
|
75601
|
+
* @param {string} [slideContainer] - id of a <SlideContainer> these steps
|
|
75602
|
+
* are the slides of. Pressing a step travels there
|
|
75603
|
+
* (--navi-go-to-slide), the halo follows the container — drags included —
|
|
75604
|
+
* and this element becomes a follower of the container
|
|
75605
|
+
* (data-slide-container-follows), which is also what keeps the arrow keys
|
|
75606
|
+
* working from here.
|
|
75607
|
+
* @param {boolean} [travelByClick=true] - whether pressing a step goes
|
|
75608
|
+
* there. Off, the steps are read-only — shown, not offered. To DO
|
|
75609
|
+
* something on a press, say `onClick` on the Item itself: like every other
|
|
75610
|
+
* prop an Item carries, it lands on that step's button.
|
|
75611
|
+
* @param {boolean} [travelByKeyboard=true] - whether the arrow keys walk
|
|
75612
|
+
* from one step to the other (the focus moves, Enter presses). Only when
|
|
75613
|
+
* the list stands alone: connected to slides the arrows belong to the
|
|
75614
|
+
* CONTAINER — this element is a follower, so a press here already walks
|
|
75615
|
+
* the slides, and the container's own `travelByKeyboard` is the one that
|
|
75616
|
+
* says so. One owner per mode, or one arrow would do both.
|
|
75617
|
+
* @param {string} [duration] - how long a movement takes (the path
|
|
75618
|
+
* sweeping, the halo sliding), any CSS duration. 300ms unless said —
|
|
75619
|
+
* here, or from outside via --step-list-duration.
|
|
75436
75620
|
*/
|
|
75437
75621
|
const StepList = ({
|
|
75438
75622
|
current,
|
|
75439
|
-
|
|
75440
|
-
|
|
75623
|
+
slideContainer,
|
|
75624
|
+
travelByClick = true,
|
|
75625
|
+
travelByKeyboard = true,
|
|
75626
|
+
duration,
|
|
75441
75627
|
children,
|
|
75442
75628
|
...rest
|
|
75443
75629
|
}) => {
|
|
@@ -75460,11 +75646,42 @@ const StepList = ({
|
|
|
75460
75646
|
};
|
|
75461
75647
|
}, []);
|
|
75462
75648
|
|
|
75463
|
-
// The steps
|
|
75464
|
-
//
|
|
75465
|
-
|
|
75466
|
-
|
|
75467
|
-
|
|
75649
|
+
// The arrows walk the steps — standing alone only: connected, this element
|
|
75650
|
+
// follows the container, whose own keydown listener already walks the
|
|
75651
|
+
// slides from here (and whose travelByKeyboard says whether to). A focus
|
|
75652
|
+
// group on top of that would make one arrow do both.
|
|
75653
|
+
useFocusGroup(rootRef, {
|
|
75654
|
+
enabled: Boolean(travelByKeyboard) && !slideContainer,
|
|
75655
|
+
direction: "x"
|
|
75656
|
+
});
|
|
75657
|
+
|
|
75658
|
+
// The roll call: every render of this list opens a fresh page, the Items
|
|
75659
|
+
// rendering below write themselves on it (in rendering order, which is the
|
|
75660
|
+
// order of the steps), and the layout effect reads the page back. What was
|
|
75661
|
+
// read is STATE — the first render knows no steps, the effect's render
|
|
75662
|
+
// draws them — and a change in what the Items say (a done toggled) flows
|
|
75663
|
+
// the same way: fresh page, fresh read, redraw.
|
|
75664
|
+
const registryRef = useRef(null);
|
|
75665
|
+
if (!registryRef.current) {
|
|
75666
|
+
registryRef.current = {
|
|
75667
|
+
renderedSteps: []
|
|
75668
|
+
};
|
|
75669
|
+
}
|
|
75670
|
+
const registry = registryRef.current;
|
|
75671
|
+
registry.renderedSteps = [];
|
|
75672
|
+
const [steps, setSteps] = useState([]);
|
|
75673
|
+
useLayoutEffect(() => {
|
|
75674
|
+
const collected = registry.renderedSteps;
|
|
75675
|
+
// An empty page while steps are known: the children were most likely
|
|
75676
|
+
// bailed out of rendering (memoized vnodes), not removed — keeping the
|
|
75677
|
+
// known steps beats erasing the drawing (see the caveat in the top
|
|
75678
|
+
// comment).
|
|
75679
|
+
if (collected.length === 0 && steps.length > 0) {
|
|
75680
|
+
return;
|
|
75681
|
+
}
|
|
75682
|
+
setSteps(previous => sameSteps(previous, collected) ? previous : [...collected]);
|
|
75683
|
+
});
|
|
75684
|
+
const stepCount = steps.length;
|
|
75468
75685
|
const dotXs = [];
|
|
75469
75686
|
if (width > 0 && stepCount > 0) {
|
|
75470
75687
|
const span = width - EDGE_INSET * 2;
|
|
@@ -75474,12 +75691,84 @@ const StepList = ({
|
|
|
75474
75691
|
index++;
|
|
75475
75692
|
}
|
|
75476
75693
|
}
|
|
75477
|
-
const indexOf = value =>
|
|
75478
|
-
|
|
75479
|
-
|
|
75480
|
-
//
|
|
75481
|
-
//
|
|
75482
|
-
|
|
75694
|
+
const indexOf = value => steps.findIndex(step => step.value === value);
|
|
75695
|
+
|
|
75696
|
+
// Where the slides are, read off the container: which slide is current,
|
|
75697
|
+
// and — while a travel or a drag is playing — which one the picture leans
|
|
75698
|
+
// towards. The current area re-renders this component (the emphasized
|
|
75699
|
+
// label, the data-current dot); the in-between positions never do: they
|
|
75700
|
+
// are written as numbers on this element and interpolated by the CSS
|
|
75701
|
+
// above, at the pace of --slide-travel-progress.
|
|
75702
|
+
const [containerCurrent, setContainerCurrent] = useState(undefined);
|
|
75703
|
+
useLayoutEffect(() => {
|
|
75704
|
+
if (!slideContainer || dotXs.length === 0) {
|
|
75705
|
+
return undefined;
|
|
75706
|
+
}
|
|
75707
|
+
const containerElement = document.getElementById(slideContainer);
|
|
75708
|
+
if (!containerElement) {
|
|
75709
|
+
console.warn(`<StepList slideContainer="${slideContainer}"> but no element with that id found`);
|
|
75710
|
+
return undefined;
|
|
75711
|
+
}
|
|
75712
|
+
const rootElement = rootRef.current;
|
|
75713
|
+
const read = () => {
|
|
75714
|
+
const currentArea = containerElement.getAttribute("data-slide-current");
|
|
75715
|
+
const towardArea = containerElement.getAttribute("data-slide-travel-toward");
|
|
75716
|
+
setContainerCurrent(currentArea ?? undefined);
|
|
75717
|
+
const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
|
|
75718
|
+
if (currentIdx === -1) {
|
|
75719
|
+
// A slide no step names (a confirmation screen): the halo is not
|
|
75720
|
+
// rendered, and the last position is left standing for the path.
|
|
75721
|
+
return;
|
|
75722
|
+
}
|
|
75723
|
+
const x = dotXs[currentIdx];
|
|
75724
|
+
let dx = 0;
|
|
75725
|
+
if (towardArea && towardArea !== currentArea) {
|
|
75726
|
+
const towardIdx = indexOf(towardArea);
|
|
75727
|
+
if (towardIdx !== -1 && towardIdx !== currentIdx) {
|
|
75728
|
+
// The container counts +1 when the picture leans on a slide BEFORE
|
|
75729
|
+
// the current one, -1 after: the delta is signed the same way, so
|
|
75730
|
+
// progress × delta lands exactly on the other dot.
|
|
75731
|
+
const sign = towardIdx > currentIdx ? -1 : 1;
|
|
75732
|
+
dx = (dotXs[towardIdx] - x) * sign;
|
|
75733
|
+
}
|
|
75734
|
+
}
|
|
75735
|
+
rootElement.style.setProperty("--step-list-pos-x", x);
|
|
75736
|
+
rootElement.style.setProperty("--step-list-pos-dx", dx);
|
|
75737
|
+
};
|
|
75738
|
+
read();
|
|
75739
|
+
const observer = new MutationObserver(read);
|
|
75740
|
+
observer.observe(containerElement, {
|
|
75741
|
+
attributes: true,
|
|
75742
|
+
attributeFilter: ["data-slide-current", "data-slide-travel-toward"]
|
|
75743
|
+
});
|
|
75744
|
+
return () => {
|
|
75745
|
+
observer.disconnect();
|
|
75746
|
+
};
|
|
75747
|
+
// width: the dots move when the room does, and the written positions are
|
|
75748
|
+
// pixels of those dots.
|
|
75749
|
+
}, [slideContainer, width, stepCount]);
|
|
75750
|
+
const resolvedCurrent = slideContainer ? containerCurrent : current;
|
|
75751
|
+
const currentIndex = resolvedCurrent === undefined ? -1 : indexOf(resolvedCurrent);
|
|
75752
|
+
// The path, deduced from what the Items say: the steps answered without a
|
|
75753
|
+
// gap from the start. -1 when the first step is not answered yet — there
|
|
75754
|
+
// is no path then, only dots.
|
|
75755
|
+
let pathEndIndex = steps.findIndex(step => !step.done);
|
|
75756
|
+
if (pathEndIndex === -1) {
|
|
75757
|
+
pathEndIndex = stepCount;
|
|
75758
|
+
}
|
|
75759
|
+
pathEndIndex -= 1;
|
|
75760
|
+
// How far the fill goes: nowhere while nothing is answered; past the last
|
|
75761
|
+
// dot (radius plus stroke) when everything is; otherwise THROUGH the
|
|
75762
|
+
// answered prefix and onward to the edge of the next dot — the segment of
|
|
75763
|
+
// appetite (see the top comment), with the dot it points at left empty.
|
|
75764
|
+
let fillX;
|
|
75765
|
+
if (pathEndIndex === -1) {
|
|
75766
|
+
fillX = 0;
|
|
75767
|
+
} else if (pathEndIndex >= stepCount - 1) {
|
|
75768
|
+
fillX = dotXs[stepCount - 1] + DOT_R + 3;
|
|
75769
|
+
} else {
|
|
75770
|
+
fillX = dotXs[pathEndIndex + 1] - DOT_R - LINE_GAP;
|
|
75771
|
+
}
|
|
75483
75772
|
const cy = RAIL_H / 2;
|
|
75484
75773
|
const slotWidth = dotXs.length > 1 ? dotXs[1] - dotXs[0] : width;
|
|
75485
75774
|
const renderRail = filled => jsx("svg", {
|
|
@@ -75492,6 +75781,10 @@ const StepList = ({
|
|
|
75492
75781
|
viewBox: `0 0 ${width} ${RAIL_H}`,
|
|
75493
75782
|
"aria-hidden": "true",
|
|
75494
75783
|
children: dotXs.map((x, index) => jsxs("g", {
|
|
75784
|
+
// Base layer only: dots the path covers are drawn filled by the
|
|
75785
|
+
// layer above anyway (see the css).
|
|
75786
|
+
"data-current": !filled && index === currentIndex ? "" : undefined,
|
|
75787
|
+
"data-done": !filled && steps[index].done ? "" : undefined,
|
|
75495
75788
|
children: [index > 0 ? jsx("line", {
|
|
75496
75789
|
x1: dotXs[index - 1] + DOT_R + LINE_GAP,
|
|
75497
75790
|
y1: cy,
|
|
@@ -75508,14 +75801,29 @@ const StepList = ({
|
|
|
75508
75801
|
"text-anchor": "middle",
|
|
75509
75802
|
children: index + 1
|
|
75510
75803
|
})]
|
|
75511
|
-
},
|
|
75804
|
+
}, steps[index].value))
|
|
75512
75805
|
});
|
|
75513
|
-
return
|
|
75806
|
+
return jsxs(Box, {
|
|
75514
75807
|
...rest,
|
|
75515
75808
|
ref: rootRef,
|
|
75516
75809
|
baseClassName: "navi_step_list",
|
|
75517
|
-
"data-step-list": ""
|
|
75518
|
-
|
|
75810
|
+
"data-step-list": ""
|
|
75811
|
+
// A follower of the container: the travel's progress is painted here
|
|
75812
|
+
// for the CSS to draw with, and the arrow keys keep walking the slides
|
|
75813
|
+
// from this element.
|
|
75814
|
+
,
|
|
75815
|
+
|
|
75816
|
+
"data-slide-container-follows": slideContainer,
|
|
75817
|
+
style: {
|
|
75818
|
+
...rest.style,
|
|
75819
|
+
...(duration ? {
|
|
75820
|
+
"--step-list-duration": duration
|
|
75821
|
+
} : undefined)
|
|
75822
|
+
},
|
|
75823
|
+
children: [jsx(StepListContext.Provider, {
|
|
75824
|
+
value: registry,
|
|
75825
|
+
children: children
|
|
75826
|
+
}), width > 0 && stepCount > 0 ? jsxs(Fragment$1, {
|
|
75519
75827
|
children: [renderRail(false), renderRail(true), currentIndex !== -1 && dotXs[currentIndex] !== undefined ? jsx("svg", {
|
|
75520
75828
|
className: "navi_step_list_rail",
|
|
75521
75829
|
width: width,
|
|
@@ -75523,8 +75831,12 @@ const StepList = ({
|
|
|
75523
75831
|
viewBox: `0 0 ${width} ${RAIL_H}`,
|
|
75524
75832
|
"aria-hidden": "true",
|
|
75525
75833
|
children: jsx("g", {
|
|
75526
|
-
className: "navi_step_list_marker"
|
|
75527
|
-
|
|
75834
|
+
className: "navi_step_list_marker"
|
|
75835
|
+
// Connected to slides, the position comes from the CSS calc
|
|
75836
|
+
// above — an inline transform would override it.
|
|
75837
|
+
,
|
|
75838
|
+
|
|
75839
|
+
style: slideContainer ? undefined : {
|
|
75528
75840
|
transform: `translateX(${dotXs[currentIndex]}px)`
|
|
75529
75841
|
},
|
|
75530
75842
|
children: jsx("circle", {
|
|
@@ -75533,15 +75845,7 @@ const StepList = ({
|
|
|
75533
75845
|
r: RING_R
|
|
75534
75846
|
})
|
|
75535
75847
|
})
|
|
75536
|
-
}) : null,
|
|
75537
|
-
const value = valueOf(stepVNode, index);
|
|
75538
|
-
// Whatever else the Item was given reaches its button — a
|
|
75539
|
-
// pseudoState held for a demo, an aria attribute.
|
|
75540
|
-
const itemRest = {
|
|
75541
|
-
...stepVNode.props
|
|
75542
|
-
};
|
|
75543
|
-
delete itemRest.value;
|
|
75544
|
-
delete itemRest.children;
|
|
75848
|
+
}) : null, steps.map((step, index) => {
|
|
75545
75849
|
// The slots tile the row, cut at the container's edges: the
|
|
75546
75850
|
// first and the last cover only the inner half of the room an
|
|
75547
75851
|
// interior slot gets, so pressing just outside the box presses
|
|
@@ -75557,43 +75861,119 @@ const StepList = ({
|
|
|
75557
75861
|
"width": `${slotRight - slotLeft}px`,
|
|
75558
75862
|
"--step-dot-x": `${dotXs[index] - slotLeft}px`
|
|
75559
75863
|
},
|
|
75560
|
-
children:
|
|
75561
|
-
...
|
|
75562
|
-
|
|
75864
|
+
children: jsxs(Button, {
|
|
75865
|
+
...step.buttonProps,
|
|
75866
|
+
// bare, not discrete: what is drawn IS the dot and its
|
|
75867
|
+
// label — the hover wash a discrete button paints over its
|
|
75868
|
+
// whole surface is exactly what must not appear here (the
|
|
75869
|
+
// feedback is the circle over the dot, see the css).
|
|
75870
|
+
variant: "bare",
|
|
75563
75871
|
className: "navi_step_list_step",
|
|
75564
75872
|
"aria-current": index === currentIndex ? "step" : undefined,
|
|
75565
75873
|
"data-current": index === currentIndex ? "" : undefined,
|
|
75566
|
-
readOnly: !
|
|
75567
|
-
|
|
75568
|
-
|
|
75569
|
-
|
|
75570
|
-
|
|
75874
|
+
readOnly: !travelByClick
|
|
75875
|
+
// Towards the slides when connected, by name: the command
|
|
75876
|
+
// reaches the container wherever this list sits on the
|
|
75877
|
+
// page. What else a press should do is the Item's own
|
|
75878
|
+
// onClick, which arrived through buttonProps.
|
|
75879
|
+
,
|
|
75880
|
+
|
|
75881
|
+
command: slideContainer && travelByClick ? `--navi-go-to-slide:${step.value}` : undefined,
|
|
75882
|
+
commandFor: slideContainer
|
|
75883
|
+
// A message about a step (a callout) points at its circle,
|
|
75884
|
+
// above it: the label lives below.
|
|
75885
|
+
,
|
|
75886
|
+
|
|
75887
|
+
"data-callout-anchor": ".navi_step_list_dot",
|
|
75888
|
+
"data-callout-position": "top",
|
|
75889
|
+
children: [jsx("span", {
|
|
75890
|
+
className: "navi_step_list_dot",
|
|
75891
|
+
"aria-hidden": "true"
|
|
75892
|
+
}), jsx("span", {
|
|
75571
75893
|
className: "navi_step_list_label",
|
|
75572
|
-
children:
|
|
75573
|
-
})
|
|
75894
|
+
children: step.label
|
|
75895
|
+
})]
|
|
75574
75896
|
})
|
|
75575
|
-
}, value);
|
|
75897
|
+
}, step.value);
|
|
75576
75898
|
})]
|
|
75577
|
-
}) : null
|
|
75899
|
+
}) : null]
|
|
75578
75900
|
});
|
|
75579
75901
|
};
|
|
75580
75902
|
|
|
75903
|
+
// The same steps saying the same things: nothing to redraw. The labels are
|
|
75904
|
+
// vnodes, fresh objects on every render — comparing them would always say
|
|
75905
|
+
// "changed", so they are left out: what they show changes through the state
|
|
75906
|
+
// it came from, which re-renders this list anyway.
|
|
75907
|
+
const sameSteps = (previousSteps, nextSteps) => {
|
|
75908
|
+
if (previousSteps.length !== nextSteps.length) {
|
|
75909
|
+
return false;
|
|
75910
|
+
}
|
|
75911
|
+
let index = 0;
|
|
75912
|
+
while (index < previousSteps.length) {
|
|
75913
|
+
const previous = previousSteps[index];
|
|
75914
|
+
const next = nextSteps[index];
|
|
75915
|
+
if (previous.value !== next.value || previous.done !== next.done) {
|
|
75916
|
+
return false;
|
|
75917
|
+
}
|
|
75918
|
+
if (!sameShallow(previous.buttonProps, next.buttonProps)) {
|
|
75919
|
+
return false;
|
|
75920
|
+
}
|
|
75921
|
+
index++;
|
|
75922
|
+
}
|
|
75923
|
+
return true;
|
|
75924
|
+
};
|
|
75925
|
+
const sameShallow = (previousObject, nextObject) => {
|
|
75926
|
+
const previousKeys = Object.keys(previousObject);
|
|
75927
|
+
const nextKeys = Object.keys(nextObject);
|
|
75928
|
+
if (previousKeys.length !== nextKeys.length) {
|
|
75929
|
+
return false;
|
|
75930
|
+
}
|
|
75931
|
+
for (const key of previousKeys) {
|
|
75932
|
+
if (previousObject[key] !== nextObject[key]) {
|
|
75933
|
+
return false;
|
|
75934
|
+
}
|
|
75935
|
+
}
|
|
75936
|
+
return true;
|
|
75937
|
+
};
|
|
75938
|
+
|
|
75581
75939
|
/**
|
|
75582
|
-
* One step of the walk: `value` names it (what `current
|
|
75583
|
-
*
|
|
75584
|
-
*
|
|
75940
|
+
* One step of the walk: `value` names it (what `current` says and what a
|
|
75941
|
+
* press reports), `done` says it is answered (its dot fills, and the path is
|
|
75942
|
+
* deduced from the answered steps), the children are its label.
|
|
75943
|
+
*
|
|
75944
|
+
* It renders NOTHING: it registers with the list around it as it renders,
|
|
75945
|
+
* and the list draws everything — the dot, the label, the button. Whatever
|
|
75946
|
+
* else it carries (onClick, pseudoState, aria-*) lands on that button.
|
|
75585
75947
|
*
|
|
75586
75948
|
* It is both StepList.Item and an export of its own, the way Slide is to
|
|
75587
75949
|
* SlideContainer.
|
|
75588
75950
|
*
|
|
75589
75951
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
75590
75952
|
* value: string,
|
|
75953
|
+
* done?: boolean,
|
|
75591
75954
|
* [key: string]: any,
|
|
75592
75955
|
* }>}
|
|
75956
|
+
* @param {boolean} [done] - this step is answered: its dot is filled — the
|
|
75957
|
+
* fill's one meaning. Steps answered out of order are filled dots
|
|
75958
|
+
* standing alone, dashed segments around them.
|
|
75593
75959
|
*/
|
|
75594
75960
|
const Step = ({
|
|
75595
|
-
|
|
75596
|
-
|
|
75961
|
+
value,
|
|
75962
|
+
done,
|
|
75963
|
+
children,
|
|
75964
|
+
...buttonProps
|
|
75965
|
+
}) => {
|
|
75966
|
+
const registry = useContext(StepListContext);
|
|
75967
|
+
if (registry) {
|
|
75968
|
+
registry.renderedSteps.push({
|
|
75969
|
+
value: value ?? String(registry.renderedSteps.length),
|
|
75970
|
+
done: Boolean(done),
|
|
75971
|
+
label: children,
|
|
75972
|
+
buttonProps
|
|
75973
|
+
});
|
|
75974
|
+
}
|
|
75975
|
+
return null;
|
|
75976
|
+
};
|
|
75597
75977
|
StepList.Item = Step;
|
|
75598
75978
|
|
|
75599
75979
|
installImportMetaCssBuild(import.meta);const css$1 = /* css */`
|