@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
@@ -15,17 +15,29 @@ import {
15
15
  isRunJSValue,
16
16
  } from '@nocobase/flow-engine';
17
17
  import _ from 'lodash';
18
- import { namePathToPathKey, parsePathString, pathKeyToNamePath } from '../models/blocks/form/value-runtime/path';
18
+ import { namePathToPathKey } from '../models/blocks/form/value-runtime/path';
19
19
  import {
20
20
  collectStaticDepsFromRunJSValue,
21
21
  collectStaticDepsFromTemplateValue,
22
22
  recordDep,
23
23
  type DepCollector,
24
24
  } from '../models/blocks/form/value-runtime/deps';
25
+ import {
26
+ buildItemListRootPath,
27
+ buildItemRowPath,
28
+ dedupeNamePaths,
29
+ findFormValueChangeSource,
30
+ getChangedPathsFromPayload,
31
+ getFieldIndexEntriesFromContext,
32
+ isNamePathPrefix,
33
+ isSameNamePath,
34
+ minimizeNamePaths,
35
+ parseDependencyPath,
36
+ parsePathKey,
37
+ type NamePath,
38
+ } from '../utils/formValueDeps';
25
39
  import { linkageRulesRefresh } from './linkageRulesRefresh';
26
40
 
27
- type NamePath = Array<string | number>;
28
-
29
41
  type LinkageRefreshDeps = {
30
42
  wildcard: boolean;
31
43
  valuePaths: NamePath[];
@@ -40,75 +52,11 @@ type LinkageRefreshBinding = {
40
52
  dispose: () => void;
41
53
  };
42
54
 
43
- type FieldIndexEntry = {
44
- name: string;
45
- index: number;
46
- };
47
-
48
55
  const FORM_VALUES_CHANGE_EVENT = 'formValuesChange';
49
56
  const LINKAGE_REFRESH_BINDINGS_KEY = '__formValueDrivenLinkageRefreshBindings';
50
57
 
51
- function isSameNamePath(a: NamePath, b: NamePath) {
52
- return a.length === b.length && a.every((seg, index) => seg === b[index]);
53
- }
54
-
55
- function isNamePathPrefix(prefix: NamePath, path: NamePath) {
56
- if (prefix.length > path.length) return false;
57
- return prefix.every((seg, index) => seg === path[index]);
58
- }
59
-
60
- function dedupeNamePaths(paths: NamePath[]) {
61
- const byKey = new Map<string, NamePath>();
62
- for (const path of paths) {
63
- if (!path?.length) continue;
64
- byKey.set(namePathToPathKey(path), path);
65
- }
66
- return Array.from(byKey.values());
67
- }
68
-
69
- function minimizeValueNamePaths(paths: NamePath[]) {
70
- const deduped = dedupeNamePaths(paths);
71
- return deduped.filter((path, index) => {
72
- return !deduped.some((other, otherIndex) => otherIndex !== index && isNamePathPrefix(path, other));
73
- });
74
- }
75
-
76
- function parseFieldIndexEntries(fieldIndex: unknown): FieldIndexEntry[] {
77
- const arr = Array.isArray(fieldIndex) ? fieldIndex : [];
78
- const entries: FieldIndexEntry[] = [];
79
- for (const it of arr) {
80
- if (typeof it !== 'string') continue;
81
- const [name, indexStr] = it.split(':');
82
- const index = Number(indexStr);
83
- if (!name || Number.isNaN(index)) continue;
84
- entries.push({ name, index });
85
- }
86
- return entries;
87
- }
88
-
89
- function getFieldIndexEntriesFromContext(ctx: any): FieldIndexEntry[] {
90
- return parseFieldIndexEntries(ctx?.model?.context?.fieldIndex ?? ctx?.fieldIndex);
91
- }
92
-
93
- function buildItemRowPath(entries: FieldIndexEntry[], parentDepth: number): NamePath | null {
94
- const targetIndex = entries.length - 1 - parentDepth;
95
- if (targetIndex < 0) return null;
96
-
97
- const out: NamePath = [];
98
- for (let i = 0; i <= targetIndex; i++) {
99
- out.push(entries[i].name, entries[i].index);
100
- }
101
- return out;
102
- }
103
-
104
- function buildItemListRootPath(entries: FieldIndexEntry[], parentDepth: number): NamePath | null {
105
- const rowPath = buildItemRowPath(entries, parentDepth);
106
- if (!rowPath?.length) return null;
107
- return rowPath.slice(0, -1);
108
- }
109
-
110
58
  function resolveItemDependencyPath(ctx: FlowContext, depPath: NamePath): LinkageRefreshDeps {
111
- const entries = getFieldIndexEntriesFromContext(ctx as any);
59
+ const entries = getFieldIndexEntriesFromContext(ctx);
112
60
  if (!entries.length) {
113
61
  return { wildcard: true, valuePaths: [], structuralPaths: [] };
114
62
  }
@@ -171,8 +119,7 @@ function addRunjsUsageToCollector(script: string, collector: DepCollector) {
171
119
  collector.wildcard = true;
172
120
  continue;
173
121
  }
174
- const segs = parsePathString(String(subPath)).filter((seg) => typeof seg !== 'object') as NamePath;
175
- recordDep(segs, collector);
122
+ recordDep(parseDependencyPath(String(subPath)), collector);
176
123
  continue;
177
124
  }
178
125
  if (varName === 'item') {
@@ -233,13 +180,13 @@ function collectLinkageRefreshDeps(ctx: FlowContext, params: any): LinkageRefres
233
180
  wildcard = true;
234
181
  continue;
235
182
  }
236
- valuePaths.push(pathKeyToNamePath(inner));
183
+ valuePaths.push(parsePathKey(inner));
237
184
  continue;
238
185
  }
239
186
 
240
187
  if (depKey === 'ctx:item' || depKey.startsWith('ctx:item:')) {
241
188
  const subPath = depKey === 'ctx:item' ? '' : depKey.slice('ctx:item:'.length);
242
- const depPath = subPath ? (parsePathString(subPath).filter((seg) => typeof seg !== 'object') as NamePath) : [];
189
+ const depPath = subPath ? parseDependencyPath(subPath) : [];
243
190
  const resolved = resolveItemDependencyPath(ctx, depPath);
244
191
  wildcard ||= resolved.wildcard;
245
192
  valuePaths.push(...resolved.valuePaths);
@@ -249,7 +196,7 @@ function collectLinkageRefreshDeps(ctx: FlowContext, params: any): LinkageRefres
249
196
 
250
197
  return {
251
198
  wildcard,
252
- valuePaths: minimizeValueNamePaths(valuePaths),
199
+ valuePaths: minimizeNamePaths(valuePaths),
253
200
  structuralPaths: dedupeNamePaths(structuralPaths),
254
201
  };
255
202
  }
@@ -258,44 +205,9 @@ function hasLinkageRefreshDeps(deps: LinkageRefreshDeps) {
258
205
  return deps.wildcard || deps.valuePaths.length > 0 || deps.structuralPaths.length > 0;
259
206
  }
260
207
 
261
- function getChangedPathsFromPayload(payload: any): NamePath[] {
262
- const rawChangedPaths = Array.isArray(payload?.changedPaths) ? payload.changedPaths : [];
263
- const out: NamePath[] = [];
264
-
265
- for (const path of rawChangedPaths) {
266
- if (Array.isArray(path)) {
267
- if (path.length === 1 && typeof path[0] === 'string') {
268
- const namePath = pathKeyToNamePath(path[0]);
269
- if (namePath.length) out.push(namePath);
270
- continue;
271
- }
272
- const segs = path.filter((seg) => typeof seg === 'string' || typeof seg === 'number') as NamePath;
273
- if (segs.length) out.push(segs);
274
- continue;
275
- }
276
- if (typeof path === 'string' && path) {
277
- out.push(pathKeyToNamePath(path));
278
- }
279
- }
280
-
281
- if (out.length) {
282
- return out;
283
- }
284
-
285
- const changedValues = payload?.changedValues;
286
- if (changedValues && typeof changedValues === 'object') {
287
- for (const key of Object.keys(changedValues)) {
288
- const namePath = pathKeyToNamePath(key);
289
- if (namePath.length) out.push(namePath);
290
- }
291
- }
292
-
293
- return out;
294
- }
295
-
296
208
  function linkageRefreshDepsMatchPayload(deps: LinkageRefreshDeps, payload: any) {
297
209
  if (!hasLinkageRefreshDeps(deps)) return false;
298
- const changedPaths = getChangedPathsFromPayload(payload);
210
+ const changedPaths = getChangedPathsFromPayload(payload, { includeArrayChangedValues: true });
299
211
  if (deps.wildcard) return true;
300
212
  if (!changedPaths.length) return true;
301
213
 
@@ -330,28 +242,8 @@ function getLinkageRefreshBindings(model: any): Map<string, LinkageRefreshBindin
330
242
  return (model[LINKAGE_REFRESH_BINDINGS_KEY] ||= new Map<string, LinkageRefreshBinding>());
331
243
  }
332
244
 
333
- function isFormBlockForLinkageRefresh(model: any) {
334
- if (!model || typeof model !== 'object') return false;
335
- if (!model.emitter || typeof model.emitter.on !== 'function' || typeof model.emitter.off !== 'function') return false;
336
- return !!model.formValueRuntime || !!model.context?.form || typeof model.context?.setFormValues === 'function';
337
- }
338
-
339
245
  function findFormBlockForLinkageRefresh(ctx: FlowContext): any | null {
340
- const candidates: any[] = [];
341
- const push = (model: any) => {
342
- if (model && !candidates.includes(model)) candidates.push(model);
343
- };
344
-
345
- push((ctx.model as any)?.context?.blockModel);
346
- push(ctx.model);
347
-
348
- let cursor: any = (ctx.model as any)?.parent;
349
- while (cursor) {
350
- push(cursor);
351
- cursor = cursor?.parent;
352
- }
353
-
354
- return candidates.find(isFormBlockForLinkageRefresh) || null;
246
+ return findFormValueChangeSource(ctx) as any;
355
247
  }
356
248
 
357
249
  function disposeLinkageRefreshBinding(model: any, key: string) {
@@ -7,7 +7,15 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { defineAction, tExpr, FlowModelContext, FlowModel, FlowExitAllException } from '@nocobase/flow-engine';
10
+ import {
11
+ defineAction,
12
+ tExpr,
13
+ FlowModelContext,
14
+ FlowModel,
15
+ FlowExitAllException,
16
+ createOpenViewRouteState,
17
+ RUNJS_OPEN_VIEW_ROUTE_STATE,
18
+ } from '@nocobase/flow-engine';
11
19
  import React from 'react';
12
20
  import { FlowPage } from '../FlowPage';
13
21
  import { PageModel, RootPageModel } from '../models';
@@ -291,8 +299,32 @@ export const openView = defineAction({
291
299
  ? (inputArgs as any).associationName
292
300
  : (params as any)?.associationName;
293
301
  const mergedTabUid = typeof inputArgs.tabUid !== 'undefined' ? inputArgs.tabUid : params.tabUid;
294
- // 移动端中只需要显示子页面
295
- const openMode = ctx.inputArgs?.isMobileLayout ? 'embed' : ctx.inputArgs?.mode || params.mode || 'drawer';
302
+ const hasRunJSOpenViewRouteState = Object.prototype.hasOwnProperty.call(inputArgs, RUNJS_OPEN_VIEW_ROUTE_STATE);
303
+ const runJSOpenViewRouteStateInput = (inputArgs as Record<PropertyKey, unknown>)[RUNJS_OPEN_VIEW_ROUTE_STATE];
304
+ const runJSOpenViewRouteState = hasRunJSOpenViewRouteState
305
+ ? createOpenViewRouteState(
306
+ runJSOpenViewRouteStateInput && typeof runJSOpenViewRouteStateInput === 'object'
307
+ ? (runJSOpenViewRouteStateInput as { mode?: unknown; size?: unknown })
308
+ : {
309
+ mode: inputArgs.mode,
310
+ size: inputArgs.size,
311
+ },
312
+ )
313
+ : undefined;
314
+ const replayOpenViewRouteStateInput = (inputArgs as { openViewRouteState?: unknown }).openViewRouteState;
315
+ const replayOpenViewRouteState =
316
+ replayOpenViewRouteStateInput && typeof replayOpenViewRouteStateInput === 'object'
317
+ ? createOpenViewRouteState(replayOpenViewRouteStateInput as { mode?: unknown; size?: unknown })
318
+ : undefined;
319
+ const runtimeOpenViewRouteState = runJSOpenViewRouteState || replayOpenViewRouteState;
320
+ // 移动端中只需要显示子页面。本次 dispatch 参数优先于持久化默认值。
321
+ const openMode = ctx.inputArgs?.isMobileLayout
322
+ ? 'embed'
323
+ : runtimeOpenViewRouteState?.mode || ctx.inputArgs?.mode || params.mode || 'drawer';
324
+ const effectiveRunJSOpenViewRouteState =
325
+ runJSOpenViewRouteState && ctx.inputArgs?.isMobileLayout
326
+ ? createOpenViewRouteState({ ...runJSOpenViewRouteState, mode: openMode })
327
+ : runJSOpenViewRouteState;
296
328
  let navigation = typeof inputArgs.navigation !== 'undefined' ? inputArgs.navigation : params.navigation;
297
329
 
298
330
  // 传递了上下文就必须禁用路由,否则下次路由打开会缺少上下文
@@ -313,6 +345,7 @@ export const openView = defineAction({
313
345
  sourceId: mergedSourceId,
314
346
  tabUid: mergedTabUid,
315
347
  viewUid: ctx.model.context?.inputArgs?.viewUid || ctx.model.uid,
348
+ ...(effectiveRunJSOpenViewRouteState ? { openViewRouteState: effectiveRunJSOpenViewRouteState } : {}),
316
349
  } as Record<string, unknown>;
317
350
  const pendingView = {
318
351
  type: pendingType,
@@ -328,6 +361,7 @@ export const openView = defineAction({
328
361
  filterByTk: mergedFilterByTk,
329
362
  sourceId: mergedSourceId,
330
363
  tabUid: mergedTabUid,
364
+ ...(effectiveRunJSOpenViewRouteState ? { openViewRouteState: effectiveRunJSOpenViewRouteState } : {}),
331
365
  };
332
366
  ctx.view.navigation.navigateTo(nextView);
333
367
  return;
@@ -373,7 +407,7 @@ export const openView = defineAction({
373
407
 
374
408
  const pageModelClass =
375
409
  ctx.inputArgs.pageModelClass || params.pageModelClass || ctx.layout?.childPageModelClass || 'ChildPageModel';
376
- const size = ctx.inputArgs.size || params.size || 'medium';
410
+ const size = runtimeOpenViewRouteState?.size || ctx.inputArgs.size || params.size || 'medium';
377
411
  let pageModelUid: string | null = null;
378
412
  let pageModelRef: FlowModel | null = null;
379
413
 
@@ -7,14 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import {
11
- ActionScene,
12
- defineAction,
13
- tExpr,
14
- createSafeWindow,
15
- createSafeDocument,
16
- createSafeNavigator,
17
- } from '@nocobase/flow-engine';
10
+ import { ActionScene, defineAction, tExpr } from '@nocobase/flow-engine';
18
11
  import { CodeEditor } from '../components/code-editor';
19
12
 
20
13
  export const runjs = defineAction({
@@ -38,11 +31,6 @@ export const runjs = defineAction({
38
31
  // 如果是 URL 触发的,则不执行代码
39
32
  if (ctx.inputArgs?.navigation) return;
40
33
 
41
- const navigator = createSafeNavigator();
42
- return ctx.runjs(params.code, {
43
- window: createSafeWindow({ navigator }),
44
- document: createSafeDocument(),
45
- navigator,
46
- });
34
+ return ctx.runjs(params.code);
47
35
  },
48
36
  });
@@ -10,6 +10,56 @@
10
10
  import { defineAction, jioToJoiSchema, tExpr } from '@nocobase/flow-engine';
11
11
  import { FieldValidation } from '../../flow-compat';
12
12
 
13
+ type ValidationType = 'string' | 'number' | 'array' | 'boolean' | 'any';
14
+
15
+ interface ValidationRule {
16
+ name: string;
17
+ args?: any;
18
+ }
19
+
20
+ interface ValidationData {
21
+ type: ValidationType;
22
+ rules?: ValidationRule[];
23
+ }
24
+
25
+ interface ValidationContext {
26
+ model: {
27
+ props: {
28
+ label?: string;
29
+ };
30
+ };
31
+ t: (key: string, options?: Record<string, unknown>) => string;
32
+ }
33
+
34
+ function buildValidationRules(ctx: ValidationContext, validation: ValidationData) {
35
+ const rules = [];
36
+ const schema = jioToJoiSchema(validation);
37
+ const label = ctx.model.props.label;
38
+ rules.push({
39
+ validator: (_: unknown, value: unknown) => {
40
+ const { error } = schema.validate(value, {
41
+ abortEarly: false,
42
+ });
43
+
44
+ if (error) {
45
+ const messages = error.details.map((d: { type: string; context?: Record<string, unknown> }) => {
46
+ return ctx.t(`${d.type}`, {
47
+ ...d.context,
48
+ ns: 'data-source-main',
49
+ label,
50
+ });
51
+ });
52
+ const div = document.createElement('div');
53
+ div.innerHTML = messages.join('; ');
54
+ return Promise.reject(div.textContent);
55
+ }
56
+
57
+ return Promise.resolve();
58
+ },
59
+ });
60
+ return rules;
61
+ }
62
+
13
63
  export const validation = defineAction({
14
64
  title: tExpr('Validation'),
15
65
  name: 'validation',
@@ -29,6 +79,7 @@ export const validation = defineAction({
29
79
  type: targetInterface.validationType,
30
80
  availableValidationOptions: [...new Set(targetInterface.availableValidationOptions)],
31
81
  excludeValidationOptions: [...new Set(targetInterface.excludeValidationOptions)],
82
+ inheritedValue: ctx.model.collectionField.validation,
32
83
  isAssociation: targetInterface.isAssociation,
33
84
  },
34
85
  },
@@ -36,40 +87,21 @@ export const validation = defineAction({
36
87
  },
37
88
  handler(ctx, params) {
38
89
  if (params.validation) {
39
- const rules = [];
40
- const schema = jioToJoiSchema(params.validation);
41
- const label = ctx.model.props.label;
42
- rules.push({
43
- validator: (_, value) => {
44
- const { error } = schema.validate(value, {
45
- abortEarly: false,
46
- });
47
-
48
- if (error) {
49
- const messages = error.details.map((d) => {
50
- return ctx.t(`${d.type}`, {
51
- ...d.context,
52
- ns: 'data-source-main',
53
- label,
54
- });
55
- });
56
- const div = document.createElement('div');
57
- div.innerHTML = messages.join('; ');
58
- return Promise.reject(div.textContent);
59
- }
60
-
61
- return Promise.resolve();
62
- },
63
- });
64
- const hasRequiredInCollection = params.validation.rules.some((rule) => rule.name === 'required');
65
- if (hasRequiredInCollection) {
90
+ const collectionValidation = ctx.model.collectionField?.validation;
91
+ const collectionRules = ctx.model.collectionField?.getComponentProps?.().rules || [];
92
+ const uiRules = params.validation.rules?.length
93
+ ? buildValidationRules(ctx as unknown as ValidationContext, params.validation)
94
+ : [];
95
+ const hasRequiredInValidation = [collectionValidation, params.validation].some(
96
+ (validation) => validation?.rules?.some((rule) => rule.name === 'required'),
97
+ );
98
+ if (hasRequiredInValidation) {
66
99
  ctx.model.setProps({
67
- required: hasRequiredInCollection,
100
+ required: hasRequiredInValidation,
68
101
  });
69
102
  }
70
- console.log(rules);
71
103
  ctx.model.setProps({
72
- rules,
104
+ rules: [...collectionRules, ...uiRules],
73
105
  validation: params.validation,
74
106
  });
75
107
  }
@@ -8,7 +8,13 @@
8
8
  */
9
9
 
10
10
  import { define, observable } from '@formily/reactive';
11
- import { parsePathnameToViewParams, type FlowEngine, FlowModel, type ViewParam } from '@nocobase/flow-engine';
11
+ import {
12
+ decodeOpenViewRouteState,
13
+ parsePathnameToViewParams,
14
+ type FlowEngine,
15
+ FlowModel,
16
+ type ViewParam,
17
+ } from '@nocobase/flow-engine';
12
18
  import {
13
19
  BaseLayoutRouteCoordinator,
14
20
  type BaseLayoutRouteCoordinatorOptions,
@@ -116,20 +122,36 @@ const isStandardLayoutRelativePath = (relativePath: string) => {
116
122
  }
117
123
 
118
124
  let i = 1;
125
+ let currentViewUid = segments[0];
119
126
  while (i < segments.length) {
120
127
  const segment = segments[i];
128
+
121
129
  if (segment === 'view') {
122
130
  if (!segments[i + 1]) {
123
131
  return false;
124
132
  }
133
+ currentViewUid = segments[i + 1];
134
+ i += 2;
135
+ continue;
136
+ }
137
+
138
+ if (segment === 'opts') {
139
+ if (!segments[i + 1] || !decodeOpenViewRouteState(currentViewUid, segments[i + 1])) {
140
+ return false;
141
+ }
142
+ i += 2;
143
+ continue;
144
+ }
145
+
146
+ if (isKnownViewParamName(segment) && segments[i + 1]) {
125
147
  i += 2;
126
148
  continue;
127
149
  }
128
150
 
129
- if (!isKnownViewParamName(segment) || !segments[i + 1]) {
151
+ if (!segments[i + 1]) {
130
152
  return false;
131
153
  }
132
- i += 2;
154
+ return false;
133
155
  }
134
156
 
135
157
  return true;
@@ -272,7 +272,7 @@ export class BaseLayoutRouteCoordinator {
272
272
  runtime.viewState[getKey(viewItem)]?.destroy?.(true);
273
273
  delete runtime.viewState[getKey(viewItem)];
274
274
  });
275
- updateViewListHidden(viewList);
275
+ updateViewListHidden(viewList, !!this.layoutContext?.isMobileLayout);
276
276
  }
277
277
 
278
278
  if (viewsToOpen.length) {
@@ -299,7 +299,7 @@ export class BaseLayoutRouteCoordinator {
299
299
  }
300
300
 
301
301
  if (runtime.meta.active) {
302
- updateViewListHidden(viewList);
302
+ updateViewListHidden(viewList, !!this.layoutContext?.isMobileLayout);
303
303
  return;
304
304
  }
305
305
 
@@ -390,6 +390,7 @@ export class BaseLayoutRouteCoordinator {
390
390
  openViewParams?.associationName && !hasUsableSourceId(viewItem.params.sourceId)
391
391
  ? null
392
392
  : openViewParams?.associationName;
393
+ const openViewRouteState = viewItem.params.openViewRouteState;
393
394
  const openerUids = viewList.slice(0, viewItem.index).map((item) => item.params.viewUid);
394
395
  const navigation = new ViewNavigation(
395
396
  this.flowEngine.context,
@@ -408,6 +409,8 @@ export class BaseLayoutRouteCoordinator {
408
409
  deactivateRef,
409
410
  openerUids,
410
411
  ...viewItem.params,
412
+ ...(openViewRouteState?.mode ? { mode: openViewRouteState.mode } : {}),
413
+ ...(openViewRouteState?.size ? { size: openViewRouteState.size } : {}),
411
414
  pageActive: runtime.meta.active,
412
415
  activationControlledByLayout: true,
413
416
  navigation,
@@ -121,6 +121,19 @@ describe('AdminLayoutRouteCoordinator', () => {
121
121
  });
122
122
  });
123
123
 
124
+ it('passes RunJS openView route state as runtime mode and size during route replay', () => {
125
+ const { dispatchEvent } = setupRouteReplay({
126
+ openViewRouteState: { mode: 'dialog', size: 'large' },
127
+ });
128
+
129
+ expect(dispatchEvent.mock.calls[0][1]).toMatchObject({
130
+ mode: 'dialog',
131
+ size: 'large',
132
+ openViewRouteState: { mode: 'dialog', size: 'large' },
133
+ triggerByRouter: true,
134
+ });
135
+ });
136
+
124
137
  it('uses layout content element before route page placeholder as view target', () => {
125
138
  const engine = new FlowEngine();
126
139
  engine.registerModels({ RouteModel });
@@ -42,6 +42,7 @@ import {
42
42
  FLOW_SETTINGS_PREFERENCE_STORAGE_KEY,
43
43
  readFlowSettingsPreference,
44
44
  } from './flowSettingsPreference';
45
+ import { joinAdminLayoutRoutePath, type AdminLayoutRoutePathLike } from './resolveAdminRouteRuntimeTarget';
45
46
  import { useAppListRender } from './AppListRender';
46
47
 
47
48
  const className1 = css`
@@ -314,25 +315,37 @@ const DesignerButtonMenuItem: FC<{ item: AdminLayoutMenuNode; fallbackParentRout
314
315
  );
315
316
  };
316
317
 
317
- const matchesRoutePath = (route: NocoBaseDesktopRoute | undefined, pathname: string): boolean => {
318
+ const matchesRoutePath = (
319
+ route: NocoBaseDesktopRoute | undefined,
320
+ pathname: string,
321
+ layout?: AdminLayoutRoutePathLike | null,
322
+ ): boolean => {
318
323
  if (!route) {
319
324
  return false;
320
325
  }
321
326
 
322
327
  const candidates = [
323
- route.id != null ? `/admin/${route.id}` : null,
324
- route.schemaUid ? `/admin/${route.schemaUid}` : null,
328
+ route.id != null ? joinAdminLayoutRoutePath(layout, route.id) : null,
329
+ route.schemaUid ? joinAdminLayoutRoutePath(layout, route.schemaUid) : null,
325
330
  ].filter(Boolean) as string[];
326
331
 
327
332
  if (candidates.some((candidate) => pathname === candidate || pathname.startsWith(`${candidate}/`))) {
328
333
  return true;
329
334
  }
330
335
 
331
- return Array.isArray(route.children) ? route.children.some((child) => matchesRoutePath(child, pathname)) : false;
336
+ return Array.isArray(route.children)
337
+ ? route.children.some((child) => matchesRoutePath(child, pathname, layout))
338
+ : false;
332
339
  };
333
340
 
334
- const findSelectedTopGroupRoute = (routes: NocoBaseDesktopRoute[], pathname: string) => {
335
- return routes.find((route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname));
341
+ const findSelectedTopGroupRoute = (
342
+ routes: NocoBaseDesktopRoute[],
343
+ pathname: string,
344
+ layout?: AdminLayoutRoutePathLike | null,
345
+ ) => {
346
+ return routes.find(
347
+ (route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname, layout),
348
+ );
336
349
  };
337
350
 
338
351
  const renderMenuNodeWithModel = (
@@ -376,6 +389,11 @@ export const AdminLayoutComponent = observer((props: any) => {
376
389
  const { token } = antdTheme.useToken();
377
390
  const customToken = token as CustomToken;
378
391
  const isMobileLayout = !!adminLayoutModel?.isMobileLayout;
392
+ const adminLayoutRoutePath = adminLayoutModel?.layout?.routePath;
393
+ const adminLayoutRoutePathLike = useMemo<AdminLayoutRoutePathLike | undefined>(
394
+ () => (adminLayoutRoutePath ? { routePath: adminLayoutRoutePath } : undefined),
395
+ [adminLayoutRoutePath],
396
+ );
379
397
  const menuRouteRefreshVersion = adminLayoutModel?.menuRouteRefreshVersion || 0;
380
398
  const isMobileSider = isMobileLayout;
381
399
  const [collapsed, setCollapsed] = useState(isMobileSider);
@@ -403,8 +421,8 @@ export const AdminLayoutComponent = observer((props: any) => {
403
421
  [adminLayoutModel],
404
422
  );
405
423
  const selectedTopGroupRoute = useMemo(
406
- () => findSelectedTopGroupRoute(allAccessRoutes, location.pathname),
407
- [allAccessRoutes, location.pathname],
424
+ () => findSelectedTopGroupRoute(allAccessRoutes, location.pathname, adminLayoutRoutePathLike),
425
+ [adminLayoutRoutePathLike, allAccessRoutes, location.pathname],
408
426
  );
409
427
 
410
428
  const handleMenuDragEnd = useCallback(
@@ -458,6 +476,7 @@ export const AdminLayoutComponent = observer((props: any) => {
458
476
 
459
477
  useEffect(() => {
460
478
  const routeRepository = flowEngine.context.routeRepository;
479
+ const deactivateLayout = routeRepository?.activateLayout?.(adminLayoutModel?.layout);
461
480
  const subscriber = () => {
462
481
  const updatedRoutes = routeRepository?.listAccessible() || [];
463
482
  setAllAccessRoutes(updatedRoutes);
@@ -470,8 +489,9 @@ export const AdminLayoutComponent = observer((props: any) => {
470
489
 
471
490
  return () => {
472
491
  routeRepository?.unsubscribe(subscriber);
492
+ deactivateLayout?.();
473
493
  };
474
- }, [flowEngine]);
494
+ }, [adminLayoutModel, adminLayoutModel?.layout?.uid, flowEngine]);
475
495
 
476
496
  useEffect(() => {
477
497
  if (typeof window === 'undefined') {
@@ -697,7 +717,11 @@ export const AdminLayoutComponent = observer((props: any) => {
697
717
  <SetIsMobileLayout isMobile={isMobile} model={adminLayoutModel}>
698
718
  <ConfigProvider theme={isMobile ? mobileTheme : theme}>
699
719
  <GlobalStyle />
700
- <AdminLayoutContent onContentElementChange={handleLayoutContentElementChange} />
720
+ <AdminLayoutContent
721
+ designable={designable}
722
+ layout={adminLayoutModel?.layout}
723
+ onContentElementChange={handleLayoutContentElementChange}
724
+ />
701
725
  </ConfigProvider>
702
726
  </SetIsMobileLayout>
703
727
  );