@jsenv/navi 0.29.54 → 0.29.55

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$10 = /* css */`
41
+ const css$11 = /* css */`
42
42
  @layer navi {
43
43
  :root {
44
44
  /* A control that overlaps its neighbours (the members of a Group share
@@ -90,7 +90,7 @@ const css$10 = /* css */`
90
90
  }
91
91
  }
92
92
  `;
93
- import.meta.css = [css$10, "@jsenv/navi/src/navi_z_indexes.js"];
93
+ import.meta.css = [css$11, "@jsenv/navi/src/navi_z_indexes.js"];
94
94
 
95
95
  const addIntoArray = (array, ...valuesToAdd) => {
96
96
  if (valuesToAdd.length === 1) {
@@ -359,7 +359,7 @@ installImportMetaCssBuild(import.meta);/**
359
359
  * the very first render and the browser does everything on its own.
360
360
  */
361
361
  const URL_TARGET_ATTRIBUTE = "data-url-target";
362
- const css$$ = /* css */`
362
+ const css$10 = /* css */`
363
363
  @layer navi {
364
364
  [${URL_TARGET_ATTRIBUTE}] {
365
365
  animation: navi_url_target var(--navi-url-target-duration, 2000ms)
@@ -377,7 +377,7 @@ const css$$ = /* css */`
377
377
  }
378
378
  }
379
379
  `;
380
- import.meta.css = [css$$, "@jsenv/navi/src/nav/url_target/url_target.js"];
380
+ import.meta.css = [css$10, "@jsenv/navi/src/nav/url_target/url_target.js"];
381
381
  let urlTargetOptions = {
382
382
  block: "center",
383
383
  behavior: "smooth",
@@ -1299,6 +1299,10 @@ naviI18n.addAll({
1299
1299
  en: "Confirm",
1300
1300
  fr: "Confirmer",
1301
1301
  },
1302
+ "button.more_actions": {
1303
+ en: "More actions",
1304
+ fr: "Autres actions",
1305
+ },
1302
1306
  });
1303
1307
 
1304
1308
  // Default built-in translations — apps can override any key via add()
@@ -7725,7 +7729,7 @@ installImportMetaCssBuild(import.meta);/**
7725
7729
  * - Arrow automatically shows when pointing at a valid anchor element
7726
7730
  * - Centers in viewport when no anchor element provided or anchor is too big
7727
7731
  */
7728
- const css$_ = /* css */`
7732
+ const css$$ = /* css */`
7729
7733
  @layer navi {
7730
7734
  .navi_callout {
7731
7735
  /* A callout is parented to what it explains, so it inherits from it — and
@@ -7964,7 +7968,7 @@ const openCallout = (message, {
7964
7968
  skipFocus = false,
7965
7969
  debug = () => {}
7966
7970
  } = {}) => {
7967
- import.meta.css = [css$_, "@jsenv/navi/src/control/rules/callout/callout.js"];
7971
+ import.meta.css = [css$$, "@jsenv/navi/src/control/rules/callout/callout.js"];
7968
7972
  if (debug === true) {
7969
7973
  debug = (e, ...args) => console.debug(`"${e.type}" -> `, ...args);
7970
7974
  }
@@ -20031,7 +20035,7 @@ const setupNetworkMonitoring = () => {
20031
20035
  };
20032
20036
  setupNetworkMonitoring();
20033
20037
 
20034
- installImportMetaCssBuild(import.meta);const css$Z = /* css */`
20038
+ installImportMetaCssBuild(import.meta);const css$_ = /* css */`
20035
20039
  .navi_loading_indicator_fluid_container {
20036
20040
  position: relative;
20037
20041
  display: flex;
@@ -20063,7 +20067,7 @@ const LoadingIndicatorFluid = ({
20063
20067
  visuallyHidden,
20064
20068
  ...rest
20065
20069
  }) => {
20066
- import.meta.css = [css$Z, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
20070
+ import.meta.css = [css$_, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
20067
20071
  const ref = useRef(null);
20068
20072
  // The container dimensions can be deduced from the ref itself as the indicator is absolute inset 0
20069
20073
  const [containerWidth, setContainerWidth] = useState(0);
@@ -20268,7 +20272,7 @@ const LoadingRectangleSvg = ({
20268
20272
  });
20269
20273
  };
20270
20274
 
20271
- installImportMetaCssBuild(import.meta);const css$Y = /* css */`
20275
+ installImportMetaCssBuild(import.meta);const css$Z = /* css */`
20272
20276
  .navi_loading_outline_wrapper {
20273
20277
  position: absolute;
20274
20278
  /* Controls place the outline slightly outside their box, right on top of
@@ -20305,7 +20309,7 @@ installImportMetaCssBuild(import.meta);const css$Y = /* css */`
20305
20309
  }
20306
20310
  `;
20307
20311
  const LoadingOutline = props => {
20308
- import.meta.css = [css$Y, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
20312
+ import.meta.css = [css$Z, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
20309
20313
  if (props.containerRef) {
20310
20314
  const container = props.containerRef.current;
20311
20315
  if (!container) {
@@ -20595,7 +20599,7 @@ const selectByTextStrings = (element, range, startText, endText) => {
20595
20599
  };
20596
20600
 
20597
20601
  installImportMetaCssBuild(import.meta);// https://jsfiddle.net/v5xzJ/4/
20598
- const css$X = /* css */`
20602
+ const css$Y = /* css */`
20599
20603
  @layer navi {
20600
20604
  .navi_text {
20601
20605
  &[data-skeleton] {
@@ -21101,7 +21105,7 @@ const TextShrinkWrap = props => {
21101
21105
  });
21102
21106
  };
21103
21107
  const TextUI = props => {
21104
- import.meta.css = [css$X, "@jsenv/navi/src/text/text.jsx"];
21108
+ import.meta.css = [css$Y, "@jsenv/navi/src/text/text.jsx"];
21105
21109
  let {
21106
21110
  ref,
21107
21111
  spacing,
@@ -26982,7 +26986,7 @@ const getAssociatedLabels = element => {
26982
26986
  return Array.from(element.labels);
26983
26987
  };
26984
26988
 
26985
- installImportMetaCssBuild(import.meta);const css$W = /* css */`
26989
+ installImportMetaCssBuild(import.meta);const css$X = /* css */`
26986
26990
  @layer navi {
26987
26991
  .navi_button {
26988
26992
  --button-border-radius: var(--navi-control-border-radius);
@@ -27413,7 +27417,7 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
27413
27417
  }
27414
27418
  `;
27415
27419
  const ButtonUI = props => {
27416
- import.meta.css = [css$W, "@jsenv/navi/src/control/input/button_ui.jsx"];
27420
+ import.meta.css = [css$X, "@jsenv/navi/src/control/input/button_ui.jsx"];
27417
27421
  const {
27418
27422
  ref,
27419
27423
  // href/link
@@ -27425,7 +27429,11 @@ const ButtonUI = props => {
27425
27429
  pressEffect,
27426
27430
  icon,
27427
27431
  cta,
27428
- spacing
27432
+ spacing,
27433
+ // Whether the button draws the loading outline itself. A button that is
27434
+ // one half of a bigger control says no: what is busy is the control, and
27435
+ // the outline belongs around the whole of it (see split_button.jsx).
27436
+ loadingOutline = true
27429
27437
  } = props;
27430
27438
  const [buttonControlRootProps, buttonControlHostProps, controlChildrenWrapperProps] = useControlProps(props, {
27431
27439
  controlType: "button",
@@ -27481,6 +27489,7 @@ const ButtonUI = props => {
27481
27489
  spacing: undefined,
27482
27490
  cta: undefined,
27483
27491
  pressEffect: undefined,
27492
+ loadingOutline: undefined,
27484
27493
  ref: ref,
27485
27494
  as: as,
27486
27495
  href: href,
@@ -27524,7 +27533,7 @@ const ButtonUI = props => {
27524
27533
  visualSelector: visualSelector,
27525
27534
  hasChildUsingForwardedProps: true,
27526
27535
  children: [jsx(LoadingOutline, {
27527
- loading: loading,
27536
+ loading: loadingOutline && loading,
27528
27537
  inset: -1,
27529
27538
  color: "var(--button-loader-color)"
27530
27539
  }), jsx(ControlChildrenWrapper, {
@@ -29247,7 +29256,7 @@ installImportMetaCssBuild(import.meta);/**
29247
29256
  * reaches the real container.
29248
29257
  */
29249
29258
  let openLocalDialogCount = 0;
29250
- const css$V = /* css */`
29259
+ const css$W = /* css */`
29251
29260
  @layer navi {
29252
29261
  .navi_dialog {
29253
29262
  /* Min gap between the dialog and the edges of its container. Written
@@ -29756,7 +29765,7 @@ const css$V = /* css */`
29756
29765
  * @param {import("ignore:preact").ComponentChildren} props.children
29757
29766
  */
29758
29767
  const Dialog = props => {
29759
- import.meta.css = [css$V, "@jsenv/navi/src/layout/dialog.jsx"];
29768
+ import.meta.css = [css$W, "@jsenv/navi/src/layout/dialog.jsx"];
29760
29769
  if (props.openController) {
29761
29770
  return jsx(ControlledDialog, {
29762
29771
  ...props
@@ -30670,7 +30679,7 @@ installImportMetaCssBuild(import.meta);/**
30670
30679
  * and applied.
30671
30680
  */
30672
30681
  let openLocalPopoverCount = 0;
30673
- const css$U = /* css */`
30682
+ const css$V = /* css */`
30674
30683
  @layer navi {
30675
30684
  .navi_popover {
30676
30685
  /* soft: user-configurable preferred max-height. Kept as a *default*
@@ -31103,7 +31112,7 @@ const css$U = /* css */`
31103
31112
  * @param {import("ignore:preact").ComponentChildren} props.children
31104
31113
  */
31105
31114
  const Popover = props => {
31106
- import.meta.css = [css$U, "@jsenv/navi/src/layout/popover.jsx"];
31115
+ import.meta.css = [css$V, "@jsenv/navi/src/layout/popover.jsx"];
31107
31116
  if (props.openController) {
31108
31117
  return jsx(ControlledPopover, {
31109
31118
  ...props
@@ -32122,7 +32131,7 @@ installImportMetaCssBuild(import.meta);/**
32122
32131
  * event, and a caller replacing the body entirely then has one protocol to
32123
32132
  * follow — `--navi-confirm` for yes, anything that closes for no.
32124
32133
  */
32125
- const css$T = /* css */`
32134
+ const css$U = /* css */`
32126
32135
  /* The width lives on the body rather than on the popup, so that custom
32127
32136
  content (which replaces this body entirely) sizes itself instead of
32128
32137
  inheriting a ceiling meant for a sentence-long question. */
@@ -32259,7 +32268,7 @@ const ConfirmPopup = ({
32259
32268
  onAnswer,
32260
32269
  onClosed
32261
32270
  }) => {
32262
- import.meta.css = [css$T, "@jsenv/navi/src/action/confirm_popup.jsx"];
32271
+ import.meta.css = [css$U, "@jsenv/navi/src/action/confirm_popup.jsx"];
32263
32272
  const {
32264
32273
  mode,
32265
32274
  confirmLabel,
@@ -32343,7 +32352,7 @@ const defaultBody = (message, {
32343
32352
  });
32344
32353
  };
32345
32354
 
32346
- installImportMetaCssBuild(import.meta);const css$S = /* css */`
32355
+ installImportMetaCssBuild(import.meta);const css$T = /* css */`
32347
32356
  .action_error {
32348
32357
  margin-top: 0;
32349
32358
  margin-bottom: 20px;
@@ -32368,7 +32377,7 @@ const ActionRenderer = ({
32368
32377
  children,
32369
32378
  disabled
32370
32379
  }) => {
32371
- import.meta.css = [css$S, "@jsenv/navi/src/action/action_renderer.jsx"];
32380
+ import.meta.css = [css$T, "@jsenv/navi/src/action/action_renderer.jsx"];
32372
32381
  if (action === undefined) {
32373
32382
  throw new Error("ActionRenderer requires an action to render, but none was provided.");
32374
32383
  }
@@ -37927,7 +37936,7 @@ const TRAVEL_HEIGHT_PROPERTY = "--navi-route-travel-height";
37927
37936
  const TRAVEL_OLD_TOP_PROPERTY = "--navi-route-travel-old-top";
37928
37937
  const TRAVEL_OLD_LEFT_PROPERTY = "--navi-route-travel-old-left";
37929
37938
  const TRAVEL_GEOMETRY_PROPERTIES = [TRAVEL_TOP_PROPERTY, TRAVEL_LEFT_PROPERTY, TRAVEL_WIDTH_PROPERTY, TRAVEL_HEIGHT_PROPERTY, TRAVEL_OLD_TOP_PROPERTY, TRAVEL_OLD_LEFT_PROPERTY];
37930
- const css$R = /* css */`
37939
+ const css$S = /* css */`
37931
37940
  /* The name that makes the page inside this box a picture of its own during a
37932
37941
  transition — rather than part of the one big picture the document takes, so
37933
37942
  the two pages can move past each other while everything else stays where it
@@ -38309,7 +38318,7 @@ const RouteTravel = ({
38309
38318
  children,
38310
38319
  ...rest
38311
38320
  }) => {
38312
- import.meta.css = [css$R, "@jsenv/navi/src/nav/route_travel.jsx"];
38321
+ import.meta.css = [css$S, "@jsenv/navi/src/nav/route_travel.jsx"];
38313
38322
  const elementRef = useRef();
38314
38323
  const gestureRef = useRef(null);
38315
38324
  // The travel in hand: the transition keeping the picture of the page being
@@ -40969,7 +40978,7 @@ const PhoneSvg = () => {
40969
40978
  };
40970
40979
 
40971
40980
  installImportMetaCssBuild(import.meta);// # TextAnchor — how it works
40972
- const css$Q = /* css */`
40981
+ const css$R = /* css */`
40973
40982
  .navi_text_anchor {
40974
40983
  vertical-align: baseline;
40975
40984
  user-select: none;
@@ -41004,7 +41013,7 @@ const TextAnchor = ({
41004
41013
  textSize,
41005
41014
  lineLayout
41006
41015
  }) => {
41007
- import.meta.css = [css$Q, "@jsenv/navi/src/text/text_anchor.jsx"];
41016
+ import.meta.css = [css$R, "@jsenv/navi/src/text/text_anchor.jsx"];
41008
41017
  const anchorRef = useRef();
41009
41018
 
41010
41019
  // Plain useLayoutEffect would also fire while an ancestor dialog/popover
@@ -41119,7 +41128,7 @@ const computeTopOffset = ({
41119
41128
  };
41120
41129
  const charTopCanvas = document.createElement("canvas");
41121
41130
 
41122
- installImportMetaCssBuild(import.meta);const css$P = /* css */`
41131
+ installImportMetaCssBuild(import.meta);const css$Q = /* css */`
41123
41132
  @layer navi {
41124
41133
  /* Ensure data attributes from box.jsx can win to update display */
41125
41134
  .navi_icon {
@@ -41277,7 +41286,7 @@ const Icon = ({
41277
41286
  fillLine,
41278
41287
  ...props
41279
41288
  }) => {
41280
- import.meta.css = [css$P, "@jsenv/navi/src/text/icon.jsx"];
41289
+ import.meta.css = [css$Q, "@jsenv/navi/src/text/icon.jsx"];
41281
41290
  const innerChildren = href ? jsx("svg", {
41282
41291
  width: "100%",
41283
41292
  height: "100%",
@@ -41439,7 +41448,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
41439
41448
  });
41440
41449
  };
41441
41450
 
41442
- installImportMetaCssBuild(import.meta);const css$O = /* css */`
41451
+ installImportMetaCssBuild(import.meta);const css$P = /* css */`
41443
41452
  @layer navi {
41444
41453
  .navi_link {
41445
41454
  --link-border-radius: unset;
@@ -41887,7 +41896,7 @@ Object.assign(PSEUDO_CLASSES, {
41887
41896
  * @param {boolean} [props.readOnly]
41888
41897
  */
41889
41898
  const Link = props => {
41890
- import.meta.css = [css$O, "@jsenv/navi/src/nav/link/link.jsx"];
41899
+ import.meta.css = [css$P, "@jsenv/navi/src/nav/link/link.jsx"];
41891
41900
  if (props.route) {
41892
41901
  return jsx(LinkWithRoute, {
41893
41902
  ...props
@@ -42190,7 +42199,7 @@ installImportMetaCssBuild(import.meta);/**
42190
42199
  * https://dribbble.com/search/tabs
42191
42200
  */
42192
42201
  let navCount = 0;
42193
- const css$N = /* css */`
42202
+ const css$O = /* css */`
42194
42203
  @layer navi {
42195
42204
  .navi_nav {
42196
42205
  --nav-border: none;
@@ -42473,7 +42482,7 @@ const Nav = ({
42473
42482
  slideContainer,
42474
42483
  ...props
42475
42484
  }) => {
42476
- import.meta.css = [css$N, "@jsenv/navi/src/nav/link/nav.jsx"];
42485
+ import.meta.css = [css$O, "@jsenv/navi/src/nav/link/nav.jsx"];
42477
42486
  const defaultRef = useRef();
42478
42487
  props.ref = props.ref || defaultRef;
42479
42488
  const navRef = props.ref;
@@ -43000,7 +43009,7 @@ installImportMetaCssBuild(import.meta);/**
43000
43009
  * Border width participates in layout (it is added to the tab and page
43001
43010
  * padding): a thick border grows the binder rather than eating into the text.
43002
43011
  */
43003
- const css$M = /* css */`
43012
+ const css$N = /* css */`
43004
43013
  @layer navi {
43005
43014
  .navi_binder {
43006
43015
  --binder-border-width: var(--navi-control-border-width);
@@ -43313,7 +43322,7 @@ const Binder = ({
43313
43322
  pagePadding,
43314
43323
  ...props
43315
43324
  }) => {
43316
- import.meta.css = [css$M, "@jsenv/navi/src/nav/binder/binder.jsx"];
43325
+ import.meta.css = [css$N, "@jsenv/navi/src/nav/binder/binder.jsx"];
43317
43326
  const items = toChildArray(children).map((child, index) => {
43318
43327
  const {
43319
43328
  value: itemValue,
@@ -43797,7 +43806,7 @@ installImportMetaCssBuild(import.meta);/**
43797
43806
  * into the size; a box-shadow draws the identical line and stays out of
43798
43807
  * layout.
43799
43808
  */
43800
- const css$L = /* css */`
43809
+ const css$M = /* css */`
43801
43810
  @layer navi {
43802
43811
  :root {
43803
43812
  --navi-fixed-bar-width: 56px;
@@ -43933,7 +43942,7 @@ const FixedBar = ({
43933
43942
  border = true,
43934
43943
  ...props
43935
43944
  }) => {
43936
- import.meta.css = [css$L, "@jsenv/navi/src/layout/fixed_bar/fixed_bar.jsx"];
43945
+ import.meta.css = [css$M, "@jsenv/navi/src/layout/fixed_bar/fixed_bar.jsx"];
43937
43946
  const defaultRef = useRef();
43938
43947
  props.ref = props.ref || defaultRef;
43939
43948
  // Whichever of width/height crosses the edge the bar sits on is what the
@@ -44021,7 +44030,7 @@ const FixedBar = ({
44021
44030
  // Subpixel layout rounds rectangles up on boxes that fit exactly.
44022
44031
  const OVERFLOW_TOLERANCE = 1;
44023
44032
 
44024
- const css$K = /* css */ `
44033
+ const css$L = /* css */ `
44025
44034
  [data-navi-overflow-x] {
44026
44035
  outline: 2px dashed #e74c3c;
44027
44036
  outline-offset: -2px;
@@ -44045,7 +44054,7 @@ const detectHorizontalOverflow = ({
44045
44054
  let styleEl = null;
44046
44055
  if (highlight) {
44047
44056
  styleEl = document.createElement("style");
44048
- styleEl.textContent = css$K;
44057
+ styleEl.textContent = css$L;
44049
44058
  document.head.appendChild(styleEl);
44050
44059
  }
44051
44060
 
@@ -44201,7 +44210,7 @@ const useFocusGroup = (
44201
44210
 
44202
44211
  installImportMetaCssBuild(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
44203
44212
  const downArrowPath = "M480-280L160-600l80-80 240 240 240-240 80 80-320 320z";
44204
- const css$J = /* css */`
44213
+ const css$K = /* css */`
44205
44214
  .navi_summary_marker {
44206
44215
  width: 1em;
44207
44216
  height: 1em;
@@ -44286,7 +44295,7 @@ const SummaryMarker = ({
44286
44295
  open,
44287
44296
  loading
44288
44297
  }) => {
44289
- import.meta.css = [css$J, "@jsenv/navi/src/control/details/summary_marker.jsx"];
44298
+ import.meta.css = [css$K, "@jsenv/navi/src/control/details/summary_marker.jsx"];
44290
44299
  const showLoading = useDebounceTrue(loading, 300);
44291
44300
  const mountedRef = useRef(false);
44292
44301
  const prevOpenRef = useRef(open);
@@ -44340,7 +44349,7 @@ const SummaryMarker = ({
44340
44349
  });
44341
44350
  };
44342
44351
 
44343
- installImportMetaCssBuild(import.meta);const css$I = /* css */`
44352
+ installImportMetaCssBuild(import.meta);const css$J = /* css */`
44344
44353
  .navi_details {
44345
44354
  position: relative;
44346
44355
  z-index: 1;
@@ -44386,7 +44395,7 @@ const Details = props => {
44386
44395
  return details;
44387
44396
  };
44388
44397
  const DetailsField = props => {
44389
- import.meta.css = [css$I, "@jsenv/navi/src/control/details/details.jsx"];
44398
+ import.meta.css = [css$J, "@jsenv/navi/src/control/details/details.jsx"];
44390
44399
  const {
44391
44400
  ref,
44392
44401
  persists,
@@ -44642,7 +44651,7 @@ const ControlGroup = props => {
44642
44651
  };
44643
44652
  const CONTROL_GROUP_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
44644
44653
 
44645
- installImportMetaCssBuild(import.meta);const css$H = /* css */`
44654
+ installImportMetaCssBuild(import.meta);const css$I = /* css */`
44646
44655
  @layer navi {
44647
44656
  .navi_checkbox {
44648
44657
  --switch-margin: 0; /* Useful to reserve space for outline */
@@ -44720,7 +44729,7 @@ installImportMetaCssBuild(import.meta);const css$H = /* css */`
44720
44729
  }
44721
44730
  `;
44722
44731
  const SwitchUI = () => {
44723
- import.meta.css = [css$H, "@jsenv/navi/src/control/input/switch_ui.jsx"];
44732
+ import.meta.css = [css$I, "@jsenv/navi/src/control/input/switch_ui.jsx"];
44724
44733
  return jsx(Box, {
44725
44734
  className: "navi_switch",
44726
44735
  as: "svg",
@@ -44762,7 +44771,7 @@ const useCheckableProps = (props, options) => {
44762
44771
  return result;
44763
44772
  };
44764
44773
 
44765
- installImportMetaCssBuild(import.meta);const css$G = /* css */`
44774
+ installImportMetaCssBuild(import.meta);const css$H = /* css */`
44766
44775
  @layer navi {
44767
44776
  .navi_checkbox {
44768
44777
  --border-radius: var(--navi-checkbox-border-radius);
@@ -45089,7 +45098,7 @@ const InputCheckboxHeadless = props => {
45089
45098
  });
45090
45099
  };
45091
45100
  const InputCheckboxFieldInterface = props => {
45092
- import.meta.css = [css$G, "@jsenv/navi/src/control/input/input_checkbox.jsx"];
45101
+ import.meta.css = [css$H, "@jsenv/navi/src/control/input/input_checkbox.jsx"];
45093
45102
  const [checkboxRootProps, checkboxHostProps] = useCheckableProps(props);
45094
45103
  const {
45095
45104
  icon,
@@ -45211,7 +45220,7 @@ const CheckboxButtonStyleCSSVars = {
45211
45220
  const CheckboxPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
45212
45221
  const CheckboxPseudoElements = ["::-navi-loader", "::-navi-checkmark"];
45213
45222
 
45214
- installImportMetaCssBuild(import.meta);const css$F = /* css */`
45223
+ installImportMetaCssBuild(import.meta);const css$G = /* css */`
45215
45224
  @layer navi {
45216
45225
  .navi_label {
45217
45226
  --label-required-indicator-color: var(--navi-color-danger, #b42318);
@@ -45291,7 +45300,7 @@ installImportMetaCssBuild(import.meta);const css$F = /* css */`
45291
45300
  * </Field>
45292
45301
  */
45293
45302
  const Field = props => {
45294
- import.meta.css = [css$F, "@jsenv/navi/src/control/field.jsx"];
45303
+ import.meta.css = [css$G, "@jsenv/navi/src/control/field.jsx"];
45295
45304
  const refDefault = useRef();
45296
45305
  props.ref = props.ref || refDefault;
45297
45306
  const {
@@ -45326,7 +45335,7 @@ const FieldCSSVars = {
45326
45335
  spacingWithControl: "--spacing-with-control"
45327
45336
  };
45328
45337
  const FieldAsContainer = props => {
45329
- import.meta.css = [css$F, "@jsenv/navi/src/control/field.jsx"];
45338
+ import.meta.css = [css$G, "@jsenv/navi/src/control/field.jsx"];
45330
45339
  const {
45331
45340
  children
45332
45341
  } = props;
@@ -45358,7 +45367,7 @@ const FieldAsContainer = props => {
45358
45367
  };
45359
45368
  const FIELD_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
45360
45369
  const Label = props => {
45361
- import.meta.css = [css$F, "@jsenv/navi/src/control/field.jsx"];
45370
+ import.meta.css = [css$G, "@jsenv/navi/src/control/field.jsx"];
45362
45371
  const {
45363
45372
  children,
45364
45373
  // Marks the label when its control is required. Takes what to show, or
@@ -45520,7 +45529,7 @@ const InputSlot = ({
45520
45529
  });
45521
45530
  };
45522
45531
 
45523
- installImportMetaCssBuild(import.meta);const css$E = /* css */`
45532
+ installImportMetaCssBuild(import.meta);const css$F = /* css */`
45524
45533
  @layer navi {
45525
45534
  .navi_radio {
45526
45535
  --margin: 3px 3px 3px 5px;
@@ -45883,7 +45892,7 @@ const InputRadioHeadless = props => {
45883
45892
  };
45884
45893
  const VARIANT_SET = new Set(["icon", "button", "radio"]);
45885
45894
  const InputRadioFieldInterface = props => {
45886
- import.meta.css = [css$E, "@jsenv/navi/src/control/input/input_radio.jsx"];
45895
+ import.meta.css = [css$F, "@jsenv/navi/src/control/input/input_radio.jsx"];
45887
45896
  const [radioRootProps, radioHostProps] = useCheckableProps(props);
45888
45897
  const {
45889
45898
  icon,
@@ -46029,7 +46038,7 @@ const RadioButtonStyleCSSVars = {
46029
46038
  const RadioPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
46030
46039
  const RadioPseudoElements = ["::-navi-loader", "::-navi-radiomark"];
46031
46040
 
46032
- installImportMetaCssBuild(import.meta);const css$D = /* css */`
46041
+ installImportMetaCssBuild(import.meta);const css$E = /* css */`
46033
46042
  @layer navi {
46034
46043
  .navi_input_range {
46035
46044
  --border-radius: 6px;
@@ -46284,7 +46293,7 @@ const InputRange = props => {
46284
46293
  });
46285
46294
  };
46286
46295
  const InputRangeFieldInterface = props => {
46287
- import.meta.css = [css$D, "@jsenv/navi/src/control/input/input_range.jsx"];
46296
+ import.meta.css = [css$E, "@jsenv/navi/src/control/input/input_range.jsx"];
46288
46297
  const {
46289
46298
  ref
46290
46299
  } = props;
@@ -46411,24 +46420,68 @@ const InputModeResolver = props => {
46411
46420
  };
46412
46421
  const InputModeNumericOrDecimal = props => {
46413
46422
  const Next = useNextResolver();
46423
+ // Where a finger landed, to tell a tap that placed the caret from a drag that
46424
+ // selected a part of the number (see onPointerUp below).
46425
+ const pointerDownRef = useRef(null);
46414
46426
  return jsx(Next, {
46415
46427
  ...props,
46428
+ // A field with no room for one more digit is a field one can only
46429
+ // REPLACE: typing into it does nothing at all, so taking the caret
46430
+ // selects what is there and the first digit typed starts a new number.
46431
+ // The same handover navi_input_full does once a field fills up, asked at
46432
+ // the other end — when the field is entered rather than when it is
46433
+ // filled.
46434
+ onFocus: e => {
46435
+ props.onFocus?.(e);
46436
+ if (e.defaultPrevented) {
46437
+ return;
46438
+ }
46439
+ selectIfFull(e.currentTarget);
46440
+ },
46441
+ onPointerDown: e => {
46442
+ props.onPointerDown?.(e);
46443
+ pointerDownRef.current = {
46444
+ x: e.clientX,
46445
+ y: e.clientY
46446
+ };
46447
+ }
46448
+ // Once more on release, for a finger: a phone places the caret where it
46449
+ // was tapped AFTER the focus above, which undoes the selection made
46450
+ // there — and a field one cannot type into is then back to being a field
46451
+ // one cannot type into. Not for a mouse, which is how one selects a
46452
+ // single digit by dragging across it; and not for a finger that
46453
+ // travelled, which was scrolling the page rather than aiming at the
46454
+ // number.
46455
+ ,
46456
+ onPointerUp: e => {
46457
+ props.onPointerUp?.(e);
46458
+ if (e.defaultPrevented) {
46459
+ return;
46460
+ }
46461
+ if (e.pointerType !== "touch") {
46462
+ return;
46463
+ }
46464
+ const pointerDown = pointerDownRef.current;
46465
+ if (pointerDown) {
46466
+ const dx = e.clientX - pointerDown.x;
46467
+ const dy = e.clientY - pointerDown.y;
46468
+ if (dx * dx + dy * dy > TAP_SLOP * TAP_SLOP) {
46469
+ return;
46470
+ }
46471
+ }
46472
+ selectIfFull(e.currentTarget);
46473
+ },
46416
46474
  onInput: e => {
46417
46475
  props.onInput?.(e);
46418
46476
  if (e.defaultPrevented) {
46419
46477
  return;
46420
46478
  }
46421
46479
  const input = e.currentTarget;
46422
- let maxLength = input.maxLength;
46423
- if (maxLength === -1) {
46424
- const naviMaxLengthAttr = input.getAttribute("navi-max-length");
46425
- maxLength = naviMaxLengthAttr === null ? undefined : Number(naviMaxLengthAttr);
46426
- }
46427
46480
  const caretAtEnd = input.selectionStart === input.value.length;
46428
46481
  if (!caretAtEnd) {
46429
46482
  return;
46430
46483
  }
46431
- if (!isFull(input, maxLength)) {
46484
+ if (!isFull(input, readMaxLength(input))) {
46432
46485
  return;
46433
46486
  }
46434
46487
  // Field is full and caret is at the end: notify listeners then
@@ -46458,6 +46511,37 @@ const InputModeNumericOrDecimal = props => {
46458
46511
  });
46459
46512
  };
46460
46513
 
46514
+ // How far a finger may travel and still be aiming at what it landed on, in
46515
+ // pixels: past that it was scrolling the page.
46516
+ const TAP_SLOP = 10;
46517
+
46518
+ // Everything already in the field, selected — but only when nothing more can be
46519
+ // typed after it, which is when replacing is the only edit left. A field one is
46520
+ // merely half-way through filling keeps its caret where it was put.
46521
+ const selectIfFull = input => {
46522
+ if (input.readOnly || input.disabled) {
46523
+ return;
46524
+ }
46525
+ if (!isFull(input, readMaxLength(input))) {
46526
+ return;
46527
+ }
46528
+ if (input.selectionStart === 0 && input.selectionEnd === input.value.length) {
46529
+ return;
46530
+ }
46531
+ input.select();
46532
+ };
46533
+
46534
+ // What the field accepts at most: our own attribute first, since the native
46535
+ // maxLength is deliberately left unset (see RealInput in input_textual.jsx).
46536
+ const readMaxLength = input => {
46537
+ const maxLength = input.maxLength;
46538
+ if (maxLength !== -1) {
46539
+ return maxLength;
46540
+ }
46541
+ const naviMaxLengthAttr = input.getAttribute("navi-max-length");
46542
+ return naviMaxLengthAttr === null ? undefined : Number(naviMaxLengthAttr);
46543
+ };
46544
+
46461
46545
  // A field is full when there is no room for another digit — and room is not
46462
46546
  // only a number of characters: an hour of two digits at most is also full at
46463
46547
  // "7", because 7 followed by anything is past the 23 it accepts. Both are the
@@ -48249,7 +48333,7 @@ installImportMetaCssBuild(import.meta);/**
48249
48333
  * This means an editable thing MUST have a parent with position relative that wraps the content and the eventual editable input
48250
48334
  *
48251
48335
  */
48252
- const css$C = /* css */`
48336
+ const css$D = /* css */`
48253
48337
  .navi_editable_wrapper {
48254
48338
  --inset-top: 0px;
48255
48339
  --inset-right: 0px;
@@ -48298,7 +48382,7 @@ const useEditionController = () => {
48298
48382
  };
48299
48383
  };
48300
48384
  const Editable = props => {
48301
- import.meta.css = [css$C, "@jsenv/navi/src/control/edition/editable.jsx"];
48385
+ import.meta.css = [css$D, "@jsenv/navi/src/control/edition/editable.jsx"];
48302
48386
  let {
48303
48387
  children,
48304
48388
  action,
@@ -48847,7 +48931,7 @@ installImportMetaCssBuild(import.meta);/**
48847
48931
  * meet are drawn once instead of twice, and only the outer corners stay
48848
48932
  * rounded. See docs/control_group.md.
48849
48933
  */
48850
- const css$B = /* css */`
48934
+ const css$C = /* css */`
48851
48935
  .navi_group {
48852
48936
  --group-border-width: var(--navi-control-border-width);
48853
48937
 
@@ -48963,7 +49047,7 @@ const Group = ({
48963
49047
  vertical = row,
48964
49048
  ...props
48965
49049
  }) => {
48966
- import.meta.css = [css$B, "@jsenv/navi/src/control/group.jsx"];
49050
+ import.meta.css = [css$C, "@jsenv/navi/src/control/group.jsx"];
48967
49051
  return jsx(Box, {
48968
49052
  baseClassName: "navi_group",
48969
49053
  "data-vertical": vertical ? "" : undefined
@@ -49129,7 +49213,7 @@ installImportMetaCssBuild(import.meta);/**
49129
49213
  * So: nothing scrollable between the cap and the slides (a shared [data-body]
49130
49214
  * around them IS a scroller, see box.jsx), and `overflow="auto"` on each Slide.
49131
49215
  */
49132
- const css$A = /* css */`
49216
+ const css$B = /* css */`
49133
49217
  /* Where the picture stands relative to the slide that is current, in boxes
49134
49218
  (see paintTravelProgress). Declared, so that it is a NUMBER the browser can
49135
49219
  interpolate: the trait an indicator draws has to travel with the slides,
@@ -49613,7 +49697,7 @@ const SlideContainer = ({
49613
49697
  children,
49614
49698
  ...rest
49615
49699
  }) => {
49616
- import.meta.css = [css$A, "@jsenv/navi/src/layout/slide_container.jsx"];
49700
+ import.meta.css = [css$B, "@jsenv/navi/src/layout/slide_container.jsx"];
49617
49701
  const debugFocus = useDebugFocus();
49618
49702
  const trackRef = useRef();
49619
49703
  // The box itself: it is what takes the keyboard when what is on screen holds
@@ -52434,7 +52518,7 @@ installImportMetaCssBuild(import.meta);/**
52434
52518
  * pass through untouched via `...rest` to whichever of Popover/Dialog
52435
52519
  * actually renders.
52436
52520
  */
52437
- const css$z = /* css */`
52521
+ const css$A = /* css */`
52438
52522
  @layer navi {
52439
52523
  .navi_popup {
52440
52524
  --popup-border-radius: var(--navi-popup-border-radius);
@@ -52547,7 +52631,7 @@ const css$z = /* css */`
52547
52631
  * @param {import("ignore:preact").ComponentChildren} props.children
52548
52632
  */
52549
52633
  const Popup = props => {
52550
- import.meta.css = [css$z, "@jsenv/navi/src/layout/popup.jsx"];
52634
+ import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
52551
52635
  const {
52552
52636
  mode: modeProp,
52553
52637
  maxWidth,
@@ -52610,7 +52694,7 @@ const Popup = props => {
52610
52694
  });
52611
52695
  };
52612
52696
 
52613
- installImportMetaCssBuild(import.meta);const css$y = /* css */`
52697
+ installImportMetaCssBuild(import.meta);const css$z = /* css */`
52614
52698
  .navi_picker {
52615
52699
  /* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
52616
52700
  overflow... are already handled correctly by Popup/Popover/Dialog
@@ -52728,7 +52812,7 @@ installImportMetaCssBuild(import.meta);const css$y = /* css */`
52728
52812
  }
52729
52813
  `;
52730
52814
  const PickerCustomResolver = props => {
52731
- import.meta.css = [css$y, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
52815
+ import.meta.css = [css$z, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
52732
52816
  if (props.children === undefined) {
52733
52817
  return jsx(PickerNative, {
52734
52818
  ...props
@@ -52840,6 +52924,9 @@ const PickerCustom = props => {
52840
52924
  delete pickerProps.open;
52841
52925
  delete pickerProps.defaultOpen;
52842
52926
  delete pickerProps.escapeEffect;
52927
+ // Read below for the popup alone; on the trigger it would land on the DOM as
52928
+ // an unknown attribute holding a ref object.
52929
+ delete pickerProps.anchor;
52843
52930
  const popupProps = {};
52844
52931
  Object.assign(pickerProps, {
52845
52932
  popupProps,
@@ -53015,7 +53102,10 @@ const PickerCustom = props => {
53015
53102
  children
53016
53103
  });
53017
53104
  Object.assign(popupProps, {
53018
- anchor: props.ref,
53105
+ // The trigger, unless the caller names something else: a picker whose
53106
+ // trigger is a piece of a bigger control (the chevron half of a split
53107
+ // button) hangs its popup off the whole control instead.
53108
+ anchor: props.anchor || props.ref,
53019
53109
  openController,
53020
53110
  // A picker whose value was never given to it reads it off the control in
53021
53111
  // its popup (see useUIFacadeStateController): the trigger shows what the
@@ -53502,7 +53592,7 @@ const LoadingIndicator = ({
53502
53592
  });
53503
53593
  };
53504
53594
 
53505
- installImportMetaCssBuild(import.meta);const css$x = /* css */`
53595
+ installImportMetaCssBuild(import.meta);const css$y = /* css */`
53506
53596
  @layer navi {
53507
53597
  .navi_separator {
53508
53598
  --size: 1px;
@@ -53580,7 +53670,7 @@ const Separator = ({
53580
53670
  style,
53581
53671
  ...props
53582
53672
  }) => {
53583
- import.meta.css = [css$x, "@jsenv/navi/src/layout/separator.jsx"];
53673
+ import.meta.css = [css$y, "@jsenv/navi/src/layout/separator.jsx"];
53584
53674
  return jsx(Box, {
53585
53675
  as: vertical ? "span" : "hr",
53586
53676
  ...props,
@@ -54073,7 +54163,7 @@ const ListItemFooter = props => {
54073
54163
  });
54074
54164
  };
54075
54165
 
54076
- installImportMetaCssBuild(import.meta);const css$w = /* css */`
54166
+ installImportMetaCssBuild(import.meta);const css$x = /* css */`
54077
54167
  @layer navi {
54078
54168
  .navi_list_container[navi-selectable] {
54079
54169
  /* Focus outline */
@@ -54285,7 +54375,7 @@ const ListSelectableResolver = props => {
54285
54375
  };
54286
54376
  const ListSelectable = props => {
54287
54377
  const Next = useNextResolver();
54288
- import.meta.css = [css$w, "@jsenv/navi/src/control/list/list_selectable.jsx"];
54378
+ import.meta.css = [css$x, "@jsenv/navi/src/control/list/list_selectable.jsx"];
54289
54379
  // we allow ourselves to auto-generate a name
54290
54380
  const defaultName = useId();
54291
54381
  props.name = props.name || `listbox_${defaultName}`;
@@ -54905,7 +54995,7 @@ const ListVirtualContext = createContext(null);
54905
54995
  // that returning a component of one's own — instead of a bare <List.Item> —
54906
54996
  // works the same way.
54907
54997
  const ListRowContext = createContext(null);
54908
- const css$v = /* css */`
54998
+ const css$w = /* css */`
54909
54999
  @layer navi {
54910
55000
  .navi_list_container {
54911
55001
  --list-outline-width: 1px;
@@ -55546,7 +55636,7 @@ const css$v = /* css */`
55546
55636
  }
55547
55637
  `;
55548
55638
  const ListUI = props => {
55549
- import.meta.css = [css$v, "@jsenv/navi/src/control/list/list.jsx"];
55639
+ import.meta.css = [css$w, "@jsenv/navi/src/control/list/list.jsx"];
55550
55640
  const {
55551
55641
  ref,
55552
55642
  renderBudget: renderBudgetProp = RENDER_BUDGET_DEFAULT,
@@ -59042,7 +59132,7 @@ const PickerPresetResolver = props => {
59042
59132
  });
59043
59133
  };
59044
59134
 
59045
- installImportMetaCssBuild(import.meta);const css$u = /* css */`
59135
+ installImportMetaCssBuild(import.meta);const css$v = /* css */`
59046
59136
  @layer navi {
59047
59137
  }
59048
59138
  .navi_badge {
@@ -59154,7 +59244,7 @@ const Badge = ({
59154
59244
  className,
59155
59245
  ...props
59156
59246
  }) => {
59157
- import.meta.css = [css$u, "@jsenv/navi/src/text/badge.jsx"];
59247
+ import.meta.css = [css$v, "@jsenv/navi/src/text/badge.jsx"];
59158
59248
  const defaultRef = useRef();
59159
59249
  props.ref = props.ref || defaultRef;
59160
59250
  const {
@@ -59206,7 +59296,7 @@ const BadgeButton = props => {
59206
59296
  };
59207
59297
  Badge.Button = BadgeButton;
59208
59298
 
59209
- installImportMetaCssBuild(import.meta);const css$t = /* css */`
59299
+ installImportMetaCssBuild(import.meta);const css$u = /* css */`
59210
59300
  @layer navi {
59211
59301
  }
59212
59302
  .navi_badge_list {
@@ -59231,7 +59321,7 @@ const BadgeList = ({
59231
59321
  max,
59232
59322
  ...props
59233
59323
  }) => {
59234
- import.meta.css = [css$t, "@jsenv/navi/src/text/badge_list.jsx"];
59324
+ import.meta.css = [css$u, "@jsenv/navi/src/text/badge_list.jsx"];
59235
59325
  const measureRef = useRef();
59236
59326
  const visibleRef = useRef();
59237
59327
  useLayoutEffect(() => {
@@ -59306,7 +59396,7 @@ const BadgeList = ({
59306
59396
  });
59307
59397
  };
59308
59398
 
59309
- installImportMetaCssBuild(import.meta);const css$s = /* css */`
59399
+ installImportMetaCssBuild(import.meta);const css$t = /* css */`
59310
59400
  .navi_color {
59311
59401
  display: block;
59312
59402
  aspect-ratio: 1/1;
@@ -59337,7 +59427,7 @@ const Color = ({
59337
59427
  children,
59338
59428
  ...rest
59339
59429
  }) => {
59340
- import.meta.css = [css$s, "@jsenv/navi/src/text/color.jsx"];
59430
+ import.meta.css = [css$t, "@jsenv/navi/src/text/color.jsx"];
59341
59431
  const color = children || undefined;
59342
59432
  return jsx(Box, {
59343
59433
  as: "span",
@@ -59792,7 +59882,7 @@ const PickerFileUI = () => {
59792
59882
  return String(value);
59793
59883
  };
59794
59884
 
59795
- installImportMetaCssBuild(import.meta);const css$r = /* css */`
59885
+ installImportMetaCssBuild(import.meta);const css$s = /* css */`
59796
59886
  @layer navi {
59797
59887
  .navi_picker {
59798
59888
  --picker-border-radius: var(--navi-control-border-radius);
@@ -60181,7 +60271,7 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
60181
60271
  }
60182
60272
  `;
60183
60273
  const PickerButton = props => {
60184
- import.meta.css = [css$r, "@jsenv/navi/src/control/picker/picker.jsx"];
60274
+ import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker.jsx"];
60185
60275
  if (typeof props.maxLines === "string") {
60186
60276
  props.maxLines = parseInt(props.maxLines);
60187
60277
  }
@@ -60592,7 +60682,7 @@ installImportMetaCssBuild(import.meta);/**
60592
60682
  * refuse it on purpose, which is what keeps the focus where the travel happens
60593
60683
  * instead of moving it into a slide that is about to leave.
60594
60684
  */
60595
- const css$q = /* css */`
60685
+ const css$r = /* css */`
60596
60686
  @layer navi {
60597
60687
  .navi_picker_spin {
60598
60688
  /* A picker one steps through is still a picker: what themes every picker
@@ -60803,6 +60893,12 @@ const css$q = /* css */`
60803
60893
  a radius there would show as a notch in the pressed background. */
60804
60894
  border-radius: 0;
60805
60895
  cursor: pointer;
60896
+ /* Pressed with a finger as readily as with a mouse: no double-tap zoom to
60897
+ wait for between two presses, and no word of the value getting selected
60898
+ under the finger of someone holding one down. */
60899
+ touch-action: manipulation;
60900
+ user-select: none;
60901
+ -webkit-user-select: none;
60806
60902
  }
60807
60903
  /* Said as data-hover rather than :hover: a touch browser synthesizes the
60808
60904
  enter and never the leave, so a CSS :hover would stay grey under the last
@@ -61055,7 +61151,7 @@ const Spin = ({
61055
61151
  nextLabel,
61056
61152
  ...rest
61057
61153
  }) => {
61058
- import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
61154
+ import.meta.css = [css$r, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
61059
61155
  const id = useId();
61060
61156
  // What the group around it says, when there is one: how big the whole thing
61061
61157
  // is written is said once, on the group, and every spin in it follows.
@@ -61414,55 +61510,11 @@ const WayOut = ({
61414
61510
  onPress,
61415
61511
  onPointerDown,
61416
61512
  children
61417
- }) => jsx(Box, {
61418
- as: "span",
61419
- baseClassName: "navi_picker_spin_way_out"
61420
- // Which end of the box it sits in, said by the chevron rather than read
61421
- // from its place among its siblings: that is what the corners it is
61422
- // rounded by are keyed on (see the CSS above).
61423
- ,
61424
-
61425
- "data-way-out": atStart ? "start" : "end"
61426
- // Tracked rather than left to CSS :hover, which stays on after a tap on a
61427
- // touch device (see the CSS above).
61428
- ,
61429
-
61430
- pseudoClasses: WAY_OUT_PSEUDO_CLASSES
61431
- // Announced as a button because that is what it is to whoever cannot see
61432
- // the chevron — and marked unavailable rather than removed when there is
61433
- // nothing that way, so it keeps its place.
61434
- ,
61435
-
61436
- role: "button",
61437
- "aria-label": label,
61438
- "aria-disabled": unavailableMessage ? "true" : undefined,
61439
- "data-unavailable": unavailableMessage ? "" : undefined
61440
- // At the chevron, not beside it: a callout aims its arrow at where the
61441
- // anchor's text starts, and there is no text here — only a glyph in the
61442
- // middle of the box, which is what one pressed and what the answer is
61443
- // about.
61444
- ,
61445
-
61446
- "data-callout-arrow-x": "center"
61447
- // Read by triggerNaviCommand below the same way it reads a button's own.
61448
- ,
61449
-
61450
- commandfor: commandFor,
61451
- flex: true,
61452
- align: "center"
61453
- // A press, answered where it starts: mousedown rather than click, which is
61454
- // what makes holding one feel immediate — and the click after it is stopped
61455
- // below, so a <Label> wrapping the whole control does not forward it to the
61456
- // control and open the calendar on the way past.
61457
- ,
61458
-
61459
- onClick: e => {
61460
- e.preventDefault();
61461
- },
61462
- onPointerDown: onPointerDown,
61463
- onMouseDown: e => {
61464
- // No focus, no text selection: the keyboard is put on the middle below.
61465
- e.preventDefault();
61513
+ }) => {
61514
+ // Where the finger landed, to tell a press from the start of a scroll (see
61515
+ // onPointerUp below).
61516
+ const pointerDownRef = useRef(null);
61517
+ const press = e => {
61466
61518
  if (unavailableMessage) {
61467
61519
  // Why it does nothing, said where one pressed: a control would have
61468
61520
  // done this through its own interaction gate, and this one has none.
@@ -61474,11 +61526,108 @@ const WayOut = ({
61474
61526
  return;
61475
61527
  }
61476
61528
  onPress(e);
61477
- },
61478
- children: jsx(Icon, {
61479
- children: children
61480
- })
61481
- });
61529
+ };
61530
+ return jsx(Box, {
61531
+ as: "span",
61532
+ baseClassName: "navi_picker_spin_way_out"
61533
+ // Which end of the box it sits in, said by the chevron rather than read
61534
+ // from its place among its siblings: that is what the corners it is
61535
+ // rounded by are keyed on (see the CSS above).
61536
+ ,
61537
+
61538
+ "data-way-out": atStart ? "start" : "end"
61539
+ // Tracked rather than left to CSS :hover, which stays on after a tap on a
61540
+ // touch device (see the CSS above).
61541
+ ,
61542
+
61543
+ pseudoClasses: WAY_OUT_PSEUDO_CLASSES
61544
+ // Announced as a button because that is what it is to whoever cannot see
61545
+ // the chevron — and marked unavailable rather than removed when there is
61546
+ // nothing that way, so it keeps its place.
61547
+ ,
61548
+
61549
+ role: "button",
61550
+ "aria-label": label,
61551
+ "aria-disabled": unavailableMessage ? "true" : undefined,
61552
+ "data-unavailable": unavailableMessage ? "" : undefined
61553
+ // At the chevron, not beside it: a callout aims its arrow at where the
61554
+ // anchor's text starts, and there is no text here — only a glyph in the
61555
+ // middle of the box, which is what one pressed and what the answer is
61556
+ // about.
61557
+ ,
61558
+
61559
+ "data-callout-arrow-x": "center"
61560
+ // Read by triggerNaviCommand below the same way it reads a button's own.
61561
+ ,
61562
+
61563
+ commandfor: commandFor,
61564
+ flex: true,
61565
+ align: "center"
61566
+ // The press is answered on the pointer events below, and the click that
61567
+ // follows them is stopped here: a <Label> wrapping the whole control
61568
+ // forwards a click to what it labels, which would open the calendar on
61569
+ // the way past.
61570
+ ,
61571
+
61572
+ onClick: e => {
61573
+ e.preventDefault();
61574
+ },
61575
+ onPointerDown: e => {
61576
+ onPointerDown(e);
61577
+ pointerDownRef.current = {
61578
+ x: e.clientX,
61579
+ y: e.clientY
61580
+ };
61581
+ if (e.pointerType === "touch") {
61582
+ return;
61583
+ }
61584
+ // No focus, no text selection: the keyboard is put on the middle below.
61585
+ // preventDefault on the pointer event rather than on the mouse one it is
61586
+ // followed by, since the press is answered here.
61587
+ e.preventDefault();
61588
+ press(e);
61589
+ }
61590
+ // A finger is answered when it lifts, not when it lands: what starts on a
61591
+ // chevron may be a press or may be the beginning of a scroll, and only the
61592
+ // release tells the two apart. A mouse has no such doubt and is answered
61593
+ // above, as it goes down, which is what makes holding one feel immediate.
61594
+ ,
61595
+
61596
+ onPointerUp: e => {
61597
+ if (e.pointerType !== "touch") {
61598
+ return;
61599
+ }
61600
+ const pointerDown = pointerDownRef.current;
61601
+ if (pointerDown) {
61602
+ const dx = e.clientX - pointerDown.x;
61603
+ const dy = e.clientY - pointerDown.y;
61604
+ if (dx * dx + dy * dy > WAY_OUT_TAP_SLOP * WAY_OUT_TAP_SLOP) {
61605
+ return;
61606
+ }
61607
+ }
61608
+ press(e);
61609
+ }
61610
+ // A chevron has nothing to copy, look up or share: the menu a long press
61611
+ // opens on a phone would only get in the way of someone stepping through
61612
+ // values, and the pressed state under it would stay on once it opened.
61613
+ ,
61614
+
61615
+ onContextMenu: e => {
61616
+ if (e.pointerType !== "touch") {
61617
+ // right click is allowed
61618
+ return;
61619
+ }
61620
+ e.preventDefault();
61621
+ },
61622
+ children: jsx(Icon, {
61623
+ children: children
61624
+ })
61625
+ });
61626
+ };
61627
+
61628
+ // How far a finger may travel and still be pressing what it landed on, in
61629
+ // pixels: past that it was scrolling the page.
61630
+ const WAY_OUT_TAP_SLOP = 10;
61482
61631
  const PICKER_SPIN_PSEUDO_CLASSES = [":hover", ":focus-visible"];
61483
61632
  const WAY_OUT_PSEUDO_CLASSES = [":hover"];
61484
61633
 
@@ -61538,7 +61687,7 @@ const renderValueDefault = value => String(value ?? "");
61538
61687
  * are passed on to the spins sitting in them.
61539
61688
  */
61540
61689
  const SpinGroup = props => {
61541
- import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
61690
+ import.meta.css = [css$r, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
61542
61691
  const {
61543
61692
  size
61544
61693
  } = props;
@@ -62069,7 +62218,7 @@ const TimeRangeSpin = ({
62069
62218
  };
62070
62219
 
62071
62220
  installImportMetaCssBuild(import.meta);// TOFIX: select in data then reset, it reset to red/blue instead of red/blue/green
62072
- const css$p = /* css */`
62221
+ const css$q = /* css */`
62073
62222
  .navi_checkbox_group {
62074
62223
  border-style: solid;
62075
62224
 
@@ -62110,7 +62259,7 @@ const CheckboxGroup = props => {
62110
62259
  return checkboxGroup;
62111
62260
  };
62112
62261
  const CheckboxGroupInterface = props => {
62113
- import.meta.css = [css$p, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
62262
+ import.meta.css = [css$q, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
62114
62263
  const {
62115
62264
  ref
62116
62265
  } = props;
@@ -62159,7 +62308,7 @@ installImportMetaCssBuild(import.meta);/**
62159
62308
  * shared sheet is registered here too — a page may render a Textarea without
62160
62309
  * any Input.
62161
62310
  */
62162
- const css$o = /* css */`
62311
+ const css$p = /* css */`
62163
62312
  .navi_input.navi_textarea {
62164
62313
  .navi_control_input {
62165
62314
  min-height: calc(var(--textarea-min-rows, 1.5) * 1lh);
@@ -62246,7 +62395,7 @@ const Textarea = ({
62246
62395
  width = "35ch",
62247
62396
  ...props
62248
62397
  }) => {
62249
- import.meta.css = [inputCss + css$o, "@jsenv/navi/src/control/input/textarea.jsx"];
62398
+ import.meta.css = [inputCss + css$p, "@jsenv/navi/src/control/input/textarea.jsx"];
62250
62399
  const defaultRef = useRef(null);
62251
62400
  props.ref = props.ref || defaultRef;
62252
62401
  usePlaceholderHeight(props.ref, props.placeholder);
@@ -62320,7 +62469,7 @@ const TextareaCharCount = ({
62320
62469
  maxLength,
62321
62470
  ...rest
62322
62471
  }) => {
62323
- import.meta.css = [css$o, "@jsenv/navi/src/control/input/textarea.jsx"];
62472
+ import.meta.css = [css$p, "@jsenv/navi/src/control/input/textarea.jsx"];
62324
62473
  const resolvedValue = signal ? signal.value : value;
62325
62474
  const length = typeof resolvedValue === "string" ? resolvedValue.length : 0;
62326
62475
  return jsx(Box, {
@@ -62505,7 +62654,7 @@ const formatIntlUnit = (unit, {
62505
62654
  }
62506
62655
  };
62507
62656
 
62508
- installImportMetaCssBuild(import.meta);const css$n = /* css */`
62657
+ installImportMetaCssBuild(import.meta);const css$o = /* css */`
62509
62658
  .navi_input_duration {
62510
62659
  --duration-separator-spacing: 4px;
62511
62660
  --loader-color: var(--navi-loader-color);
@@ -62572,7 +62721,7 @@ installImportMetaCssBuild(import.meta);const css$n = /* css */`
62572
62721
  * "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
62573
62722
  */
62574
62723
  const InputDuration = props => {
62575
- import.meta.css = [css$n, "@jsenv/navi/src/control/input/input_duration.jsx"];
62724
+ import.meta.css = [css$o, "@jsenv/navi/src/control/input/input_duration.jsx"];
62576
62725
  const defaultRef = useRef();
62577
62726
  props.ref = props.ref || defaultRef;
62578
62727
  props.max = props.max || "23h59";
@@ -63074,7 +63223,7 @@ const InputDurationPart = ({
63074
63223
  });
63075
63224
  };
63076
63225
 
63077
- installImportMetaCssBuild(import.meta);const css$m = /* css */`
63226
+ installImportMetaCssBuild(import.meta);const css$n = /* css */`
63078
63227
  .navi_radio_group {
63079
63228
  border-style: solid;
63080
63229
 
@@ -63094,7 +63243,7 @@ const RadioGroup = props => {
63094
63243
  return radioGroup;
63095
63244
  };
63096
63245
  const RadioGroupInterface = props => {
63097
- import.meta.css = [css$m, "@jsenv/navi/src/control/input/radio_group.jsx"];
63246
+ import.meta.css = [css$n, "@jsenv/navi/src/control/input/radio_group.jsx"];
63098
63247
  const {
63099
63248
  ref
63100
63249
  } = props;
@@ -63152,7 +63301,7 @@ installImportMetaCssBuild(import.meta);/**
63152
63301
  * control is drawn — the list it opens stays the platform's own, which is the
63153
63302
  * whole point of using a select.
63154
63303
  */
63155
- const css$l = /* css */`
63304
+ const css$m = /* css */`
63156
63305
  .navi_input.navi_select {
63157
63306
  .navi_control_input {
63158
63307
  /* Room for the chevron, which sits over the padding rather than beside
@@ -63207,7 +63356,7 @@ const Select = ({
63207
63356
  multiple,
63208
63357
  ...props
63209
63358
  }) => {
63210
- import.meta.css = [inputCss + css$l, "@jsenv/navi/src/control/input/select.jsx"];
63359
+ import.meta.css = [inputCss + css$m, "@jsenv/navi/src/control/input/select.jsx"];
63211
63360
  const defaultRef = useRef(null);
63212
63361
  props.ref = props.ref || defaultRef;
63213
63362
  seedDefaultValueFromSignal(props);
@@ -63256,6 +63405,344 @@ const Select = ({
63256
63405
  });
63257
63406
  };
63258
63407
 
63408
+ installImportMetaCssBuild(import.meta);/**
63409
+ * SplitButton — one action, several variants of it.
63410
+ *
63411
+ * The left half does the thing; the right half opens the list of the other
63412
+ * ways of doing it. What choosing means is the caller's call (`chooseEffect`):
63413
+ * the chosen way becomes what the button does, or it is done right away.
63414
+ *
63415
+ * Both halves are `<Button>`s, deliberately: the chevron half has to wear the
63416
+ * exact same paint as the label half for any `cta`/`variant`/`color` the
63417
+ * caller passes, and a `<Picker variant="icon">` next to a button paints from
63418
+ * its own set of custom properties — matching them would mean restating the
63419
+ * button's palette here and keeping the copy in step forever. So the popup
63420
+ * comes from a headless `<Picker>` sitting behind the chevron button, hung off
63421
+ * the whole split button (`anchor`) so the menu lines up with it.
63422
+ *
63423
+ * The busy state is worn by the control, not by the half that happens to be
63424
+ * running: one loading outline around both, which is why each half is told
63425
+ * `loadingOutline={false}`.
63426
+ */
63427
+ const css$l = /* css */`
63428
+ /* Around the pair rather than in it: the outline is drawn against this
63429
+ element and follows its corners, and a Group counts its own children to
63430
+ know which corners to square — an outline among them would be one of the
63431
+ halves. Hence a box holding a group, rather than the group itself. */
63432
+ .navi_split_button {
63433
+ position: relative;
63434
+ display: inline-flex;
63435
+ border-radius: var(--navi-control-border-radius);
63436
+
63437
+ > .navi_group {
63438
+ flex: 1 1 auto;
63439
+ }
63440
+ }
63441
+ /* The chevron half is a button with a headless picker behind it: the picker
63442
+ box is inset: 0 of whatever is positioned above it, and that must be the
63443
+ chevron alone, not the whole split button. */
63444
+ .navi_split_button_menu {
63445
+ position: relative;
63446
+ display: flex;
63447
+ }
63448
+ `;
63449
+
63450
+ /**
63451
+ * @type {import("ignore:preact").FunctionComponent<{
63452
+ * options: Array<{value: any, label: import("ignore:preact").ComponentChildren, [key: string]: any}>,
63453
+ * value?: any,
63454
+ * defaultValue?: any,
63455
+ * label?: import("ignore:preact").ComponentChildren,
63456
+ * action?: (value: any, event: Event) => void | Promise<void>,
63457
+ * onValueChange?: (value: any, event: Event) => void,
63458
+ * chooseEffect?: "select" | "run",
63459
+ * menuLabel?: string,
63460
+ * menuIcon?: import("ignore:preact").ComponentChildren,
63461
+ * menuIconSize?: number | string,
63462
+ * loading?: boolean,
63463
+ * readOnly?: boolean,
63464
+ * disabled?: boolean,
63465
+ * cta?: boolean,
63466
+ * variant?: string,
63467
+ * color?: string,
63468
+ * backgroundColor?: string,
63469
+ * borderColor?: string,
63470
+ * borderWidth?: number | string,
63471
+ * borderRadius?: number | string,
63472
+ * pressEffect?: "none" | string,
63473
+ * mode?: "popover" | "dialog",
63474
+ * popoverMode?: "nearby" | "overlay",
63475
+ * positionArea?: string,
63476
+ * popupWidthFitContent?: boolean,
63477
+ * popoverMaxHeight?: number | string,
63478
+ * dialogMaxWidth?: number | string,
63479
+ * dialogMaxHeight?: number | string,
63480
+ * dialogExpand?: boolean,
63481
+ * dialogExpandX?: boolean,
63482
+ * dialogExpandY?: boolean,
63483
+ * dockedOnSmallTouchScreen?: boolean,
63484
+ * marginWithContainer?: number | string,
63485
+ * backdropVariant?: "auto" | "discrete" | "invisible",
63486
+ * pointerInteractionOutsideEffect?: "close" | "cancel" | "capture",
63487
+ * escapeEffect?: "cancel" | "close",
63488
+ * popupLayer?: "top" | "local",
63489
+ * [key: string]: any,
63490
+ * }>}
63491
+ * @param {Array<{value: any, label: import("ignore:preact").ComponentChildren}>} options
63492
+ * The ways of doing the thing, in the order the menu lists them. Anything
63493
+ * else on an entry (`disabled`, `icon`, `padding`…) reaches its row.
63494
+ * @param {any} [value] Which entry the button stands for — the one it runs, and
63495
+ * the one it is named after. `defaultValue` is the uncontrolled form; without
63496
+ * either, the first entry.
63497
+ * @param {import("ignore:preact").ComponentChildren} [label] What the button says, when
63498
+ * that is not the name of the entry it stands for — a button that keeps one
63499
+ * name whatever the menu does.
63500
+ * @param {(value: any, event: Event) => void} [action] Do the thing, for the
63501
+ * entry given. Pressed on the button it is the entry the button stands for;
63502
+ * with `chooseEffect="run"` it is also the entry just chosen. Awaited: the
63503
+ * whole split button is busy until it settles.
63504
+ * @param {"select"|"run"} [chooseEffect="select"] What choosing an entry does.
63505
+ * "select" hands the button that entry, to be run by a press on it — for
63506
+ * something one does again and again, where the last choice is the likely
63507
+ * next one. "run" does it there and then and leaves the button alone — for a
63508
+ * menu of one-offs around a primary action.
63509
+ * @param {string} [menuLabel] What the chevron half is called, for a screen
63510
+ * reader and for the tooltip a long press raises.
63511
+ * @param {import("ignore:preact").ComponentChildren} [menuIcon] What the chevron half
63512
+ * draws, in place of the chevron. It is the whole of what that half says, so
63513
+ * pass something that still reads as "there is more here" — and say what,
63514
+ * through `menuLabel`.
63515
+ * @param {number|string} [menuIconSize] How big that icon is drawn. Defaults to
63516
+ * the button's own font size.
63517
+ * @param {"popover"|"dialog"} [mode="popover"] What the menu is drawn as. A
63518
+ * picker left to itself turns into a dialog on a small screen; a split
63519
+ * button's menu stays hung off the button there, so this says popover unless
63520
+ * the caller asks for the dialog back.
63521
+ * @param {string} [positionArea="bottom-end"] Where the menu goes — relative to
63522
+ * the whole split button in popover mode, relative to the viewport in dialog
63523
+ * mode. Same grammar as Picker/Popover. The default only applies to a
63524
+ * popover; a dialog keeps Dialog's own "center".
63525
+ *
63526
+ * Every other prop the Picker's popup answers to is forwarded as-is —
63527
+ * `dockedOnSmallTouchScreen`, `dialogExpand*`, `dialogMaxWidth`/`Height`,
63528
+ * `marginWithContainer`, `popoverMode`, `popoverSpacing`, `popupLayer`,
63529
+ * `popupWidthFitContent`, `popoverMaxHeight`, `backdropVariant`,
63530
+ * `pointerInteractionOutsideEffect`, `escapeEffect`, `closeOnFocusOut`,
63531
+ * `scrollCapture`, `focusCapture`, `popupBackgroundColor`,
63532
+ * `popupBorderRadius`, `animation`. See picker.jsx for what each one says.
63533
+ * Anything else lands on the split button's own box.
63534
+ */
63535
+ const SplitButton = props => {
63536
+ import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
63537
+ const {
63538
+ options = [],
63539
+ value,
63540
+ defaultValue,
63541
+ label,
63542
+ action,
63543
+ onValueChange,
63544
+ chooseEffect = "select",
63545
+ menuLabel = naviI18n("button.more_actions"),
63546
+ menuIcon = jsx(ChevronDownSvg$1, {}),
63547
+ menuIconSize,
63548
+ loading,
63549
+ readOnly,
63550
+ disabled,
63551
+ // Shared paint: whatever the caller says about how the button looks is said
63552
+ // about both halves, or the seam shows.
63553
+ cta,
63554
+ variant,
63555
+ color,
63556
+ backgroundColor,
63557
+ borderColor,
63558
+ borderWidth,
63559
+ borderRadius,
63560
+ // A split button is a frame the user's eye reads as one box; the left half
63561
+ // shrinking under the finger while the right half stays put breaks it.
63562
+ pressEffect = "none",
63563
+ id,
63564
+ ...rest
63565
+ } = props;
63566
+ // Everything the popup answers to travels to the Picker; everything else is
63567
+ // the split button's own box (margins, width, data-*). Sorted by name rather
63568
+ // than named one by one so a Picker popup prop is forwarded by adding it to
63569
+ // that list, not by threading it through here.
63570
+ const [popupProps, boxProps] = splitPopupProps(rest);
63571
+ // A split button is a control on the page, not a place one goes: its menu
63572
+ // hangs off it even on a phone, where a picker left to itself would decide a
63573
+ // small screen means a dialog. Passing mode="dialog" asks for that back.
63574
+ const mode = popupProps.mode === undefined ? "popover" : popupProps.mode;
63575
+ popupProps.mode = mode;
63576
+ // Read against the trigger in popover mode and against the VIEWPORT in
63577
+ // dialog mode, so this default only holds for the former — under the chevron
63578
+ // half, growing leftwards when the menu is wider than the button. A dialog
63579
+ // keeps Dialog's own "center".
63580
+ if (popupProps.positionArea === undefined && mode === "popover") {
63581
+ popupProps.positionArea = "bottom-end";
63582
+ }
63583
+ const idDefault = useId();
63584
+ const idResolved = id || idDefault;
63585
+ const menuId = `${idResolved}_menu`;
63586
+ const rootRef = useRef(null);
63587
+ const [valueState, setValueState] = useState(defaultValue === undefined ? options[0]?.value : defaultValue);
63588
+ const valueResolved = value === undefined ? valueState : value;
63589
+ const optionShown = options.find(option => option.value === valueResolved) || options[0];
63590
+
63591
+ // Busy is the control's, not the half that happens to be running: an action
63592
+ // given as a plain function is watched by running it from here, one given as
63593
+ // a navi action carries its own running state and is handed over untouched.
63594
+ const actionIsNaviAction = typeof action === "function" && action.isAction;
63595
+ const naviActionStatus = useActionStatus(actionIsNaviAction ? action : undefined);
63596
+ const [actionRunning, setActionRunning] = useState(false);
63597
+ const runAction = async (...args) => {
63598
+ setActionRunning(true);
63599
+ try {
63600
+ return await action(...args);
63601
+ } finally {
63602
+ setActionRunning(false);
63603
+ }
63604
+ };
63605
+ const actionResolved = actionIsNaviAction ? action : action ? runAction : undefined;
63606
+ const loadingResolved = Boolean(loading || actionRunning || naviActionStatus.loading);
63607
+ const halfProps = {
63608
+ cta,
63609
+ variant,
63610
+ color,
63611
+ backgroundColor,
63612
+ borderColor,
63613
+ borderWidth,
63614
+ borderRadius,
63615
+ pressEffect,
63616
+ loading: loadingResolved,
63617
+ readOnly,
63618
+ disabled,
63619
+ // The outline belongs around the pair, drawn below.
63620
+ loadingOutline: false
63621
+ };
63622
+ return jsxs(Box, {
63623
+ ref: rootRef,
63624
+ className: "navi_split_button",
63625
+ borderRadius: borderRadius,
63626
+ ...boxProps,
63627
+ children: [jsx(LoadingOutline, {
63628
+ loading: loadingResolved,
63629
+ inset: -1,
63630
+ color: "var(--button-loader-color)"
63631
+ }), jsxs(Group, {
63632
+ children: [jsx(Button, {
63633
+ id: idResolved,
63634
+ value: valueResolved,
63635
+ action: actionResolved,
63636
+ ...halfProps,
63637
+ children: label === undefined ? optionShown?.label : label
63638
+ }), jsxs("div", {
63639
+ className: "navi_split_button_menu",
63640
+ children: [jsx(Button, {
63641
+ icon: true,
63642
+ paddingX: "s",
63643
+ expandY: true
63644
+ // flex + align: the icon is the whole content, so it is laid out as
63645
+ // its own box in the middle of the button. Left to flow inline it
63646
+ // would sit on the baseline of a line of text that is not there,
63647
+ // with the descender space still kept below it.
63648
+ ,
63649
+
63650
+ flex: true,
63651
+ align: "center",
63652
+ "aria-label": menuLabel,
63653
+ command: "--navi-open",
63654
+ commandFor: menuId,
63655
+ ...halfProps,
63656
+ children: jsx(Icon, {
63657
+ size: menuIconSize,
63658
+ lineOverflow: "allow",
63659
+ children: menuIcon
63660
+ })
63661
+ }), jsx(Picker, {
63662
+ id: menuId,
63663
+ variant: "headless",
63664
+ allowNameless: true,
63665
+ anchor: rootRef,
63666
+ ...popupProps,
63667
+ readOnly: readOnly,
63668
+ disabled: disabled,
63669
+ loading: loadingResolved,
63670
+ value: chooseEffect === "select" ? valueResolved : undefined,
63671
+ action: chooseEffect === "select" ? (chosenValue, event) => {
63672
+ setValueState(chosenValue);
63673
+ onValueChange?.(chosenValue, event);
63674
+ } : undefined,
63675
+ children: chooseEffect === "select" ? jsx(List, {
63676
+ selectable: true,
63677
+ command: "--navi-send",
63678
+ children: options.map((option, index) => {
63679
+ const {
63680
+ value: optionValue,
63681
+ label: optionLabel,
63682
+ ...optionRest
63683
+ } = option;
63684
+ return jsx(List.Item, {
63685
+ selectable: true,
63686
+ id: `${menuId}_${index}`,
63687
+ index: index,
63688
+ value: optionValue,
63689
+ selected: optionValue === valueResolved,
63690
+ padding: "s",
63691
+ spacing: "s",
63692
+ ...optionRest,
63693
+ children: optionLabel
63694
+ }, optionValue);
63695
+ })
63696
+ }) : jsx(List, {
63697
+ children: options.map((option, index) => {
63698
+ const {
63699
+ value: optionValue,
63700
+ label: optionLabel,
63701
+ ...optionRest
63702
+ } = option;
63703
+ return jsx(List.Item, {
63704
+ id: `${menuId}_${index}`,
63705
+ index: index,
63706
+ padding: "0",
63707
+ children: jsx(Button, {
63708
+ variant: "bare",
63709
+ expandX: true,
63710
+ alignX: "start",
63711
+ padding: "s",
63712
+ pressEffect: "none",
63713
+ command: "--navi-close",
63714
+ commandFor: menuId,
63715
+ value: optionValue,
63716
+ action: actionResolved,
63717
+ ...optionRest,
63718
+ children: optionLabel
63719
+ })
63720
+ }, optionValue);
63721
+ })
63722
+ })
63723
+ })]
63724
+ })]
63725
+ })]
63726
+ });
63727
+ };
63728
+
63729
+ // What the Picker's popup answers to — Picker's own popup props, named here so
63730
+ // a caller reaches all of them through the split button (see picker.jsx's JSDoc
63731
+ // for what each one says).
63732
+ const POPUP_PROP_SET = new Set(["mode", "popupLayer", "positionArea", "popoverMode", "popoverSpacing", "popupWidthFitContent", "popoverMaxHeight", "dialogMaxWidth", "dialogMaxHeight", "dialogExpand", "dialogExpandX", "dialogExpandY", "dockedOnSmallTouchScreen", "marginWithContainer", "backdropVariant", "pointerInteractionOutsideEffect", "escapeEffect", "closeOnFocusOut", "scrollCapture", "focusCapture", "popupBackgroundColor", "popupBorderRadius", "animation"]);
63733
+ const splitPopupProps = props => {
63734
+ const popupProps = {};
63735
+ const boxProps = {};
63736
+ for (const key of Object.keys(props)) {
63737
+ if (POPUP_PROP_SET.has(key)) {
63738
+ popupProps[key] = props[key];
63739
+ } else {
63740
+ boxProps[key] = props[key];
63741
+ }
63742
+ }
63743
+ return [popupProps, boxProps];
63744
+ };
63745
+
63259
63746
  /**
63260
63747
  * applySearch — matches value against searchText.
63261
63748
  *
@@ -71924,5 +72411,5 @@ const UserSvg = () => jsx("svg", {
71924
72411
  })
71925
72412
  });
71926
72413
 
71927
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeSpin, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
72414
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeSpin, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
71928
72415
  //# sourceMappingURL=jsenv_navi.js.map