@daz4126/helium 1.0.0-beta.2 → 1.0.0-beta.4

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-lite.js CHANGED
@@ -4,19 +4,47 @@
4
4
  * No AJAX, no @import, no SSE, no Turbo
5
5
  */
6
6
 
7
- const parseEx = v => { try { return Function(`return(${v})`)() } catch { return v } };
8
- const tryNum = v => { const t = String(v).trim(), n = +t; return t && !isNaN(n) ? n : v };
9
- const isValidIdentifier = v => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(v);
10
- const RESERVED = new Set(['undefined', 'null', 'true', 'false', 'NaN', 'Infinity', 'this', 'arguments']);
11
- const KEY_MODS = { shift: "shiftKey", ctrl: "ctrlKey", alt: "altKey", meta: "metaKey" };
12
- const debounce = (f, d) => { let t; return (...a) => (clearTimeout(t), t = setTimeout(f, d, ...a)) };
7
+ const parseEx = (v) => {
8
+ try {
9
+ return Function(`return(${v})`)();
10
+ } catch {
11
+ return v;
12
+ }
13
+ };
14
+ const tryNum = (v) => {
15
+ const t = String(v).trim(),
16
+ n = +t;
17
+ return t && !isNaN(n) ? n : v;
18
+ };
19
+ const isValidIdentifier = (v) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(v);
20
+ const RESERVED = new Set([
21
+ "undefined",
22
+ "null",
23
+ "true",
24
+ "false",
25
+ "NaN",
26
+ "Infinity",
27
+ "this",
28
+ "arguments",
29
+ ]);
30
+ const KEY_MODS = {
31
+ shift: "shiftKey",
32
+ ctrl: "ctrlKey",
33
+ alt: "altKey",
34
+ meta: "metaKey",
35
+ };
36
+ const debounce = (f, d) => {
37
+ let t;
38
+ return (...a) => (clearTimeout(t), (t = setTimeout(f, d, ...a)));
39
+ };
13
40
 
14
41
  const ATTR_REGEX = /^(@|:|data-he)/;
15
42
 
16
43
  const match = (name, ...attrs) => {
17
44
  const p = name.split(/[.:]/)[0];
18
- if (p === ":" || p === "") return false;
19
- return attrs.some(a => p === `@${a}` || p === `data-he-${a}`);
45
+ return p == ":" || p == ""
46
+ ? false
47
+ : attrs.some((a) => p === `@${a}` || p === `data-he-${a}`);
20
48
  };
21
49
 
22
50
  let HELIUM = null;
@@ -24,8 +52,13 @@ let HELIUM = null;
24
52
  async function helium(initialState = {}) {
25
53
  const inits = [];
26
54
  const ALL = Symbol("all");
27
- const root = document.querySelector('[\\@helium]') || document.querySelector('[data-helium]') || document.body;
28
- const storageKey = root.getAttribute('@local-storage') || root.getAttribute('data-he-local-storage');
55
+ const root =
56
+ document.querySelector("[\\@helium]") ||
57
+ document.querySelector("[data-helium]") ||
58
+ document.body;
59
+ const storageKey =
60
+ root.getAttribute("@local-storage") ||
61
+ root.getAttribute("data-he-local-storage");
29
62
 
30
63
  if (!HELIUM) {
31
64
  HELIUM = {
@@ -36,12 +69,14 @@ async function helium(initialState = {}) {
36
69
  processed: new WeakSet(),
37
70
  parentKeys: new WeakMap(),
38
71
  fnCache: new Map(),
39
- proxyCache: new WeakMap()
72
+ proxyCache: new WeakMap(),
40
73
  };
41
74
  }
42
75
 
43
- const $ = s => document.querySelector(s);
44
- const html = s => Object.assign(document.createElement("template"), { innerHTML: s.trim() }).content.firstChild;
76
+ const $ = (s) => document.querySelector(s);
77
+ const html = (s) =>
78
+ Object.assign(document.createElement("template"), { innerHTML: s.trim() })
79
+ .content.firstChild;
45
80
 
46
81
  const applyingBindings = new Set();
47
82
 
@@ -57,9 +92,9 @@ async function helium(initialState = {}) {
57
92
 
58
93
  const handler = {
59
94
  has(t, p) {
60
- if (typeof p === 'string' && p.startsWith('$')) return false;
95
+ if (typeof p === "string" && p.startsWith("$")) return false;
61
96
  if (Reflect.has(t, p)) return true;
62
- if (typeof globalThis[p] !== 'undefined') return false;
97
+ if (typeof globalThis[p] !== "undefined") return false;
63
98
  return true;
64
99
  },
65
100
  get(t, p, r) {
@@ -81,21 +116,16 @@ async function helium(initialState = {}) {
81
116
  HELIUM.bindings.get(ALL)?.forEach(safeApplyBinding);
82
117
  if (storageKey) localStorage.setItem(storageKey, JSON.stringify(state));
83
118
  return res;
84
- }
119
+ },
85
120
  };
86
121
 
87
122
  const state = new Proxy({}, handler);
88
- if (storageKey) try { Object.assign(state, JSON.parse(localStorage.getItem(storageKey))); } catch {}
123
+ if (storageKey)
124
+ try {
125
+ Object.assign(state, JSON.parse(localStorage.getItem(storageKey)));
126
+ } catch {}
89
127
  Object.assign(state, initialState);
90
128
 
91
- const getRefsObject = () => {
92
- const refs = {};
93
- for (const [name, element] of HELIUM.refs) {
94
- refs[name] = element;
95
- }
96
- return refs;
97
- };
98
-
99
129
  const createScope = (el, event = {}) => {
100
130
  return {
101
131
  $,
@@ -103,7 +133,7 @@ async function helium(initialState = {}) {
103
133
  $event: event,
104
134
  $el: el,
105
135
  $html: html,
106
- ...getRefsObject()
136
+ ...Object.fromEntries(HELIUM.refs),
107
137
  };
108
138
  };
109
139
 
@@ -115,7 +145,7 @@ async function helium(initialState = {}) {
115
145
  "$scope",
116
146
  withReturn
117
147
  ? `with($scope){with($scope.$data){return(${expr.trim()})}}`
118
- : `with($scope){with($scope.$data){${expr.trim()}}}`
148
+ : `with($scope){with($scope.$data){${expr.trim()}}}`,
119
149
  );
120
150
  const compiled = { execute: (scope) => fn(scope), getIds: null };
121
151
  HELIUM.fnCache.set(key, compiled);
@@ -136,18 +166,22 @@ async function helium(initialState = {}) {
136
166
  if (prop === "innerHTML") {
137
167
  const content = Array.isArray(r) ? r.join`` : r;
138
168
  return typeof Idiomorph === "object"
139
- ? Idiomorph.morph(el, content, { morphStyle: 'innerHTML' })
140
- : el.innerHTML = content;
169
+ ? Idiomorph.morph(el, content, { morphStyle: "innerHTML" })
170
+ : (el.innerHTML = content);
141
171
  }
142
172
  if (prop === "class" && r && typeof r === "object")
143
173
  return Object.entries(r).forEach(([k, v]) =>
144
- k.split(/\s+/).forEach(c => el.classList.toggle(c, v)));
174
+ k.split(/\s+/).forEach((c) => el.classList.toggle(c, v)),
175
+ );
145
176
 
146
177
  if (prop === "style" && r && typeof r === "object")
147
- return el.style.cssText = Object.entries(r)
178
+ return (el.style.cssText = Object.entries(r)
148
179
  .filter(([_, v]) => v != null && v !== false)
149
- .map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}:${v}`)
150
- .join("; ");
180
+ .map(
181
+ ([k, v]) =>
182
+ `${k.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase())}:${v}`,
183
+ )
184
+ .join("; "));
151
185
 
152
186
  if (prop === "hidden") {
153
187
  el.hidden = isHiddenAttr ? !!r : !r;
@@ -156,8 +190,9 @@ async function helium(initialState = {}) {
156
190
 
157
191
  if (prop in el) {
158
192
  if (isRadio && prop !== "checked") el.checked = el.value === r;
159
- else if (el.type === "radio" && prop !== "checked") el.checked = el.value === r;
160
- else el[prop] = prop === "textContent" ? (r ?? '') : parseEx(r);
193
+ else if (el.type === "radio" && prop !== "checked")
194
+ el.checked = el.value === r;
195
+ else el[prop] = prop === "textContent" ? (r ?? "") : parseEx(r);
161
196
  return;
162
197
  }
163
198
 
@@ -171,12 +206,16 @@ async function helium(initialState = {}) {
171
206
  return true;
172
207
  },
173
208
  get(target, prop) {
174
- if (typeof prop == 'string') {
175
- excludeChanged ? !accessed.has(prop) && accessed.set(prop, target[prop]) : accessed.add(prop);
209
+ if (typeof prop == "string") {
210
+ excludeChanged
211
+ ? !accessed.has(prop) && accessed.set(prop, target[prop])
212
+ : accessed.add(prop);
176
213
  }
177
214
  const val = target[prop];
178
- return typeof val == "object" && val != null ? new Proxy(val, this) : val;
179
- }
215
+ return typeof val == "object" && val != null
216
+ ? new Proxy(val, this)
217
+ : val;
218
+ },
180
219
  });
181
220
 
182
221
  try {
@@ -186,21 +225,29 @@ async function helium(initialState = {}) {
186
225
  $event: {},
187
226
  $el: el,
188
227
  $html: html,
189
- ...getRefsObject()
228
+ ...Object.fromEntries(HELIUM.refs),
190
229
  };
191
230
  compiled.execute(scope);
192
231
  } catch {}
193
232
 
194
- return excludeChanged ? [...accessed.keys()].filter(prop => state[prop] === accessed.get(prop)) : [...accessed];
233
+ return excludeChanged
234
+ ? [...accessed.keys()].filter(
235
+ (prop) => state[prop] === accessed.get(prop),
236
+ )
237
+ : [...accessed];
195
238
  };
196
239
 
197
240
  const getDependencies = (compiled, el, excludeChanged = false) => {
198
241
  return trackDependenciesProxy(compiled, el, excludeChanged);
199
242
  };
200
243
 
201
- const cleanup = el => {
202
- [el, ...el.querySelectorAll('*')].forEach(e => {
203
- HELIUM.listeners.get(e)?.forEach(({ receiver, event, handler }) => receiver.removeEventListener(event, handler));
244
+ const cleanup = (el) => {
245
+ [el, ...el.querySelectorAll("*")].forEach((e) => {
246
+ HELIUM.listeners
247
+ .get(e)
248
+ ?.forEach(({ receiver, event, handler }) =>
249
+ receiver.removeEventListener(event, handler),
250
+ );
204
251
  HELIUM.listeners.delete(e);
205
252
  });
206
253
  };
@@ -209,91 +256,154 @@ async function helium(initialState = {}) {
209
256
  const newBindings = [];
210
257
  const deferredBindings = [];
211
258
 
212
- const heElements = [element, ...element.querySelectorAll("*")].filter(e => {
213
- if (HELIUM.processed.has(e)) return false;
214
- for (let i = 0; i < e.attributes.length; i++) {
215
- if (ATTR_REGEX.test(e.attributes[i].name)) return true;
216
- }
217
- return false;
218
- });
259
+ const heElements = [element, ...element.querySelectorAll("*")].filter(
260
+ (e) => {
261
+ if (HELIUM.processed.has(e)) return false;
262
+ for (let i = 0; i < e.attributes.length; i++) {
263
+ if (ATTR_REGEX.test(e.attributes[i].name)) return true;
264
+ }
265
+ return false;
266
+ },
267
+ );
219
268
 
220
269
  const addBinding = (val, b) => {
221
- HELIUM.bindings.set(val, b.calc ? [b, ...(HELIUM.bindings.get(val) || [])] : [...(HELIUM.bindings.get(val) || []), b]);
270
+ HELIUM.bindings.set(
271
+ val,
272
+ b.calc
273
+ ? [b, ...(HELIUM.bindings.get(val) || [])]
274
+ : [...(HELIUM.bindings.get(val) || []), b],
275
+ );
222
276
  b.calc ? newBindings.unshift(b) : newBindings.push(b);
223
277
  };
224
278
 
225
- heElements.forEach(el => {
279
+ heElements.forEach((el) => {
226
280
  HELIUM.processed.add(el);
227
281
 
228
282
  const attrs = el.attributes;
229
283
  const inputType = el.type?.toLowerCase();
230
- const isCheckbox = inputType == "checkbox", isRadio = inputType == "radio", isSelect = el.tagName == "SELECT";
284
+ const isCheckbox = inputType == "checkbox",
285
+ isRadio = inputType == "radio",
286
+ isSelect = el.tagName == "SELECT";
231
287
 
232
288
  for (let i = 0; i < attrs.length; i++) {
233
289
  const { name, value } = attrs[i];
234
290
 
235
- if (!name.startsWith('@') && !name.startsWith(':') && !name.startsWith('data-he')) continue;
291
+ if (
292
+ !name.startsWith("@") &&
293
+ !name.startsWith(":") &&
294
+ !name.startsWith("data-he")
295
+ )
296
+ continue;
236
297
 
237
298
  // Initialize state first if needed (skip reserved words, only if not already set)
238
- if (match(name, "text", "html", "bind") && isValidIdentifier(value) && !RESERVED.has(value) && state[value] == null) {
239
- state[value] = match(name, "bind") ? (el.type == "checkbox" ? el.checked : tryNum(el.value)) : tryNum(el.textContent);
299
+ if (
300
+ match(name, "text", "html", "bind") &&
301
+ isValidIdentifier(value) &&
302
+ !RESERVED.has(value) &&
303
+ state[value] == null
304
+ ) {
305
+ state[value] = match(name, "bind")
306
+ ? el.type == "checkbox"
307
+ ? el.checked
308
+ : tryNum(el.value)
309
+ : tryNum(el.textContent);
240
310
  }
241
311
 
242
312
  // Process the attribute
243
313
  if (name === "@data" || name === "data-he") {
244
314
  Object.assign(state, parseEx(value));
245
- }
246
- else if (name.startsWith(":") || name.startsWith("data-he-attr:")) {
247
- const propName = name.startsWith(":") ? name.slice(1) : name.slice(13);
248
- deferredBindings.push({ el, prop: propName, compiled: compile(value, true) });
249
- }
250
- else if (match(name, "ref")) {
315
+ } else if (name.startsWith(":") || name.startsWith("data-he-attr:")) {
316
+ const propName = name.startsWith(":")
317
+ ? name.slice(1)
318
+ : name.slice(13);
319
+ deferredBindings.push({
320
+ el,
321
+ prop: propName,
322
+ compiled: compile(value, true),
323
+ });
324
+ } else if (match(name, "ref")) {
251
325
  HELIUM.refs.set("$" + value, el);
252
- }
253
- else if (match(name, "text", "html")) {
254
- deferredBindings.push({ el, prop: match(name, "text") ? "textContent" : "innerHTML", compiled: compile(value, true) });
255
- }
256
- else if (match(name, "bind")) {
257
- const event = (isCheckbox || isRadio || isSelect) ? "change" : "input";
326
+ } else if (match(name, "text", "html")) {
327
+ deferredBindings.push({
328
+ el,
329
+ prop: match(name, "text") ? "textContent" : "innerHTML",
330
+ compiled: compile(value, true),
331
+ });
332
+ } else if (match(name, "bind")) {
333
+ const event = isCheckbox || isRadio || isSelect ? "change" : "input";
258
334
  const prop = isCheckbox ? "checked" : "value";
259
- const inputHandler = e => state[value] = isCheckbox ? e.target.checked : e.target.value;
335
+ const inputHandler = (e) =>
336
+ (state[value] = isCheckbox ? e.target.checked : e.target.value);
260
337
  el.addEventListener(event, inputHandler);
261
338
  if (!HELIUM.listeners.has(el)) HELIUM.listeners.set(el, []);
262
- HELIUM.listeners.get(el).push({ receiver: el, event, handler: inputHandler });
263
- deferredBindings.push({ el, prop, compiled: compile(value, true), isRadio });
339
+ HELIUM.listeners
340
+ .get(el)
341
+ .push({ receiver: el, event, handler: inputHandler });
342
+ deferredBindings.push({
343
+ el,
344
+ prop,
345
+ compiled: compile(value, true),
346
+ isRadio,
347
+ });
264
348
  if (isCheckbox) el.checked = !!state[value];
265
349
  else if (isRadio) el.checked = el.value == state[value];
266
350
  else el.value = state[value] ?? "";
267
- }
268
- else if (match(name, "hidden", "visible")) {
351
+ } else if (match(name, "hidden", "visible")) {
269
352
  const isHidden = match(name, "hidden");
270
- deferredBindings.push({ el, prop: "hidden", compiled: compile(value, true), isHiddenAttr: isHidden });
271
- }
272
- else if (match(name, "calculate")) {
353
+ deferredBindings.push({
354
+ el,
355
+ prop: "hidden",
356
+ compiled: compile(value, true),
357
+ isHiddenAttr: isHidden,
358
+ });
359
+ } else if (match(name, "calculate")) {
273
360
  const calcName = name.split(":")[1];
274
361
  if (!(calcName in state)) state[calcName] = undefined;
275
- deferredBindings.push({ el, calc: calcName, prop: null, compiled: compile(value, true) });
276
- }
277
- else if (match(name, "effect")) {
278
- deferredBindings.push({ el, prop: null, compiled: compile(value, true), keys: name.split(":").slice(1) });
279
- }
280
- else if (match(name, "init")) {
362
+ deferredBindings.push({
363
+ el,
364
+ calc: calcName,
365
+ prop: null,
366
+ compiled: compile(value, true),
367
+ });
368
+ } else if (match(name, "effect")) {
369
+ deferredBindings.push({
370
+ el,
371
+ prop: null,
372
+ compiled: compile(value, true),
373
+ keys: name.split(":").slice(1),
374
+ });
375
+ } else if (match(name, "init")) {
281
376
  inits.push({ compiled: compile(value, true), el });
282
- }
283
- else if (name.startsWith("@") || name.startsWith("data-he")) {
377
+ } else if (name.startsWith("@") || name.startsWith("data-he")) {
284
378
  const fullName = name.startsWith("@") ? name.slice(1) : name.slice(8);
285
379
  const [event, ...mods] = fullName.split(".");
286
- const receiver = mods.includes("outside") || mods.includes("document") ? document : el;
287
- const debounceMod = mods.find(m => m.startsWith("debounce"));
288
- const debounceDelay = debounceMod ? (t => t && !isNaN(t) ? Number(t) : 300)(debounceMod.split(":")[1]) : 0;
289
- const _handler = e => {
380
+ const receiver =
381
+ mods.includes("outside") || mods.includes("document")
382
+ ? document
383
+ : el;
384
+ const debounceMod = mods.find((m) => m.startsWith("debounce"));
385
+ const debounceDelay = debounceMod
386
+ ? ((t) => (t && !isNaN(t) ? Number(t) : 300))(
387
+ debounceMod.split(":")[1],
388
+ )
389
+ : 0;
390
+ const _handler = (e) => {
290
391
  if (mods.includes("prevent")) e.preventDefault();
291
392
  if (mods.includes("stop")) e.stopPropagation();
292
- for (const [mod, prop] of Object.entries(KEY_MODS)) if (mods.includes(mod) && !e[prop]) return;
393
+ for (const [mod, prop] of Object.entries(KEY_MODS))
394
+ if (mods.includes(mod) && !e[prop]) return;
293
395
  if (["keydown", "keyup", "keypress"].includes(event)) {
294
396
  const last = mods[mods.length - 1];
295
- if (last && !["prevent", "once", "outside", "document", "stop"].includes(last) && !last.startsWith("debounce") && !Object.keys(KEY_MODS).includes(last)) {
296
- const keyName = e.key == " " ? "Space" : e.key == "Escape" ? "Esc" : e.key;
397
+ if (
398
+ last &&
399
+ !["prevent", "once", "outside", "document", "stop"].includes(
400
+ last,
401
+ ) &&
402
+ !last.startsWith("debounce") &&
403
+ !Object.keys(KEY_MODS).includes(last)
404
+ ) {
405
+ const keyName =
406
+ e.key == " " ? "Space" : e.key == "Escape" ? "Esc" : e.key;
297
407
  if (keyName.toLowerCase() !== last.toLowerCase()) return;
298
408
  }
299
409
  }
@@ -302,10 +412,14 @@ async function helium(initialState = {}) {
302
412
  const compiled = compile(value, true);
303
413
  compiled.execute(scope);
304
414
  }
305
- if (mods.includes("once")) receiver.removeEventListener(event, handler);
415
+ if (mods.includes("once"))
416
+ receiver.removeEventListener(event, handler);
306
417
  };
307
- const handler = debounceDelay > 0 ? debounce(_handler, debounceDelay) : _handler;
308
- const eventOptions = mods.includes("once") ? { once: true } : undefined;
418
+ const handler =
419
+ debounceDelay > 0 ? debounce(_handler, debounceDelay) : _handler;
420
+ const eventOptions = mods.includes("once")
421
+ ? { once: true }
422
+ : undefined;
309
423
  receiver.addEventListener(event, handler, eventOptions);
310
424
  if (!HELIUM.listeners.has(el)) HELIUM.listeners.set(el, []);
311
425
  HELIUM.listeners.get(el).push({ receiver, event, handler });
@@ -313,9 +427,11 @@ async function helium(initialState = {}) {
313
427
  }
314
428
  });
315
429
 
316
- deferredBindings.forEach(b => {
317
- const tracked = b.keys?.includes("*") ? [ALL] : getDependencies(b.compiled, b.el, true).concat(b.keys || []);
318
- tracked.forEach(key => addBinding(key, b));
430
+ deferredBindings.forEach((b) => {
431
+ const tracked = b.keys?.includes("*")
432
+ ? [ALL]
433
+ : getDependencies(b.compiled, b.el, true).concat(b.keys || []);
434
+ tracked.forEach((key) => addBinding(key, b));
319
435
  if (tracked.length === 0) {
320
436
  newBindings.push(b);
321
437
  }
@@ -355,13 +471,13 @@ function heliumTeardown() {
355
471
  }
356
472
 
357
473
  // Browser globals
358
- if (typeof window !== 'undefined') {
474
+ if (typeof window !== "undefined") {
359
475
  window.helium = helium;
360
476
  window.heliumTeardown = heliumTeardown;
361
477
  }
362
478
 
363
479
  // Auto-initialize
364
- if (typeof document !== 'undefined') {
480
+ if (typeof document !== "undefined") {
365
481
  document.addEventListener("DOMContentLoaded", () => helium());
366
482
  }
367
483
 
package/helium.js CHANGED
@@ -46,11 +46,9 @@ const defaultEngine = {
46
46
  }
47
47
  };
48
48
 
49
- // Factory function to create helium with custom engine and prefixes
49
+ // Factory function to create helium with custom engine
50
50
  export function createHelium(options = {}) {
51
51
  const engine = options.engine ?? defaultEngine;
52
- const prefix = options.prefix ?? 'he'; // 'he' or 'xe'
53
- const rootAttr = options.rootAttr ?? 'helium'; // 'helium' or 'xenon'
54
52
 
55
53
  // Engine-aware expression parser (CSP-safe when using jexpr engine)
56
54
  const evalExpr = v => {
@@ -61,13 +59,10 @@ export function createHelium(options = {}) {
61
59
  } catch { return v; }
62
60
  };
63
61
 
64
- // Build prefix-aware regex and matcher
65
- const ATTR_REGEX = new RegExp(`^(@|:|data-${prefix})`);
66
-
67
62
  const match = (name, ...attrs) => {
68
63
  const p = name.split(/[.:]/)[0];
69
64
  if (p === ":" || p === "") return false;
70
- return attrs.some(a => p === `@${a}` || p === `data-${prefix}-${a}`);
65
+ return attrs.some(a => p === `@${a}` || p === 'data-he-' + a);
71
66
  };
72
67
 
73
68
  // Single global object to hold all settings (scoped to this instance)
@@ -76,8 +71,8 @@ export function createHelium(options = {}) {
76
71
  async function helium(initialState = {}) {
77
72
  const inits = [];
78
73
  const ALL = Symbol("all");
79
- const root = document.querySelector(`[\\@${rootAttr}]`) || document.querySelector(`[data-${rootAttr}]`) || document.body;
80
- const storageKey = root.getAttribute('@local-storage') || root.getAttribute(`data-${prefix}-local-storage`);
74
+ const root = document.querySelector('[\\@helium]') || document.querySelector('[data-helium]') || document.body;
75
+ const storageKey = root.getAttribute('@local-storage') || root.getAttribute('data-he-local-storage');
81
76
 
82
77
  // Initialize or reuse HELIUM object
83
78
  if (!HELIUM) {
@@ -249,15 +244,6 @@ export function createHelium(options = {}) {
249
244
  // Merge initial state
250
245
  Object.assign(state, initialState);
251
246
 
252
- // Build refs object for scope
253
- const getRefsObject = () => {
254
- const refs = {};
255
- for (const [name, element] of HELIUM.refs) {
256
- refs[name] = element;
257
- }
258
- return refs;
259
- };
260
-
261
247
  // Create scope for expression execution
262
248
  const createScope = (el, event = {}) => {
263
249
  return engine.createScope({
@@ -271,7 +257,7 @@ export function createHelium(options = {}) {
271
257
  put,
272
258
  patch,
273
259
  del,
274
- refs: getRefsObject()
260
+ refs: Object.fromEntries(HELIUM.refs)
275
261
  });
276
262
  };
277
263
 
@@ -354,7 +340,7 @@ export function createHelium(options = {}) {
354
340
  put,
355
341
  patch,
356
342
  del,
357
- refs: getRefsObject()
343
+ refs: Object.fromEntries(HELIUM.refs)
358
344
  });
359
345
  compiled.execute(scope);
360
346
  } catch {}
@@ -387,7 +373,8 @@ export function createHelium(options = {}) {
387
373
  const heElements = [element, ...element.querySelectorAll("*")].filter(e => {
388
374
  if (HELIUM.processed.has(e)) return false;
389
375
  for (let i = 0; i < e.attributes.length; i++) {
390
- if (ATTR_REGEX.test(e.attributes[i].name)) return true;
376
+ const n = e.attributes[i].name;
377
+ if (n[0] === '@' || n[0] === ':' || n.startsWith('data-he')) return true;
391
378
  }
392
379
  return false;
393
380
  });
@@ -400,7 +387,7 @@ export function createHelium(options = {}) {
400
387
  const importPromises = [];
401
388
 
402
389
  heElements.forEach((el) => {
403
- const importAttr = el.getAttribute("@import") || el.getAttribute(`data-${prefix}-import`);
390
+ const importAttr = el.getAttribute("@import") || el.getAttribute('data-he-import');
404
391
  if (importAttr) {
405
392
  importAttr.split(",").map((m) => m.trim()).forEach((moduleName) => {
406
393
  // Build path: use as-is for URLs, otherwise resolve relative to document location
@@ -408,9 +395,7 @@ export function createHelium(options = {}) {
408
395
  const hasExtension = moduleName.endsWith(".js");
409
396
  const hasPathPrefix = moduleName.startsWith("/") || moduleName.startsWith("./") || moduleName.startsWith("../");
410
397
  const relativePath = (isUrl || hasPathPrefix ? "" : "./") + moduleName + (isUrl || hasExtension ? "" : ".js");
411
- // Ensure base URL ends with / so relative paths resolve correctly
412
- const baseUrl = location.href.endsWith('/') || location.href.includes('.html') ? location.href : location.href + '/';
413
- const path = isUrl ? relativePath : new URL(relativePath, baseUrl).href;
398
+ const path = isUrl ? moduleName : new URL(relativePath, location.href).href;
414
399
  importPromises.push(
415
400
  import(path)
416
401
  .then((module) => {
@@ -448,7 +433,7 @@ export function createHelium(options = {}) {
448
433
  const {name, value} = attrs[i];
449
434
 
450
435
  // Skip non-helium attributes early
451
- if (!name.startsWith('@') && !name.startsWith(':') && !name.startsWith(`data-${prefix}`)) continue;
436
+ if (name[0] !== '@' && name[0] !== ':' && !name.startsWith('data-he')) continue;
452
437
 
453
438
  // Initialize state first if needed (skip reserved words, only if not already set)
454
439
  if (match(name, "text", "html", "bind") && isValidIdentifier(value) && !RESERVED.has(value) && state[value] == null) {
@@ -456,11 +441,11 @@ export function createHelium(options = {}) {
456
441
  }
457
442
 
458
443
  // Process the attribute
459
- if (name === "@data" || name === `data-${prefix}`) {
444
+ if (name === "@data" || name === 'data-he') {
460
445
  Object.assign(state, evalExpr(value));
461
446
  }
462
- else if (name.startsWith(":") || name.startsWith(`data-${prefix}-attr:`)) {
463
- const propName = name.startsWith(":") ? name.slice(1) : name.slice(prefix.length + 11);
447
+ else if (name.startsWith(":") || name.startsWith('data-he-attr:')) {
448
+ const propName = name.startsWith(":") ? name.slice(1) : name.slice(13);
464
449
  deferredBindings.push({el, prop: propName, compiled: compile(value, true)});
465
450
  }
466
451
  else if (match(name, "ref")) {
@@ -496,8 +481,8 @@ export function createHelium(options = {}) {
496
481
  else if (match(name, "init")) {
497
482
  inits.push({ compiled: compile(value, true), el });
498
483
  }
499
- else if (name.startsWith("@") || name.startsWith(`data-${prefix}`)) {
500
- const fullName = name.startsWith("@") ? name.slice(1) : name.slice(prefix.length + 6);
484
+ else if (name.startsWith("@") || name.startsWith('data-he')) {
485
+ const fullName = name.startsWith("@") ? name.slice(1) : name.slice(8);
501
486
  const [eventName, ...mods] = fullName.split(".");
502
487
  const isHttpMethod = ["get", "post", "put", "patch", "delete"].includes(eventName);
503
488
  const event = isHttpMethod ? getEvent(el) : eventName;
@@ -517,7 +502,7 @@ export function createHelium(options = {}) {
517
502
  }
518
503
  if (!mods.includes("outside") || !el.contains(e.target)) {
519
504
  if (isHttpMethod) {
520
- const getAttr = name => el.getAttribute(`data-${prefix}-${name}`) || el.getAttribute(`@${name}`);
505
+ const getAttr = a => el.getAttribute('data-he-' + a) || el.getAttribute('@' + a);
521
506
  const pairs = (getAttr('target') || "").split(",").map(p => p.split(":").map(s => s.trim()));
522
507
  const target = pairs.map(([target]) => target);
523
508
  const action = pairs.map(([, action]) => action);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daz4126/helium",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "main": "helium.js",
5
5
  "type": "module",
6
6
  "exports": {