@nocobase/client-v2 2.2.0-beta.8 → 2.2.0

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 (271) hide show
  1. package/es/APIClient.d.ts +13 -3
  2. package/es/BaseApplication.d.ts +3 -0
  3. package/es/RouteRepository.d.ts +8 -0
  4. package/es/RouterManager.d.ts +15 -0
  5. package/es/authRedirect.d.ts +1 -0
  6. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  8. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  9. package/es/components/category-tabs/index.d.ts +9 -0
  10. package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
  11. package/es/components/form/TypedVariableInput.d.ts +31 -5
  12. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  13. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  14. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  15. package/es/components/form/filter/index.d.ts +2 -0
  16. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  17. package/es/components/index.d.ts +1 -0
  18. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  19. package/es/entry-actions/index.d.ts +9 -0
  20. package/es/flow/actions/index.d.ts +1 -1
  21. package/es/flow/actions/linkageRules.d.ts +27 -0
  22. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  23. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  24. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  25. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  26. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  27. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  28. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  29. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  30. package/es/flow/components/FieldAssignValueInput.d.ts +7 -24
  31. package/es/flow/components/FlowRoute.d.ts +2 -2
  32. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  33. package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
  34. package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +31 -0
  35. package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
  36. package/es/flow/components/field-value-variable/index.d.ts +11 -0
  37. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  38. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  39. package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
  40. package/es/flow/index.d.ts +1 -0
  41. package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
  42. package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
  43. package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
  44. package/es/flow/models/base/ActionModelCore.d.ts +6 -2
  45. package/es/flow/models/base/BlockGridModel.d.ts +3 -0
  46. package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
  47. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  48. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  49. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  50. package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
  51. package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
  52. package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
  53. package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
  54. package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
  55. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  56. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
  57. package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
  58. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  59. package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
  60. package/es/flow/models/blocks/table/utils.d.ts +1 -0
  61. package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
  62. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
  63. package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
  64. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
  65. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  66. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  67. package/es/flow-compat/passwordUtils.d.ts +1 -1
  68. package/es/flow-compat/routeTypes.d.ts +1 -0
  69. package/es/index.d.ts +7 -0
  70. package/es/index.mjs +301 -177
  71. package/es/ui-operation/index.d.ts +15 -0
  72. package/es/ui-operation/ui-operation-codec.d.ts +10 -0
  73. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  74. package/es/utils/index.d.ts +1 -0
  75. package/es/utils/markdownSanitize.d.ts +11 -0
  76. package/lib/index.js +313 -189
  77. package/lib/locale/languageCodes.js +3 -1
  78. package/package.json +9 -7
  79. package/src/APIClient.ts +90 -7
  80. package/src/Application.tsx +7 -1
  81. package/src/BaseApplication.tsx +13 -6
  82. package/src/RouteRepository.ts +25 -0
  83. package/src/RouterManager.tsx +142 -1
  84. package/src/__tests__/APIClient.test.tsx +58 -0
  85. package/src/__tests__/RouteRepository.test.ts +23 -0
  86. package/src/__tests__/RouterManager.test.ts +125 -0
  87. package/src/__tests__/app.test.tsx +73 -0
  88. package/src/__tests__/authRedirect.test.ts +17 -0
  89. package/src/__tests__/browserChecker.test.ts +61 -0
  90. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  91. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +35 -2
  92. package/src/__tests__/settings-center.test.tsx +270 -6
  93. package/src/acl/ACLProvider.tsx +2 -0
  94. package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
  95. package/src/authRedirect.ts +38 -0
  96. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  97. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  98. package/src/collection-manager/field-validation.ts +1 -1
  99. package/src/components/README.md +7 -1
  100. package/src/components/README.zh-CN.md +6 -1
  101. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  102. package/src/components/category-tabs/index.ts +10 -0
  103. package/src/components/form/JsonTextArea.tsx +4 -0
  104. package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
  105. package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
  106. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
  107. package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
  108. package/src/components/form/TypedVariableInput.tsx +452 -101
  109. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  110. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  111. package/src/components/form/filter/CollectionFilterItem.tsx +36 -9
  112. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  113. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +55 -0
  114. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  115. package/src/components/form/filter/index.ts +2 -0
  116. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  117. package/src/components/form/table/Table.tsx +81 -6
  118. package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
  119. package/src/components/index.ts +1 -0
  120. package/src/entry-actions/EntryActionManager.ts +76 -0
  121. package/src/entry-actions/index.ts +10 -0
  122. package/src/flow/FlowPage.tsx +38 -3
  123. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  124. package/src/flow/__tests__/FlowRoute.test.tsx +675 -12
  125. package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
  126. package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
  127. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +62 -0
  128. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
  129. package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
  130. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
  131. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  132. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  133. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +80 -20
  134. package/src/flow/actions/dataScopeFilter.ts +10 -1
  135. package/src/flow/actions/dateTimeFormat.tsx +2 -2
  136. package/src/flow/actions/index.ts +2 -0
  137. package/src/flow/actions/linkageRules.tsx +144 -26
  138. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  139. package/src/flow/actions/openView.tsx +21 -1
  140. package/src/flow/admin-shell/BaseLayoutModel.tsx +124 -0
  141. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +184 -42
  142. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1016 -119
  143. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +41 -5
  144. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +177 -1
  145. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +20 -0
  146. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
  147. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  148. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  149. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
  150. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
  151. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +436 -2
  152. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +48 -0
  153. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  154. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  155. package/src/flow/common/Markdown/Display.tsx +14 -3
  156. package/src/flow/common/Markdown/Edit.tsx +19 -7
  157. package/src/flow/common/Markdown/style.ts +4 -0
  158. package/src/flow/components/BlockItemCard.tsx +2 -2
  159. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  160. package/src/flow/components/FieldAssignValueInput.tsx +72 -584
  161. package/src/flow/components/FlowRoute.tsx +204 -46
  162. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  163. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  164. package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
  165. package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +326 -0
  166. package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -0
  167. package/src/flow/components/field-value-variable/dateValue.ts +223 -0
  168. package/src/flow/components/field-value-variable/index.ts +12 -0
  169. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  170. package/src/flow/components/filter/VariableFilterItem.tsx +170 -13
  171. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  172. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  173. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +195 -1
  174. package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
  175. package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
  176. package/src/flow/index.ts +3 -0
  177. package/src/flow/internal/components/Markdown/util.ts +2 -1
  178. package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
  179. package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
  180. package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
  181. package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
  182. package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
  183. package/src/flow/models/base/ActionModel.tsx +10 -8
  184. package/src/flow/models/base/ActionModelCore.tsx +16 -2
  185. package/src/flow/models/base/BlockGridModel.tsx +26 -0
  186. package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
  187. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  188. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  189. package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
  190. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
  191. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  192. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  193. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
  194. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
  195. package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
  196. package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
  197. package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
  198. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
  199. package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
  200. package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
  201. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
  202. package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
  203. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +155 -25
  204. package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
  205. package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
  206. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +395 -1
  207. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
  208. package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
  209. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
  210. package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
  211. package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
  212. package/src/flow/models/blocks/form/FormBlockModel.tsx +47 -0
  213. package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
  214. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  215. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
  216. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  217. package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
  218. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
  219. package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
  220. package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
  221. package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
  222. package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
  223. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
  224. package/src/flow/models/blocks/table/TableBlockModel.tsx +29 -3
  225. package/src/flow/models/blocks/table/TableColumnModel.tsx +54 -11
  226. package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
  227. package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
  228. package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
  229. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +84 -0
  230. package/src/flow/models/blocks/table/utils.ts +7 -0
  231. package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
  232. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +10 -1
  233. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
  234. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
  235. package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +24 -1
  236. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +54 -149
  237. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +167 -3
  238. package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
  239. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
  240. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
  241. package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
  242. package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
  243. package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
  244. package/src/flow/models/fields/TextareaFieldModel.tsx +42 -19
  245. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  246. package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
  247. package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
  248. package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
  249. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +32 -1
  250. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  251. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +26 -10
  252. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +51 -14
  253. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
  254. package/src/flow/models/topbar/TopbarActionModel.tsx +78 -3
  255. package/src/flow/routeTransientInputArgs.ts +27 -0
  256. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +42 -0
  257. package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
  258. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  259. package/src/flow-compat/routeTypes.ts +1 -0
  260. package/src/index.ts +7 -0
  261. package/src/layout-manager/LayoutContentRoute.tsx +2 -1
  262. package/src/layout-manager/LayoutRoute.tsx +2 -1
  263. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
  264. package/src/locale/languageCodes.ts +3 -1
  265. package/src/nocobase-buildin-plugin/index.tsx +19 -1
  266. package/src/settings-center/AdminSettingsLayout.tsx +23 -2
  267. package/src/ui-operation/index.ts +33 -0
  268. package/src/ui-operation/ui-operation-codec.ts +54 -0
  269. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  270. package/src/utils/index.tsx +1 -0
  271. package/src/utils/markdownSanitize.ts +88 -0
@@ -285,13 +285,44 @@ function SetIsMobileLayout(props: { isMobile: boolean; children: any; model?: Ad
285
285
  const flowEngine = useFlowEngine();
286
286
  const adminLayoutModel = props.model || flowEngine.getModel<AdminLayoutModel>(ADMIN_LAYOUT_MODEL_UID);
287
287
 
288
- useEffect(() => {
288
+ useLayoutEffect(() => {
289
289
  adminLayoutModel?.setIsMobileLayout(props.isMobile);
290
290
  }, [adminLayoutModel, props.isMobile]);
291
291
 
292
292
  return props.children;
293
293
  }
294
294
 
295
+ function AdminLayoutContentWithMobileState(props: {
296
+ isMobile: boolean;
297
+ model?: AdminLayoutModel;
298
+ designable?: boolean;
299
+ layout?: AdminLayoutModel['layout'];
300
+ onContentElementChange?: (element: HTMLDivElement | null) => void;
301
+ }) {
302
+ const modelRef = useRef(props.model);
303
+ const isMobileRef = useRef(props.isMobile);
304
+ const onContentElementChangeRef = useRef(props.onContentElementChange);
305
+
306
+ modelRef.current = props.model;
307
+ isMobileRef.current = props.isMobile;
308
+ onContentElementChangeRef.current = props.onContentElementChange;
309
+
310
+ const handleContentElementChange = useCallback((element: HTMLDivElement | null) => {
311
+ if (element) {
312
+ modelRef.current?.setIsMobileLayout(isMobileRef.current);
313
+ }
314
+ onContentElementChangeRef.current?.(element);
315
+ }, []);
316
+
317
+ return (
318
+ <AdminLayoutContent
319
+ designable={props.designable}
320
+ layout={props.layout}
321
+ onContentElementChange={handleContentElementChange}
322
+ />
323
+ );
324
+ }
325
+
295
326
  const DesignerButtonMenuItem: FC<{ item: AdminLayoutMenuNode; fallbackParentRoute?: NocoBaseDesktopRoute }> = (
296
327
  props,
297
328
  ) => {
@@ -410,8 +441,8 @@ export const AdminLayoutComponent = observer((props: any) => {
410
441
  );
411
442
  const designable = !isMobileSider && preferredFlowSettingsEnabled;
412
443
  const { styles } = useHeaderStyle();
413
- const { appList } = useApplications();
414
- const appListRender = useAppListRender();
444
+ const { appList, appSwitcherModel } = useApplications(adminLayoutModel);
445
+ const appListRender = useAppListRender(appSwitcherModel);
415
446
  const flowSettingsSyncRef = useRef(0);
416
447
  const desiredFlowSettingsEnabledRef = useRef(false);
417
448
  const handleLayoutContentElementChange = useCallback(
@@ -634,6 +665,10 @@ export const AdminLayoutComponent = observer((props: any) => {
634
665
  z-index: 2000 !important;
635
666
  }
636
667
 
668
+ .ant-pro-layout-apps-popover .ant-popover-content {
669
+ margin-top: 12px;
670
+ }
671
+
637
672
  .ant-pro-layout-apps-icon {
638
673
  color: ${customToken.colorTextHeaderMenu || 'rgba(255, 255, 255, 0.85)'};
639
674
  }
@@ -677,7 +712,6 @@ export const AdminLayoutComponent = observer((props: any) => {
677
712
  <MobileMenuControlContext.Provider value={{ closeMobileMenu }}>
678
713
  <DndProvider collisionDetection={menuCollisionDetection} onDragEnd={handleMenuDragEnd}>
679
714
  <ProLayout
680
- key={`admin-layout-menu-${menuRouteRefreshVersion}`}
681
715
  {...props}
682
716
  contentStyle={contentStyle}
683
717
  siderWidth={customToken.siderWidth || 200}
@@ -717,7 +751,9 @@ export const AdminLayoutComponent = observer((props: any) => {
717
751
  <SetIsMobileLayout isMobile={isMobile} model={adminLayoutModel}>
718
752
  <ConfigProvider theme={isMobile ? mobileTheme : theme}>
719
753
  <GlobalStyle />
720
- <AdminLayoutContent
754
+ <AdminLayoutContentWithMobileState
755
+ isMobile={isMobile}
756
+ model={adminLayoutModel}
721
757
  designable={designable}
722
758
  layout={adminLayoutModel?.layout}
723
759
  onContentElementChange={handleLayoutContentElementChange}
@@ -10,8 +10,9 @@
10
10
  import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
11
11
  import { act, render, screen, waitFor } from '@testing-library/react';
12
12
  import React from 'react';
13
- import { createMemoryRouter, RouterProvider } from 'react-router-dom';
13
+ import { createMemoryRouter, Outlet, RouterProvider } from 'react-router-dom';
14
14
  import { describe, expect, it, vi } from 'vitest';
15
+ import { NocoBaseDesktopRouteType } from '../../../flow-compat';
15
16
  import { RouteRepository } from '../../../RouteRepository';
16
17
  import { AdminLayoutEntryGuard } from './AdminLayoutEntryGuard';
17
18
  import type { AdminLayoutModel } from './AdminLayoutModel';
@@ -307,4 +308,179 @@ describe('AdminLayoutEntryGuard', () => {
307
308
  expect(activateLayout).toHaveBeenCalledTimes(2);
308
309
  expect(activateLayout).toHaveBeenLastCalledWith(expect.objectContaining({ uid: 'admin-layout-next' }));
309
310
  });
311
+
312
+ it('should redirect group id paths to the first v2 landing route', async () => {
313
+ const modernWindow = window as Window & { __nocobase_modern_client_prefix__?: string };
314
+ modernWindow.__nocobase_modern_client_prefix__ = 'v2';
315
+ const engine = new FlowEngine();
316
+ const request = vi.fn().mockResolvedValue({
317
+ data: {
318
+ data: [],
319
+ },
320
+ });
321
+ const routeRepository = new RouteRepository({
322
+ api: {
323
+ request,
324
+ resource: vi.fn(),
325
+ },
326
+ } as never);
327
+ routeRepository.setRoutes([
328
+ {
329
+ id: 1,
330
+ title: 'Group',
331
+ type: NocoBaseDesktopRouteType.group,
332
+ children: [
333
+ {
334
+ id: 11,
335
+ title: 'Flow page',
336
+ schemaUid: 'flow-page-1',
337
+ type: NocoBaseDesktopRouteType.flowPage,
338
+ },
339
+ ],
340
+ },
341
+ ]);
342
+ engine.context.defineProperty('routeRepository', {
343
+ value: routeRepository,
344
+ });
345
+ engine.context.defineProperty('app', {
346
+ value: {
347
+ getPublicPath: () => '/apps/demo/v2/',
348
+ router: {
349
+ getBasename: () => '/apps/demo/v2',
350
+ },
351
+ },
352
+ });
353
+
354
+ const model = {
355
+ layout: {
356
+ routeName: 'admin',
357
+ routePath: '/admin',
358
+ uid: 'admin-layout-model',
359
+ },
360
+ } as AdminLayoutModel;
361
+ const router = createMemoryRouter(
362
+ [
363
+ {
364
+ path: '/admin',
365
+ id: 'admin',
366
+ element: (
367
+ <FlowEngineProvider engine={engine}>
368
+ <AdminLayoutEntryGuard model={model}>
369
+ <Outlet />
370
+ </AdminLayoutEntryGuard>
371
+ </FlowEngineProvider>
372
+ ),
373
+ children: [
374
+ {
375
+ path: ':name',
376
+ id: 'admin.__page',
377
+ element: <div>Admin page shell</div>,
378
+ },
379
+ ],
380
+ },
381
+ ],
382
+ {
383
+ initialEntries: ['/admin/1'],
384
+ },
385
+ );
386
+
387
+ try {
388
+ render(<RouterProvider router={router} />);
389
+
390
+ await waitFor(() => {
391
+ expect(router.state.location.pathname).toBe('/admin/flow-page-1');
392
+ });
393
+ expect(await screen.findByText('Admin page shell')).toBeInTheDocument();
394
+ } finally {
395
+ delete modernWindow.__nocobase_modern_client_prefix__;
396
+ }
397
+ });
398
+
399
+ it('should prefer schema uid routes before resolving numeric group ids', async () => {
400
+ const modernWindow = window as Window & { __nocobase_modern_client_prefix__?: string };
401
+ modernWindow.__nocobase_modern_client_prefix__ = 'v2';
402
+ const engine = new FlowEngine();
403
+ const routeRepository = {
404
+ activateLayout: vi.fn(() => vi.fn()),
405
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
406
+ isAccessibleLoaded: vi.fn(() => true),
407
+ getRouteBySchemaUid: vi.fn((pageUid: string) =>
408
+ pageUid === '1'
409
+ ? {
410
+ schemaUid: '1',
411
+ type: NocoBaseDesktopRouteType.flowPage,
412
+ }
413
+ : undefined,
414
+ ),
415
+ getRouteById: vi.fn((routeId: string) =>
416
+ routeId === '1'
417
+ ? {
418
+ id: 1,
419
+ type: NocoBaseDesktopRouteType.group,
420
+ children: [
421
+ {
422
+ schemaUid: 'flow-page-1',
423
+ type: NocoBaseDesktopRouteType.flowPage,
424
+ },
425
+ ],
426
+ }
427
+ : undefined,
428
+ ),
429
+ listAccessible: vi.fn(() => []),
430
+ };
431
+ engine.context.defineProperty('routeRepository', {
432
+ value: routeRepository,
433
+ });
434
+ engine.context.defineProperty('app', {
435
+ value: {
436
+ getPublicPath: () => '/apps/demo/v2/',
437
+ router: {
438
+ getBasename: () => '/apps/demo/v2',
439
+ },
440
+ },
441
+ });
442
+
443
+ const model = {
444
+ layout: {
445
+ routeName: 'admin',
446
+ routePath: '/admin',
447
+ uid: 'admin-layout-model',
448
+ },
449
+ } as AdminLayoutModel;
450
+ const router = createMemoryRouter(
451
+ [
452
+ {
453
+ path: '/admin',
454
+ id: 'admin',
455
+ element: (
456
+ <FlowEngineProvider engine={engine}>
457
+ <AdminLayoutEntryGuard model={model}>
458
+ <Outlet />
459
+ </AdminLayoutEntryGuard>
460
+ </FlowEngineProvider>
461
+ ),
462
+ children: [
463
+ {
464
+ path: ':name',
465
+ id: 'admin.__page',
466
+ element: <div>Admin page shell</div>,
467
+ },
468
+ ],
469
+ },
470
+ ],
471
+ {
472
+ initialEntries: ['/admin/1'],
473
+ },
474
+ );
475
+
476
+ try {
477
+ render(<RouterProvider router={router} />);
478
+
479
+ await screen.findByText('Admin page shell');
480
+ expect(router.state.location.pathname).toBe('/admin/1');
481
+ expect(routeRepository.getRouteById).not.toHaveBeenCalled();
482
+ } finally {
483
+ delete modernWindow.__nocobase_modern_client_prefix__;
484
+ }
485
+ });
310
486
  });
@@ -132,6 +132,26 @@ export const AdminLayoutEntryGuard: FC<{ children: React.ReactNode; model?: Admi
132
132
  }
133
133
  }
134
134
 
135
+ const groupRoute = currentRoute ? undefined : routeRepository?.getRouteById?.(pageUid);
136
+ if (!currentRoute && groupRoute?.type === NocoBaseDesktopRouteType.group) {
137
+ const target = resolveAdminRouteRuntimeTarget({
138
+ app,
139
+ route: groupRoute,
140
+ layout: layoutRuntime,
141
+ });
142
+
143
+ if (target.runtimePath) {
144
+ replaceTriggeredRef.current = true;
145
+ if (target.navigationMode === 'document') {
146
+ window.location.replace(target.runtimePath);
147
+ return;
148
+ }
149
+
150
+ navigate(toRouterNavigationPath(target.runtimePath, app.router.getBasename()), { replace: true });
151
+ return;
152
+ }
153
+ }
154
+
135
155
  if (active) {
136
156
  setReady(true);
137
157
  }
@@ -17,7 +17,7 @@ import {
17
17
  FlowSettingsButton,
18
18
  observer,
19
19
  } from '@nocobase/flow-engine';
20
- import { Badge, Tooltip } from 'antd';
20
+ import { Badge } from 'antd';
21
21
  import qs from 'qs';
22
22
  import React, { FC, useCallback, useContext, useEffect } from 'react';
23
23
  import { Link, useLocation, type NavigateFunction } from 'react-router-dom';
@@ -153,6 +153,24 @@ const getLayoutRoutePathFromModel = (model: FlowModel): AdminLayoutRoutePathLike
153
153
  };
154
154
 
155
155
  const menuItemStyle = { display: 'flex', alignItems: 'center', justifyContent: 'space-between' };
156
+ const siderMenuItemStyle: React.CSSProperties = { ...menuItemStyle, width: '100%', minWidth: 0 };
157
+ const siderMenuItemContentStyle: React.CSSProperties = { flex: 1, minWidth: 0, overflow: 'hidden' };
158
+ const siderMenuItemLinkStyle: React.CSSProperties = {
159
+ display: 'block',
160
+ flex: 1,
161
+ width: '100%',
162
+ minWidth: 0,
163
+ overflow: 'hidden',
164
+ };
165
+ const getMenuItemText = (name: React.ReactNode) => {
166
+ if (typeof name === 'string' || typeof name === 'number') {
167
+ return String(name);
168
+ }
169
+
170
+ if (React.isValidElement<{ title?: unknown }>(name) && typeof name.props.title === 'string') {
171
+ return name.props.title;
172
+ }
173
+ };
156
174
  const groupLandingEntryStyle = {
157
175
  display: 'inline-flex',
158
176
  alignItems: 'center',
@@ -553,16 +571,16 @@ export function resolveAdminLayoutMenuDragMoveOptionsFromEvent(
553
571
 
554
572
  const GroupItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderOptions }> = (props) => {
555
573
  const { item } = props;
574
+ const { inHeader } = useContext(HeaderContext);
556
575
  const badgeCount = useEvaluatedExpression(item._route.options?.badge?.count, item._model?.context);
557
576
  const navigate = useNavigateNoUpdate();
558
577
  const routerBasename = useRouterBasename();
559
578
  const { closeMobileMenu } = useContext(MobileMenuControlContext);
560
579
  const ariaLabel =
561
- typeof item.name === 'string' || typeof item.name === 'number'
562
- ? String(item.name)
563
- : typeof item._route?.title === 'string' || typeof item._route?.title === 'number'
564
- ? String(item._route.title)
565
- : undefined;
580
+ getMenuItemText(item.name) ??
581
+ (typeof item._route?.title === 'string' || typeof item._route?.title === 'number'
582
+ ? String(item._route.title)
583
+ : undefined);
566
584
  const runtimePath = item._runtimePath;
567
585
  const spaRuntimePath = runtimePath ? toRouterNavigationPath(runtimePath, routerBasename) : runtimePath;
568
586
 
@@ -633,7 +651,12 @@ const GroupItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRender
633
651
  return (
634
652
  <ParentRouteContext.Provider value={item._parentRoute}>
635
653
  <NocoBaseRouteContext.Provider value={item._route}>
636
- <div aria-label={ariaLabel} role="none" style={menuItemStyle}>
654
+ <div
655
+ aria-label={ariaLabel}
656
+ title={inHeader || item._route?.tooltip ? undefined : ariaLabel}
657
+ role="none"
658
+ style={inHeader ? menuItemStyle : siderMenuItemStyle}
659
+ >
637
660
  {content}
638
661
  {landingEntry}
639
662
  {badgeCount != null && (
@@ -650,24 +673,9 @@ const GroupItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRender
650
673
  );
651
674
  };
652
675
 
653
- const WithTooltip: FC<{ title: React.ReactNode; hidden: boolean; badgeProps: any }> = (props) => {
654
- const { inHeader } = useContext(HeaderContext);
655
-
656
- if (props.hidden || inHeader) {
657
- return props.children;
658
- }
659
-
660
- return (
661
- <Tooltip title={props.title} placement="right">
662
- <Badge {...props.badgeProps} style={{ transform: 'none', maxWidth: '10em' }} dot={false}>
663
- {props.children}
664
- </Badge>
665
- </Tooltip>
666
- );
667
- };
668
-
669
676
  const MenuItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderOptions }> = (props) => {
670
677
  const { item } = props;
678
+ const { inHeader } = useContext(HeaderContext);
671
679
  const location = useLocation();
672
680
  const badgeCount = useEvaluatedExpression(item._route.options?.badge?.count, item._model?.context);
673
681
  const navigate = useNavigateNoUpdate();
@@ -677,7 +685,10 @@ const MenuItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderO
677
685
  const runtimePath = item._runtimePath || path;
678
686
  const spaRuntimePath = runtimePath ? toRouterNavigationPath(runtimePath, basenameOfCurrentRouter) : runtimePath;
679
687
  const isDocumentNavigation = item._navigationMode === 'document';
680
- const badgeProps = { ...item._route.options?.badge, count: badgeCount };
688
+ const { textColor: badgeTextColor, ...antdBadgeOptions } = item._route.options?.badge || {};
689
+ const badgeProps = { ...antdBadgeOptions, count: badgeCount };
690
+ const menuItemText = getMenuItemText(item.name);
691
+ const nativeTitle = inHeader || item._route?.tooltip ? undefined : menuItemText;
681
692
 
682
693
  const canUseNativeAnchor = !!runtimePath && isDocumentNavigation;
683
694
 
@@ -767,17 +778,25 @@ const MenuItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderO
767
778
  return (
768
779
  <ParentRouteContext.Provider value={item._parentRoute}>
769
780
  <NocoBaseRouteContext.Provider value={item._route}>
770
- <div role="none" style={menuItemStyle}>
771
- <div onClick={handleClickLink}>
772
- <Link to={location.pathname} aria-label={typeof item.name === 'string' ? item.name : undefined}>
781
+ <div role="none" style={inHeader ? menuItemStyle : siderMenuItemStyle}>
782
+ <div onClick={handleClickLink} style={inHeader ? undefined : siderMenuItemContentStyle}>
783
+ <Link
784
+ to={location.pathname}
785
+ aria-label={menuItemText}
786
+ title={nativeTitle}
787
+ style={inHeader ? undefined : siderMenuItemLinkStyle}
788
+ >
773
789
  {props.children}
774
790
  </Link>
775
791
  </div>
776
792
  {badgeCount != null && (
777
793
  <Badge
778
- {...item._route.options?.badge}
779
- count={badgeCount}
780
- style={{ marginLeft: 4, color: item._route.options?.badge?.textColor, maxWidth: '10em' }}
794
+ {...badgeProps}
795
+ style={{
796
+ marginLeft: 4,
797
+ ...(badgeTextColor == null ? {} : { color: badgeTextColor }),
798
+ maxWidth: '10em',
799
+ }}
781
800
  dot={false}
782
801
  ></Badge>
783
802
  )}
@@ -787,45 +806,45 @@ const MenuItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderO
787
806
  );
788
807
  }
789
808
 
790
- const itemContent = (
791
- <WithTooltip
792
- title={item.name}
793
- hidden={
794
- item._route.type === NocoBaseDesktopRouteType.group || (item._depth || 0) > 0 || !props.options?.collapsed
795
- }
796
- badgeProps={badgeProps}
809
+ const itemContent = canUseNativeAnchor ? (
810
+ <a
811
+ href={runtimePath}
812
+ aria-label={menuItemText}
813
+ title={nativeTitle}
814
+ onClick={handleClickMenuItem}
815
+ style={inHeader ? undefined : siderMenuItemLinkStyle}
797
816
  >
798
- {canUseNativeAnchor ? (
799
- <a
800
- href={runtimePath}
801
- aria-label={typeof item.name === 'string' ? item.name : undefined}
802
- onClick={handleClickMenuItem as any}
803
- >
804
- {props.children}
805
- </a>
806
- ) : runtimePath ? (
807
- <Link
808
- to={spaRuntimePath}
809
- aria-label={typeof item.name === 'string' ? item.name : undefined}
810
- onClick={handleClickMenuItem}
811
- >
812
- {props.children}
813
- </Link>
814
- ) : (
815
- <span aria-label={typeof item.name === 'string' ? item.name : undefined}>{props.children}</span>
816
- )}
817
- </WithTooltip>
817
+ {props.children}
818
+ </a>
819
+ ) : runtimePath ? (
820
+ <Link
821
+ to={spaRuntimePath}
822
+ aria-label={menuItemText}
823
+ title={nativeTitle}
824
+ onClick={handleClickMenuItem}
825
+ style={inHeader ? undefined : siderMenuItemLinkStyle}
826
+ >
827
+ {props.children}
828
+ </Link>
829
+ ) : (
830
+ <span aria-label={menuItemText} title={nativeTitle} style={inHeader ? undefined : siderMenuItemLinkStyle}>
831
+ {props.children}
832
+ </span>
818
833
  );
819
834
 
820
835
  return (
821
836
  <ParentRouteContext.Provider value={item._parentRoute}>
822
837
  <NocoBaseRouteContext.Provider value={item._route}>
823
- <div role="none" style={menuItemStyle}>
838
+ <div role="none" style={inHeader ? menuItemStyle : siderMenuItemStyle}>
824
839
  {itemContent}
825
840
  {badgeCount != null && (
826
841
  <Badge
827
842
  {...badgeProps}
828
- style={{ marginLeft: 4, color: item._route.options?.badge?.textColor, maxWidth: '10em' }}
843
+ style={{
844
+ marginLeft: 4,
845
+ ...(badgeTextColor == null ? {} : { color: badgeTextColor }),
846
+ maxWidth: '10em',
847
+ }}
829
848
  dot={false}
830
849
  ></Badge>
831
850
  )}
@@ -10,17 +10,28 @@
10
10
  import type { AppListProps } from '@ant-design/pro-layout/es/components/AppsLogoComponents/types';
11
11
  import { css } from '@emotion/css';
12
12
  import { Card, Typography, theme } from 'antd';
13
+ import { observer } from '@nocobase/flow-engine';
13
14
  import React from 'react';
15
+ import type { AppSwitcherActionPanelModel } from './AppSwitcherActionPanelModel';
14
16
 
15
17
  function renderIcon(icon: React.ReactNode | (() => React.ReactNode)) {
16
18
  return typeof icon === 'function' ? icon() : icon;
17
19
  }
18
20
 
19
- export function useAppListRender() {
21
+ const AppSwitcherActionsContent = observer(
22
+ (props: { model: AppSwitcherActionPanelModel }) => <>{props.model.renderContent()}</>,
23
+ { displayName: 'AppSwitcherActionsContent' },
24
+ );
25
+
26
+ export function useAppListRender(appSwitcherModel?: AppSwitcherActionPanelModel) {
20
27
  const { token } = theme.useToken();
21
28
 
22
29
  return React.useCallback(
23
30
  (appList: AppListProps) => {
31
+ if (appSwitcherModel?.hasActions() || appSwitcherModel?.context.flowSettingsEnabled) {
32
+ return <AppSwitcherActionsContent model={appSwitcherModel} />;
33
+ }
34
+
24
35
  const columnCount = Math.min(Math.max(appList.length, 1), 2);
25
36
 
26
37
  return (
@@ -134,6 +145,6 @@ export function useAppListRender() {
134
145
  </div>
135
146
  );
136
147
  },
137
- [token],
148
+ [appSwitcherModel, token],
138
149
  );
139
150
  }