@jsenv/navi 0.29.97 → 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,
@@ -27333,18 +27343,31 @@ const useInteractiveProps = (props, {
27333
27343
  controlRootProps.onnavi_action_end?.(e);
27334
27344
  uiStateController.onActionEnd(e);
27335
27345
 
27336
- // For radio/checkbox: auto-trigger the parent group's action after the
27337
- // leaf action completes. The parent (radio_group/checkbox_group) has
27338
- // 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.
27339
27353
  const parentController = uiStateController.parentUIStateController;
27340
- 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")) {
27341
27355
  const parentEl = parentController.ref.current;
27342
27356
  if (parentEl) {
27343
- const originalEvent = e.detail.eventChain[0];
27344
27357
  dispatchRequestAction(parentEl, {
27345
- event: originalEvent,
27358
+ event: e.detail.eventChain[0],
27346
27359
  name: "auto_group_action",
27347
- 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
27348
27371
  });
27349
27372
  }
27350
27373
  }
@@ -27406,7 +27429,7 @@ const getAssociatedLabels = element => {
27406
27429
  return Array.from(element.labels);
27407
27430
  };
27408
27431
 
27409
- installImportMetaCssBuild(import.meta);const css$_ = /* css */`
27432
+ installImportMetaCssBuild(import.meta);const css$$ = /* css */`
27410
27433
  @layer navi {
27411
27434
  .navi_button {
27412
27435
  --button-border-radius: var(--navi-control-border-radius);
@@ -27837,7 +27860,7 @@ installImportMetaCssBuild(import.meta);const css$_ = /* css */`
27837
27860
  }
27838
27861
  `;
27839
27862
  const ButtonUI = props => {
27840
- 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"];
27841
27864
  const {
27842
27865
  ref,
27843
27866
  // href/link
@@ -29548,7 +29571,7 @@ installImportMetaCssBuild(import.meta);/**
29548
29571
  * reaches the real container.
29549
29572
  */
29550
29573
  let openLocalDialogCount = 0;
29551
- const css$Z = /* css */`
29574
+ const css$_ = /* css */`
29552
29575
  @layer navi {
29553
29576
  .navi_dialog {
29554
29577
  /* Min gap between the dialog and the edges of its container. Written
@@ -30132,7 +30155,7 @@ const css$Z = /* css */`
30132
30155
  * @param {import("ignore:preact").ComponentChildren} props.children
30133
30156
  */
30134
30157
  const Dialog = props => {
30135
- import.meta.css = [css$Z, "@jsenv/navi/src/layout/dialog.jsx"];
30158
+ import.meta.css = [css$_, "@jsenv/navi/src/layout/dialog.jsx"];
30136
30159
  if (props.openController) {
30137
30160
  return jsx(ControlledDialog, {
30138
30161
  ...props
@@ -31124,7 +31147,7 @@ installImportMetaCssBuild(import.meta);/**
31124
31147
  * and applied.
31125
31148
  */
31126
31149
  let openLocalPopoverCount = 0;
31127
- const css$Y = /* css */`
31150
+ const css$Z = /* css */`
31128
31151
  @layer navi {
31129
31152
  .navi_popover {
31130
31153
  /* soft: user-configurable preferred max-height. Kept as a *default*
@@ -31580,7 +31603,7 @@ const css$Y = /* css */`
31580
31603
  * @param {import("ignore:preact").ComponentChildren} props.children
31581
31604
  */
31582
31605
  const Popover = props => {
31583
- import.meta.css = [css$Y, "@jsenv/navi/src/layout/popover.jsx"];
31606
+ import.meta.css = [css$Z, "@jsenv/navi/src/layout/popover.jsx"];
31584
31607
  if (props.openController) {
31585
31608
  return jsx(ControlledPopover, {
31586
31609
  ...props
@@ -32608,7 +32631,7 @@ installImportMetaCssBuild(import.meta);/**
32608
32631
  * event, and a caller replacing the body entirely then has one protocol to
32609
32632
  * follow — `--navi-confirm` for yes, anything that closes for no.
32610
32633
  */
32611
- const css$X = /* css */`
32634
+ const css$Y = /* css */`
32612
32635
  /* The width lives on the body rather than on the popup, so that custom
32613
32636
  content (which replaces this body entirely) sizes itself instead of
32614
32637
  inheriting a ceiling meant for a sentence-long question. */
@@ -32745,7 +32768,7 @@ const ConfirmPopup = ({
32745
32768
  onAnswer,
32746
32769
  onClosed
32747
32770
  }) => {
32748
- 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"];
32749
32772
  const {
32750
32773
  mode,
32751
32774
  confirmLabel,
@@ -32829,7 +32852,7 @@ const defaultBody = (message, {
32829
32852
  });
32830
32853
  };
32831
32854
 
32832
- installImportMetaCssBuild(import.meta);const css$W = /* css */`
32855
+ installImportMetaCssBuild(import.meta);const css$X = /* css */`
32833
32856
  .action_error {
32834
32857
  margin-top: 0;
32835
32858
  margin-bottom: 20px;
@@ -32854,7 +32877,7 @@ const ActionRenderer = ({
32854
32877
  children,
32855
32878
  disabled
32856
32879
  }) => {
32857
- 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"];
32858
32881
  if (action === undefined) {
32859
32882
  throw new Error("ActionRenderer requires an action to render, but none was provided.");
32860
32883
  }
@@ -38598,7 +38621,7 @@ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
38598
38621
  // the root pictures must NOT move (they carry the whole viewport, blank bands
38599
38622
  // included).
38600
38623
 
38601
- const css$V = /* css */`
38624
+ const css$W = /* css */`
38602
38625
  /* The marked region is a picture of its own for the length of a transition of
38603
38626
  OURS, and only then — the name is what makes the pages a picture the
38604
38627
  movement below can carry.
@@ -39027,7 +39050,7 @@ const RouteTransitionArea = ({
39027
39050
  children,
39028
39051
  ...rest
39029
39052
  }) => {
39030
- 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"];
39031
39054
  const props = {
39032
39055
  ...rest,
39033
39056
  [TRANSITION_AREA_ATTRIBUTE]: ""
@@ -39084,7 +39107,7 @@ const RouteTransitionArea = ({
39084
39107
  * @returns {() => void} remove this relation.
39085
39108
  */
39086
39109
  const defineRouteTransition = (from, to, transition) => {
39087
- 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"];
39088
39111
  const {
39089
39112
  type,
39090
39113
  duration
@@ -39120,7 +39143,7 @@ const defineRouteTransition = (from, to, transition) => {
39120
39143
  * @returns {() => void} remove this default.
39121
39144
  */
39122
39145
  const defineRouteDefaultTransition = transition => {
39123
- 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"];
39124
39147
  const value = normalizeTransition(transition);
39125
39148
  defaultTransition = value;
39126
39149
  return () => {
@@ -39721,7 +39744,7 @@ const DRAGGED_ATTRIBUTE = "data-navi-route-travel-dragged";
39721
39744
  const TURNED_ATTRIBUTE = "data-navi-route-travel-turned";
39722
39745
  // The name the box wears while it travels, and only then (see nameForTravel).
39723
39746
  const TRAVEL_NAME = "navi-route-travel";
39724
- const css$U = /* css */`
39747
+ const css$V = /* css */`
39725
39748
  /* The name that makes the page inside this box a picture of its own during a
39726
39749
  transition — rather than part of the one big picture the document takes, so
39727
39750
  the two pages can move past each other while everything else stays where it
@@ -40111,7 +40134,7 @@ const RouteTravel = ({
40111
40134
  children,
40112
40135
  ...rest
40113
40136
  }) => {
40114
- 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"];
40115
40138
  const elementRef = useRef();
40116
40139
  const gestureRef = useRef(null);
40117
40140
  // The travel in hand: the transition keeping the picture of the page being
@@ -42673,7 +42696,7 @@ const PhoneSvg = () => {
42673
42696
  };
42674
42697
 
42675
42698
  installImportMetaCssBuild(import.meta);// # TextAnchor — how it works
42676
- const css$T = /* css */`
42699
+ const css$U = /* css */`
42677
42700
  .navi_text_anchor {
42678
42701
  vertical-align: baseline;
42679
42702
  user-select: none;
@@ -42708,7 +42731,7 @@ const TextAnchor = ({
42708
42731
  textSize,
42709
42732
  lineLayout
42710
42733
  }) => {
42711
- 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"];
42712
42735
  const anchorRef = useRef();
42713
42736
 
42714
42737
  // Plain useLayoutEffect would also fire while an ancestor dialog/popover
@@ -42823,7 +42846,7 @@ const computeTopOffset = ({
42823
42846
  };
42824
42847
  const charTopCanvas = document.createElement("canvas");
42825
42848
 
42826
- installImportMetaCssBuild(import.meta);const css$S = /* css */`
42849
+ installImportMetaCssBuild(import.meta);const css$T = /* css */`
42827
42850
  @layer navi {
42828
42851
  /* Ensure data attributes from box.jsx can win to update display */
42829
42852
  .navi_icon {
@@ -42981,7 +43004,7 @@ const Icon = ({
42981
43004
  fillLine,
42982
43005
  ...props
42983
43006
  }) => {
42984
- import.meta.css = [css$S, "@jsenv/navi/src/text/icon.jsx"];
43007
+ import.meta.css = [css$T, "@jsenv/navi/src/text/icon.jsx"];
42985
43008
  const innerChildren = href ? jsx("svg", {
42986
43009
  width: "100%",
42987
43010
  height: "100%",
@@ -43143,7 +43166,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
43143
43166
  });
43144
43167
  };
43145
43168
 
43146
- installImportMetaCssBuild(import.meta);const css$R = /* css */`
43169
+ installImportMetaCssBuild(import.meta);const css$S = /* css */`
43147
43170
  @layer navi {
43148
43171
  .navi_link {
43149
43172
  --link-border-radius: unset;
@@ -43605,7 +43628,7 @@ Object.assign(PSEUDO_CLASSES, {
43605
43628
  * @param {boolean} [props.readOnly]
43606
43629
  */
43607
43630
  const Link = props => {
43608
- 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"];
43609
43632
  if (props.route) {
43610
43633
  return jsx(LinkWithRoute, {
43611
43634
  ...props
@@ -43928,7 +43951,7 @@ installImportMetaCssBuild(import.meta);/**
43928
43951
  * https://dribbble.com/search/tabs
43929
43952
  */
43930
43953
  let navCount = 0;
43931
- const css$Q = /* css */`
43954
+ const css$R = /* css */`
43932
43955
  @layer navi {
43933
43956
  .navi_nav {
43934
43957
  --nav-border: none;
@@ -44211,7 +44234,7 @@ const Nav = ({
44211
44234
  slideContainer,
44212
44235
  ...props
44213
44236
  }) => {
44214
- 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"];
44215
44238
  const defaultRef = useRef();
44216
44239
  props.ref = props.ref || defaultRef;
44217
44240
  const navRef = props.ref;
@@ -44738,7 +44761,7 @@ installImportMetaCssBuild(import.meta);/**
44738
44761
  * Border width participates in layout (it is added to the tab and page
44739
44762
  * padding): a thick border grows the binder rather than eating into the text.
44740
44763
  */
44741
- const css$P = /* css */`
44764
+ const css$Q = /* css */`
44742
44765
  @layer navi {
44743
44766
  .navi_binder {
44744
44767
  --binder-border-width: var(--navi-control-border-width);
@@ -45051,7 +45074,7 @@ const Binder = ({
45051
45074
  pagePadding,
45052
45075
  ...props
45053
45076
  }) => {
45054
- 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"];
45055
45078
  const items = toChildArray(children).map((child, index) => {
45056
45079
  const {
45057
45080
  value: itemValue,
@@ -45538,7 +45561,7 @@ installImportMetaCssBuild(import.meta);/**
45538
45561
  * added to the size asked for exactly like the notch inset is, so the
45539
45562
  * content still gets the size the prop names.
45540
45563
  */
45541
- const css$O = /* css */`
45564
+ const css$P = /* css */`
45542
45565
  @layer navi {
45543
45566
  :root {
45544
45567
  --navi-fixed-bar-width: 56px;
@@ -45686,7 +45709,7 @@ const FixedBar = ({
45686
45709
  border = true,
45687
45710
  ...props
45688
45711
  }) => {
45689
- 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"];
45690
45713
  const defaultRef = useRef();
45691
45714
  props.ref = props.ref || defaultRef;
45692
45715
  // Said with the width the border rule reads rather than with an attribute of
@@ -45780,7 +45803,7 @@ const FixedBar = ({
45780
45803
  // Subpixel layout rounds rectangles up on boxes that fit exactly.
45781
45804
  const OVERFLOW_TOLERANCE = 1;
45782
45805
 
45783
- const css$N = /* css */ `
45806
+ const css$O = /* css */ `
45784
45807
  [data-navi-overflow-x] {
45785
45808
  outline: 2px dashed #e74c3c;
45786
45809
  outline-offset: -2px;
@@ -45804,7 +45827,7 @@ const detectHorizontalOverflow = ({
45804
45827
  let styleEl = null;
45805
45828
  if (highlight) {
45806
45829
  styleEl = document.createElement("style");
45807
- styleEl.textContent = css$N;
45830
+ styleEl.textContent = css$O;
45808
45831
  document.head.appendChild(styleEl);
45809
45832
  }
45810
45833
 
@@ -45959,7 +45982,7 @@ const useFocusGroup = (
45959
45982
  };
45960
45983
 
45961
45984
  installImportMetaCssBuild(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
45962
- const css$M = /* css */`
45985
+ const css$N = /* css */`
45963
45986
  .navi_summary_marker {
45964
45987
  width: 1em;
45965
45988
  height: 1em;
@@ -46049,7 +46072,7 @@ const SummaryMarker = ({
46049
46072
  loading,
46050
46073
  openDirection = "down"
46051
46074
  }) => {
46052
- 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"];
46053
46076
  const showLoading = useDebounceTrue(loading, 300);
46054
46077
  return jsx("span", {
46055
46078
  className: "navi_summary_marker",
@@ -46094,7 +46117,7 @@ const SummaryMarker = ({
46094
46117
  });
46095
46118
  };
46096
46119
 
46097
- installImportMetaCssBuild(import.meta);const css$L = /* css */`
46120
+ installImportMetaCssBuild(import.meta);const css$M = /* css */`
46098
46121
  .navi_details {
46099
46122
  position: relative;
46100
46123
  z-index: 1;
@@ -46140,7 +46163,7 @@ const Details = props => {
46140
46163
  return details;
46141
46164
  };
46142
46165
  const DetailsField = props => {
46143
- 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"];
46144
46167
  const {
46145
46168
  ref,
46146
46169
  persists,
@@ -46360,7 +46383,7 @@ installImportMetaCssBuild(import.meta);/**
46360
46383
  * UI when it comes first). Once settled open the clipping is released, so a
46361
46384
  * popover or focus ring inside is not cut at the edges.
46362
46385
  */
46363
- const css$K = /* css */`
46386
+ const css$L = /* css */`
46364
46387
  .navi_expandable {
46365
46388
  position: relative;
46366
46389
  display: flex;
@@ -46586,7 +46609,7 @@ const useExpandableContext = partName => {
46586
46609
  * and rebuilds it from scratch on every expansion.
46587
46610
  */
46588
46611
  const Expandable = props => {
46589
- 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"];
46590
46613
  const {
46591
46614
  ref,
46592
46615
  ui,
@@ -47251,7 +47274,7 @@ const ControlGroup = props => {
47251
47274
  };
47252
47275
  const CONTROL_GROUP_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
47253
47276
 
47254
- installImportMetaCssBuild(import.meta);const css$J = /* css */`
47277
+ installImportMetaCssBuild(import.meta);const css$K = /* css */`
47255
47278
  @layer navi {
47256
47279
  .navi_checkbox {
47257
47280
  --switch-margin: 0; /* Useful to reserve space for outline */
@@ -47329,7 +47352,7 @@ installImportMetaCssBuild(import.meta);const css$J = /* css */`
47329
47352
  }
47330
47353
  `;
47331
47354
  const SwitchUI = () => {
47332
- 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"];
47333
47356
  return jsx(Box, {
47334
47357
  className: "navi_switch",
47335
47358
  as: "svg",
@@ -47371,7 +47394,7 @@ const useCheckableProps = (props, options) => {
47371
47394
  return result;
47372
47395
  };
47373
47396
 
47374
- installImportMetaCssBuild(import.meta);const css$I = /* css */`
47397
+ installImportMetaCssBuild(import.meta);const css$J = /* css */`
47375
47398
  @layer navi {
47376
47399
  .navi_checkbox {
47377
47400
  --border-radius: var(--navi-checkbox-border-radius);
@@ -47698,7 +47721,7 @@ const InputCheckboxHeadless = props => {
47698
47721
  });
47699
47722
  };
47700
47723
  const InputCheckboxFieldInterface = props => {
47701
- 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"];
47702
47725
  const [checkboxRootProps, checkboxHostProps] = useCheckableProps(props);
47703
47726
  const {
47704
47727
  icon,
@@ -47820,7 +47843,7 @@ const CheckboxButtonStyleCSSVars = {
47820
47843
  const CheckboxPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
47821
47844
  const CheckboxPseudoElements = ["::-navi-loader", "::-navi-checkmark"];
47822
47845
 
47823
- installImportMetaCssBuild(import.meta);const css$H = /* css */`
47846
+ installImportMetaCssBuild(import.meta);const css$I = /* css */`
47824
47847
  @layer navi {
47825
47848
  .navi_label {
47826
47849
  --label-required-indicator-color: var(--navi-color-danger, #b42318);
@@ -47900,7 +47923,7 @@ installImportMetaCssBuild(import.meta);const css$H = /* css */`
47900
47923
  * </Field>
47901
47924
  */
47902
47925
  const Field = props => {
47903
- import.meta.css = [css$H, "@jsenv/navi/src/control/field.jsx"];
47926
+ import.meta.css = [css$I, "@jsenv/navi/src/control/field.jsx"];
47904
47927
  const refDefault = useRef();
47905
47928
  props.ref = props.ref || refDefault;
47906
47929
  const {
@@ -47935,7 +47958,7 @@ const FieldCSSVars = {
47935
47958
  spacingWithControl: "--spacing-with-control"
47936
47959
  };
47937
47960
  const FieldAsContainer = props => {
47938
- import.meta.css = [css$H, "@jsenv/navi/src/control/field.jsx"];
47961
+ import.meta.css = [css$I, "@jsenv/navi/src/control/field.jsx"];
47939
47962
  const {
47940
47963
  children
47941
47964
  } = props;
@@ -47967,7 +47990,7 @@ const FieldAsContainer = props => {
47967
47990
  };
47968
47991
  const FIELD_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
47969
47992
  const Label = props => {
47970
- import.meta.css = [css$H, "@jsenv/navi/src/control/field.jsx"];
47993
+ import.meta.css = [css$I, "@jsenv/navi/src/control/field.jsx"];
47971
47994
  const {
47972
47995
  children,
47973
47996
  // Marks the label when its control is required. Takes what to show, or
@@ -48129,7 +48152,7 @@ const InputSlot = ({
48129
48152
  });
48130
48153
  };
48131
48154
 
48132
- installImportMetaCssBuild(import.meta);const css$G = /* css */`
48155
+ installImportMetaCssBuild(import.meta);const css$H = /* css */`
48133
48156
  @layer navi {
48134
48157
  .navi_radio {
48135
48158
  --margin: 3px 3px 3px 5px;
@@ -48492,7 +48515,7 @@ const InputRadioHeadless = props => {
48492
48515
  };
48493
48516
  const VARIANT_SET = new Set(["icon", "button", "radio"]);
48494
48517
  const InputRadioFieldInterface = props => {
48495
- 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"];
48496
48519
  const [radioRootProps, radioHostProps] = useCheckableProps(props);
48497
48520
  const {
48498
48521
  icon,
@@ -48638,7 +48661,7 @@ const RadioButtonStyleCSSVars = {
48638
48661
  const RadioPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
48639
48662
  const RadioPseudoElements = ["::-navi-loader", "::-navi-radiomark"];
48640
48663
 
48641
- installImportMetaCssBuild(import.meta);const css$F = /* css */`
48664
+ installImportMetaCssBuild(import.meta);const css$G = /* css */`
48642
48665
  @layer navi {
48643
48666
  .navi_input_range {
48644
48667
  --border-radius: 6px;
@@ -48893,7 +48916,7 @@ const InputRange = props => {
48893
48916
  });
48894
48917
  };
48895
48918
  const InputRangeFieldInterface = props => {
48896
- 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"];
48897
48920
  const {
48898
48921
  ref
48899
48922
  } = props;
@@ -51001,7 +51024,7 @@ installImportMetaCssBuild(import.meta);/**
51001
51024
  * This means an editable thing MUST have a parent with position relative that wraps the content and the eventual editable input
51002
51025
  *
51003
51026
  */
51004
- const css$E = /* css */`
51027
+ const css$F = /* css */`
51005
51028
  .navi_editable_wrapper {
51006
51029
  --inset-top: 0px;
51007
51030
  --inset-right: 0px;
@@ -51050,7 +51073,7 @@ const useEditionController = () => {
51050
51073
  };
51051
51074
  };
51052
51075
  const Editable = props => {
51053
- 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"];
51054
51077
  let {
51055
51078
  children,
51056
51079
  action,
@@ -51599,7 +51622,7 @@ installImportMetaCssBuild(import.meta);/**
51599
51622
  * meet are drawn once instead of twice, and only the outer corners stay
51600
51623
  * rounded. See docs/control_group.md.
51601
51624
  */
51602
- const css$D = /* css */`
51625
+ const css$E = /* css */`
51603
51626
  .navi_group {
51604
51627
  --group-border-width: var(--navi-control-border-width);
51605
51628
 
@@ -51715,7 +51738,7 @@ const Group = ({
51715
51738
  vertical = row,
51716
51739
  ...props
51717
51740
  }) => {
51718
- import.meta.css = [css$D, "@jsenv/navi/src/control/group.jsx"];
51741
+ import.meta.css = [css$E, "@jsenv/navi/src/control/group.jsx"];
51719
51742
  return jsx(Box, {
51720
51743
  baseClassName: "navi_group",
51721
51744
  "data-vertical": vertical ? "" : undefined
@@ -51881,7 +51904,7 @@ installImportMetaCssBuild(import.meta);/**
51881
51904
  * So: nothing scrollable between the cap and the slides (a shared [data-body]
51882
51905
  * around them IS a scroller, see box.jsx), and `overflow="auto"` on each Slide.
51883
51906
  */
51884
- const css$C = /* css */`
51907
+ const css$D = /* css */`
51885
51908
  /* Where the picture stands relative to the slide that is current, in boxes
51886
51909
  (see paintTravelProgress). Declared, so that it is a NUMBER the browser can
51887
51910
  interpolate: the trait an indicator draws has to travel with the slides,
@@ -52375,7 +52398,7 @@ const SlideContainer = ({
52375
52398
  children,
52376
52399
  ...rest
52377
52400
  }) => {
52378
- 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"];
52379
52402
  const debugFocus = useDebugFocus();
52380
52403
  const trackRef = useRef();
52381
52404
  // The box itself: it is what takes the keyboard when what is on screen holds
@@ -55220,7 +55243,7 @@ installImportMetaCssBuild(import.meta);/**
55220
55243
  * and only under its own `sizeFromAnchor`) pass through untouched via
55221
55244
  * `...rest` to whichever of Popover/Dialog actually renders.
55222
55245
  */
55223
- const css$B = /* css */`
55246
+ const css$C = /* css */`
55224
55247
  @layer navi {
55225
55248
  .navi_popup {
55226
55249
  --popup-border-radius: var(--navi-popup-border-radius);
@@ -55337,7 +55360,7 @@ const css$B = /* css */`
55337
55360
  * @param {import("ignore:preact").ComponentChildren} props.children
55338
55361
  */
55339
55362
  const Popup = props => {
55340
- import.meta.css = [css$B, "@jsenv/navi/src/layout/popup.jsx"];
55363
+ import.meta.css = [css$C, "@jsenv/navi/src/layout/popup.jsx"];
55341
55364
  const {
55342
55365
  mode: modeProp,
55343
55366
  maxWidth,
@@ -55398,7 +55421,7 @@ const Popup = props => {
55398
55421
  });
55399
55422
  };
55400
55423
 
55401
- installImportMetaCssBuild(import.meta);const css$A = /* css */`
55424
+ installImportMetaCssBuild(import.meta);const css$B = /* css */`
55402
55425
  .navi_picker {
55403
55426
  /* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
55404
55427
  overflow... are already handled correctly by Popup/Popover/Dialog
@@ -55526,7 +55549,7 @@ installImportMetaCssBuild(import.meta);const css$A = /* css */`
55526
55549
  }
55527
55550
  `;
55528
55551
  const PickerCustomResolver = props => {
55529
- 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"];
55530
55553
  if (props.children === undefined) {
55531
55554
  return jsx(PickerNative, {
55532
55555
  ...props
@@ -56320,7 +56343,7 @@ const LoadingIndicator = ({
56320
56343
  });
56321
56344
  };
56322
56345
 
56323
- installImportMetaCssBuild(import.meta);const css$z = /* css */`
56346
+ installImportMetaCssBuild(import.meta);const css$A = /* css */`
56324
56347
  @layer navi {
56325
56348
  .navi_separator {
56326
56349
  --size: 1px;
@@ -56398,7 +56421,7 @@ const Separator = ({
56398
56421
  style,
56399
56422
  ...props
56400
56423
  }) => {
56401
- import.meta.css = [css$z, "@jsenv/navi/src/layout/separator.jsx"];
56424
+ import.meta.css = [css$A, "@jsenv/navi/src/layout/separator.jsx"];
56402
56425
  return jsx(Box, {
56403
56426
  as: vertical ? "span" : "hr",
56404
56427
  ...props,
@@ -56891,7 +56914,7 @@ const ListItemFooter = props => {
56891
56914
  });
56892
56915
  };
56893
56916
 
56894
- installImportMetaCssBuild(import.meta);const css$y = /* css */`
56917
+ installImportMetaCssBuild(import.meta);const css$z = /* css */`
56895
56918
  @layer navi {
56896
56919
  .navi_list_container[navi-selectable] {
56897
56920
  /* Focus outline */
@@ -57103,7 +57126,7 @@ const ListSelectableResolver = props => {
57103
57126
  };
57104
57127
  const ListSelectable = props => {
57105
57128
  const Next = useNextResolver();
57106
- 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"];
57107
57130
  // we allow ourselves to auto-generate a name
57108
57131
  const defaultName = useId();
57109
57132
  props.name = props.name || `listbox_${defaultName}`;
@@ -57723,7 +57746,7 @@ const ListVirtualContext = createContext(null);
57723
57746
  // that returning a component of one's own — instead of a bare <List.Item> —
57724
57747
  // works the same way.
57725
57748
  const ListRowContext = createContext(null);
57726
- const css$x = /* css */`
57749
+ const css$y = /* css */`
57727
57750
  @layer navi {
57728
57751
  .navi_list_container {
57729
57752
  --list-outline-width: 1px;
@@ -58422,7 +58445,7 @@ const css$x = /* css */`
58422
58445
  }
58423
58446
  `;
58424
58447
  const ListUI = props => {
58425
- 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"];
58426
58449
  const {
58427
58450
  ref,
58428
58451
  renderBudget: renderBudgetProp = RENDER_BUDGET_DEFAULT,
@@ -62091,7 +62114,7 @@ const PickerPresetResolver = props => {
62091
62114
  });
62092
62115
  };
62093
62116
 
62094
- installImportMetaCssBuild(import.meta);const css$w = /* css */`
62117
+ installImportMetaCssBuild(import.meta);const css$x = /* css */`
62095
62118
  @layer navi {
62096
62119
  }
62097
62120
  .navi_badge {
@@ -62203,7 +62226,7 @@ const Badge = ({
62203
62226
  className,
62204
62227
  ...props
62205
62228
  }) => {
62206
- import.meta.css = [css$w, "@jsenv/navi/src/text/badge.jsx"];
62229
+ import.meta.css = [css$x, "@jsenv/navi/src/text/badge.jsx"];
62207
62230
  const defaultRef = useRef();
62208
62231
  props.ref = props.ref || defaultRef;
62209
62232
  const {
@@ -62255,7 +62278,7 @@ const BadgeButton = props => {
62255
62278
  };
62256
62279
  Badge.Button = BadgeButton;
62257
62280
 
62258
- installImportMetaCssBuild(import.meta);const css$v = /* css */`
62281
+ installImportMetaCssBuild(import.meta);const css$w = /* css */`
62259
62282
  @layer navi {
62260
62283
  }
62261
62284
  .navi_badge_list {
@@ -62280,7 +62303,7 @@ const BadgeList = ({
62280
62303
  max,
62281
62304
  ...props
62282
62305
  }) => {
62283
- 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"];
62284
62307
  const measureRef = useRef();
62285
62308
  const visibleRef = useRef();
62286
62309
  useLayoutEffect(() => {
@@ -62355,7 +62378,7 @@ const BadgeList = ({
62355
62378
  });
62356
62379
  };
62357
62380
 
62358
- installImportMetaCssBuild(import.meta);const css$u = /* css */`
62381
+ installImportMetaCssBuild(import.meta);const css$v = /* css */`
62359
62382
  .navi_color {
62360
62383
  display: block;
62361
62384
  aspect-ratio: 1/1;
@@ -62386,7 +62409,7 @@ const Color = ({
62386
62409
  children,
62387
62410
  ...rest
62388
62411
  }) => {
62389
- import.meta.css = [css$u, "@jsenv/navi/src/text/color.jsx"];
62412
+ import.meta.css = [css$v, "@jsenv/navi/src/text/color.jsx"];
62390
62413
  const color = children || undefined;
62391
62414
  return jsx(Box, {
62392
62415
  as: "span",
@@ -62843,7 +62866,7 @@ const PickerFileUI = () => {
62843
62866
  return String(value);
62844
62867
  };
62845
62868
 
62846
- installImportMetaCssBuild(import.meta);const css$t = /* css */`
62869
+ installImportMetaCssBuild(import.meta);const css$u = /* css */`
62847
62870
  @layer navi {
62848
62871
  .navi_picker {
62849
62872
  --picker-border-radius: var(--navi-control-border-radius);
@@ -63232,7 +63255,7 @@ installImportMetaCssBuild(import.meta);const css$t = /* css */`
63232
63255
  }
63233
63256
  `;
63234
63257
  const PickerButton = props => {
63235
- 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"];
63236
63259
  if (typeof props.maxLines === "string") {
63237
63260
  props.maxLines = parseInt(props.maxLines);
63238
63261
  }
@@ -63681,7 +63704,7 @@ installImportMetaCssBuild(import.meta);/**
63681
63704
  * refuse it on purpose, which is what keeps the focus where the travel happens
63682
63705
  * instead of moving it into a slide that is about to leave.
63683
63706
  */
63684
- const css$s = /* css */`
63707
+ const css$t = /* css */`
63685
63708
  @layer navi {
63686
63709
  .navi_picker_spin {
63687
63710
  /* A picker one steps through is still a picker: what themes every picker
@@ -64150,7 +64173,7 @@ const Spin = ({
64150
64173
  nextLabel,
64151
64174
  ...rest
64152
64175
  }) => {
64153
- 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"];
64154
64177
  const id = useId();
64155
64178
  // What the group around it says, when there is one: how big the whole thing
64156
64179
  // is written is said once, on the group, and every spin in it follows.
@@ -64686,7 +64709,7 @@ const renderValueDefault = value => String(value ?? "");
64686
64709
  * are passed on to the spins sitting in them.
64687
64710
  */
64688
64711
  const SpinGroup = props => {
64689
- 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"];
64690
64713
  const {
64691
64714
  size
64692
64715
  } = props;
@@ -65199,7 +65222,7 @@ const TimeRangeSpin = ({
65199
65222
  };
65200
65223
 
65201
65224
  installImportMetaCssBuild(import.meta);// TOFIX: select in data then reset, it reset to red/blue instead of red/blue/green
65202
- const css$r = /* css */`
65225
+ const css$s = /* css */`
65203
65226
  .navi_checkbox_group {
65204
65227
  border-style: solid;
65205
65228
 
@@ -65240,7 +65263,7 @@ const CheckboxGroup = props => {
65240
65263
  return checkboxGroup;
65241
65264
  };
65242
65265
  const CheckboxGroupInterface = props => {
65243
- 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"];
65244
65267
  const {
65245
65268
  ref
65246
65269
  } = props;
@@ -65289,7 +65312,7 @@ installImportMetaCssBuild(import.meta);/**
65289
65312
  * shared sheet is registered here too — a page may render a Textarea without
65290
65313
  * any Input.
65291
65314
  */
65292
- const css$q = /* css */`
65315
+ const css$r = /* css */`
65293
65316
  .navi_input.navi_textarea {
65294
65317
  .navi_control_input {
65295
65318
  min-height: calc(var(--textarea-min-rows, 1.5) * 1lh);
@@ -65376,7 +65399,7 @@ const Textarea = ({
65376
65399
  width = "35ch",
65377
65400
  ...props
65378
65401
  }) => {
65379
- 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"];
65380
65403
  const defaultRef = useRef(null);
65381
65404
  props.ref = props.ref || defaultRef;
65382
65405
  usePlaceholderHeight(props.ref, props.placeholder);
@@ -65450,7 +65473,7 @@ const TextareaCharCount = ({
65450
65473
  maxLength,
65451
65474
  ...rest
65452
65475
  }) => {
65453
- 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"];
65454
65477
  const resolvedValue = signal ? signal.value : value;
65455
65478
  const length = typeof resolvedValue === "string" ? resolvedValue.length : 0;
65456
65479
  return jsx(Box, {
@@ -65635,7 +65658,7 @@ const formatIntlUnit = (unit, {
65635
65658
  }
65636
65659
  };
65637
65660
 
65638
- installImportMetaCssBuild(import.meta);const css$p = /* css */`
65661
+ installImportMetaCssBuild(import.meta);const css$q = /* css */`
65639
65662
  .navi_input_duration {
65640
65663
  --duration-separator-spacing: 4px;
65641
65664
  --loader-color: var(--navi-loader-color);
@@ -65702,7 +65725,7 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
65702
65725
  * "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
65703
65726
  */
65704
65727
  const InputDuration = props => {
65705
- 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"];
65706
65729
  const defaultRef = useRef();
65707
65730
  props.ref = props.ref || defaultRef;
65708
65731
  props.max = props.max || "23h59";
@@ -66204,7 +66227,7 @@ const InputDurationPart = ({
66204
66227
  });
66205
66228
  };
66206
66229
 
66207
- installImportMetaCssBuild(import.meta);const css$o = /* css */`
66230
+ installImportMetaCssBuild(import.meta);const css$p = /* css */`
66208
66231
  .navi_radio_group {
66209
66232
  border-style: solid;
66210
66233
 
@@ -66224,7 +66247,7 @@ const RadioGroup = props => {
66224
66247
  return radioGroup;
66225
66248
  };
66226
66249
  const RadioGroupInterface = props => {
66227
- 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"];
66228
66251
  const {
66229
66252
  ref
66230
66253
  } = props;
@@ -66282,7 +66305,7 @@ installImportMetaCssBuild(import.meta);/**
66282
66305
  * control is drawn — the list it opens stays the platform's own, which is the
66283
66306
  * whole point of using a select.
66284
66307
  */
66285
- const css$n = /* css */`
66308
+ const css$o = /* css */`
66286
66309
  .navi_input.navi_select {
66287
66310
  .navi_control_input {
66288
66311
  /* Room for the chevron, which sits over the padding rather than beside
@@ -66337,7 +66360,7 @@ const Select = ({
66337
66360
  multiple,
66338
66361
  ...props
66339
66362
  }) => {
66340
- 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"];
66341
66364
  const defaultRef = useRef(null);
66342
66365
  props.ref = props.ref || defaultRef;
66343
66366
  seedDefaultValueFromSignal(props);
@@ -66405,7 +66428,7 @@ installImportMetaCssBuild(import.meta);/**
66405
66428
  * running: one loading outline around both, which is why each half is told
66406
66429
  * `loadingOutline={false}`.
66407
66430
  */
66408
- const css$m = /* css */`
66431
+ const css$n = /* css */`
66409
66432
  /* Around the pair rather than in it: the outline is drawn against this
66410
66433
  element and follows its corners, and a Group counts its own children to
66411
66434
  know which corners to square — an outline among them would be one of the
@@ -66517,7 +66540,7 @@ const css$m = /* css */`
66517
66540
  * Anything else lands on the split button's own box.
66518
66541
  */
66519
66542
  const SplitButton = props => {
66520
- 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"];
66521
66544
  const {
66522
66545
  options = [],
66523
66546
  value,
@@ -67168,7 +67191,7 @@ installImportMetaCssBuild(import.meta);/*
67168
67191
  * accessors (top/height vs left/width) chosen from `horizontal`, and the CSS has
67169
67192
  * a [data-horizontal] variant.
67170
67193
  */
67171
- const css$l = /* css */`
67194
+ const css$m = /* css */`
67172
67195
  .navi_wheel_container {
67173
67196
  /* Row size and emphasis band are read together: the band is what a value
67174
67197
  must cross to lose its emphasis, and the row is how far it has to travel
@@ -68290,7 +68313,7 @@ const useWheelKeyboard = ({
68290
68313
  }, [isHorizontal, interactive, isLoop]);
68291
68314
  };
68292
68315
  function WheelUI(props) {
68293
- 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"];
68294
68317
  const {
68295
68318
  ref,
68296
68319
  visibleCount = 3,
@@ -68691,6 +68714,16 @@ function WheelUI(props) {
68691
68714
  // uiAction; here we fire the action explicitly, once, on the settled value.
68692
68715
  const input = inputRef.current;
68693
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
+ });
68694
68727
  dispatchRequestAction(input, {
68695
68728
  event: settleEvent
68696
68729
  });
@@ -69199,7 +69232,7 @@ Wheel.Item = WheelItem;
69199
69232
  * @param {boolean} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
69200
69233
  */
69201
69234
  const WheelGroup = props => {
69202
- 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"];
69203
69236
  // WheelGroup IS a control group: it aggregates its named wheels ("hours",
69204
69237
  // "minutes"…) into one object value, so it can sit directly inside a Form or a
69205
69238
  // Picker with no extra <ControlGroup> wrapper. The wheel-specific presentation
@@ -69230,7 +69263,11 @@ const WheelGroup = props => {
69230
69263
  const [controlgroupRootProps, controlgroupProps, childrenWrapperProps] = useControlgroupProps(props, {
69231
69264
  allowCapture: true,
69232
69265
  wantRequesterButtonState: true,
69233
- 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",
69234
69271
  stateType: "object",
69235
69272
  cascadeValidationToChildren: true,
69236
69273
  aggregateChildStates: props.aggregateChildStates,
@@ -69365,7 +69402,7 @@ const WheelColon = props => {
69365
69402
  };
69366
69403
  Wheel.Colon = WheelColon;
69367
69404
 
69368
- /**
69405
+ installImportMetaCssBuild(import.meta);/**
69369
69406
  * A time of day, and a span between two of them, set by turning rather than by
69370
69407
  * typing. A wheel only ever shows values that exist: there is no half-written
69371
69408
  * hour to bound and correct under the fingers, which is what a time typed digit
@@ -69375,11 +69412,33 @@ Wheel.Colon = WheelColon;
69375
69412
  * like `TimeSpin` — the two are interchangeable in a form. `TimeRangeWheel` is
69376
69413
  * two of those and carries `{ start, end }`, with the rule such a pair always
69377
69414
  * has: the end comes after the start. Here that rule is lived rather than
69378
- * checked — the bounds push each other while they turn, so what the wheels show
69379
- * is always a span. The send-time constraint stays underneath for what pushing
69380
- * 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).
69381
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);
69382
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
+ `;
69383
69442
  const HOUR_COUNT = 24;
69384
69443
  const MINUTES_PER_HOUR = 60;
69385
69444
  const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
@@ -69538,6 +69597,7 @@ const TimeRangeWheel = ({
69538
69597
  endTimeProps,
69539
69598
  ...rest
69540
69599
  }) => {
69600
+ import.meta.css = [css$l, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
69541
69601
  const startId = useId();
69542
69602
  const startRef = useRef(null);
69543
69603
  const endRef = useRef(null);
@@ -69549,10 +69609,15 @@ const TimeRangeWheel = ({
69549
69609
  distributeChildUIState
69550
69610
  } = useAnswered(placeholder, rest, aggregateSpan, distributeSpan);
69551
69611
 
69552
- // What the pair does while it is being turned: the bound that just moved is
69553
- // the one the user is holding, so it stays where it was put and the OTHER one
69554
- // gives way. A refusal at the end of the gesture would leave the person to
69555
- // 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.
69556
69621
  const keepBoundsApart = (movedSide, movedTime, e) => {
69557
69622
  const movedMinutes = minutesFromTime(movedTime);
69558
69623
  if (movedMinutes === null) {
@@ -69596,6 +69661,7 @@ const TimeRangeWheel = ({
69596
69661
  value: answeredRef,
69597
69662
  children: [startLabel === null ? null : jsx(Text, {
69598
69663
  size: size,
69664
+ className: "navi_time_range_label",
69599
69665
  children: startLabel
69600
69666
  }), jsx(TimeWheel, {
69601
69667
  id: startId,
@@ -69605,12 +69671,20 @@ const TimeRangeWheel = ({
69605
69671
  hours: hours,
69606
69672
  loop: loop,
69607
69673
  size: size,
69608
- placeholder: placeholder ? placeholder.start : undefined,
69609
- 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
+ },
69610
69683
  ...timeProps,
69611
69684
  ...startTimeProps
69612
69685
  }), endLabel === null ? null : jsx(Text, {
69613
69686
  size: size,
69687
+ className: "navi_time_range_label",
69614
69688
  children: endLabel
69615
69689
  }), jsx(TimeWheel, {
69616
69690
  ref: endRef,
@@ -69620,11 +69694,14 @@ const TimeRangeWheel = ({
69620
69694
  loop: loop,
69621
69695
  size: size,
69622
69696
  placeholder: placeholder ? placeholder.end : undefined,
69623
- uiAction: (value, e) => keepBoundsApart("end", value, e)
69697
+ onnavi_wheel_settle: e => {
69698
+ keepBoundsApart("end", getUIStateFromElement(e.currentTarget), e);
69699
+ }
69624
69700
  // Which time it comes after, and how much room there must be between
69625
69701
  // the two: said on the LATER of the two, so the answer is given where
69626
69702
  // the time one would have to move is (see time_range_constraint.js).
69627
69703
  ,
69704
+
69628
69705
  "data-time-after": startId,
69629
69706
  "data-time-min-duration": minDuration,
69630
69707
  ...timeProps,
@@ -75257,18 +75334,34 @@ installImportMetaCssBuild(import.meta);/**
75257
75334
  * Two distinct facts, drawn separately — they usually agree, and everything
75258
75335
  * this component says comes from the moments they do not:
75259
75336
  *
75260
- * - the PATH (`reached`): how far the steps are answered without a gap. A
75261
- * solid line runs from the first dot to the step it names, and those dots
75262
- * are filled; past it the line is dashed the road not walked yet. It
75263
- * 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.
75264
75350
  * - the POSITION (`current`): the step being looked at, marked by a halo
75265
- * around its dot and its label emphasized. It travels freely, so it can be
75266
- * AHEAD of the path (browsing step 3 while step 1 still misses an answer)
75267
- * 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.
75268
75353
  *
75269
75354
  * Both move smoothly on change (a CSS transition each), so pressing a step
75270
75355
  * or answering one is seen travelling rather than jumping.
75271
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
+ *
75272
75365
  * The dots are drawn in SVG, twice: a muted layer, and a filled layer
75273
75366
  * clipped at the path's edge — the clip is what makes the path's progress a
75274
75367
  * single sweep that fills the line and the dots it crosses in one movement.
@@ -75277,28 +75370,21 @@ installImportMetaCssBuild(import.meta);/**
75277
75370
  * surface. Colors are CSS custom properties (see the css below), overridden
75278
75371
  * from outside for a dark band or a different accent.
75279
75372
  *
75280
- * The steps are the CHILDREN — <StepList.Item value="club">Club</StepList.Item>
75281
- * — read off the vnodes (toChildArray, so a .map() or a fragment is fine; a
75282
- * component of your own wrapping an Item is not seen). The Item renders its
75283
- * label; everything positional is this component's business.
75284
- *
75285
75373
  * `slideContainer` connects the list to a <SlideContainer> by id, both ways:
75286
75374
  * pressing a step travels there (--navi-go-to-slide), and the position is
75287
75375
  * READ off the container rather than said by a prop — including mid-travel:
75288
75376
  * the container paints --slide-travel-progress on this element (it is a
75289
75377
  * follower, same mechanism as <Nav slideContainer>), so the halo rides the
75290
- * drag under the finger, in CSS alone. The path then follows the position
75291
- * too, clamped between `reached` (never retracts) and `reachable` (never
75292
- * ahead of the answers): dragging towards a step whose way is earned fills
75293
- * the line under the finger, dragging past the answers does not.
75378
+ * drag under the finger, in CSS alone. The path is not concerned: it moves
75379
+ * on answers, never on movement.
75294
75380
  */
75295
75381
  const css$2 = /* css */`
75296
75382
  .navi_step_list {
75297
- /* The knobs: accent is the path, muted is what the path has not
75298
- reached, on-accent writes on filled dots, and --step-list-path-line
75299
- exists apart from the accent for a dark band where the walked line
75300
- reads better plain white. Said from OUTSIDE (any ancestor — a dark
75301
- band, a themed app) on the plain names; resolved here through an
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
75302
75388
  indirection (--x-…, the way Button does), because a default written
75303
75389
  on the plain name on this very element would beat anything an
75304
75390
  ancestor says. */
@@ -75313,10 +75399,9 @@ const css$2 = /* css */`
75313
75399
  --step-list-current-color,
75314
75400
  light-dark(#1c2433, white)
75315
75401
  );
75316
- --x-step-list-path-line: var(
75317
- --step-list-path-line,
75318
- var(--x-step-list-accent)
75319
- );
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);
75320
75405
 
75321
75406
  position: relative;
75322
75407
  display: block;
@@ -75354,14 +75439,26 @@ const css$2 = /* css */`
75354
75439
  .navi_step_list_rail g[data-current] text {
75355
75440
  fill: var(--x-step-list-current-color);
75356
75441
  }
75357
- /* The path: same drawing, filled, revealed up to the step it has come to.
75358
- The clip is set inline (a width in px); transitioning it is what makes
75359
- an answered step SWEEP the line and the next dot rather than pop. */
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. */
75360
75457
  .navi_step_list_rail_filled {
75361
- transition: clip-path 300ms ease;
75458
+ transition: clip-path var(--x-step-list-duration) ease;
75362
75459
  }
75363
75460
  .navi_step_list_rail_filled line {
75364
- stroke: var(--x-step-list-path-line);
75461
+ stroke: var(--x-step-list-accent);
75365
75462
  stroke-dasharray: none;
75366
75463
  }
75367
75464
  .navi_step_list_rail_filled circle {
@@ -75375,7 +75472,7 @@ const css$2 = /* css */`
75375
75472
  to dot (transform, transitioned) — the g moves, the circle inside is
75376
75473
  drawn at x=0. */
75377
75474
  .navi_step_list_marker {
75378
- transition: transform 300ms ease;
75475
+ transition: transform var(--x-step-list-duration) ease;
75379
75476
  }
75380
75477
  .navi_step_list_marker circle {
75381
75478
  fill: none;
@@ -75387,8 +75484,7 @@ const css$2 = /* css */`
75387
75484
  container paints --slide-travel-progress here (this element follows it,
75388
75485
  see data-slide-container-follows) — an asked-for travel animates it, a
75389
75486
  finger drags it — and everything below is a calc() of that number, so
75390
- the halo and the path move per frame in CSS alone. The transitions are
75391
- off: they would chase a finger that is already the pace.
75487
+ the halo and the path move per frame in CSS alone.
75392
75488
  Position, in dots-x px: where the picture is right now. */
75393
75489
  .navi_step_list[data-slide-container-follows] {
75394
75490
  --step-list-position: calc(
@@ -75400,36 +75496,11 @@ const css$2 = /* css */`
75400
75496
  transform: translateX(calc(var(--step-list-position) * 1px));
75401
75497
  transition: none;
75402
75498
  }
75403
- /* The path follows the position, clamped: never back below what was
75404
- earned (--step-list-reached-x), never ahead of what the answers allow
75405
- (--step-list-reachable-x). The +14 covers the dot it stands on (radius
75406
- plus stroke). */
75407
- .navi_step_list[data-slide-container-follows] .navi_step_list_rail_filled {
75408
- clip-path: inset(
75409
- 0
75410
- calc(
75411
- (
75412
- var(--step-list-w, 0) -
75413
- (
75414
- clamp(
75415
- var(--step-list-reached-x, -9999),
75416
- var(--step-list-position),
75417
- var(--step-list-reachable-x, -9999)
75418
- ) +
75419
- 14
75420
- )
75421
- ) *
75422
- 1px
75423
- )
75424
- 0 0
75425
- );
75426
- transition: none;
75427
- }
75428
75499
 
75429
75500
  /* One press target per step, covering the dot AND the label under it. The
75430
75501
  feedback is NOT the whole surface: a rectangle would say the whole band
75431
75502
  is a button, when the affordance is the dot — so hover and focus land on
75432
- a circle drawn over the dot (::before), plus the label brightening.
75503
+ a circle drawn over the dot, plus the label brightening.
75433
75504
  --step-dot-x anchors both on the dot, wherever the dot sits in the slot:
75434
75505
  the first and last slots are asymmetric (cut at the container's edge,
75435
75506
  see the geometry in the component). */
@@ -75454,13 +75525,15 @@ const css$2 = /* css */`
75454
75525
  --button-color-readonly: var(--x-step-list-muted);
75455
75526
  /* The button's own focus ring, silenced: it would outline the whole
75456
75527
  press surface, and the ring this list draws is the one around the dot
75457
- (see the ::before rules below) — two rings read as a mistake. Width
75458
- rather than style, because the ::before sets its own style in full. */
75528
+ (see below) — two rings read as a mistake. Width rather than style,
75529
+ because the dot sets its own style in full. */
75459
75530
  --button-outline-width: 0px;
75460
75531
  }
75461
75532
  /* Centered on the dot: same vertical middle as the rail (top 0, height 34,
75462
- cy 17). */
75463
- .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 {
75464
75537
  position: absolute;
75465
75538
  top: 17px;
75466
75539
  left: var(--step-dot-x);
@@ -75468,18 +75541,18 @@ const css$2 = /* css */`
75468
75541
  height: 30px;
75469
75542
  border-radius: 50%;
75470
75543
  translate: -50% -50%;
75471
- content: "";
75544
+ pointer-events: none;
75472
75545
  }
75473
- .navi_step_list_step:hover::before,
75474
- .navi_step_list_step[data-hover]::before {
75546
+ .navi_step_list_step:hover .navi_step_list_dot,
75547
+ .navi_step_list_step[data-hover] .navi_step_list_dot {
75475
75548
  background: color-mix(in srgb, var(--x-step-list-accent) 15%, transparent);
75476
75549
  }
75477
75550
  .navi_step_list .navi_step_list_step:hover,
75478
75551
  .navi_step_list .navi_step_list_step[data-hover] {
75479
75552
  --button-color: var(--x-step-list-current-color);
75480
75553
  }
75481
- .navi_step_list_step:focus-visible::before,
75482
- .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 {
75483
75556
  outline-width: var(--navi-focus-outline-width);
75484
75557
  outline-style: solid;
75485
75558
  outline-color: var(--navi-focus-outline-color);
@@ -75507,28 +75580,24 @@ const EDGE_INSET = 30;
75507
75580
  // halo of a current dot not to sit on the line.
75508
75581
  const LINE_GAP = 5;
75509
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
+
75510
75588
  /**
75511
75589
  * @type {import("ignore:preact").FunctionComponent<{
75512
75590
  * current?: string,
75513
- * reached?: string,
75514
- * reachable?: string,
75515
75591
  * slideContainer?: string,
75516
75592
  * travelByClick?: boolean,
75517
75593
  * travelByKeyboard?: boolean,
75594
+ * duration?: string,
75518
75595
  * [key: string]: any,
75519
75596
  * }>}
75520
75597
  * @param {string} [current] - the step being looked at: its dot gets the
75521
75598
  * halo, its label the emphasis. Omit for "nowhere" — a confirmation
75522
75599
  * screen after the walk, say. With `slideContainer` the position is read
75523
75600
  * off the container instead, and this prop is ignored.
75524
- * @param {string} [reached] - the step the path has come to: the line is
75525
- * solid and the dots filled up to it, dashed past it. Omit for a path
75526
- * that has not started.
75527
- * @param {string} [reachable] - how far the path MAY go (with
75528
- * `slideContainer` only): between `reached` and this step the path
75529
- * follows the position — a drag towards a step whose way is earned fills
75530
- * the line under the finger. Defaults to `reached`: the path then never
75531
- * moves with the position at all.
75532
75601
  * @param {string} [slideContainer] - id of a <SlideContainer> these steps
75533
75602
  * are the slides of. Pressing a step travels there
75534
75603
  * (--navi-go-to-slide), the halo follows the container — drags included —
@@ -75545,14 +75614,16 @@ const LINE_GAP = 5;
75545
75614
  * CONTAINER — this element is a follower, so a press here already walks
75546
75615
  * the slides, and the container's own `travelByKeyboard` is the one that
75547
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.
75548
75620
  */
75549
75621
  const StepList = ({
75550
75622
  current,
75551
- reached,
75552
- reachable,
75553
75623
  slideContainer,
75554
75624
  travelByClick = true,
75555
75625
  travelByKeyboard = true,
75626
+ duration,
75556
75627
  children,
75557
75628
  ...rest
75558
75629
  }) => {
@@ -75584,11 +75655,33 @@ const StepList = ({
75584
75655
  direction: "x"
75585
75656
  });
75586
75657
 
75587
- // The steps, read off the children: each <StepList.Item> vnode says which
75588
- // step it is (value) and is rendered as the label under its dot.
75589
- const stepVNodes = toChildArray(children).filter(child => child && child.props);
75590
- const stepCount = stepVNodes.length;
75591
- const valueOf = (vnode, index) => vnode.props.value ?? String(index);
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;
75592
75685
  const dotXs = [];
75593
75686
  if (width > 0 && stepCount > 0) {
75594
75687
  const span = width - EDGE_INSET * 2;
@@ -75598,7 +75691,7 @@ const StepList = ({
75598
75691
  index++;
75599
75692
  }
75600
75693
  }
75601
- const indexOf = value => stepVNodes.findIndex((vnode, index) => valueOf(vnode, index) === value);
75694
+ const indexOf = value => steps.findIndex(step => step.value === value);
75602
75695
 
75603
75696
  // Where the slides are, read off the container: which slide is current,
75604
75697
  // and — while a travel or a drag is playing — which one the picture leans
@@ -75656,19 +75749,31 @@ const StepList = ({
75656
75749
  }, [slideContainer, width, stepCount]);
75657
75750
  const resolvedCurrent = slideContainer ? containerCurrent : current;
75658
75751
  const currentIndex = resolvedCurrent === undefined ? -1 : indexOf(resolvedCurrent);
75659
- const reachedIndex = reached === undefined ? -1 : indexOf(reached);
75660
- let reachableIndex = reachable === undefined ? -1 : indexOf(reachable);
75661
- if (reachableIndex < reachedIndex) {
75662
- reachableIndex = reachedIndex;
75663
- }
75664
- // Covers the reached dot entirely (radius plus stroke), and nothing when
75665
- // the path has not started.
75666
- const fillX = reachedIndex === -1 ? 0 : dotXs[reachedIndex] + DOT_R + 3;
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
+ }
75667
75772
  const cy = RAIL_H / 2;
75668
75773
  const slotWidth = dotXs.length > 1 ? dotXs[1] - dotXs[0] : width;
75669
75774
  const renderRail = filled => jsx("svg", {
75670
75775
  className: filled ? "navi_step_list_rail navi_step_list_rail_filled" : "navi_step_list_rail",
75671
- style: filled && !slideContainer ? {
75776
+ style: filled ? {
75672
75777
  clipPath: `inset(0 ${width - fillX}px 0 0)`
75673
75778
  } : undefined,
75674
75779
  width: width,
@@ -75676,9 +75781,10 @@ const StepList = ({
75676
75781
  viewBox: `0 0 ${width} ${RAIL_H}`,
75677
75782
  "aria-hidden": "true",
75678
75783
  children: dotXs.map((x, index) => jsxs("g", {
75679
- // Base layer only: a current dot the path covers keeps the filled
75680
- // colors (see the css).
75784
+ // Base layer only: dots the path covers are drawn filled by the
75785
+ // layer above anyway (see the css).
75681
75786
  "data-current": !filled && index === currentIndex ? "" : undefined,
75787
+ "data-done": !filled && steps[index].done ? "" : undefined,
75682
75788
  children: [index > 0 ? jsx("line", {
75683
75789
  x1: dotXs[index - 1] + DOT_R + LINE_GAP,
75684
75790
  y1: cy,
@@ -75695,9 +75801,9 @@ const StepList = ({
75695
75801
  "text-anchor": "middle",
75696
75802
  children: index + 1
75697
75803
  })]
75698
- }, valueOf(stepVNodes[index], index)))
75804
+ }, steps[index].value))
75699
75805
  });
75700
- return jsx(Box, {
75806
+ return jsxs(Box, {
75701
75807
  ...rest,
75702
75808
  ref: rootRef,
75703
75809
  baseClassName: "navi_step_list",
@@ -75710,13 +75816,14 @@ const StepList = ({
75710
75816
  "data-slide-container-follows": slideContainer,
75711
75817
  style: {
75712
75818
  ...rest.style,
75713
- ...(slideContainer ? {
75714
- "--step-list-w": width,
75715
- "--step-list-reached-x": reachedIndex === -1 ? -9999 : dotXs[reachedIndex],
75716
- "--step-list-reachable-x": reachableIndex === -1 ? -9999 : dotXs[reachableIndex]
75819
+ ...(duration ? {
75820
+ "--step-list-duration": duration
75717
75821
  } : undefined)
75718
75822
  },
75719
- children: width > 0 && stepCount > 0 ? jsxs(Fragment$1, {
75823
+ children: [jsx(StepListContext.Provider, {
75824
+ value: registry,
75825
+ children: children
75826
+ }), width > 0 && stepCount > 0 ? jsxs(Fragment$1, {
75720
75827
  children: [renderRail(false), renderRail(true), currentIndex !== -1 && dotXs[currentIndex] !== undefined ? jsx("svg", {
75721
75828
  className: "navi_step_list_rail",
75722
75829
  width: width,
@@ -75738,15 +75845,7 @@ const StepList = ({
75738
75845
  r: RING_R
75739
75846
  })
75740
75847
  })
75741
- }) : null, stepVNodes.map((stepVNode, index) => {
75742
- const value = valueOf(stepVNode, index);
75743
- // Whatever else the Item was given reaches its button — a
75744
- // pseudoState held for a demo, an aria attribute.
75745
- const itemRest = {
75746
- ...stepVNode.props
75747
- };
75748
- delete itemRest.value;
75749
- delete itemRest.children;
75848
+ }) : null, steps.map((step, index) => {
75750
75849
  // The slots tile the row, cut at the container's edges: the
75751
75850
  // first and the last cover only the inner half of the room an
75752
75851
  // interior slot gets, so pressing just outside the box presses
@@ -75762,8 +75861,8 @@ const StepList = ({
75762
75861
  "width": `${slotRight - slotLeft}px`,
75763
75862
  "--step-dot-x": `${dotXs[index] - slotLeft}px`
75764
75863
  },
75765
- children: jsx(Button, {
75766
- ...itemRest,
75864
+ children: jsxs(Button, {
75865
+ ...step.buttonProps,
75767
75866
  // bare, not discrete: what is drawn IS the dot and its
75768
75867
  // label — the hover wash a discrete button paints over its
75769
75868
  // whole surface is exactly what must not appear here (the
@@ -75776,38 +75875,105 @@ const StepList = ({
75776
75875
  // Towards the slides when connected, by name: the command
75777
75876
  // reaches the container wherever this list sits on the
75778
75877
  // page. What else a press should do is the Item's own
75779
- // onClick, which arrived through itemRest.
75878
+ // onClick, which arrived through buttonProps.
75780
75879
  ,
75781
75880
 
75782
- command: slideContainer && travelByClick ? `--navi-go-to-slide:${value}` : undefined,
75783
- commandFor: slideContainer,
75784
- children: jsx("span", {
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", {
75785
75893
  className: "navi_step_list_label",
75786
- children: stepVNode
75787
- })
75894
+ children: step.label
75895
+ })]
75788
75896
  })
75789
- }, value);
75897
+ }, step.value);
75790
75898
  })]
75791
- }) : null
75899
+ }) : null]
75792
75900
  });
75793
75901
  };
75794
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
+
75795
75939
  /**
75796
- * One step of the walk: `value` names it (what `current`/`reached` say and
75797
- * what a press reports), the children are its label. Rendered under its dot;
75798
- * 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.
75799
75947
  *
75800
75948
  * It is both StepList.Item and an export of its own, the way Slide is to
75801
75949
  * SlideContainer.
75802
75950
  *
75803
75951
  * @type {import("ignore:preact").FunctionComponent<{
75804
75952
  * value: string,
75953
+ * done?: boolean,
75805
75954
  * [key: string]: any,
75806
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.
75807
75959
  */
75808
75960
  const Step = ({
75809
- children
75810
- }) => 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
+ };
75811
75977
  StepList.Item = Step;
75812
75978
 
75813
75979
  installImportMetaCssBuild(import.meta);const css$1 = /* css */`