@jsenv/navi 0.27.45 → 0.27.47

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.
@@ -22598,13 +22598,17 @@ const useUIStateController = (
22598
22598
  );
22599
22599
  }
22600
22600
  }
22601
- // Still fire uiAction so external listeners (e.g. signals) stay in
22602
- // sync, but do NOT fire the command and do NOT notify the parent —
22603
- // both would cause an infinite loop when a parent cascades state
22604
- // down to its children (child command would re-trigger the cascade).
22605
- uiStateController.onUIAction(e, {
22606
- skipCommand: true,
22607
- });
22601
+ // initial_state_push is pure initialization (equivalent to defaultValue on the
22602
+ // child itself): skip uiAction entirely so no side effects fire on mount.
22603
+ if (e.type !== "initial_state_push") {
22604
+ // Still fire uiAction so external listeners (e.g. signals) stay in
22605
+ // sync, but do NOT fire the command and do NOT notify the parent —
22606
+ // both would cause an infinite loop when a parent cascades state
22607
+ // down to its children (child command would re-trigger the cascade).
22608
+ uiStateController.onUIAction(e, {
22609
+ skipCommand: true,
22610
+ });
22611
+ }
22608
22612
  // Exception: when the facade propagates a child state change up to the
22609
22613
  // real picker input, also notify the parent group (e.g. Form) so it
22610
22614
  // keeps its cached aggregated state in sync and fires its own uiAction.
@@ -22741,7 +22745,6 @@ const useUIStateController = (
22741
22745
  uiStateController.rules = rules;
22742
22746
  return uiStateController;
22743
22747
  };
22744
-
22745
22748
  const NO_PARENT = [() => {}, () => {}, () => {}];
22746
22749
  const useParentControllerNotifiers = (
22747
22750
  parentUIStateController,
@@ -22821,6 +22824,90 @@ const useParentControllerNotifiers = (
22821
22824
  * **Filtering**: `childControlFilter` can exclude certain child types from aggregation
22822
22825
  * (e.g. ignoring buttons inside a selectable list).
22823
22826
  */
22827
+ const CANNOT_DERIVE = Symbol("cannot_derive");
22828
+
22829
+ // Default aggregate/distribute implementations keyed by controlType or stateType.
22830
+ // Looked up in useUIGroupStateController to fill in omitted aggregateChildStates /
22831
+ // distributeChildUIState. If neither a default nor an explicit impl is found for a
22832
+ // group, creation throws so the caller knows it must supply them.
22833
+ const GROUP_DEFAULTS = {
22834
+ radio_group: {
22835
+ childControlFilter: (child) =>
22836
+ child.controlType === "input" && child.controlHostProps?.type === "radio",
22837
+ aggregateChildStates: (children) => {
22838
+ for (const child of children) {
22839
+ const childUIState = child.uiState;
22840
+ if (childUIState !== undefined) {
22841
+ return childUIState;
22842
+ }
22843
+ }
22844
+ return undefined;
22845
+ },
22846
+ distributeChildUIState: (newUIState, childUIStateController) => {
22847
+ const childSelected = childUIStateController.props.value === newUIState;
22848
+ if (childSelected) {
22849
+ return childUIStateController.props.value;
22850
+ }
22851
+ return undefined;
22852
+ },
22853
+ },
22854
+ checkbox_group: {
22855
+ childControlFilter: (child) =>
22856
+ child.controlType === "input" &&
22857
+ child.controlHostProps?.type === "checkbox",
22858
+ aggregateChildStates: (children) => {
22859
+ const values = [];
22860
+ for (const child of children) {
22861
+ const childUIState = child.uiState;
22862
+ if (childUIState !== undefined) {
22863
+ values.push(childUIState);
22864
+ }
22865
+ }
22866
+ return values.length === 0 ? undefined : values;
22867
+ },
22868
+ distributeChildUIState: (newUIState, childUIStateController) => {
22869
+ const childSelected =
22870
+ Array.isArray(newUIState) &&
22871
+ newUIState.includes(childUIStateController.props.value);
22872
+ if (childSelected) {
22873
+ return childUIStateController.props.value;
22874
+ }
22875
+ return undefined;
22876
+ },
22877
+ },
22878
+ object: {
22879
+ aggregateChildStates: (children) => {
22880
+ const groupValues = {};
22881
+ for (const child of children) {
22882
+ const { name, uiState, allowNameless } = child;
22883
+ if (!name) {
22884
+ if (!allowNameless) {
22885
+ console.warn(
22886
+ "A group child is missing a name property, its state won't be included in the group state",
22887
+ child,
22888
+ );
22889
+ }
22890
+ continue;
22891
+ }
22892
+ groupValues[name] = uiState;
22893
+ }
22894
+ return groupValues;
22895
+ },
22896
+ distributeChildUIState: (newUIState, child) => {
22897
+ const childName = child.name;
22898
+ if (
22899
+ childName &&
22900
+ newUIState !== null &&
22901
+ typeof newUIState === "object" &&
22902
+ Object.prototype.hasOwnProperty.call(newUIState, childName)
22903
+ ) {
22904
+ return newUIState[childName];
22905
+ }
22906
+ return CANNOT_DERIVE;
22907
+ },
22908
+ },
22909
+ };
22910
+
22824
22911
  const useUIGroupStateController = (
22825
22912
  props,
22826
22913
  controlType,
@@ -22840,11 +22927,26 @@ const useUIGroupStateController = (
22840
22927
  const debugUIGroup = useDebugUIState();
22841
22928
  const debugFocus = useDebugFocus();
22842
22929
 
22843
- if (typeof aggregateChildStates !== "function") {
22844
- throw new TypeError("aggregateChildStates must be a function");
22930
+ const defaults = GROUP_DEFAULTS[controlType] ?? GROUP_DEFAULTS[stateType];
22931
+ const resolvedChildControlFilter =
22932
+ childControlFilter ?? defaults?.childControlFilter ?? null;
22933
+ const resolvedAggregateChildStates =
22934
+ aggregateChildStates ?? defaults?.aggregateChildStates;
22935
+ const resolvedDistributeChildUIState =
22936
+ distributeChildUIState ?? defaults?.distributeChildUIState;
22937
+ if (
22938
+ typeof resolvedAggregateChildStates !== "function" ||
22939
+ typeof resolvedDistributeChildUIState !== "function"
22940
+ ) {
22941
+ throw new Error(
22942
+ `No aggregate/distribute implementation found for controlType="${controlType}" stateType="${stateType}". ` +
22943
+ `Either use a known controlType/stateType or provide aggregateChildStates and distributeChildUIState explicitly.`,
22944
+ );
22845
22945
  }
22846
22946
  const parentUIStateController = useContext(ParentUIStateControllerContext);
22847
- const { id, name, value, uiAction, command } = props;
22947
+ const hasValueProp = Object.hasOwn(props, "value");
22948
+ const hasDefaultValueProp = Object.hasOwn(props, "defaultValue");
22949
+ const { id, name, value, defaultValue, uiAction, command } = props;
22848
22950
  const ref = props.ref;
22849
22951
  const uiActionRef = useRef(uiAction);
22850
22952
  const fallbackState =
@@ -22893,7 +22995,7 @@ const useUIGroupStateController = (
22893
22995
  pendingChangeRef.current = true;
22894
22996
  return;
22895
22997
  }
22896
- const aggChildState = aggregateChildStates(
22998
+ const aggChildState = resolvedAggregateChildStates(
22897
22999
  childUIStateControllerArray,
22898
23000
  fallbackState,
22899
23001
  );
@@ -22958,13 +23060,68 @@ const useUIGroupStateController = (
22958
23060
  }
22959
23061
  });
22960
23062
 
23063
+ const isMonitoringChild = (childUIStateController) => {
23064
+ if (childUIStateController.isProxy) {
23065
+ return false;
23066
+ }
23067
+ if (
23068
+ resolvedChildControlFilter &&
23069
+ !resolvedChildControlFilter(childUIStateController)
23070
+ ) {
23071
+ return false;
23072
+ }
23073
+ return true;
23074
+ };
23075
+ const shouldPropagateStateToChild = (childUIStateController) => {
23076
+ if (!isMonitoringChild(childUIStateController)) {
23077
+ return false;
23078
+ }
23079
+ if (childUIStateController.controlType === "button") {
23080
+ return false;
23081
+ }
23082
+ return true;
23083
+ };
23084
+
22961
23085
  const existingController = controllerRef.current;
22962
23086
  if (existingController) {
23087
+ const prevValue = existingController.value;
23088
+ const prevHasValueProp = existingController.hasValueProp;
22963
23089
  existingController.props = props;
22964
23090
  existingController.id = id;
22965
23091
  existingController.name = name;
22966
23092
  existingController.value = value;
23093
+ existingController.defaultValue = defaultValue;
23094
+ existingController.hasValueProp = hasValueProp;
23095
+ existingController.hasDefaultValueProp = hasDefaultValueProp;
22967
23096
  uiActionRef.current = uiAction;
23097
+ // When the controlled value prop changes (or when becoming controlled for the
23098
+ // first time), silently cascade to children that have no individual state prop.
23099
+ if (
23100
+ hasValueProp &&
23101
+ (!prevHasValueProp || !compareTwoJsValues(value, prevValue))
23102
+ ) {
23103
+ const propagateDownEvent = new CustomEvent(
23104
+ "propagate_down_set_ui_state",
23105
+ { detail: {} },
23106
+ );
23107
+ for (const childUIStateController of childUIStateControllerArray) {
23108
+ if (!shouldPropagateStateToChild(childUIStateController)) {
23109
+ continue;
23110
+ }
23111
+ if (childUIStateController.hasStateProp) {
23112
+ continue;
23113
+ }
23114
+ const childNewState = existingController.distributeChildUIState(
23115
+ value,
23116
+ childUIStateController,
23117
+ );
23118
+ if (childNewState === CANNOT_DERIVE) {
23119
+ continue;
23120
+ }
23121
+ childUIStateController.setUIState(childNewState, propagateDownEvent);
23122
+ }
23123
+ existingController.syncInternalState(value);
23124
+ }
22968
23125
  return existingController;
22969
23126
  }
22970
23127
  debugUIGroup(
@@ -22973,32 +23130,23 @@ const useUIGroupStateController = (
22973
23130
 
22974
23131
  const [publishUIState, subscribeUIState] = createPubSub();
22975
23132
  const uiStateSignal = signal(fallbackState);
22976
- const isMonitoringChild = (childUIStateController) => {
22977
- if (childUIStateController.isProxy) {
22978
- return false;
22979
- }
22980
- if (childControlFilter && !childControlFilter(childUIStateController)) {
22981
- return false;
22982
- }
22983
- return true;
22984
- };
22985
23133
  const groupUIStateController = {
22986
23134
  controlType,
22987
23135
  id,
22988
23136
  name,
22989
23137
  value,
23138
+ defaultValue,
23139
+ hasValueProp,
23140
+ hasDefaultValueProp,
22990
23141
  props,
22991
23142
  uiState: fallbackState,
22992
23143
  uiStateSignal,
22993
23144
  wantRequesterButtonState,
22994
23145
  elementRef: ref,
22995
23146
  getPropFromState: (uiState) => uiState,
22996
- // Cascades the new value to each monitored child (fires each child's uiAction
22997
- // via internalBehavior), then re-aggregates and fires this group's own reactions.
22998
- // Cascade strategy depends on controlType:
22999
- // - "radio_group": child gets true/false based on whether its value matches the scalar state.
23000
- // - "checkbox_group": child gets true/false based on whether its value is in the state array.
23001
- // - default (ControlGroup): child gets the value at its named key in the state object.
23147
+ distributeChildUIState: resolvedDistributeChildUIState,
23148
+ // Cascades newUIState to each monitored child via resolvedDistributeChildUIState,
23149
+ // then re-aggregates and fires this group's own reactions.
23002
23150
  setUIState: (newUIState, e) => {
23003
23151
  if (
23004
23152
  stateType === "object" &&
@@ -23017,73 +23165,42 @@ const useUIGroupStateController = (
23017
23165
  );
23018
23166
  return;
23019
23167
  }
23020
- const propagateDownEvent = new CustomEvent(
23021
- "propagate_down_set_ui_state",
23022
- { detail: {} },
23023
- );
23168
+ // initial_state_push propagates silently (no uiAction anywhere in the chain);
23169
+ // regular updates use propagate_down_set_ui_state which fires uiAction on children.
23170
+ const propagateEventType =
23171
+ e.type === "initial_state_push"
23172
+ ? "initial_state_push"
23173
+ : "propagate_down_set_ui_state";
23174
+ const propagateDownEvent = new CustomEvent(propagateEventType, {
23175
+ detail: {},
23176
+ });
23024
23177
  chainEvent(propagateDownEvent, e);
23025
23178
  for (const childUIStateController of childUIStateControllerArray) {
23026
- if (!isMonitoringChild(childUIStateController)) {
23179
+ if (!shouldPropagateStateToChild(childUIStateController)) {
23027
23180
  continue;
23028
23181
  }
23029
- if (childUIStateController.controlType === "button") {
23182
+ const childNewState = resolvedDistributeChildUIState(
23183
+ newUIState,
23184
+ childUIStateController,
23185
+ );
23186
+ if (childNewState === CANNOT_DERIVE) {
23030
23187
  continue;
23031
23188
  }
23032
- if (controlType === "radio_group") {
23033
- const childSelected =
23034
- childUIStateController.props.value === newUIState;
23035
- // Pass the child's own value (not `true`) when selected, `undefined` (not `false`) when not.
23036
- // `toDomProps` uses `newUIState !== undefined` to determine `checked`, so passing `false`
23037
- // would incorrectly render the radio as checked.
23038
- const childNewState = childSelected
23039
- ? childUIStateController.props.value
23040
- : undefined;
23041
- childUIStateController.setUIState(childNewState, propagateDownEvent);
23042
- } else if (controlType === "checkbox_group") {
23043
- const childSelected =
23044
- Array.isArray(newUIState) &&
23045
- newUIState.includes(childUIStateController.props.value);
23046
- const childNewState = childSelected
23047
- ? childUIStateController.props.value
23048
- : undefined;
23049
- childUIStateController.setUIState(childNewState, propagateDownEvent);
23050
- } else if (distributeChildUIState) {
23051
- const childrenStateMap = distributeChildUIState(newUIState);
23052
- if (childrenStateMap && typeof childrenStateMap === "object") {
23053
- const childName = childUIStateController.name;
23054
- if (
23055
- childName &&
23056
- Object.prototype.hasOwnProperty.call(childrenStateMap, childName)
23057
- ) {
23058
- childUIStateController.setUIState(
23059
- childrenStateMap[childName],
23060
- propagateDownEvent,
23061
- );
23062
- }
23063
- }
23064
- } else {
23065
- const childName = childUIStateController.name;
23066
- if (
23067
- childName &&
23068
- newUIState !== null &&
23069
- typeof newUIState === "object" &&
23070
- Object.prototype.hasOwnProperty.call(newUIState, childName)
23071
- ) {
23072
- childUIStateController.setUIState(
23073
- newUIState[childName],
23074
- propagateDownEvent,
23075
- );
23076
- }
23077
- }
23189
+ childUIStateController.setUIState(childNewState, propagateDownEvent);
23078
23190
  }
23079
23191
  // Re-aggregate from children and apply — do NOT call onChange to avoid a loop
23080
23192
  // (onChange would call setUIState again, which would cascade again).
23081
- const aggChildState = aggregateChildStates(
23193
+ const aggChildState = resolvedAggregateChildStates(
23082
23194
  childUIStateControllerArray,
23083
23195
  fallbackState,
23084
23196
  );
23085
23197
  const groupUIState =
23086
23198
  aggChildState === undefined ? fallbackState : aggChildState;
23199
+ if (e.type === "initial_state_push") {
23200
+ // Silent initialization: update state without firing uiAction or notifying parent.
23201
+ groupUIStateController.syncInternalState(groupUIState);
23202
+ return;
23203
+ }
23087
23204
  applyState(groupUIState, e, { internalBehavior: true });
23088
23205
  },
23089
23206
  // Called on mount/unmount/render-batch: updates state silently with no external reactions.
@@ -23133,6 +23250,33 @@ const useUIGroupStateController = (
23133
23250
  debugUIGroup(
23134
23251
  `${controlType}.registerChild("${childControlType}") -> registered (total: ${childUIStateControllerArray.length})`,
23135
23252
  );
23253
+ // Auto-derive child's initial state from the group's value/defaultValue
23254
+ // when the child has no individually-controlled state prop.
23255
+ // Use initial_state_push so uiAction does not fire during mount initialization.
23256
+ if (!childUIStateController.hasStateProp) {
23257
+ const initialEvent = new CustomEvent("initial_state_push", {
23258
+ detail: {},
23259
+ });
23260
+ if (groupUIStateController.hasValueProp) {
23261
+ // Controlled: always cascade current group value (even undefined = deselect all).
23262
+ const childNewState = resolvedDistributeChildUIState(
23263
+ groupUIStateController.value,
23264
+ childUIStateController,
23265
+ );
23266
+ if (childNewState !== CANNOT_DERIVE) {
23267
+ childUIStateController.setUIState(childNewState, initialEvent);
23268
+ }
23269
+ } else if (groupUIStateController.hasDefaultValueProp) {
23270
+ // Uncontrolled: set initial state from defaultValue on mount.
23271
+ const childNewState = resolvedDistributeChildUIState(
23272
+ groupUIStateController.defaultValue,
23273
+ childUIStateController,
23274
+ );
23275
+ if (childNewState !== CANNOT_DERIVE) {
23276
+ childUIStateController.setUIState(childNewState, initialEvent);
23277
+ }
23278
+ }
23279
+ }
23136
23280
  onChange(new CustomEvent(`${childControlType}_mount`), {
23137
23281
  notifyExternal: "silent",
23138
23282
  // childUIStateController,
@@ -23211,10 +23355,7 @@ const useUIGroupStateController = (
23211
23355
  );
23212
23356
  chainEvent(propagateDownResetEvent, e);
23213
23357
  for (const childUIStateController of childUIStateControllerArray) {
23214
- if (!isMonitoringChild(childUIStateController)) {
23215
- continue;
23216
- }
23217
- if (childUIStateController.controlType === "button") {
23358
+ if (!shouldPropagateStateToChild(childUIStateController)) {
23218
23359
  continue;
23219
23360
  }
23220
23361
  childUIStateController.resetUIState(propagateDownResetEvent);
@@ -23387,6 +23528,18 @@ const useUIFacadeStateController = (props, realUIStateController) => {
23387
23528
  );
23388
23529
  firstChildControllerRef.current = child;
23389
23530
  realUIStateController.facadeChild = child;
23531
+ // If the picker already has a meaningful state (from value or defaultValue),
23532
+ // push it to the child on registration so it reflects the pre-set value
23533
+ // without firing uiAction (equivalent to defaultValue on the child itself).
23534
+ const initialState = realUIStateController.uiState;
23535
+ if (initialState !== undefined) {
23536
+ updatingRef.current = true;
23537
+ const initialEvent = new CustomEvent("initial_state_push", {
23538
+ detail: {},
23539
+ });
23540
+ child.setUIState(initialState, initialEvent);
23541
+ updatingRef.current = false;
23542
+ }
23390
23543
  }
23391
23544
  },
23392
23545
  unregisterChild: (child) => {
@@ -23479,6 +23632,10 @@ const INTERNAL_EVENT_SET = new Set([
23479
23632
  // so the picker knows the child's current value (e.g. for "store value at open"),
23480
23633
  // but no action pipeline, command, or synthetic input event should fire.
23481
23634
  "facade_child_mount_sync",
23635
+ // Facade pushing its current state (from value/defaultValue) down to the first child
23636
+ // on registration, and group pushing value/defaultValue to children on registerChild.
23637
+ // Equivalent to defaultValue initialization: no uiAction, no commands, no parent notification.
23638
+ "initial_state_push",
23482
23639
  ]);
23483
23640
  const isInternalEvent = (e) => {
23484
23641
  return INTERNAL_EVENT_SET.has(e.type);
@@ -24282,6 +24439,12 @@ const useControlgroupProps = (props, {
24282
24439
  cascadeValidationToChildren
24283
24440
  });
24284
24441
  const [boundAction] = useActionBoundToOneParam(action, uiGroupStateController.uiStateSignal);
24442
+ // Mirror single-input behaviour: a controlled value with no handler makes the
24443
+ // group read-only so children don't appear interactive when they can't change.
24444
+ const implicitReadOnly = uiGroupStateController.hasValueProp && !action && !props.uiAction;
24445
+ if (implicitReadOnly && !props.readOnly) {
24446
+ props.readOnly = true;
24447
+ }
24285
24448
  const [actionRequester, setActionRequester] = useState();
24286
24449
  const [controlRootProps, controlgroupProps] = useInteractiveProps(props, {
24287
24450
  uiStateController: uiGroupStateController,
@@ -30480,25 +30643,7 @@ const ControlGroup = props => {
30480
30643
  wantRequesterButtonState: true,
30481
30644
  controlType: props.type || "control_group",
30482
30645
  stateType: "object",
30483
- cascadeValidationToChildren: true,
30484
- aggregateChildStates: childUIStateControllers => {
30485
- const groupValues = {};
30486
- for (const childUIStateController of childUIStateControllers) {
30487
- const {
30488
- name,
30489
- uiState,
30490
- allowNameless
30491
- } = childUIStateController;
30492
- if (!name) {
30493
- if (!allowNameless) {
30494
- console.warn("A ControlGroup child is missing a name property, its state won't be included in the group state", childUIStateController);
30495
- }
30496
- continue;
30497
- }
30498
- groupValues[name] = uiState;
30499
- }
30500
- return groupValues;
30501
- }
30646
+ cascadeValidationToChildren: true
30502
30647
  });
30503
30648
  const {
30504
30649
  children
@@ -32422,7 +32567,8 @@ const InputRangeFieldInterface = props => {
32422
32567
  };
32423
32568
  resolveInputProps(props);
32424
32569
  const [rangeRootProps, rangeHostProps] = useControlProps(props, {
32425
- controlType: "input"});
32570
+ controlType: "input"
32571
+ });
32426
32572
  const {
32427
32573
  basePseudoState
32428
32574
  } = rangeHostProps;
@@ -33078,6 +33224,38 @@ const InputTextualWithSuggestions = props => {
33078
33224
  });
33079
33225
  };
33080
33226
 
33227
+ // When readonly focus and mousedown should select input content
33228
+ // (the only relevant interaction to perform on readonly is copying the value)
33229
+ // Nice side effect is that input_group.jsx will see all input is selected
33230
+ // and arrow left/right will always nav between inputs.
33231
+ // (Otherwise we would prevent left/right + show calllout about readonly)
33232
+ const useAutoSelectReadOnly = (props) => {
33233
+ const onFocus = (e) => {
33234
+ props.onFocus(e);
33235
+ if (e.defaultPrevented) {
33236
+ return;
33237
+ }
33238
+ if (!e.target.readOnly) {
33239
+ return;
33240
+ }
33241
+ e.preventDefault();
33242
+ e.target.select();
33243
+ };
33244
+ const onMouseDown = (e) => {
33245
+ props.onMouseDown(e);
33246
+ if (e.defaultPrevented) {
33247
+ return;
33248
+ }
33249
+ if (!e.target.readOnly) {
33250
+ return;
33251
+ }
33252
+ e.preventDefault();
33253
+ e.target.select();
33254
+ };
33255
+
33256
+ return { onFocus, onMouseDown };
33257
+ };
33258
+
33081
33259
  installImportMetaCssBuild(import.meta);/**
33082
33260
  * Input component for all textual input types.
33083
33261
  *
@@ -33513,35 +33691,14 @@ const InputTextualFirstResolver = props => {
33513
33691
  };
33514
33692
  const InputTextual = createComponentResolver([InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
33515
33693
  const RealInput = props => {
33694
+ const autoSelectReadOnlyProps = useAutoSelectReadOnly(props);
33516
33695
  return jsx(Box, {
33517
33696
  ...props,
33518
33697
  as: "input",
33519
- baseClassName: "navi_control_input"
33520
- // When readonly focus and mousedown should select input content
33521
- // (the only relevant interaction to perform on readonly is copying the value)
33522
- // Nice side effect is that input_group.jsx will see all input is selected
33523
- // and arrow left/right will always nav between inputs.
33524
- // (Otherwise we would prevent left/right + show calllout about readonly)
33525
- ,
33526
-
33527
- onFocus: e => {
33528
- props.onFocus(e);
33529
- if (!e.defaultPrevented && e.target.readOnly) {
33530
- e.preventDefault();
33531
- e.target.select();
33532
- }
33533
- },
33534
- onMouseDown: e => {
33535
- props.onMouseDown(e);
33536
- if (!e.defaultPrevented && e.target.readOnly) {
33537
- e.preventDefault();
33538
- e.target.select();
33539
- }
33540
- }
33698
+ baseClassName: "navi_control_input",
33699
+ ...autoSelectReadOnlyProps,
33541
33700
  // Never set native maxLength — our guard handles it. maxLength stays in
33542
33701
  // inputControlHostProps so form validation constraints still read it.
33543
- ,
33544
-
33545
33702
  maxLength: undefined
33546
33703
  });
33547
33704
  };
@@ -33856,25 +34013,7 @@ const FormControl = props => {
33856
34013
  wantRequesterButtonState: true,
33857
34014
  controlType: "form",
33858
34015
  stateType: "object",
33859
- cascadeValidationToChildren: true,
33860
- aggregateChildStates: childUIStateControllers => {
33861
- const formValues = {};
33862
- for (const childUIStateController of childUIStateControllers) {
33863
- const {
33864
- name,
33865
- uiState,
33866
- allowNameless
33867
- } = childUIStateController;
33868
- if (!name) {
33869
- if (!allowNameless) {
33870
- console.warn("A form child component is missing a name property, its state won't be included in the form state", childUIStateController);
33871
- }
33872
- continue;
33873
- }
33874
- formValues[name] = uiState;
33875
- }
33876
- return formValues;
33877
- }
34016
+ cascadeValidationToChildren: true
33878
34017
  });
33879
34018
  const {
33880
34019
  basePseudoState,
@@ -34100,19 +34239,7 @@ const CheckboxGroupInterface = props => {
34100
34239
  ...props
34101
34240
  }, {
34102
34241
  stateType: "array",
34103
- controlType: "checkbox_group",
34104
- childControlFilter: childUIStateController => {
34105
- return childUIStateController.controlType === "input" && childUIStateController.controlHostProps.type === "checkbox";
34106
- },
34107
- aggregateChildStates: childUIStateControllers => {
34108
- const values = [];
34109
- for (const childUIStateController of childUIStateControllers) {
34110
- if (childUIStateController.uiState) {
34111
- values.push(childUIStateController.uiState);
34112
- }
34113
- }
34114
- return values.length === 0 ? undefined : values;
34115
- }
34242
+ controlType: "checkbox_group"
34116
34243
  });
34117
34244
  useFocusGroup(ref, {
34118
34245
  wrap: "both"
@@ -34439,6 +34566,9 @@ const isTextInputElement = (el) => {
34439
34566
  installImportMetaCssBuild(import.meta);const css$t = /* css */`
34440
34567
  .navi_input_duration {
34441
34568
  --duration-separator-spacing: 4px;
34569
+ --loader-color: var(--navi-loader-color);
34570
+
34571
+ position: relative; /* For loading outline */
34442
34572
 
34443
34573
  .navi_label {
34444
34574
  &[data-separator] {
@@ -34540,13 +34670,8 @@ const InputDuration = props => {
34540
34670
  // Exception: if the current value already has minutes, show them read-only so
34541
34671
  // the stored precision is faithfully displayed.
34542
34672
  const showMinutes = maxSeconds >= 60 && (stepSeconds === undefined || stepSeconds % 3600 !== 0 || valueHasMinutes);
34543
-
34544
- // Mirror the single-input behaviour: a controlled value with no handler makes the field read-only.
34545
- const implicitReadOnly = hasValue && !uiAction && !action;
34546
- if (implicitReadOnly && !props.readOnly && undefined) ;
34547
34673
  const [groupRootProps, groupHostProps, childrenWrapperProps] = useControlgroupProps({
34548
34674
  ...props,
34549
- readOnly: props.readOnly || implicitReadOnly,
34550
34675
  uiAction: (groupState, event) => {
34551
34676
  const hiddenInput = ref.current;
34552
34677
  if (hiddenInput) {
@@ -34595,15 +34720,10 @@ const InputDuration = props => {
34595
34720
  // sub-inputs are correctly reset to their original raw string values.
34596
34721
  // ISO 8601 encodes milliseconds as fractional seconds (e.g. "PT0.5S" = 500ms),
34597
34722
  // so fractional seconds are split back into whole seconds + ms.
34598
- distributeChildUIState: groupState => {
34723
+ distributeChildUIState: (groupState, childUIStateController) => {
34599
34724
  const components = parseDuration(groupState);
34600
34725
  if (!components) {
34601
- return {
34602
- hour: undefined,
34603
- minute: undefined,
34604
- second: undefined,
34605
- millisecond: undefined
34606
- };
34726
+ return undefined;
34607
34727
  }
34608
34728
  const rawSeconds = components.seconds;
34609
34729
  let secondForField = rawSeconds;
@@ -34612,12 +34732,13 @@ const InputDuration = props => {
34612
34732
  secondForField = Math.floor(rawSeconds);
34613
34733
  millisecondForField = Math.round(rawSeconds % 1 * 1000);
34614
34734
  }
34615
- return {
34735
+ const fieldMap = {
34616
34736
  hour: components.hours,
34617
34737
  minute: components.minutes,
34618
34738
  second: secondForField,
34619
34739
  millisecond: millisecondForField
34620
34740
  };
34741
+ return fieldMap[childUIStateController.name];
34621
34742
  }
34622
34743
  });
34623
34744
  const {
@@ -34639,6 +34760,8 @@ const InputDuration = props => {
34639
34760
  secondValue = Math.floor(rawSecondValue);
34640
34761
  millisecondValue = Math.round(rawSecondValue % 1 * 1000);
34641
34762
  }
34763
+ const loading = basePseudoState[":-navi-loading"];
34764
+ delete basePseudoState[":-navi-loading"];
34642
34765
  return jsxs(Box, {
34643
34766
  ref: groupRef,
34644
34767
  className: "navi_input_duration",
@@ -34648,7 +34771,11 @@ const InputDuration = props => {
34648
34771
  unit: undefined,
34649
34772
  unitHour: undefined,
34650
34773
  ...clipboardProps,
34651
- children: [jsx("input", {
34774
+ children: [jsx(LoadingOutline, {
34775
+ loading: loading,
34776
+ color: "var(--loader-color)",
34777
+ inset: -1
34778
+ }), jsx("input", {
34652
34779
  ...groupHostProps,
34653
34780
  type: "hidden",
34654
34781
  basePseudoState: undefined // eslint-disable-line react/no-unknown-property
@@ -35006,20 +35133,7 @@ const RadioGroupInterface = props => {
35006
35133
  resetOnError: true,
35007
35134
  ...props
35008
35135
  }, {
35009
- controlType: "radio_group",
35010
- childControlFilter: childUIStateController => {
35011
- return childUIStateController.controlType === "input" && childUIStateController.controlHostProps.type === "radio";
35012
- },
35013
- aggregateChildStates: childUIStateControllers => {
35014
- let activeValue;
35015
- for (const childUIStateController of childUIStateControllers) {
35016
- if (childUIStateController.uiState) {
35017
- activeValue = childUIStateController.uiState;
35018
- break;
35019
- }
35020
- }
35021
- return activeValue;
35022
- }
35136
+ controlType: "radio_group"
35023
35137
  });
35024
35138
  useFocusGroup(ref, {
35025
35139
  wrap: "both"
@@ -37547,30 +37661,7 @@ const ListSelectable = props => {
37547
37661
  } = props;
37548
37662
  const [listControlRootProps, listControlProps, childrenWrapperProps] = useControlgroupProps(props, {
37549
37663
  stateType: multiple ? "array" : "",
37550
- controlType: multiple ? "checkbox_group" : "radio_group",
37551
- childControlFilter: multiple ? childUIStateController => {
37552
- return childUIStateController.controlType === "input" && childUIStateController.controlHostProps.type === "checkbox";
37553
- } : childUIStateController => {
37554
- return childUIStateController.controlType === "input" && childUIStateController.controlHostProps.type === "radio";
37555
- },
37556
- aggregateChildStates: multiple ? childUIStateControllers => {
37557
- const values = [];
37558
- for (const childUIStateController of childUIStateControllers) {
37559
- if (childUIStateController.uiState) {
37560
- values.push(childUIStateController.uiState);
37561
- }
37562
- }
37563
- return values.length === 0 ? undefined : values;
37564
- } : childUIStateControllers => {
37565
- let activeValue;
37566
- for (const childUIStateController of childUIStateControllers) {
37567
- if (childUIStateController.uiState) {
37568
- activeValue = childUIStateController.uiState;
37569
- break;
37570
- }
37571
- }
37572
- return activeValue;
37573
- }
37664
+ controlType: multiple ? "checkbox_group" : "radio_group"
37574
37665
  });
37575
37666
  const uiGroupStateController = getUIStateControllerById(listControlProps.id);
37576
37667
  useFocusGroup(ref, {
@@ -37655,6 +37746,8 @@ const ListSelectable = props => {
37655
37746
  ...listControlRootProps,
37656
37747
  ...listControlProps,
37657
37748
  name: undefined,
37749
+ value: undefined,
37750
+ defaultValue: undefined,
37658
37751
  selectedIndicator: undefined,
37659
37752
  selectable: undefined,
37660
37753
  multiple: undefined
@@ -38727,7 +38820,13 @@ const useListScrollSync = ({
38727
38820
  }
38728
38821
  hasBeenDisplayedRef.current = true;
38729
38822
  const items = tracker.itemsSignal.peek();
38730
- const firstSelected = items.find(i => i.selected);
38823
+ const firstSelected = items.find(i => {
38824
+ if (i.selected) {
38825
+ return true;
38826
+ }
38827
+ const inputController = getUIStateControllerById(`${i.id}_input`);
38828
+ return inputController ? inputController.uiStateSignal.peek() : false;
38829
+ });
38731
38830
  if (firstSelected) {
38732
38831
  scrollToItem(firstSelected, {
38733
38832
  event: new CustomEvent("navi_displayed", {
@@ -40382,7 +40481,7 @@ installImportMetaCssBuild(import.meta);const css$i = /* css */`
40382
40481
  --x-picker-padding-right: 0;
40383
40482
  --x-picker-padding-bottom: 0;
40384
40483
  --x-picker-padding-left: 0;
40385
- --picker-border-width: 0;
40484
+ --picker-border-width: 0px; /* must carry a unit (px) — used in calc() to offset the custom input overlay */
40386
40485
  --x-picker-border-color: transparent;
40387
40486
  --x-picker-background-color: transparent;
40388
40487
  --x-picker-icon-color: currentColor;
@@ -40392,7 +40491,7 @@ installImportMetaCssBuild(import.meta);const css$i = /* css */`
40392
40491
  --x-picker-padding-right: 0;
40393
40492
  --x-picker-padding-bottom: 0;
40394
40493
  --x-picker-padding-left: 0;
40395
- --picker-border-width: 0;
40494
+ --picker-border-width: 0px; /* must carry a unit (px) — used in calc() to offset the custom input overlay */
40396
40495
  --x-picker-border-color: transparent;
40397
40496
  --x-picker-background-color: transparent;
40398
40497
  --x-picker-icon-color: currentColor;
@@ -40605,19 +40704,20 @@ const isWithinPickerContent = (el, pickerEl) => {
40605
40704
  };
40606
40705
  const PickerInput = props => {
40607
40706
  const {
40608
- ui
40707
+ ui,
40708
+ readOnly
40609
40709
  } = props;
40610
40710
 
40611
40711
  // After type resolution: force readOnly when the input type would open the
40612
40712
  // mobile keyboard. We also suppress the visual ":read-only" state so the
40613
40713
  // picker still looks interactive (it is — just not keyboard-typeable).
40614
- if (MOBILE_KEYBOARD_TYPES.has(props.type) && !props.readOnly) {
40615
- props.readOnly = true;
40616
- props["data-readonly-forced"] = "";
40617
- }
40714
+ const readOnlyForced = readOnly ? false : MOBILE_KEYBOARD_TYPES.has(props.type || "text");
40715
+ const autoSelectReadOnlyProps = useAutoSelectReadOnly(props);
40618
40716
  return jsx(Box, {
40619
40717
  as: "input",
40620
40718
  ...props,
40719
+ readOnly: readOnlyForced ? true : readOnly,
40720
+ "data-readonly-forced": readOnlyForced ? "" : undefined,
40621
40721
  ui: undefined,
40622
40722
  className: "navi_picker_input",
40623
40723
  pseudoClasses: PickerInputPseudoClasses,
@@ -40630,7 +40730,8 @@ const PickerInput = props => {
40630
40730
  // Ensure tab does not tab through the browser picker elements (like in input date)
40631
40731
  performTabNavigation(e);
40632
40732
  }
40633
- }
40733
+ },
40734
+ ...autoSelectReadOnlyProps
40634
40735
  });
40635
40736
  };
40636
40737
  // Input types that open the software keyboard on mobile.