@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.
Files changed (2) hide show
  1. package/helium.js +55 -93
  2. 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, proxyCache] = [new Map(), new Map(), new WeakMap(), new WeakMap()];
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 === "GET" ? null : JSON.stringify(opts),
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
- get(target, prop, receiver) {
35
- const val = Reflect.get(target, prop, receiver);
36
- if (typeof val === "object" && val !== null) {
37
- if (!proxyCache.has(val)) proxyCache.set(val, new Proxy(val, handler));
38
- return proxyCache.get(val);
39
- }
40
- return val;
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
- return result;
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
- const [add, remove] = [[], []];
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
- // Track which state properties an expression depends on
95
- const trackDependencies = (fn, el) => {
78
+ const trackDependencies = (fn, el) => {
96
79
  const accessed = new Set();
97
- const trackingHandler = {
98
- get(target, prop, receiver) {
99
- if (prop !== Symbol.toStringTag && typeof prop === 'string') {
100
- accessed.add(prop);
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 = (el) => {
119
- const clean = (e) => {
120
- if (listeners.has(e)) {
121
- listeners.get(e).forEach(({receiver, eventName, handler}) => {
122
- receiver.removeEventListener(eventName, handler);
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 === 1) {
134
- if (element.hasAttribute?.("data-helium-processed")) return newBindings;
135
- element.setAttribute("data-helium-processed", "");
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 (["@html","data-he-html","@text","data-he-text","@bind","data-he-bind"].includes(name)) {
115
+ if (he(name,"text","html","bind")) {
148
116
  try {
149
117
  new Function(`let ${value}=1`);
150
- state[value] ||= ["@bind","data-he-bind"].includes(name) ? el.value : el.textContent;
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 (["@ref","data-he-ref"].includes(name)) refs.set("$" + value, el);
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 deps = trackDependencies(fn, el);
171
- const b = { el, prop: ["@text","data-he-text"].includes(name) ? "textContent" : "innerHTML", expr: value, fn };
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 (["@bind", "data-he-bind"].includes(name)) {
142
+ if (he(name,"bind")) {
177
143
  const inputType = el.type?.toLowerCase();
178
- const isCheckbox = inputType === "checkbox";
179
- const isRadio = inputType === "radio";
180
- const event = (isCheckbox || isRadio || el.tagName === "SELECT") ? "change" : "input";
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, expr: value, fn: compile(value, true) });
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 (["@hidden","@visible","data-he-hidden","data-he-visible"].includes(name)) {
196
- const fn = compile(`${["@hidden","data-he-hidden"].includes(name) ? "!" : ""}!(${value})`, true);
197
- const deps = trackDependencies(fn, el);
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
- deps.forEach(dep => addBinding(dep, b));
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 deps = trackDependencies(fn, el);
206
- const b = { el, prop: name.slice(1), expr: value, fn };
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 (["@init","data-he-init"].includes(name)) {
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 === " " ? "Space" : e.key === "Escape" ? "Esc" : 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 === 1) cleanup(node);
228
+ if (node.nodeType == 1) cleanup(node);
267
229
  }
268
230
  for (const node of m.addedNodes) {
269
- if (node.nodeType === 1 && !node.hasAttribute("data-helium-processed")) {
231
+ if (node.nodeType == 1 && !node.hasAttribute("data-he-p")) {
270
232
  processElements(node).forEach(applyBinding);
271
233
  }
272
234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daz4126/helium",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "main": "helium.js",
5
5
  "type": "module",
6
6
  "scripts": {