@jsenv/navi 0.29.471 → 0.29.473

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.
@@ -43943,6 +43943,7 @@ const useRenderScope = (init, update) => {
43943
43943
  * registerChild(child): void; // Called on child mount
43944
43944
  * onChildUIAction(child, e, { stateChanged: boolean }): void; // Called when a child fires a UI action
43945
43945
  * unregisterChild(child): void; // Called on child unmount
43946
+ * collectsChild(child): boolean; // Whether registerChild(child) would put it in a value, here or above
43946
43947
  * props: Object;
43947
43948
  * ref: Ref; // Used to dispatch DOM events
43948
43949
  * getManagedControls(): UIStateController[]; // Returns controls whose validity is managed by this controller
@@ -44667,6 +44668,10 @@ const useUIStateController = (
44667
44668
  unregisterChild: (childUIStateController) => {
44668
44669
  s.parentUIStateController?.unregisterChild(childUIStateController);
44669
44670
  },
44671
+ collectsChild: (childUIStateController) =>
44672
+ Boolean(
44673
+ s.parentUIStateController?.collectsChild(childUIStateController),
44674
+ ),
44670
44675
  onChildUIAction: (childUIStateController, e, options) => {
44671
44676
  s.parentUIStateController?.onChildUIAction(
44672
44677
  childUIStateController,
@@ -45053,10 +45058,13 @@ const readNamedChildUIStates = (children, { warnNameless }) => {
45053
45058
  // Looked up in useUIGroupStateController to fill in omitted aggregateChildStates /
45054
45059
  // distributeChildUIState. If neither a default nor an explicit impl is found for a
45055
45060
  // group, creation throws so the caller knows it must supply them.
45061
+ // The radio/checkbox filters read `props.type`, which controlHostProps copies:
45062
+ // a child is also asked about while rendering, before its first render has
45063
+ // built controlHostProps (see collectsChild).
45056
45064
  const GROUP_DEFAULTS = {
45057
45065
  radio_group: {
45058
45066
  childControlFilter: (child) =>
45059
- child.controlType === "input" && child.controlHostProps?.type === "radio",
45067
+ child.controlType === "input" && child.props?.type === "radio",
45060
45068
  aggregateChildStates: keptOrFirstDefinedChildUIState,
45061
45069
  distributeChildUIState: (newUIState, childUIStateController) => {
45062
45070
  const childSelected = childUIStateController.props.value === newUIState;
@@ -45068,8 +45076,7 @@ const GROUP_DEFAULTS = {
45068
45076
  },
45069
45077
  checkbox_group: {
45070
45078
  childControlFilter: (child) =>
45071
- child.controlType === "input" &&
45072
- child.controlHostProps?.type === "checkbox",
45079
+ child.controlType === "input" && child.props?.type === "checkbox",
45073
45080
  aggregateChildStates: keptAndCheckedChildUIStates,
45074
45081
  distributeChildUIState: (newUIState, childUIStateController) => {
45075
45082
  const childSelected =
@@ -45614,6 +45621,9 @@ const useUIGroupStateController = (
45614
45621
 
45615
45622
  const controller = {
45616
45623
  controlType,
45624
+ // Published from the first render, like a leaf's: a group asks its
45625
+ // parent whether anyone listens before its first update.
45626
+ parentUIStateController,
45617
45627
  id,
45618
45628
  name,
45619
45629
  value,
@@ -45837,6 +45847,18 @@ const useUIGroupStateController = (
45837
45847
  notifyExternal: "silent",
45838
45848
  });
45839
45849
  },
45850
+ // registerChild above, asked ahead: monitored here, handed up, or
45851
+ // captured and dropped.
45852
+ collectsChild: (childUIStateController) => {
45853
+ if (isMonitoringChild(childUIStateController)) {
45854
+ return true;
45855
+ }
45856
+ const currentParent = s.parentUIStateController;
45857
+ if (allowCapture || !currentParent) {
45858
+ return false;
45859
+ }
45860
+ return currentParent.collectsChild(childUIStateController);
45861
+ },
45840
45862
  onChildUIAction: (
45841
45863
  childUIStateController,
45842
45864
  e,
@@ -46314,6 +46336,9 @@ const useUIFacadeStateController = (props, realUIStateController) => {
46314
46336
  s.realUIStateController.facadeChild = null;
46315
46337
  }
46316
46338
  },
46339
+ // A refused child is not handed further up: the popup's controls
46340
+ // answer to the picker or to nobody.
46341
+ collectsChild: (child) => canRegisterAsFacadeChild(child),
46317
46342
  getManagedControls: () => {
46318
46343
  const child = firstChildControllerRef.current;
46319
46344
  if (!child) {
@@ -46934,8 +46959,6 @@ const useControlProps = (props, {
46934
46959
  const controlInfo = createControlInfo(props, {
46935
46960
  controlType
46936
46961
  });
46937
- const readOnlyUncontrolled = useReadOnlyUncontrolled(props, controlInfo);
46938
- controlInfo.readOnlyUncontrolled = readOnlyUncontrolled;
46939
46962
  const uiStateController = useUIStateController(props, {
46940
46963
  controlInfo,
46941
46964
  syncDomState,
@@ -46943,6 +46966,9 @@ const useControlProps = (props, {
46943
46966
  persists,
46944
46967
  uiActionInternal
46945
46968
  });
46969
+ // After the controller: whether a group listens is asked of the parent it
46970
+ // registers into. Read by useInteractiveProps below.
46971
+ controlInfo.readOnlyUncontrolled = useReadOnlyUncontrolled(props, controlInfo, uiStateController);
46946
46972
  // What the control draws is what it holds by itself, and this read is what
46947
46973
  // re-renders it when that changes. A button inheriting the value of the
46948
46974
  // control around it (a submit in a form, see ownUIStateSignal) draws none of
@@ -47948,16 +47974,21 @@ const INPUT_TYPE_SUPPORTING_READONLY_SET = new Set(["text", "date", "datetime-lo
47948
47974
  // warning below so a group can ask the same question a single control does: a
47949
47975
  // control with a `value` and nobody listening cannot be changed by hand, and
47950
47976
  // that is true whatever the control is.
47951
- const useIsControlListenedTo = props => {
47977
+ // The group is asked, not assumed from a controller being above: a control in
47978
+ // between may only pass registration along (a <Details> holds none of the
47979
+ // values it folds), and a group may hand the control further up or drop it.
47980
+ const useIsControlListenedTo = (props, uiStateController) => {
47952
47981
  const isProxy = Boolean(props["navi-control-proxy-for"]);
47953
47982
  const formContext = useContext(FormContext);
47954
- const parentUIStateController = useContext(ParentUIStateControllerContext);
47983
+ const {
47984
+ parentUIStateController
47985
+ } = uiStateController;
47955
47986
  return Boolean(props.signal ||
47956
47987
  // a bound signal is written back on uiAction → interactive
47957
- props.uiAction || props.action || formContext || parentUIStateController || isProxy || props.command);
47988
+ props.uiAction || props.action || formContext || parentUIStateController?.collectsChild(uiStateController) || isProxy || props.command);
47958
47989
  };
47959
- const useReadOnlyUncontrolled = (props, controlInfo) => {
47960
- const listenedTo = useIsControlListenedTo(props);
47990
+ const useReadOnlyUncontrolled = (props, controlInfo, uiStateController) => {
47991
+ const listenedTo = useIsControlListenedTo(props, uiStateController);
47961
47992
  const warnedRef = useRef(false);
47962
47993
  if (!controlInfo.hasStateProp) {
47963
47994
  return false;
@@ -48025,7 +48056,7 @@ const useControlgroupProps = (props, {
48025
48056
  // makes the group read-only so children don't appear interactive when they
48026
48057
  // can't change. A form or a group around it IS someone listening — that is
48027
48058
  // what will send the value and hand a new one back.
48028
- const listenedTo = useIsControlListenedTo(props);
48059
+ const listenedTo = useIsControlListenedTo(props, uiGroupStateController);
48029
48060
  const implicitReadOnlyWarnedRef = useRef(false);
48030
48061
  const implicitReadOnly = uiGroupStateController.hasValueProp && !listenedTo;
48031
48062
  if (implicitReadOnly && !props.readOnly) {
@@ -52857,10 +52888,16 @@ const LinkWithRoute = ({
52857
52888
  currentExcept,
52858
52889
  currentAlso,
52859
52890
  children,
52891
+ // Kept out of `rest`: spread after the route's url, an `href={undefined}`
52892
+ // riding along would erase it.
52893
+ href,
52860
52894
  ...rest
52861
52895
  }) => {
52862
52896
  {
52863
52897
  assertRoute(route);
52898
+ if (href !== undefined) {
52899
+ console.warn(`A link with a "route" ignores its "href" (${href}): the route builds the address.`);
52900
+ }
52864
52901
  }
52865
52902
  const url = route.buildUrl(routeParams);
52866
52903
  {
@@ -58039,15 +58076,21 @@ const ButtonRouteResolver = props => {
58039
58076
  };
58040
58077
  const ButtonWithRoute = props => {
58041
58078
  const Next = useNextResolver();
58079
+ // `href` is kept out of `rest`: spread after the route's url, an
58080
+ // `href={undefined}` riding along would erase it.
58042
58081
  const {
58043
58082
  route,
58044
58083
  routeParams,
58045
58084
  children,
58046
58085
  pseudoState,
58086
+ href,
58047
58087
  ...rest
58048
58088
  } = props;
58049
58089
  {
58050
58090
  assertRoute(route);
58091
+ if (href !== undefined) {
58092
+ console.warn(`A button with a "route" ignores its "href" (${href}): the route builds the address.`);
58093
+ }
58051
58094
  }
58052
58095
  const url = route.buildUrl(routeParams);
58053
58096
  const linkMatching = useRouteCurrent(route, routeParams);
@@ -74434,10 +74477,18 @@ const PickerCustom = props => {
74434
74477
  props.id = props.id || controlId || idDefault;
74435
74478
  // Given no value, the picker holds what the control in its popup holds (see
74436
74479
  // the `mount` decision below) — and a group around it collects that from the
74437
- // moment both exist.
74438
- const valueInPopup = !isControlValueGivenByProps(props);
74480
+ // moment both exist. A picker that picks nothing holds none: its popup's
74481
+ // controls are never adopted (see canRegisterAsFacadeChild).
74482
+ const valueInPopup = !props.picksNothing && !isControlValueGivenByProps(props);
74483
+ // Asked of the chain, not read off the context: a control above may only
74484
+ // pass registration along (a <Details> holds none of the values it folds),
74485
+ // and a group may hand a picker further up or drop it. "picker" is what the
74486
+ // picker's own controller registers as (see picker.jsx).
74439
74487
  const parentController = useContext(ParentUIStateControllerContext);
74440
- const answersToGroup = Boolean(parentController) && !props.standalone;
74488
+ const answersToGroup = !props.standalone && Boolean(parentController?.collectsChild({
74489
+ controlType: "picker",
74490
+ props
74491
+ }));
74441
74492
  // Same narrow-container/maxWidth-compact heuristic Popup itself uses (see
74442
74493
  // popup_mode.jsx's own useResolvedPopupMode) — frozen for the lifetime of an opening
74443
74494
  // (computed when closed, stable while open, so a screen resize mid-session
@@ -75631,12 +75682,7 @@ const PickerConfirmResolver = props => {
75631
75682
  mode: mode,
75632
75683
  focusCapture: focusCapture,
75633
75684
  openWhileReadOnly: openWhileReadOnly,
75634
- onConfirm: onConfirm
75635
- // A question holds no value to read before it is asked: the popup is
75636
- // built on the first open, like that of a picker told its value (see
75637
- // the `mount` resolution in picker_custom.jsx).
75638
- ,
75639
- mount: MOUNT_DEFAULT,
75685
+ onConfirm: onConfirm,
75640
75686
  message: undefined,
75641
75687
  confirmLabel: undefined,
75642
75688
  cancelLabel: undefined,
@@ -87594,8 +87640,9 @@ const PickerFirstResolver = props => {
87594
87640
  * it (no `ui`, a `Picker.UI.*`, a component `ui`), when there is a clear
87595
87641
  * cross, or when a form or group around the picker collects it; when an
87596
87642
  * element `ui` calls `usePickerState()`. Read by nothing, it waits for the
87597
- * first open, like a picker told a value. A `type="confirm"` picker never
87598
- * builds them early: its popup is a question, there is nothing to read.
87643
+ * first open, like a picker told a value. A `picksNothing` picker (and so
87644
+ * `type="confirm"`) never builds them early: its popup holds no value, there
87645
+ * is nothing to read.
87599
87646
  */
87600
87647
  // Every picker type is resolved into one of these before it gets here (see
87601
87648
  // PickerTypeResolver and resolveInputProps): a native input type, or navi_js