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
162
163
164
165
166 |
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x6
x57
x57
x57
x6
x6
x69
x1
x1
x74
x69
x31
x31
x68
x69
x9
x10
x10
x10
x14
x10
x1
x1
x10
x9
x69
x78
x30
x30
x78
x63
x63
x78
x11
x11
x67
x67
x70
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x70
x37
x37
x37
x34
x8
x8
x8
x70
x67
x78
x2
x2
x2
x5
x2
x2
x78
x1
x1
x1
x50
x47
x47
x3
x3
x3
x3
x3
x3
x1
x1
x78
x1
x1
x1
x15
x15
x15
x15
x1
x1
x67
x135
x4
x4
x135
x1
x1
x135
x1
x1
x134
x67
x78
x78
x78
x78
x69
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 this>>(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 const satisfies Directive<{
Name: RegExp
Cache: WeakMap<HTMLElement, WeakMap<Attr, Map<string, { target: EventTarget; listener: EventListener }>>>
Typings: typeof typings
Default: true
}>
export default _event
|