@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.
@@ -14802,6 +14802,9 @@ const createOpenController = (
14802
14802
  // content is still waiting for a first open to be built. Called below,
14803
14803
  // before openEffect, so the popup measures and positions the real thing.
14804
14804
  mountContent: null,
14805
+ // Set alongside mountContent: the content asked for outside any opening,
14806
+ // by something that reads it while the popup is closed.
14807
+ buildContent: null,
14805
14808
  // The caller's own `onOpen`, set by Dialog/Popover from their props on
14806
14809
  // every render (like openEffect). Called BEFORE mountContent, so whatever
14807
14810
  // it decides — which record this dialog is opening on — is already true by
@@ -39204,7 +39207,11 @@ const flushSyncRendering = (fn) => {
39204
39207
  *
39205
39208
  * "always" is for content something else depends on before any opening: a
39206
39209
  * value the popup's owner reads off its own children, fields a form around it
39207
- * collects on submit, a size measured from outside.
39210
+ * collects on submit, a size measured from outside. When the popup's owner
39211
+ * cannot tell at mount whether anything will depend on it, the content waits
39212
+ * and whatever depends on it asks for it, closed, with
39213
+ * `openController.buildContent()` (a picker's drawing reading the value its
39214
+ * popup holds, see usePickerContext).
39208
39215
  *
39209
39216
  * "idle" is "always" minus the cost on the critical render: the page appears
39210
39217
  * without the content, and the browser builds it in an idle moment after
@@ -39331,6 +39338,14 @@ const usePopupContentMount = (
39331
39338
  }
39332
39339
  }
39333
39340
  };
39341
+ // A plain render rather than a flush: the popup stays closed, and nothing is
39342
+ // about to measure it.
39343
+ openController.buildContent = () => {
39344
+ if (contentMountedRef.current) {
39345
+ return;
39346
+ }
39347
+ setContentMounted(true);
39348
+ };
39334
39349
  openController.unmountContent =
39335
39350
  mount === "while-opened"
39336
39351
  ? () => {
@@ -67997,6 +68012,69 @@ const toPopoverAnimation = animation => {
67997
68012
  return animation;
67998
68013
  };
67999
68014
 
68015
+ const PickerContext = createContext();
68016
+
68017
+ /*
68018
+ * The picker's context, for whatever draws its value. A picker told no value
68019
+ * holds what the control in its popup holds, and builds that popup only when
68020
+ * something reads it (see the mount decision in picker_custom.jsx): what the
68021
+ * picker draws itself it knows about, a drawing given as an element is the
68022
+ * caller's, and says so here by reading. In a layout effect, so the frame that
68023
+ * first shows the drawing already shows the value.
68024
+ */
68025
+ const usePickerContext = () => {
68026
+ const context = useContext(PickerContext);
68027
+ const requestValue = context?.requestValue;
68028
+ useLayoutEffect(() => {
68029
+ requestValue?.();
68030
+ }, [requestValue]);
68031
+ return context;
68032
+ };
68033
+
68034
+ /*
68035
+ * A typed picker (`type="date"`, `type="array"`, …) draws its value with a ui
68036
+ * of navi's own, installed as the very "ui" prop a caller overrides to draw
68037
+ * that value themselves. Marking navi's own tells the two apart afterwards:
68038
+ * an empty value is greyed as a placeholder only when the drawing is navi's —
68039
+ * a caller's ui may well be their way of writing "no filter", which is an
68040
+ * answer, not a blank (see navi-placeholder in picker.jsx).
68041
+ */
68042
+ const asPickerOwnUI = PickerUI => {
68043
+ PickerUI.isPickerOwnUI = true;
68044
+ return PickerUI;
68045
+ };
68046
+ const pickerUIIsNaviOwn = ui => {
68047
+ return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
68048
+ };
68049
+
68050
+ /**
68051
+ * What the picker holds, read from inside its `ui` — for a drawing given as
68052
+ * an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
68053
+ * props to. `value` is the state the picker holds right now: the answer the
68054
+ * popup just closed on, before the server has said anything, and put back if
68055
+ * the action fails. `loading` is the run in flight; `interactive` is false
68056
+ * while the picker is disabled, read-only or busy.
68057
+ *
68058
+ * @returns {{ value: any, loading: boolean, interactive: boolean } | null}
68059
+ * null outside a picker.
68060
+ */
68061
+ const usePickerState = () => {
68062
+ const context = usePickerContext();
68063
+ if (!context) {
68064
+ return null;
68065
+ }
68066
+ const {
68067
+ value,
68068
+ loading,
68069
+ interactive
68070
+ } = context;
68071
+ return {
68072
+ value,
68073
+ loading,
68074
+ interactive
68075
+ };
68076
+ };
68077
+
68000
68078
  installImportMetaCssBuild(import.meta);
68001
68079
  const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
68002
68080
  display: none;
@@ -68224,6 +68302,12 @@ const PickerCustom = props => {
68224
68302
  const idDefault = useId();
68225
68303
  const controlId = useContext(ControlIdContext);
68226
68304
  props.id = props.id || controlId || idDefault;
68305
+ // Given no value, the picker holds what the control in its popup holds (see
68306
+ // the `mount` decision below) — and a group around it collects that from the
68307
+ // moment both exist.
68308
+ const valueInPopup = !isControlValueGivenByProps(props);
68309
+ const parentController = useContext(ParentUIStateControllerContext);
68310
+ const answersToGroup = Boolean(parentController) && !props.standalone;
68227
68311
  // Same narrow-container/maxWidth-compact heuristic Popup itself uses (see
68228
68312
  // popup_mode.jsx's own useResolvedPopupMode) — frozen for the lifetime of an opening
68229
68313
  // (computed when closed, stable while open, so a screen resize mid-session
@@ -68456,12 +68540,20 @@ const PickerCustom = props => {
68456
68540
  const requestInteraction = options => {
68457
68541
  dispatchRequestInteraction(ref.current, options);
68458
68542
  };
68543
+
68544
+ // What a drawing of the caller's calls as it reads the value (see
68545
+ // usePickerContext): the popup holding it, built while closed. Only when
68546
+ // `mount` is left to the picker — a caller who set it has said when.
68547
+ const requestValue = useStableCallback(() => {
68548
+ openController.buildContent();
68549
+ });
68459
68550
  const {
68460
68551
  onActionStart,
68461
68552
  children,
68462
68553
  uiAction: uiActionProp
68463
68554
  } = props;
68464
68555
  Object.assign(pickerProps, {
68556
+ "requestValue": valueInPopup && props.mount === undefined ? requestValue : undefined,
68465
68557
  "aria-expanded": Boolean(expanded),
68466
68558
  "onActionStart": e => {
68467
68559
  onActionStart?.(e);
@@ -68500,13 +68592,12 @@ const PickerCustom = props => {
68500
68592
  anchor: props.anchor || props.ref,
68501
68593
  openController,
68502
68594
  // A picker whose value was never given to it reads it off the control in
68503
- // its popup (see useUIFacadeStateController): the trigger shows what the
68504
- // list inside says is selected, so that list has to exist before anyone
68505
- // opens anything. Told a value — even an empty one — the picker owns it
68506
- // and pushes it down instead, leaving the popup free to build its
68507
- // content only when it is first opened (see popup_content_mount.js).
68508
- // A caller who knows better says so with the popup's own props.
68509
- mount: props.mount ?? (isControlValueGivenByProps(props) ? MOUNT_DEFAULT : "always"),
68595
+ // its popup (see useUIFacadeStateController), so that control is built as
68596
+ // soon as something reads the value: at mount when the picker sees the
68597
+ // reader, otherwise when a drawing of the caller's asks (requestValue
68598
+ // above). Told a value — even an empty one — the picker pushes it down,
68599
+ // and the popup waits for its first open (see popup_content_mount.js).
68600
+ mount: props.mount ?? (valueInPopup && isPickerValueReadAtMount(props, answersToGroup) ? "always" : MOUNT_DEFAULT),
68510
68601
  // Not on pickerProps (the trigger): commands.js's own
68511
68602
  // resolveClosestExpandable() does `el.closest("[aria-expanded]")` to
68512
68603
  // find where to dispatch navi_request_open/navi_request_close — and
@@ -68793,6 +68884,29 @@ const PickerCustom = props => {
68793
68884
  });
68794
68885
  };
68795
68886
 
68887
+ // Whether something reads a picker's value before its popup has opened, as
68888
+ // far as the picker sees from its own props: the drawing navi renders for it
68889
+ // (its own, or the typed one PickerTypeResolver installs — `ui` is still unset
68890
+ // here), one of navi's Picker.UI.*, a component handed `value`, the clear
68891
+ // cross, a form submitting it by name, a group it answers to. A headless
68892
+ // picker draws nothing, and an element `ui` is the caller's: it reads through
68893
+ // usePickerContext, which asks for the value then.
68894
+ const isPickerValueReadAtMount = (props, answersToGroup) => {
68895
+ if (props.name !== undefined || answersToGroup) {
68896
+ return true;
68897
+ }
68898
+ if (props.variant === "headless") {
68899
+ return false;
68900
+ }
68901
+ if (props.clearable) {
68902
+ return true;
68903
+ }
68904
+ const {
68905
+ ui
68906
+ } = props;
68907
+ return ui === undefined || typeof ui === "function" || pickerUIIsNaviOwn(ui);
68908
+ };
68909
+
68796
68910
  // A hold declared on something AROUND the picker (a card opened by
68797
68911
  // `openOn="longpress"`, holding this one in its drawing): the finger going
68798
68912
  // down may be the start of that hold, so this picker opens on the click — which
@@ -69308,52 +69422,6 @@ const PickerConfirmBody = ({
69308
69422
  });
69309
69423
  };
69310
69424
 
69311
- const PickerContext = createContext();
69312
-
69313
- /*
69314
- * A typed picker (`type="date"`, `type="array"`, …) draws its value with a ui
69315
- * of navi's own, installed as the very "ui" prop a caller overrides to draw
69316
- * that value themselves. Marking navi's own tells the two apart afterwards:
69317
- * an empty value is greyed as a placeholder only when the drawing is navi's —
69318
- * a caller's ui may well be their way of writing "no filter", which is an
69319
- * answer, not a blank (see navi-placeholder in picker.jsx).
69320
- */
69321
- const asPickerOwnUI = PickerUI => {
69322
- PickerUI.isPickerOwnUI = true;
69323
- return PickerUI;
69324
- };
69325
- const pickerUIIsNaviOwn = ui => {
69326
- return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
69327
- };
69328
-
69329
- /**
69330
- * What the picker holds, read from inside its `ui` — for a drawing given as
69331
- * an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
69332
- * props to. `value` is the state the picker holds right now: the answer the
69333
- * popup just closed on, before the server has said anything, and put back if
69334
- * the action fails. `loading` is the run in flight; `interactive` is false
69335
- * while the picker is disabled, read-only or busy.
69336
- *
69337
- * @returns {{ value: any, loading: boolean, interactive: boolean } | null}
69338
- * null outside a picker.
69339
- */
69340
- const usePickerState = () => {
69341
- const context = useContext(PickerContext);
69342
- if (!context) {
69343
- return null;
69344
- }
69345
- const {
69346
- value,
69347
- loading,
69348
- interactive
69349
- } = context;
69350
- return {
69351
- value,
69352
- loading,
69353
- interactive
69354
- };
69355
- };
69356
-
69357
69425
  const PickerNaviMinute = props => {
69358
69426
  const Next = useNextResolver();
69359
69427
  const {
@@ -77763,7 +77831,7 @@ const PickerObjectUI = /*#__PURE__*/asPickerOwnUI(() => {
77763
77831
  const {
77764
77832
  value,
77765
77833
  placeholder
77766
- } = useContext(PickerContext);
77834
+ } = usePickerContext();
77767
77835
  if (uiStateHoldsNothing(value)) {
77768
77836
  if (!placeholder) {
77769
77837
  return null;
@@ -77799,7 +77867,7 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
77799
77867
  value,
77800
77868
  placeholder,
77801
77869
  maxLines
77802
- } = useContext(PickerContext);
77870
+ } = usePickerContext();
77803
77871
  if (uiStateHoldsNothing(value)) {
77804
77872
  if (!placeholder) {
77805
77873
  return null;
@@ -77881,7 +77949,7 @@ const PickerColorUI = /*#__PURE__*/asPickerOwnUI(() => {
77881
77949
  const {
77882
77950
  value,
77883
77951
  placeholder
77884
- } = useContext(PickerContext);
77952
+ } = usePickerContext();
77885
77953
  if (!value) {
77886
77954
  if (!placeholder) {
77887
77955
  return jsx(Color, {});
@@ -77905,7 +77973,7 @@ const PickerDateUI = /*#__PURE__*/asPickerOwnUI(props => {
77905
77973
  const {
77906
77974
  value,
77907
77975
  placeholder
77908
- } = useContext(PickerContext);
77976
+ } = usePickerContext();
77909
77977
  if (!value) {
77910
77978
  if (!placeholder) {
77911
77979
  return jsx(Time, {
@@ -77937,7 +78005,7 @@ const PickerMonthUI = /*#__PURE__*/asPickerOwnUI(props => {
77937
78005
  const {
77938
78006
  value,
77939
78007
  placeholder
77940
- } = useContext(PickerContext);
78008
+ } = usePickerContext();
77941
78009
  if (!value) {
77942
78010
  if (!placeholder) {
77943
78011
  return jsx(Time, {
@@ -77968,7 +78036,7 @@ const PickerWeekUI = /*#__PURE__*/asPickerOwnUI(props => {
77968
78036
  const {
77969
78037
  value,
77970
78038
  placeholder
77971
- } = useContext(PickerContext);
78039
+ } = usePickerContext();
77972
78040
  if (!value) {
77973
78041
  if (!placeholder) {
77974
78042
  return jsx(Time, {
@@ -77999,7 +78067,7 @@ const PickerTimeUI = /*#__PURE__*/asPickerOwnUI(props => {
77999
78067
  const {
78000
78068
  value,
78001
78069
  placeholder
78002
- } = useContext(PickerContext);
78070
+ } = usePickerContext();
78003
78071
  if (!value) {
78004
78072
  if (!placeholder) {
78005
78073
  return jsx(Time, {
@@ -78030,7 +78098,7 @@ const PickerDurationUI = /*#__PURE__*/asPickerOwnUI(props => {
78030
78098
  const {
78031
78099
  value,
78032
78100
  placeholder
78033
- } = useContext(PickerContext);
78101
+ } = usePickerContext();
78034
78102
  if (!value) {
78035
78103
  if (!placeholder) {
78036
78104
  return jsx(Time, {
@@ -78060,7 +78128,7 @@ const PickerDatetimeUI = /*#__PURE__*/asPickerOwnUI(props => {
78060
78128
  const {
78061
78129
  value,
78062
78130
  placeholder
78063
- } = useContext(PickerContext);
78131
+ } = usePickerContext();
78064
78132
  if (!value) {
78065
78133
  if (!placeholder) {
78066
78134
  return jsx(Time, {
@@ -78089,7 +78157,7 @@ const PickerFileUI = /*#__PURE__*/asPickerOwnUI(() => {
78089
78157
  const {
78090
78158
  value,
78091
78159
  placeholder
78092
- } = useContext(PickerContext);
78160
+ } = usePickerContext();
78093
78161
  if (!value) {
78094
78162
  if (!placeholder) {
78095
78163
  return null;
@@ -78623,7 +78691,11 @@ const PickerButton = props => {
78623
78691
  // a piece of, waiting as a whole (see the `loadingOutline` doc).
78624
78692
  loadingOutline,
78625
78693
  // Where the outline runs, in px from the picker's box: negative is outside.
78626
- loadingOutlineInset = -2
78694
+ loadingOutlineInset = -2,
78695
+ // Builds the popup this picker's value is read from, for a drawing that
78696
+ // reads it (see usePickerContext). Set by PickerCustom, only while that
78697
+ // value lives in the popup.
78698
+ requestValue
78627
78699
  } = props;
78628
78700
  // A word in a sentence is never truncated — and the clamp's overflow: hidden
78629
78701
  // would cut its dotted underline, which sits on the edge of the line box
@@ -78719,7 +78791,8 @@ const PickerButton = props => {
78719
78791
  id: inputProps.id,
78720
78792
  interactive,
78721
78793
  loading,
78722
- clearConfirm
78794
+ clearConfirm,
78795
+ requestValue
78723
78796
  };
78724
78797
  return (
78725
78798
  /* Read-only crosses into everything the picker is made of: what it really
@@ -78770,7 +78843,8 @@ const PickerButton = props => {
78770
78843
  error: undefined,
78771
78844
  dayLabel: undefined,
78772
78845
  loadingOutline: undefined,
78773
- loadingOutlineInset: undefined
78846
+ loadingOutlineInset: undefined,
78847
+ requestValue: undefined
78774
78848
  // This wrapper will receive keyboard event bubbling from the picker popup content
78775
78849
  // we re-dispatch on the input (to get escape to close for instance)
78776
78850
  ,
@@ -78984,7 +79058,7 @@ const PickerClear = ({
78984
79058
  children,
78985
79059
  ...rest
78986
79060
  }) => {
78987
- const pickerContext = useContext(PickerContext);
79061
+ const pickerContext = usePickerContext();
78988
79062
  if (!pickerContext) {
78989
79063
  return null;
78990
79064
  }
@@ -79256,7 +79330,7 @@ const PickerDefaultUI = () => {
79256
79330
  const {
79257
79331
  value,
79258
79332
  placeholder
79259
- } = useContext(PickerContext);
79333
+ } = usePickerContext();
79260
79334
  if (!value) {
79261
79335
  if (!placeholder) {
79262
79336
  return null;