Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify backend protocol for event-listener & context #88

Merged
merged 1 commit into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions glass-easel/src/backend/backend_protocol.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -75,17 +75,19 @@ export interface Element extends Partial<suggestedBackend.Element> {
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 {
Expand Down Expand Up @@ -309,6 +311,10 @@ export class EmptyBackendElement implements Element {
// empty
}

setDataset(_name: string, _value: unknown): void {
// empty
}

setText(_content: string): void {
// empty
}
Expand All @@ -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
}

Expand All @@ -358,6 +364,10 @@ export class EmptyBackendElement implements Element {
},
}
}

getContext(cb: (res: unknown) => void): void {
cb(null)
}
}

/** A shadow root for empty backend implementation */
Expand Down
11 changes: 8 additions & 3 deletions glass-easel/src/backend/composed_backend_protocol.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -63,14 +63,15 @@ export interface Element extends Partial<suggestedBackend.Element> {
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 */
Expand Down Expand Up @@ -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
}

Expand All @@ -298,4 +299,8 @@ export class EmptyComposedBackendElement implements Element {
},
}
}

getContext(cb: (res: unknown) => void): void {
cb(null)
}
}
35 changes: 25 additions & 10 deletions glass-easel/src/backend/domlike_backend_protocol.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -39,7 +39,7 @@ export interface Context extends Partial<suggestedBackend.Context> {
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,
Expand All @@ -56,6 +56,7 @@ export interface Context extends Partial<suggestedBackend.Context> {
status: MediaQueryStatus,
listener: (res: { matches: boolean }) => void,
): Observer
getContext(element: Element, cb: (res: unknown) => void): void
}

export interface Element extends Partial<suggestedBackend.Element> {
Expand Down Expand Up @@ -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<string, true>
private _$elementEventListeners = new WeakMap<Element, Record<string, true>>()
private _$elementCaptureEventListeners = new WeakMap<Element, Record<string, true>>()
private _$triggedEvents = new WeakSet<Event>()
private _$eventListener?: (
target: any,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -392,23 +400,26 @@ 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 },
)
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)
})
}
Expand Down Expand Up @@ -542,4 +553,8 @@ export class CurrentWindowBackendContext implements Context {
},
}
}

getContext(element: Element, cb: (res: unknown) => void): void {
cb(null)
}
}
65 changes: 57 additions & 8 deletions glass-easel/src/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
EventOptions,
EventTarget,
FinalChanged,
MutLevel,
} from './event'
import { triggerWarning } from './func_arr'
import { ParsedSelector } from './selector'
Expand Down Expand Up @@ -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<unknown>, 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])
}
Expand All @@ -2011,7 +2035,7 @@ export class Element implements NodeCast {
removeListener(name: string, func: EventListener<unknown>, 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])
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
}
}
47 changes: 31 additions & 16 deletions glass-easel/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const getCurrentTimeStamp = () => {
return ts - relativeTimeStamp
}

const enum MutLevel {
export const enum MutLevel {
None = 0,
Mut = 1,
Final = 2,
Expand All @@ -70,9 +70,9 @@ type EventFuncArr<TDetail> = {
export const enum FinalChanged {
NotChanged = 0,
Failed,
Init,
Added,
Removed,
None,
Final,
Mut,
}

/** The target of an event */
Expand Down Expand Up @@ -120,14 +120,17 @@ export class EventTarget<TEvents extends { [type: string]: unknown }> {
}
}
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<T extends string>(
Expand All @@ -141,11 +144,23 @@ export class EventTarget<TEvents extends { [type: string]: unknown }> {
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
}
}

Expand Down
Loading