From 128e54212421583f536d3702a5a102912e524b10 Mon Sep 17 00:00:00 2001 From: chyizheng Date: Mon, 4 Sep 2023 18:47:53 +0800 Subject: [PATCH] feat(core): set listener stats --- glass-easel/src/backend/backend_protocol.ts | 16 ++++- .../src/backend/composed_backend_protocol.ts | 11 +++- .../src/backend/domlike_backend_protocol.ts | 35 +++++++--- glass-easel/src/element.ts | 65 ++++++++++++++++--- glass-easel/src/event.ts | 47 +++++++++----- glass-easel/src/index.ts | 1 + glass-easel/src/tmpl/proc_gen_wrapper.ts | 2 +- glass-easel/tests/backend/domlike.test.ts | 10 +-- 8 files changed, 138 insertions(+), 49 deletions(-) diff --git a/glass-easel/src/backend/backend_protocol.ts b/glass-easel/src/backend/backend_protocol.ts index 99bd113..4258af2 100644 --- a/glass-easel/src/backend/backend_protocol.ts +++ b/glass-easel/src/backend/backend_protocol.ts @@ -1,6 +1,6 @@ /* eslint-disable class-methods-use-this */ -import { EventBubbleStatus, EventOptions } from '../event' +import { EventBubbleStatus, EventOptions, MutLevel } from '../event' import { safeCallback } from '../func_arr' import { BackendMode, @@ -75,17 +75,19 @@ export interface Element extends Partial { clearClasses(): void setAttribute(name: string, value: unknown): void removeAttribute(name: string): void + setDataset(name: string, value: unknown): void setText(content: string): void getBoundingClientRect(cb: (res: BoundingClientRect) => void): void getScrollOffset(cb: (res: ScrollOffset) => void): void - setEventDefaultPrevented(type: string, enabled: boolean): void setModelBindingStat(attributeName: string, listener: ((newValue: unknown) => void) | null): void + setListenerStats(type: string, capture: boolean, mutLevel: MutLevel): void createIntersectionObserver( relativeElement: Element | null, relativeElementMargin: string, thresholds: number[], listener: (res: IntersectionStatus) => void, ): Observer + getContext(cb: (res: unknown) => void): void } export interface ShadowRootContext extends Element { @@ -309,6 +311,10 @@ export class EmptyBackendElement implements Element { // empty } + setDataset(_name: string, _value: unknown): void { + // empty + } + setText(_content: string): void { // empty } @@ -335,7 +341,7 @@ export class EmptyBackendElement implements Element { }, 0) } - setEventDefaultPrevented(_type: string, _enabled: boolean): void { + setListenerStats(_type: string, _capture: boolean, _mutLevel: MutLevel): void { // empty } @@ -358,6 +364,10 @@ export class EmptyBackendElement implements Element { }, } } + + getContext(cb: (res: unknown) => void): void { + cb(null) + } } /** A shadow root for empty backend implementation */ diff --git a/glass-easel/src/backend/composed_backend_protocol.ts b/glass-easel/src/backend/composed_backend_protocol.ts index 6e501ed..568bb45 100644 --- a/glass-easel/src/backend/composed_backend_protocol.ts +++ b/glass-easel/src/backend/composed_backend_protocol.ts @@ -1,6 +1,6 @@ /* eslint-disable class-methods-use-this */ -import { EventOptions, EventBubbleStatus } from '../event' +import { EventOptions, EventBubbleStatus, MutLevel } from '../event' import { safeCallback } from '../func_arr' import { BackendMode, @@ -63,14 +63,15 @@ export interface Element extends Partial { setText(content: string): void getBoundingClientRect(cb: (res: BoundingClientRect) => void): void getScrollOffset(cb: (res: ScrollOffset) => void): void - setEventDefaultPrevented(type: string, enabled: boolean): void setModelBindingStat(attributeName: string, listener: ((newValue: unknown) => void) | null): void + setListenerStats(type: string, capture: boolean, mutLevel: MutLevel): void createIntersectionObserver( relativeElement: Element | null, relativeElementMargin: string, thresholds: number[], listener: (res: IntersectionStatus) => void, ): Observer + getContext(cb: (res: unknown) => void): void } /** An empty backend implementation */ @@ -275,7 +276,7 @@ export class EmptyComposedBackendElement implements Element { }, 0) } - setEventDefaultPrevented(_type: string, _enabled: boolean): void { + setListenerStats(_type: string, _capture: boolean, _mutLevel: MutLevel): void { // empty } @@ -298,4 +299,8 @@ export class EmptyComposedBackendElement implements Element { }, } } + + getContext(cb: (res: unknown) => void): void { + cb(null) + } } diff --git a/glass-easel/src/backend/domlike_backend_protocol.ts b/glass-easel/src/backend/domlike_backend_protocol.ts index fc73685..da00b5b 100644 --- a/glass-easel/src/backend/domlike_backend_protocol.ts +++ b/glass-easel/src/backend/domlike_backend_protocol.ts @@ -1,7 +1,7 @@ /* eslint-disable class-methods-use-this */ /* global window, document */ -import { EventOptions, EventBubbleStatus } from '../event' +import { EventOptions, EventBubbleStatus, MutLevel } from '../event' import { safeCallback, triggerWarning } from '../func_arr' import { BackendMode, @@ -39,7 +39,7 @@ export interface Context extends Partial { options: EventOptions, ) => EventBubbleStatus, ): void - setElementEventDefaultPrevented(element: Element, type: string, enabled: boolean): void + setListenerStats(element: Element, type: string, capture: boolean, mutLevel: MutLevel): void setModelBindingStat( element: Element, attributeName: string, @@ -56,6 +56,7 @@ export interface Context extends Partial { status: MediaQueryStatus, listener: (res: { matches: boolean }) => void, ): Observer + getContext(element: Element, cb: (res: unknown) => void): void } export interface Element extends Partial { @@ -120,6 +121,7 @@ export class CurrentWindowBackendContext implements Context { private _$styleSheetRegistry = Object.create(null) as { [path: string]: string } private _$delegatedEventListeners = Object.create(null) as Record private _$elementEventListeners = new WeakMap>() + private _$elementCaptureEventListeners = new WeakMap>() private _$triggedEvents = new WeakSet() private _$eventListener?: ( target: any, @@ -213,12 +215,17 @@ export class CurrentWindowBackendContext implements Context { } private _$trigger(ev: Event, type: string, detail: unknown, bubbles: boolean, composed: boolean) { - if (!this._$eventListener) return - const target = ev.target - const bubbleStatus = this._$eventListener(target, type, detail, { + if (!this._$eventListener || !ev.target) return + + let t: Element | null = ev.target as any as Element + while (t && !t.__wxElement) t = t.parentNode + if (!t) return + + const bubbleStatus = this._$eventListener(t.__wxElement!, type, detail, { originalEvent: ev, bubbles, composed, + capturePhase: true, }) if (bubbleStatus === EventBubbleStatus.NoDefault) { ev.preventDefault() @@ -377,13 +384,14 @@ export class CurrentWindowBackendContext implements Context { listeners.mouseup = true } - setElementEventDefaultPrevented(element: Element, type: string, _enabled: boolean): void { + setListenerStats(element: Element, type: string, capture: boolean, mutLevel: MutLevel): void { // for non-passive events, // the default-prevented status can also be found in `EventBubbleStatus` , // so there is nothing to do with non-passive events. if (!element) return const shouldDelegate = DELEGATE_EVENTS.includes(type) + const defaultPrevented = mutLevel === MutLevel.Final if (shouldDelegate) { if (this._$delegatedEventListeners[type]) return @@ -392,6 +400,7 @@ export class CurrentWindowBackendContext implements Context { document.body.addEventListener( type, (ev) => { + if (defaultPrevented) ev.preventDefault() this._$trigger(ev, type, this._$getEventDetail(ev), ev.bubbles, ev.composed) }, { capture: true }, @@ -399,16 +408,18 @@ export class CurrentWindowBackendContext implements Context { return } - if (!this._$elementEventListeners.has(element)) { - this._$elementEventListeners.set(element, Object.create(null)) - } - const listeners = this._$elementEventListeners.get(element)! + const elementEventListeners = capture + ? this._$elementCaptureEventListeners + : this._$elementEventListeners + if (!elementEventListeners.has(element)) elementEventListeners.set(element, Object.create(null)) + const listeners = elementEventListeners.get(element)! if (listeners[type]) return listeners[type] = true element.addEventListener(type, (ev) => { if (this._$triggedEvents.has(ev)) return this._$triggedEvents.add(ev) + if (defaultPrevented) ev.preventDefault() this._$trigger(ev, type, this._$getEventDetail(ev), ev.bubbles, ev.composed) }) } @@ -542,4 +553,8 @@ export class CurrentWindowBackendContext implements Context { }, } } + + getContext(element: Element, cb: (res: unknown) => void): void { + cb(null) + } } diff --git a/glass-easel/src/element.ts b/glass-easel/src/element.ts index ce89927..11599cb 100644 --- a/glass-easel/src/element.ts +++ b/glass-easel/src/element.ts @@ -10,6 +10,7 @@ import { EventOptions, EventTarget, FinalChanged, + MutLevel, } from './event' import { triggerWarning } from './func_arr' import { ParsedSelector } from './selector' @@ -1984,24 +1985,47 @@ export class Element implements NodeCast { Event.dispatchEvent(this, ev) } - private _$updateEventDefaultPrevented(name: string, enabled: boolean) { - if (!this._$backendElement) return + /* @internal */ + private _$setListenerStats( + name: string, + finalChanged: FinalChanged, + options: EventListenerOptions = {}, + ) { + const capture = !!options.capture || !!options.useCapture + let mutLevel: MutLevel + switch (finalChanged) { + case FinalChanged.None: + mutLevel = MutLevel.None + break + case FinalChanged.Mut: + mutLevel = MutLevel.Mut + break + case FinalChanged.Final: + mutLevel = MutLevel.Final + break + default: + return + } if (BM.DOMLIKE || (BM.DYNAMIC && this.getBackendMode() === BackendMode.Domlike)) { - ;(this._$nodeTreeContext as domlikeBackend.Context).setElementEventDefaultPrevented( + ;(this._$nodeTreeContext as domlikeBackend.Context).setListenerStats( this._$backendElement as domlikeBackend.Element, name, - enabled, + capture, + mutLevel, ) } else { - ;(this._$backendElement as backend.Element).setEventDefaultPrevented(name, enabled) + ;(this._$backendElement as backend.Element | composedBackend.Element).setListenerStats( + name, + capture, + mutLevel, + ) } } /** Add an event listener on the element */ addListener(name: string, func: EventListener, options?: EventListenerOptions) { const finalChanged = this._$eventTarget.addListener(name, func, options) - if (finalChanged === FinalChanged.Init) this._$updateEventDefaultPrevented(name, false) - else if (finalChanged === FinalChanged.Added) this._$updateEventDefaultPrevented(name, true) + this._$setListenerStats(name, finalChanged, options) if (this instanceof Component && this._$definition._$options.listenerChangeLifetimes) { this.triggerLifetime('listenerChange', [true, name, func, options]) } @@ -2011,7 +2035,7 @@ export class Element implements NodeCast { removeListener(name: string, func: EventListener, options?: EventListenerOptions) { const finalChanged = this._$eventTarget.removeListener(name, func, options) if (finalChanged === FinalChanged.Failed) return - if (finalChanged !== FinalChanged.NotChanged) this._$updateEventDefaultPrevented(name, false) + this._$setListenerStats(name, finalChanged, options) if (this instanceof Component && this._$definition._$options.listenerChangeLifetimes) { this.triggerLifetime('listenerChange', [false, name, func, options]) } @@ -2067,6 +2091,15 @@ export class Element implements NodeCast { if (this._$backendElement) this._$backendElement.removeAttribute(name) } + /** Set a dataset on the element */ + setDataset(name: string, value: unknown) { + this.dataset[name] = value + + if (BM.SHADOW || (BM.DYNAMIC && this.getBackendMode() === BackendMode.Shadow)) { + ;(this._$backendElement as backend.Element).setDataset(name, value) + } + } + /** Set a mark on the element */ setMark(name: string, value: unknown) { if (!this._$marks) { @@ -2649,4 +2682,20 @@ export class Element implements NodeCast { } return null } + + /** + * Get an interactive context + */ + getContext(cb: (res: unknown) => void): void { + const backendElement = this._$backendElement + if (!backendElement) return + if (BM.DOMLIKE || (BM.DYNAMIC && this.getBackendMode() === BackendMode.Domlike)) { + ;(this._$nodeTreeContext as domlikeBackend.Context).getContext( + backendElement as domlikeBackend.Element, + cb, + ) + } else { + ;(backendElement as backend.Element).getContext(cb) + } + } } diff --git a/glass-easel/src/event.ts b/glass-easel/src/event.ts index 316b0eb..dfeda3f 100644 --- a/glass-easel/src/event.ts +++ b/glass-easel/src/event.ts @@ -55,7 +55,7 @@ const getCurrentTimeStamp = () => { return ts - relativeTimeStamp } -const enum MutLevel { +export const enum MutLevel { None = 0, Mut = 1, Final = 2, @@ -70,9 +70,9 @@ type EventFuncArr = { export const enum FinalChanged { NotChanged = 0, Failed, - Init, - Added, - Removed, + None, + Final, + Mut, } /** The target of an event */ @@ -120,14 +120,17 @@ export class EventTarget { } } efa.funcArr.add(func, mutLevel) - if (mutLevel === MutLevel.Final) efa.finalCount += 1 - else if (mutLevel === MutLevel.Mut) efa.mutCount += 1 - if (initialized) { - return mutLevel === MutLevel.Final && efa.finalCount === 1 - ? FinalChanged.Added - : FinalChanged.NotChanged + if (mutLevel === MutLevel.Final) { + efa.finalCount += 1 + return initialized && efa.finalCount !== 1 ? FinalChanged.NotChanged : FinalChanged.Final } - return mutLevel === MutLevel.Final ? FinalChanged.Added : FinalChanged.Init + if (mutLevel === MutLevel.Mut) { + efa.mutCount += 1 + return initialized && !(efa.mutCount === 1 && efa.finalCount === 0) + ? FinalChanged.NotChanged + : FinalChanged.Mut + } + return initialized ? FinalChanged.NotChanged : FinalChanged.None } removeListener( @@ -141,11 +144,23 @@ export class EventTarget { if (!efa) return FinalChanged.Failed const mutLevel = efa.funcArr.remove(func) if (mutLevel === null) return FinalChanged.Failed - if (mutLevel === MutLevel.Final) efa.finalCount -= 1 - else if (mutLevel === MutLevel.Mut) efa.mutCount -= 1 - return mutLevel === MutLevel.Final && efa.finalCount === 0 - ? FinalChanged.Removed - : FinalChanged.NotChanged + + if (mutLevel === MutLevel.Final) { + efa.finalCount -= 1 + // eslint-disable-next-line no-nested-ternary + return efa.finalCount !== 0 + ? FinalChanged.NotChanged + : efa.mutCount > 0 + ? FinalChanged.Mut + : FinalChanged.None + } + if (mutLevel === MutLevel.Mut) { + efa.mutCount -= 1 + return efa.mutCount !== 0 || efa.finalCount !== 0 + ? FinalChanged.NotChanged + : FinalChanged.None + } + return FinalChanged.NotChanged } } diff --git a/glass-easel/src/index.ts b/glass-easel/src/index.ts index c3db9a3..dd32858 100644 --- a/glass-easel/src/index.ts +++ b/glass-easel/src/index.ts @@ -32,6 +32,7 @@ export { EventOptions, EventListener, EventListenerOptions, + MutLevel as EventMutLevel, } from './event' export * as typeUtils from './component_params' export { diff --git a/glass-easel/src/tmpl/proc_gen_wrapper.ts b/glass-easel/src/tmpl/proc_gen_wrapper.ts index 9392ba3..c2fb5b7 100644 --- a/glass-easel/src/tmpl/proc_gen_wrapper.ts +++ b/glass-easel/src/tmpl/proc_gen_wrapper.ts @@ -894,7 +894,7 @@ export class ProcGenWrapper { // set dataset d(elem: Element, name: string, v: unknown) { - elem.dataset[name] = v + elem.setDataset(name, v) } // set mark diff --git a/glass-easel/tests/backend/domlike.test.ts b/glass-easel/tests/backend/domlike.test.ts index c0de7d3..a1be94f 100644 --- a/glass-easel/tests/backend/domlike.test.ts +++ b/glass-easel/tests/backend/domlike.test.ts @@ -14,14 +14,8 @@ componentSpace.defineComponent({ describe('domlike backend', () => { beforeAll(() => { - domBackend.onEvent((target: glassEasel.domlikeBackend.Element, type, detail, options) => { - let cur = target as Element & glassEasel.domlikeBackend.Element - while (cur && !cur.__wxElement) { - cur = cur.parentNode as Element & glassEasel.domlikeBackend.Element - } - if (!cur) return - const ev = new glassEasel.Event(type, detail, options) - glassEasel.Event.dispatchEvent(target.__wxElement as any, ev) + domBackend.onEvent((target, type, detail, options) => { + glassEasel.Event.triggerEvent(target, type, detail, options) }) })