@daz4126/helium 0.24.0 → 0.25.0
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 +60 -45
- package/package.json +1 -1
package/helium.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
const parseEx=v=>{try{return Function(`return(${v})`)()}catch{return v}}
|
|
2
|
-
const
|
|
2
|
+
const INPUT_EVENTS = { form: "submit", input: "input", textarea:"input", select:"change" };
|
|
3
|
+
const getEvent = el => INPUT_EVENTS[el.tagName.toLowerCase()] || "click";
|
|
3
4
|
const debounce=(f,d)=>{let t;return(...a)=>(clearTimeout(t),t=setTimeout(f,d,...a))}
|
|
4
5
|
|
|
5
6
|
// Single global object to hold all settings
|
|
6
7
|
let HELIUM = null;
|
|
7
8
|
|
|
8
|
-
window.helium = function() {
|
|
9
|
+
window.helium = async function() {
|
|
9
10
|
let initFn;
|
|
10
11
|
const ALL = Symbol("all");
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const HE_ATTR_REGEX = /^(@|:|data-he)/;
|
|
13
|
+
const he = (n, ...a) => {
|
|
14
|
+
const prefix = n.split(/[.:]/)[0];
|
|
15
|
+
if (prefix === ":" || prefix === "") return false;
|
|
16
|
+
for (const attr of a) {
|
|
17
|
+
if (prefix === `@${attr}` || prefix === `data-he-${attr}`) return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
16
21
|
const root = document.querySelector("[\\@helium]") || document.querySelector("[data-helium]") || document.body;
|
|
17
22
|
|
|
18
23
|
// Initialize or reuse HELIUM object
|
|
@@ -66,7 +71,7 @@ fetch(url, {
|
|
|
66
71
|
type.includes("json") ? res.json() :
|
|
67
72
|
res.text());
|
|
68
73
|
}).then(data =>
|
|
69
|
-
data.turbo &&
|
|
74
|
+
(data.turbo && window.Turbo)
|
|
70
75
|
? Turbo.renderStreamMessage(data.data)
|
|
71
76
|
: update(data, options.target, options.loading ? options.action.map(a => a && "replace") : options.action, options.template)
|
|
72
77
|
).catch(e => console.error("AJAX:", e.message));
|
|
@@ -103,7 +108,7 @@ const handler = {
|
|
|
103
108
|
|
|
104
109
|
function applyBinding(b,e={},elCtx=b.el){
|
|
105
110
|
const {el,prop,fn,calc}=b;
|
|
106
|
-
const r=fn($,state,e,elCtx,html
|
|
111
|
+
const r=fn($,state,e,elCtx,html,get,post,put,patch,del);
|
|
107
112
|
if (calc) state[calc] = r
|
|
108
113
|
|
|
109
114
|
if (prop==="innerHTML") {
|
|
@@ -117,7 +122,10 @@ function applyBinding(b,e={},elCtx=b.el){
|
|
|
117
122
|
k.split(/\s+/).forEach(c => el.classList.toggle(c,v)));
|
|
118
123
|
|
|
119
124
|
if (prop==="style" && r && typeof r==="object")
|
|
120
|
-
return el.style.cssText = Object.entries(r)
|
|
125
|
+
return el.style.cssText = Object.entries(r)
|
|
126
|
+
.filter(([_, v]) => v != null && v !== false)
|
|
127
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
128
|
+
.join("; ");
|
|
121
129
|
|
|
122
130
|
if (prop in el) {
|
|
123
131
|
if (el.type === "radio" && prop != "checked") el.checked = el.value===r;
|
|
@@ -134,7 +142,6 @@ const compile = (expr, withReturn = false) => {
|
|
|
134
142
|
try {
|
|
135
143
|
const fn = new Function(
|
|
136
144
|
"$","$data","$event","$el","$html","$get","$post","$put","$patch","$delete",
|
|
137
|
-
...Object.keys(state), ...[...HELIUM.refs.keys()],
|
|
138
145
|
withReturn
|
|
139
146
|
? `with($data){return(${expr.trim()})}`
|
|
140
147
|
: `with($data){${expr.trim()}}`
|
|
@@ -142,17 +149,7 @@ const compile = (expr, withReturn = false) => {
|
|
|
142
149
|
HELIUM.fnCache.set(key, fn);
|
|
143
150
|
return fn;
|
|
144
151
|
} catch {
|
|
145
|
-
|
|
146
|
-
const fn = new Function(
|
|
147
|
-
"$","$data","$event","$el","$html","$get","$post","$put","$patch","$delete",
|
|
148
|
-
...Object.keys(state), ...[...HELIUM.refs.keys()],
|
|
149
|
-
`with($data){${expr.trim()}}`
|
|
150
|
-
);
|
|
151
|
-
HELIUM.fnCache.set(key, fn);
|
|
152
|
-
return fn;
|
|
153
|
-
} catch {
|
|
154
|
-
return () => expr;
|
|
155
|
-
}
|
|
152
|
+
return () => expr;
|
|
156
153
|
}
|
|
157
154
|
};
|
|
158
155
|
|
|
@@ -173,7 +170,7 @@ const trackDependencies = (fn, el, excludeChanged = false) => {
|
|
|
173
170
|
return excludeChanged ? [...accessed.keys()].filter(prop => state[prop] === accessed.get(prop)) : [...accessed];
|
|
174
171
|
};
|
|
175
172
|
|
|
176
|
-
|
|
173
|
+
const cleanup = el => {
|
|
177
174
|
[el,...el.querySelectorAll('*')].forEach(e => {
|
|
178
175
|
HELIUM.listeners.get(e)?.forEach(({receiver,event,handler}) => receiver.removeEventListener(event,handler));
|
|
179
176
|
HELIUM.listeners.delete(e);
|
|
@@ -184,33 +181,45 @@ async function processElements(element) {
|
|
|
184
181
|
const newBindings = [];
|
|
185
182
|
const deferredBindings = [];
|
|
186
183
|
|
|
187
|
-
|
|
188
|
-
|
|
184
|
+
const heElements = [element, ...element.querySelectorAll("*")].filter(e => {
|
|
185
|
+
if (HELIUM.processed.has(e)) return false;
|
|
186
|
+
for (let i = 0; i < e.attributes.length; i++) {
|
|
187
|
+
if (HE_ATTR_REGEX.test(e.attributes[i].name)) return true;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
});
|
|
189
191
|
|
|
190
192
|
const addBinding = (val, b) => {
|
|
191
193
|
HELIUM.bindings.set(val, b.calc ? [b,...(HELIUM.bindings.get(val) || [])] : [...(HELIUM.bindings.get(val) || []), b]);
|
|
192
194
|
b.calc ? newBindings.unshift(b) : newBindings.push(b);
|
|
193
195
|
};
|
|
194
196
|
|
|
195
|
-
|
|
197
|
+
const importPromises = [];
|
|
198
|
+
|
|
199
|
+
heElements.forEach(el => {
|
|
196
200
|
const importAttr = el.getAttribute("@import") || el.getAttribute("data-he-import");
|
|
197
|
-
if (importAttr) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
201
|
+
if (importAttr) {
|
|
202
|
+
importAttr.split(",").map(m => m.trim()).forEach(moduleName => {
|
|
203
|
+
importPromises.push(
|
|
204
|
+
import(`helium_modules/${moduleName}`)
|
|
205
|
+
.then(module => {
|
|
206
|
+
Object.keys(module).forEach(key => state[key] = module[key]);
|
|
207
|
+
})
|
|
208
|
+
.catch(error => {
|
|
209
|
+
console.error(`Failed to import module: ${moduleName}`, error.message);
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
});
|
|
206
213
|
}
|
|
207
|
-
})
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (importPromises.length > 0) await Promise.all(importPromises);
|
|
208
217
|
|
|
209
218
|
heElements.forEach(el => {
|
|
210
219
|
HELIUM.processed.add(el);
|
|
211
220
|
|
|
212
221
|
const attrs = el.attributes;
|
|
213
|
-
const execFn = v => compile(v, true)($, state, {}, el, html, get, post, put, patch, del
|
|
222
|
+
const execFn = v => compile(v, true)($, state, {}, el, html, get, post, put, patch, del);
|
|
214
223
|
const inputType = el.type?.toLowerCase();
|
|
215
224
|
const isCheckbox = inputType == "checkbox", isRadio = inputType == "radio", isSelect = el.tagName == "SELECT";
|
|
216
225
|
|
|
@@ -230,7 +239,7 @@ async function processElements(element) {
|
|
|
230
239
|
}
|
|
231
240
|
|
|
232
241
|
// Process the attribute
|
|
233
|
-
if (
|
|
242
|
+
if (name === "@data" || name === "data-he") {
|
|
234
243
|
Object.assign(state, parseEx(value));
|
|
235
244
|
}
|
|
236
245
|
else if (name.startsWith(":") || name.startsWith("data-he-attr:")) {
|
|
@@ -275,7 +284,7 @@ async function processElements(element) {
|
|
|
275
284
|
const debounceMod = mods.find(m => m.startsWith("debounce"));
|
|
276
285
|
const debounceDelay = debounceMod ? (t => t && !isNaN(t) ? Number(t) : 300)(debounceMod.split(":")[1]) : 0;
|
|
277
286
|
const _handler = e => {
|
|
278
|
-
const exFn = v => compile(v,true)($, state, e, el, html, get, post, put, patch, del
|
|
287
|
+
const exFn = v => compile(v,true)($, state, e, el, html, get, post, put, patch, del)
|
|
279
288
|
if (mods.includes("prevent")) e.preventDefault();
|
|
280
289
|
const keyMods = {shift: "shiftKey", ctrl: "ctrlKey", alt: "altKey", meta: "metaKey"};
|
|
281
290
|
for (const [mod, prop] of Object.entries(keyMods)) if (mods.includes(mod) && !e[prop]) return;
|
|
@@ -329,17 +338,23 @@ const action = pairs.map(([, action]) => action);
|
|
|
329
338
|
}
|
|
330
339
|
|
|
331
340
|
// Create new observer
|
|
332
|
-
HELIUM.observer = new MutationObserver(ms => {
|
|
341
|
+
HELIUM.observer = new MutationObserver(async (ms) => {
|
|
333
342
|
for (const m of ms) {
|
|
334
|
-
m.removedNodes.forEach(n => n.nodeType === 1 && cleanup(n));
|
|
335
|
-
|
|
343
|
+
m.removedNodes.forEach((n) => n.nodeType === 1 && cleanup(n));
|
|
344
|
+
|
|
345
|
+
for (const n of m.addedNodes) {
|
|
346
|
+
if (n.nodeType === 1 && !HELIUM.processed.has(n)) {
|
|
347
|
+
const bindings = await processElements(n);
|
|
348
|
+
bindings?.forEach(applyBinding);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
336
351
|
}
|
|
337
352
|
});
|
|
338
353
|
HELIUM.observer.observe(root, { childList: true, subtree: true });
|
|
339
354
|
|
|
340
|
-
processElements(root);
|
|
341
|
-
|
|
342
|
-
if(initFn) initFn($, state, {}, {}, html, get, post, put, patch, del
|
|
355
|
+
const initialBindings = await processElements(root);
|
|
356
|
+
initialBindings.forEach(applyBinding);
|
|
357
|
+
if(initFn) initFn($, state, {}, {}, html, get, post, put, patch, del)
|
|
343
358
|
}
|
|
344
359
|
|
|
345
360
|
window.heliumTeardown = function() {
|