1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 |
x6
x6
x6
x6
x24
x24
x18
x18
x18
x18
x42
x18
x18
x18
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x63
x63
x63
x6
x6
x73
x74
x74
x700
x73
x102
x102
x139
x73
x82
x92
x368
x92
x547
x92
x93
x93
x92
x82
x73
x149
x179
x179
x149
x210
x210
x149
x160
x160
x214
x214
x283
x285
x285
x285
x285
x285
x285
x285
x285
x285
x285
x283
x1276
x319
x319
x317
x325
x325
x325
x2547
x214
x149
x151
x151
x151
x158
x151
x151
x149
x150
x150
x150
x201
x248
x248
x204
x204
x204
x204
x204
x204
x150
x150
x149
x150
x150
x150
x166
x166
x166
x166
x150
x150
x280
x414
x418
x418
x414
x415
x415
x414
x415
x415
x547
x280
x989
x745
x596
x149
x73
x6
x6 |
|
import { type Cache, type callback, type Directive, Phase } from "@mizu/internal/engine"
import { keyboard } from "./keyboard.ts"
export type * from "@mizu/internal/engine"
export const typings = {
modifiers: {
throttle: { type: Date, default: 250 },
debounce: { type: Date, default: 250 },
keys: { type: String },
prevent: { type: Boolean },
stop: { type: Boolean },
self: { type: Boolean },
attach: { type: String, allowed: ["element", "window", "document"] },
passive: { type: Boolean },
once: { type: Boolean },
capture: { type: Boolean },
},
} as const
export const _event = {
name: /^@(?<event>)/,
prefix: "@",
phase: Phase.INTERACTIVITY,
default: "null",
typings,
multiple: true,
init(renderer) {
if (!renderer.cache(this.name)) {
renderer.cache<Cache<typeof _event>>(this.name, new WeakMap())
}
},
async execute(renderer, element, { cache, attributes, context, state }) {
if (!renderer.isHtmlElement(element)) {
return
}
const parsed = attributes.map((attribute) => renderer.parseAttribute(attribute, this.typings, { prefix: this.prefix, modifiers: true }))
if ((arguments[2]._event) && (arguments[2].attributes.length === 1)) {
parsed[0].name = arguments[2]._event
}
const shorthands = parsed.filter(({ name }) => !name.length)
if (shorthands.length) {
for (const shorthand of shorthands) {
const [attribute] = parsed.splice(parsed.indexOf(shorthand), 1)
const value = await renderer.evaluate(element, attribute.value, { context, state })
if (typeof value === "object") {
parsed.unshift(...Object.entries(value ?? {}).map(([name, value]) => ({ ...attribute, name, value })))
} else {
renderer.warn(`[${this.name}] empty shorthand expects an object but got ${typeof value}, ignoring`, element)
}
}
}
for (const { name: event, value: expression, modifiers, attribute } of parsed) {
if (!cache.has(element)) {
cache.set(element, new WeakMap())
}
if (!cache.get(element)!.has(attribute)) {
cache.get(element)!.set(attribute, new Map())
}
if (cache.get(element)!.get(attribute)!.has(event)) {
continue
}
const _callback = arguments[2]._callback
let callback = function (event: Event) {
if (!element.hasAttribute(attribute.name)) {
const registered = cache.get(element)?.get(attribute)?.get(event.type)
if (registered) {
registered.target.removeEventListener(event.type, registered.listener)
}
cache.get(element)?.get(attribute)?.delete(event.type)
if (!cache.get(element)?.get(attribute)?.size) {
cache.get(element)?.delete(attribute)
}
return
}
if (_callback) {
_callback(event, { attribute, expression })
return
}
if (typeof (expression as string | callback) === "function") {
;(expression as unknown as callback)(event)
return
}
renderer.evaluate(element, `${expression || _event.default}`, { context, state: { ...state, $event: event }, args: [event] })
} as callback
if (modifiers.keys) {
const check = keyboard(modifiers.keys)
callback = ((callback) =>
function (event: KeyboardEvent) {
return check(event) ? callback(...arguments) : false
})(callback)
}
if (modifiers.throttle) {
let throttled = false
callback = ((callback) =>
function () {
if (throttled) {
return false
}
throttled = true
try {
return callback(...arguments)
} finally {
setTimeout(() => throttled = false, modifiers.throttle)
}
})(callback)
}
if (modifiers.debounce) {
let timeout = NaN
callback = ((callback) =>
function () {
const args = arguments
clearTimeout(timeout)
timeout = setTimeout(() => callback(...args), modifiers.debounce)
return false
})(callback)
}
const listener = function (event: Event) {
if (modifiers.prevent) {
event.preventDefault()
}
if (modifiers.stop) {
event.stopPropagation()
}
if (modifiers.self && event.target !== element) {
return
}
return callback(event)
}
const target = { window: renderer.window, document: renderer.document }[modifiers.attach as string] ?? element
target.addEventListener(event, listener, { passive: modifiers.passive, once: modifiers.once, capture: modifiers.capture })
cache.get(element)?.get(attribute)?.set(event, { target, listener })
}
},
} as Directive<WeakMap<HTMLElement, WeakMap<Attr, Map<string, { target: EventTarget; listener: EventListener }>>>, typeof typings> & { typings: typeof typings; execute: NonNullable<Directive["execute"]> }
export default _event
|