@daz4126/helium 0.24.0 → 0.26.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 +61 -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,46 @@ 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
+ const prefix = 'helium_' + 'modules/';
205
+ import(prefix + moduleName)
206
+ .then(module => {
207
+ Object.keys(module).forEach(key => state[key] = module[key]);
208
+ })
209
+ .catch(error => {
210
+ console.error(`Failed to import module: ${moduleName}`, error.message);
211
+ })
212
+ );
213
+ });
206
214
  }
207
- })
215
+ });
216
+
217
+ if (importPromises.length > 0) await Promise.all(importPromises);
208
218
 
209
219
  heElements.forEach(el => {
210
220
  HELIUM.processed.add(el);
211
221
 
212
222
  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()]);
223
+ const execFn = v => compile(v, true)($, state, {}, el, html, get, post, put, patch, del);
214
224
  const inputType = el.type?.toLowerCase();
215
225
  const isCheckbox = inputType == "checkbox", isRadio = inputType == "radio", isSelect = el.tagName == "SELECT";
216
226
 
@@ -230,7 +240,7 @@ async function processElements(element) {
230
240
  }
231
241
 
232
242
  // Process the attribute
233
- if (["@data", "data-he"].includes(name)) {
243
+ if (name === "@data" || name === "data-he") {
234
244
  Object.assign(state, parseEx(value));
235
245
  }
236
246
  else if (name.startsWith(":") || name.startsWith("data-he-attr:")) {
@@ -275,7 +285,7 @@ async function processElements(element) {
275
285
  const debounceMod = mods.find(m => m.startsWith("debounce"));
276
286
  const debounceDelay = debounceMod ? (t => t && !isNaN(t) ? Number(t) : 300)(debounceMod.split(":")[1]) : 0;
277
287
  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()])
288
+ const exFn = v => compile(v,true)($, state, e, el, html, get, post, put, patch, del)
279
289
  if (mods.includes("prevent")) e.preventDefault();
280
290
  const keyMods = {shift: "shiftKey", ctrl: "ctrlKey", alt: "altKey", meta: "metaKey"};
281
291
  for (const [mod, prop] of Object.entries(keyMods)) if (mods.includes(mod) && !e[prop]) return;
@@ -329,17 +339,23 @@ const action = pairs.map(([, action]) => action);
329
339
  }
330
340
 
331
341
  // Create new observer
332
- HELIUM.observer = new MutationObserver(ms => {
342
+ HELIUM.observer = new MutationObserver(async (ms) => {
333
343
  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));
344
+ m.removedNodes.forEach((n) => n.nodeType === 1 && cleanup(n));
345
+
346
+ for (const n of m.addedNodes) {
347
+ if (n.nodeType === 1 && !HELIUM.processed.has(n)) {
348
+ const bindings = await processElements(n);
349
+ bindings?.forEach(applyBinding);
350
+ }
351
+ }
336
352
  }
337
353
  });
338
354
  HELIUM.observer.observe(root, { childList: true, subtree: true });
339
355
 
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()])
356
+ const initialBindings = await processElements(root);
357
+ initialBindings.forEach(applyBinding);
358
+ if(initFn) initFn($, state, {}, {}, html, get, post, put, patch, del)
343
359
  }
344
360
 
345
361
  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.26.0",
4
4
  "main": "helium.js",
5
5
  "type": "module",
6
6
  "scripts": {