@77-zhike/app-sdk 0.2.0-d7b1.6 → 0.2.0-d7b1.7
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 +192 -0
- package/package.json +1 -1
- package/src/cli/project-init.js +1 -0
- package/src/client/destinations.d.ts +25 -0
- package/src/client/destinations.js +70 -0
- package/src/client/external-url.js +9 -0
- package/src/client/index.d.ts +186 -3
- package/src/client/index.js +45 -3
- package/src/client/interactions.d.ts +7 -3
- package/src/client/interactions.js +19 -2
- package/src/client/points.generated.js +48 -0
- package/src/forms/components.generated.js +2 -2
- package/src/forms/factory.js +14 -5
- package/src/forms/index.d.ts +63 -3
- package/src/forms/index.js +2 -0
- package/src/forms/options.d.ts +7 -2
- package/src/forms/options.js +8 -1
- package/src/forms/record-options.d.ts +28 -0
- package/src/forms/record-options.js +92 -0
- package/src/forms/schema.d.ts +50 -5
- package/src/forms/schema.js +105 -0
- package/src/forms/state.d.ts +28 -0
- package/src/forms/state.js +92 -0
- package/src/forms/validation.d.ts +9 -0
- package/src/forms/validation.js +83 -0
- package/src/forms/values.d.ts +5 -0
- package/src/forms/values.js +38 -0
- package/src/server/index.d.ts +15 -0
- package/src/server/index.js +20 -0
- package/src/toolchain/real-build.js +29 -3
- package/src/toolchain/source-discovery.js +202 -16
- package/src/ui/components.generated.js +3 -1
- package/src/ui/factory.js +50 -3
- package/src/ui/index.d.ts +157 -8
- package/src/ui/index.js +1 -1
- package/src/ui/validation.js +38 -0
|
@@ -8,8 +8,13 @@ import { ZhikeCliError } from "../errors.js";
|
|
|
8
8
|
|
|
9
9
|
const KEY_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
10
10
|
const RECORD_OBJECT_PATTERN = /^[a-z][a-z0-9_]{0,106}$/;
|
|
11
|
+
const RECORD_EXTENSION_OBJECT_PATTERN = /^(?:accounts|contacts|opportunities|custom_[a-z0-9_]{1,100})$/;
|
|
11
12
|
const RECORD_ACTION_LABEL_MAX_LENGTH = 80;
|
|
12
13
|
const RECORD_ACTION_OBJECTS_MAX_LENGTH = 32;
|
|
14
|
+
const RECORD_ACTION_TYPES = new Set(["record-action", "object-action", "bulk-record-action"]);
|
|
15
|
+
const RECORD_COMPONENT_TYPES = new Set(["record-widget", "record-section", "record-tab"]);
|
|
16
|
+
const PAGE_NAME_MAX_LENGTH = 80;
|
|
17
|
+
const PAGE_VARIANTS = new Set(["full", "centered"]);
|
|
13
18
|
const SDK_ICON_NAMES = new Set(sdkIconNames);
|
|
14
19
|
const SERVER_MODULE_PATTERN = /^server\/functions\/[a-z0-9][a-z0-9/-]*$/;
|
|
15
20
|
const LIFECYCLE_EVENTS = new Set([
|
|
@@ -86,6 +91,9 @@ export async function discoverAppSources(projectDirectory) {
|
|
|
86
91
|
const extensionMatch = file.relativePath.match(
|
|
87
92
|
/^app\/extensions\/([a-z][a-z0-9-]{0,63})\/extension\.tsx$/,
|
|
88
93
|
);
|
|
94
|
+
const pageMatch = file.relativePath.match(
|
|
95
|
+
/^app\/pages\/([a-z][a-z0-9-]{0,63})\/page\.tsx$/,
|
|
96
|
+
);
|
|
89
97
|
const lifecycleMatch = file.relativePath.match(
|
|
90
98
|
/^app\/events\/(app-installed|app-uninstalled|connection-added|connection-removed)\.event\.ts$/,
|
|
91
99
|
);
|
|
@@ -103,6 +111,14 @@ export async function discoverAppSources(projectDirectory) {
|
|
|
103
111
|
);
|
|
104
112
|
registerUniqueKey(globalKeys, definition.key, file.relativePath);
|
|
105
113
|
lifecycleEvents.push(definition);
|
|
114
|
+
} else if (pageMatch) {
|
|
115
|
+
const definition = parseWorkspacePage(
|
|
116
|
+
sourceFile,
|
|
117
|
+
file.relativePath,
|
|
118
|
+
pageMatch[1],
|
|
119
|
+
);
|
|
120
|
+
registerUniqueKey(globalKeys, definition.key, file.relativePath);
|
|
121
|
+
frontendExtensions.push(definition);
|
|
106
122
|
} else if (extensionMatch) {
|
|
107
123
|
const definition = parseFrontendExtension(
|
|
108
124
|
sourceFile,
|
|
@@ -221,16 +237,21 @@ function parseFrontendExtension(sourceFile, relativePath, directoryKey) {
|
|
|
221
237
|
const key = requireStringProperty(object, "key", relativePath);
|
|
222
238
|
const type = requireStringProperty(object, "type", relativePath);
|
|
223
239
|
const point = frontendPoint(type);
|
|
224
|
-
if (!point) throw toolchainError("zhike_frontend_extension_type_unsupported", `${relativePath}: 扩展类型尚未开放`);
|
|
225
|
-
const handlerName = point.kind === "view" ? "component" : (point.callbackName || "handler");
|
|
240
|
+
if (!point || type === "page") throw toolchainError("zhike_frontend_extension_type_unsupported", `${relativePath}: 扩展类型尚未开放`);
|
|
241
|
+
const handlerName = point.kind === "view" ? (point.componentName || "component") : (point.callbackName || "handler");
|
|
226
242
|
const forbiddenNames = point.kind === "view"
|
|
227
|
-
? ["handler", "onTrigger"]
|
|
228
|
-
: ["component", ...(handlerName === "handler" ? ["onTrigger"] : ["handler"])];
|
|
243
|
+
? ["component", "Widget", "Section", "Tab", "Page", "handler", "onTrigger"].filter(name => name !== handlerName)
|
|
244
|
+
: ["component", "Widget", "Section", "Tab", "Page", ...(handlerName === "handler" ? ["onTrigger"] : ["handler"])];
|
|
229
245
|
if (forbiddenNames.some(name => findProperty(object, name))) {
|
|
230
246
|
throw toolchainError("zhike_frontend_definition_mixed", `${relativePath}: component 与 handler 不得混填`);
|
|
231
247
|
}
|
|
232
248
|
const property = findProperty(object, handlerName);
|
|
233
|
-
if (
|
|
249
|
+
if (
|
|
250
|
+
point.kind === "view" &&
|
|
251
|
+
property &&
|
|
252
|
+
((ts.isPropertyAssignment(property) && ts.isIdentifier(property.initializer)) ||
|
|
253
|
+
ts.isShorthandPropertyAssignment(property))
|
|
254
|
+
) {
|
|
234
255
|
// 引用的组件由 TypeScript 检查可调用性;控制面不执行组件。
|
|
235
256
|
} else {
|
|
236
257
|
requireFunctionProperty(object, handlerName, relativePath);
|
|
@@ -241,7 +262,7 @@ function parseFrontendExtension(sourceFile, relativePath, directoryKey) {
|
|
|
241
262
|
`${relativePath}: key 必须与扩展目录一致`,
|
|
242
263
|
);
|
|
243
264
|
}
|
|
244
|
-
if (type
|
|
265
|
+
if (RECORD_ACTION_TYPES.has(type)) {
|
|
245
266
|
return Object.freeze({
|
|
246
267
|
key,
|
|
247
268
|
type,
|
|
@@ -250,9 +271,90 @@ function parseFrontendExtension(sourceFile, relativePath, directoryKey) {
|
|
|
250
271
|
...parseRecordActionMetadata(object, relativePath),
|
|
251
272
|
});
|
|
252
273
|
}
|
|
274
|
+
if (RECORD_COMPONENT_TYPES.has(type)) {
|
|
275
|
+
return Object.freeze({
|
|
276
|
+
key,
|
|
277
|
+
type,
|
|
278
|
+
entry: key,
|
|
279
|
+
relativePath,
|
|
280
|
+
...parseRecordComponentMetadata(object, relativePath, handlerName),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
253
283
|
return Object.freeze({ key, type, entry: key, relativePath });
|
|
254
284
|
}
|
|
255
285
|
|
|
286
|
+
function parseWorkspacePage(sourceFile, relativePath, slug) {
|
|
287
|
+
requireSdkNamedImport(
|
|
288
|
+
sourceFile,
|
|
289
|
+
relativePath,
|
|
290
|
+
"@77-zhike/app-sdk/client",
|
|
291
|
+
"definePage",
|
|
292
|
+
);
|
|
293
|
+
const call = requireDefaultExportCall(sourceFile, relativePath, "definePage");
|
|
294
|
+
const object = requireObjectLiteral(call.arguments[0], relativePath);
|
|
295
|
+
assertOnlyProperties(
|
|
296
|
+
object,
|
|
297
|
+
["name", "order", "variant", "Page", "showInNavigation"],
|
|
298
|
+
relativePath,
|
|
299
|
+
"zhike_page_definition_invalid",
|
|
300
|
+
"Page definition 包含不支持的字段",
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const rawName = requireStringProperty(object, "name", relativePath);
|
|
304
|
+
const name = rawName.trim();
|
|
305
|
+
if (
|
|
306
|
+
[...name].length === 0 ||
|
|
307
|
+
[...name].length > PAGE_NAME_MAX_LENGTH ||
|
|
308
|
+
/\p{Cc}/u.test(name)
|
|
309
|
+
) {
|
|
310
|
+
throw toolchainError(
|
|
311
|
+
"zhike_page_name_invalid",
|
|
312
|
+
`${relativePath}: name trim 后必须是 1~${PAGE_NAME_MAX_LENGTH} 个非控制字符`,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const variant = requireStringProperty(object, "variant", relativePath);
|
|
316
|
+
if (!PAGE_VARIANTS.has(variant)) {
|
|
317
|
+
throw toolchainError(
|
|
318
|
+
"zhike_page_variant_invalid",
|
|
319
|
+
`${relativePath}: variant 必须是 full 或 centered`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const orderProperty = findProperty(object, "order");
|
|
323
|
+
const order = orderProperty === undefined
|
|
324
|
+
? undefined
|
|
325
|
+
: requireSafeIntegerProperty(orderProperty, relativePath);
|
|
326
|
+
const visibilityProperty = findProperty(object, "showInNavigation");
|
|
327
|
+
let showInNavigation;
|
|
328
|
+
if (visibilityProperty !== undefined) {
|
|
329
|
+
if (!ts.isPropertyAssignment(visibilityProperty)
|
|
330
|
+
|| ![ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword].includes(visibilityProperty.initializer.kind)) {
|
|
331
|
+
throw toolchainError("zhike_page_visibility_invalid", `${relativePath}: showInNavigation 必须是静态布尔值`);
|
|
332
|
+
}
|
|
333
|
+
showInNavigation = visibilityProperty.initializer.kind === ts.SyntaxKind.TrueKeyword;
|
|
334
|
+
}
|
|
335
|
+
const pageProperty = findProperty(object, "Page");
|
|
336
|
+
if (
|
|
337
|
+
!pageProperty ||
|
|
338
|
+
!ts.isPropertyAssignment(pageProperty) ||
|
|
339
|
+
!(ts.isIdentifier(pageProperty.initializer) || isFunctionLike(pageProperty.initializer))
|
|
340
|
+
) {
|
|
341
|
+
throw toolchainError(
|
|
342
|
+
"zhike_page_component_invalid",
|
|
343
|
+
`${relativePath}: Page 必须是 React component 标识符或函数`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return Object.freeze({
|
|
347
|
+
key: slug,
|
|
348
|
+
type: "page",
|
|
349
|
+
entry: slug,
|
|
350
|
+
relativePath,
|
|
351
|
+
name,
|
|
352
|
+
...(order === undefined ? {} : { order }),
|
|
353
|
+
...(showInNavigation === undefined ? {} : { showInNavigation }),
|
|
354
|
+
variant,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
256
358
|
function parseRecordActionMetadata(object, relativePath) {
|
|
257
359
|
assertOnlyProperties(object, ["key", "type", "label", "icon", "objects", "onTrigger"], relativePath);
|
|
258
360
|
const label = requireStringProperty(object, "label", relativePath).trim();
|
|
@@ -283,9 +385,48 @@ function parseRecordActionMetadata(object, relativePath) {
|
|
|
283
385
|
};
|
|
284
386
|
}
|
|
285
387
|
|
|
286
|
-
function
|
|
388
|
+
function parseRecordComponentMetadata(object, relativePath, componentName) {
|
|
389
|
+
assertOnlyProperties(
|
|
390
|
+
object,
|
|
391
|
+
["key", "type", "label", "objects", componentName],
|
|
392
|
+
relativePath,
|
|
393
|
+
"zhike_record_component_definition_invalid",
|
|
394
|
+
"Record Page component definition 包含不支持的字段",
|
|
395
|
+
);
|
|
396
|
+
const label = requireStringProperty(object, "label", relativePath).trim();
|
|
397
|
+
if (
|
|
398
|
+
[...label].length === 0 ||
|
|
399
|
+
[...label].length > RECORD_ACTION_LABEL_MAX_LENGTH ||
|
|
400
|
+
/\p{Cc}/u.test(label)
|
|
401
|
+
) {
|
|
402
|
+
throw toolchainError(
|
|
403
|
+
"zhike_record_component_label_invalid",
|
|
404
|
+
`${relativePath}: label 必须是 1~${RECORD_ACTION_LABEL_MAX_LENGTH} 个非控制字符`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const objectsProperty = findProperty(object, "objects");
|
|
408
|
+
const objects = objectsProperty
|
|
409
|
+
? requireRecordObjects(
|
|
410
|
+
objectsProperty,
|
|
411
|
+
relativePath,
|
|
412
|
+
"zhike_record_component_objects_invalid",
|
|
413
|
+
true,
|
|
414
|
+
)
|
|
415
|
+
: null;
|
|
416
|
+
return {
|
|
417
|
+
label,
|
|
418
|
+
...(objects === null ? {} : { objects }),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function requireRecordObjects(
|
|
423
|
+
property,
|
|
424
|
+
relativePath,
|
|
425
|
+
errorCode = "zhike_record_action_objects_invalid",
|
|
426
|
+
rejectDuplicates = false,
|
|
427
|
+
) {
|
|
287
428
|
if (!ts.isPropertyAssignment(property)) {
|
|
288
|
-
throw toolchainError(
|
|
429
|
+
throw toolchainError(errorCode, `${relativePath}: objects 必须是字符串字面量或字符串字面量数组`);
|
|
289
430
|
}
|
|
290
431
|
const initializer = property.initializer;
|
|
291
432
|
const values = ts.isStringLiteral(initializer)
|
|
@@ -293,7 +434,7 @@ function requireRecordObjects(property, relativePath) {
|
|
|
293
434
|
: ts.isArrayLiteralExpression(initializer)
|
|
294
435
|
? initializer.elements.map(item => {
|
|
295
436
|
if (!ts.isStringLiteral(item)) {
|
|
296
|
-
throw toolchainError(
|
|
437
|
+
throw toolchainError(errorCode, `${relativePath}: objects 必须是字符串字面量数组`);
|
|
297
438
|
}
|
|
298
439
|
return item.text;
|
|
299
440
|
})
|
|
@@ -302,17 +443,24 @@ function requireRecordObjects(property, relativePath) {
|
|
|
302
443
|
values === null ||
|
|
303
444
|
values.length === 0 ||
|
|
304
445
|
values.length > RECORD_ACTION_OBJECTS_MAX_LENGTH ||
|
|
305
|
-
values.
|
|
446
|
+
(rejectDuplicates && new Set(values).size !== values.length) ||
|
|
447
|
+
values.some(value => !RECORD_OBJECT_PATTERN.test(value) || !RECORD_EXTENSION_OBJECT_PATTERN.test(value))
|
|
306
448
|
) {
|
|
307
449
|
throw toolchainError(
|
|
308
|
-
|
|
309
|
-
`${relativePath}: objects
|
|
450
|
+
errorCode,
|
|
451
|
+
`${relativePath}: objects 只支持 accounts、contacts、opportunities 或 custom_*,且必须包含 1~${RECORD_ACTION_OBJECTS_MAX_LENGTH} 个合法公开对象标识`,
|
|
310
452
|
);
|
|
311
453
|
}
|
|
312
|
-
return Object.freeze([...new Set(values)]);
|
|
454
|
+
return Object.freeze(rejectDuplicates ? values : [...new Set(values)]);
|
|
313
455
|
}
|
|
314
456
|
|
|
315
|
-
function assertOnlyProperties(
|
|
457
|
+
function assertOnlyProperties(
|
|
458
|
+
object,
|
|
459
|
+
allowed,
|
|
460
|
+
relativePath,
|
|
461
|
+
code = "zhike_frontend_definition_unknown_property",
|
|
462
|
+
message = "Frontend Extension 包含不支持的字段",
|
|
463
|
+
) {
|
|
316
464
|
const allowedNames = new Set(allowed);
|
|
317
465
|
for (const property of object.properties) {
|
|
318
466
|
const name = property.name && (
|
|
@@ -320,8 +468,8 @@ function assertOnlyProperties(object, allowed, relativePath) {
|
|
|
320
468
|
) ? property.name.text : null;
|
|
321
469
|
if (!name || !allowedNames.has(name)) {
|
|
322
470
|
throw toolchainError(
|
|
323
|
-
|
|
324
|
-
`${relativePath}:
|
|
471
|
+
code,
|
|
472
|
+
`${relativePath}: ${message}`,
|
|
325
473
|
);
|
|
326
474
|
}
|
|
327
475
|
}
|
|
@@ -571,6 +719,20 @@ function rejectUnsupportedPaths(files) {
|
|
|
571
719
|
`${path}: Frontend Extension 必须使用约定目录`,
|
|
572
720
|
);
|
|
573
721
|
}
|
|
722
|
+
if (/^app\/pages\/[^/]+\/page\.tsx$/.test(path)) {
|
|
723
|
+
const slug = path.split("/")[2];
|
|
724
|
+
if (!KEY_PATTERN.test(slug)) {
|
|
725
|
+
throw toolchainError(
|
|
726
|
+
"zhike_page_slug_invalid",
|
|
727
|
+
`${path}: Page slug 必须是最多 64 字符的 lowercase kebab-case`,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
} else if (path.startsWith("app/pages/") && /\/page\.(?:ts|tsx)$/.test(path)) {
|
|
731
|
+
throw toolchainError(
|
|
732
|
+
"zhike_page_location_invalid",
|
|
733
|
+
`${path}: Page 必须位于 src/app/pages/<slug>/page.tsx 一级目录`,
|
|
734
|
+
);
|
|
735
|
+
}
|
|
574
736
|
if (
|
|
575
737
|
path.startsWith("app/webhooks/") &&
|
|
576
738
|
path.endsWith(".ts") &&
|
|
@@ -624,6 +786,30 @@ function requireStringArrayProperty(object, name, relativePath) {
|
|
|
624
786
|
});
|
|
625
787
|
}
|
|
626
788
|
|
|
789
|
+
function requireSafeIntegerProperty(property, relativePath) {
|
|
790
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
791
|
+
throw toolchainError(
|
|
792
|
+
"zhike_page_order_invalid",
|
|
793
|
+
`${relativePath}: order 必须是安全整数字面量`,
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
const initializer = property.initializer;
|
|
797
|
+
const value = ts.isNumericLiteral(initializer)
|
|
798
|
+
? Number(initializer.text)
|
|
799
|
+
: ts.isPrefixUnaryExpression(initializer) &&
|
|
800
|
+
initializer.operator === ts.SyntaxKind.MinusToken &&
|
|
801
|
+
ts.isNumericLiteral(initializer.operand)
|
|
802
|
+
? -Number(initializer.operand.text)
|
|
803
|
+
: Number.NaN;
|
|
804
|
+
if (!Number.isSafeInteger(value)) {
|
|
805
|
+
throw toolchainError(
|
|
806
|
+
"zhike_page_order_invalid",
|
|
807
|
+
`${relativePath}: order 必须是安全整数字面量`,
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
return value;
|
|
811
|
+
}
|
|
812
|
+
|
|
627
813
|
function requireFunctionProperty(object, name, relativePath, { requireAsync = false } = {}) {
|
|
628
814
|
const property = findProperty(object, name);
|
|
629
815
|
const functionNode =
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
// 此文件由 scripts/generate-components.mjs 生成,请修改产品能力声明。
|
|
2
|
-
export const componentTypes = Object.freeze(["
|
|
2
|
+
export const componentTypes = Object.freeze(["Icon","Stack","SplitLayout","SplitLayoutMain","SplitLayoutAside","SplitLayoutActions","Section","Banner","Badge","LoadingState","Card","Grid","EmptyState","Link","Button","Switch","LookupCell","Tabs","TabsList","TabsTrigger","TabsContent","Table","TableHeader","TableBody","TableRow","TableCell","Avatar","DescriptionList","DescriptionListItem","DescriptionListMenu","DescriptionListMenuItem","Divider","Json","ExternalLink","StatusBadge","TextBlock","TypographyTitle","TypographyBody","TypographyCaption","TableHeaderCell","WidgetTextWidget","WidgetTitle","WidgetTextPrimary","WidgetTextSecondary","WidgetLoading","WidgetDecoration"]);
|
|
3
|
+
export const legacyComponentTypes = Object.freeze(["Text","Icon","Stack","SplitLayout","SplitLayoutMain","SplitLayoutAside","SplitLayoutActions","Section","Banner","Badge","LoadingState","Card","Grid","EmptyState","Link","Button","Switch","LookupCell","Tabs","TabsList","TabsTrigger","TabsContent","Table","TableHeader","TableBody","TableRow","TableCell","Avatar","DescriptionList","DescriptionListItem","DescriptionListMenu","DescriptionListMenuItem","Divider","Json","ExternalLink","StatusBadge","TextBlock","TypographyTitle","TypographyBody","TypographyCaption","TableHeaderCell","WidgetTextWidget","WidgetTitle","WidgetTextPrimary","WidgetTextSecondary","WidgetLoading","WidgetDecoration"]);
|
|
4
|
+
export const badgeColors = Object.freeze(["amber","blue","cyan","green","grey","lavender","lime","orange","pink","purple","red","yellow"]);
|
package/src/ui/factory.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
|
-
import { componentTypes } from './components.generated.js';
|
|
1
|
+
import { componentTypes, legacyComponentTypes } from './components.generated.js';
|
|
2
2
|
import { useNormalizedOptions } from '../forms/normalize-options.js';
|
|
3
|
+
import { assertExternalUrl } from '../client/external-url.js';
|
|
4
|
+
import { assertBannerActions, assertJsonValue } from './validation.js';
|
|
3
5
|
|
|
4
|
-
export function createUiComponents(React) {
|
|
6
|
+
export function createUiComponents(React, { legacy = false, useOwner = () => null, openExternal = () => Promise.reject(new Error('runtime_host_interaction_unavailable')) } = {}) {
|
|
5
7
|
const TabValue = React.createContext(null);
|
|
6
|
-
|
|
8
|
+
const components = Object.fromEntries((legacy ? legacyComponentTypes : componentTypes).map(type => [type, function RemoteComponent(props) {
|
|
9
|
+
if (!legacy && type === 'TextBlock') return React.createElement(type, { ...props, align: props.align ?? 'left' });
|
|
10
|
+
if (!legacy && type === 'Badge' && props.tone !== undefined) throw new Error('Badge.tone 已移除,请使用 StatusBadge');
|
|
11
|
+
if (type === 'Avatar') {
|
|
12
|
+
const { src, ...rest } = props;
|
|
13
|
+
return React.createElement(type, { ...rest, ...(src == null ? {} : { src: assertExternalUrl(src) }) });
|
|
14
|
+
}
|
|
15
|
+
if (type === 'Json') {
|
|
16
|
+
assertJsonValue(props.data);
|
|
17
|
+
return React.createElement(type, props);
|
|
18
|
+
}
|
|
19
|
+
if (type === 'Banner') {
|
|
20
|
+
const { actions, onAction, ...rest } = props;
|
|
21
|
+
if (onAction !== undefined || (actions !== undefined && !Array.isArray(actions))) throw new Error('runtime_ui_contract_invalid');
|
|
22
|
+
if (!actions?.length) return React.createElement(type, rest);
|
|
23
|
+
// 显式复制 metadata 和 handler;调用期间 App 原地修改数组不能重定向旧动作。
|
|
24
|
+
const data = actions.map(action => ({ label: action.label, variant: action.variant }));
|
|
25
|
+
const handlers = actions.map(action => action.onTrigger);
|
|
26
|
+
assertBannerActions(data);
|
|
27
|
+
if (handlers.some(handler => typeof handler !== 'function')) throw new Error('runtime_ui_contract_invalid');
|
|
28
|
+
return React.createElement(type, { ...rest, actions: data, onAction(index) {
|
|
29
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= handlers.length) throw new Error('runtime_ui_contract_invalid');
|
|
30
|
+
return handlers[index]();
|
|
31
|
+
} });
|
|
32
|
+
}
|
|
33
|
+
if (type === 'ExternalLink') {
|
|
34
|
+
const owner = useOwner();
|
|
35
|
+
const { href: input, onOpen, ...rest } = props;
|
|
36
|
+
if (onOpen !== undefined) throw new Error('runtime_ui_contract_invalid');
|
|
37
|
+
const href = assertExternalUrl(input);
|
|
38
|
+
return React.createElement(type, { ...rest, href, onOpen: () => openExternal(owner, href) });
|
|
39
|
+
}
|
|
7
40
|
if (type === 'Tabs') return React.createElement(TabValue.Provider, { value: props.value }, React.createElement(type, props));
|
|
8
41
|
if (type === 'TabsContent') {
|
|
9
42
|
const value = React.useContext(TabValue);
|
|
@@ -16,4 +49,18 @@ export function createUiComponents(React) {
|
|
|
16
49
|
}
|
|
17
50
|
return React.createElement(type, props);
|
|
18
51
|
}]));
|
|
52
|
+
Object.assign(components.DescriptionList, { Item: components.DescriptionListItem, Menu: components.DescriptionListMenu });
|
|
53
|
+
components.DescriptionListMenu.Item = components.DescriptionListMenuItem;
|
|
54
|
+
Object.assign(components.Table, { Header: components.TableHeader, HeaderCell: components.TableHeaderCell, Body: components.TableBody, Row: components.TableRow, Cell: components.TableCell });
|
|
55
|
+
Object.assign(components.TableCell, { Avatar: components.Avatar, Badge: components.Badge });
|
|
56
|
+
Object.assign(components.SplitLayout, { Main: components.SplitLayoutMain, Aside: components.SplitLayoutAside, Actions: components.SplitLayoutActions });
|
|
57
|
+
components.Typography = Object.freeze({ Title: components.TypographyTitle, Body: components.TypographyBody, Caption: components.TypographyCaption });
|
|
58
|
+
components.Widget = Object.freeze({
|
|
59
|
+
TextWidget: components.WidgetTextWidget,
|
|
60
|
+
Title: components.WidgetTitle,
|
|
61
|
+
Text: Object.freeze({ Primary: components.WidgetTextPrimary, Secondary: components.WidgetTextSecondary }),
|
|
62
|
+
Loading: components.WidgetLoading,
|
|
63
|
+
Decoration: components.WidgetDecoration,
|
|
64
|
+
});
|
|
65
|
+
return components;
|
|
19
66
|
}
|