@nocobase/client-v2 2.1.35 → 2.1.37

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 (28) hide show
  1. package/es/flow/actions/linkageRules.d.ts +24 -0
  2. package/es/flow/components/FieldAssignValueInput.d.ts +2 -23
  3. package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +23 -0
  4. package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +28 -0
  5. package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
  6. package/es/flow/components/field-value-variable/index.d.ts +11 -0
  7. package/es/index.mjs +64 -73
  8. package/lib/index.js +92 -101
  9. package/package.json +7 -7
  10. package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
  11. package/src/flow/actions/linkageRules.tsx +25 -7
  12. package/src/flow/components/FieldAssignValueInput.tsx +34 -571
  13. package/src/flow/components/field-value-variable/DateVariableEditor.tsx +162 -0
  14. package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +306 -0
  15. package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -0
  16. package/src/flow/components/field-value-variable/dateValue.ts +223 -0
  17. package/src/flow/components/field-value-variable/index.ts +12 -0
  18. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
  19. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
  20. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +398 -0
  21. package/src/flow/models/blocks/form/value-runtime/rules.ts +39 -9
  22. package/src/flow/models/blocks/form/value-runtime/runtime.ts +39 -19
  23. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +8 -0
  24. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
  25. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
  26. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +46 -0
  27. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +1 -0
  28. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +20 -2
@@ -8,19 +8,9 @@
8
8
  */
9
9
 
10
10
  import React from 'react';
11
- import { css } from '@emotion/css';
12
- import { Divider, Input, InputNumber, Select, Space, theme } from 'antd';
13
- import { dayjs } from '@nocobase/utils/client';
11
+ import { Input } from 'antd';
14
12
  import {
15
13
  FlowModelRenderer,
16
- VariableInput,
17
- tExpr,
18
- isVariableExpression,
19
- parseValueToPath,
20
- isRunJSValue,
21
- isCtxDateExpression,
22
- parseCtxDateExpression,
23
- serializeCtxDateValue,
24
14
  type CollectionField,
25
15
  type MetaTreeNode,
26
16
  useFlowContext,
@@ -39,283 +29,21 @@ import { RunJSValueEditor } from './RunJSValueEditor';
39
29
  import { pickOperatorStyle as pickStyle, resolveOperatorComponent } from '../internal/utils/operatorSchemaHelper';
40
30
  import { InputFieldModel } from '../models/fields/InputFieldModel';
41
31
  import { normalizeFilterValueByOperator } from '../models/blocks/filter-form/valueNormalization';
42
- import { FieldAssignExactDatePicker, type ExactDatePickerMode } from './FieldAssignExactDatePicker';
43
32
  import { limitAssociationMetaTree } from './filter/metaTreeAssociationDepth';
44
-
45
- const DATE_FIELD_INTERFACES = new Set(['date', 'datetime', 'datetimeNoTz', 'createdAt', 'updatedAt', 'unixTimestamp']);
46
-
47
- const TZ_AWARE_DATE_INTERFACES = new Set(['datetime', 'createdAt', 'updatedAt', 'unixTimestamp']);
48
-
49
- const DATE_ONLY_OUTPUT_FORMAT = 'YYYY-MM-DD';
50
- const DATETIME_NO_TZ_OUTPUT_FORMAT = 'YYYY-MM-DD HH:mm:ss';
51
-
52
- export type DateVariableExactNormalizeMode = 'none' | 'date' | 'datetimeNoTz' | 'iso';
53
-
54
- const DATE_DYNAMIC_OPTION_KEYS = [
55
- 'exact',
56
- 'past',
57
- 'next',
58
- 'today',
59
- 'yesterday',
60
- 'tomorrow',
61
- 'thisWeek',
62
- 'lastWeek',
63
- 'nextWeek',
64
- 'thisMonth',
65
- 'lastMonth',
66
- 'nextMonth',
67
- 'thisQuarter',
68
- 'lastQuarter',
69
- 'nextQuarter',
70
- 'thisYear',
71
- 'lastYear',
72
- 'nextYear',
73
- ] as const;
74
-
75
- type DateDynamicOptionValue = (typeof DATE_DYNAMIC_OPTION_KEYS)[number] | 'now';
76
-
77
- const DATE_DYNAMIC_OPTION_LABELS: Record<(typeof DATE_DYNAMIC_OPTION_KEYS)[number], string> = {
78
- exact: 'Exact day',
79
- past: 'Past',
80
- next: 'Next',
81
- today: 'Today',
82
- yesterday: 'Yesterday',
83
- tomorrow: 'Tomorrow',
84
- thisWeek: 'This Week',
85
- lastWeek: 'Last Week',
86
- nextWeek: 'Next Week',
87
- thisMonth: 'This Month',
88
- lastMonth: 'Last Month',
89
- nextMonth: 'Next Month',
90
- thisQuarter: 'This Quarter',
91
- lastQuarter: 'Last Quarter',
92
- nextQuarter: 'Next Quarter',
93
- thisYear: 'This Year',
94
- lastYear: 'Last Year',
95
- nextYear: 'Next Year',
96
- };
97
-
98
- function buildDateDynamicOptions(t?: (key: string) => string, includeNow = false) {
99
- const options: Array<{ value: DateDynamicOptionValue; label: string }> = DATE_DYNAMIC_OPTION_KEYS.map((key) => ({
100
- value: key,
101
- label: t?.(DATE_DYNAMIC_OPTION_LABELS[key]) ?? DATE_DYNAMIC_OPTION_LABELS[key],
102
- }));
103
-
104
- if (includeNow) {
105
- options.splice(3, 0, { value: 'now', label: t?.('Now') ?? 'Now' });
106
- }
107
-
108
- return options;
109
- }
110
-
111
- function parseDateByFormat(value: string, format: string): dayjs.Dayjs | null {
112
- const raw = String(value || '').trim();
113
- if (!raw) return null;
114
-
115
- const hasTimezone = /(?:Z|[+-]\d{2}:\d{2})$/i.test(raw);
116
- if (hasTimezone) {
117
- const parsed = dayjs(raw);
118
- if (parsed.isValid()) {
119
- return parsed;
120
- }
121
- }
122
-
123
- if (format) {
124
- const strict = dayjs(raw, format, true);
125
- if (strict.isValid()) {
126
- return strict;
127
- }
128
- }
129
-
130
- const fallback = dayjs(raw);
131
- if (fallback.isValid()) {
132
- return fallback;
133
- }
134
-
135
- if (format) {
136
- const loose = dayjs(raw, format);
137
- if (loose.isValid()) {
138
- return loose;
139
- }
140
- }
141
-
142
- return null;
143
- }
144
-
145
- function parseDateFromRawValue(value: unknown, format: string): dayjs.Dayjs | null {
146
- if (dayjs.isDayjs(value)) {
147
- return value;
148
- }
149
-
150
- if (value instanceof Date) {
151
- const parsedDate = dayjs(value);
152
- return parsedDate.isValid() ? parsedDate : null;
153
- }
154
-
155
- if (typeof value === 'string') {
156
- return parseDateByFormat(value, format);
157
- }
158
-
159
- return null;
160
- }
161
-
162
- function normalizeExactDateValue(
163
- value: unknown,
164
- options: {
165
- format: string;
166
- showTime: boolean;
167
- exactNormalizeMode: DateVariableExactNormalizeMode;
168
- },
169
- ): unknown {
170
- const parsed = parseDateFromRawValue(value, options.format);
171
- if (!parsed?.isValid()) {
172
- return value;
173
- }
174
-
175
- switch (options.exactNormalizeMode) {
176
- case 'date':
177
- return parsed.format(DATE_ONLY_OUTPUT_FORMAT);
178
- case 'datetimeNoTz':
179
- return parsed.format(options.showTime ? DATETIME_NO_TZ_OUTPUT_FORMAT : DATE_ONLY_OUTPUT_FORMAT);
180
- case 'iso':
181
- return parsed.toISOString();
182
- default:
183
- return value;
184
- }
185
- }
186
-
187
- function toExactPickerSingleValue(rawValue: unknown, format: string): dayjs.Dayjs | null {
188
- const parsed = parseDateFromRawValue(rawValue, format);
189
- return parsed?.isValid() ? parsed : null;
190
- }
191
-
192
- function toExactPickerRangeValue(rawValue: unknown, format: string): [dayjs.Dayjs, dayjs.Dayjs] | null {
193
- if (!Array.isArray(rawValue)) return null;
194
- const left = toExactPickerSingleValue(rawValue[0], format);
195
- const right = toExactPickerSingleValue(rawValue[1], format);
196
- if (!left || !right) return null;
197
- return [left, right];
198
- }
199
-
200
- export function toExactPickerDisplayValue(
201
- rawValue: unknown,
202
- options: {
203
- format: string;
204
- isRange: boolean;
205
- },
206
- ): dayjs.Dayjs | [dayjs.Dayjs, dayjs.Dayjs] | null {
207
- if (options.isRange) {
208
- return toExactPickerRangeValue(rawValue, options.format);
209
- }
210
- return toExactPickerSingleValue(rawValue, options.format);
211
- }
212
-
213
- function getDateVariableExactNormalizeMode(fieldInterface: string): DateVariableExactNormalizeMode {
214
- if (fieldInterface === 'date') {
215
- return 'date';
216
- }
217
-
218
- if (fieldInterface === 'datetimeNoTz') {
219
- return 'datetimeNoTz';
220
- }
221
-
222
- if (TZ_AWARE_DATE_INTERFACES.has(fieldInterface)) {
223
- return 'iso';
224
- }
225
-
226
- return 'none';
227
- }
228
-
229
- export function normalizeDateVariableExactValue(
230
- rawValue: any,
231
- options: {
232
- exactNormalizeMode: DateVariableExactNormalizeMode;
233
- format: string;
234
- showTime: boolean;
235
- },
236
- ): any {
237
- if (options.exactNormalizeMode === 'none') {
238
- return rawValue;
239
- }
240
-
241
- if (typeof rawValue === 'string') {
242
- return normalizeExactDateValue(rawValue, options);
243
- }
244
-
245
- if (dayjs.isDayjs(rawValue) || rawValue instanceof Date) {
246
- return normalizeExactDateValue(rawValue, options);
247
- }
248
-
249
- if (Array.isArray(rawValue)) {
250
- return rawValue.map((item) => {
251
- if (typeof item === 'string' || dayjs.isDayjs(item) || item instanceof Date) {
252
- return normalizeExactDateValue(item, options);
253
- }
254
- return item;
255
- });
256
- }
257
-
258
- return rawValue;
259
- }
260
-
261
- type DateVariableComponentProps = {
262
- picker: ExactDatePickerMode;
263
- showTime: boolean;
264
- timeFormat: string;
265
- format: string;
266
- exactNormalizeMode: DateVariableExactNormalizeMode;
267
- };
268
-
269
- function normalizeExactDatePickerMode(value: unknown): ExactDatePickerMode {
270
- if (value === 'year' || value === 'quarter' || value === 'month' || value === 'date') {
271
- return value;
272
- }
273
-
274
- return 'date';
275
- }
276
-
277
- const DEFAULT_DATE_VARIABLE_COMPONENT_PROPS: DateVariableComponentProps = {
278
- picker: 'date',
279
- showTime: false,
280
- timeFormat: 'HH:mm:ss',
281
- format: 'YYYY-MM-DD',
282
- exactNormalizeMode: 'none',
283
- };
284
-
285
- function getFieldInterface(field: any): string {
286
- return typeof field?.interface === 'string' ? field.interface : '';
287
- }
288
-
289
- function getFieldComponentProps(field: any): Record<string, any> {
290
- return (
291
- (typeof field?.getComponentProps === 'function' ? field.getComponentProps() : null) ||
292
- field?.uiSchema?.['x-component-props'] ||
293
- {}
294
- );
295
- }
296
-
297
- export function normalizeDateVariableOutput(rawValue: any, options: DateVariableComponentProps): any {
298
- if (rawValue === null || isRunJSValue(rawValue)) {
299
- return rawValue;
300
- }
301
-
302
- if (typeof rawValue === 'string' && isVariableExpression(rawValue) && !isCtxDateExpression(rawValue)) {
303
- return rawValue;
304
- }
305
-
306
- if (rawValue === '' || typeof rawValue === 'undefined') {
307
- return '';
308
- }
309
-
310
- const normalized = normalizeDateVariableExactValue(rawValue, {
311
- exactNormalizeMode: options.exactNormalizeMode,
312
- format: options.format || 'YYYY-MM-DD HH:mm:ss',
313
- showTime: options.showTime,
314
- });
315
-
316
- const serialized = serializeCtxDateValue(normalized);
317
- return serialized || normalized;
318
- }
33
+ import {
34
+ DEFAULT_DATE_VARIABLE_COMPONENT_PROPS,
35
+ FieldValueVariableInput,
36
+ getFieldInterface,
37
+ isDateLikeField as isDateLikeCollectionField,
38
+ resolveDateVariableComponentProps,
39
+ } from './field-value-variable';
40
+
41
+ export {
42
+ normalizeDateVariableExactValue,
43
+ normalizeDateVariableOutput,
44
+ toExactPickerDisplayValue,
45
+ type DateVariableExactNormalizeMode,
46
+ } from './field-value-variable';
319
47
 
320
48
  interface Props {
321
49
  /** 赋值目标路径,例如 `title` / `users.nickname` / `user.name` */
@@ -337,8 +65,7 @@ interface Props {
337
65
  value?: string;
338
66
  };
339
67
  /**
340
- * 在日期字段场景下,用日期变量编辑器替换 Constant 位。
341
- * 默认 false,保持历史行为。
68
+ * @deprecated Date 已作为独立一级变量提供,此参数仅为调用兼容保留。
342
69
  */
343
70
  enableDateVariableAsConstant?: boolean;
344
71
  maxAssociationFieldDepth?: number;
@@ -711,7 +438,6 @@ export const FieldAssignValueInput: React.FC<Props> = ({
711
438
  operatorMetaList,
712
439
  preferFormItemFieldModel,
713
440
  associationFieldNamesOverride,
714
- enableDateVariableAsConstant = false,
715
441
  maxAssociationFieldDepth = 2,
716
442
  }) => {
717
443
  const flowCtx = useFlowContext<FlowModelContext>();
@@ -892,69 +618,18 @@ export const FieldAssignValueInput: React.FC<Props> = ({
892
618
  }, [cf, currentAllowMultiple, itemCollectionField]);
893
619
 
894
620
  const isDateLikeField = React.useMemo(() => {
895
- if (sourceInterface && DATE_FIELD_INTERFACES.has(sourceInterface)) {
896
- return true;
897
- }
898
-
899
621
  const leaf =
900
622
  (typeof fieldName === 'string' && fieldName) ||
901
623
  (typeof targetPath === 'string' ? targetPath.split('.').filter(Boolean).slice(-1)[0] : '');
902
624
 
903
- return leaf === 'createdAt' || leaf === 'updatedAt';
904
- }, [fieldName, sourceInterface, targetPath]);
905
-
906
- const useDateVariableConstant = enableDateVariableAsConstant && isDateLikeField;
625
+ return isDateLikeCollectionField(sourceCollectionField, leaf, sourceInterface);
626
+ }, [fieldName, sourceCollectionField, sourceInterface, targetPath]);
907
627
 
908
628
  const dateVariableComponentProps = React.useMemo(() => {
909
- if (!useDateVariableConstant) {
910
- return DEFAULT_DATE_VARIABLE_COMPONENT_PROPS;
911
- }
912
-
913
- const componentProps = getFieldComponentProps(sourceCollectionField);
914
-
915
- const picker = normalizeExactDatePickerMode(componentProps?.picker);
916
- const inferredShowTime = ['datetime', 'datetimeNoTz', 'createdAt', 'updatedAt', 'unixTimestamp'].includes(
917
- sourceInterface,
918
- );
919
- const showTime = typeof componentProps?.showTime === 'boolean' ? componentProps.showTime : inferredShowTime;
920
-
921
- const dateFormat =
922
- typeof componentProps?.dateFormat === 'string' && componentProps.dateFormat
923
- ? componentProps.dateFormat
924
- : typeof componentProps?.format === 'string' && componentProps.format
925
- ? componentProps.format.split(' ')[0]
926
- : 'YYYY-MM-DD';
927
- const timeFormat =
928
- typeof componentProps?.timeFormat === 'string' && componentProps.timeFormat
929
- ? componentProps.timeFormat
930
- : 'HH:mm:ss';
931
-
932
- const format =
933
- typeof componentProps?.format === 'string' && componentProps.format
934
- ? componentProps.format
935
- : showTime
936
- ? `${dateFormat} ${timeFormat}`
937
- : dateFormat;
938
-
939
- return {
940
- picker,
941
- showTime,
942
- timeFormat,
943
- format,
944
- exactNormalizeMode: getDateVariableExactNormalizeMode(sourceInterface),
945
- };
946
- }, [sourceCollectionField, sourceInterface, useDateVariableConstant]);
947
-
948
- const dateVariableDisplayProps = React.useMemo(() => {
949
- const { exactNormalizeMode, ...rest } = dateVariableComponentProps;
950
- return rest;
951
- }, [dateVariableComponentProps]);
952
-
953
- const dateVariableDisplayPropsRef = React.useRef(dateVariableDisplayProps);
954
- dateVariableDisplayPropsRef.current = dateVariableDisplayProps;
955
-
956
- const dateVariableTranslateRef = React.useRef(flowCtx.t);
957
- dateVariableTranslateRef.current = flowCtx.t;
629
+ return isDateLikeField
630
+ ? resolveDateVariableComponentProps(sourceCollectionField, sourceInterface)
631
+ : DEFAULT_DATE_VARIABLE_COMPONENT_PROPS;
632
+ }, [isDateLikeField, sourceCollectionField, sourceInterface]);
958
633
 
959
634
  const coerceEmptyValueForRenderer = React.useCallback(
960
635
  (v: any) => {
@@ -1237,158 +912,6 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1237
912
  return C;
1238
913
  }, [placeholder, tempRoot, coerceEmptyValueForRenderer, normalizeEventValue, operator]);
1239
914
 
1240
- const DateVariableConstantEditor = React.useMemo(() => {
1241
- const C: React.FC<any> = (inputProps) => {
1242
- const wrapperStyle = pickStyle(inputProps?.style);
1243
- const raw = inputProps?.value;
1244
- const parsed = isCtxDateExpression(raw) ? parseCtxDateExpression(raw) : raw;
1245
- const parsedValue = typeof parsed === 'undefined' ? undefined : parsed;
1246
- const { token } = theme.useToken();
1247
- const [open, setOpen] = React.useState(false);
1248
- const t = dateVariableTranslateRef.current;
1249
- const datePickerProps = dateVariableDisplayPropsRef.current;
1250
- const options = React.useMemo(() => buildDateDynamicOptions(t, true), [t]);
1251
-
1252
- const dynamicType =
1253
- parsedValue && typeof parsedValue === 'object' && !Array.isArray(parsedValue)
1254
- ? (parsedValue as any)?.type
1255
- : undefined;
1256
- const selectedType = typeof dynamicType === 'string' && dynamicType ? dynamicType : 'exact';
1257
- const isRange = Array.isArray(parsedValue);
1258
- const exactSingleValue = toExactPickerDisplayValue(parsedValue, {
1259
- format: datePickerProps.format,
1260
- isRange: false,
1261
- });
1262
- const exactRangeValue = toExactPickerDisplayValue(parsedValue, {
1263
- format: datePickerProps.format,
1264
- isRange: true,
1265
- });
1266
-
1267
- const handleSelect = (val: string) => {
1268
- setOpen(false);
1269
- if (val === 'exact') {
1270
- inputProps?.onChange?.('');
1271
- return;
1272
- }
1273
- const next: any = { type: val };
1274
- if (val === 'past' || val === 'next') {
1275
- next.number = 1;
1276
- next.unit = 'day';
1277
- }
1278
- inputProps?.onChange?.(next);
1279
- };
1280
-
1281
- const handleExactSingleChange = (nextValue: any) => {
1282
- inputProps?.onChange?.(nextValue || '');
1283
- };
1284
-
1285
- const handleExactRangeChange = (nextValue: any) => {
1286
- inputProps?.onChange?.(nextValue || '');
1287
- };
1288
-
1289
- const dropdownRender = () => {
1290
- const firstPart = options.slice(0, 3);
1291
- const secondPart = options.slice(3);
1292
- const optionStyle = css`
1293
- padding: 3px 10px;
1294
- cursor: pointer;
1295
- white-space: nowrap;
1296
- overflow: hidden;
1297
- text-overflow: ellipsis;
1298
- &:hover {
1299
- background-color: ${token.colorFillSecondary};
1300
- }
1301
- `;
1302
- return (
1303
- <div style={{ maxHeight: 300, overflowY: 'auto' }}>
1304
- {firstPart.map((opt) => (
1305
- <div key={opt.value} role="option" onClick={() => handleSelect(opt.value)} className={optionStyle}>
1306
- {opt.label}
1307
- </div>
1308
- ))}
1309
- <Divider style={{ margin: '4px 0' }} />
1310
- {secondPart.map((opt) => (
1311
- <div
1312
- key={opt.value}
1313
- role="option"
1314
- className={optionStyle}
1315
- onClick={() => handleSelect(opt.value)}
1316
- title={opt.label}
1317
- >
1318
- {opt.label}
1319
- </div>
1320
- ))}
1321
- </div>
1322
- );
1323
- };
1324
-
1325
- return (
1326
- <Space.Compact style={withFullWidthStyle(wrapperStyle)}>
1327
- <Select
1328
- options={options}
1329
- open={open}
1330
- onDropdownVisibleChange={setOpen}
1331
- allowClear={false}
1332
- style={{
1333
- width: '100%',
1334
- minWidth: 100,
1335
- maxWidth: ['past', 'next', 'exact', undefined].includes(dynamicType) ? 100 : null,
1336
- }}
1337
- value={selectedType}
1338
- onChange={handleSelect}
1339
- dropdownRender={dropdownRender}
1340
- />
1341
- {['past', 'next'].includes(selectedType) && [
1342
- <InputNumber
1343
- key="number"
1344
- style={{ flex: 1 }}
1345
- value={(parsedValue as any)?.number}
1346
- onChange={(nextNumber) => {
1347
- inputProps?.onChange?.({
1348
- ...(parsedValue as any),
1349
- type: selectedType,
1350
- number: nextNumber,
1351
- unit: (parsedValue as any)?.unit || 'day',
1352
- });
1353
- }}
1354
- />,
1355
- <Select
1356
- key="unit"
1357
- value={(parsedValue as any)?.unit}
1358
- style={{ minWidth: 130, maxWidth: 140 }}
1359
- onChange={(nextUnit) => {
1360
- inputProps?.onChange?.({
1361
- ...(parsedValue as any),
1362
- type: selectedType,
1363
- unit: nextUnit,
1364
- number: (parsedValue as any)?.number || 1,
1365
- });
1366
- }}
1367
- options={[
1368
- { value: 'day', label: t?.('Day') ?? 'Day' },
1369
- { value: 'week', label: t?.('Calendar week') ?? 'Calendar week' },
1370
- { value: 'month', label: t?.('Calendar Month') ?? 'Calendar Month' },
1371
- { value: 'year', label: t?.('Calendar Year') ?? 'Calendar Year' },
1372
- ]}
1373
- popupMatchSelectWidth
1374
- />,
1375
- ]}
1376
- {(selectedType === 'exact' || !selectedType) && (
1377
- <FieldAssignExactDatePicker
1378
- {...datePickerProps}
1379
- isRange={isRange}
1380
- value={isRange ? exactRangeValue : exactSingleValue}
1381
- onChange={isRange ? handleExactRangeChange : handleExactSingleChange}
1382
- style={{ flex: 1 }}
1383
- />
1384
- )}
1385
- </Space.Compact>
1386
- );
1387
- };
1388
-
1389
- return C;
1390
- }, []);
1391
-
1392
915
  const NullComponent = React.useMemo(() => {
1393
916
  const N: React.FC = () => (
1394
917
  <Input placeholder={`<${flowCtx.t?.('Null') ?? 'Null'}>`} readOnly style={{ width: '100%' }} />
@@ -1403,54 +926,15 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1403
926
  return C;
1404
927
  }, [flowCtx]);
1405
928
 
1406
- const ConstantEditor = useDateVariableConstant ? DateVariableConstantEditor : ConstantValueEditor;
1407
-
1408
- const metaTree = React.useMemo<() => Promise<any[]>>(() => {
929
+ const baseMetaTree = React.useMemo<() => Promise<MetaTreeNode[]>>(() => {
1409
930
  return async () => {
1410
931
  const base = (await flowCtx.getPropertyMetaTree?.()) || [];
1411
932
  const extra = extraMetaTreeRef.current;
1412
933
  const extraTree = Array.isArray(extra) ? extra : [];
1413
934
  const mergedBase = mergeItemMetaTreeForAssignValue(base as MetaTreeNode[], extraTree as MetaTreeNode[]);
1414
- const limitedBase = limitAssociationMetaTree(mergedBase, { maxAssociationDepth: maxAssociationFieldDepth });
1415
- return [
1416
- {
1417
- title: tExpr('Constant'),
1418
- name: 'constant',
1419
- type: 'string',
1420
- paths: ['constant'],
1421
- render: ConstantEditor,
1422
- },
1423
- { title: tExpr('Null'), name: 'null', type: 'object', paths: ['null'], render: NullComponent },
1424
- { title: tExpr('RunJS'), name: 'runjs', type: 'object', paths: ['runjs'], render: RunJSComponent },
1425
- ...limitedBase,
1426
- ];
935
+ return limitAssociationMetaTree(mergedBase, { maxAssociationDepth: maxAssociationFieldDepth });
1427
936
  };
1428
- }, [flowCtx, ConstantEditor, NullComponent, RunJSComponent, maxAssociationFieldDepth]);
1429
-
1430
- const displayValue = React.useMemo(() => {
1431
- if (!useDateVariableConstant) {
1432
- return value;
1433
- }
1434
-
1435
- if (isCtxDateExpression(value)) {
1436
- const parsed = parseCtxDateExpression(value);
1437
- return typeof parsed === 'undefined' ? '' : parsed;
1438
- }
1439
-
1440
- return value;
1441
- }, [useDateVariableConstant, value]);
1442
-
1443
- const handleVariableInputChange = React.useCallback(
1444
- (nextValue: any) => {
1445
- if (!useDateVariableConstant) {
1446
- onChange(nextValue);
1447
- return;
1448
- }
1449
-
1450
- onChange(normalizeDateVariableOutput(nextValue, dateVariableComponentProps));
1451
- },
1452
- [dateVariableComponentProps, onChange, useDateVariableConstant],
1453
- );
937
+ }, [flowCtx, maxAssociationFieldDepth]);
1454
938
 
1455
939
  if (!fieldPath) {
1456
940
  // 不可用占位
@@ -1458,38 +942,17 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1458
942
  }
1459
943
 
1460
944
  return (
1461
- <VariableInput
1462
- value={displayValue}
1463
- onChange={handleVariableInputChange}
1464
- metaTree={metaTree}
945
+ <FieldValueVariableInput
946
+ value={value}
947
+ onChange={onChange}
948
+ baseMetaTree={baseMetaTree}
949
+ constantComponent={ConstantValueEditor}
950
+ nullComponent={NullComponent}
951
+ runJSComponent={RunJSComponent}
952
+ isDateLikeField={isDateLikeField}
953
+ dateComponentProps={dateVariableComponentProps}
1465
954
  style={{ width: '100%' }}
1466
955
  clearValue={''}
1467
- converters={{
1468
- renderInputComponent: (meta) => {
1469
- const firstPath = meta?.paths?.[0];
1470
- if (firstPath === 'constant') return ConstantEditor;
1471
- if (firstPath === 'null') return NullComponent;
1472
- if (firstPath === 'runjs') return RunJSComponent;
1473
- return null;
1474
- },
1475
- resolveValueFromPath: (item) => {
1476
- const firstPath = item?.paths?.[0];
1477
- if (firstPath === 'constant') {
1478
- return useDateVariableConstant ? { type: 'today' } : '';
1479
- }
1480
- if (firstPath === 'null') return null;
1481
- if (firstPath === 'runjs') return { code: '', version: 'v2' };
1482
- return undefined;
1483
- },
1484
- resolvePathFromValue: (currentValue) => {
1485
- if (currentValue === null) return ['null'];
1486
- if (isRunJSValue(currentValue)) return ['runjs'];
1487
- if (useDateVariableConstant && isCtxDateExpression(currentValue)) {
1488
- return ['constant'];
1489
- }
1490
- return isVariableExpression(currentValue) ? parseValueToPath(currentValue) : ['constant'];
1491
- },
1492
- }}
1493
956
  />
1494
957
  );
1495
958
  };