@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.
- package/dist/jsenv_navi.js +322 -221
- package/dist/jsenv_navi.js.map +37 -31
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -22598,13 +22598,17 @@ const useUIStateController = (
|
|
|
22598
22598
|
);
|
|
22599
22599
|
}
|
|
22600
22600
|
}
|
|
22601
|
-
//
|
|
22602
|
-
//
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
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
|
-
|
|
22844
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
22997
|
-
//
|
|
22998
|
-
//
|
|
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
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
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 (!
|
|
23179
|
+
if (!shouldPropagateStateToChild(childUIStateController)) {
|
|
23027
23180
|
continue;
|
|
23028
23181
|
}
|
|
23029
|
-
|
|
23182
|
+
const childNewState = resolvedDistributeChildUIState(
|
|
23183
|
+
newUIState,
|
|
23184
|
+
childUIStateController,
|
|
23185
|
+
);
|
|
23186
|
+
if (childNewState === CANNOT_DERIVE) {
|
|
23030
23187
|
continue;
|
|
23031
23188
|
}
|
|
23032
|
-
|
|
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 =
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 =>
|
|
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:
|
|
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:
|
|
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
|
-
|
|
40615
|
-
|
|
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.
|