@nocobase/client-v2 2.1.0-alpha.30 → 2.1.0-alpha.32

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 (103) hide show
  1. package/es/components/form/JsonTextArea.d.ts +18 -0
  2. package/es/components/index.d.ts +1 -0
  3. package/es/flow/actions/dateRangeLimit.d.ts +9 -0
  4. package/es/flow/actions/index.d.ts +1 -0
  5. package/es/flow/actions/linkageRulesFormValueRefresh.d.ts +10 -0
  6. package/es/flow/index.d.ts +1 -0
  7. package/es/flow/models/actions/AssociateActionModel.d.ts +19 -0
  8. package/es/flow/models/actions/AssociationActionUtils.d.ts +17 -0
  9. package/es/flow/models/actions/DisassociateActionModel.d.ts +16 -0
  10. package/es/flow/models/actions/index.d.ts +3 -0
  11. package/es/flow/models/base/GridModel.d.ts +3 -1
  12. package/es/flow/models/base/PageModel/PageModel.d.ts +4 -0
  13. package/es/flow/models/base/PageModel/RootPageModel.d.ts +9 -0
  14. package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +15 -6
  15. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +7 -0
  16. package/es/flow/models/blocks/shared/filterOperators.d.ts +9 -0
  17. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +3 -0
  18. package/es/flow/models/fields/AssociationFieldModel/recordSelectSettingsUtils.d.ts +9 -0
  19. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +20 -0
  20. package/es/flow/models/fields/JSEditableFieldModel.d.ts +4 -0
  21. package/es/flow-compat/data.d.ts +9 -2
  22. package/es/flow-compat/index.d.ts +1 -1
  23. package/es/index.d.ts +1 -0
  24. package/es/index.mjs +100 -93
  25. package/lib/index.js +101 -94
  26. package/package.json +6 -5
  27. package/src/BaseApplication.tsx +1 -1
  28. package/src/__tests__/app.test.tsx +23 -6
  29. package/src/__tests__/globalDeps.test.ts +5 -0
  30. package/src/components/form/JsonTextArea.tsx +129 -0
  31. package/src/components/index.ts +1 -0
  32. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +478 -0
  33. package/src/flow/actions/__tests__/linkageRules.formValueDrivenRefresh.test.ts +438 -0
  34. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +42 -0
  35. package/src/flow/actions/__tests__/pattern.test.ts +190 -0
  36. package/src/flow/actions/dateRangeLimit.tsx +66 -0
  37. package/src/flow/actions/index.ts +1 -0
  38. package/src/flow/actions/linkageRules.tsx +119 -14
  39. package/src/flow/actions/linkageRulesFormValueRefresh.ts +492 -0
  40. package/src/flow/actions/linkageRulesRefresh.tsx +4 -2
  41. package/src/flow/actions/openView.tsx +2 -1
  42. package/src/flow/actions/pattern.tsx +25 -2
  43. package/src/flow/actions/titleField.tsx +8 -3
  44. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +7 -1
  45. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +117 -0
  46. package/src/flow/components/FieldAssignValueInput.tsx +1 -0
  47. package/src/flow/components/filter/LinkageFilterItem.tsx +6 -5
  48. package/src/flow/components/filter/VariableFilterItem.tsx +14 -13
  49. package/src/flow/components/filter/__tests__/LinkageFilterItem.test.tsx +33 -0
  50. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +48 -5
  51. package/src/flow/index.ts +1 -0
  52. package/src/flow/internal/utils/__tests__/titleFieldQuickSync.test.ts +1 -0
  53. package/src/flow/internal/utils/titleFieldQuickSync.ts +2 -2
  54. package/src/flow/models/actions/AssociateActionModel.tsx +196 -0
  55. package/src/flow/models/actions/AssociationActionUtils.ts +90 -0
  56. package/src/flow/models/actions/DisassociateActionModel.tsx +57 -0
  57. package/src/flow/models/actions/FilterActionModel.tsx +17 -9
  58. package/src/flow/models/actions/__tests__/AssociationActionModel.test.ts +250 -0
  59. package/src/flow/models/actions/index.ts +3 -0
  60. package/src/flow/models/base/GridModel.tsx +21 -1
  61. package/src/flow/models/base/PageModel/PageModel.tsx +15 -3
  62. package/src/flow/models/base/PageModel/RootPageModel.tsx +37 -2
  63. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +73 -0
  64. package/src/flow/models/base/PageModel/__tests__/RootPageModel.test.ts +116 -0
  65. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +98 -0
  66. package/src/flow/models/blocks/details/DetailsItemModel.tsx +3 -0
  67. package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +200 -36
  68. package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +270 -1
  69. package/src/flow/models/blocks/filter-form/__tests__/customFieldOperators.test.tsx +23 -0
  70. package/src/flow/models/blocks/filter-form/customFieldOperators.ts +12 -1
  71. package/src/flow/models/blocks/filter-form/fields/FieldComponentProps.tsx +22 -8
  72. package/src/flow/models/blocks/filter-form/fields/__tests__/FilterFormCustomFieldModel.recordSelect.test.tsx +18 -0
  73. package/src/flow/models/blocks/filter-manager/FilterManager.ts +51 -1
  74. package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +75 -0
  75. package/src/flow/models/blocks/form/FormItemModel.tsx +48 -28
  76. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +167 -1
  77. package/src/flow/models/blocks/form/value-runtime/runtime.ts +103 -11
  78. package/src/flow/models/blocks/shared/filterOperators.ts +14 -0
  79. package/src/flow/models/blocks/table/TableBlockModel.tsx +19 -3
  80. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +5 -1
  81. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +48 -8
  82. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +47 -0
  83. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +42 -0
  84. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableField.refresh.test.tsx +122 -0
  85. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +2 -0
  86. package/src/flow/models/fields/AssociationFieldModel/recordSelectSettingsUtils.ts +20 -0
  87. package/src/flow/models/fields/ClickableFieldModel.tsx +21 -9
  88. package/src/flow/models/fields/DateTimeFieldModel/DateOnlyFieldModel.tsx +9 -0
  89. package/src/flow/models/fields/DateTimeFieldModel/DateTimeFieldModel.tsx +4 -0
  90. package/src/flow/models/fields/DateTimeFieldModel/DateTimeNoTzFieldModel.tsx +9 -0
  91. package/src/flow/models/fields/DateTimeFieldModel/DateTimeTzFieldModel.tsx +9 -0
  92. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +242 -0
  93. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +152 -0
  94. package/src/flow/models/fields/DividerItemModel.tsx +30 -15
  95. package/src/flow/models/fields/JSEditableFieldModel.tsx +110 -14
  96. package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +87 -0
  97. package/src/flow/models/fields/__tests__/JSEditableFieldModel.test.tsx +210 -0
  98. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +11 -3
  99. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +235 -0
  100. package/src/flow-compat/data.ts +25 -3
  101. package/src/flow-compat/index.ts +7 -1
  102. package/src/index.ts +1 -0
  103. package/src/utils/globalDeps.ts +6 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { defineAction, tExpr } from '@nocobase/flow-engine';
11
+ import { FieldAssignValueInput } from '../components/FieldAssignValueInput';
12
+
13
+ function normalizeDateRangeValue(value: any) {
14
+ if (value === '' || value === null || typeof value === 'undefined') {
15
+ return undefined;
16
+ }
17
+ return value;
18
+ }
19
+
20
+ export const dateRangeLimit = defineAction({
21
+ name: 'dateRangeLimit',
22
+ title: tExpr('Date range limit'),
23
+ uiMode: {
24
+ type: 'dialog',
25
+ props: {
26
+ width: 720,
27
+ },
28
+ },
29
+ uiSchema(ctx: any) {
30
+ const targetPath = ctx.model.context?.collectionField?.name || '';
31
+ const dateLimitInputProps = {
32
+ targetPath,
33
+ enableDateVariableAsConstant: true,
34
+ };
35
+
36
+ return {
37
+ _minDate: {
38
+ type: 'string',
39
+ title: tExpr('MinDate'),
40
+ 'x-decorator': 'FormItem',
41
+ 'x-component': FieldAssignValueInput,
42
+ 'x-component-props': dateLimitInputProps,
43
+ },
44
+ _maxDate: {
45
+ type: 'string',
46
+ title: tExpr('MaxDate'),
47
+ 'x-decorator': 'FormItem',
48
+ 'x-component': FieldAssignValueInput,
49
+ 'x-component-props': dateLimitInputProps,
50
+ },
51
+ };
52
+ },
53
+ defaultParams(ctx: any) {
54
+ return {
55
+ _minDate: normalizeDateRangeValue(ctx.model.props?._minDate),
56
+ _maxDate: normalizeDateRangeValue(ctx.model.props?._maxDate),
57
+ };
58
+ },
59
+ useRawParams: true,
60
+ async handler(ctx: any, params) {
61
+ ctx.model.setProps({
62
+ _minDate: normalizeDateRangeValue(params?._minDate),
63
+ _maxDate: normalizeDateRangeValue(params?._maxDate),
64
+ });
65
+ },
66
+ });
@@ -20,6 +20,7 @@ export * from './refreshTargetBlocks';
20
20
  export * from './setTargetDataScope';
21
21
  export { titleField } from './titleField';
22
22
  export * from './dateTimeFormat';
23
+ export * from './dateRangeLimit';
23
24
  export * from './sortingRules';
24
25
  export * from './dataLoadingMode';
25
26
  export * from './renderMode';
@@ -52,6 +52,7 @@ import {
52
52
  isToManyAssociationField,
53
53
  } from '../internal/utils/modelUtils';
54
54
  import { namePathToPathKey, parsePathString, resolveDynamicNamePath } from '../models/blocks/form/value-runtime/path';
55
+ import { ensureFormValueDrivenLinkageRefresh } from './linkageRulesFormValueRefresh';
55
56
 
56
57
  interface LinkageRule {
57
58
  /** 随机生成的字符串 */
@@ -1754,7 +1755,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1754
1755
 
1755
1756
  const linkageRules: LinkageRule[] = params.value as LinkageRule[];
1756
1757
  const allModels: FlowModel[] = ctx.model.__allModels || (ctx.model.__allModels = []);
1757
- const directValuePatches: Array<{ path: any; value: any }> = [];
1758
+ const directValuePatches: Array<{ path: Array<string | number>; value: any; whenEmpty?: boolean }> = [];
1758
1759
  const rootCollection = getCollectionFromModel((ctx.model as any)?.context?.blockModel ?? ctx.model);
1759
1760
  const isSafeToWriteAssociationSubpath = (namePath: any): boolean => {
1760
1761
  if (!Array.isArray(namePath) || !namePath.length) return true;
@@ -1795,6 +1796,24 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1795
1796
  const fieldIndex = (ctx.model as any)?.context?.fieldIndex;
1796
1797
  return resolveDynamicNamePath(path, fieldIndex);
1797
1798
  };
1799
+ const getDefaultPatchRuntime = () => {
1800
+ const blockModel = (ctx.model as any)?.context?.blockModel ?? ctx.model;
1801
+ return blockModel?.formValueRuntime ?? (ctx as any)?.formValueRuntime;
1802
+ };
1803
+ const rememberAppliedDefaultPatches = (patches: typeof directValuePatches) => {
1804
+ const runtime = getDefaultPatchRuntime();
1805
+ if (typeof runtime?.recordDefaultValuePatch !== 'function') return;
1806
+
1807
+ const lastPatchByPathKey = new Map<string, (typeof directValuePatches)[number]>();
1808
+ for (const patch of patches) {
1809
+ lastPatchByPathKey.set(namePathToPathKey(patch.path), patch);
1810
+ }
1811
+
1812
+ for (const patch of lastPatchByPathKey.values()) {
1813
+ if (!patch.whenEmpty) continue;
1814
+ runtime.recordDefaultValuePatch(patch.path, patch.value);
1815
+ }
1816
+ };
1798
1817
  const addFormValuePatch = (patch: { path: any; value: any; whenEmpty?: boolean }) => {
1799
1818
  if (!patch) return;
1800
1819
  const path = (patch as any)?.path;
@@ -1818,20 +1837,33 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1818
1837
  return;
1819
1838
  }
1820
1839
  const whenEmpty = !!(patch as any)?.whenEmpty;
1840
+ const value = (patch as any)?.value;
1821
1841
  try {
1822
1842
  const form = ctx.model?.context?.form;
1823
1843
  const current = form?.getFieldValue?.(resolvedPath);
1824
- if (whenEmpty && typeof current !== 'undefined' && current !== null && current !== '') {
1825
- return;
1844
+ if (whenEmpty) {
1845
+ const runtime = getDefaultPatchRuntime();
1846
+ if (typeof runtime?.canApplyDefaultValuePatch === 'function') {
1847
+ const canApply = runtime.canApplyDefaultValuePatch(resolvedPath, value);
1848
+ if (!canApply) {
1849
+ return;
1850
+ }
1851
+ } else if (typeof current !== 'undefined' && current !== null && current !== '') {
1852
+ return;
1853
+ }
1826
1854
  }
1827
- if (_.isEqual(current, (patch as any)?.value)) {
1855
+ if (_.isEqual(current, value)) {
1828
1856
  return;
1829
1857
  }
1830
1858
  } catch {
1831
1859
  // ignore
1832
1860
  }
1833
1861
 
1834
- directValuePatches.push({ path: resolvedPath, value: (patch as any)?.value });
1862
+ directValuePatches.push({
1863
+ path: resolvedPath,
1864
+ value,
1865
+ ...(whenEmpty ? { whenEmpty: true } : {}),
1866
+ });
1835
1867
  };
1836
1868
 
1837
1869
  const getModelTargetPathForPatch = (model: any): string | null => {
@@ -2013,6 +2045,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2013
2045
  if (typeof directSetter === 'function') {
2014
2046
  try {
2015
2047
  await trySetFormValues(directSetter, directCtx, 'linkage');
2048
+ rememberAppliedDefaultPatches(allPatches);
2016
2049
  return;
2017
2050
  } catch (error) {
2018
2051
  console.warn('[linkageRules] Failed to set form values via setFormValues', {
@@ -2029,6 +2062,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2029
2062
  if (typeof blockSetter === 'function') {
2030
2063
  try {
2031
2064
  await trySetFormValues(blockSetter, blockCtx, 'linkage');
2065
+ rememberAppliedDefaultPatches(allPatches);
2032
2066
  return;
2033
2067
  } catch (error) {
2034
2068
  console.warn('[linkageRules] Failed to set form values via setFormValues', {
@@ -2058,6 +2092,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2058
2092
  console.warn('[linkageRules] Failed to set form field value (fallback setFieldValue)', { path }, error);
2059
2093
  }
2060
2094
  });
2095
+ rememberAppliedDefaultPatches(allPatches);
2061
2096
  };
2062
2097
 
2063
2098
  export const blockLinkageRules = defineAction({
@@ -2081,6 +2116,7 @@ export const blockLinkageRules = defineAction({
2081
2116
  },
2082
2117
  useRawParams: true,
2083
2118
  handler: async (ctx, params) => {
2119
+ ensureFormValueDrivenLinkageRefresh(ctx, params, 'blockLinkageRules');
2084
2120
  const resolved = await resolveLinkageRulesParamsPreservingRunJsScripts(ctx, params);
2085
2121
  return commonLinkageRulesHandler(ctx, resolved);
2086
2122
  },
@@ -2107,6 +2143,7 @@ export const actionLinkageRules = defineAction({
2107
2143
  },
2108
2144
  useRawParams: true,
2109
2145
  handler: async (ctx, params) => {
2146
+ ensureFormValueDrivenLinkageRefresh(ctx, params, 'actionLinkageRules');
2110
2147
  const resolved = await resolveLinkageRulesParamsPreservingRunJsScripts(ctx, params);
2111
2148
  return commonLinkageRulesHandler(ctx, resolved);
2112
2149
  },
@@ -2341,33 +2378,95 @@ export const fieldLinkageRules = defineAction({
2341
2378
  return;
2342
2379
  }
2343
2380
 
2344
- const getRowScopeKeyFromModel = (model: any): string | null => {
2381
+ const getRowFieldIndexInfoFromModel = (model: any) => {
2345
2382
  const fieldIndex = model?.context?.fieldIndex;
2346
2383
  const arr = Array.isArray(fieldIndex) ? fieldIndex : [];
2347
- if (!arr.length) return null;
2384
+ const normalized = arr.filter((it): it is string => typeof it === 'string');
2348
2385
  const entries: Array<{ name: string; index: number }> = [];
2349
- for (const it of arr) {
2350
- if (typeof it !== 'string') continue;
2386
+ const path: Array<string | number> = [];
2387
+ for (const it of normalized) {
2351
2388
  const [name, indexStr] = it.split(':');
2352
2389
  const index = Number(indexStr);
2353
2390
  if (!name || Number.isNaN(index)) continue;
2354
2391
  entries.push({ name, index });
2392
+ path.push(name, index);
2355
2393
  }
2394
+ return { normalized, entries, path };
2395
+ };
2396
+
2397
+ const getRowScopeKeyFromModel = (model: any): string | null => {
2398
+ const { entries } = getRowFieldIndexInfoFromModel(model);
2356
2399
  if (!entries.length) return null;
2357
2400
  const deepest = entries[entries.length - 1].name;
2358
2401
  const occurrence = entries.reduce((count, e) => (e.name === deepest ? count + 1 : count), 0);
2359
2402
  return `${deepest}#${occurrence}`;
2360
2403
  };
2361
2404
 
2405
+ const getRowFieldIndexKeyFromModel = (model: any): string | null => {
2406
+ const { normalized } = getRowFieldIndexInfoFromModel(model);
2407
+ if (!normalized.length) return null;
2408
+ return JSON.stringify(normalized);
2409
+ };
2410
+
2411
+ const getRowPathFromModel = (model: any): Array<string | number> | null => {
2412
+ const { path } = getRowFieldIndexInfoFromModel(model);
2413
+ return path.length ? path : null;
2414
+ };
2415
+
2416
+ const getFormForRowFork = (model: any) => {
2417
+ return (
2418
+ model?.context?.form ??
2419
+ model?.context?.blockModel?.context?.form ??
2420
+ ctx.model?.context?.form ??
2421
+ ctx.model?.context?.blockModel?.context?.form
2422
+ );
2423
+ };
2424
+
2425
+ const isRowForkMountedInCurrentValue = (model: any): boolean => {
2426
+ const rowPath = getRowPathFromModel(model);
2427
+ if (!rowPath) return true;
2428
+ const form = getFormForRowFork(model);
2429
+ if (!form || typeof form.getFieldValue !== 'function') return true;
2430
+ return typeof form.getFieldValue(rowPath as any) !== 'undefined';
2431
+ };
2432
+
2433
+ const hasRowItemContext = (model: any): boolean => {
2434
+ const itemOptions = model?.context?.getPropertyOptions?.('item');
2435
+ if (itemOptions) return true;
2436
+ return typeof model?.context?.item !== 'undefined';
2437
+ };
2438
+
2439
+ const hasSubTableRowMarker = (model: any): boolean => {
2440
+ const markerOptions = model?.context?.getPropertyOptions?.('subTableRowFork');
2441
+ if (markerOptions) return true;
2442
+ return (
2443
+ typeof model?.context?.subTableRowFork !== 'undefined' ||
2444
+ typeof model?.subTableRowFork !== 'undefined' ||
2445
+ model?.subTableRowFork === true
2446
+ );
2447
+ };
2448
+
2362
2449
  const isRowGridForkModel = (model: any): boolean => {
2363
2450
  if (!model || typeof model !== 'object') return false;
2364
2451
  if ((model as any)?.subModels?.field) return false;
2365
2452
  if (!(model as any)?.subModels?.items) return false;
2366
- return !!getRowScopeKeyFromModel(model);
2453
+ return !!getRowScopeKeyFromModel(model) && !!getRowFieldIndexKeyFromModel(model);
2454
+ };
2455
+
2456
+ const isSubTableRowForkModel = (model: any): boolean => {
2457
+ if (!model || typeof model !== 'object') return false;
2458
+ if (!hasSubTableRowMarker(model)) return false;
2459
+ if (!getRowScopeKeyFromModel(model) || !getRowFieldIndexKeyFromModel(model)) return false;
2460
+ return hasRowItemContext(model);
2461
+ };
2462
+
2463
+ const isRowScopedForkModel = (model: any): boolean => {
2464
+ return isRowGridForkModel(model) || isSubTableRowForkModel(model);
2367
2465
  };
2368
2466
 
2369
- const collectRowGridForksByKey = (): Map<string, FlowModel[]> => {
2467
+ const collectRowScopedForksByKey = (): Map<string, FlowModel[]> => {
2370
2468
  const out = new Map<string, FlowModel[]>();
2469
+ const seenByKey = new Map<string, Set<string>>();
2371
2470
  const engine = ctx.engine;
2372
2471
  if (!engine?.forEachModel) return out;
2373
2472
 
@@ -2376,9 +2475,15 @@ export const fieldLinkageRules = defineAction({
2376
2475
  if (!forks || typeof forks.forEach !== 'function') return;
2377
2476
  forks.forEach((fork: any) => {
2378
2477
  if (!fork || fork.disposed) return;
2379
- if (!isRowGridForkModel(fork)) return;
2478
+ if (!isRowScopedForkModel(fork)) return;
2479
+ if (!isRowForkMountedInCurrentValue(fork)) return;
2380
2480
  const rowScopeKey = getRowScopeKeyFromModel(fork);
2381
- if (!rowScopeKey) return;
2481
+ const fieldIndexKey = getRowFieldIndexKeyFromModel(fork);
2482
+ if (!rowScopeKey || !fieldIndexKey) return;
2483
+ const seen = seenByKey.get(rowScopeKey) || new Set<string>();
2484
+ if (seen.has(fieldIndexKey)) return;
2485
+ seen.add(fieldIndexKey);
2486
+ seenByKey.set(rowScopeKey, seen);
2382
2487
  const arr = out.get(rowScopeKey) || [];
2383
2488
  arr.push(fork as FlowModel);
2384
2489
  out.set(rowScopeKey, arr);
@@ -2389,7 +2494,7 @@ export const fieldLinkageRules = defineAction({
2389
2494
  };
2390
2495
 
2391
2496
  const runRowScoped = async (): Promise<boolean> => {
2392
- const forksByKey = collectRowGridForksByKey();
2497
+ const forksByKey = collectRowScopedForksByKey();
2393
2498
  let hasAnyRowFork = false;
2394
2499
  for (const [rowScopeKey, rowParams] of rowParamsByKey.entries()) {
2395
2500
  const forks = forksByKey.get(rowScopeKey) || [];