@svadmin/core 0.52.0 → 0.53.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.
- package/package.json +1 -1
- package/src/i18n.svelte.ts +8 -0
- package/src/inferencer.ts +9 -0
- package/src/query-session.svelte.ts +9 -5
- package/src/session-query.svelte.ts +26 -11
- package/src/task-hooks.svelte.ts +33 -67
- package/src/types.ts +13 -2
package/package.json
CHANGED
package/src/i18n.svelte.ts
CHANGED
|
@@ -18,9 +18,13 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
18
18
|
'common.moveUp': '上移',
|
|
19
19
|
'common.moveDown': '下移',
|
|
20
20
|
'common.treeSelect': '树形选择',
|
|
21
|
+
'tree.toggleChildren': '展开或折叠子节点',
|
|
22
|
+
'tree.loadFailed': '子节点加载失败',
|
|
21
23
|
'common.cascader': '级联选择',
|
|
22
24
|
'common.transfer': '穿梭框',
|
|
23
25
|
'common.filterBuilder': '高级筛选',
|
|
26
|
+
'filter.incomplete': '请补全筛选条件后再应用。',
|
|
27
|
+
'filter.readonly': '只读筛选条件',
|
|
24
28
|
'common.addRule': '添加筛选条件',
|
|
25
29
|
'common.sourceList': '源列表',
|
|
26
30
|
'common.targetList': '目标列表',
|
|
@@ -716,9 +720,13 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
716
720
|
'common.moveUp': 'Move Up',
|
|
717
721
|
'common.moveDown': 'Move Down',
|
|
718
722
|
'common.treeSelect': 'Tree Select',
|
|
723
|
+
'tree.toggleChildren': 'Expand or collapse children',
|
|
724
|
+
'tree.loadFailed': 'Failed to load child nodes',
|
|
719
725
|
'common.cascader': 'Cascader',
|
|
720
726
|
'common.transfer': 'Transfer',
|
|
721
727
|
'common.filterBuilder': 'Filter Builder',
|
|
728
|
+
'filter.incomplete': 'Complete the filter conditions before applying.',
|
|
729
|
+
'filter.readonly': 'Read-only filter condition',
|
|
722
730
|
'common.addRule': 'Add Filter Rule',
|
|
723
731
|
'common.sourceList': 'Source',
|
|
724
732
|
'common.targetList': 'Target',
|
package/src/inferencer.ts
CHANGED
|
@@ -351,6 +351,15 @@ export function generateTypeBoxSchemaCode(resource: ResourceDefinition): string
|
|
|
351
351
|
case 'date':
|
|
352
352
|
typeDef = "Type.String({ format: 'date-time' })";
|
|
353
353
|
break;
|
|
354
|
+
case 'datetime':
|
|
355
|
+
case 'time':
|
|
356
|
+
// 原生输入没有强制时区,不把本地时间误标为 RFC3339 date-time。
|
|
357
|
+
typeDef = 'Type.String()';
|
|
358
|
+
break;
|
|
359
|
+
case 'daterange':
|
|
360
|
+
// 与 DateRangeInput 一致:两个端点明确存在,未选择时为 null。
|
|
361
|
+
typeDef = 'Type.Union([Type.Object({ start: Type.Union([Type.String(), Type.Null()]), end: Type.Union([Type.String(), Type.Null()]) }, { additionalProperties: false }), Type.Null()])';
|
|
362
|
+
break;
|
|
354
363
|
case 'email':
|
|
355
364
|
typeDef = "Type.String({ format: 'email' })";
|
|
356
365
|
break;
|
|
@@ -26,6 +26,8 @@ export interface QuerySessionOptions {
|
|
|
26
26
|
isTargetCurrent?: () => boolean;
|
|
27
27
|
/** Allow a shared query function to delegate work to another live observer. */
|
|
28
28
|
allowRetiredQueryFn?: boolean;
|
|
29
|
+
/** Internal domain adapter; must return a sanitized error without provider diagnostics. */
|
|
30
|
+
normalizeError?: (error: unknown) => Error;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export function supersededQuerySession(): HttpError {
|
|
@@ -88,6 +90,8 @@ export function createQuerySession<O extends QuerySessionOptions>(context: Admin
|
|
|
88
90
|
origin.provider === context.authProvider && origin.tenant === context.tenantCacheKey?.__svadminTenant &&
|
|
89
91
|
origin.router === context.routerProvider && (origin.isTargetCurrent === undefined || origin.isTargetCurrent() === true);
|
|
90
92
|
const current = (origin: Origin) => targetCurrent(origin) && sessionCurrent(origin);
|
|
93
|
+
const errorFor = (origin: Origin, error: unknown): Error =>
|
|
94
|
+
origin.normalizeError ? origin.normalizeError(error) : querySessionError(error);
|
|
91
95
|
|
|
92
96
|
function notify(origin: Origin, type: 'success' | 'error', value: unknown): void {
|
|
93
97
|
try {
|
|
@@ -125,7 +129,7 @@ export function createQuerySession<O extends QuerySessionOptions>(context: Admin
|
|
|
125
129
|
});
|
|
126
130
|
return;
|
|
127
131
|
}
|
|
128
|
-
const error =
|
|
132
|
+
const error = errorFor(origin, result.error);
|
|
129
133
|
query.setState({
|
|
130
134
|
error,
|
|
131
135
|
errorUpdatedAt: now,
|
|
@@ -154,7 +158,7 @@ export function createQuerySession<O extends QuerySessionOptions>(context: Admin
|
|
|
154
158
|
throw supersededQuerySession();
|
|
155
159
|
}
|
|
156
160
|
if (!current(origin)) settleDetached(origin, { success: false, error });
|
|
157
|
-
throw
|
|
161
|
+
throw errorFor(origin, error);
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
164
|
|
|
@@ -174,7 +178,7 @@ export function createQuerySession<O extends QuerySessionOptions>(context: Admin
|
|
|
174
178
|
untrack(() => {
|
|
175
179
|
void handleAuthError(querySessionError(result.error), context, () => targetCurrent(origin),
|
|
176
180
|
() => clearAuthQueries(client, origin.provider), origin.auth).catch(() => {});
|
|
177
|
-
notify(origin, 'error',
|
|
181
|
+
notify(origin, 'error', errorFor(origin, result.error));
|
|
178
182
|
});
|
|
179
183
|
}
|
|
180
184
|
});
|
|
@@ -186,12 +190,12 @@ export function createQuerySession<O extends QuerySessionOptions>(context: Admin
|
|
|
186
190
|
if (query?.getObserversCount() === 0) void query.cancel();
|
|
187
191
|
});
|
|
188
192
|
});
|
|
189
|
-
return { get options() { return options; }, client, current, sessionCurrent, run, observe };
|
|
193
|
+
return { get options() { return options; }, client, current, sessionCurrent, run, observe, errorFor };
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
export function pendingQueryResult<T, F extends (settings?: RefetchOptions) => Promise<unknown> = QueryObserverResult<T, unknown>['refetch']>(
|
|
193
197
|
refetch: F,
|
|
194
|
-
): Omit<QueryObserverPendingResult<T,
|
|
198
|
+
): Omit<QueryObserverPendingResult<T, Error>, 'refetch'> & { refetch: F } {
|
|
195
199
|
return {
|
|
196
200
|
data: undefined, dataUpdatedAt: 0, error: null, errorUpdatedAt: 0,
|
|
197
201
|
failureCount: 0, failureReason: null, errorUpdateCount: 0,
|
|
@@ -3,7 +3,7 @@ import { tick, untrack } from 'svelte';
|
|
|
3
3
|
import type { AdminContextAccessor } from './context.svelte';
|
|
4
4
|
import { definedOptions } from './defined-options';
|
|
5
5
|
import {
|
|
6
|
-
createQuerySession, createQueryResultView, pendingQueryResult,
|
|
6
|
+
createQuerySession, createQueryResultView, pendingQueryResult, supersededQuerySession,
|
|
7
7
|
type QuerySessionOptions,
|
|
8
8
|
} from './query-session.svelte';
|
|
9
9
|
|
|
@@ -14,17 +14,28 @@ interface SessionQueryOptions<TQuery, T> extends QuerySessionOptions {
|
|
|
14
14
|
staleTime?: number;
|
|
15
15
|
gcTime?: number;
|
|
16
16
|
refetchOnWindowFocus?: boolean;
|
|
17
|
+
refetchInterval?: number | false;
|
|
18
|
+
refetchIntervalInBackground?: boolean;
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
// 只有显式提供错误归一化器的内部领域查询才承诺 Error;普通查询保留 unknown 契约。
|
|
22
|
+
export function createSessionQuery<TQuery, T = TQuery>(
|
|
23
|
+
context: AdminContextAccessor,
|
|
24
|
+
getOptions: () => SessionQueryOptions<TQuery, T> & { normalizeError: (error: unknown) => Error },
|
|
25
|
+
): QueryObserverResult<T, Error>;
|
|
26
|
+
export function createSessionQuery<TQuery, T = TQuery>(
|
|
27
|
+
context: AdminContextAccessor,
|
|
28
|
+
getOptions: () => SessionQueryOptions<TQuery, T>,
|
|
29
|
+
): QueryObserverResult<T, unknown>;
|
|
19
30
|
/** Ordinary reads keep their public result shape while cache and effects own an auth revision. */
|
|
20
31
|
export function createSessionQuery<TQuery, T = TQuery>(
|
|
21
32
|
context: AdminContextAccessor,
|
|
22
33
|
getOptions: () => SessionQueryOptions<TQuery, T>,
|
|
23
|
-
): QueryObserverResult<T,
|
|
34
|
+
): QueryObserverResult<T, Error> {
|
|
24
35
|
const session = createQuerySession(context, getOptions);
|
|
25
36
|
const inFlight = new Map<string, { origin: typeof session.options; request: Promise<TQuery> }>();
|
|
26
37
|
type Origin = typeof session.options;
|
|
27
|
-
const query = createQuery<TQuery,
|
|
38
|
+
const query = createQuery<TQuery, Error>(() => {
|
|
28
39
|
const origin = session.options;
|
|
29
40
|
return {
|
|
30
41
|
queryKey: origin.queryKey,
|
|
@@ -45,39 +56,43 @@ export function createSessionQuery<TQuery, T = TQuery>(
|
|
|
45
56
|
enabled: origin.enabled && origin.auth.available,
|
|
46
57
|
...definedOptions({
|
|
47
58
|
staleTime: origin.staleTime, gcTime: origin.gcTime, refetchOnWindowFocus: origin.refetchOnWindowFocus,
|
|
59
|
+
refetchInterval: origin.refetchInterval, refetchIntervalInBackground: origin.refetchIntervalInBackground,
|
|
48
60
|
}),
|
|
49
61
|
};
|
|
50
62
|
});
|
|
51
63
|
|
|
52
|
-
function project(origin: Origin, result: QueryObserverResult<TQuery,
|
|
53
|
-
const refetch = async (settings?: RefetchOptions): Promise<QueryObserverResult<T,
|
|
64
|
+
function project(origin: Origin, result: QueryObserverResult<TQuery, Error>): QueryObserverResult<T, Error> {
|
|
65
|
+
const refetch = async (settings?: RefetchOptions): Promise<QueryObserverResult<T, Error>> => {
|
|
54
66
|
if (!session.current(origin)) return pendingQueryResult(refetch);
|
|
55
67
|
await tick();
|
|
56
68
|
if (!session.current(origin)) return pendingQueryResult(refetch);
|
|
57
69
|
try {
|
|
58
70
|
return project(origin, await query.refetch(settings));
|
|
59
71
|
} catch (error) {
|
|
60
|
-
throw session.current(origin) ?
|
|
72
|
+
throw session.current(origin) ? session.errorFor(origin, error) : supersededQuerySession();
|
|
61
73
|
}
|
|
62
74
|
};
|
|
63
75
|
if (!session.current(origin)) return pendingQueryResult(refetch);
|
|
76
|
+
// 新查询键尚未入缓存时也要订阅结果,否则请求完成后视图不会重新计算。
|
|
77
|
+
const observedData = result.data;
|
|
78
|
+
const observedError = result.error;
|
|
64
79
|
const state = session.client.getQueryState(origin.queryKey);
|
|
65
80
|
// The observer may still expose its previous key until Svelte flushes its effects.
|
|
66
|
-
if (!state || state.data !==
|
|
67
|
-
const failureReason = result.failureReason === null ? null :
|
|
81
|
+
if (!state || state.data !== observedData || state.error !== observedError) return pendingQueryResult(refetch);
|
|
82
|
+
const failureReason = result.failureReason === null ? null : session.errorFor(origin, result.failureReason);
|
|
68
83
|
try {
|
|
69
84
|
if (result.isSuccess || result.isRefetchError) {
|
|
70
85
|
const data = untrack(() => origin.decode(result.data, state.dataUpdateCount));
|
|
71
86
|
if (!session.current(origin)) return pendingQueryResult(refetch);
|
|
72
87
|
if (result.isSuccess) return { ...result, data, failureReason, refetch };
|
|
73
|
-
return { ...result, data, error:
|
|
88
|
+
return { ...result, data, error: session.errorFor(origin, result.error), failureReason, refetch };
|
|
74
89
|
}
|
|
75
|
-
if (result.isError) return { ...result, error:
|
|
90
|
+
if (result.isError) return { ...result, error: session.errorFor(origin, result.error), failureReason, refetch };
|
|
76
91
|
return { ...result, failureReason, refetch };
|
|
77
92
|
} catch (error) {
|
|
78
93
|
if (!session.current(origin)) return pendingQueryResult(refetch);
|
|
79
94
|
return {
|
|
80
|
-
...pendingQueryResult(refetch), data: undefined, error:
|
|
95
|
+
...pendingQueryResult(refetch), data: undefined, error: session.errorFor(origin, error), status: 'error',
|
|
81
96
|
isError: true, isPending: false, isLoading: false, isLoadingError: true,
|
|
82
97
|
isRefetchError: false, isSuccess: false, isPlaceholderData: false,
|
|
83
98
|
};
|
package/src/task-hooks.svelte.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { definedOptions } from './defined-options';
|
|
2
|
-
import { createMutation,
|
|
2
|
+
import { createMutation,useQueryClient } from '@tanstack/svelte-query';
|
|
3
|
+
import { createSessionQuery } from './session-query.svelte';
|
|
4
|
+
import { supersededQuerySession } from './query-session.svelte';
|
|
3
5
|
import { getAdminOptions } from './options.svelte';
|
|
4
6
|
import { captureAdminContext } from './context.svelte';
|
|
5
7
|
import type { AdminContextAccessor } from './context.svelte';
|
|
@@ -9,7 +11,7 @@ import {
|
|
|
9
11
|
import { queryKeyMatches, type QueryKey } from './query-keys';
|
|
10
12
|
import { replaceReactiveMembers } from './reactive-projection';
|
|
11
13
|
import { taskQueryParams, withValidatedTaskProvider } from './task-provider';
|
|
12
|
-
import
|
|
14
|
+
import { TaskError, decodeTaskRecord, decodeTaskList } from './task-contract';
|
|
13
15
|
import { checkError,createOvertimeTracker,fireErrorNotification,fireSuccessNotification } from './hook-utils.svelte';
|
|
14
16
|
import type { NotificationConfig,OvertimeOptions } from './hook-utils.svelte';
|
|
15
17
|
import { useTranslation } from './i18n.svelte';
|
|
@@ -48,6 +50,19 @@ function scopedTaskKey(key: QueryKey, provider: TaskProvider | undefined): Query
|
|
|
48
50
|
return [{ ...key[0], params: definedOptions({ taskProvider: providerKey, query: key[0].params }) }];
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
function taskReadError(error: unknown): Error {
|
|
54
|
+
try {
|
|
55
|
+
if (typeof error === 'object' && error !== null) {
|
|
56
|
+
const code: unknown = Object.getOwnPropertyDescriptor(error, 'code')?.value;
|
|
57
|
+
if (code === 'QUERY_SESSION_SUPERSEDED') return supersededQuerySession();
|
|
58
|
+
if (code === 'INVALID_TASK_INPUT' || code === 'INVALID_TASK_RESPONSE' || code === 'TASK_PROVIDER_FAILED') {
|
|
59
|
+
return new TaskError(code);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch { /* 不执行错误对象中的 getter,也不回显上游诊断。 */ }
|
|
63
|
+
return new TaskError('TASK_PROVIDER_FAILED');
|
|
64
|
+
}
|
|
65
|
+
|
|
51
66
|
export interface UseSubmitTaskOptions {
|
|
52
67
|
mutationOptions?: {
|
|
53
68
|
onSuccess?: (data: unknown,variables: unknown,context: unknown) => void;
|
|
@@ -157,12 +172,17 @@ export function useTask(
|
|
|
157
172
|
const i18n=useTranslation();
|
|
158
173
|
const adminOptions=getAdminOptions();
|
|
159
174
|
|
|
160
|
-
const query=
|
|
175
|
+
const query=createSessionQuery<TaskRecord>(adminContext, () => {
|
|
161
176
|
const activeProvider=options.taskProvider??adminContext.taskProvider;
|
|
162
177
|
const provider=activeProvider? withValidatedTaskProvider(activeProvider):undefined;
|
|
163
178
|
const taskId=options.taskId;
|
|
164
179
|
const queryOptions=options.queryOptions;
|
|
165
180
|
return {
|
|
181
|
+
resource: taskId ?? '',
|
|
182
|
+
successNotification: options.successNotification,
|
|
183
|
+
errorNotification: options.errorNotification ?? i18n.t('task.fetchFailed'),
|
|
184
|
+
normalizeError: taskReadError,
|
|
185
|
+
decode: value => decodeTaskRecord(value, taskId),
|
|
166
186
|
queryKey: scopedTaskKey(adminContext.queryKeys().task.one(taskId??''),provider),
|
|
167
187
|
queryFn: async () => {
|
|
168
188
|
if(!provider) throw new Error('TaskProvider not found.');
|
|
@@ -184,37 +204,6 @@ export function useTask(
|
|
|
184
204
|
|
|
185
205
|
const overtime=createOvertimeTracker(() => query.isLoading,options.overtimeOptions??adminOptions.overtime);
|
|
186
206
|
|
|
187
|
-
let lastSuccessAt=0;
|
|
188
|
-
let lastErrorAt=0;
|
|
189
|
-
$effect(() => {
|
|
190
|
-
if(query.isSuccess&&query.dataUpdatedAt>lastSuccessAt) {
|
|
191
|
-
lastSuccessAt=query.dataUpdatedAt;
|
|
192
|
-
if(options.successNotification) {
|
|
193
|
-
fireSuccessNotification({
|
|
194
|
-
config: options.successNotification,
|
|
195
|
-
defaultMessage: '',
|
|
196
|
-
data: query.data,
|
|
197
|
-
...definedOptions({ resource: options.taskId }),
|
|
198
|
-
...definedOptions({
|
|
199
|
-
provider: adminContext.notificationProvider
|
|
200
|
-
})
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
} else if(query.isError&&query.errorUpdatedAt>lastErrorAt) {
|
|
204
|
-
lastErrorAt=query.errorUpdatedAt;
|
|
205
|
-
checkError(query.error,adminContext);
|
|
206
|
-
fireErrorNotification({
|
|
207
|
-
config: options.errorNotification,
|
|
208
|
-
defaultMessage: i18n.t('task.fetchFailed'),
|
|
209
|
-
error: query.error,
|
|
210
|
-
...definedOptions({ resource: options.taskId }),
|
|
211
|
-
...definedOptions({
|
|
212
|
-
provider: adminContext.notificationProvider
|
|
213
|
-
})
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
|
|
218
207
|
return replaceReactiveMembers(query,{ get overtime() { return overtime; } });
|
|
219
208
|
}
|
|
220
209
|
|
|
@@ -242,13 +231,22 @@ export function useTaskList(
|
|
|
242
231
|
const i18n=useTranslation();
|
|
243
232
|
const adminOptions=getAdminOptions();
|
|
244
233
|
|
|
245
|
-
const query=
|
|
234
|
+
const query=createSessionQuery<TaskListResult>(adminContext, () => {
|
|
246
235
|
const activeProvider=options.taskProvider??adminContext.taskProvider;
|
|
247
236
|
const provider=activeProvider? withValidatedTaskProvider(activeProvider):undefined;
|
|
248
237
|
const params=options.params===undefined? undefined:taskQueryParams(options.params);
|
|
249
238
|
const dlq=options.dlq??false;
|
|
250
239
|
const queryOptions=options.queryOptions;
|
|
240
|
+
const resource = dlq ? 'taskDlq' : 'tasks';
|
|
241
|
+
const successNotification = options.successNotification;
|
|
251
242
|
return {
|
|
243
|
+
resource,
|
|
244
|
+
successNotification: typeof successNotification === 'function'
|
|
245
|
+
? (data: unknown) => successNotification(data, params, resource)
|
|
246
|
+
: successNotification,
|
|
247
|
+
errorNotification: options.errorNotification ?? i18n.t('task.fetchListFailed'),
|
|
248
|
+
normalizeError: taskReadError,
|
|
249
|
+
decode: decodeTaskList,
|
|
252
250
|
queryKey: scopedTaskKey(adminContext.queryKeys().task.list(definedOptions({
|
|
253
251
|
list: dlq? 'dlq':'default',
|
|
254
252
|
params,
|
|
@@ -277,38 +275,6 @@ export function useTaskList(
|
|
|
277
275
|
|
|
278
276
|
const overtime=createOvertimeTracker(() => query.isLoading,options.overtimeOptions??adminOptions.overtime);
|
|
279
277
|
|
|
280
|
-
let lastSuccessAt=0;
|
|
281
|
-
let lastErrorAt=0;
|
|
282
|
-
$effect(() => {
|
|
283
|
-
if(query.isSuccess&&query.dataUpdatedAt>lastSuccessAt) {
|
|
284
|
-
lastSuccessAt=query.dataUpdatedAt;
|
|
285
|
-
if(options.successNotification) {
|
|
286
|
-
fireSuccessNotification({
|
|
287
|
-
config: options.successNotification,
|
|
288
|
-
defaultMessage: '',
|
|
289
|
-
data: query.data,
|
|
290
|
-
values: options.params,
|
|
291
|
-
resource: options.dlq? 'taskDlq':'tasks',
|
|
292
|
-
...definedOptions({
|
|
293
|
-
provider: adminContext.notificationProvider
|
|
294
|
-
})
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
} else if(query.isError&&query.errorUpdatedAt>lastErrorAt) {
|
|
298
|
-
lastErrorAt=query.errorUpdatedAt;
|
|
299
|
-
checkError(query.error,adminContext);
|
|
300
|
-
fireErrorNotification({
|
|
301
|
-
config: options.errorNotification,
|
|
302
|
-
defaultMessage: i18n.t('task.fetchListFailed'),
|
|
303
|
-
error: query.error,
|
|
304
|
-
resource: options.dlq? 'taskDlq':'tasks',
|
|
305
|
-
...definedOptions({
|
|
306
|
-
provider: adminContext.notificationProvider
|
|
307
|
-
})
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
|
|
312
278
|
return replaceReactiveMembers(query,{ get overtime() { return overtime; } });
|
|
313
279
|
}
|
|
314
280
|
|
package/src/types.ts
CHANGED
|
@@ -408,9 +408,9 @@ export interface MenuItem {
|
|
|
408
408
|
export interface FieldDefinition {
|
|
409
409
|
key: string;
|
|
410
410
|
label: string;
|
|
411
|
-
type: 'text' | 'number' | 'boolean' | 'date' | 'select' | 'multiselect' | 'tags'
|
|
411
|
+
type: 'text' | 'number' | 'boolean' | 'date' | 'time' | 'datetime' | 'daterange' | 'select' | 'multiselect' | 'tags'
|
|
412
412
|
| 'textarea' | 'richtext' | 'image' | 'images' | 'json' | 'relation' | 'color'
|
|
413
|
-
| 'url' | 'email' | 'phone' | 'currency' | 'file' | 'markdown' | 'password' | 'array'
|
|
413
|
+
| 'url' | 'email' | 'phone' | 'currency' | 'percent' | 'file' | 'markdown' | 'password' | 'array'
|
|
414
414
|
| 'tree-select' | 'treeselect' | 'cascader' | 'transfer' | 'rate' | 'rating' | 'avatar' | 'copy' | 'code';
|
|
415
415
|
required?: boolean;
|
|
416
416
|
searchable?: boolean;
|
|
@@ -430,6 +430,17 @@ export interface FieldDefinition {
|
|
|
430
430
|
changeOnSelect?: boolean;
|
|
431
431
|
separator?: string;
|
|
432
432
|
defaultValue?: unknown;
|
|
433
|
+
// Numeric input contract
|
|
434
|
+
min?: number;
|
|
435
|
+
max?: number;
|
|
436
|
+
step?: number;
|
|
437
|
+
precision?: number;
|
|
438
|
+
currency?: string;
|
|
439
|
+
locale?: string;
|
|
440
|
+
scale?: '100' | '1';
|
|
441
|
+
// File upload contract
|
|
442
|
+
accept?: string;
|
|
443
|
+
maxSize?: number;
|
|
433
444
|
// Relation support
|
|
434
445
|
resource?: string; // related resource name
|
|
435
446
|
optionLabel?: string; // field to use as label
|