@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.
@@ -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$14 = /* 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$14, "@jsenv/navi/src/navi_z_indexes.js"];
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$13 = /* 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$13, "@jsenv/navi/src/nav/url_target/url_target.js"];
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$12 = /* 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$12, "@jsenv/navi/src/control/rules/callout/callout.js"];
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
- !field.optimistic &&
8017
- boundAction &&
8018
- boundAction.runningStateSignal.value === RUNNING
8019
- ) {
8020
- return true;
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$11 = /* 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$11, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
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$10 = /* 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$10, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
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$$ = /* 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$$, "@jsenv/navi/src/text/text.jsx"];
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
- // For radio/checkbox: auto-trigger the parent group's action after the
27327
- // leaf action completes. The parent (radio_group/checkbox_group) has
27328
- // already aggregated the new state by now, so uiStateSignal is correct.
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: originalEvent,
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$_ = /* 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$_, "@jsenv/navi/src/control/input/button_ui.jsx"];
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$Z = /* 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$Z, "@jsenv/navi/src/layout/dialog.jsx"];
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$Y = /* 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$Y, "@jsenv/navi/src/layout/popover.jsx"];
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$X = /* 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$X, "@jsenv/navi/src/action/confirm_popup.jsx"];
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$W = /* 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$W, "@jsenv/navi/src/action/action_renderer.jsx"];
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$V = /* 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$V, "@jsenv/navi/src/nav/route_transition.jsx"];
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$V, "@jsenv/navi/src/nav/route_transition.jsx"];
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$V, "@jsenv/navi/src/nav/route_transition.jsx"];
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$U = /* 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$U, "@jsenv/navi/src/nav/route_travel.jsx"];
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$T = /* 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$T, "@jsenv/navi/src/text/text_anchor.jsx"];
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$S = /* 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$S, "@jsenv/navi/src/text/icon.jsx"];
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$R = /* 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$R, "@jsenv/navi/src/nav/link/link.jsx"];
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$Q = /* 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$Q, "@jsenv/navi/src/nav/link/nav.jsx"];
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$P = /* 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$P, "@jsenv/navi/src/nav/binder/binder.jsx"];
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$O = /* 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$O, "@jsenv/navi/src/layout/fixed_bar/fixed_bar.jsx"];
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$N = /* 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$N;
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$M = /* 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$M, "@jsenv/navi/src/control/details/summary_marker.jsx"];
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$L = /* 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$L, "@jsenv/navi/src/control/details/details.jsx"];
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$K = /* 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$K, "@jsenv/navi/src/control/expandable/expandable.jsx"];
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$J = /* 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$J, "@jsenv/navi/src/control/input/switch_ui.jsx"];
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$I = /* 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$I, "@jsenv/navi/src/control/input/input_checkbox.jsx"];
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$H = /* 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$H, "@jsenv/navi/src/control/field.jsx"];
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$H, "@jsenv/navi/src/control/field.jsx"];
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$H, "@jsenv/navi/src/control/field.jsx"];
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$G = /* 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$G, "@jsenv/navi/src/control/input/input_radio.jsx"];
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$F = /* 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$F, "@jsenv/navi/src/control/input/input_range.jsx"];
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$E = /* 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$E, "@jsenv/navi/src/control/edition/editable.jsx"];
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$D = /* 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$D, "@jsenv/navi/src/control/group.jsx"];
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$C = /* 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$C, "@jsenv/navi/src/layout/slide_container.jsx"];
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$B = /* 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$B, "@jsenv/navi/src/layout/popup.jsx"];
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$A = /* 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$A, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
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$z = /* 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$z, "@jsenv/navi/src/layout/separator.jsx"];
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$y = /* 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$y, "@jsenv/navi/src/control/list/list_selectable.jsx"];
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$x = /* 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$x, "@jsenv/navi/src/control/list/list.jsx"];
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$w = /* 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$w, "@jsenv/navi/src/text/badge.jsx"];
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$v = /* 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$v, "@jsenv/navi/src/text/badge_list.jsx"];
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$u = /* 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$u, "@jsenv/navi/src/text/color.jsx"];
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$t = /* 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$t, "@jsenv/navi/src/control/picker/picker.jsx"];
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$s = /* 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$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
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$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
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$r = /* 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$r, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
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$q = /* 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$q, "@jsenv/navi/src/control/input/textarea.jsx"];
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$q, "@jsenv/navi/src/control/input/textarea.jsx"];
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$p = /* 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$p, "@jsenv/navi/src/control/input/input_duration.jsx"];
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$o = /* 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$o, "@jsenv/navi/src/control/input/radio_group.jsx"];
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$n = /* 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$n, "@jsenv/navi/src/control/input/select.jsx"];
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$m = /* 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$m, "@jsenv/navi/src/control/input/split_button.jsx"];
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$l = /* 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$l, "@jsenv/navi/src/control/wheel/wheel.jsx"];
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$l, "@jsenv/navi/src/control/wheel/wheel.jsx"];
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
- controlType: "control_group",
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 — the bounds push each other while they turn, so what the wheels show
69369
- * is always a span. The send-time constraint stays underneath for what pushing
69370
- * cannot fix (a start so late the span no longer fits in the day).
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 while it is being turned: the bound that just moved is
69543
- // the one the user is holding, so it stays where it was put and the OTHER one
69544
- // gives way. A refusal at the end of the gesture would leave the person to
69545
- // undo what they just did.
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
- uiAction: (value, e) => keepBoundsApart("start", value, e),
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
- uiAction: (value, e) => keepBoundsApart("end", value, e)
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 (`reached`): how far the steps are answered without a gap. A
75251
- * solid line runs from the first dot to the step it names, and those dots
75252
- * are filled; past it the line is dashed the road not walked yet. It
75253
- * moves when a step is answered, not when one merely looks around.
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 be
75256
- * AHEAD of the path (browsing step 3 while step 1 still misses an answer)
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
- * The steps are the CHILDREN <StepList.Item value="club">Club</StepList.Item>
75271
- * read off the vnodes (toChildArray, so a .map() or a fragment is fine; a
75272
- * component of your own wrapping an Item is not seen). The Item renders its
75273
- * label; everything positional is this component's business.
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, in one place: accent is the path, muted is what the path
75278
- has not reached, on-accent writes on filled dots. --step-list-path-line
75279
- exists apart from the accent for a dark band where the walked line
75280
- reads better plain white. */
75281
- --step-list-accent: #4f8ef7;
75282
- --step-list-on-accent: white;
75283
- --step-list-muted: light-dark(#8a93a8, #8b99b8);
75284
- --step-list-line: light-dark(#c9d0dd, rgba(255, 255, 255, 0.35));
75285
- --step-list-current-color: light-dark(#1c2433, white);
75286
- --step-list-path-line: var(--step-list-accent);
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 path: same drawing, filled, revealed up to the step it has come to.
75315
- The clip is set inline (a width in px); transitioning it is what makes
75316
- an answered step SWEEP the line and the next dot rather than pop. */
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 300ms ease;
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-path-line);
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 300ms ease;
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 (::before), plus the label brightening.
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 discrete variant declares its own hover background
75356
- var, and navi's stylesheet is injected after this one specificity is
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
- --button-background-color: transparent;
75369
- --button-background-color-hover: transparent;
75370
- --button-background-color-readonly: transparent;
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
- .navi_step_list_step::before {
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
- content: "";
75544
+ pointer-events: none;
75383
75545
  }
75384
- .navi_step_list_step:hover::before,
75385
- .navi_step_list_step[data-hover]::before {
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::before,
75393
- .navi_step_list_step[data-focus-visible]::before {
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
- * reached?: string,
75425
- * onStepPress?: (value: string, event: Event) => void,
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
- * @param {string} [reached] - the step the path has come to: the line is
75432
- * solid and the dots filled up to it, dashed past it. Omit for a path
75433
- * that has not started.
75434
- * @param {(value: string, event: Event) => void} [onStepPress] - a step was
75435
- * pressed. Without it the steps are read-only shown, not offered.
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
- reached,
75440
- onStepPress,
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, read off the children: each <StepList.Item> vnode says which
75464
- // step it is (value) and is rendered as the label under its dot.
75465
- const stepVNodes = toChildArray(children).filter(child => child && child.props);
75466
- const stepCount = stepVNodes.length;
75467
- const valueOf = (vnode, index) => vnode.props.value ?? String(index);
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 => stepVNodes.findIndex((vnode, index) => valueOf(vnode, index) === value);
75478
- const currentIndex = current === undefined ? -1 : indexOf(current);
75479
- const reachedIndex = reached === undefined ? -1 : indexOf(reached);
75480
- // Covers the reached dot entirely (radius plus stroke), and nothing when
75481
- // the path has not started.
75482
- const fillX = reachedIndex === -1 ? 0 : dotXs[reachedIndex] + DOT_R + 3;
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
- }, valueOf(stepVNodes[index], index)))
75804
+ }, steps[index].value))
75512
75805
  });
75513
- return jsx(Box, {
75806
+ return jsxs(Box, {
75514
75807
  ...rest,
75515
75808
  ref: rootRef,
75516
75809
  baseClassName: "navi_step_list",
75517
- "data-step-list": "",
75518
- children: width > 0 && stepCount > 0 ? jsxs(Fragment$1, {
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
- style: {
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, stepVNodes.map((stepVNode, index) => {
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: jsx(Button, {
75561
- ...itemRest,
75562
- variant: "discrete",
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: !onStepPress,
75567
- onClick: onStepPress ? e => {
75568
- onStepPress(value, e);
75569
- } : undefined,
75570
- children: jsx("span", {
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: stepVNode
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`/`reached` say and
75583
- * what a press reports), the children are its label. Rendered under its dot;
75584
- * where the dot is, and what state it shows, is the StepList's business.
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
- children
75596
- }) => children;
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 */`