Skip to content

Commit

Permalink
feat: EventEmitter
Browse files Browse the repository at this point in the history
  • Loading branch information
qq15725 committed Nov 23, 2024
1 parent 14b2462 commit 2cfe34c
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 7 deletions.
123 changes: 123 additions & 0 deletions src/EventEmitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
export type EventListenerValue<T = any> = (ev: T) => void
export type EventListenerOptions = boolean | AddEventListenerOptions
export interface EventListener<T = any> {
value: EventListenerValue<T>
options?: EventListenerOptions
}

export class EventEmitter<T extends Record<string, any> = Record<string, any>> {
eventListeners = new Map<keyof T, EventListener | EventListener[]>()

addEventListener<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>, options?: EventListenerOptions): this {
const object = { value: listener, options }
const listeners = this.eventListeners.get(event)
if (!listeners) {
this.eventListeners.set(event, object)
}
else if (Array.isArray(listeners)) {
listeners.push(object)
}
else {
this.eventListeners.set(event, [listeners, object])
}
return this
}

removeEventListener<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>, options?: EventListenerOptions): this {
if (!listener) {
this.eventListeners.delete(event)
return this
}

const listeners = this.eventListeners.get(event)

if (!listeners) {
return this
}

if (Array.isArray(listeners)) {
const events = []
for (let i = 0, length = listeners.length; i < length; i++) {
const object = listeners[i]
if (
object.value !== listener
|| (
typeof options === 'object' && options?.once
&& (typeof object.options === 'boolean' || !object.options?.once)
)
) {
events.push(object)
}
}
if (events.length) {
this.eventListeners.set(event, events.length === 1 ? events[0] : events)
}
else {
this.eventListeners.delete(event)
}
}
else {
if (
listeners.value === listener
&& (
(typeof options === 'boolean' || !options?.once)
|| (typeof listeners.options === 'boolean' || listeners.options?.once)
)
) {
this.eventListeners.delete(event)
}
}
return this
}

removeAllListeners(): this {
this.eventListeners.clear()
return this
}

hasEventListener(event: string): boolean {
return this.eventListeners.has(event)
}

dispatchEvent<K extends keyof T>(event: K, args: T[K]): boolean {
const listeners = this.eventListeners.get(event)

if (listeners) {
if (Array.isArray(listeners)) {
for (let len = listeners.length, i = 0; i < len; i++) {
const object = listeners[i]
if (typeof object.options === 'object' && object.options?.once) {
this.off(event, object.value, object.options)
}
object.value.apply(this, args)
}
}
else {
if (typeof listeners.options === 'object' && listeners.options?.once) {
this.off(event, listeners.value, listeners.options)
}
listeners.value.apply(this, args)
}
return true
}
else {
return false
}
}

on<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>, options?: EventListenerOptions): this {
return this.addEventListener(event, listener, options)
}

once<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>): this {
return this.addEventListener(event, listener, { once: true })
}

off<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>, options?: EventListenerOptions): this {
return this.removeEventListener(event, listener, options)
}

emit<K extends keyof T>(event: K, args: T[K]): void {
this.dispatchEvent(event, args)
}
}
23 changes: 16 additions & 7 deletions src/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { TextContent, TextOptions, TextPlugin, TextStyle } from './types'
import { BoundingBox, getPathsBoundingBox, Vector2 } from 'modern-path2d'
import { drawPath, setupView, uploadColors } from './canvas'
import { Paragraph } from './content'
import { EventEmitter } from './EventEmitter'
import { Measurer } from './Measurer'
import { highlight, listStyle, render, textDecoration } from './plugins'

Expand Down Expand Up @@ -69,7 +70,13 @@ export const defaultTextStyles: TextStyle = {
skewY: 0,
}

export class Text {
export interface TextEventMap {
update: { text: Text }
measure: { text: Text, result: MeasureResult }
render: { text: Text }
}

export class Text extends EventEmitter<TextEventMap> {
debug: boolean
content: TextContent
style: Partial<TextStyle>
Expand Down Expand Up @@ -100,6 +107,8 @@ export class Text {
}

constructor(options: TextOptions = {}) {
super()

this.debug = options.debug ?? false
this.content = options.content ?? ''
this.style = options.style ?? {}
Expand Down Expand Up @@ -204,8 +213,7 @@ export class Text {
c.update(this.fonts)
})
this.rawGlyphBox = this.getGlyphBox()
const plugins = [...this.plugins.values()]
plugins
Array.from(this.plugins.values())
.sort((a, b) => (a.updateOrder ?? 0) - (b.updateOrder ?? 0))
.forEach((plugin) => {
plugin.update?.(this)
Expand All @@ -218,6 +226,7 @@ export class Text {
;(result as any)[key] = (this as any)[key]
;(this as any)[key] = (old as any)[key]
}
this.emit('measure', { text: this, result })
return result
}

Expand All @@ -242,10 +251,9 @@ export class Text {
}

updatePathBox(): this {
const plugins = [...this.plugins.values()]
this.pathBox = BoundingBox.from(
this.glyphBox,
...plugins
...Array.from(this.plugins.values())
.map((plugin) => {
return plugin.getBoundingBox
? plugin.getBoundingBox(this)
Expand Down Expand Up @@ -281,6 +289,7 @@ export class Text {
for (const key in result) {
(this as any)[key] = (result as any)[key]
}
this.emit('update', { text: this })
return this
}

Expand All @@ -295,8 +304,7 @@ export class Text {
}
setupView(ctx, pixelRatio, this.boundingBox)
uploadColors(ctx, this)
const plugins = [...this.plugins.values()]
plugins
Array.from(this.plugins.values())
.sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0))
.forEach((plugin) => {
if (plugin.render) {
Expand All @@ -313,6 +321,7 @@ export class Text {
})
}
})
this.emit('render', { text: this })
return this
}
}

0 comments on commit 2cfe34c

Please sign in to comment.