Skip to content

Commit 3f19103

Browse files
authored
Merge pull request #252 from wechat-miniprogram/feat-extend-listener-wrapper
Extend EventListenerWrapper & ChangePropFilter
2 parents e4d0174 + e167369 commit 3f19103

File tree

8 files changed

+224
-52
lines changed

8 files changed

+224
-52
lines changed

glass-easel/src/behavior.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import {
5050
type PropertyDefinition,
5151
} from './data_proxy'
5252
import { simpleDeepCopy } from './data_utils'
53-
import { type EventListener } from './event'
53+
import { type EventListener, type ShadowedEvent } from './event'
5454
import { FuncArr, safeCallback } from './func_arr'
5555
import { type ComponentOptions } from './global_options'
5656
import { normalizeRelation, type RelationDefinition, type RelationHandler } from './relation'
@@ -189,6 +189,11 @@ export class BehaviorBuilder<
189189
_$init: { func: (this: any, ctx: any) => any; once: boolean }[] = []
190190
/** @internal */
191191
_$methodCallerInit?: (this: ComponentInstance<TData, TProperty, TMethod, TExtraThisFields>) => any
192+
/** @internal */
193+
_$listenerEventReplacer?: (
194+
this: ComponentInstance<TData, TProperty, TMethod, TExtraThisFields>,
195+
event: ShadowedEvent<unknown>,
196+
) => ShadowedEvent<unknown>
192197

193198
/** @internal */
194199
constructor(is: string | undefined, ownerSpace: ComponentSpace) {
@@ -209,6 +214,21 @@ export class BehaviorBuilder<
209214
return this as any
210215
}
211216

217+
/**
218+
* Set a listener wrapper function
219+
*
220+
* It should return the wrapped listener.
221+
*/
222+
listenerEventReplacer(
223+
func: (
224+
this: ComponentInstance<TData, TProperty, TMethod, TExtraThisFields>,
225+
event: ShadowedEvent<unknown>,
226+
) => ShadowedEvent<unknown>,
227+
): ResolveBehaviorBuilder<this, TChainingFilter> {
228+
this._$listenerEventReplacer = func
229+
return this as any
230+
}
231+
212232
/**
213233
* Add a behavior
214234
*
@@ -850,6 +870,11 @@ export class Behavior<
850870
_$methodCallerInit?: (
851871
this: ComponentInstance<TData, TProperty, TMethod, TExtraThisFields>,
852872
) => ComponentInstance<TData, TProperty, TMethod, TExtraThisFields>
873+
/** @internal */
874+
_$listenerEventReplacer?: (
875+
this: ComponentInstance<TData, TProperty, TMethod, TExtraThisFields>,
876+
event: ShadowedEvent<unknown>,
877+
) => ShadowedEvent<unknown>
853878

854879
/**
855880
* Create a behavior with classic-style definition
@@ -900,6 +925,7 @@ export class Behavior<
900925
this._$relationMap = undefined
901926
this._$init = []
902927
this._$methodCallerInit = builder._$methodCallerInit
928+
this._$listenerEventReplacer = builder._$listenerEventReplacer
903929
}
904930

905931
general(): GeneralBehavior {

glass-easel/src/component.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import {
5353
performanceMeasureStart,
5454
} from './dev_tools'
5555
import { Element } from './element'
56-
import { type EventListener, type EventListenerOptions } from './event'
56+
import { type ShadowedEvent, type EventListener, type EventListenerOptions } from './event'
5757
import { type ExternalShadowRoot } from './external_shadow_tree'
5858
import { FuncArr, safeCallback, type GeneralFuncType } from './func_arr'
5959
import {
@@ -1205,6 +1205,13 @@ export class Component<
12051205
return this._$methodCaller
12061206
}
12071207

1208+
/**
1209+
* Get the final event object
1210+
*/
1211+
getEventObject(e: ShadowedEvent<unknown>): ShadowedEvent<any> {
1212+
return this._$behavior._$listenerEventReplacer?.(e) || e
1213+
}
1214+
12081215
/**
12091216
* Add a lifetime event listener on the component
12101217
*/

glass-easel/src/event.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,12 +272,13 @@ export class Event<TDetail> {
272272
targetCaller: Element,
273273
mark: { [name: string]: unknown } | null,
274274
currentTargetCaller: Element,
275+
ownerHost: GeneralComponent | undefined,
275276
): ShadowedEvent<TDetail> {
276277
const ret = Object.create(this) as ShadowedEvent<TDetail>
277278
ret.target = targetCaller
278279
ret.mark = mark
279280
ret.currentTarget = currentTargetCaller
280-
return ret
281+
return ownerHost?.getEventObject(ret) || ret
281282
}
282283

283284
getEventName(): string {
@@ -346,7 +347,7 @@ export class Event<TDetail> {
346347
? currentTarget.getMethodCaller() || currentTarget
347348
: currentTarget
348349
this._$hasListener ||= efa.funcArr.hasFunc()
349-
const ev = this.wrapShadowedEvent(targetCaller, mark, currentTargetCaller)
350+
const ev = this.wrapShadowedEvent(targetCaller, mark, currentTargetCaller, target.ownerShadowRoot?.getHostNode())
350351
const ret = efa.funcArr.call(
351352
currentTargetCaller,
352353
[ev],

glass-easel/src/tmpl/native_rendering.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export class GlassEaselTemplateDOM implements Template {
3030
innerData: DataValue
3131
genObjectGroupEnv: ProcGenEnv
3232
updateMode: string
33-
methods: { [name: string]: GeneralFuncType }
3433

3534
constructor(behavior: GeneralBehavior) {
3635
if (typeof behavior._$template !== 'object' && behavior._$template !== undefined) {
@@ -47,7 +46,6 @@ export class GlassEaselTemplateDOM implements Template {
4746
}
4847
}
4948
this.updateMode = ''
50-
this.methods = behavior._$methodMap
5149
}
5250

5351
createInstance(comp: GeneralComponent): TemplateInstance {
@@ -140,7 +138,7 @@ export class GlassEaselTemplateDOMInstance implements TemplateInstance, External
140138
const evName = event.getEventName()
141139
const bubbles = event.bubbles
142140
for (;;) {
143-
const shadowedEvent = event.wrapShadowedEvent(target as any, null, cur as any)
141+
const shadowedEvent = event.wrapShadowedEvent(target as any, null, cur as any, this.comp)
144142
const f = (cur as unknown as ElementWithEvent)._$wxTmplEv?.[evName]
145143
if (f) {
146144
const r = f.call(cur, shadowedEvent)

glass-easel/src/tmpl/proc_gen_wrapper.ts

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import { Component, type GeneralComponent } from '../component'
44
import { type DataPath } from '../data_path'
55
import { type DataValue } from '../data_proxy'
66
import { Element, StyleSegmentIndex } from '../element'
7-
import { type ShadowedEvent, type EventListener } from '../event'
7+
import { type EventListener } from '../event'
88
import { safeCallback } from '../func_arr'
9-
import { ENV } from '../global_options'
109
import { type NativeNode } from '../native_node'
1110
import { type Node } from '../node'
1211
import { SlotMode, type ShadowRoot } from '../shadow_root'
@@ -31,28 +30,34 @@ export type ChangePropListener<T> = (
3130

3231
export type ChangePropFilter = <T>(
3332
listener: ChangePropListener<T>,
34-
generalLvaluePath?: DataPath | null,
33+
generalLvaluePath: DataPath | null | undefined,
34+
elem: Element,
35+
propName: string,
3536
) => ChangePropListener<T>
3637

3738
export interface EventListenerWrapper {
3839
<T>(
39-
elem: GeneralComponent,
40-
event: ShadowedEvent<T>,
40+
elem: Element,
41+
evName: string,
4142
listener: EventListener<T>,
43+
final: boolean,
44+
mutated: boolean,
45+
capture: boolean,
4246
generalLvaluePath?: DataPath | null,
43-
): boolean | void
47+
): EventListener<T> | null
48+
isEventListenerWrapper?: true
4449
}
4550

46-
const emptyFilter = <T>(x: T) => x
51+
const defaultChangePropFilter: ChangePropFilter = <T>(x: T) => x
4752

48-
const defaultEventListenerWrapper: EventListenerWrapper = (elem, event, listener) =>
49-
listener.apply(elem, [event])
53+
const defaultEventListenerWrapper: EventListenerWrapper = (elem, evName, listener) => (e) =>
54+
listener.call(elem.ownerShadowRoot?.getHostNode().getMethodCaller(), e)
5055

5156
type TmplArgs = {
5257
key?: number | string
5358
keyList?: RangeListManager
5459
dynEvListeners?: {
55-
[name: string]: EventListener<unknown>
60+
[name: string]: EventListener<unknown> | null
5661
}
5762
dynamicSlotNameMatched?: boolean
5863
changeProp?: {
@@ -183,7 +188,7 @@ export class ProcGenWrapper {
183188
procGen: ProcGen
184189
fallbackListenerOnNativeNode: boolean
185190
bindingMapDisabled = false
186-
changePropFilter: ChangePropFilter = emptyFilter
191+
changePropFilter: ChangePropFilter = defaultChangePropFilter
187192
eventListenerWrapper: EventListenerWrapper = defaultEventListenerWrapper
188193

189194
constructor(shadowRoot: ShadowRoot, procGen: ProcGen, fallbackListenerOnNativeNode: boolean) {
@@ -1146,21 +1151,23 @@ export class ProcGenWrapper {
11461151
generalLvaluePath?: DataPath | null,
11471152
) => {
11481153
const handler = typeof v === 'function' ? v : dataValueToString(v)
1149-
const listener: EventListener<unknown> = (ev) => {
1150-
const host = elem.ownerShadowRoot!.getHostNode()
1151-
const methodCaller = host.getMethodCaller()
1152-
const f = typeof handler === 'function' ? handler : Component.getMethod(host, handler)
1153-
if (typeof f === 'function') {
1154-
return this.eventListenerWrapper(
1155-
methodCaller,
1156-
ev,
1157-
f as EventListener<unknown>,
1158-
generalLvaluePath,
1159-
)
1160-
}
1161-
return undefined
1162-
}
1163-
if (ENV.DEV) {
1154+
const listener = this.eventListenerWrapper(
1155+
elem,
1156+
evName,
1157+
function (this: any, ...args: any[]) {
1158+
const host = elem.ownerShadowRoot!.getHostNode()
1159+
const f = typeof handler === 'function' ? handler : Component.getMethod(host, handler)
1160+
if (typeof f === 'function') {
1161+
return f.call(this, ...args) as boolean | void
1162+
}
1163+
return undefined
1164+
},
1165+
final,
1166+
mutated,
1167+
capture,
1168+
generalLvaluePath,
1169+
)
1170+
if (listener) {
11641171
Object.defineProperty(listener, 'name', {
11651172
value: typeof handler === 'string' ? handler : handler.name,
11661173
})
@@ -1179,7 +1186,7 @@ export class ProcGenWrapper {
11791186
}
11801187
dynEvListeners[evName] = listener
11811188
}
1182-
if (handler) elem.addListener(evName, listener, evOptions)
1189+
if (handler && listener) elem.addListener(evName, listener, evOptions)
11831190
}
11841191

11851192
// update a property or external class of a component, or an attribute of a native node
@@ -1267,7 +1274,7 @@ export class ProcGenWrapper {
12671274
tmplArgs.changeProp = Object.create(null) as typeof tmplArgs.changeProp
12681275
}
12691276
tmplArgs.changeProp![name] = {
1270-
listener: this.changePropFilter(v, generalLvaluePath),
1277+
listener: this.changePropFilter(v, generalLvaluePath, elem, name),
12711278
oldValue: undefined,
12721279
}
12731280
}

glass-easel/src/tmpl/proc_gen_wrapper_dom.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,15 @@ export class ProcGenWrapperDom {
219219

220220
// set event handler
221221
v = (elem: HTMLElement, evName: string, v: string, final: boolean) => {
222-
this.shadowRoot.setListener(elem as unknown as GeneralBackendElement, evName, (ev) => {
223-
const handler = this.shadowRoot.template.methods[v]
224-
const ret = handler?.(ev) as unknown
222+
const listener = (ev: ShadowedEvent<unknown>) => {
223+
const ret = this.shadowRoot.comp.callMethod(v, ev) as unknown
225224
if (final) return false
226225
return ret
226+
}
227+
Object.defineProperty(listener, 'name', {
228+
value: v,
227229
})
230+
this.shadowRoot.setListener(elem as unknown as GeneralBackendElement, evName, listener)
228231
}
229232

230233
// update a property or external class of a component, or an attribute of a native node

glass-easel/tests/core/behavior.test.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,116 @@ describe('chaining-form interface', () => {
512512
expect(callOrder).toStrictEqual([3, 1, 2])
513513
})
514514

515+
test('chaining listener event replacer', () => {
516+
const callOrder: [number, number | undefined][] = []
517+
const beh = componentSpace
518+
.define()
519+
.init(({ self, lifetime }) => {
520+
lifetime('created', () => {
521+
self.addListener('customEv', (e) => {
522+
callOrder.push([
523+
1,
524+
(e as glassEasel.ShadowedEvent<{ eventOrder?: number }>).detail.eventOrder,
525+
])
526+
})
527+
})
528+
})
529+
.registerBehavior()
530+
531+
let eventOrder = 0
532+
const compDef = componentSpace
533+
.define()
534+
.behavior(beh)
535+
.template(tmpl(`<div id="wrapper" bind:customEv="onCustomEv"></div>`))
536+
.listenerEventReplacer((e) => {
537+
eventOrder += 1
538+
e.detail = { eventOrder }
539+
return e
540+
})
541+
.init(({ self, lifetime, listener }) => {
542+
lifetime('created', () => {
543+
;(self.shadowRoot as glassEasel.ShadowRoot).addListener('customEv', (e) => {
544+
callOrder.push([
545+
3,
546+
(e as glassEasel.ShadowedEvent<{ eventOrder: number }>).detail.eventOrder,
547+
])
548+
})
549+
})
550+
return {
551+
onCustomEv: listener((e) => {
552+
callOrder.push([
553+
2,
554+
(e as glassEasel.ShadowedEvent<{ eventOrder: number }>).detail.eventOrder,
555+
])
556+
}),
557+
}
558+
})
559+
.registerComponent()
560+
const root = glassEasel.Component.createWithContext('root', compDef, domBackend)
561+
;(root.$.wrapper as glassEasel.Element).triggerEvent(
562+
'customEv',
563+
{},
564+
{ bubbles: true, composed: true },
565+
)
566+
expect(callOrder).toStrictEqual([
567+
[2, 1],
568+
[3, 2],
569+
[1, undefined],
570+
])
571+
})
572+
573+
test('chaining listener event replacer on native rendering', () => {
574+
const callOrder: [number, number | undefined][] = []
575+
const beh = componentSpace
576+
.define()
577+
.init(({ self, lifetime }) => {
578+
lifetime('created', () => {
579+
self.addListener('customEv', (e) => {
580+
callOrder.push([
581+
1,
582+
(e as glassEasel.ShadowedEvent<{ eventOrder?: number }>).detail.eventOrder,
583+
])
584+
})
585+
})
586+
})
587+
.registerBehavior()
588+
589+
let eventOrder = 0
590+
const compDef = componentSpace
591+
.define()
592+
.behavior(beh)
593+
.options({ externalComponent: true })
594+
.template(tmpl(`<div id="wrapper" bind:customEv="onCustomEv"></div>`))
595+
.listenerEventReplacer((e) => {
596+
eventOrder += 1
597+
e.detail = { eventOrder }
598+
return e
599+
})
600+
.init(({ listener }) => {
601+
return {
602+
onCustomEv: listener((e) => {
603+
callOrder.push([
604+
2,
605+
(e as glassEasel.ShadowedEvent<{ eventOrder: number }>).detail.eventOrder,
606+
])
607+
}),
608+
}
609+
})
610+
.registerComponent()
611+
const root = glassEasel.Component.createWithContext('root', compDef, domBackend)
612+
glassEasel.triggerExternalEvent(
613+
root,
614+
root.$.wrapper as glassEasel.GeneralBackendElement,
615+
'customEv',
616+
{},
617+
{ bubbles: true, composed: true },
618+
)
619+
expect(callOrder).toStrictEqual([
620+
[2, 1],
621+
[1, undefined],
622+
])
623+
})
624+
515625
test('chaining relations', () => {
516626
const eventArr: number[] = []
517627
const parentDef = componentSpace

0 commit comments

Comments
 (0)