@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.3

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.
Files changed (220) hide show
  1. package/es/PluginSettingsManager.d.ts +33 -0
  2. package/es/RouteRepository.d.ts +21 -6
  3. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  4. package/es/components/category-tabs/index.d.ts +9 -0
  5. package/es/components/form/JsonTextArea.d.ts +2 -1
  6. package/es/components/form/TypedVariableInput.d.ts +22 -4
  7. package/es/components/form/VariableJsonTextArea.d.ts +19 -0
  8. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  9. package/es/components/form/filter/index.d.ts +2 -0
  10. package/es/components/form/index.d.ts +1 -0
  11. package/es/components/index.d.ts +2 -0
  12. package/es/flow/actions/afterSuccess.d.ts +8 -1
  13. package/es/flow/actions/index.d.ts +1 -1
  14. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  15. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  16. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
  17. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  18. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
  19. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  20. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  21. package/es/flow/components/FieldAssignValueInput.d.ts +2 -0
  22. package/es/flow/index.d.ts +1 -0
  23. package/es/flow/models/base/GridModel.d.ts +1 -1
  24. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
  25. package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
  26. package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
  27. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
  28. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
  29. package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
  30. package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
  31. package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
  32. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  33. package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
  34. package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
  35. package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
  36. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  37. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
  38. package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
  39. package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
  40. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  41. package/es/flow/utils/formValueDeps.d.ts +32 -0
  42. package/es/flow-compat/FieldValidation.d.ts +2 -1
  43. package/es/index.d.ts +2 -1
  44. package/es/index.mjs +233 -137
  45. package/lib/index.js +234 -138
  46. package/package.json +7 -7
  47. package/src/PluginSettingsManager.ts +53 -0
  48. package/src/RouteRepository.ts +126 -24
  49. package/src/__tests__/PluginSettingsManager.test.ts +13 -0
  50. package/src/__tests__/RouteRepository.test.ts +216 -0
  51. package/src/__tests__/browserChecker.test.ts +61 -0
  52. package/src/__tests__/exports.test.ts +16 -0
  53. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
  54. package/src/__tests__/plugin-manager.test.tsx +44 -2
  55. package/src/__tests__/settings-center.test.tsx +40 -1
  56. package/src/collection-manager/field-configure.ts +1 -1
  57. package/src/collection-manager/interfaces/id.ts +1 -1
  58. package/src/collection-manager/interfaces/m2m.tsx +2 -2
  59. package/src/collection-manager/interfaces/m2o.tsx +2 -2
  60. package/src/collection-manager/interfaces/nanoid.ts +1 -1
  61. package/src/collection-manager/interfaces/o2m.tsx +2 -2
  62. package/src/collection-manager/interfaces/obo.tsx +2 -2
  63. package/src/collection-manager/interfaces/oho.tsx +2 -2
  64. package/src/collection-manager/interfaces/properties/index.ts +2 -2
  65. package/src/collection-manager/interfaces/uuid.ts +1 -1
  66. package/src/components/AppComponents.tsx +19 -3
  67. package/src/components/README.md +7 -1
  68. package/src/components/README.zh-CN.md +6 -1
  69. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  70. package/src/components/category-tabs/index.ts +10 -0
  71. package/src/components/form/JsonTextArea.tsx +21 -11
  72. package/src/components/form/TypedVariableInput.tsx +416 -93
  73. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  74. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  75. package/src/components/form/__tests__/TypedVariableInput.test.tsx +268 -9
  76. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  77. package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
  78. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
  79. package/src/components/form/filter/index.ts +2 -0
  80. package/src/components/form/index.tsx +1 -0
  81. package/src/components/index.ts +2 -0
  82. package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
  83. package/src/flow/__tests__/getKey.test.ts +7 -0
  84. package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
  85. package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
  86. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +58 -0
  87. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
  88. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  89. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  90. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  91. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  92. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +104 -0
  93. package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
  94. package/src/flow/actions/afterSuccess.tsx +142 -3
  95. package/src/flow/actions/customVariable.tsx +1 -2
  96. package/src/flow/actions/dataScopeFilter.ts +10 -0
  97. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  98. package/src/flow/actions/index.ts +1 -1
  99. package/src/flow/actions/linkageRules.tsx +123 -35
  100. package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
  101. package/src/flow/actions/openView.tsx +38 -4
  102. package/src/flow/actions/runjs.tsx +2 -14
  103. package/src/flow/actions/validation.tsx +62 -30
  104. package/src/flow/admin-shell/BaseLayoutModel.tsx +25 -3
  105. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +5 -2
  106. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +13 -0
  107. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +34 -10
  108. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
  109. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
  110. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  111. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  112. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  113. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  114. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  115. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  116. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
  117. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  118. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +82 -0
  119. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
  120. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  121. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  122. package/src/flow/components/DefaultValue.tsx +1 -2
  123. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  124. package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
  125. package/src/flow/components/FieldAssignValueInput.tsx +10 -5
  126. package/src/flow/components/FlowRoute.tsx +56 -11
  127. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  128. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
  129. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
  130. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  131. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  132. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  133. package/src/flow/components/fieldAssignOptions.ts +155 -27
  134. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  135. package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
  136. package/src/flow/index.ts +2 -1
  137. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  138. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  139. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  140. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  141. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  142. package/src/flow/models/base/ActionModel.tsx +12 -1
  143. package/src/flow/models/base/ActionModelCore.tsx +6 -4
  144. package/src/flow/models/base/GridModel.tsx +38 -7
  145. package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
  146. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
  147. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  148. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
  149. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  150. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  151. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +31 -3
  152. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  153. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
  154. package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
  155. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  156. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  157. package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
  158. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  159. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  160. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  161. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
  162. package/src/flow/models/blocks/form/submitHandler.ts +17 -3
  163. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
  164. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  165. package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
  166. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  167. package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
  168. package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
  169. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  170. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +36 -13
  171. package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
  172. package/src/flow/models/blocks/table/TableColumnModel.tsx +36 -3
  173. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
  174. package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
  175. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  176. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
  177. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +159 -1
  178. package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
  179. package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
  180. package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
  181. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  182. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +29 -6
  183. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +16 -4
  184. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
  185. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  186. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
  187. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  188. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  189. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  190. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  191. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  192. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  193. package/src/flow/models/fields/InputFieldModel.tsx +40 -2
  194. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  195. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  196. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  197. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  198. package/src/flow/models/fields/TextareaFieldModel.tsx +49 -3
  199. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  200. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
  201. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  202. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +100 -0
  203. package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
  204. package/src/flow/resolveViewParamsToViewList.tsx +11 -3
  205. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +258 -0
  206. package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
  207. package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
  208. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  209. package/src/flow/utils/formValueDeps.ts +170 -0
  210. package/src/flow-compat/FieldValidation.tsx +122 -60
  211. package/src/flow-compat/Popover.tsx +43 -4
  212. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  213. package/src/index.ts +8 -1
  214. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  215. package/src/nocobase-buildin-plugin/index.tsx +44 -14
  216. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  217. package/src/settings-center/SystemSettingsPage.tsx +1 -2
  218. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  219. package/src/settings-center/plugin-manager/index.tsx +3 -0
  220. package/src/settings-center/utils.tsx +0 -6
@@ -38,12 +38,18 @@ type FormBlockModel = FlowModel & {
38
38
  getAclActionName?: () => string;
39
39
  };
40
40
 
41
+ export type FormValuePatch = {
42
+ path: NamePath;
43
+ value: unknown;
44
+ };
45
+
41
46
  export class FormValueRuntime {
42
47
  private readonly model: FormBlockModel;
43
48
  private readonly getForm: () => FormInstance;
44
49
 
45
50
  private readonly valuesMirror = observable({});
46
51
  private readonly explicitSet = new Set<string>();
52
+ private readonly userEditedSet = new Set<string>();
47
53
  private readonly lastDefaultValueByPathKey = new Map<string, any>();
48
54
  private readonly lastWriteMetaByPathKey = new Map<string, FormValueWriteMeta>();
49
55
  private readonly observableBindings = new Map<string, ObservableBinding>();
@@ -59,6 +65,8 @@ export class FormValueRuntime {
59
65
  private lastObservedToken = 0;
60
66
 
61
67
  private readonly formValuesProxy: any;
68
+ private readonly mountedFieldModelsByPathKey = new Map<string, Set<FlowModel>>();
69
+ private readonly mountedFieldModelPathKeys = new WeakMap<FlowModel, Set<string>>();
62
70
 
63
71
  private mountedListener?: (payload: { model: FlowModel }) => void;
64
72
  private unmountedListener?: (payload: { model: FlowModel }) => void;
@@ -99,6 +107,7 @@ export class FormValueRuntime {
99
107
  getFormValueAtPath: (namePath) => this.getFormValueAtPath(namePath),
100
108
  setFormValues: (callerCtx, patch, ruleOptions) => this.setFormValues(callerCtx, patch, ruleOptions),
101
109
  findExplicitHit: (pathKey) => this.findExplicitHit(pathKey),
110
+ findUserEditedHit: (pathKey) => this.findUserEditedHit(pathKey),
102
111
  lastDefaultValueByPathKey: this.lastDefaultValueByPathKey,
103
112
  lastWriteMetaByPathKey: this.lastWriteMetaByPathKey,
104
113
  observableBindings: this.observableBindings,
@@ -117,6 +126,7 @@ export class FormValueRuntime {
117
126
  *
118
127
  * - mode=default → source=default(遵循 explicit/空值覆盖语义)
119
128
  * - mode=assign → source=system(不受 explicit 影响,依赖变化时持续生效)
129
+ * - mode=override → source=override(首次覆盖已有值,用户修改后停止)
120
130
  */
121
131
  syncAssignRules(items: FormAssignRuleItem[]) {
122
132
  this.ruleEngine.syncAssignRules(items);
@@ -137,6 +147,58 @@ export class FormValueRuntime {
137
147
  return this.getForm().getFieldsValue(true);
138
148
  }
139
149
 
150
+ getUserEditedValuePatches(): FormValuePatch[] {
151
+ const snapshot = this.getFormValuesSnapshot();
152
+ if (!snapshot || typeof snapshot !== 'object') {
153
+ return [];
154
+ }
155
+
156
+ const patches: FormValuePatch[] = [];
157
+ const pathKeys = Array.from(this.userEditedSet).sort(
158
+ (a, b) => pathKeyToNamePath(a).length - pathKeyToNamePath(b).length,
159
+ );
160
+
161
+ for (const pathKey of pathKeys) {
162
+ if (!this.isCurrentUserEditedPath(pathKey)) {
163
+ continue;
164
+ }
165
+
166
+ const namePath = pathKeyToNamePath(pathKey);
167
+ if (!namePath.length || !_.has(snapshot, namePath as any)) {
168
+ continue;
169
+ }
170
+
171
+ const value = _.get(snapshot, namePath as any);
172
+ if (typeof value === 'undefined') {
173
+ continue;
174
+ }
175
+
176
+ patches.push({
177
+ path: namePath,
178
+ value: this.omitNonUserDescendantValues(pathKey, this.toMirrorSnapshot(value)),
179
+ });
180
+ }
181
+
182
+ return patches;
183
+ }
184
+
185
+ getUserEditedValuesSnapshot(): Record<string, unknown> {
186
+ const values: Record<string, unknown> = {};
187
+ for (const patch of this.getUserEditedValuePatches()) {
188
+ _.set(values, patch.path as any, patch.value);
189
+ }
190
+ return values;
191
+ }
192
+
193
+ private toMirrorSnapshot(value: any) {
194
+ const raw = isObservable(value) ? toJS(value) : value;
195
+ return _.cloneDeepWith(raw, (item) => {
196
+ if (!item || typeof item !== 'object') return undefined;
197
+ if (Array.isArray(item) || _.isPlainObject(item)) return undefined;
198
+ return item;
199
+ });
200
+ }
201
+
140
202
  canApplyDefaultValuePatch(namePath: NamePath, resolved: any) {
141
203
  if (!namePath?.length) return false;
142
204
  if (typeof resolved === 'undefined') return false;
@@ -153,19 +215,24 @@ export class FormValueRuntime {
153
215
 
154
216
  const canOverwrite = isEmptyValue(current) || currentEqualsLastDefault;
155
217
  if (!canOverwrite && _.isEqual(current, nextSnapshot)) {
156
- this.lastDefaultValueByPathKey.set(pathKey, nextSnapshot);
218
+ this.lastDefaultValueByPathKey.set(pathKey, this.toMirrorSnapshot(nextSnapshot));
157
219
  return false;
158
220
  }
159
221
 
160
222
  return canOverwrite;
161
223
  }
162
224
 
225
+ canApplyOverrideValuePatch(namePath: NamePath) {
226
+ if (!namePath?.length) return false;
227
+ return !this.findUserEditedHit(namePathToPathKey(namePath));
228
+ }
229
+
163
230
  recordDefaultValuePatch(namePath: NamePath, value?: any) {
164
231
  if (!namePath?.length) return;
165
232
  const pathKey = namePathToPathKey(namePath);
166
233
  const snapshot =
167
234
  arguments.length >= 2 ? (isObservable(value) ? toJS(value) : value) : this.getFormValueAtPath(namePath);
168
- this.lastDefaultValueByPathKey.set(pathKey, snapshot);
235
+ this.lastDefaultValueByPathKey.set(pathKey, this.toMirrorSnapshot(snapshot));
169
236
  const current = this.getFormValueAtPath(namePath);
170
237
  const currentSnapshot = isObservable(current) ? toJS(current) : current;
171
238
  if (_.isEqual(currentSnapshot, snapshot)) {
@@ -195,9 +262,11 @@ export class FormValueRuntime {
195
262
 
196
263
  if (!this.mountedListener && !this.unmountedListener) {
197
264
  this.mountedListener = ({ model }: { model: FlowModel }) => {
265
+ this.indexMountedFieldModelTree(model);
198
266
  this.ruleEngine.onModelMounted(model);
199
267
  };
200
268
  this.unmountedListener = ({ model }: { model: FlowModel }) => {
269
+ this.unindexMountedFieldModelTree(model);
201
270
  this.ruleEngine.onModelUnmounted(model);
202
271
  };
203
272
 
@@ -205,10 +274,12 @@ export class FormValueRuntime {
205
274
  engineEmitter.on('model:unmounted', this.unmountedListener);
206
275
  }
207
276
 
277
+ this.rebuildMountedFieldModelIndex();
278
+
208
279
  if (options?.sync) {
209
280
  const snapshot = this.getFormValuesSnapshot();
210
281
  if (snapshot && typeof snapshot === 'object') {
211
- _.merge(this.valuesMirror, snapshot);
282
+ _.merge(this.valuesMirror, this.toMirrorSnapshot(snapshot));
212
283
  this.bumpChangeTick();
213
284
  }
214
285
  this.ruleEngine.enable();
@@ -228,6 +299,7 @@ export class FormValueRuntime {
228
299
  }
229
300
 
230
301
  this.ruleEngine.dispose();
302
+ this.mountedFieldModelsByPathKey.clear();
231
303
 
232
304
  for (const binding of this.observableBindings.values()) {
233
305
  binding.dispose();
@@ -241,6 +313,7 @@ export class FormValueRuntime {
241
313
  if (this.disposed) return;
242
314
 
243
315
  this.explicitSet.clear();
316
+ this.userEditedSet.clear();
244
317
  this.lastDefaultValueByPathKey.clear();
245
318
  this.lastWriteMetaByPathKey.clear();
246
319
  this.txWriteCounts.clear();
@@ -258,7 +331,7 @@ export class FormValueRuntime {
258
331
  delete (this.valuesMirror as Record<string, any>)[key];
259
332
  }
260
333
  if (snapshot && typeof snapshot === 'object') {
261
- _.merge(this.valuesMirror, snapshot);
334
+ _.merge(this.valuesMirror, this.toMirrorSnapshot(snapshot));
262
335
  }
263
336
 
264
337
  this.writeSeq += 1;
@@ -266,6 +339,13 @@ export class FormValueRuntime {
266
339
  this.ruleEngine.rescheduleAllRules();
267
340
  }
268
341
 
342
+ resetUserEditedState() {
343
+ if (this.disposed) return;
344
+
345
+ this.userEditedSet.clear();
346
+ this.ruleEngine.rescheduleAllRules();
347
+ }
348
+
269
349
  isSuppressed() {
270
350
  return this.suppressFormCallbackDepth > 0;
271
351
  }
@@ -292,7 +372,7 @@ export class FormValueRuntime {
292
372
  this.writeSeq += 1;
293
373
  bumpedWriteSeq = true;
294
374
  }
295
- _.set(this.valuesMirror, namePath, nextValue);
375
+ _.set(this.valuesMirror, namePath, this.toMirrorSnapshot(nextValue));
296
376
  changedPaths.push(namePath);
297
377
  const isMeaningfulTouched =
298
378
  field?.touched === true && !this.shouldIgnoreSyntheticTouchedInit(namePath, prevValue, nextValue);
@@ -311,6 +391,7 @@ export class FormValueRuntime {
311
391
  if (!suppressed && touchedChangedPathKeys.size) {
312
392
  for (const key of touchedChangedPathKeys) {
313
393
  this.markExplicit(key);
394
+ this.markUserEdited(key);
314
395
  }
315
396
  }
316
397
 
@@ -374,6 +455,11 @@ export class FormValueRuntime {
374
455
  this.explicitSet.delete(key);
375
456
  }
376
457
 
458
+ for (const key of Array.from(this.userEditedSet)) {
459
+ if (!this.isDeletedArrayItemPath(key, snapshot)) continue;
460
+ this.userEditedSet.delete(key);
461
+ }
462
+
377
463
  for (const key of Array.from(this.lastDefaultValueByPathKey.keys())) {
378
464
  if (!this.isDeletedArrayItemPath(key, snapshot)) continue;
379
465
  this.lastDefaultValueByPathKey.delete(key);
@@ -444,6 +530,7 @@ export class FormValueRuntime {
444
530
  if (!nextIndexByIdentity.size) continue;
445
531
 
446
532
  this.reconcileArrayItemSet(this.explicitSet, path, prevValue, nextIndexByIdentity);
533
+ this.reconcileArrayItemSet(this.userEditedSet, path, prevValue, nextIndexByIdentity);
447
534
  this.reconcileArrayItemMap(this.lastDefaultValueByPathKey, path, prevValue, nextIndexByIdentity);
448
535
  this.reconcileArrayItemMap(this.lastWriteMetaByPathKey, path, prevValue, nextIndexByIdentity);
449
536
  this.reconcileObservableBindings(path, prevValue, nextIndexByIdentity);
@@ -647,7 +734,7 @@ export class FormValueRuntime {
647
734
  this.writeSeq += 1;
648
735
  bumpedWriteSeq = true;
649
736
  }
650
- _.set(this.valuesMirror, p, nextValue);
737
+ _.set(this.valuesMirror, p, this.toMirrorSnapshot(nextValue));
651
738
  hasMirrorChange = true;
652
739
  actuallyChangedPaths.push(p);
653
740
  }
@@ -665,7 +752,11 @@ export class FormValueRuntime {
665
752
 
666
753
  // 非 default 来源写入:需要使默认值永久失效(explicit)
667
754
  for (const p of explicitPathsToMark) {
668
- this.markExplicit(namePathToPathKey(p));
755
+ const pathKey = namePathToPathKey(p);
756
+ this.markExplicit(pathKey);
757
+ if (source === 'user') {
758
+ this.markUserEdited(pathKey);
759
+ }
669
760
  }
670
761
 
671
762
  if (hasMirrorChange) {
@@ -726,6 +817,9 @@ export class FormValueRuntime {
726
817
  seen,
727
818
  );
728
819
  }
820
+ if (prevValue.length !== nextValue.length) {
821
+ return nestedCount + (this.pushExplicitPath(basePath, explicitPaths, seen) ? 1 : 0);
822
+ }
729
823
  return nestedCount;
730
824
  }
731
825
 
@@ -870,7 +964,8 @@ export class FormValueRuntime {
870
964
  } else {
871
965
  form.setFieldsValue?.({ [pathKey]: value });
872
966
  }
873
- _.set(this.valuesMirror, [pathKey], value);
967
+ _.set(this.valuesMirror, [pathKey], this.toMirrorSnapshot(value));
968
+ this.syncMountedFieldModelValue([pathKey], value);
874
969
  }
875
970
  this.bumpChangeTick();
876
971
  } finally {
@@ -885,6 +980,9 @@ export class FormValueRuntime {
885
980
  if (markExplicit) {
886
981
  for (const k of patchKeys) {
887
982
  this.markExplicit(k);
983
+ if (source === 'user') {
984
+ this.markUserEdited(k);
985
+ }
888
986
  }
889
987
  }
890
988
 
@@ -969,7 +1067,8 @@ export class FormValueRuntime {
969
1067
  try {
970
1068
  for (const { namePath, value } of filteredToWrite) {
971
1069
  form.setFieldValue?.(namePath, value);
972
- _.set(this.valuesMirror, namePath, value);
1070
+ _.set(this.valuesMirror, namePath, this.toMirrorSnapshot(value));
1071
+ this.syncMountedFieldModelValue(namePath, value);
973
1072
  changedPaths.push(namePath);
974
1073
  }
975
1074
  this.bumpChangeTick();
@@ -984,13 +1083,16 @@ export class FormValueRuntime {
984
1083
  if (markExplicit) {
985
1084
  for (const { pathKey } of filteredToWrite) {
986
1085
  this.markExplicit(pathKey);
1086
+ if (source === 'user') {
1087
+ this.markUserEdited(pathKey);
1088
+ }
987
1089
  }
988
1090
  }
989
1091
 
990
1092
  if (source === 'default') {
991
1093
  for (const { namePath, pathKey } of filteredToWrite) {
992
1094
  const current = this.getFormValueAtPath(namePath);
993
- this.lastDefaultValueByPathKey.set(pathKey, current);
1095
+ this.lastDefaultValueByPathKey.set(pathKey, this.toMirrorSnapshot(current));
994
1096
  }
995
1097
  }
996
1098
 
@@ -1052,6 +1154,7 @@ export class FormValueRuntime {
1052
1154
  if (this.disposed) return;
1053
1155
  const form = this.getForm?.();
1054
1156
  if (!form) return;
1157
+ if (source === 'override' && this.findUserEditedHit(pathKey)) return;
1055
1158
 
1056
1159
  const prevValue = _.get(this.valuesMirror, namePath);
1057
1160
  if (_.isEqual(prevValue, nextValue)) return;
@@ -1062,7 +1165,8 @@ export class FormValueRuntime {
1062
1165
  this.suppressFormCallbackDepth++;
1063
1166
  try {
1064
1167
  form.setFieldValue?.(namePath, nextValue);
1065
- _.set(this.valuesMirror, namePath, nextValue);
1168
+ _.set(this.valuesMirror, namePath, this.toMirrorSnapshot(nextValue));
1169
+ this.syncMountedFieldModelValue(namePath, nextValue);
1066
1170
  this.bumpChangeTick();
1067
1171
  } finally {
1068
1172
  this.suppressFormCallbackDepth--;
@@ -1098,7 +1202,10 @@ export class FormValueRuntime {
1098
1202
  }
1099
1203
  this.emitFormValuesChange(payload);
1100
1204
 
1101
- if (source === 'default' && this.isExplicit(pathKey)) {
1205
+ if (
1206
+ (source === 'default' && this.isExplicit(pathKey)) ||
1207
+ (source === 'override' && this.findUserEditedHit(pathKey))
1208
+ ) {
1102
1209
  const existing = this.observableBindings.get(pathKey);
1103
1210
  if (existing) {
1104
1211
  existing.dispose();
@@ -1107,7 +1214,7 @@ export class FormValueRuntime {
1107
1214
  }
1108
1215
 
1109
1216
  if (source === 'default') {
1110
- this.lastDefaultValueByPathKey.set(pathKey, this.getFormValueAtPath(namePath));
1217
+ this.lastDefaultValueByPathKey.set(pathKey, this.toMirrorSnapshot(this.getFormValueAtPath(namePath)));
1111
1218
  }
1112
1219
  }
1113
1220
 
@@ -1133,6 +1240,148 @@ export class FormValueRuntime {
1133
1240
  this.model.emitter?.emit?.('formValuesChange', payload);
1134
1241
  }
1135
1242
 
1243
+ private syncMountedFieldModelValue(namePath: NamePath, value: unknown) {
1244
+ const targetKey = namePathToPathKey(namePath);
1245
+ if (!targetKey) return;
1246
+
1247
+ const models = this.mountedFieldModelsByPathKey.get(targetKey);
1248
+ if (!models?.size) return;
1249
+
1250
+ const visited = new Set<FlowModel>();
1251
+ for (const model of Array.from(models)) {
1252
+ this.syncMountedFieldModelValueForModel(model, value, visited);
1253
+ }
1254
+ }
1255
+
1256
+ private syncMountedFieldModelValueForModel(
1257
+ model: FlowModel | null | undefined,
1258
+ value: unknown,
1259
+ visited: Set<FlowModel>,
1260
+ ) {
1261
+ if (!model || visited.has(model)) return;
1262
+ visited.add(model);
1263
+ if (!this.isMountedModelInThisForm(model)) return;
1264
+
1265
+ const nextProps = { value };
1266
+ model.setProps?.(nextProps);
1267
+ const fieldModel = (model as FlowModel & { subModels?: { field?: FlowModel } }).subModels?.field;
1268
+ fieldModel?.setProps?.(nextProps);
1269
+ }
1270
+
1271
+ private rebuildMountedFieldModelIndex() {
1272
+ this.mountedFieldModelsByPathKey.clear();
1273
+ const engine = this.model?.context?.engine;
1274
+ if (!engine || typeof engine.forEachModel !== 'function') return;
1275
+
1276
+ engine.forEachModel((model: FlowModel) => {
1277
+ this.indexMountedFieldModelTree(model);
1278
+ });
1279
+ }
1280
+
1281
+ private indexMountedFieldModelTree(model: FlowModel | null | undefined) {
1282
+ this.visitModelAndForks(model, (item) => this.indexMountedFieldModel(item));
1283
+ }
1284
+
1285
+ private unindexMountedFieldModelTree(model: FlowModel | null | undefined) {
1286
+ this.visitModelAndForks(model, (item) => this.unindexMountedFieldModel(item));
1287
+ }
1288
+
1289
+ private visitModelAndForks(model: FlowModel | null | undefined, visit: (model: FlowModel) => void) {
1290
+ if (!model) return;
1291
+ const visited = new Set<FlowModel>();
1292
+ const walk = (item: FlowModel | null | undefined) => {
1293
+ if (!item || visited.has(item)) return;
1294
+ visited.add(item);
1295
+ visit(item);
1296
+ const forks = (item as FlowModel & { forks?: { forEach?: (cb: (fork: FlowModel) => void) => void } }).forks;
1297
+ forks?.forEach?.((fork) => walk(fork));
1298
+ };
1299
+ walk(model);
1300
+ }
1301
+
1302
+ private indexMountedFieldModel(model: FlowModel) {
1303
+ if (!this.isMountedModelInThisForm(model)) return;
1304
+
1305
+ const pathKeys = new Set<string>();
1306
+ for (const path of this.getMountedModelNamePaths(model)) {
1307
+ const key = namePathToPathKey(path);
1308
+ if (key) pathKeys.add(key);
1309
+ }
1310
+ if (!pathKeys.size) return;
1311
+
1312
+ this.unindexMountedFieldModel(model);
1313
+ this.mountedFieldModelPathKeys.set(model, pathKeys);
1314
+
1315
+ for (const key of pathKeys) {
1316
+ const models = this.mountedFieldModelsByPathKey.get(key) || new Set<FlowModel>();
1317
+ models.add(model);
1318
+ this.mountedFieldModelsByPathKey.set(key, models);
1319
+ }
1320
+ }
1321
+
1322
+ private unindexMountedFieldModel(model: FlowModel) {
1323
+ const pathKeys = this.mountedFieldModelPathKeys.get(model);
1324
+ if (!pathKeys) return;
1325
+
1326
+ for (const key of pathKeys) {
1327
+ const models = this.mountedFieldModelsByPathKey.get(key);
1328
+ if (!models) continue;
1329
+ models.delete(model);
1330
+ if (!models.size) {
1331
+ this.mountedFieldModelsByPathKey.delete(key);
1332
+ }
1333
+ }
1334
+
1335
+ this.mountedFieldModelPathKeys.delete(model);
1336
+ }
1337
+
1338
+ private isMountedModelInThisForm(model: FlowModel) {
1339
+ const blockModel = (model.context as { blockModel?: FlowModel } | undefined)?.blockModel;
1340
+ return !!blockModel && String(blockModel.uid) === String(this.model.uid);
1341
+ }
1342
+
1343
+ private getMountedModelNamePaths(model: FlowModel): NamePath[] {
1344
+ const paths: NamePath[] = [];
1345
+ const push = (value: unknown) => {
1346
+ const path = this.normalizeNamePathValue(value);
1347
+ if (path?.length) paths.push(path);
1348
+ };
1349
+
1350
+ push((model.context as { fieldPathArray?: unknown } | undefined)?.fieldPathArray);
1351
+
1352
+ const props = typeof model.getProps === 'function' ? model.getProps() : (model as { props?: unknown }).props;
1353
+ if (props && typeof props === 'object') {
1354
+ push((props as { name?: unknown }).name);
1355
+ }
1356
+
1357
+ try {
1358
+ const init = model.getStepParams?.('fieldSettings', 'init');
1359
+ if (init && typeof init === 'object') {
1360
+ push((init as { fieldPath?: unknown }).fieldPath);
1361
+ }
1362
+ } catch {
1363
+ // ignore models without field settings
1364
+ }
1365
+
1366
+ const seen = new Set<string>();
1367
+ return paths.filter((path) => {
1368
+ const key = namePathToPathKey(path);
1369
+ if (!key || seen.has(key)) return false;
1370
+ seen.add(key);
1371
+ return true;
1372
+ });
1373
+ }
1374
+
1375
+ private normalizeNamePathValue(value: unknown): NamePath | null {
1376
+ if (Array.isArray(value)) {
1377
+ const path = value.filter((seg): seg is string | number => typeof seg === 'string' || typeof seg === 'number');
1378
+ return path.length === value.length ? path : null;
1379
+ }
1380
+ if (typeof value === 'number') return [value];
1381
+ if (typeof value === 'string' && value) return pathKeyToNamePath(value);
1382
+ return null;
1383
+ }
1384
+
1136
1385
  private markExplicit(pathKey: string) {
1137
1386
  if (this.explicitSet.has(pathKey)) return;
1138
1387
  this.explicitSet.add(pathKey);
@@ -1154,6 +1403,18 @@ export class FormValueRuntime {
1154
1403
  }
1155
1404
  }
1156
1405
 
1406
+ private markUserEdited(pathKey: string) {
1407
+ if (this.userEditedSet.has(pathKey)) return;
1408
+ this.userEditedSet.add(pathKey);
1409
+
1410
+ for (const [k, binding] of Array.from(this.observableBindings.entries())) {
1411
+ if (binding.source !== 'override') continue;
1412
+ if (!this.findUserEditedHit(k)) continue;
1413
+ binding.dispose();
1414
+ this.observableBindings.delete(k);
1415
+ }
1416
+ }
1417
+
1157
1418
  private normalizeObservedNamePath(name: NamePath | string | number | undefined): NamePath | null {
1158
1419
  if (Array.isArray(name)) return name as NamePath;
1159
1420
  if (typeof name === 'number') return [name];
@@ -1216,4 +1477,78 @@ export class FormValueRuntime {
1216
1477
  }
1217
1478
  return null;
1218
1479
  }
1480
+
1481
+ private findUserEditedHit(pathKey: string): string | null {
1482
+ if (this.userEditedSet.has(pathKey)) return pathKey;
1483
+ const namePath = pathKeyToNamePath(pathKey);
1484
+ const prefix: NamePath = [];
1485
+
1486
+ for (let i = 0; i < namePath.length; i++) {
1487
+ prefix.push(namePath[i]);
1488
+ const key = namePathToPathKey(prefix as any);
1489
+ if (!this.userEditedSet.has(key)) continue;
1490
+
1491
+ const nextSeg = namePath[i + 1];
1492
+ if (typeof nextSeg === 'number') {
1493
+ continue;
1494
+ }
1495
+ return key;
1496
+ }
1497
+ for (const key of this.userEditedSet) {
1498
+ if (!this.isDescendantPathKey(key, pathKey)) continue;
1499
+ return key;
1500
+ }
1501
+ return null;
1502
+ }
1503
+
1504
+ private findLatestWriteMeta(pathKey: string): FormValueWriteMeta | undefined {
1505
+ let latest: FormValueWriteMeta | undefined;
1506
+ const namePath = pathKeyToNamePath(pathKey);
1507
+ const prefix: NamePath = [];
1508
+
1509
+ for (let i = 0; i < namePath.length; i++) {
1510
+ prefix.push(namePath[i]);
1511
+ const meta = this.lastWriteMetaByPathKey.get(namePathToPathKey(prefix as any));
1512
+ if (!meta) {
1513
+ continue;
1514
+ }
1515
+ if (!latest || meta.writeSeq >= latest.writeSeq) {
1516
+ latest = meta;
1517
+ }
1518
+ }
1519
+
1520
+ return latest;
1521
+ }
1522
+
1523
+ private isCurrentUserEditedPath(pathKey: string) {
1524
+ const lastWrite = this.findLatestWriteMeta(pathKey);
1525
+ return !lastWrite || lastWrite.source === 'user';
1526
+ }
1527
+
1528
+ private omitNonUserDescendantValues(pathKey: string, value: unknown) {
1529
+ if (!value || typeof value !== 'object') {
1530
+ return value;
1531
+ }
1532
+
1533
+ const namePath = pathKeyToNamePath(pathKey);
1534
+ for (const childKey of this.lastWriteMetaByPathKey.keys()) {
1535
+ if (!this.isDescendantPathKey(childKey, pathKey)) {
1536
+ continue;
1537
+ }
1538
+ if (this.isCurrentUserEditedPath(childKey)) {
1539
+ continue;
1540
+ }
1541
+ _.unset(value as Record<string, unknown>, pathKeyToNamePath(childKey).slice(namePath.length) as any);
1542
+ }
1543
+
1544
+ return value;
1545
+ }
1546
+
1547
+ private isDescendantPathKey(candidateKey: string, parentKey: string) {
1548
+ if (!candidateKey || !parentKey || candidateKey === parentKey) return false;
1549
+ const candidatePath = pathKeyToNamePath(candidateKey);
1550
+ const parentPath = pathKeyToNamePath(parentKey);
1551
+ if (candidatePath.length <= parentPath.length) return false;
1552
+ return parentPath.every((seg, index) => candidatePath[index] === seg);
1553
+ }
1219
1554
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  export type NamePath = Array<string | number>;
11
11
 
12
- export type ValueSource = 'default' | 'linkage' | 'user' | 'system';
12
+ export type ValueSource = 'default' | 'linkage' | 'user' | 'system' | 'override';
13
13
 
14
14
  export type Patch =
15
15
  | Record<string, any>
@@ -57,7 +57,7 @@ export interface FormValuesChangePayload {
57
57
  allValuesSnapshot?: any;
58
58
  }
59
59
 
60
- export type AssignMode = 'default' | 'assign';
60
+ export type AssignMode = 'default' | 'assign' | 'override';
61
61
 
62
62
  export type FormAssignRuleItem = {
63
63
  key?: string;