@nocobase/client-v2 3.0.0-alpha.6 → 3.0.0-alpha.8

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