@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.
Files changed (2) hide show
  1. package/helium.js +60 -45
  2. 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 getEvent = el => ({form:"submit",input:"input",textarea:"input",select:"change"}[el.tagName.toLowerCase()]||"click")
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 he = (n,...a) => {
12
- const prefix = n.split(/[.:]/)[0];
13
- if (prefix === ":" || prefix === "") return false;
14
- return a.map(b => `|@${b}|data-he-${b}|`).join``.includes(`|${prefix}|`);
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 && "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,...Object.values(state),...[...HELIUM.refs.values()]);
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).map(([k,v])=>v?`${k}:${v}`:'').join(";");
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
- try {
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
- const cleanup = el => {
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
- const heElements = [element, ...element.querySelectorAll("*")]
188
- .filter(e => !HELIUM.processed.has(e) && [...e.attributes].some(a => /^(@|:|data-he)/.test(a.name)));
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
- heElements.forEach(async el => {
197
+ const importPromises = [];
198
+
199
+ heElements.forEach(el => {
196
200
  const importAttr = el.getAttribute("@import") || el.getAttribute("data-he-import");
197
- if (importAttr) {
198
- importAttr.split(",").map(m => m.trim()).forEach(async moduleName => {
199
- try {
200
- const module = await import(`helium_modules/${moduleName}.js`);
201
- Object.keys(module).forEach(key => state[key] = module[key]);
202
- } catch (error) {
203
- console.error(`Failed to import module: ${moduleName}`, error.message);
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, ...Object.values(state), ...[...HELIUM.refs.values()]);
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 (["@data", "data-he"].includes(name)) {
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, ...Object.values(state), ...[...HELIUM.refs.values()])
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
- m.addedNodes.forEach(n => n.nodeType === 1 && !HELIUM.processed.has(n) && processElements(n).forEach(applyBinding));
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
- for (const [key, items] of HELIUM.bindings.entries()) items.forEach(applyBinding);
342
- if(initFn) initFn($, state, {}, {}, html, get, post, put, patch, del, ...Object.values(state), ...[...HELIUM.refs.values()])
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daz4126/helium",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "main": "helium.js",
5
5
  "type": "module",
6
6
  "scripts": {