@jsenv/navi 0.29.424 → 0.29.425

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.
@@ -15389,6 +15389,9 @@ const createOpenController = (
15389
15389
  // content is still waiting for a first open to be built. Called below,
15390
15390
  // before openEffect, so the popup measures and positions the real thing.
15391
15391
  mountContent: null,
15392
+ // Set alongside mountContent: the content asked for outside any opening,
15393
+ // by something that reads it while the popup is closed.
15394
+ buildContent: null,
15392
15395
  // The caller's own `onOpen`, set by Dialog/Popover from their props on
15393
15396
  // every render (like openEffect). Called BEFORE mountContent, so whatever
15394
15397
  // it decides — which record this dialog is opening on — is already true by
@@ -40243,7 +40246,11 @@ const flushSyncRendering = (fn) => {
40243
40246
  *
40244
40247
  * "always" is for content something else depends on before any opening: a
40245
40248
  * value the popup's owner reads off its own children, fields a form around it
40246
- * collects on submit, a size measured from outside.
40249
+ * collects on submit, a size measured from outside. When the popup's owner
40250
+ * cannot tell at mount whether anything will depend on it, the content waits
40251
+ * and whatever depends on it asks for it, closed, with
40252
+ * `openController.buildContent()` (a picker's drawing reading the value its
40253
+ * popup holds, see usePickerContext).
40247
40254
  *
40248
40255
  * "idle" is "always" minus the cost on the critical render: the page appears
40249
40256
  * without the content, and the browser builds it in an idle moment after
@@ -40370,6 +40377,14 @@ const usePopupContentMount = (
40370
40377
  }
40371
40378
  }
40372
40379
  };
40380
+ // A plain render rather than a flush: the popup stays closed, and nothing is
40381
+ // about to measure it.
40382
+ openController.buildContent = () => {
40383
+ if (contentMountedRef.current) {
40384
+ return;
40385
+ }
40386
+ setContentMounted(true);
40387
+ };
40373
40388
  openController.unmountContent =
40374
40389
  mount === "while-opened"
40375
40390
  ? () => {
@@ -69366,6 +69381,69 @@ const toPopoverAnimation = animation => {
69366
69381
  return animation;
69367
69382
  };
69368
69383
 
69384
+ const PickerContext = createContext();
69385
+
69386
+ /*
69387
+ * The picker's context, for whatever draws its value. A picker told no value
69388
+ * holds what the control in its popup holds, and builds that popup only when
69389
+ * something reads it (see the mount decision in picker_custom.jsx): what the
69390
+ * picker draws itself it knows about, a drawing given as an element is the
69391
+ * caller's, and says so here by reading. In a layout effect, so the frame that
69392
+ * first shows the drawing already shows the value.
69393
+ */
69394
+ const usePickerContext = () => {
69395
+ const context = useContext(PickerContext);
69396
+ const requestValue = context?.requestValue;
69397
+ useLayoutEffect(() => {
69398
+ requestValue?.();
69399
+ }, [requestValue]);
69400
+ return context;
69401
+ };
69402
+
69403
+ /*
69404
+ * A typed picker (`type="date"`, `type="array"`, …) draws its value with a ui
69405
+ * of navi's own, installed as the very "ui" prop a caller overrides to draw
69406
+ * that value themselves. Marking navi's own tells the two apart afterwards:
69407
+ * an empty value is greyed as a placeholder only when the drawing is navi's —
69408
+ * a caller's ui may well be their way of writing "no filter", which is an
69409
+ * answer, not a blank (see navi-placeholder in picker.jsx).
69410
+ */
69411
+ const asPickerOwnUI = PickerUI => {
69412
+ PickerUI.isPickerOwnUI = true;
69413
+ return PickerUI;
69414
+ };
69415
+ const pickerUIIsNaviOwn = ui => {
69416
+ return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
69417
+ };
69418
+
69419
+ /**
69420
+ * What the picker holds, read from inside its `ui` — for a drawing given as
69421
+ * an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
69422
+ * props to. `value` is the state the picker holds right now: the answer the
69423
+ * popup just closed on, before the server has said anything, and put back if
69424
+ * the action fails. `loading` is the run in flight; `interactive` is false
69425
+ * while the picker is disabled, read-only or busy.
69426
+ *
69427
+ * @returns {{ value: any, loading: boolean, interactive: boolean } | null}
69428
+ * null outside a picker.
69429
+ */
69430
+ const usePickerState = () => {
69431
+ const context = usePickerContext();
69432
+ if (!context) {
69433
+ return null;
69434
+ }
69435
+ const {
69436
+ value,
69437
+ loading,
69438
+ interactive
69439
+ } = context;
69440
+ return {
69441
+ value,
69442
+ loading,
69443
+ interactive
69444
+ };
69445
+ };
69446
+
69369
69447
  installImportMetaCssBuild(import.meta);
69370
69448
  const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
69371
69449
  display: none;
@@ -69593,6 +69671,12 @@ const PickerCustom = props => {
69593
69671
  const idDefault = useId();
69594
69672
  const controlId = useContext(ControlIdContext);
69595
69673
  props.id = props.id || controlId || idDefault;
69674
+ // Given no value, the picker holds what the control in its popup holds (see
69675
+ // the `mount` decision below) — and a group around it collects that from the
69676
+ // moment both exist.
69677
+ const valueInPopup = !isControlValueGivenByProps(props);
69678
+ const parentController = useContext(ParentUIStateControllerContext);
69679
+ const answersToGroup = Boolean(parentController) && !props.standalone;
69596
69680
  // Same narrow-container/maxWidth-compact heuristic Popup itself uses (see
69597
69681
  // popup_mode.jsx's own useResolvedPopupMode) — frozen for the lifetime of an opening
69598
69682
  // (computed when closed, stable while open, so a screen resize mid-session
@@ -69838,12 +69922,20 @@ const PickerCustom = props => {
69838
69922
  const requestInteraction = options => {
69839
69923
  dispatchRequestInteraction(ref.current, options);
69840
69924
  };
69925
+
69926
+ // What a drawing of the caller's calls as it reads the value (see
69927
+ // usePickerContext): the popup holding it, built while closed. Only when
69928
+ // `mount` is left to the picker — a caller who set it has said when.
69929
+ const requestValue = useStableCallback(() => {
69930
+ openController.buildContent();
69931
+ });
69841
69932
  const {
69842
69933
  onActionStart,
69843
69934
  children,
69844
69935
  uiAction: uiActionProp
69845
69936
  } = props;
69846
69937
  Object.assign(pickerProps, {
69938
+ "requestValue": valueInPopup && props.mount === undefined ? requestValue : undefined,
69847
69939
  "aria-expanded": Boolean(expanded),
69848
69940
  "onActionStart": e => {
69849
69941
  onActionStart?.(e);
@@ -69882,13 +69974,12 @@ const PickerCustom = props => {
69882
69974
  anchor: props.anchor || props.ref,
69883
69975
  openController,
69884
69976
  // A picker whose value was never given to it reads it off the control in
69885
- // its popup (see useUIFacadeStateController): the trigger shows what the
69886
- // list inside says is selected, so that list has to exist before anyone
69887
- // opens anything. Told a value — even an empty one — the picker owns it
69888
- // and pushes it down instead, leaving the popup free to build its
69889
- // content only when it is first opened (see popup_content_mount.js).
69890
- // A caller who knows better says so with the popup's own props.
69891
- mount: props.mount ?? (isControlValueGivenByProps(props) ? MOUNT_DEFAULT : "always"),
69977
+ // its popup (see useUIFacadeStateController), so that control is built as
69978
+ // soon as something reads the value: at mount when the picker sees the
69979
+ // reader, otherwise when a drawing of the caller's asks (requestValue
69980
+ // above). Told a value — even an empty one — the picker pushes it down,
69981
+ // and the popup waits for its first open (see popup_content_mount.js).
69982
+ mount: props.mount ?? (valueInPopup && isPickerValueReadAtMount(props, answersToGroup) ? "always" : MOUNT_DEFAULT),
69892
69983
  // Not on pickerProps (the trigger): commands.js's own
69893
69984
  // resolveClosestExpandable() does `el.closest("[aria-expanded]")` to
69894
69985
  // find where to dispatch navi_request_open/navi_request_close — and
@@ -70176,6 +70267,29 @@ const PickerCustom = props => {
70176
70267
  });
70177
70268
  };
70178
70269
 
70270
+ // Whether something reads a picker's value before its popup has opened, as
70271
+ // far as the picker sees from its own props: the drawing navi renders for it
70272
+ // (its own, or the typed one PickerTypeResolver installs — `ui` is still unset
70273
+ // here), one of navi's Picker.UI.*, a component handed `value`, the clear
70274
+ // cross, a form submitting it by name, a group it answers to. A headless
70275
+ // picker draws nothing, and an element `ui` is the caller's: it reads through
70276
+ // usePickerContext, which asks for the value then.
70277
+ const isPickerValueReadAtMount = (props, answersToGroup) => {
70278
+ if (props.name !== undefined || answersToGroup) {
70279
+ return true;
70280
+ }
70281
+ if (props.variant === "headless") {
70282
+ return false;
70283
+ }
70284
+ if (props.clearable) {
70285
+ return true;
70286
+ }
70287
+ const {
70288
+ ui
70289
+ } = props;
70290
+ return ui === undefined || typeof ui === "function" || pickerUIIsNaviOwn(ui);
70291
+ };
70292
+
70179
70293
  // A hold declared on something AROUND the picker (a card opened by
70180
70294
  // `openOn="longpress"`, holding this one in its drawing): the finger going
70181
70295
  // down may be the start of that hold, so this picker opens on the click — which
@@ -70691,52 +70805,6 @@ const PickerConfirmBody = ({
70691
70805
  });
70692
70806
  };
70693
70807
 
70694
- const PickerContext = createContext();
70695
-
70696
- /*
70697
- * A typed picker (`type="date"`, `type="array"`, …) draws its value with a ui
70698
- * of navi's own, installed as the very "ui" prop a caller overrides to draw
70699
- * that value themselves. Marking navi's own tells the two apart afterwards:
70700
- * an empty value is greyed as a placeholder only when the drawing is navi's —
70701
- * a caller's ui may well be their way of writing "no filter", which is an
70702
- * answer, not a blank (see navi-placeholder in picker.jsx).
70703
- */
70704
- const asPickerOwnUI = PickerUI => {
70705
- PickerUI.isPickerOwnUI = true;
70706
- return PickerUI;
70707
- };
70708
- const pickerUIIsNaviOwn = ui => {
70709
- return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
70710
- };
70711
-
70712
- /**
70713
- * What the picker holds, read from inside its `ui` — for a drawing given as
70714
- * an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
70715
- * props to. `value` is the state the picker holds right now: the answer the
70716
- * popup just closed on, before the server has said anything, and put back if
70717
- * the action fails. `loading` is the run in flight; `interactive` is false
70718
- * while the picker is disabled, read-only or busy.
70719
- *
70720
- * @returns {{ value: any, loading: boolean, interactive: boolean } | null}
70721
- * null outside a picker.
70722
- */
70723
- const usePickerState = () => {
70724
- const context = useContext(PickerContext);
70725
- if (!context) {
70726
- return null;
70727
- }
70728
- const {
70729
- value,
70730
- loading,
70731
- interactive
70732
- } = context;
70733
- return {
70734
- value,
70735
- loading,
70736
- interactive
70737
- };
70738
- };
70739
-
70740
70808
  const PickerNaviMinute = props => {
70741
70809
  const Next = useNextResolver();
70742
70810
  const {
@@ -79253,7 +79321,7 @@ const PickerObjectUI = /*#__PURE__*/asPickerOwnUI(() => {
79253
79321
  const {
79254
79322
  value,
79255
79323
  placeholder
79256
- } = useContext(PickerContext);
79324
+ } = usePickerContext();
79257
79325
  if (uiStateHoldsNothing(value)) {
79258
79326
  if (!placeholder) {
79259
79327
  return null;
@@ -79289,7 +79357,7 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
79289
79357
  value,
79290
79358
  placeholder,
79291
79359
  maxLines
79292
- } = useContext(PickerContext);
79360
+ } = usePickerContext();
79293
79361
  if (uiStateHoldsNothing(value)) {
79294
79362
  if (!placeholder) {
79295
79363
  return null;
@@ -79371,7 +79439,7 @@ const PickerColorUI = /*#__PURE__*/asPickerOwnUI(() => {
79371
79439
  const {
79372
79440
  value,
79373
79441
  placeholder
79374
- } = useContext(PickerContext);
79442
+ } = usePickerContext();
79375
79443
  if (!value) {
79376
79444
  if (!placeholder) {
79377
79445
  return jsx(Color, {});
@@ -79395,7 +79463,7 @@ const PickerDateUI = /*#__PURE__*/asPickerOwnUI(props => {
79395
79463
  const {
79396
79464
  value,
79397
79465
  placeholder
79398
- } = useContext(PickerContext);
79466
+ } = usePickerContext();
79399
79467
  if (!value) {
79400
79468
  if (!placeholder) {
79401
79469
  return jsx(Time, {
@@ -79427,7 +79495,7 @@ const PickerMonthUI = /*#__PURE__*/asPickerOwnUI(props => {
79427
79495
  const {
79428
79496
  value,
79429
79497
  placeholder
79430
- } = useContext(PickerContext);
79498
+ } = usePickerContext();
79431
79499
  if (!value) {
79432
79500
  if (!placeholder) {
79433
79501
  return jsx(Time, {
@@ -79458,7 +79526,7 @@ const PickerWeekUI = /*#__PURE__*/asPickerOwnUI(props => {
79458
79526
  const {
79459
79527
  value,
79460
79528
  placeholder
79461
- } = useContext(PickerContext);
79529
+ } = usePickerContext();
79462
79530
  if (!value) {
79463
79531
  if (!placeholder) {
79464
79532
  return jsx(Time, {
@@ -79489,7 +79557,7 @@ const PickerTimeUI = /*#__PURE__*/asPickerOwnUI(props => {
79489
79557
  const {
79490
79558
  value,
79491
79559
  placeholder
79492
- } = useContext(PickerContext);
79560
+ } = usePickerContext();
79493
79561
  if (!value) {
79494
79562
  if (!placeholder) {
79495
79563
  return jsx(Time, {
@@ -79520,7 +79588,7 @@ const PickerDurationUI = /*#__PURE__*/asPickerOwnUI(props => {
79520
79588
  const {
79521
79589
  value,
79522
79590
  placeholder
79523
- } = useContext(PickerContext);
79591
+ } = usePickerContext();
79524
79592
  if (!value) {
79525
79593
  if (!placeholder) {
79526
79594
  return jsx(Time, {
@@ -79550,7 +79618,7 @@ const PickerDatetimeUI = /*#__PURE__*/asPickerOwnUI(props => {
79550
79618
  const {
79551
79619
  value,
79552
79620
  placeholder
79553
- } = useContext(PickerContext);
79621
+ } = usePickerContext();
79554
79622
  if (!value) {
79555
79623
  if (!placeholder) {
79556
79624
  return jsx(Time, {
@@ -79579,7 +79647,7 @@ const PickerFileUI = /*#__PURE__*/asPickerOwnUI(() => {
79579
79647
  const {
79580
79648
  value,
79581
79649
  placeholder
79582
- } = useContext(PickerContext);
79650
+ } = usePickerContext();
79583
79651
  if (!value) {
79584
79652
  if (!placeholder) {
79585
79653
  return null;
@@ -80115,7 +80183,11 @@ const PickerButton = props => {
80115
80183
  // a piece of, waiting as a whole (see the `loadingOutline` doc).
80116
80184
  loadingOutline,
80117
80185
  // Where the outline runs, in px from the picker's box: negative is outside.
80118
- loadingOutlineInset = -2
80186
+ loadingOutlineInset = -2,
80187
+ // Builds the popup this picker's value is read from, for a drawing that
80188
+ // reads it (see usePickerContext). Set by PickerCustom, only while that
80189
+ // value lives in the popup.
80190
+ requestValue
80119
80191
  } = props;
80120
80192
  // A word in a sentence is never truncated — and the clamp's overflow: hidden
80121
80193
  // would cut its dotted underline, which sits on the edge of the line box
@@ -80213,7 +80285,8 @@ const PickerButton = props => {
80213
80285
  id: inputProps.id,
80214
80286
  interactive,
80215
80287
  loading,
80216
- clearConfirm
80288
+ clearConfirm,
80289
+ requestValue
80217
80290
  };
80218
80291
  return (
80219
80292
  /* Read-only crosses into everything the picker is made of: what it really
@@ -80264,7 +80337,8 @@ const PickerButton = props => {
80264
80337
  error: undefined,
80265
80338
  dayLabel: undefined,
80266
80339
  loadingOutline: undefined,
80267
- loadingOutlineInset: undefined
80340
+ loadingOutlineInset: undefined,
80341
+ requestValue: undefined
80268
80342
  // This wrapper will receive keyboard event bubbling from the picker popup content
80269
80343
  // we re-dispatch on the input (to get escape to close for instance)
80270
80344
  ,
@@ -80478,7 +80552,7 @@ const PickerClear = ({
80478
80552
  children,
80479
80553
  ...rest
80480
80554
  }) => {
80481
- const pickerContext = useContext(PickerContext);
80555
+ const pickerContext = usePickerContext();
80482
80556
  if (!pickerContext) {
80483
80557
  warnOnClearOutsidePicker();
80484
80558
  return null;
@@ -80762,7 +80836,7 @@ const PickerDefaultUI = () => {
80762
80836
  const {
80763
80837
  value,
80764
80838
  placeholder
80765
- } = useContext(PickerContext);
80839
+ } = usePickerContext();
80766
80840
  if (!value) {
80767
80841
  if (!placeholder) {
80768
80842
  return null;
@@ -81336,10 +81410,13 @@ const PickerFirstResolver = props => {
81336
81410
  * @param {string} [cancelTestId]
81337
81411
  * @param {"always"|"idle"|"from-first-open"|"while-opened"} [mount] The popup's own
81338
81412
  * (see Popup): when the children are built and thrown away. Left out, a
81339
- * picker told no value builds them right away (`"always"`) — it reads its
81340
- * value off the control they hold — and one told a value waits for the first
81341
- * open. A `type="confirm"` picker never builds them early: its popup is a
81342
- * question, there is nothing to read.
81413
+ * picker told no value holds what the control they hold holds, and builds
81414
+ * them as soon as that value is read: right away (`"always"`) when navi draws
81415
+ * it (no `ui`, a `Picker.UI.*`, a component `ui`), when there is a clear
81416
+ * cross, or when a form or group around the picker collects it; when an
81417
+ * element `ui` calls `usePickerState()`. Read by nothing, it waits for the
81418
+ * first open, like a picker told a value. A `type="confirm"` picker never
81419
+ * builds them early: its popup is a question, there is nothing to read.
81343
81420
  */
81344
81421
  // Every picker type is resolved into one of these before it gets here (see
81345
81422
  // PickerTypeResolver and resolveInputProps): a native input type, or navi_js