@daz4126/helium 0.10.0 → 0.10.2
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/helium.js +55 -93
- package/package.json +1 -1
package/helium.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export default function helium(data = {}) {
|
|
2
|
+
const he = (name,...args) => args.map(b => `|@${b}|data-he-${b}|`).join``.includes(`|${name}|`)
|
|
2
3
|
const root = document.querySelector("[\\@helium]") || document.querySelector("[data-helium]") || document.body;
|
|
3
|
-
const [bindings, refs, listeners
|
|
4
|
+
const [bindings, refs, listeners] = [new Map(), new Map(), new WeakMap()];
|
|
4
5
|
const $ = (s) => document.querySelector(s);
|
|
5
6
|
const html = (s) => {
|
|
6
7
|
const t = document.createElement("template");
|
|
@@ -19,11 +20,11 @@ export default function helium(data = {}) {
|
|
|
19
20
|
fetch(url, {
|
|
20
21
|
method,
|
|
21
22
|
headers,
|
|
22
|
-
body: method
|
|
23
|
+
body: method == "GET" ? null : JSON.stringify(opts),
|
|
23
24
|
credentials: "same-origin"
|
|
24
25
|
})
|
|
25
26
|
.then(r => r.headers.get("content-type")?.includes("application/json") ? r.json() : r.text())
|
|
26
|
-
.then(d => state[target] = d)
|
|
27
|
+
.then(d => {if(target) state[target] = d})
|
|
27
28
|
.catch(e => console.error("AJAX error:", e.message));
|
|
28
29
|
};
|
|
29
30
|
|
|
@@ -31,28 +32,16 @@ export default function helium(data = {}) {
|
|
|
31
32
|
const [post, put, patch, del] = ["POST","PUT","PATCH","DELETE"].map(m => (u, d, t) => ajax(u, m, t, d));
|
|
32
33
|
|
|
33
34
|
const handler = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
set(target, prop, val, receiver) {
|
|
43
|
-
const result = Reflect.set(target, prop, val, receiver);
|
|
44
|
-
if (target === data && bindings.has(prop)) {
|
|
45
|
-
bindings.get(prop).forEach(applyBinding);
|
|
46
|
-
} else {
|
|
47
|
-
for (const [key, bound] of bindings.entries()) {
|
|
48
|
-
if (key === Object.keys(data).find(k => data[k] === target)) {
|
|
49
|
-
bound.forEach(applyBinding);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
35
|
+
get(target, prop, receiver) {
|
|
36
|
+
const val = Reflect.get(target, prop, receiver);
|
|
37
|
+
return typeof val == "object" && val != null ? new Proxy(val, handler) : val;
|
|
38
|
+
},
|
|
39
|
+
set(target, prop, val) {
|
|
40
|
+
const result = Reflect.set(target, prop, val);
|
|
41
|
+
bindings.get(prop)?.forEach(applyBinding);
|
|
42
|
+
return result;
|
|
52
43
|
}
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
};
|
|
44
|
+
};
|
|
56
45
|
|
|
57
46
|
const state = new Proxy(data, handler);
|
|
58
47
|
let isUpdatingDOM = false;
|
|
@@ -62,15 +51,10 @@ export default function helium(data = {}) {
|
|
|
62
51
|
const result = fn($, state, event, elCtx, html, get, post, put, patch, del, ...Object.values(data), ...[...refs.values()]);
|
|
63
52
|
if (prop == "innerHTML") {
|
|
64
53
|
isUpdatingDOM = true;
|
|
65
|
-
el.innerHTML = result;
|
|
54
|
+
el.innerHTML = Array.isArray(result) ? result.join`` : result;
|
|
66
55
|
isUpdatingDOM = false;
|
|
67
56
|
} else if(prop == "class" && typeof result == "object"){
|
|
68
|
-
|
|
69
|
-
for (const [key, value] of Object.entries(result)) {
|
|
70
|
-
(value ? add : remove).push(key);
|
|
71
|
-
}
|
|
72
|
-
el.classList.add(...add);
|
|
73
|
-
el.classList.remove(...remove);
|
|
57
|
+
Object.entries(result).forEach(([k, v]) => el.classList.toggle(k, v));
|
|
74
58
|
} else if(prop == "style" && typeof result == "object"){
|
|
75
59
|
el.style = Object.entries(result).filter(([k, v]) => v).map(([k, v]) => `${k}:${v}`).join(";");
|
|
76
60
|
} else if (prop in el) {
|
|
@@ -91,63 +75,48 @@ export default function helium(data = {}) {
|
|
|
91
75
|
}
|
|
92
76
|
};
|
|
93
77
|
|
|
94
|
-
|
|
95
|
-
const trackDependencies = (fn, el) => {
|
|
78
|
+
const trackDependencies = (fn, el) => {
|
|
96
79
|
const accessed = new Set();
|
|
97
|
-
const
|
|
98
|
-
get(target, prop
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const val = Reflect.get(target, prop, receiver);
|
|
103
|
-
return typeof val === "object" && val !== null ? new Proxy(val, trackingHandler) : val;
|
|
80
|
+
const trackProxy = new Proxy(data, {
|
|
81
|
+
get(target, prop) {
|
|
82
|
+
if (typeof prop == 'string') accessed.add(prop);
|
|
83
|
+
const val = target[prop];
|
|
84
|
+
return typeof val == "object" && val != null ? new Proxy(val, this) : val;
|
|
104
85
|
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const trackingProxy = new Proxy(data, trackingHandler);
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
fn($, trackingProxy, {}, el, html, get, post, put, patch, del, ...Object.values(data), ...[...refs.values()]);
|
|
111
|
-
} catch (e) {
|
|
112
|
-
// Expression might fail on first run, that's ok
|
|
113
|
-
}
|
|
86
|
+
});
|
|
114
87
|
|
|
88
|
+
try { fn.call(null, $, trackProxy, refs); } catch {}
|
|
115
89
|
return [...accessed];
|
|
116
90
|
};
|
|
117
91
|
|
|
118
|
-
const cleanup =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
listeners.delete(e);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
clean(el);
|
|
128
|
-
el.querySelectorAll('*').forEach(clean);
|
|
92
|
+
const cleanup = el => {
|
|
93
|
+
[el, ...el.querySelectorAll('*')].forEach(e => {
|
|
94
|
+
listeners.get(e)?.forEach(({receiver, eventName, handler}) =>
|
|
95
|
+
receiver.removeEventListener(eventName, handler));
|
|
96
|
+
listeners.delete(e);
|
|
97
|
+
});
|
|
129
98
|
};
|
|
130
99
|
|
|
131
100
|
function processElements(element) {
|
|
132
101
|
const newBindings = [];
|
|
133
|
-
if (element.nodeType
|
|
134
|
-
if (element.hasAttribute?.("data-
|
|
135
|
-
element.setAttribute("data-
|
|
102
|
+
if (element.nodeType == 1) {
|
|
103
|
+
if (element.hasAttribute?.("data-he-p")) return newBindings;
|
|
104
|
+
element.setAttribute("data-he-p", "");
|
|
136
105
|
}
|
|
137
106
|
|
|
138
107
|
const heElements = [element, ...element.querySelectorAll("*")].filter(el =>
|
|
139
|
-
Array.from(el.attributes).some(attr =>
|
|
140
|
-
attr.name.startsWith("@") || attr.name.startsWith(":") || attr.name.startsWith("data-he-")
|
|
108
|
+
Array.from(el.attributes).some(attr => /^(@|:|data-he-)/.test(attr.name)
|
|
141
109
|
)
|
|
142
110
|
);
|
|
143
111
|
|
|
144
112
|
// Seed default state values
|
|
145
113
|
heElements.forEach(el => {
|
|
146
114
|
for (const { name, value } of el.attributes || []) {
|
|
147
|
-
if (
|
|
115
|
+
if (he(name,"text","html","bind")) {
|
|
148
116
|
try {
|
|
149
117
|
new Function(`let ${value}=1`);
|
|
150
|
-
|
|
118
|
+
const val = he(name,"bind") ? el.type == "checkbox" ? el.checked : el.value : el.textContent;
|
|
119
|
+
state[value] ||= Number.isNaN(Number(val)) ? val : Number(val);
|
|
151
120
|
} catch (e) {}
|
|
152
121
|
}
|
|
153
122
|
}
|
|
@@ -163,21 +132,18 @@ export default function helium(data = {}) {
|
|
|
163
132
|
const execFn = v => compile(v, true)($, state, {}, el, html, get, post, put, patch, del, ...Object.values(data), ...[...refs.values()]);
|
|
164
133
|
for (const { name, value } of el.attributes || []) {
|
|
165
134
|
if (["@data","data-he"].includes(name)) Object.assign(state, execFn(value));
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
if (["@text","data-he-text","@html","data-he-html"].includes(name)) {
|
|
135
|
+
if (he(name,"ref")) refs.set("$" + value, el);
|
|
136
|
+
if (he(name,"text","html")) {
|
|
169
137
|
const fn = compile(value, true);
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
deps.forEach(dep => addBinding(dep, b));
|
|
138
|
+
const b = { el, prop: he(name,"text") ? "textContent" : "innerHTML", fn };
|
|
139
|
+
trackDependencies(fn, el).forEach(dep => addBinding(dep, b));
|
|
174
140
|
}
|
|
175
141
|
|
|
176
|
-
if (
|
|
142
|
+
if (he(name,"bind")) {
|
|
177
143
|
const inputType = el.type?.toLowerCase();
|
|
178
|
-
const isCheckbox = inputType
|
|
179
|
-
const isRadio = inputType
|
|
180
|
-
const event = (isCheckbox || isRadio || el.tagName
|
|
144
|
+
const isCheckbox = inputType == "checkbox";
|
|
145
|
+
const isRadio = inputType == "radio";
|
|
146
|
+
const event = (isCheckbox || isRadio || el.tagName == "SELECT") ? "change" : "input";
|
|
181
147
|
const prop = isCheckbox ? "checked" : "value";
|
|
182
148
|
|
|
183
149
|
const inputHandler = e => state[value] = isCheckbox ? e.target.checked : e.target.value;
|
|
@@ -185,35 +151,31 @@ export default function helium(data = {}) {
|
|
|
185
151
|
if (!listeners.has(el)) listeners.set(el, []);
|
|
186
152
|
listeners.get(el).push({receiver: el, eventName: event, handler: inputHandler});
|
|
187
153
|
|
|
188
|
-
addBinding(value, { el, prop,
|
|
154
|
+
addBinding(value, { el, prop, fn: compile(value, true) });
|
|
189
155
|
|
|
190
156
|
if (isCheckbox) el.checked = !!state[value];
|
|
191
157
|
else if (isRadio) el.checked = el.value == state[value];
|
|
192
158
|
else el.value = state[value] ?? "";
|
|
193
159
|
}
|
|
194
160
|
|
|
195
|
-
if (
|
|
196
|
-
const fn = compile(`${
|
|
197
|
-
const
|
|
198
|
-
const b = { el, prop: "hidden", expr: value, fn };
|
|
161
|
+
if (he(name,"hidden","visible")) {
|
|
162
|
+
const fn = compile(`${he(name,"hidden") ? "!" : ""}!(${value})`, true);
|
|
163
|
+
const b = { el, prop: "hidden", fn };
|
|
199
164
|
|
|
200
|
-
|
|
165
|
+
trackDependencies(fn, el).forEach(dep => addBinding(dep, b));
|
|
201
166
|
}
|
|
202
167
|
|
|
203
168
|
if (name.startsWith(":")) {
|
|
204
169
|
const fn = compile(value, true);
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
deps.forEach(dep => addBinding(dep, b));
|
|
170
|
+
const b = { el, prop: name.slice(1), fn };
|
|
171
|
+
trackDependencies(fn, el).forEach(dep => addBinding(dep, b));
|
|
209
172
|
}
|
|
210
173
|
|
|
211
|
-
if (
|
|
174
|
+
if (he(name,"init")) {
|
|
212
175
|
compile(value, false)($, state, undefined, el, html, get, post, put, patch, del, ...Object.values(data), ...[...refs.values()]);
|
|
213
176
|
} else if (name.startsWith("@") || name.startsWith("data-he-on")) {
|
|
214
177
|
const [eventName, ...mods] = name.slice(name.startsWith("@") ? 1 : 10).split(".");
|
|
215
178
|
const receiver = mods.includes("outside") || mods.includes("document") ? document : el;
|
|
216
|
-
|
|
217
179
|
let debounceDelay = 0;
|
|
218
180
|
const debounceMod = mods.find(m => m.startsWith("debounce"));
|
|
219
181
|
if (debounceMod) {
|
|
@@ -230,7 +192,7 @@ export default function helium(data = {}) {
|
|
|
230
192
|
if (["keydown","keyup","keypress"].includes(eventName)) {
|
|
231
193
|
const last = mods[mods.length - 1];
|
|
232
194
|
if (last) {
|
|
233
|
-
let keyName = e.key
|
|
195
|
+
let keyName = e.key == " " ? "Space" : e.key == "Escape" ? "Esc" : e.key;
|
|
234
196
|
if (keyName.toLowerCase() !== last.toLowerCase()) return;
|
|
235
197
|
}
|
|
236
198
|
}
|
|
@@ -263,10 +225,10 @@ export default function helium(data = {}) {
|
|
|
263
225
|
if (isUpdatingDOM) return;
|
|
264
226
|
for (const m of mutations) {
|
|
265
227
|
for (const node of m.removedNodes) {
|
|
266
|
-
if (node.nodeType
|
|
228
|
+
if (node.nodeType == 1) cleanup(node);
|
|
267
229
|
}
|
|
268
230
|
for (const node of m.addedNodes) {
|
|
269
|
-
if (node.nodeType
|
|
231
|
+
if (node.nodeType == 1 && !node.hasAttribute("data-he-p")) {
|
|
270
232
|
processElements(node).forEach(applyBinding);
|
|
271
233
|
}
|
|
272
234
|
}
|