@svadmin/surface 0.8.19 → 0.10.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/README.md +76 -38
- package/STYLING.md +33 -0
- package/compatibility.json +40 -8
- package/dist/agent-contract.d.ts +37 -0
- package/dist/agent-contract.js +206 -0
- package/dist/agent.d.ts +38 -7
- package/dist/agent.js +98 -59
- package/dist/binding.d.ts +2 -0
- package/dist/binding.js +7 -1
- package/dist/builtin-definitions.d.ts +6 -0
- package/dist/builtin-definitions.js +40 -0
- package/dist/builtin-schemas.d.ts +25 -0
- package/dist/builtin-schemas.js +13 -0
- package/dist/business-data.d.ts +16 -0
- package/dist/business-data.js +42 -0
- package/dist/business-definitions.d.ts +28 -0
- package/dist/business-definitions.js +53 -0
- package/dist/business.d.ts +3 -0
- package/dist/business.js +17 -0
- package/dist/catalog.d.ts +4 -61
- package/dist/catalog.js +20 -44
- package/dist/components/ActivityFeedWidget.svelte +34 -0
- package/dist/components/ActivityFeedWidget.svelte.d.ts +4 -0
- package/dist/components/MetricWidget.svelte +13 -8
- package/dist/components/MetricWidget.svelte.d.ts +1 -0
- package/dist/components/ResourceDetailWidget.svelte +56 -0
- package/dist/components/ResourceDetailWidget.svelte.d.ts +4 -0
- package/dist/components/ResourceFormBody.svelte +97 -0
- package/dist/components/ResourceFormBody.svelte.d.ts +13 -0
- package/dist/components/ResourceFormWidget.svelte +19 -0
- package/dist/components/ResourceFormWidget.svelte.d.ts +4 -0
- package/dist/components/ResourceTableWidget.svelte +15 -12
- package/dist/components/ResourceTableWidget.svelte.d.ts +1 -0
- package/dist/components/SurfaceEditPreview.svelte +107 -0
- package/dist/components/SurfaceEditPreview.svelte.d.ts +20 -0
- package/dist/components/SurfaceRenderer.svelte +121 -163
- package/dist/components/SurfaceRenderer.svelte.d.ts +5 -0
- package/dist/components/SurfaceWorkflowProvider.svelte +22 -0
- package/dist/components/SurfaceWorkflowProvider.svelte.d.ts +17 -0
- package/dist/components/SvarGridWidget.svelte +47 -0
- package/dist/components/SvarGridWidget.svelte.d.ts +4 -0
- package/dist/components/SvarSurfaceProvider.svelte +12 -0
- package/dist/components/SvarSurfaceProvider.svelte.d.ts +11 -0
- package/dist/design-contract.d.ts +2 -0
- package/dist/design-contract.js +2 -0
- package/dist/edits.d.ts +57 -0
- package/dist/edits.js +166 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -1
- package/dist/interactive.d.ts +6 -0
- package/dist/interactive.js +15 -0
- package/dist/localization.d.ts +11 -1
- package/dist/localization.js +20 -0
- package/dist/openui.d.ts +64 -0
- package/dist/openui.js +168 -0
- package/dist/recipes.d.ts +20 -0
- package/dist/recipes.js +20 -0
- package/dist/schema.d.ts +108 -0
- package/dist/schema.js +73 -0
- package/dist/server-sqlite.d.ts +1 -0
- package/dist/server-sqlite.js +2 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +4 -0
- package/dist/source-cache.d.ts +30 -0
- package/dist/source-cache.js +135 -0
- package/dist/styles/editor.css +124 -0
- package/dist/styles/editor.d.ts +8 -0
- package/dist/styles/editor.js +9 -0
- package/dist/styles.css +43 -0
- package/dist/svar-catalog.d.ts +3 -0
- package/dist/svar-catalog.js +7 -0
- package/dist/svar-context.d.ts +9 -0
- package/dist/svar-context.js +1 -0
- package/dist/svar-schema.d.ts +21 -0
- package/dist/svar-schema.js +36 -0
- package/dist/svar.d.ts +4 -0
- package/dist/svar.js +3 -0
- package/dist/svelte.d.ts +4 -2
- package/dist/svelte.js +2 -1
- package/dist/types.d.ts +5 -1
- package/dist/validation.js +14 -108
- package/dist/wire.d.ts +13 -0
- package/dist/wire.js +30 -0
- package/dist/workflows/action-contracts.d.ts +7 -0
- package/dist/workflows/action-contracts.js +54 -0
- package/dist/workflows/catalog.d.ts +20 -0
- package/dist/workflows/catalog.js +50 -0
- package/dist/workflows/client.d.ts +45 -0
- package/dist/workflows/client.js +125 -0
- package/dist/workflows/context.d.ts +9 -0
- package/dist/workflows/context.js +5 -0
- package/dist/workflows/openui-guard.d.ts +19 -0
- package/dist/workflows/openui-guard.js +209 -0
- package/dist/workflows/service.d.ts +58 -0
- package/dist/workflows/service.js +296 -0
- package/dist/workflows/sqlite-store.d.ts +20 -0
- package/dist/workflows/sqlite-store.js +91 -0
- package/dist/workflows/types.d.ts +103 -0
- package/dist/workflows/types.js +8 -0
- package/dist/workflows.d.ts +5 -0
- package/dist/workflows.js +4 -0
- package/package.json +59 -8
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export declare const surfaceIdSchema: import("@sinclair/typebox").TString;
|
|
2
|
+
export declare const surfaceFilterSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
3
|
+
field: import("@sinclair/typebox").TString;
|
|
4
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"eq">, import("@sinclair/typebox").TLiteral<"ne">, import("@sinclair/typebox").TLiteral<"lt">, import("@sinclair/typebox").TLiteral<"lte">, import("@sinclair/typebox").TLiteral<"gt">, import("@sinclair/typebox").TLiteral<"gte">, import("@sinclair/typebox").TLiteral<"contains">, import("@sinclair/typebox").TLiteral<"startswith">, import("@sinclair/typebox").TLiteral<"endswith">]>;
|
|
5
|
+
value: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber, import("@sinclair/typebox").TBoolean, import("@sinclair/typebox").TNull]>;
|
|
6
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
7
|
+
field: import("@sinclair/typebox").TString;
|
|
8
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"in">, import("@sinclair/typebox").TLiteral<"nin">]>;
|
|
9
|
+
value: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber, import("@sinclair/typebox").TBoolean, import("@sinclair/typebox").TNull]>>;
|
|
10
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
11
|
+
field: import("@sinclair/typebox").TString;
|
|
12
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"null">, import("@sinclair/typebox").TLiteral<"nnull">]>;
|
|
13
|
+
}>]>;
|
|
14
|
+
export declare const surfaceListSourceSchema: import("@sinclair/typebox").TObject<{
|
|
15
|
+
id: import("@sinclair/typebox").TString;
|
|
16
|
+
type: import("@sinclair/typebox").TLiteral<"resource-list">;
|
|
17
|
+
resource: import("@sinclair/typebox").TString;
|
|
18
|
+
pageSize: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
19
|
+
sorters: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
20
|
+
field: import("@sinclair/typebox").TString;
|
|
21
|
+
order: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"asc">, import("@sinclair/typebox").TLiteral<"desc">]>;
|
|
22
|
+
}>>>;
|
|
23
|
+
filters: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
24
|
+
field: import("@sinclair/typebox").TString;
|
|
25
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"eq">, import("@sinclair/typebox").TLiteral<"ne">, import("@sinclair/typebox").TLiteral<"lt">, import("@sinclair/typebox").TLiteral<"lte">, import("@sinclair/typebox").TLiteral<"gt">, import("@sinclair/typebox").TLiteral<"gte">, import("@sinclair/typebox").TLiteral<"contains">, import("@sinclair/typebox").TLiteral<"startswith">, import("@sinclair/typebox").TLiteral<"endswith">]>;
|
|
26
|
+
value: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber, import("@sinclair/typebox").TBoolean, import("@sinclair/typebox").TNull]>;
|
|
27
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
28
|
+
field: import("@sinclair/typebox").TString;
|
|
29
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"in">, import("@sinclair/typebox").TLiteral<"nin">]>;
|
|
30
|
+
value: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber, import("@sinclair/typebox").TBoolean, import("@sinclair/typebox").TNull]>>;
|
|
31
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
32
|
+
field: import("@sinclair/typebox").TString;
|
|
33
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"null">, import("@sinclair/typebox").TLiteral<"nnull">]>;
|
|
34
|
+
}>]>>>;
|
|
35
|
+
}>;
|
|
36
|
+
export declare const surfaceOneSourceSchema: import("@sinclair/typebox").TObject<{
|
|
37
|
+
id: import("@sinclair/typebox").TString;
|
|
38
|
+
type: import("@sinclair/typebox").TLiteral<"resource-one">;
|
|
39
|
+
resource: import("@sinclair/typebox").TString;
|
|
40
|
+
recordId: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber]>;
|
|
41
|
+
}>;
|
|
42
|
+
export declare const surfaceLayoutSchema: import("@sinclair/typebox").TObject<{
|
|
43
|
+
type: import("@sinclair/typebox").TLiteral<"grid">;
|
|
44
|
+
columns: import("@sinclair/typebox").TLiteral<12>;
|
|
45
|
+
gap: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"sm">, import("@sinclair/typebox").TLiteral<"md">, import("@sinclair/typebox").TLiteral<"lg">]>>;
|
|
46
|
+
}>;
|
|
47
|
+
export declare const surfaceWidgetSchema: import("@sinclair/typebox").TObject<{
|
|
48
|
+
id: import("@sinclair/typebox").TString;
|
|
49
|
+
type: import("@sinclair/typebox").TString;
|
|
50
|
+
props: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnknown>;
|
|
51
|
+
binding: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
52
|
+
sourceId: import("@sinclair/typebox").TString;
|
|
53
|
+
pointer: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
54
|
+
}>>;
|
|
55
|
+
placement: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
56
|
+
columnSpan: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
57
|
+
}>>;
|
|
58
|
+
}>;
|
|
59
|
+
export declare const surfaceSpecSchema: import("@sinclair/typebox").TObject<{
|
|
60
|
+
schemaVersion: import("@sinclair/typebox").TString;
|
|
61
|
+
catalogVersion: import("@sinclair/typebox").TString;
|
|
62
|
+
surfaceId: import("@sinclair/typebox").TString;
|
|
63
|
+
title: import("@sinclair/typebox").TString;
|
|
64
|
+
layout: import("@sinclair/typebox").TObject<{
|
|
65
|
+
type: import("@sinclair/typebox").TLiteral<"grid">;
|
|
66
|
+
columns: import("@sinclair/typebox").TLiteral<12>;
|
|
67
|
+
gap: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"sm">, import("@sinclair/typebox").TLiteral<"md">, import("@sinclair/typebox").TLiteral<"lg">]>>;
|
|
68
|
+
}>;
|
|
69
|
+
dataSources: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
70
|
+
id: import("@sinclair/typebox").TString;
|
|
71
|
+
type: import("@sinclair/typebox").TLiteral<"resource-list">;
|
|
72
|
+
resource: import("@sinclair/typebox").TString;
|
|
73
|
+
pageSize: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
74
|
+
sorters: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
75
|
+
field: import("@sinclair/typebox").TString;
|
|
76
|
+
order: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"asc">, import("@sinclair/typebox").TLiteral<"desc">]>;
|
|
77
|
+
}>>>;
|
|
78
|
+
filters: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
79
|
+
field: import("@sinclair/typebox").TString;
|
|
80
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"eq">, import("@sinclair/typebox").TLiteral<"ne">, import("@sinclair/typebox").TLiteral<"lt">, import("@sinclair/typebox").TLiteral<"lte">, import("@sinclair/typebox").TLiteral<"gt">, import("@sinclair/typebox").TLiteral<"gte">, import("@sinclair/typebox").TLiteral<"contains">, import("@sinclair/typebox").TLiteral<"startswith">, import("@sinclair/typebox").TLiteral<"endswith">]>;
|
|
81
|
+
value: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber, import("@sinclair/typebox").TBoolean, import("@sinclair/typebox").TNull]>;
|
|
82
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
83
|
+
field: import("@sinclair/typebox").TString;
|
|
84
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"in">, import("@sinclair/typebox").TLiteral<"nin">]>;
|
|
85
|
+
value: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber, import("@sinclair/typebox").TBoolean, import("@sinclair/typebox").TNull]>>;
|
|
86
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
87
|
+
field: import("@sinclair/typebox").TString;
|
|
88
|
+
operator: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"null">, import("@sinclair/typebox").TLiteral<"nnull">]>;
|
|
89
|
+
}>]>>>;
|
|
90
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
91
|
+
id: import("@sinclair/typebox").TString;
|
|
92
|
+
type: import("@sinclair/typebox").TLiteral<"resource-one">;
|
|
93
|
+
resource: import("@sinclair/typebox").TString;
|
|
94
|
+
recordId: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber]>;
|
|
95
|
+
}>]>>;
|
|
96
|
+
widgets: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
97
|
+
id: import("@sinclair/typebox").TString;
|
|
98
|
+
type: import("@sinclair/typebox").TString;
|
|
99
|
+
props: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnknown>;
|
|
100
|
+
binding: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
101
|
+
sourceId: import("@sinclair/typebox").TString;
|
|
102
|
+
pointer: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
103
|
+
}>>;
|
|
104
|
+
placement: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
105
|
+
columnSpan: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
106
|
+
}>>;
|
|
107
|
+
}>>;
|
|
108
|
+
}>;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { SURFACE_LIMITS } from './types.js';
|
|
3
|
+
// 结构定义只维护一份:运行时校验与 AI 输出 schema 共用,策略校验仍在 validation.ts。
|
|
4
|
+
export const surfaceIdSchema = Type.String({
|
|
5
|
+
minLength: 1,
|
|
6
|
+
maxLength: SURFACE_LIMITS.maxIdLength,
|
|
7
|
+
pattern: '^[A-Za-z][A-Za-z0-9_-]*$',
|
|
8
|
+
});
|
|
9
|
+
const fieldSchema = Type.String({ minLength: 1, maxLength: SURFACE_LIMITS.maxIdLength });
|
|
10
|
+
const jsonPrimitiveSchema = Type.Union([Type.String(), Type.Number(), Type.Boolean(), Type.Null()]);
|
|
11
|
+
export const surfaceFilterSchema = Type.Union([
|
|
12
|
+
Type.Object({
|
|
13
|
+
field: fieldSchema,
|
|
14
|
+
operator: Type.Union([
|
|
15
|
+
Type.Literal('eq'), Type.Literal('ne'), Type.Literal('lt'), Type.Literal('lte'),
|
|
16
|
+
Type.Literal('gt'), Type.Literal('gte'), Type.Literal('contains'),
|
|
17
|
+
Type.Literal('startswith'), Type.Literal('endswith'),
|
|
18
|
+
]),
|
|
19
|
+
value: jsonPrimitiveSchema,
|
|
20
|
+
}, { additionalProperties: false }),
|
|
21
|
+
Type.Object({
|
|
22
|
+
field: fieldSchema,
|
|
23
|
+
operator: Type.Union([Type.Literal('in'), Type.Literal('nin')]),
|
|
24
|
+
value: Type.Array(jsonPrimitiveSchema),
|
|
25
|
+
}, { additionalProperties: false }),
|
|
26
|
+
Type.Object({
|
|
27
|
+
field: fieldSchema,
|
|
28
|
+
operator: Type.Union([Type.Literal('null'), Type.Literal('nnull')]),
|
|
29
|
+
}, { additionalProperties: false }),
|
|
30
|
+
]);
|
|
31
|
+
export const surfaceListSourceSchema = Type.Object({
|
|
32
|
+
id: surfaceIdSchema,
|
|
33
|
+
type: Type.Literal('resource-list'),
|
|
34
|
+
resource: surfaceIdSchema,
|
|
35
|
+
pageSize: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
36
|
+
sorters: Type.Optional(Type.Array(Type.Object({
|
|
37
|
+
field: fieldSchema,
|
|
38
|
+
order: Type.Union([Type.Literal('asc'), Type.Literal('desc')]),
|
|
39
|
+
}, { additionalProperties: false }))),
|
|
40
|
+
filters: Type.Optional(Type.Array(surfaceFilterSchema)),
|
|
41
|
+
}, { additionalProperties: false });
|
|
42
|
+
export const surfaceOneSourceSchema = Type.Object({
|
|
43
|
+
id: surfaceIdSchema,
|
|
44
|
+
type: Type.Literal('resource-one'),
|
|
45
|
+
resource: surfaceIdSchema,
|
|
46
|
+
recordId: Type.Union([Type.String(), Type.Number()]),
|
|
47
|
+
}, { additionalProperties: false });
|
|
48
|
+
export const surfaceLayoutSchema = Type.Object({
|
|
49
|
+
type: Type.Literal('grid'),
|
|
50
|
+
columns: Type.Literal(12),
|
|
51
|
+
gap: Type.Optional(Type.Union([Type.Literal('sm'), Type.Literal('md'), Type.Literal('lg')])),
|
|
52
|
+
}, { additionalProperties: false });
|
|
53
|
+
export const surfaceWidgetSchema = Type.Object({
|
|
54
|
+
id: surfaceIdSchema,
|
|
55
|
+
type: surfaceIdSchema,
|
|
56
|
+
props: Type.Record(Type.String(), Type.Unknown()),
|
|
57
|
+
binding: Type.Optional(Type.Object({
|
|
58
|
+
sourceId: surfaceIdSchema,
|
|
59
|
+
pointer: Type.Optional(Type.String()),
|
|
60
|
+
}, { additionalProperties: false })),
|
|
61
|
+
placement: Type.Optional(Type.Object({
|
|
62
|
+
columnSpan: Type.Optional(Type.Integer({ minimum: 1, maximum: 12 })),
|
|
63
|
+
}, { additionalProperties: false })),
|
|
64
|
+
}, { additionalProperties: false });
|
|
65
|
+
export const surfaceSpecSchema = Type.Object({
|
|
66
|
+
schemaVersion: Type.String(),
|
|
67
|
+
catalogVersion: Type.String({ minLength: 1 }),
|
|
68
|
+
surfaceId: surfaceIdSchema,
|
|
69
|
+
title: Type.String({ minLength: 1, maxLength: SURFACE_LIMITS.maxTitleLength }),
|
|
70
|
+
layout: surfaceLayoutSchema,
|
|
71
|
+
dataSources: Type.Array(Type.Union([surfaceListSourceSchema, surfaceOneSourceSchema])),
|
|
72
|
+
widgets: Type.Array(surfaceWidgetSchema),
|
|
73
|
+
}, { additionalProperties: false });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SqliteSurfaceWorkflowStore } from './workflows/sqlite-store.js';
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { defineSurfaceAction } from './workflows/action-contracts.js';
|
|
2
|
+
export { createSurfaceWorkflowService } from './workflows/service.js';
|
|
3
|
+
export { SurfaceWorkflowError } from './workflows/types.js';
|
|
4
|
+
export type { SurfaceActionDescriptor, SurfaceRegisteredAction, SurfaceActionProposal, SurfaceActionStatus, SurfaceWorkflowContext, SurfaceWorkflowStore, SurfaceWorkflowTransaction, SurfaceWorkflowAuditEvent, SurfaceStoredRevision, SurfaceWorkflowOptions, } from './workflows/types.js';
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// This entry is server-only. Never re-export it from the DOM-free browser contract entry.
|
|
2
|
+
export { defineSurfaceAction } from './workflows/action-contracts.js';
|
|
3
|
+
export { createSurfaceWorkflowService } from './workflows/service.js';
|
|
4
|
+
export { SurfaceWorkflowError } from './workflows/types.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SurfaceDataSource, SurfaceResourcePolicy, SurfaceSourceDataState } from './types.js';
|
|
2
|
+
/** Only query/projection inputs belong in this key, never widget or layout data. */
|
|
3
|
+
export declare function snapshotSurfaceSource(source: SurfaceDataSource, policy: SurfaceResourcePolicy): {
|
|
4
|
+
source: SurfaceDataSource;
|
|
5
|
+
resourcePolicy: SurfaceResourcePolicy;
|
|
6
|
+
key: string;
|
|
7
|
+
};
|
|
8
|
+
export interface SurfaceSourceRequest {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
/** Values compare by Object.is; provider and permission identities are not serialized. */
|
|
11
|
+
readonly identity: readonly unknown[];
|
|
12
|
+
readonly load: (isCurrent: () => boolean) => Promise<SurfaceSourceDataState>;
|
|
13
|
+
/** Also guards prop changes that happen before Svelte's next effect flush. */
|
|
14
|
+
readonly isCurrent: () => boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function sameSourceIdentity(left: readonly unknown[], right: readonly unknown[]): boolean;
|
|
17
|
+
interface SourceCacheSink {
|
|
18
|
+
readonly set: (sourceId: string, state: SurfaceSourceDataState) => void;
|
|
19
|
+
readonly remove: (sourceId: string) => void;
|
|
20
|
+
readonly onError: (state: Extract<SurfaceSourceDataState, {
|
|
21
|
+
status: 'error';
|
|
22
|
+
}>) => void;
|
|
23
|
+
}
|
|
24
|
+
/** Per-renderer reconciliation, not a cross-user or cross-component data cache. */
|
|
25
|
+
export declare function createSurfaceSourceCache(sink: SourceCacheSink): {
|
|
26
|
+
reconcile: (requests: readonly SurfaceSourceRequest[], force?: true | string) => Promise<void>;
|
|
27
|
+
clear: () => void;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Only query/projection inputs belong in this key, never widget or layout data. */
|
|
2
|
+
export function snapshotSurfaceSource(source, policy) {
|
|
3
|
+
const snapshot = source.type === 'resource-one'
|
|
4
|
+
? { id: source.id, type: source.type, resource: source.resource, recordId: source.recordId }
|
|
5
|
+
: {
|
|
6
|
+
id: source.id,
|
|
7
|
+
type: source.type,
|
|
8
|
+
resource: source.resource,
|
|
9
|
+
pageSize: source.pageSize ?? 10,
|
|
10
|
+
sorters: (source.sorters ?? []).map(({ field, order }) => ({ field, order })),
|
|
11
|
+
filters: (source.filters ?? []).map((filter) => {
|
|
12
|
+
if (filter.operator === 'null' || filter.operator === 'nnull') {
|
|
13
|
+
return { field: filter.field, operator: filter.operator };
|
|
14
|
+
}
|
|
15
|
+
if (filter.operator === 'in' || filter.operator === 'nin') {
|
|
16
|
+
return { field: filter.field, operator: filter.operator, value: [...filter.value] };
|
|
17
|
+
}
|
|
18
|
+
return { ...filter };
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
const resourcePolicy = {
|
|
22
|
+
readFields: [...new Set(policy.readFields)].sort(),
|
|
23
|
+
filterFields: [...new Set(policy.filterFields ?? [])].sort(),
|
|
24
|
+
sortFields: [...new Set(policy.sortFields ?? [])].sort(),
|
|
25
|
+
allowGetOne: policy.allowGetOne ?? false,
|
|
26
|
+
...(policy.maxPageSize === undefined ? {} : { maxPageSize: policy.maxPageSize }),
|
|
27
|
+
};
|
|
28
|
+
// Tuples give deterministic keys even when JSON object property order changes.
|
|
29
|
+
// Array order (notably multi-column sorting) intentionally remains significant.
|
|
30
|
+
const query = snapshot.type === 'resource-one'
|
|
31
|
+
? [snapshot.type, snapshot.resource, snapshot.recordId]
|
|
32
|
+
: [
|
|
33
|
+
snapshot.type, snapshot.resource, snapshot.pageSize,
|
|
34
|
+
snapshot.sorters?.map(({ field, order }) => [field, order]),
|
|
35
|
+
snapshot.filters?.map((filter) => [
|
|
36
|
+
filter.field, filter.operator, 'value' in filter ? filter.value : null,
|
|
37
|
+
]),
|
|
38
|
+
];
|
|
39
|
+
return {
|
|
40
|
+
source: snapshot,
|
|
41
|
+
resourcePolicy,
|
|
42
|
+
key: JSON.stringify([query, resourcePolicy]),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function sameSourceIdentity(left, right) {
|
|
46
|
+
return left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
|
|
47
|
+
}
|
|
48
|
+
/** Per-renderer reconciliation, not a cross-user or cross-component data cache. */
|
|
49
|
+
export function createSurfaceSourceCache(sink) {
|
|
50
|
+
const entries = new Map();
|
|
51
|
+
let disposed = false;
|
|
52
|
+
function clear() {
|
|
53
|
+
const ids = [...entries.keys()];
|
|
54
|
+
entries.clear(); // Retire every pending owner before emitting state changes.
|
|
55
|
+
for (const id of ids)
|
|
56
|
+
sink.remove(id);
|
|
57
|
+
}
|
|
58
|
+
function start(request) {
|
|
59
|
+
const entry = { identity: [...request.identity], pending: Promise.resolve() };
|
|
60
|
+
entries.set(request.id, entry);
|
|
61
|
+
const current = () => {
|
|
62
|
+
try {
|
|
63
|
+
return !disposed && entries.get(request.id) === entry && request.isCurrent();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const retire = () => {
|
|
70
|
+
if (entries.get(request.id) !== entry)
|
|
71
|
+
return;
|
|
72
|
+
entries.delete(request.id);
|
|
73
|
+
sink.remove(request.id);
|
|
74
|
+
};
|
|
75
|
+
sink.set(request.id, { status: 'loading', sourceId: request.id });
|
|
76
|
+
entry.pending = (async () => {
|
|
77
|
+
if (!current()) {
|
|
78
|
+
retire();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
let state;
|
|
82
|
+
try {
|
|
83
|
+
state = await request.load(current);
|
|
84
|
+
}
|
|
85
|
+
catch (failure) {
|
|
86
|
+
state = {
|
|
87
|
+
status: 'error', sourceId: request.id,
|
|
88
|
+
error: {
|
|
89
|
+
code: 'provider_failed', sourceId: request.id,
|
|
90
|
+
message: failure instanceof Error ? failure.message : 'Data provider request failed',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (!current()) {
|
|
95
|
+
retire();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
sink.set(request.id, state);
|
|
99
|
+
if (state.status === 'error')
|
|
100
|
+
sink.onError(state);
|
|
101
|
+
})();
|
|
102
|
+
return entry.pending;
|
|
103
|
+
}
|
|
104
|
+
async function reconcile(requests, force) {
|
|
105
|
+
if (disposed)
|
|
106
|
+
return;
|
|
107
|
+
const activeIds = new Set(requests.map((request) => request.id));
|
|
108
|
+
for (const id of entries.keys()) {
|
|
109
|
+
if (!activeIds.has(id)) {
|
|
110
|
+
entries.delete(id);
|
|
111
|
+
sink.remove(id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const pending = [];
|
|
115
|
+
for (const request of requests) {
|
|
116
|
+
const existing = entries.get(request.id);
|
|
117
|
+
if (force === true || force === request.id || !existing
|
|
118
|
+
|| !sameSourceIdentity(existing.identity, request.identity)) {
|
|
119
|
+
pending.push(start(request));
|
|
120
|
+
}
|
|
121
|
+
else if (force === undefined) {
|
|
122
|
+
pending.push(existing.pending);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await Promise.all(pending);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
reconcile,
|
|
129
|
+
clear,
|
|
130
|
+
dispose() {
|
|
131
|
+
disposed = true;
|
|
132
|
+
clear();
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
@layer theme, base, components, utilities;
|
|
2
|
+
|
|
3
|
+
@layer theme.sv-surface{
|
|
4
|
+
.svadmin-surface-editor {
|
|
5
|
+
--svsurface-colors-background: var(--card, #ffffff);
|
|
6
|
+
--svsurface-colors-foreground: var(--foreground, #18181b);
|
|
7
|
+
--svsurface-colors-muted: var(--muted-foreground, #52525b);
|
|
8
|
+
--svsurface-colors-border: var(--border, #d4d4d8);
|
|
9
|
+
--svsurface-colors-primary: var(--primary, #2563eb);
|
|
10
|
+
--svsurface-colors-on-primary: var(--primary-foreground, #ffffff);
|
|
11
|
+
--svsurface-colors-destructive: var(--destructive, #b91c1c);
|
|
12
|
+
--svsurface-spacing-sm: 0.5rem;
|
|
13
|
+
--svsurface-spacing-md: 0.75rem;
|
|
14
|
+
--svsurface-spacing-lg: 1rem;
|
|
15
|
+
--svsurface-radii-control: 0.375rem;
|
|
16
|
+
--svsurface-radii-panel: 0.75rem;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@layer components.sv-surface{
|
|
21
|
+
@layer _base{
|
|
22
|
+
|
|
23
|
+
.svsurface-surface-editor {
|
|
24
|
+
border: 1px solid;
|
|
25
|
+
border-color: var(--svsurface-colors-border);
|
|
26
|
+
border-radius: var(--svsurface-radii-panel);
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
background-color: var(--svsurface-colors-background);
|
|
29
|
+
color: var(--svsurface-colors-foreground);
|
|
30
|
+
font-family: inherit;
|
|
31
|
+
width: 100%;
|
|
32
|
+
min-width: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.svsurface-surface-editor [data-part="title"] {
|
|
36
|
+
margin: 0;
|
|
37
|
+
font-size: 1rem;
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
overflow-wrap: anywhere;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.svsurface-surface-editor [data-part="status"] {
|
|
43
|
+
margin: 0.25rem 0 0;
|
|
44
|
+
color: var(--svsurface-colors-muted);
|
|
45
|
+
font-size: 0.875rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.svsurface-surface-editor [data-part="error"] {
|
|
49
|
+
border: 1px solid;
|
|
50
|
+
padding: var(--svsurface-spacing-md);
|
|
51
|
+
border-color: var(--svsurface-colors-destructive);
|
|
52
|
+
border-radius: var(--svsurface-radii-control);
|
|
53
|
+
color: var(--svsurface-colors-destructive);
|
|
54
|
+
overflow-wrap: anywhere;
|
|
55
|
+
margin-bottom: var(--svsurface-spacing-lg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.svsurface-surface-editor [data-part="toolbar"] {
|
|
59
|
+
gap: var(--svsurface-spacing-md);
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-wrap: wrap;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: space-between;
|
|
64
|
+
margin-bottom: var(--svsurface-spacing-lg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.svsurface-surface-editor [data-part="actions"] {
|
|
68
|
+
gap: var(--svsurface-spacing-sm);
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-wrap: wrap;
|
|
71
|
+
align-items: center;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.svsurface-surface-editor [data-part="viewport"] {
|
|
75
|
+
min-width: 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.svsurface-surface-editor-button {
|
|
79
|
+
border: 1px solid;
|
|
80
|
+
padding: 0.5rem 0.875rem;
|
|
81
|
+
font: inherit;
|
|
82
|
+
border-color: var(--svsurface-colors-border);
|
|
83
|
+
border-radius: var(--svsurface-radii-control);
|
|
84
|
+
appearance: none;
|
|
85
|
+
-webkit-appearance: none;
|
|
86
|
+
font-size: 0.875rem;
|
|
87
|
+
line-height: 1.25;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
white-space: normal;
|
|
90
|
+
min-height: 2.5rem;
|
|
91
|
+
max-width: 100%;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.svsurface-surface-editor-button:is(:disabled, [disabled], [data-disabled], [aria-disabled=true]) {
|
|
95
|
+
opacity: 0.5;
|
|
96
|
+
cursor: not-allowed;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.svsurface-surface-editor-button:is(:focus-visible, [data-focus-visible]) {
|
|
100
|
+
outline: 2px solid;
|
|
101
|
+
outline-color: var(--svsurface-colors-primary);
|
|
102
|
+
outline-offset: 2px;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.svsurface-surface-editor--density_comfortable {
|
|
107
|
+
padding: var(--svsurface-spacing-lg);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.svsurface-surface-editor--density_compact {
|
|
111
|
+
padding: var(--svsurface-spacing-md);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.svsurface-surface-editor-button--variant_secondary {
|
|
115
|
+
background-color: var(--svsurface-colors-background);
|
|
116
|
+
color: var(--svsurface-colors-foreground);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.svsurface-surface-editor-button--variant_primary {
|
|
120
|
+
border-color: var(--svsurface-colors-primary);
|
|
121
|
+
background-color: var(--svsurface-colors-primary);
|
|
122
|
+
color: var(--svsurface-colors-on-primary);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const editorClasses: {
|
|
2
|
+
readonly compact: "svsurface-surface-editor svsurface-surface-editor--density_compact";
|
|
3
|
+
readonly comfortable: "svsurface-surface-editor svsurface-surface-editor--density_comfortable";
|
|
4
|
+
};
|
|
5
|
+
export declare const editorButtonClasses: {
|
|
6
|
+
readonly secondary: "svsurface-surface-editor-button svsurface-surface-editor-button--variant_secondary";
|
|
7
|
+
readonly primary: "svsurface-surface-editor-button svsurface-surface-editor-button--variant_primary";
|
|
8
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// 本地有限变体,与 editor.css 一起维护。
|
|
2
|
+
export const editorClasses = {
|
|
3
|
+
"compact": "svsurface-surface-editor svsurface-surface-editor--density_compact",
|
|
4
|
+
"comfortable": "svsurface-surface-editor svsurface-surface-editor--density_comfortable"
|
|
5
|
+
};
|
|
6
|
+
export const editorButtonClasses = {
|
|
7
|
+
"secondary": "svsurface-surface-editor-button svsurface-surface-editor-button--variant_secondary",
|
|
8
|
+
"primary": "svsurface-surface-editor-button svsurface-surface-editor-button--variant_primary"
|
|
9
|
+
};
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* 独立的有限语义样式;保持旧 UI peer 和无宿主编译器的消费方式。 */
|
|
2
|
+
.svadmin-surface-metric__root,
|
|
3
|
+
.svadmin-surface-table__root { min-width: 0; }
|
|
4
|
+
.svadmin-surface-tone--neutral { --svadmin-surface-accent: var(--border); }
|
|
5
|
+
.svadmin-surface-tone--success { --svadmin-surface-accent: var(--success); }
|
|
6
|
+
.svadmin-surface-tone--warning { --svadmin-surface-accent: var(--warning); }
|
|
7
|
+
.svadmin-surface-tone--danger { --svadmin-surface-accent: var(--destructive); }
|
|
8
|
+
.svadmin-surface-tone--info { --svadmin-surface-accent: var(--info); }
|
|
9
|
+
.svadmin-surface-density--compact {
|
|
10
|
+
--svadmin-surface-padding: 0.75rem;
|
|
11
|
+
--svadmin-surface-cell-padding: 0.25rem;
|
|
12
|
+
--svadmin-surface-cell-font: 0.75rem;
|
|
13
|
+
--svadmin-metric-state-padding: 0.75rem;
|
|
14
|
+
--svadmin-metric-state-height: 4.5rem;
|
|
15
|
+
--svadmin-table-state-height: 6rem;
|
|
16
|
+
}
|
|
17
|
+
.svadmin-surface-density--comfortable {
|
|
18
|
+
--svadmin-surface-padding: 1.25rem;
|
|
19
|
+
--svadmin-surface-cell-padding: 0.5rem;
|
|
20
|
+
--svadmin-surface-cell-font: 0.875rem;
|
|
21
|
+
--svadmin-metric-state-padding: 1.25rem;
|
|
22
|
+
--svadmin-metric-state-height: 6rem;
|
|
23
|
+
--svadmin-table-state-height: 8rem;
|
|
24
|
+
}
|
|
25
|
+
.svadmin-surface-metric__card.svadmin-surface-metric__card {
|
|
26
|
+
border-inline-start: 3px solid var(--svadmin-surface-accent);
|
|
27
|
+
padding: var(--svadmin-surface-padding);
|
|
28
|
+
}
|
|
29
|
+
.svadmin-surface-metric__description,
|
|
30
|
+
.svadmin-surface-table__state { color: var(--muted-foreground); }
|
|
31
|
+
.svadmin-surface-metric__state {
|
|
32
|
+
--svadmin-metric-state-border-width: 3px;
|
|
33
|
+
--svadmin-metric-state-accent: var(--svadmin-surface-accent);
|
|
34
|
+
}
|
|
35
|
+
.svadmin-surface-table__header.svadmin-surface-density--compact,
|
|
36
|
+
.svadmin-surface-table__content.svadmin-surface-density--compact { padding-inline: 0.75rem; }
|
|
37
|
+
.svadmin-surface-table__header.svadmin-surface-density--comfortable,
|
|
38
|
+
.svadmin-surface-table__content.svadmin-surface-density--comfortable { padding-inline: 1rem; }
|
|
39
|
+
.svadmin-surface-table__head[data-slot="table-head"],
|
|
40
|
+
.svadmin-surface-table__cell[data-slot="table-cell"] {
|
|
41
|
+
padding-block: var(--svadmin-surface-cell-padding);
|
|
42
|
+
font-size: var(--svadmin-surface-cell-font);
|
|
43
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defaultSurfaceCatalog, defineSurfaceCatalog } from './catalog.js';
|
|
2
|
+
import SvarGridWidget from './components/SvarGridWidget.svelte';
|
|
3
|
+
import { svarGridDefinition } from './svar-schema.js';
|
|
4
|
+
/** 扩展独立目录版本,不改变默认目录,也不允许覆盖其他可信组件注册。 */
|
|
5
|
+
export function createSvarSurfaceCatalog(base = defaultSurfaceCatalog) {
|
|
6
|
+
return defineSurfaceCatalog({ version: `${base.version}+svar/v1`, widgets: [...base.widgets, { ...svarGridDefinition, component: SvarGridWidget }] });
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import type SvarDataGrid from '@svadmin/ui/components/SvarDataGrid.svelte';
|
|
3
|
+
export declare const SVAR_SURFACE_CONTEXT: unique symbol;
|
|
4
|
+
export interface SvarSurfaceContext {
|
|
5
|
+
readonly Grid: ComponentProps<typeof SvarDataGrid>['Grid'];
|
|
6
|
+
readonly Theme: ComponentProps<typeof SvarDataGrid>['Theme'];
|
|
7
|
+
/** 非敏感的宿主作用域版本;不会写入 Surface spec。 */
|
|
8
|
+
readonly scopeKey: string | number;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SVAR_SURFACE_CONTEXT = Symbol('svadmin.surface.svar');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Static } from '@sinclair/typebox';
|
|
2
|
+
import type { JsonObject, SurfaceWidgetDefinition } from './types.js';
|
|
3
|
+
/** JSON-only 契约;宿主引擎、回调、URL 和写操作不属于 AI 可以生成的属性。 */
|
|
4
|
+
export declare const svarGridPropsSchema: import("@sinclair/typebox").TObject<{
|
|
5
|
+
title: import("@sinclair/typebox").TString;
|
|
6
|
+
rowKey: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
7
|
+
height: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
8
|
+
density: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"compact">, import("@sinclair/typebox").TLiteral<"comfortable">]>>;
|
|
9
|
+
freezeLeft: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
10
|
+
freezeRight: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
11
|
+
emptyLabel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
12
|
+
columns: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
13
|
+
field: import("@sinclair/typebox").TString;
|
|
14
|
+
label: import("@sinclair/typebox").TString;
|
|
15
|
+
width: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
16
|
+
format: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"plain">, import("@sinclair/typebox").TLiteral<"number">, import("@sinclair/typebox").TLiteral<"percent">, import("@sinclair/typebox").TLiteral<"date">, import("@sinclair/typebox").TLiteral<"datetime">, import("@sinclair/typebox").TLiteral<"boolean">]>>;
|
|
17
|
+
}>>;
|
|
18
|
+
}>;
|
|
19
|
+
export type SvarGridProps = Static<typeof svarGridPropsSchema>;
|
|
20
|
+
export declare function decodeSvarGridProps(props: JsonObject): SvarGridProps;
|
|
21
|
+
export declare const svarGridDefinition: SurfaceWidgetDefinition;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { Value } from '@sinclair/typebox/value';
|
|
3
|
+
const field = Type.String({ minLength: 1, maxLength: 64, pattern: '^[A-Za-z][A-Za-z0-9_-]*$' });
|
|
4
|
+
/** JSON-only 契约;宿主引擎、回调、URL 和写操作不属于 AI 可以生成的属性。 */
|
|
5
|
+
export const svarGridPropsSchema = Type.Object({
|
|
6
|
+
title: Type.String({ minLength: 1, maxLength: 80 }),
|
|
7
|
+
rowKey: Type.Optional(field),
|
|
8
|
+
height: Type.Optional(Type.Integer({ minimum: 160, maximum: 1200 })),
|
|
9
|
+
density: Type.Optional(Type.Union([Type.Literal('compact'), Type.Literal('comfortable')])),
|
|
10
|
+
freezeLeft: Type.Optional(Type.Integer({ minimum: 0, maximum: 8 })),
|
|
11
|
+
freezeRight: Type.Optional(Type.Integer({ minimum: 0, maximum: 8 })),
|
|
12
|
+
emptyLabel: Type.Optional(Type.String({ minLength: 1, maxLength: 80 })),
|
|
13
|
+
columns: Type.Array(Type.Object({
|
|
14
|
+
field, label: Type.String({ minLength: 1, maxLength: 60 }),
|
|
15
|
+
width: Type.Optional(Type.Integer({ minimum: 60, maximum: 640 })),
|
|
16
|
+
format: Type.Optional(Type.Union([Type.Literal('plain'), Type.Literal('number'), Type.Literal('percent'), Type.Literal('date'), Type.Literal('datetime'), Type.Literal('boolean')])),
|
|
17
|
+
}, { additionalProperties: false }), { minItems: 1, maxItems: 16 }),
|
|
18
|
+
}, { additionalProperties: false });
|
|
19
|
+
export function decodeSvarGridProps(props) {
|
|
20
|
+
const value = Value.Decode(svarGridPropsSchema, props);
|
|
21
|
+
const keys = value.columns.map(column => column.field);
|
|
22
|
+
if (new Set(keys).size !== keys.length)
|
|
23
|
+
throw new Error('Duplicate grid columns');
|
|
24
|
+
if ((value.freezeLeft ?? 0) + (value.freezeRight ?? 0) > keys.length || (value.freezeRight ?? 0) >= keys.length) {
|
|
25
|
+
throw new Error('Frozen columns must leave a scrollable grid');
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
export const svarGridDefinition = {
|
|
30
|
+
type: 'data-grid', dataKind: 'items', propsSchema: svarGridPropsSchema,
|
|
31
|
+
getReferencedFields(props) {
|
|
32
|
+
const value = decodeSvarGridProps(props);
|
|
33
|
+
// 行标识也是一次字段读取,必须与显示列一样经过宿主白名单检查。
|
|
34
|
+
return [...new Set([value.rowKey ?? 'id', ...value.columns.map(column => column.field)])];
|
|
35
|
+
},
|
|
36
|
+
};
|