@daz4126/helium 0.12.0 → 0.14.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 +95 -23
- package/package.json +1 -1
package/helium.js
CHANGED
|
@@ -3,9 +3,10 @@ const getEvent = el => ({form:"submit",input:"input",textarea:"input",select:"ch
|
|
|
3
3
|
const debounce=(f,d)=>{let t;return(...a)=>(clearTimeout(t),t=setTimeout(f,d,...a))}
|
|
4
4
|
|
|
5
5
|
export default function helium(data = {}) {
|
|
6
|
+
let initFn;
|
|
6
7
|
const he = (n,...a) => a.map(b => `|@${b}|data-he-${b}|`).join``.includes(`|${n.split('.')[0]}|`);
|
|
7
8
|
const root = document.querySelector("[\\@helium]") || document.querySelector("[data-helium]") || document.body;
|
|
8
|
-
const [bindings, refs, listeners] = [new Map(), new Map(), new WeakMap()];
|
|
9
|
+
const [bindings, refs, listeners, processed, parentKeys] = [new Map(), new Map(), new WeakMap(), new WeakSet(), new WeakMap()];
|
|
9
10
|
const $ = s => document.querySelector(s);
|
|
10
11
|
const html = s => Object.assign(document.createElement("template"),{innerHTML:s.trim()}).content.firstChild
|
|
11
12
|
|
|
@@ -16,8 +17,7 @@ export default function helium(data = {}) {
|
|
|
16
17
|
action ? element[action=="replace"?"replaceWith":action](content) : element.innerHTML = content;
|
|
17
18
|
return content
|
|
18
19
|
} else state[target] = data
|
|
19
|
-
}
|
|
20
|
-
|
|
20
|
+
}
|
|
21
21
|
|
|
22
22
|
const ajax = (u,m,o={},p={}) => {
|
|
23
23
|
if(o.loading) o.target = update(o.loading,o.target,o.action) || o.target;
|
|
@@ -27,26 +27,87 @@ const ajax = (u,m,o={},p={}) => {
|
|
|
27
27
|
...(!fd&&m!=="GET"&&{"Content-Type":"application/json"}),
|
|
28
28
|
...(t&&{"X-CSRF-Token":t})
|
|
29
29
|
},body:m==="GET"?null:(fd?p:JSON.stringify(p)),credentials:"same-origin"})
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
.then(r => {
|
|
31
|
+
const type = r.headers.get("content-type") || "";
|
|
32
|
+
return (type.includes("turbo-stream") ? r.text().then(d => ({ t: true, d })) :
|
|
33
|
+
type.includes("json") ? r.json() :
|
|
34
|
+
r.text());
|
|
35
|
+
}).then(d =>
|
|
36
|
+
d.t && "Turbo"
|
|
37
|
+
? Turbo.renderStreamMessage(d.d)
|
|
38
|
+
: update(d, o.target, o.loading ? "replace" : o.action, o.template)
|
|
39
|
+
).catch(e => console.error("AJAX:", e.message));
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
const get = (u,t) => ajax(u,"GET",t);
|
|
36
43
|
const [post, put, patch, del] = ["POST","PUT","PATCH","DELETE"].map(m => (u, d, t) => ajax(u, m, t, d));
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
get: (t,p,r) => {
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
const handler = {
|
|
46
|
+
get: (t,p,r) => {
|
|
47
|
+
const v = Reflect.get(t,p,r);
|
|
48
|
+
if (typeof v==="object" && v!==null) {
|
|
49
|
+
const proxy = new Proxy(v, handler);
|
|
50
|
+
parentKeys.set(v, p);
|
|
51
|
+
return proxy;
|
|
52
|
+
}
|
|
53
|
+
return v;
|
|
54
|
+
},
|
|
55
|
+
set: (t,p,v) => {
|
|
56
|
+
const res = Reflect.set(t,p,v);
|
|
57
|
+
if (Array.isArray(t) && !isNaN(p)) {
|
|
58
|
+
const parentKey = parentKeys.get(t);
|
|
59
|
+
if (parentKey) bindings.get(parentKey)?.forEach(applyBinding);
|
|
60
|
+
}
|
|
61
|
+
bindings.get(p)?.forEach(applyBinding);
|
|
62
|
+
return res
|
|
63
|
+
}
|
|
64
|
+
};
|
|
42
65
|
|
|
43
66
|
const state = new Proxy(data, handler);
|
|
44
|
-
let isUpdatingDOM = false;
|
|
45
67
|
|
|
46
68
|
function applyBinding(b,e={},elCtx=b.el){
|
|
47
69
|
const {el,prop,fn}=b;
|
|
48
70
|
const r=fn($,state,e,elCtx,html,...Object.values(data),...[...refs.values()]);
|
|
49
|
-
if(prop==="innerHTML"
|
|
71
|
+
if(prop==="innerHTML" && Array.isArray(r) && el.children.length > 0){
|
|
72
|
+
const temp = html(`<${el.tagName.toLowerCase()}>${r.join``}</${el.tagName.toLowerCase()}>`);
|
|
73
|
+
const newChildren = [...temp.children];
|
|
74
|
+
|
|
75
|
+
const newItems = newChildren.map((child, idx) => ({
|
|
76
|
+
key: child.getAttribute("key") || child.dataset.key,
|
|
77
|
+
element: child,
|
|
78
|
+
index: idx
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
const existingItems = [...el.children].map((child, idx) => ({
|
|
82
|
+
key: child.getAttribute("key") || child.dataset.key,
|
|
83
|
+
element: child,
|
|
84
|
+
index: idx
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < Math.max(newItems.length, existingItems.length); i++) {
|
|
88
|
+
const existing = existingItems[i];
|
|
89
|
+
const newItem = newItems[i];
|
|
90
|
+
|
|
91
|
+
if (!newItem && existing) {
|
|
92
|
+
cleanup(existing.element);
|
|
93
|
+
existing.element.remove();
|
|
94
|
+
} else if (newItem && !existing) {
|
|
95
|
+
el.appendChild(newItem.element);
|
|
96
|
+
} else if (newItem && existing) {
|
|
97
|
+
if (existing.element.outerHTML !== newItem.element.outerHTML) {
|
|
98
|
+
if(typeof Idiomorph === "object") {
|
|
99
|
+
Idiomorph.morph(existing.element, newItem.element.outerHTML);
|
|
100
|
+
} else {
|
|
101
|
+
cleanup(existing.element);
|
|
102
|
+
existing.element.replaceWith(newItem.element);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else if(prop==="innerHTML"){
|
|
108
|
+
const content = Array.isArray(r)?r.join``:r
|
|
109
|
+
typeof Idiomorph === "object" ? Idiomorph.morph(el, content,{morphStyle:'innerHTML'}) : el.innerHTML = content;
|
|
110
|
+
}
|
|
50
111
|
else if(prop==="class"&&r&&typeof r==="object")for(const[k,v]of Object.entries(r))k.split(/\s+/).forEach(c=>el.classList.toggle(c,v));
|
|
51
112
|
else if(prop==="style"&&r&&typeof r==="object")el.style=Object.entries(r).filter(([,v])=>v).map(([k,v])=>`${k}:${v}`).join(";")
|
|
52
113
|
else if(prop in el){if(el.type==="radio")el.checked=el.value===r;else el[prop]=prop==="textContent"?r:parseEx(r)}
|
|
@@ -60,10 +121,19 @@ function applyBinding(b,e={},elCtx=b.el){
|
|
|
60
121
|
} catch {return () => expr}
|
|
61
122
|
};
|
|
62
123
|
|
|
63
|
-
const trackDependencies = fn => {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
124
|
+
const trackDependencies = (fn, el) => {
|
|
125
|
+
const accessed = new Set();
|
|
126
|
+
const trackProxy = new Proxy(data, {
|
|
127
|
+
get(target, prop) {
|
|
128
|
+
if (typeof prop == 'string') accessed.add(prop);
|
|
129
|
+
const val = target[prop];
|
|
130
|
+
return typeof val == "object" && val != null ? new Proxy(val, this) : val;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
try { fn.call(null, $, trackProxy, refs); } catch {}
|
|
135
|
+
return [...accessed];
|
|
136
|
+
};
|
|
67
137
|
|
|
68
138
|
const cleanup = el => {
|
|
69
139
|
[el,...el.querySelectorAll('*')].forEach(e => {
|
|
@@ -73,15 +143,14 @@ const trackDependencies = fn => {
|
|
|
73
143
|
};
|
|
74
144
|
|
|
75
145
|
function processElements(element) {
|
|
146
|
+
let count = 0
|
|
76
147
|
const newBindings = [];
|
|
77
|
-
if (element.nodeType == 1)
|
|
78
|
-
if (element.hasAttribute?.("data-he-p")) return newBindings;
|
|
79
|
-
element.setAttribute("data-he-p", "");
|
|
80
|
-
}
|
|
148
|
+
if (element.nodeType == 1 && processed.has(element)) return newBindings;
|
|
81
149
|
|
|
82
150
|
const heElements=[element,...element.querySelectorAll("*")].filter(e=>[...e.attributes].some(a=>/^(@|:|data-he)/.test(a.name)))
|
|
83
151
|
|
|
84
152
|
heElements.forEach(el => {
|
|
153
|
+
count++
|
|
85
154
|
for (const {name, value} of el.attributes || []) {
|
|
86
155
|
if (he(name,"text","html","bind")) {
|
|
87
156
|
try {
|
|
@@ -98,6 +167,9 @@ const trackDependencies = fn => {
|
|
|
98
167
|
};
|
|
99
168
|
|
|
100
169
|
heElements.forEach(el => {
|
|
170
|
+
if (processed.has(el)) return;
|
|
171
|
+
processed.add(el);
|
|
172
|
+
|
|
101
173
|
const execFn = v => compile(v, true)($, state, {}, el, html,get,post, put,patch,del, ...Object.values(data), ...[...refs.values()]);
|
|
102
174
|
const inputType = el.type?.toLowerCase();
|
|
103
175
|
const isCheckbox = inputType == "checkbox", isRadio = inputType == "radio", isSelect = el.tagName == "SELECT";
|
|
@@ -131,7 +203,7 @@ const trackDependencies = fn => {
|
|
|
131
203
|
trackDependencies(fn, el).forEach(dep => addBinding(dep, {el, prop: name.slice(1), fn}));
|
|
132
204
|
}
|
|
133
205
|
if (he(name,"init")) {
|
|
134
|
-
|
|
206
|
+
initFn = compile(value,true);
|
|
135
207
|
} else if (name.startsWith("@") || name.startsWith("data-he")) {
|
|
136
208
|
const fullName = name.startsWith("@") ? name.slice(1) : name.slice(8);
|
|
137
209
|
const [eventName, ...mods] = fullName.split(".");
|
|
@@ -186,12 +258,12 @@ const trackDependencies = fn => {
|
|
|
186
258
|
return newBindings;
|
|
187
259
|
}
|
|
188
260
|
new MutationObserver(ms=>{
|
|
189
|
-
if(isUpdatingDOM)return;
|
|
190
261
|
for(const m of ms){
|
|
191
262
|
m.removedNodes.forEach(n=>n.nodeType===1&&cleanup(n));
|
|
192
|
-
m.addedNodes.forEach(n=>n.nodeType===1&&!
|
|
263
|
+
m.addedNodes.forEach(n=>n.nodeType===1&&!processed.has(n)&&processElements(n).forEach(applyBinding));
|
|
193
264
|
}
|
|
194
265
|
}).observe(root,{childList:1,subtree:1});
|
|
195
266
|
processElements(root);
|
|
196
267
|
for (const [key, items] of bindings.entries()) items.forEach(applyBinding);
|
|
268
|
+
if(initFn) initFn($, state, {}, {}, html,get,post, put,patch,del, ...Object.values(data), ...[...refs.values()])
|
|
197
269
|
}
|