diff --git a/packages/arco-lib/src/sunmao-helper.ts b/packages/arco-lib/src/sunmao-helper.ts index a0e33dcc..d7dd6136 100644 --- a/packages/arco-lib/src/sunmao-helper.ts +++ b/packages/arco-lib/src/sunmao-helper.ts @@ -36,10 +36,10 @@ export const getComponentProps = < TState, TMethods, TSlots extends Record, - KStyleSlot extends string, - KEvent extends string + KStyleSlots extends ReadonlyArray, + KEvents extends ReadonlyArray >( - props: T & ComponentImplProps + props: T & ComponentImplProps ) => { const { /* eslint-disable @typescript-eslint/no-unused-vars */ diff --git a/packages/chakra-ui-lib/src/components/Table/spec.ts b/packages/chakra-ui-lib/src/components/Table/spec.ts index 6b7929af..c56f0612 100644 --- a/packages/chakra-ui-lib/src/components/Table/spec.ts +++ b/packages/chakra-ui-lib/src/components/Table/spec.ts @@ -24,7 +24,6 @@ const PropsSpec = Type.Object({ }); export const implementTable = implementRuntimeComponent({ - kind: 'Component', version: 'chakra_ui/v1', metadata: { name: 'table', diff --git a/packages/core/package.json b/packages/core/package.json index 26350826..8797168f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -34,6 +34,7 @@ "dependencies": {}, "devDependencies": { "@swc/core": "^1.2.121", + "@sinclair/typebox": "^0.21.2", "@types/jest": "^26.0.23", "@types/json-schema": "^7.0.7", "@typescript-eslint/eslint-plugin": "^4.28.1", diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 395cdc82..6210f9a4 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -1,59 +1,83 @@ import { JSONSchema7 } from 'json-schema'; +import type { Static } from '@sinclair/typebox'; import { parseVersion, Version } from './version'; import { ComponentMetadata } from './metadata'; import { MethodSchema } from './method'; import { SlotSpec } from './slot'; +type DeepPartial = T extends Record | Record[] + ? Partial<{ + [K in keyof T]: DeepPartial; + }> + : T; + type ComponentSpec< - KMethodName extends string, - KStyleSlot extends string, - KSlot extends string, - KEvent extends string + KProperties extends JSONSchema7, + KState extends JSONSchema7, + KMethods extends Record, + KStyleSlots extends ReadonlyArray, + KSlots extends Record, + KEvents extends ReadonlyArray > = { - properties: JSONSchema7; - state: JSONSchema7; - methods: Record; - styleSlots: ReadonlyArray; - slots: Record; - events: ReadonlyArray; + properties: KProperties; + state: KState; + methods: KMethods; + styleSlots: KStyleSlots; + slots: KSlots; + events: KEvents; }; export type Component< - KMethodName extends string, - KStyleSlot extends string, - KSlot extends string, - KEvent extends string + KProperties extends JSONSchema7, + KState extends JSONSchema7, + KMethods extends Record, + KStyleSlots extends ReadonlyArray, + KSlots extends Record, + KEvents extends ReadonlyArray > = { version: string; kind: 'Component'; - metadata: ComponentMetadata; - spec: ComponentSpec; + metadata: ComponentMetadata>>; + spec: ComponentSpec; }; export type RuntimeComponent< - KMethodName extends string, - KStyleSlot extends string, - KSlot extends string, - KEvent extends string -> = Component & { + KProperties extends JSONSchema7, + KState extends JSONSchema7, + KMethods extends Record, + KStyleSlots extends ReadonlyArray, + KSlots extends Record, + KEvents extends ReadonlyArray +> = Component & { parsedVersion: Version; }; export type CreateComponentOptions< - KMethodName extends string, - KStyleSlot extends string, - KSlot extends string, - KEvent extends string -> = Omit, 'kind'>; + KProperties extends JSONSchema7, + KState extends JSONSchema7, + KMethods extends Record, + KStyleSlots extends ReadonlyArray, + KSlots extends Record, + KEvents extends ReadonlyArray +> = Omit, 'kind'>; export function createComponent< - KMethodName extends string, - KStyleSlot extends string, - KSlot extends string, - KEvent extends string + KProperties extends JSONSchema7, + KState extends JSONSchema7, + KMethods extends Record, + KStyleSlots extends ReadonlyArray, + KSlots extends Record, + KEvents extends ReadonlyArray >( - options: CreateComponentOptions -): RuntimeComponent { + options: CreateComponentOptions< + KProperties, + KState, + KMethods, + KStyleSlots, + KSlots, + KEvents + > +): RuntimeComponent { return { ...options, kind: 'Component', diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index af389527..6bef151f 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -1,10 +1,11 @@ -import { JSONSchema7Object } from 'json-schema'; - -export type Metadata> = { +export type Metadata< + TAnnotations = Record, + TExample = Record +> = { name: string; description?: string; annotations?: Record & TAnnotations; - exampleProperties?: JSONSchema7Object; + exampleProperties?: TExample; }; type ComponentCategory = @@ -15,7 +16,10 @@ type ComponentCategory = | 'Advance' | undefined; -export type ComponentMetadata = Metadata<{ category?: ComponentCategory }> & { +export type ComponentMetadata> = Metadata< + { category?: ComponentCategory }, + TExample +> & { // TODO:(yanzhen): move to annotations displayName: string; icon?: string; diff --git a/packages/editor/src/AppModel/ComponentModel.ts b/packages/editor/src/AppModel/ComponentModel.ts index 900affcb..6663e016 100644 --- a/packages/editor/src/AppModel/ComponentModel.ts +++ b/packages/editor/src/AppModel/ComponentModel.ts @@ -6,7 +6,12 @@ import { CoreTraitName, AnyTypePlaceholder, } from '@sunmao-ui/shared'; -import { ComponentSchema, MethodSchema, RuntimeComponent } from '@sunmao-ui/core'; +import { + ComponentSchema, + MethodSchema, + RuntimeComponent, + SlotSpec, +} from '@sunmao-ui/core'; import { genComponent, genTrait } from './utils'; import { ComponentId, @@ -32,10 +37,12 @@ const DynamicStateTrait = [ ]; type ComponentSpecModel = RuntimeComponent< - MethodName, - StyleSlotName, - SlotName, - EventName + any, + any, + Record, + ReadonlyArray, + Record, + ReadonlyArray >; export class ComponentModel implements IComponentModel { spec: ComponentSpecModel; diff --git a/packages/editor/src/components/ComponentsList/ComponentList.tsx b/packages/editor/src/components/ComponentsList/ComponentList.tsx index a6bfc65c..a31e9beb 100644 --- a/packages/editor/src/components/ComponentsList/ComponentList.tsx +++ b/packages/editor/src/components/ComponentsList/ComponentList.tsx @@ -16,9 +16,10 @@ import { CoreComponentName, CORE_VERSION } from '@sunmao-ui/shared'; import { groupBy, sortBy } from 'lodash'; import { EditorServices } from '../../types'; import { ExplorerMenuTabs } from '../../constants/enum'; -import { RuntimeComponent } from '@sunmao-ui/core'; +import { RuntimeComponent, SlotSpec } from '@sunmao-ui/core'; import { css } from '@emotion/css'; import { ComponentFilter } from './ComponentFilter'; +import { JSONSchema7 } from 'json-schema'; type Props = { services: EditorServices; @@ -26,7 +27,14 @@ type Props = { type Category = { name: string; - components: RuntimeComponent[]; + components: RuntimeComponent< + any, + any, + Record, + ReadonlyArray, + Record, + ReadonlyArray + >[]; }; const PRESET_CATEGORY_ORDER = { diff --git a/packages/editor/src/validator/SchemaValidator.ts b/packages/editor/src/validator/SchemaValidator.ts index 8af87c9a..b8b42cd6 100644 --- a/packages/editor/src/validator/SchemaValidator.ts +++ b/packages/editor/src/validator/SchemaValidator.ts @@ -1,4 +1,4 @@ -import { RuntimeComponent } from '@sunmao-ui/core'; +import { RuntimeComponent, SlotSpec } from '@sunmao-ui/core'; import { RegistryInterface } from '@sunmao-ui/runtime'; import Ajv from 'ajv'; import { PropertiesValidatorRule } from '.'; @@ -13,6 +13,7 @@ import { ValidatorMap, } from './interfaces'; import { rules } from './rules'; +import { JSONSchema7 } from 'json-schema'; export class SchemaValidator implements ISchemaValidator { private result: ValidateErrorResult[] = []; @@ -22,7 +23,14 @@ export class SchemaValidator implements ISchemaValidator { private propertiesRules: PropertiesValidatorRule[] = []; private componentIdSpecMap: Record< string, - RuntimeComponent + RuntimeComponent< + any, + any, + Record, + ReadonlyArray, + Record, + ReadonlyArray + > > = {}; private ajv!: Ajv; diff --git a/packages/editor/src/validator/interfaces.ts b/packages/editor/src/validator/interfaces.ts index 18cfb882..6931ad2f 100644 --- a/packages/editor/src/validator/interfaces.ts +++ b/packages/editor/src/validator/interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, RuntimeComponent } from '@sunmao-ui/core'; +import { ComponentSchema, RuntimeComponent, SlotSpec } from '@sunmao-ui/core'; import { RegistryInterface } from '@sunmao-ui/runtime'; import Ajv, { ValidateFunction } from 'ajv'; import { AppModel } from '../AppModel/AppModel'; @@ -8,6 +8,7 @@ import { IFieldModel, ITraitModel, } from '../AppModel/IAppModel'; +import { JSONSchema7 } from 'json-schema'; export interface ValidatorMap { components: Record; @@ -22,7 +23,17 @@ interface BaseValidateContext { appModel: IAppModel; ajv: Ajv; dependencyNames: string[]; - componentIdSpecMap: Record>; + componentIdSpecMap: Record< + string, + RuntimeComponent< + any, + any, + Record, + ReadonlyArray, + Record, + ReadonlyArray + > + >; } export interface ComponentValidateContext extends BaseValidateContext { diff --git a/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx b/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx index 8139abc7..b0e04e45 100644 --- a/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx +++ b/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx @@ -28,20 +28,20 @@ export const ImplWrapperMain = React.forwardRef[]>( - () => { - return c.traits.map(t => - executeTrait( - t, - stateManager.deepEval(t.properties, { - evalListItem, - scopeObject: { $slot: slotProps }, - fallbackWhenError: () => undefined, - }) - ) - ); - } - ); + const [traitResults, setTraitResults] = useState< + TraitResult, ReadonlyArray>[] + >(() => { + return c.traits.map(t => + executeTrait( + t, + stateManager.deepEval(t.properties, { + evalListItem, + scopeObject: { $slot: slotProps }, + fallbackWhenError: () => undefined, + }) + ) + ); + }); useEffect(() => { return () => { @@ -81,9 +81,15 @@ export const ImplWrapperMain = React.forwardRef['props'] = useMemo(() => { + const propsFromTraits: TraitResult< + ReadonlyArray, + ReadonlyArray + >['props'] = useMemo(() => { return Array.from(traitResults.values()).reduce( - (prevProps, result: TraitResult) => { + ( + prevProps, + result: TraitResult, ReadonlyArray> + ) => { if (!result.props) { return prevProps; } @@ -96,7 +102,7 @@ export const ImplWrapperMain = React.forwardRef['props'] + {} as TraitResult, ReadonlyArray>['props'] ); }, [traitResults]); diff --git a/packages/runtime/src/components/_internal/ImplWrapper/UnmountImplWrapper.tsx b/packages/runtime/src/components/_internal/ImplWrapper/UnmountImplWrapper.tsx index 35c0f579..f56329a2 100644 --- a/packages/runtime/src/components/_internal/ImplWrapper/UnmountImplWrapper.tsx +++ b/packages/runtime/src/components/_internal/ImplWrapper/UnmountImplWrapper.tsx @@ -23,12 +23,13 @@ export const UnmountImplWrapper = React.forwardRef { - const results: TraitResult[] = unmountTraits.map(t => { - const properties = stateManager.deepEval(t.properties, { - scopeObject: { $slot: props.slotProps }, + const results: TraitResult, ReadonlyArray>[] = + unmountTraits.map(t => { + const properties = stateManager.deepEval(t.properties, { + scopeObject: { $slot: props.slotProps }, + }); + return executeTrait(t, properties); }); - return executeTrait(t, properties); - }); return results.some(result => result.unmount); }); diff --git a/packages/runtime/src/services/Registry.tsx b/packages/runtime/src/services/Registry.tsx index 2194d015..220cb932 100644 --- a/packages/runtime/src/services/Registry.tsx +++ b/packages/runtime/src/services/Registry.tsx @@ -1,4 +1,5 @@ -import { parseType } from '@sunmao-ui/core'; +import { JSONSchema7 } from 'json-schema'; +import { parseType, SlotSpec } from '@sunmao-ui/core'; // components /* --- core --- */ import CoreText from '../components/core/Text'; @@ -36,17 +37,26 @@ import { ImplementedUtilMethod } from '../types/utilMethod'; import { UtilMethodManager } from './UtilMethodManager'; export type SunmaoLib = { - components?: ImplementedRuntimeComponent[]; + components?: ImplementedRuntimeComponent< + any, + any, + Record, + ReadonlyArray, + Record, + ReadonlyArray + >[]; traits?: ImplementedRuntimeTraitFactory[]; modules?: ImplementedRuntimeModule[]; utilMethods?: UtilMethodFactory[]; }; type AnyImplementedRuntimeComponent = ImplementedRuntimeComponent< - string, - string, - string, - string + any, + any, + Record, + ReadonlyArray, + Record, + ReadonlyArray >; export type RegistryInterface = InstanceType; diff --git a/packages/runtime/src/types/component.ts b/packages/runtime/src/types/component.ts index b1d18bc6..ff6a4782 100644 --- a/packages/runtime/src/types/component.ts +++ b/packages/runtime/src/types/component.ts @@ -1,9 +1,11 @@ +import { JSONSchema7 } from 'json-schema'; import { Static } from '@sinclair/typebox'; import { RuntimeApplication, RuntimeComponentSchema, RuntimeComponent, SlotSpec, + MethodSchema, } from '@sunmao-ui/core'; import React from 'react'; import { UIServices, ComponentParamsFromApp } from './application'; @@ -29,10 +31,10 @@ export type ComponentImplProps< TState, TMethods, TSlots extends Record, - KStyleSlot extends string, - KEvent extends string + KStyleSlots extends ReadonlyArray, + KEvents extends ReadonlyArray > = ImplWrapperProps & - TraitResult['props'] & + TraitResult['props'] & RuntimeFunctions & { elementRef?: React.MutableRefObject; getElement?: (ele: HTMLElement) => void; @@ -40,21 +42,23 @@ export type ComponentImplProps< export type ComponentImpl< TProps extends Record = Record, - TState = any, + TState = Record, TMethods = Record, TSlots extends Record = Record, - KStyleSlot extends string = string, - KEvent extends string = string + KStyleSlots extends ReadonlyArray = ReadonlyArray, + KEvents extends ReadonlyArray = ReadonlyArray > = React.FC< - TProps & ComponentImplProps + TProps & ComponentImplProps >; export type ImplementedRuntimeComponent< - KMethodName extends string, - KStyleSlot extends string, - KSlot extends string, - KEvent extends string -> = RuntimeComponent & { + KProperties extends JSONSchema7, + KState extends JSONSchema7, + KMethods extends Record, + KStyleSlots extends ReadonlyArray, + KSlots extends Record, + KEvents extends ReadonlyArray +> = RuntimeComponent & { impl: ComponentImpl; }; diff --git a/packages/runtime/src/types/trait.ts b/packages/runtime/src/types/trait.ts index 90de41be..fb96a8b7 100644 --- a/packages/runtime/src/types/trait.ts +++ b/packages/runtime/src/types/trait.ts @@ -2,11 +2,16 @@ import { RuntimeTrait, RuntimeTraitSchema } from '@sunmao-ui/core'; import { UIServices } from './application'; import { RuntimeFunctions } from './component'; -export type TraitResult = { +type ToStringUnion> = T[number]; + +export type TraitResult< + KStyleSlots extends ReadonlyArray, + KEvents extends ReadonlyArray +> = { props: { data?: unknown; - customStyle?: Record; - callbackMap?: CallbackMap; + customStyle?: Record, string>; + callbackMap?: CallbackMap>; componentDidUnmount?: Array<() => void>; componentDidMount?: Array<() => Function | void>; componentDidUpdate?: Array<() => Function | void>; @@ -23,7 +28,7 @@ export type TraitImpl = ( evalListItem?: boolean; slotProps?: unknown; } -) => TraitResult; +) => TraitResult, ReadonlyArray>; export type TraitImplFactory = () => TraitImpl; diff --git a/packages/runtime/src/utils/buildKit.ts b/packages/runtime/src/utils/buildKit.ts index cc96886d..92d94082 100644 --- a/packages/runtime/src/utils/buildKit.ts +++ b/packages/runtime/src/utils/buildKit.ts @@ -1,4 +1,5 @@ import { Static } from '@sinclair/typebox'; +import { JSONSchema7 } from 'json-schema'; import { createComponent, CreateComponentOptions, @@ -7,8 +8,9 @@ import { TraitSpec, createUtilMethod, CreateUtilMethodOptions, + SlotSpec, + MethodSchema, } from '@sunmao-ui/core'; -import { type DeepPartial } from '@sunmao-ui/shared'; import { ComponentImpl, ImplementedRuntimeComponent, @@ -25,27 +27,61 @@ type ToMap = { type ToStringUnion> = T[number]; export function implementRuntimeComponent< - KMethodName extends string, - KStyleSlot extends string, - KSlot extends string, + KStyle extends string, KEvent extends string, - T extends CreateComponentOptions + T extends CreateComponentOptions< + T['spec']['properties'] extends JSONSchema7 ? T['spec']['properties'] : JSONSchema7, + T['spec']['state'] extends JSONSchema7 ? T['spec']['state'] : JSONSchema7, + T['spec']['methods'] extends Record + ? T['spec']['methods'] + : Record, + ToStringUnion extends KStyle + ? ReadonlyArray> + : ReadonlyArray, + T['spec']['slots'] extends Record + ? T['spec']['slots'] + : Record, + ToStringUnion extends KEvent + ? ReadonlyArray> + : ReadonlyArray + > >( - options: T & { - metadata: { exampleProperties: DeepPartial> }; - } + options: T ): ( impl: ComponentImpl< Static extends Record ? Static : Record, - Static, + Static extends Record + ? Static + : Record, ToMap, - T['spec']['slots'], - ToStringUnion, - ToStringUnion + T['spec']['slots'] extends Record + ? T['spec']['slots'] + : Record, + ToStringUnion extends KStyle + ? ReadonlyArray> + : ReadonlyArray, + ToStringUnion extends KEvent + ? ReadonlyArray> + : ReadonlyArray > -) => ImplementedRuntimeComponent { +) => ImplementedRuntimeComponent< + T['spec']['properties'] extends JSONSchema7 ? T['spec']['properties'] : JSONSchema7, + T['spec']['state'] extends JSONSchema7 ? T['spec']['state'] : JSONSchema7, + T['spec']['methods'] extends Record + ? T['spec']['methods'] + : Record, + ToStringUnion extends KStyle + ? ReadonlyArray> + : ReadonlyArray, + T['spec']['slots'] extends Record + ? T['spec']['slots'] + : Record, + ToStringUnion extends KEvent + ? ReadonlyArray> + : ReadonlyArray +> { return impl => ({ ...createComponent(options), impl, diff --git a/packages/shared/src/types/utils.ts b/packages/shared/src/types/utils.ts index 9b75c724..5e3061a2 100644 --- a/packages/shared/src/types/utils.ts +++ b/packages/shared/src/types/utils.ts @@ -1,4 +1,4 @@ -export type DeepPartial = T extends Record +export type DeepPartial = T extends Record | Record[] ? Partial<{ [K in keyof T]: DeepPartial; }>