@hypen-space/web 0.4.36 → 0.4.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canvas/accessibility.js +174 -174
- package/dist/canvas/accessibility.js.map +1 -10
- package/dist/canvas/events.js +264 -360
- package/dist/canvas/events.js.map +1 -11
- package/dist/canvas/index.js +11 -47
- package/dist/canvas/index.js.map +1 -9
- package/dist/canvas/input.js +178 -289
- package/dist/canvas/input.js.map +1 -11
- package/dist/canvas/layout.js +349 -547
- package/dist/canvas/layout.js.map +1 -12
- package/dist/canvas/paint.js +1122 -1146
- package/dist/canvas/paint.js.map +1 -12
- package/dist/canvas/renderer.js +352 -2259
- package/dist/canvas/renderer.js.map +1 -17
- package/dist/canvas/text.js +129 -264
- package/dist/canvas/text.js.map +1 -11
- package/dist/canvas/types.js +7 -2
- package/dist/canvas/types.js.map +1 -9
- package/dist/canvas/utils.js +158 -161
- package/dist/canvas/utils.js.map +1 -10
- package/dist/client.js +19 -45
- package/dist/client.js.map +1 -9
- package/dist/dom/applicators/advanced-layout.js +105 -138
- package/dist/dom/applicators/advanced-layout.js.map +1 -10
- package/dist/dom/applicators/background.js +36 -76
- package/dist/dom/applicators/background.js.map +1 -10
- package/dist/dom/applicators/border.js +82 -106
- package/dist/dom/applicators/border.js.map +1 -10
- package/dist/dom/applicators/color.js +17 -58
- package/dist/dom/applicators/color.js.map +1 -10
- package/dist/dom/applicators/display.js +39 -79
- package/dist/dom/applicators/display.js.map +1 -10
- package/dist/dom/applicators/effects.js +121 -147
- package/dist/dom/applicators/effects.js.map +1 -10
- package/dist/dom/applicators/events.js +413 -450
- package/dist/dom/applicators/events.js.map +1 -11
- package/dist/dom/applicators/font.js +119 -140
- package/dist/dom/applicators/font.js.map +1 -10
- package/dist/dom/applicators/index.js +386 -1770
- package/dist/dom/applicators/index.js.map +1 -26
- package/dist/dom/applicators/layout.js +143 -149
- package/dist/dom/applicators/layout.js.map +1 -10
- package/dist/dom/applicators/margin.js +64 -105
- package/dist/dom/applicators/margin.js.map +1 -10
- package/dist/dom/applicators/padding.js +64 -105
- package/dist/dom/applicators/padding.js.map +1 -10
- package/dist/dom/applicators/size.js +169 -172
- package/dist/dom/applicators/size.js.map +1 -10
- package/dist/dom/applicators/transform.js +73 -114
- package/dist/dom/applicators/transform.js.map +1 -10
- package/dist/dom/applicators/transition.js +47 -88
- package/dist/dom/applicators/transition.js.map +1 -10
- package/dist/dom/applicators/types.js +7 -2
- package/dist/dom/applicators/types.js.map +1 -9
- package/dist/dom/applicators/typography.js +68 -109
- package/dist/dom/applicators/typography.js.map +1 -10
- package/dist/dom/canvas/index.js +49 -83
- package/dist/dom/canvas/index.js.map +1 -10
- package/dist/dom/components/app.js +16 -63
- package/dist/dom/components/app.js.map +1 -10
- package/dist/dom/components/audio.js +31 -76
- package/dist/dom/components/audio.js.map +1 -10
- package/dist/dom/components/avatar.js +41 -87
- package/dist/dom/components/avatar.js.map +1 -10
- package/dist/dom/components/badge.js +35 -83
- package/dist/dom/components/badge.js.map +1 -10
- package/dist/dom/components/button.js +26 -69
- package/dist/dom/components/button.js.map +1 -10
- package/dist/dom/components/card.js +13 -63
- package/dist/dom/components/card.js.map +1 -10
- package/dist/dom/components/center.js +21 -65
- package/dist/dom/components/center.js.map +1 -10
- package/dist/dom/components/checkbox.js +37 -82
- package/dist/dom/components/checkbox.js.map +1 -10
- package/dist/dom/components/column.js +16 -62
- package/dist/dom/components/column.js.map +1 -10
- package/dist/dom/components/container.js +11 -59
- package/dist/dom/components/container.js.map +1 -10
- package/dist/dom/components/divider.js +27 -73
- package/dist/dom/components/divider.js.map +1 -10
- package/dist/dom/components/grid.js +41 -80
- package/dist/dom/components/grid.js.map +1 -10
- package/dist/dom/components/heading.js +28 -74
- package/dist/dom/components/heading.js.map +1 -10
- package/dist/dom/components/hypenapp.js +291 -288
- package/dist/dom/components/hypenapp.js.map +1 -10
- package/dist/dom/components/image.js +18 -67
- package/dist/dom/components/image.js.map +1 -10
- package/dist/dom/components/index.js +109 -1427
- package/dist/dom/components/index.js.map +1 -42
- package/dist/dom/components/input.js +19 -69
- package/dist/dom/components/input.js.map +1 -10
- package/dist/dom/components/link.js +23 -70
- package/dist/dom/components/link.js.map +1 -10
- package/dist/dom/components/list.js +26 -72
- package/dist/dom/components/list.js.map +1 -10
- package/dist/dom/components/paragraph.js +14 -63
- package/dist/dom/components/paragraph.js.map +1 -10
- package/dist/dom/components/progressbar.js +38 -85
- package/dist/dom/components/progressbar.js.map +1 -10
- package/dist/dom/components/route.js +27 -73
- package/dist/dom/components/route.js.map +1 -10
- package/dist/dom/components/router.js +15 -62
- package/dist/dom/components/router.js.map +1 -10
- package/dist/dom/components/row.js +25 -70
- package/dist/dom/components/row.js.map +1 -10
- package/dist/dom/components/select.js +42 -85
- package/dist/dom/components/select.js.map +1 -10
- package/dist/dom/components/slider.js +31 -76
- package/dist/dom/components/slider.js.map +1 -10
- package/dist/dom/components/spacer.js +10 -60
- package/dist/dom/components/spacer.js.map +1 -10
- package/dist/dom/components/spinner.js +41 -88
- package/dist/dom/components/spinner.js.map +1 -10
- package/dist/dom/components/stack.js +19 -67
- package/dist/dom/components/stack.js.map +1 -10
- package/dist/dom/components/switch.js +50 -93
- package/dist/dom/components/switch.js.map +1 -10
- package/dist/dom/components/text.js +24 -69
- package/dist/dom/components/text.js.map +1 -10
- package/dist/dom/components/textarea.js +35 -79
- package/dist/dom/components/textarea.js.map +1 -10
- package/dist/dom/components/video.js +35 -79
- package/dist/dom/components/video.js.map +1 -10
- package/dist/dom/debug.js +159 -160
- package/dist/dom/debug.js.map +1 -10
- package/dist/dom/element-data.js +181 -143
- package/dist/dom/element-data.js.map +1 -10
- package/dist/dom/events.js +133 -142
- package/dist/dom/events.js.map +1 -10
- package/dist/dom/index.js +11 -48
- package/dist/dom/index.js.map +1 -9
- package/dist/dom/renderer.js +311 -3596
- package/dist/dom/renderer.js.map +1 -62
- package/dist/hypen.js +385 -3880
- package/dist/hypen.js.map +1 -63
- package/dist/index.js +36 -53
- package/dist/index.js.map +1 -9
- package/package.json +2 -2
|
@@ -1,484 +1,447 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
var __toCommonJS = (from) => {
|
|
9
|
-
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
|
-
if (entry)
|
|
11
|
-
return entry;
|
|
12
|
-
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (var key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(entry, key))
|
|
16
|
-
__defProp(entry, key, {
|
|
17
|
-
get: __accessProp.bind(from, key),
|
|
18
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
__moduleCache.set(from, entry);
|
|
22
|
-
return entry;
|
|
23
|
-
};
|
|
24
|
-
var __moduleCache;
|
|
25
|
-
var __returnValue = (v) => v;
|
|
26
|
-
function __exportSetter(name, newValue) {
|
|
27
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
-
}
|
|
29
|
-
var __export = (target, all) => {
|
|
30
|
-
for (var name in all)
|
|
31
|
-
__defProp(target, name, {
|
|
32
|
-
get: all[name],
|
|
33
|
-
enumerable: true,
|
|
34
|
-
configurable: true,
|
|
35
|
-
set: __exportSetter.bind(all, name)
|
|
36
|
-
});
|
|
37
|
-
};
|
|
38
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
39
|
-
|
|
40
|
-
// src/dom/element-data.ts
|
|
41
|
-
import { getElementDisposables } from "@hypen-space/core/disposable";
|
|
42
|
-
var elementDataMap = new WeakMap;
|
|
43
|
-
function getHypenData(element) {
|
|
44
|
-
let data = elementDataMap.get(element);
|
|
45
|
-
if (!data) {
|
|
46
|
-
data = {};
|
|
47
|
-
elementDataMap.set(element, data);
|
|
48
|
-
}
|
|
49
|
-
return data;
|
|
50
|
-
}
|
|
51
|
-
function hasHypenData(element) {
|
|
52
|
-
return elementDataMap.has(element);
|
|
53
|
-
}
|
|
54
|
-
function clearHypenData(element) {
|
|
55
|
-
elementDataMap.delete(element);
|
|
56
|
-
}
|
|
57
|
-
function getEngine(element) {
|
|
58
|
-
return getHypenData(element).engine;
|
|
59
|
-
}
|
|
60
|
-
function setEngine(element, engine) {
|
|
61
|
-
getHypenData(element).engine = engine;
|
|
62
|
-
}
|
|
63
|
-
function findEngine(element) {
|
|
64
|
-
let current = element;
|
|
65
|
-
while (current) {
|
|
66
|
-
const engine = getEngine(current);
|
|
67
|
-
if (engine)
|
|
68
|
-
return engine;
|
|
69
|
-
current = current.parentElement;
|
|
70
|
-
}
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
function getRegisteredEvents(element) {
|
|
74
|
-
const data = getHypenData(element);
|
|
75
|
-
if (!data.registeredEvents) {
|
|
76
|
-
data.registeredEvents = new Set;
|
|
77
|
-
}
|
|
78
|
-
return data.registeredEvents;
|
|
79
|
-
}
|
|
80
|
-
function isEventRegistered(element, eventKey) {
|
|
81
|
-
return getRegisteredEvents(element).has(eventKey);
|
|
82
|
-
}
|
|
83
|
-
function registerEvent(element, eventKey) {
|
|
84
|
-
getRegisteredEvents(element).add(eventKey);
|
|
85
|
-
}
|
|
86
|
-
function unregisterEvent(element, eventKey) {
|
|
87
|
-
getRegisteredEvents(element).delete(eventKey);
|
|
88
|
-
}
|
|
89
|
-
function getKeyTarget(element) {
|
|
90
|
-
return getHypenData(element).keyTarget;
|
|
91
|
-
}
|
|
92
|
-
function setKeyTarget(element, key) {
|
|
93
|
-
getHypenData(element).keyTarget = key;
|
|
94
|
-
}
|
|
95
|
-
function getMeta(element, key) {
|
|
96
|
-
return getHypenData(element).meta?.[key];
|
|
97
|
-
}
|
|
98
|
-
function setMeta(element, key, value) {
|
|
99
|
-
const data = getHypenData(element);
|
|
100
|
-
if (!data.meta) {
|
|
101
|
-
data.meta = {};
|
|
102
|
-
}
|
|
103
|
-
data.meta[key] = value;
|
|
104
|
-
}
|
|
105
|
-
function disposeHypenElement(element) {
|
|
106
|
-
try {
|
|
107
|
-
const disposables = getElementDisposables(element);
|
|
108
|
-
disposables.dispose();
|
|
109
|
-
} catch {}
|
|
110
|
-
clearHypenData(element);
|
|
111
|
-
}
|
|
112
|
-
var HYPEN_ENGINE_SYMBOL = Symbol.for("hypen.engine");
|
|
113
|
-
var REGISTERED_EVENTS_SYMBOL = Symbol.for("hypen.registeredEvents");
|
|
114
|
-
var KEY_TARGET_SYMBOL = Symbol.for("hypen.keyTarget");
|
|
115
|
-
function getLegacyEngine(element) {
|
|
116
|
-
const engine = getEngine(element);
|
|
117
|
-
if (engine)
|
|
118
|
-
return engine;
|
|
119
|
-
return element[HYPEN_ENGINE_SYMBOL] ?? element.__hypenEngine;
|
|
120
|
-
}
|
|
121
|
-
function setLegacyEngine(element, engine) {
|
|
122
|
-
setEngine(element, engine);
|
|
123
|
-
element.__hypenEngine = engine;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/dom/applicators/events.ts
|
|
127
|
-
import {
|
|
128
|
-
getElementDisposables as getElementDisposables2,
|
|
129
|
-
disposableListener,
|
|
130
|
-
disposableTimeout
|
|
131
|
-
} from "@hypen-space/core/disposable";
|
|
1
|
+
/**
|
|
2
|
+
* Event Applicators
|
|
3
|
+
*
|
|
4
|
+
* Handles event applicators like onClick, onPress, etc.
|
|
5
|
+
* Uses a factory pattern to reduce boilerplate and ensure consistency.
|
|
6
|
+
*/
|
|
7
|
+
import { getElementDisposables, disposableListener, disposableTimeout, } from "@hypen-space/core/disposable";
|
|
132
8
|
import { frameworkLoggers } from "@hypen-space/core/logger";
|
|
133
|
-
|
|
9
|
+
import { getEngine, getRegisteredEvents, registerEvent, unregisterEvent, getKeyTarget, setKeyTarget, } from "../element-data.js";
|
|
10
|
+
const log = frameworkLoggers.events;
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Utility Functions
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Convert Map or nested objects to plain objects
|
|
16
|
+
*/
|
|
134
17
|
function toPlainObject(value) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
18
|
+
if (value instanceof Map) {
|
|
19
|
+
const obj = {};
|
|
20
|
+
for (const [key, val] of value.entries()) {
|
|
21
|
+
obj[key] = toPlainObject(val);
|
|
22
|
+
}
|
|
23
|
+
return obj;
|
|
139
24
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (Array.isArray(value)) {
|
|
143
|
-
return value.map((item) => toPlainObject(item));
|
|
144
|
-
}
|
|
145
|
-
if (value && typeof value === "object") {
|
|
146
|
-
const obj = {};
|
|
147
|
-
for (const [key, val] of Object.entries(value)) {
|
|
148
|
-
obj[key] = toPlainObject(val);
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
return value.map((item) => toPlainObject(item));
|
|
149
27
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
28
|
+
if (value && typeof value === "object") {
|
|
29
|
+
const obj = {};
|
|
30
|
+
for (const [key, val] of Object.entries(value)) {
|
|
31
|
+
obj[key] = toPlainObject(val);
|
|
32
|
+
}
|
|
33
|
+
return obj;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
153
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Extract action name and custom payload from an applicator value
|
|
39
|
+
*/
|
|
154
40
|
function extractActionDetails(value) {
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
actionName = actionName.substring(8);
|
|
162
|
-
}
|
|
163
|
-
return { actionName, payload: {} };
|
|
164
|
-
}
|
|
165
|
-
if (value && typeof value === "object") {
|
|
166
|
-
const plain = toPlainObject(value);
|
|
167
|
-
const payload = {};
|
|
168
|
-
let actionName = null;
|
|
169
|
-
if (plain && typeof plain === "object") {
|
|
170
|
-
const actionValue = plain["0"];
|
|
171
|
-
if (typeof actionValue === "string" && actionValue.startsWith("@")) {
|
|
172
|
-
actionName = actionValue.substring(1);
|
|
41
|
+
// String format: "@actions.doSomething" or "@doSomething"
|
|
42
|
+
if (typeof value === "string") {
|
|
43
|
+
if (!value.startsWith("@")) {
|
|
44
|
+
return { actionName: null, payload: {} };
|
|
45
|
+
}
|
|
46
|
+
let actionName = value.substring(1);
|
|
173
47
|
if (actionName.startsWith("actions.")) {
|
|
174
|
-
|
|
48
|
+
actionName = actionName.substring(8);
|
|
175
49
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
50
|
+
return { actionName, payload: {} };
|
|
51
|
+
}
|
|
52
|
+
// Object format: { "0": "@actions.doSomething", "customKey": "value" }
|
|
53
|
+
if (value && typeof value === "object") {
|
|
54
|
+
const plain = toPlainObject(value);
|
|
55
|
+
const payload = {};
|
|
56
|
+
let actionName = null;
|
|
57
|
+
if (plain && typeof plain === "object") {
|
|
58
|
+
const actionValue = plain["0"];
|
|
59
|
+
if (typeof actionValue === "string" && actionValue.startsWith("@")) {
|
|
60
|
+
actionName = actionValue.substring(1);
|
|
61
|
+
if (actionName.startsWith("actions.")) {
|
|
62
|
+
actionName = actionName.substring(8);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const [key, val] of Object.entries(plain)) {
|
|
66
|
+
if (key !== "0") {
|
|
67
|
+
// If the key is numeric (like "1", "2") and the value is an object,
|
|
68
|
+
// merge the object's keys into the payload directly.
|
|
69
|
+
// This handles: .onClick("@actions.foo", { id: "123" })
|
|
70
|
+
// where the second positional arg becomes "1": { id: "123" }
|
|
71
|
+
if (/^\d+$/.test(key) && val && typeof val === "object" && !Array.isArray(val)) {
|
|
72
|
+
for (const [innerKey, innerVal] of Object.entries(val)) {
|
|
73
|
+
payload[innerKey] = innerVal;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
payload[key] = val;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
182
80
|
}
|
|
183
|
-
} else {
|
|
184
|
-
payload[key] = val;
|
|
185
|
-
}
|
|
186
81
|
}
|
|
187
|
-
|
|
82
|
+
return { actionName, payload };
|
|
188
83
|
}
|
|
189
|
-
return { actionName, payload };
|
|
190
|
-
}
|
|
191
|
-
return { actionName: null, payload: {} };
|
|
84
|
+
return { actionName: null, payload: {} };
|
|
192
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract relevant data from a DOM event
|
|
88
|
+
*/
|
|
193
89
|
function extractEventData(event, element) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
data.value = element.value;
|
|
213
|
-
}
|
|
214
|
-
if (element instanceof HTMLSelectElement) {
|
|
215
|
-
data.value = element.value;
|
|
216
|
-
data.selectedIndex = element.selectedIndex;
|
|
217
|
-
}
|
|
218
|
-
if (event.type === "submit" && element instanceof HTMLFormElement) {
|
|
219
|
-
data.formData = new FormData(element);
|
|
220
|
-
}
|
|
221
|
-
return data;
|
|
222
|
-
}
|
|
223
|
-
function createEventHandler(eventType, options = {}) {
|
|
224
|
-
return (element, value) => {
|
|
225
|
-
const { actionName, payload: customPayload } = extractActionDetails(value);
|
|
226
|
-
if (!actionName) {
|
|
227
|
-
log.warn(`${eventType} requires an action reference starting with @, got:`, value);
|
|
228
|
-
return;
|
|
90
|
+
const data = {
|
|
91
|
+
type: event.type,
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
};
|
|
94
|
+
// Mouse events
|
|
95
|
+
if (event instanceof MouseEvent) {
|
|
96
|
+
data.clientX = event.clientX;
|
|
97
|
+
data.clientY = event.clientY;
|
|
98
|
+
data.button = event.button;
|
|
99
|
+
}
|
|
100
|
+
// Keyboard events
|
|
101
|
+
if (event instanceof KeyboardEvent) {
|
|
102
|
+
data.key = event.key;
|
|
103
|
+
data.code = event.code;
|
|
104
|
+
data.ctrlKey = event.ctrlKey;
|
|
105
|
+
data.shiftKey = event.shiftKey;
|
|
106
|
+
data.altKey = event.altKey;
|
|
107
|
+
data.metaKey = event.metaKey;
|
|
229
108
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return;
|
|
109
|
+
// Input element values
|
|
110
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
111
|
+
data.value = element.value;
|
|
234
112
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
113
|
+
// Select element values
|
|
114
|
+
if (element instanceof HTMLSelectElement) {
|
|
115
|
+
data.value = element.value;
|
|
116
|
+
data.selectedIndex = element.selectedIndex;
|
|
117
|
+
}
|
|
118
|
+
// Form data
|
|
119
|
+
if (event.type === "submit" && element instanceof HTMLFormElement) {
|
|
120
|
+
data.formData = new FormData(element);
|
|
121
|
+
}
|
|
122
|
+
return data;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Capitalize first letter of a string
|
|
126
|
+
*/
|
|
127
|
+
function capitalize(str) {
|
|
128
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
129
|
+
}
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Event Handler Factory
|
|
132
|
+
// ============================================================================
|
|
133
|
+
/**
|
|
134
|
+
* Create an event handler applicator with common boilerplate
|
|
135
|
+
*/
|
|
136
|
+
function createEventHandler(eventType, options = {}) {
|
|
137
|
+
return (element, value) => {
|
|
138
|
+
const { actionName, payload: customPayload } = extractActionDetails(value);
|
|
139
|
+
if (!actionName) {
|
|
140
|
+
log.warn(`${eventType} requires an action reference starting with @, got:`, value);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const disposables = getElementDisposables(element);
|
|
144
|
+
// Track that we've registered this event type
|
|
145
|
+
// The disposable stack handles cleanup automatically
|
|
146
|
+
const eventKey = `${eventType}:${actionName}`;
|
|
147
|
+
if (getRegisteredEvents(element).has(eventKey)) {
|
|
148
|
+
// Already registered - skip to avoid duplicates
|
|
149
|
+
// This can happen during re-renders
|
|
150
|
+
return;
|
|
256
151
|
}
|
|
257
|
-
|
|
152
|
+
registerEvent(element, eventKey);
|
|
153
|
+
// Create throttle state if needed
|
|
154
|
+
let throttleTimer = null;
|
|
155
|
+
// Create the event listener
|
|
156
|
+
const listener = (event) => {
|
|
157
|
+
// Handle throttling
|
|
158
|
+
if (options.throttleMs && throttleTimer) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (options.throttleMs) {
|
|
162
|
+
throttleTimer = disposableTimeout(() => {
|
|
163
|
+
throttleTimer = null;
|
|
164
|
+
}, options.throttleMs);
|
|
165
|
+
}
|
|
166
|
+
// Handle preventDefault
|
|
167
|
+
if (options.preventDefault) {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
}
|
|
170
|
+
// Build payload
|
|
171
|
+
const payload = Object.keys(customPayload).length > 0
|
|
172
|
+
? { ...customPayload }
|
|
173
|
+
: options.extractPayload
|
|
174
|
+
? options.extractPayload(event, element)
|
|
175
|
+
: extractEventData(event, element);
|
|
176
|
+
// Dispatch to engine, catching any async rejections
|
|
177
|
+
const engine = getEngine(element);
|
|
178
|
+
if (engine) {
|
|
179
|
+
try {
|
|
180
|
+
engine.dispatchAction(actionName, payload);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
log.error(`Error dispatching action "${actionName}":`, err);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
// Register the listener using disposable pattern
|
|
188
|
+
disposables.add(disposableListener(element, eventType, listener, {
|
|
189
|
+
passive: options.passive,
|
|
190
|
+
}));
|
|
191
|
+
// Clean up registered events tracking on dispose
|
|
192
|
+
disposables.addCallback(() => {
|
|
193
|
+
unregisterEvent(element, eventKey);
|
|
194
|
+
if (throttleTimer) {
|
|
195
|
+
throttleTimer.dispose();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
258
198
|
};
|
|
259
|
-
disposables.add(disposableListener(element, eventType, listener, {
|
|
260
|
-
passive: options.passive
|
|
261
|
-
}));
|
|
262
|
-
disposables.addCallback(() => {
|
|
263
|
-
unregisterEvent(element, eventKey);
|
|
264
|
-
if (throttleTimer) {
|
|
265
|
-
throttleTimer.dispose();
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
};
|
|
269
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Create a keyboard event handler that filters by key
|
|
202
|
+
*/
|
|
270
203
|
function createKeyHandler(defaultKey = "Enter") {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
204
|
+
return (element, value) => {
|
|
205
|
+
const { actionName, payload: customPayload } = extractActionDetails(value);
|
|
206
|
+
if (!actionName) {
|
|
207
|
+
log.warn(`onKey requires an action reference starting with @, got:`, value);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const disposables = getElementDisposables(element);
|
|
211
|
+
const eventKey = `keydown:${actionName}:${defaultKey}`;
|
|
212
|
+
if (getRegisteredEvents(element).has(eventKey)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
registerEvent(element, eventKey);
|
|
216
|
+
// Get target key from element data or use default
|
|
217
|
+
const targetKey = getKeyTarget(element) || defaultKey;
|
|
218
|
+
const keyToMatch = targetKey.toLowerCase() === "return" ? "Enter" : targetKey;
|
|
219
|
+
const listener = (event) => {
|
|
220
|
+
const keyEvent = event;
|
|
221
|
+
if (keyEvent.key !== keyToMatch) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
const target = event.target;
|
|
226
|
+
const payload = Object.keys(customPayload).length > 0
|
|
227
|
+
? { ...customPayload }
|
|
228
|
+
: {
|
|
229
|
+
type: event.type,
|
|
230
|
+
timestamp: Date.now(),
|
|
231
|
+
key: keyEvent.key,
|
|
232
|
+
code: keyEvent.code,
|
|
233
|
+
value: target.value,
|
|
234
|
+
input: target.value,
|
|
235
|
+
ctrlKey: keyEvent.ctrlKey,
|
|
236
|
+
shiftKey: keyEvent.shiftKey,
|
|
237
|
+
altKey: keyEvent.altKey,
|
|
238
|
+
metaKey: keyEvent.metaKey,
|
|
239
|
+
};
|
|
240
|
+
const engine = getEngine(element);
|
|
241
|
+
if (engine) {
|
|
242
|
+
engine.dispatchAction(actionName, payload);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
disposables.add(disposableListener(element, "keydown", listener));
|
|
246
|
+
disposables.addCallback(() => {
|
|
247
|
+
unregisterEvent(element, eventKey);
|
|
248
|
+
});
|
|
308
249
|
};
|
|
309
|
-
disposables.add(disposableListener(element, "keydown", listener));
|
|
310
|
-
disposables.addCallback(() => {
|
|
311
|
-
unregisterEvent(element, eventKey);
|
|
312
|
-
});
|
|
313
|
-
};
|
|
314
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Create a long-click/long-press handler
|
|
253
|
+
*/
|
|
315
254
|
function createLongClickHandler(thresholdMs = 500) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
const disposables = getElementDisposables2(element);
|
|
323
|
-
const eventKey = `longclick:${actionName}`;
|
|
324
|
-
if (getRegisteredEvents(element).has(eventKey)) {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
registerEvent(element, eventKey);
|
|
328
|
-
let longClickTimer = null;
|
|
329
|
-
const downListener = (event) => {
|
|
330
|
-
const pointerEvent = event;
|
|
331
|
-
longClickTimer = disposableTimeout(() => {
|
|
332
|
-
const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
|
|
333
|
-
type: "longclick",
|
|
334
|
-
timestamp: Date.now(),
|
|
335
|
-
clientX: pointerEvent.clientX,
|
|
336
|
-
clientY: pointerEvent.clientY
|
|
337
|
-
};
|
|
338
|
-
const engine = getEngine(element);
|
|
339
|
-
if (engine) {
|
|
340
|
-
engine.dispatchAction(actionName, payload);
|
|
255
|
+
return (element, value) => {
|
|
256
|
+
const { actionName, payload: customPayload } = extractActionDetails(value);
|
|
257
|
+
if (!actionName) {
|
|
258
|
+
log.warn(`onLongClick requires an action reference starting with @, got:`, value);
|
|
259
|
+
return;
|
|
341
260
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
longClickTimer = null;
|
|
349
|
-
|
|
261
|
+
const disposables = getElementDisposables(element);
|
|
262
|
+
const eventKey = `longclick:${actionName}`;
|
|
263
|
+
if (getRegisteredEvents(element).has(eventKey)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
registerEvent(element, eventKey);
|
|
267
|
+
let longClickTimer = null;
|
|
268
|
+
const downListener = (event) => {
|
|
269
|
+
const pointerEvent = event;
|
|
270
|
+
longClickTimer = disposableTimeout(() => {
|
|
271
|
+
const payload = Object.keys(customPayload).length > 0
|
|
272
|
+
? { ...customPayload }
|
|
273
|
+
: {
|
|
274
|
+
type: "longclick",
|
|
275
|
+
timestamp: Date.now(),
|
|
276
|
+
clientX: pointerEvent.clientX,
|
|
277
|
+
clientY: pointerEvent.clientY,
|
|
278
|
+
};
|
|
279
|
+
const engine = getEngine(element);
|
|
280
|
+
if (engine) {
|
|
281
|
+
engine.dispatchAction(actionName, payload);
|
|
282
|
+
}
|
|
283
|
+
longClickTimer = null;
|
|
284
|
+
}, thresholdMs);
|
|
285
|
+
};
|
|
286
|
+
const cancelListener = () => {
|
|
287
|
+
if (longClickTimer) {
|
|
288
|
+
longClickTimer.dispose();
|
|
289
|
+
longClickTimer = null;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
disposables.add(disposableListener(element, "pointerdown", downListener));
|
|
293
|
+
disposables.add(disposableListener(element, "pointerup", cancelListener));
|
|
294
|
+
disposables.add(disposableListener(element, "pointerleave", cancelListener));
|
|
295
|
+
disposables.addCallback(() => {
|
|
296
|
+
unregisterEvent(element, eventKey);
|
|
297
|
+
cancelListener();
|
|
298
|
+
});
|
|
350
299
|
};
|
|
351
|
-
disposables.add(disposableListener(element, "pointerdown", downListener));
|
|
352
|
-
disposables.add(disposableListener(element, "pointerup", cancelListener));
|
|
353
|
-
disposables.add(disposableListener(element, "pointerleave", cancelListener));
|
|
354
|
-
disposables.addCallback(() => {
|
|
355
|
-
unregisterEvent(element, eventKey);
|
|
356
|
-
cancelListener();
|
|
357
|
-
});
|
|
358
|
-
};
|
|
359
300
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// Payload Extractors
|
|
303
|
+
// ============================================================================
|
|
304
|
+
const inputPayload = (event, element) => {
|
|
305
|
+
const target = element;
|
|
306
|
+
return {
|
|
307
|
+
type: event.type,
|
|
308
|
+
timestamp: Date.now(),
|
|
309
|
+
value: target.value,
|
|
310
|
+
input: target.value,
|
|
311
|
+
};
|
|
368
312
|
};
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
313
|
+
const scrollPayload = (_event, element) => {
|
|
314
|
+
const scrollTop = element.scrollTop;
|
|
315
|
+
const scrollHeight = element.scrollHeight;
|
|
316
|
+
const clientHeight = element.clientHeight;
|
|
317
|
+
const scrollPercentage = scrollHeight - clientHeight > 0
|
|
318
|
+
? (scrollTop / (scrollHeight - clientHeight)) * 100
|
|
319
|
+
: 0;
|
|
320
|
+
const nearBottom = scrollHeight - scrollTop - clientHeight < 100 || scrollPercentage > 90;
|
|
321
|
+
return {
|
|
322
|
+
type: "scroll",
|
|
323
|
+
timestamp: Date.now(),
|
|
324
|
+
scrollTop,
|
|
325
|
+
scrollLeft: element.scrollLeft,
|
|
326
|
+
scrollHeight,
|
|
327
|
+
scrollWidth: element.scrollWidth,
|
|
328
|
+
clientHeight,
|
|
329
|
+
clientWidth: element.clientWidth,
|
|
330
|
+
scrollPercentage: Math.round(scrollPercentage),
|
|
331
|
+
nearBottom,
|
|
332
|
+
atBottom: scrollHeight - scrollTop === clientHeight,
|
|
333
|
+
atTop: scrollTop === 0,
|
|
334
|
+
};
|
|
389
335
|
};
|
|
390
|
-
|
|
391
|
-
type: event.type,
|
|
392
|
-
timestamp: Date.now(),
|
|
393
|
-
value: element.value ?? undefined
|
|
394
|
-
});
|
|
395
|
-
var mousePayload = (event, _element) => {
|
|
396
|
-
const mouseEvent = event;
|
|
397
|
-
return {
|
|
336
|
+
const focusPayload = (event, element) => ({
|
|
398
337
|
type: event.type,
|
|
399
338
|
timestamp: Date.now(),
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
339
|
+
value: element.value ?? undefined,
|
|
340
|
+
});
|
|
341
|
+
const mousePayload = (event, _element) => {
|
|
342
|
+
const mouseEvent = event;
|
|
343
|
+
return {
|
|
344
|
+
type: event.type,
|
|
345
|
+
timestamp: Date.now(),
|
|
346
|
+
clientX: mouseEvent.clientX,
|
|
347
|
+
clientY: mouseEvent.clientY,
|
|
348
|
+
};
|
|
403
349
|
};
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
350
|
+
// ============================================================================
|
|
351
|
+
// Event Handlers Export
|
|
352
|
+
// ============================================================================
|
|
353
|
+
export const eventHandlers = {
|
|
354
|
+
// Basic click/press
|
|
355
|
+
onClick: createEventHandler("click"),
|
|
356
|
+
onPress: createEventHandler("click"), // Alias for mobile-style naming
|
|
357
|
+
// Form events
|
|
358
|
+
onChange: createEventHandler("change"),
|
|
359
|
+
onSubmit: createEventHandler("submit", { preventDefault: true }),
|
|
360
|
+
onInput: createEventHandler("input", { extractPayload: inputPayload }),
|
|
361
|
+
// Keyboard events
|
|
362
|
+
onKey: createKeyHandler("Enter"),
|
|
363
|
+
"onKey.key": (element, value) => {
|
|
364
|
+
// Store the target key for the action handler to use
|
|
365
|
+
setKeyTarget(element, String(value));
|
|
366
|
+
},
|
|
367
|
+
"onKey.action": createKeyHandler("Enter"),
|
|
368
|
+
// Scroll (throttled)
|
|
369
|
+
onScroll: createEventHandler("scroll", {
|
|
370
|
+
throttleMs: 100,
|
|
371
|
+
passive: true,
|
|
372
|
+
extractPayload: scrollPayload,
|
|
373
|
+
}),
|
|
374
|
+
// Long click/press
|
|
375
|
+
onLongClick: createLongClickHandler(500),
|
|
376
|
+
onLongPress: createLongClickHandler(500), // Alias for mobile-style naming
|
|
377
|
+
// Focus events
|
|
378
|
+
onFocus: createEventHandler("focus", { extractPayload: focusPayload }),
|
|
379
|
+
onBlur: createEventHandler("blur", { extractPayload: focusPayload }),
|
|
380
|
+
// Mouse hover events
|
|
381
|
+
onMouseEnter: createEventHandler("mouseenter", { extractPayload: mousePayload }),
|
|
382
|
+
onMouseLeave: createEventHandler("mouseleave", { extractPayload: mousePayload }),
|
|
383
|
+
// Two-way binding for .bind(@state.x)
|
|
384
|
+
bind: ((element, value) => {
|
|
385
|
+
const bindPath = typeof value === "string" ? value : null;
|
|
386
|
+
if (!bindPath)
|
|
387
|
+
return;
|
|
388
|
+
const disposables = getElementDisposables(element);
|
|
389
|
+
const eventKey = `bind:${bindPath}`;
|
|
390
|
+
if (getRegisteredEvents(element).has(eventKey))
|
|
391
|
+
return;
|
|
392
|
+
registerEvent(element, eventKey);
|
|
393
|
+
// Determine the target element, event type, and value extractor based on component type
|
|
394
|
+
const hypenType = element.dataset?.hypenType;
|
|
395
|
+
if (hypenType === "checkbox" || hypenType === "switch") {
|
|
396
|
+
// Checkbox/Switch: wrapper <label> containing <input type="checkbox">
|
|
397
|
+
const input = element.querySelector('input[type="checkbox"]');
|
|
398
|
+
if (!input)
|
|
399
|
+
return;
|
|
400
|
+
const listener = () => {
|
|
401
|
+
const engine = getEngine(element);
|
|
402
|
+
if (engine) {
|
|
403
|
+
engine.dispatchAction("__hypen_bind", {
|
|
404
|
+
path: bindPath,
|
|
405
|
+
value: input.checked,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
disposables.add(disposableListener(input, "change", listener, { passive: true }));
|
|
447
410
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
411
|
+
else if (element instanceof HTMLSelectElement) {
|
|
412
|
+
// Select: listen to change event, read .value
|
|
413
|
+
const listener = () => {
|
|
414
|
+
const engine = getEngine(element);
|
|
415
|
+
if (engine) {
|
|
416
|
+
engine.dispatchAction("__hypen_bind", {
|
|
417
|
+
path: bindPath,
|
|
418
|
+
value: element.value,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
disposables.add(disposableListener(element, "change", listener, { passive: true }));
|
|
458
423
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
424
|
+
else if (element instanceof HTMLInputElement ||
|
|
425
|
+
element instanceof HTMLTextAreaElement) {
|
|
426
|
+
// Input/Textarea: listen to input event, read .value
|
|
427
|
+
const listener = () => {
|
|
428
|
+
const engine = getEngine(element);
|
|
429
|
+
if (engine) {
|
|
430
|
+
engine.dispatchAction("__hypen_bind", {
|
|
431
|
+
path: bindPath,
|
|
432
|
+
value: element.value,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
disposables.add(disposableListener(element, "input", listener, { passive: true }));
|
|
469
437
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
export {
|
|
481
|
-
eventHandlers
|
|
438
|
+
else {
|
|
439
|
+
log.warn(`.bind() is not supported on element type "${element.dataset?.hypenType || element.tagName}". ` +
|
|
440
|
+
`Supported types: input, textarea, checkbox, switch, select.`);
|
|
441
|
+
unregisterEvent(element, eventKey);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
disposables.addCallback(() => unregisterEvent(element, eventKey));
|
|
445
|
+
}),
|
|
482
446
|
};
|
|
483
|
-
|
|
484
|
-
//# debugId=7925A87B5F9F454264756E2164756E21
|
|
447
|
+
//# sourceMappingURL=events.js.map
|