Files
infocom-systems-design/node_modules/bare-events/web.js
2025-10-03 22:27:28 +03:00

330 lines
7.0 KiB
JavaScript

// Event state
const BUBBLES = 0x1
const CANCELABLE = 0x2
const COMPOSED = 0x4
const CANCELED = 0x8
const DISPATCH = 0x10
const STOP = 0x20
// EventTarget state
const CAPTURE = 0x1
const PASSIVE = 0x2
const ONCE = 0x4
// https://dom.spec.whatwg.org/#event
exports.Event = class Event {
// https://dom.spec.whatwg.org/#dom-event-event
constructor(type, options = {}) {
const { bubbles = false, cancelable = false, composed = false } = options
this._type = type
this._target = null
this._state = 0
if (bubbles) this._state |= BUBBLES
if (cancelable) this._state |= CANCELABLE
if (composed) this._state |= COMPOSED
}
// https://dom.spec.whatwg.org/#dom-event-type
get type() {
return this._type
}
// https://dom.spec.whatwg.org/#dom-event-target
get target() {
return this._target
}
// https://dom.spec.whatwg.org/#dom-event-currenttarget
get currentTarget() {
return null
}
// https://dom.spec.whatwg.org/#dom-event-bubbles
get bubbles() {
return (this._state & BUBBLES) !== 0
}
// https://dom.spec.whatwg.org/#dom-event-cancelable
get cancelable() {
return (this._state & CANCELABLE) !== 0
}
// https://dom.spec.whatwg.org/#dom-event-composed
get composed() {
return (this._state & COMPOSED) !== 0
}
// https://dom.spec.whatwg.org/#dom-event-defaultprevented
get defaultPrevented() {
return (this._state & CANCELED) !== 0
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
get isTrusted() {
return false
}
// https://dom.spec.whatwg.org/#dom-event-preventdefault
preventDefault() {
if (this._state & CANCELABLE) this._state |= CANCELED
}
// https://dom.spec.whatwg.org/#dom-event-stoppropagation
stopPropagation() {}
// https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation
stopImmediatePropagation() {
this._state |= STOP
}
toJSON() {
return {
type: this.type,
target: this.target,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
defaultPrevented: this.defaultPrevented,
isTrusted: this.isTrusted
}
}
[Symbol.for('bare.inspect')]() {
return {
__proto__: { constructor: Event },
type: this.type,
target: this.target,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
defaultPrevented: this.defaultPrevented,
isTrusted: this.isTrusted
}
}
}
// https://dom.spec.whatwg.org/#eventtarget
exports.EventTarget = class EventTarget {
// https://dom.spec.whatwg.org/#dom-eventtarget-eventtarget
constructor() {
this._listeners = new Map()
}
// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
addEventListener(type, callback = null, options = {}) {
if (typeof options === 'boolean') options = { capture: options }
const {
capture = false,
passive = false,
once = false,
signal = null
} = options
if (signal !== null && signal.aborted) return
if (callback === null) return
const listener = new EventListener(
type,
callback,
capture,
passive,
once,
signal
)
const listeners = this._listeners.get(type)
if (listeners === undefined) this._listeners.set(type, listener)
else {
for (const existing of listeners) {
if (callback === existing.callback && capture === existing.capture) {
return // Duplicate listener
}
}
listener.link(listeners)
if (signal !== null) {
signal.addEventListener('abort', onabort)
function onabort() {
listener.unlink()
}
}
}
}
// https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener
removeEventListener(type, callback = null, options = {}) {
if (typeof options === 'boolean') options = { capture: options }
const { capture = false } = options
const listeners = this._listeners.get(type)
if (listeners === undefined) return
for (const existing of listeners) {
if (callback === existing.callback && capture === existing.capture) {
const next = existing.unlink()
if (listeners === existing) this._listeners.set(type, next)
return
}
}
}
// https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent
dispatchEvent(event) {
event._target = this
event._state |= DISPATCH
const listeners = this._listeners.get(event.type)
try {
if (listeners === undefined) return true
for (const listener of listeners) {
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
if (listener.once) listener.unlink()
let callback = listener.callback
let context = this
if (typeof callback === 'object') {
context = callback
callback = callback.handleEvent
}
Reflect.apply(callback, context, [event])
if (event._state & STOP) break
}
return (event._state & CANCELED) === 0
} finally {
event._state &= ~DISPATCH
event._state &= ~STOP
}
}
toJSON() {
return {}
}
[Symbol.for('bare.inspect')]() {
return {
__proto__: { constructor: EventTarget }
}
}
}
// https://dom.spec.whatwg.org/#concept-event-listener
class EventListener {
constructor(type, callback, capture, passive, once, signal) {
this._type = type
this._callback = callback
this._signal = signal
this._state = 0
if (capture) this._state |= CAPTURE
if (passive) this._state |= PASSIVE
if (once) this._state |= ONCE
this._previous = this
this._next = this
}
get type() {
return this._type
}
get callback() {
return this._callback
}
get capture() {
return (this._state & CAPTURE) !== 0
}
get passive() {
return (this._state & PASSIVE) !== 0
}
get once() {
return (this._state & ONCE) !== 0
}
get removed() {
return this._previous === this && this._next === this
}
link(listener) {
const next = this._next
const previous = listener._previous
this._next = listener
listener._previous = this
previous._next = next
next._previous = previous
return listener
}
unlink() {
if (this.removed) return this
const next = this._next
const previous = this._previous
this._next = this
this._previous = this
previous._next = next
next._previous = previous
return next
}
*[Symbol.iterator]() {
let current = this
while (true) {
const next = current._next
yield current
if (next === this) break
current = next
}
}
toJSON() {
return {
type: this.type,
capture: this.capture,
passive: this.passive,
once: this.once,
removed: this.removed
}
}
[Symbol.for('bare.inspect')]() {
return {
__proto__: { constructor: EventListener },
type: this.type,
callback: this.callback,
capture: this.capture,
passive: this.passive,
once: this.once,
removed: this.removed
}
}
}