diff --git a/glass-easel-miniprogram-adapter/src/behavior.ts b/glass-easel-miniprogram-adapter/src/behavior.ts index e0a3ac4..eb08946 100644 --- a/glass-easel-miniprogram-adapter/src/behavior.ts +++ b/glass-easel-miniprogram-adapter/src/behavior.ts @@ -1,5 +1,6 @@ import * as glassEasel from 'glass-easel' import { GeneralComponentDefinition, utils as typeUtils } from './types' +import { GeneralComponent } from './component' type DataList = typeUtils.DataList type PropertyList = typeUtils.PropertyList @@ -23,19 +24,24 @@ export class Behavior< TProperty extends PropertyList, TMethod extends MethodList, TChainingFilter extends ChainingFilterType, + TComponentExport = never, > { /** @internal */ _$: glassEasel.GeneralBehavior /** @internal */ _$bindedDefinitionFilter?: (target: GeneralComponentDefinition) => void + /** @internal */ + _$export?: (source: GeneralComponent | null) => TComponentExport /** @internal */ constructor( inner: glassEasel.Behavior, parents: GeneralBehavior[], definitionFilter: DefinitionFilter | undefined, + componentExport: ((source: GeneralComponent | null) => TComponentExport) | undefined, ) { this._$ = inner as glassEasel.GeneralBehavior + this._$export = componentExport // processing definition filter if (definitionFilter !== undefined) { diff --git a/glass-easel-miniprogram-adapter/src/component.ts b/glass-easel-miniprogram-adapter/src/component.ts index 331eb8c..aeb2327 100644 --- a/glass-easel-miniprogram-adapter/src/component.ts +++ b/glass-easel-miniprogram-adapter/src/component.ts @@ -18,7 +18,7 @@ type ExportType< UProperty extends PropertyList, UMethod extends MethodList, UComponentExport, -> = UComponentExport extends undefined +> = [UComponentExport] extends [never] ? Component : UComponentExport @@ -52,7 +52,7 @@ const filterComponentExport = ( source: ComponentCaller, elem: glassEasel.Element, ): any => { - if (elem instanceof glassEasel.Component) { + if (glassEasel.Component.isComponent(elem)) { const selectedSpace = elem.getRootBehavior().ownerSpace // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const uncheckedCaller = elem.getMethodCaller() @@ -477,7 +477,10 @@ export class ComponentProto< > { private proto: Component - constructor(methods: TMethod, componentExport?: () => TComponentExport) { + constructor( + methods: TMethod, + componentExport?: (source: GeneralComponent | null) => TComponentExport, + ) { this.proto = Object.create(ComponentCaller.prototype) as Component< TData, TProperty, diff --git a/glass-easel-miniprogram-adapter/src/space.ts b/glass-easel-miniprogram-adapter/src/space.ts index 8411009..ea071c0 100644 --- a/glass-easel-miniprogram-adapter/src/space.ts +++ b/glass-easel-miniprogram-adapter/src/space.ts @@ -50,10 +50,9 @@ export interface BehaviorConstructor { TData extends DataList, TProperty extends PropertyList, TMethod extends MethodList, - // eslint-disable-next-line @typescript-eslint/no-unused-vars TComponentExport, >( - definition: BehaviorDefinition, + definition: BehaviorDefinition, ): Behavior trait(): TraitBehavior trait( @@ -441,18 +440,16 @@ export class CodeSpace { TData extends DataList, TProperty extends PropertyList, TMethod extends MethodList, - // eslint-disable-next-line @typescript-eslint/no-unused-vars TComponentExport, >( - definition: BehaviorDefinition, + definition: BehaviorDefinition, ): Behavior function behaviorConstructor< TData extends DataList, TProperty extends PropertyList, TMethod extends MethodList, - // eslint-disable-next-line @typescript-eslint/no-unused-vars TComponentExport, - >(definition?: BehaviorDefinition) { + >(definition?: BehaviorDefinition) { if (definition !== undefined) { return self.behavior().definition(definition).register() } @@ -521,7 +518,7 @@ export class BaseBehaviorBuilder< TMethod extends MethodList = Empty, TChainingFilter extends ChainingFilterType = never, TPendingChainingFilter extends ChainingFilterType = never, - TComponentExport = undefined, + TComponentExport = never, > { protected _$codeSpace!: CodeSpace protected _$!: glassEasel.BehaviorBuilder< @@ -533,6 +530,7 @@ export class BaseBehaviorBuilder< TPendingChainingFilter > protected _$parents: GeneralBehavior[] = [] + protected _$export?: (source: GeneralComponent | null) => TComponentExport /** Implement a trait behavior */ implement( @@ -549,6 +547,26 @@ export class BaseBehaviorBuilder< return this } + /** Set the export value when the component is being selected */ + export( + f: (this: GeneralComponent, source: GeneralComponent | null) => TNewComponentExport, + ): ResolveBehaviorBuilder< + BaseBehaviorBuilder< + TPrevData, + TData, + TProperty, + TMethod, + TChainingFilter, + TPendingChainingFilter, + TNewComponentExport + >, + TChainingFilter + > { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + this._$export = f as any + return this as any + } + data( gen: () => typeUtils.NewFieldList, T>, ): ResolveBehaviorBuilder< @@ -791,14 +809,15 @@ export class BaseBehaviorBuilder< TNewData extends DataList = Empty, TNewProperty extends PropertyList = Empty, TNewMethod extends MethodList = Empty, + TNewComponentExport = never, >( - def: BehaviorDefinition & + def: BehaviorDefinition & ThisType< Component< TData & TNewData, TProperty & TNewProperty, TMethod & TNewMethod, - TComponentExport + TNewComponentExport > >, ): ResolveBehaviorBuilder< @@ -809,7 +828,7 @@ export class BaseBehaviorBuilder< TMethod & TNewMethod, TChainingFilter, TPendingChainingFilter, - TComponentExport + TNewComponentExport >, TChainingFilter > { @@ -830,6 +849,7 @@ export class BaseBehaviorBuilder< pageLifetimes: rawPageLifetimes, relations: rawRelations, externalClasses, + export: exports, } = def behaviors?.forEach((beh) => { this._$parents.push(beh as GeneralBehavior) @@ -896,6 +916,8 @@ export class BaseBehaviorBuilder< } } if (externalClasses) inner.externalClasses(externalClasses) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + if (exports) this._$export = exports as any return this as any } } @@ -907,7 +929,7 @@ export class BehaviorBuilder< TMethod extends MethodList = Empty, TChainingFilter extends ChainingFilterType = never, TPendingChainingFilter extends ChainingFilterType = never, - TComponentExport = undefined, + TComponentExport = never, > extends BaseBehaviorBuilder< TPrevData, TData, @@ -978,6 +1000,24 @@ export class BehaviorBuilder< return this as any } + /** Set the export value when the component is being selected */ + override export( + f: (this: GeneralComponent, source: GeneralComponent | null) => TNewComponentExport, + ): ResolveBehaviorBuilder< + BehaviorBuilder< + TPrevData, + TData, + TProperty, + TMethod, + TChainingFilter, + TPendingChainingFilter, + TNewComponentExport + >, + TChainingFilter + > { + return super.export(f) as any + } + /** * Add some template data fields * @@ -1083,14 +1123,15 @@ export class BehaviorBuilder< TNewData extends DataList = Empty, TNewProperty extends PropertyList = Empty, TNewMethod extends MethodList = Empty, + TNewComponentExport = never >( - def: BehaviorDefinition & + def: BehaviorDefinition & ThisType< Component< TData & TNewData, TProperty & TNewProperty, TMethod & TNewMethod, - TComponentExport + TNewComponentExport > >, ): ResolveBehaviorBuilder< @@ -1101,7 +1142,7 @@ export class BehaviorBuilder< TMethod & TNewMethod, TChainingFilter, TPendingChainingFilter, - TComponentExport + TNewComponentExport >, TChainingFilter > { @@ -1113,8 +1154,13 @@ export class BehaviorBuilder< /** * Finish the behavior definition process */ - register(): Behavior { - return new Behavior(this._$.registerBehavior(), this._$parents, this._$definitionFilter) + register(): Behavior { + return new Behavior( + this._$.registerBehavior(), + this._$parents, + this._$definitionFilter, + this._$export, + ) } } @@ -1128,7 +1174,7 @@ export class ComponentBuilder< TMethod extends MethodList = Empty, TChainingFilter extends ChainingFilterType = never, TPendingChainingFilter extends ChainingFilterType = never, - TComponentExport = undefined, + TComponentExport = never, > extends BaseBehaviorBuilder< TPrevData, TData, @@ -1141,7 +1187,6 @@ export class ComponentBuilder< private _$is!: string private _$alias?: string[] private _$options?: ComponentDefinitionOptions - private _$export?: (source: GeneralComponent | null) => TComponentExport private _$proto?: ComponentProto /** @internal */ @@ -1158,12 +1203,11 @@ export class ComponentBuilder< if (proto === undefined) { const methods = originalCaller.getComponentDefinition().behavior.getMethods() // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - proto = ret._$proto = new ComponentProto(methods) as any + proto = ret._$proto = new ComponentProto(methods, ret._$export) as any } const caller = proto!.derive() // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment caller._$ = originalCaller as any - caller._$export = ret._$export return caller }) if (overallBehavior) ret._$.behavior(overallBehavior) @@ -1186,8 +1230,9 @@ export class ComponentBuilder< UProperty extends PropertyList, UMethod extends MethodList, UChainingFilter extends ChainingFilterType, + UComponentExport, >( - behavior: Behavior, + behavior: Behavior, ): ResolveBehaviorBuilder< ComponentBuilder< TPrevData, @@ -1196,22 +1241,23 @@ export class ComponentBuilder< TMethod & UMethod, UChainingFilter, TPendingChainingFilter, - TComponentExport + UComponentExport >, UChainingFilter > { this._$parents.push(behavior as GeneralBehavior) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this._$ = this._$.behavior(behavior._$) + if (behavior._$export) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + this._$export = behavior._$export as any + } return this as any } /** Set the export value when the component is being selected */ - export( - f: ( - this: Component, - source: GeneralComponent | null, - ) => TNewComponentExport, + override export( + f: (this: GeneralComponent, source: GeneralComponent | null) => TNewComponentExport, ): ResolveBehaviorBuilder< ComponentBuilder< TPrevData, @@ -1334,7 +1380,7 @@ export class ComponentBuilder< TNewData extends DataList = Empty, TNewProperty extends PropertyList = Empty, TNewMethod extends MethodList = Empty, - TNewComponentExport = undefined, + TNewComponentExport = never, >( def: ComponentDefinition & ThisType< diff --git a/glass-easel-miniprogram-adapter/src/types.ts b/glass-easel-miniprogram-adapter/src/types.ts index 4c4e240..c2431f1 100644 --- a/glass-easel-miniprogram-adapter/src/types.ts +++ b/glass-easel-miniprogram-adapter/src/types.ts @@ -1,5 +1,6 @@ import { DeepCopyKind, typeUtils as utils } from 'glass-easel' import type { DefinitionFilter, GeneralBehavior, Behavior } from './behavior' +import { GeneralComponent } from './component' export { typeUtils as utils } from 'glass-easel' @@ -40,8 +41,9 @@ export type BehaviorDefinition< TData extends utils.DataList, TProperty extends utils.PropertyList, TMethod extends utils.MethodList, + TComponentExport, > = { - behaviors?: Behavior[] + behaviors?: Behavior[] properties?: TProperty data?: TData | (() => TData) observers?: @@ -61,6 +63,7 @@ export type BehaviorDefinition< relations?: utils.RelationParamsWithKey externalClasses?: string[] definitionFilter?: DefinitionFilter + export?: (source: GeneralComponent | null) => TComponentExport } export type ComponentDefinition< @@ -70,8 +73,7 @@ export type ComponentDefinition< TComponentExport, > = { options?: ComponentDefinitionOptions - export?: () => TComponentExport -} & BehaviorDefinition +} & BehaviorDefinition export type GeneralComponentDefinition = ComponentDefinition diff --git a/glass-easel-miniprogram-adapter/tests/selector.test.ts b/glass-easel-miniprogram-adapter/tests/selector.test.ts index 0080f7e..6c9bfc0 100644 --- a/glass-easel-miniprogram-adapter/tests/selector.test.ts +++ b/glass-easel-miniprogram-adapter/tests/selector.test.ts @@ -79,48 +79,171 @@ describe('selector query', () => { const env = new MiniProgramEnv() const codeSpace = env.createCodeSpace('', true) - codeSpace.addComponentStaticConfig('child/comp', { - component: true, + codeSpace.addComponentStaticConfig('child1/comp', { component: true }) + codeSpace.addComponentStaticConfig('child2/comp', { component: true }) + codeSpace.addCompiledTemplate('child1/comp', tmpl('{{a}}')) + codeSpace.addCompiledTemplate('child2/comp', tmpl('{{a}}')) + + const child1Def = codeSpace.componentEnv('child1/comp', ({ Component }) => + Component() + .data(() => ({ + a: 123, + })) + .export(function () { + return { + id: 1, + set: (d: { a: number }) => { + this.setData(d) + }, + } + }) + .register(), + ) + + const child2Def = codeSpace.componentEnv('child2/comp', ({ Component }) => + Component() + .definition({ + data: () => ({ + a: 123, + }), + export() { + return { + id: 2, + set: (d: { a: number }) => { + this.setData(d) + }, + } + }, + }) + .register(), + ) + + codeSpace.addComponentStaticConfig('path/to/comp', { + usingComponents: { + child1: '/child1/comp', + child2: '/child2/comp', + }, }) - codeSpace.addCompiledTemplate('child/comp', tmpl('{{a}}')) + codeSpace.addCompiledTemplate( + 'path/to/comp', + tmpl(` +
+ + +
+ `), + ) + codeSpace.componentEnv('path/to/comp', ({ Component }) => { + const selfDef = Component() + .lifetime('attached', function () { + expect(this.selectComponent('#d')).toBe(null) + expect(this.selectComponent('#c1', child2Def)).toBe(null) + expect(this.selectComponent('#c1', selfDef)).toBe(null) + expect(this.selectComponent('#c2', child1Def)).toBe(null) + expect(this.selectComponent('#c2', selfDef)).toBe(null) + + const c1any = this.selectComponent('#c1') as { id: number } + const c1 = this.selectComponent('#c1', child1Def)! + const c2any = this.selectComponent('#c2') as { id: number } + const c2 = this.selectComponent('#c2', child2Def)! + + expect(c1any.id).toBe(c1.id) + expect(c2any.id).toBe(c2.id) + + c1.set({ a: 456 }) + c2.set({ a: 789 }) + }) + .register() + }) + + const ab = env.associateBackend() + const root = ab.createRoot('body', codeSpace, 'path/to/comp') + glassEasel.Element.pretendAttached(root.getComponent()) + expect(domHtml(root.getComponent())).toBe('
456789
') + }) + + test('select single component (with custom export on behavior)', () => { + const env = new MiniProgramEnv() + const codeSpace = env.createCodeSpace('', true) + + codeSpace.addComponentStaticConfig('child1/comp', { component: true }) + codeSpace.addComponentStaticConfig('child2/comp', { component: true }) + codeSpace.addCompiledTemplate('child1/comp', tmpl('{{a}}')) + codeSpace.addCompiledTemplate('child2/comp', tmpl('{{a}}')) + // eslint-disable-next-line arrow-body-style - const childDef = codeSpace.componentEnv('child/comp', ({ Component }) => { - return Component() + const child1Def = codeSpace.componentEnv('child1/comp', ({ Behavior, Component }) => { + const beh = Behavior() .data(() => ({ a: 123, })) .export(function () { return { + id: 1, set: (d: { a: number }) => { this.setData(d) }, } }) .register() + + return Component().behavior(beh).register() + }) + + const child2Def = codeSpace.componentEnv('child2/comp', ({ Behavior, Component }) => { + const beh = Behavior() + .definition({ + data: () => ({ + a: 123, + }), + export() { + return { + id: 2, + set: (d: { a: number }) => { + this.setData(d) + }, + } + }, + }) + .register() + + return Component().behavior(beh).register() }) codeSpace.addComponentStaticConfig('path/to/comp', { usingComponents: { - child: '/child/comp', + child1: '/child1/comp', + child2: '/child2/comp', }, }) codeSpace.addCompiledTemplate( 'path/to/comp', tmpl(`
- - + +
`), ) codeSpace.componentEnv('path/to/comp', ({ Component }) => { const selfDef = Component() .lifetime('attached', function () { - // eslint-disable-next-line - this.selectComponent('#c1').set({ a: 456 }) - this.selectComponent('#c2', childDef)!.set({ a: 789 }) - expect(this.selectComponent('#c2', selfDef)).toBe(null) expect(this.selectComponent('#d')).toBe(null) + expect(this.selectComponent('#c1', child2Def)).toBe(null) + expect(this.selectComponent('#c1', selfDef)).toBe(null) + expect(this.selectComponent('#c2', child1Def)).toBe(null) + expect(this.selectComponent('#c2', selfDef)).toBe(null) + + const c1any = this.selectComponent('#c1') as { id: number } + const c1 = this.selectComponent('#c1', child1Def)! + const c2any = this.selectComponent('#c2') as { id: number } + const c2 = this.selectComponent('#c2', child2Def)! + + expect(c1any.id).toBe(c1.id) + expect(c2any.id).toBe(c2.id) + + c1.set({ a: 456 }) + c2.set({ a: 789 }) }) .register() }) @@ -128,7 +251,7 @@ describe('selector query', () => { const ab = env.associateBackend() const root = ab.createRoot('body', codeSpace, 'path/to/comp') glassEasel.Element.pretendAttached(root.getComponent()) - expect(domHtml(root.getComponent())).toBe('
456789
') + expect(domHtml(root.getComponent())).toBe('
456789
') }) test('select all components', () => {