@jsenv/navi 0.29.443 → 0.29.444

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.
@@ -6502,6 +6502,10 @@ const CONTROL_PROP_SET = new Set([
6502
6502
  "charGuard",
6503
6503
  "maxLengthGuard",
6504
6504
 
6505
+ // A checkbox that is a yes/no: `true` checked, `false` unchecked, instead of
6506
+ // HTML's value-or-nothing (see resolveEmptyUIState).
6507
+ "boolean",
6508
+
6505
6509
  // This control answers for itself: it registers into no group, no form and no
6506
6510
  // picker around it (see useUIStateController). It belongs here rather than
6507
6511
  // being deleted off `props` where it is read: props is the vnode's own object
@@ -43198,6 +43202,14 @@ const useUIStateController = (
43198
43202
  return ownUIState || parentUIState;
43199
43203
  })
43200
43204
  : ownUIStateSignal;
43205
+ // What the control is worth, which its action is bound to: what it
43206
+ // holds, or its own empty when it holds nothing. Apart from
43207
+ // uiStateSignal because unchecked is `undefined` there (the DOM reads any
43208
+ // other value as checked), while a yes/no unchecked is worth `false`.
43209
+ const emptyUIStateSignal = signal(emptyUIState);
43210
+ const valueSignal = computed(() =>
43211
+ uiStateOrEmpty(uiStateSignal.value, emptyUIStateSignal.value),
43212
+ );
43201
43213
 
43202
43214
  // The two-way half of a bound `signal` prop: setting it re-renders and
43203
43215
  // re-syncs via state_prop_change, but with the same value → guarded as a
@@ -43246,6 +43258,7 @@ const useUIStateController = (
43246
43258
  // travel command ended up carrying a picker's selection: see
43247
43259
  // resolveCommandValue in commands.js.
43248
43260
  ownUIStateSignal,
43261
+ valueSignal,
43249
43262
  value: controlInfo.value,
43250
43263
  // The suggestion this control started on — what tells a field showing
43251
43264
  // its default from one carrying an answer (see isUIStateHeld).
@@ -43309,8 +43322,12 @@ const useUIStateController = (
43309
43322
  }
43310
43323
  s.uiActionInternal?.(currentUIState, e);
43311
43324
  if (s.uiAction) {
43312
- debugUIState(`calling uiAction for ${controlType}`, currentUIState);
43313
- s.uiAction(currentUIState, e);
43325
+ const value = uiStateOrEmpty(
43326
+ currentUIState,
43327
+ controller.emptyUIState,
43328
+ );
43329
+ debugUIState(`calling uiAction for ${controlType}`, value);
43330
+ s.uiAction(value, e);
43314
43331
  }
43315
43332
  if (skipCommand) ; else {
43316
43333
  const command = controller.controlHostProps.command;
@@ -43814,6 +43831,7 @@ const useUIStateController = (
43814
43831
  return {
43815
43832
  controller,
43816
43833
  parentUiStateSignalHolder,
43834
+ emptyUIStateSignal,
43817
43835
  ...liveValues(),
43818
43836
  };
43819
43837
  },
@@ -43935,6 +43953,7 @@ const useUIStateController = (
43935
43953
  );
43936
43954
  scope.parentUiStateSignalHolder.value =
43937
43955
  parentUIStateController?.uiStateSignal ?? null;
43956
+ scope.emptyUIStateSignal.value = emptyUIState;
43938
43957
 
43939
43958
  const { controller } = scope;
43940
43959
  const controllerRef = controller.ref;
@@ -44145,14 +44164,11 @@ const keptAndNamedChildUIStates = (children, fallbackState, kept) => {
44145
44164
  const readNamedChildUIStates = (children, { warnNameless }) => {
44146
44165
  const values = {};
44147
44166
  for (const child of children) {
44148
- const { name, emptyUIState } = child;
44167
+ const { name } = child;
44149
44168
  // A control holding nothing writes its own empty, not a hole: the key is in
44150
44169
  // the object either way, and what is read from it keeps the shape the
44151
44170
  // reader was promised (see resolveEmptyUIState).
44152
- const uiState =
44153
- child.uiState === undefined && emptyUIState !== undefined
44154
- ? emptyUIState
44155
- : child.uiState;
44171
+ const uiState = uiStateOrEmpty(child.uiState, child.emptyUIState);
44156
44172
  if (!name) {
44157
44173
  if (isNamelessGrouping(child, uiState)) {
44158
44174
  Object.assign(
@@ -45499,11 +45515,10 @@ const useUIFacadeStateController = (props, realUIStateController) => {
45499
45515
  // whose last item was unselected holds [], the same thing clearing it
45500
45516
  // leaves — not a value that changes type on its owner the moment it
45501
45517
  // empties.
45502
- const { emptyUIState } = s.realUIStateController;
45503
- const uiStateToAdopt =
45504
- child.uiState === undefined && emptyUIState !== undefined
45505
- ? emptyUIState
45506
- : child.uiState;
45518
+ const uiStateToAdopt = uiStateOrEmpty(
45519
+ child.uiState,
45520
+ s.realUIStateController.emptyUIState,
45521
+ );
45507
45522
  s.realUIStateController.setUIState(uiStateToAdopt, propagateUpEvent);
45508
45523
  updatingRef.current = false;
45509
45524
  },
@@ -45821,8 +45836,9 @@ const resolveEmptyUIState = (props, controlType) => {
45821
45836
  if (controlType === "input" && props.type === "checkbox") {
45822
45837
  // A checkbox is a member of a set, the way HTML has it: checked it sends
45823
45838
  // its value ("on" by default), unchecked it sends nothing at all. Only one
45824
- // holding `true` is a yes/no, and a yes/no nobody said yes to is `false`.
45825
- return props.value === true ? false : undefined;
45839
+ // declared `boolean` is a yes/no, and a yes/no nobody said yes to is
45840
+ // `false`.
45841
+ return props.boolean ? false : undefined;
45826
45842
  }
45827
45843
  const stateShape = props["navi-state-shape"];
45828
45844
  if (stateShape === "array") {
@@ -45834,6 +45850,15 @@ const resolveEmptyUIState = (props, controlType) => {
45834
45850
  return undefined;
45835
45851
  };
45836
45852
 
45853
+ // What a control holding `uiState` is worth: its own empty when it holds
45854
+ // nothing, so the reader gets the shape it was promised.
45855
+ const uiStateOrEmpty = (uiState, emptyUIState) => {
45856
+ if (uiState === undefined && emptyUIState !== undefined) {
45857
+ return emptyUIState;
45858
+ }
45859
+ return uiState;
45860
+ };
45861
+
45837
45862
  // What a cleared control shows: its own empty, kept in the shape it was holding.
45838
45863
  const resolveClearedUIState = (controller) => {
45839
45864
  const { controlType, props } = controller;
@@ -46069,7 +46094,7 @@ const useControlProps = (props, {
46069
46094
  // of a form would re-render at each change of any of its fields.
46070
46095
  // eslint-disable-next-line no-unused-expressions
46071
46096
  uiStateController.ownUIStateSignal.value;
46072
- const boundAction = useAction(props.action, uiStateController.uiStateSignal);
46097
+ const boundAction = useAction(props.action, uiStateController.valueSignal);
46073
46098
  const [controlRootProps, controlHostProps] = useInteractiveProps(props, {
46074
46099
  uiStateController,
46075
46100
  boundAction,
@@ -58190,7 +58215,23 @@ const css$M = /* css */`@layer navi {
58190
58215
  const InputCheckbox = props => {
58191
58216
  const defaultRef = useRef();
58192
58217
  props.ref = props.ref || defaultRef;
58193
- props.value = props.value === undefined ? "on" : props.value;
58218
+ const valueWarnedRef = useRef(false);
58219
+ if (!valueWarnedRef.current) {
58220
+ // `true` is what `boolean` writes into `value` below, and Preact hands the
58221
+ // same props object back on a re-render the component triggers itself.
58222
+ if (props.boolean && props.value !== undefined && props.value !== true) {
58223
+ valueWarnedRef.current = true;
58224
+ console.warn(`[checkbox] "boolean" is true or false, its "value" is ignored: remove value={${JSON.stringify(props.value)}}.`);
58225
+ } else if (!props.boolean && props.value === true) {
58226
+ valueWarnedRef.current = true;
58227
+ console.warn(`[checkbox] value={true} is a value like any other: unchecked is undefined. For a yes/no (true or false), use the "boolean" prop.`);
58228
+ }
58229
+ }
58230
+ if (props.boolean) {
58231
+ props.value = true;
58232
+ } else if (props.value === undefined) {
58233
+ props.value = "on";
58234
+ }
58194
58235
  if (props.headless) {
58195
58236
  return jsx(InputCheckboxHeadless, {
58196
58237
  ...props,