tw-framework 1.0.6 → 1.0.7
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/dist/hydration-runtime.js +67 -5
- package/dist/testing.cjs +6878 -431
- package/dist/tw.mjs +811 -487
- package/package.json +1 -1
|
@@ -23,10 +23,18 @@
|
|
|
23
23
|
try { var c = globalThis.__twClient; return (c && c.$page) || {}; } catch (e) { return {}; }
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
var warnedExprs = {};
|
|
26
27
|
function evalExpr(expr) {
|
|
27
28
|
try {
|
|
28
29
|
return (new Function("state", "tw", "with (tw) { with (state) { return (" + expr + "); } }"))(state, twClientScope());
|
|
29
30
|
} catch (e) {
|
|
31
|
+
// Silent failures were a debugging nightmare: a typo in an
|
|
32
|
+
// expression just blanked every span it touched. Warn once per
|
|
33
|
+
// expression, then stay quiet.
|
|
34
|
+
if (!warnedExprs[expr]) {
|
|
35
|
+
warnedExprs[expr] = 1;
|
|
36
|
+
try { console.warn("[tw] expression failed:", expr, "-", e.message); } catch (we) { /* ignore */ }
|
|
37
|
+
}
|
|
30
38
|
return undefined;
|
|
31
39
|
}
|
|
32
40
|
}
|
|
@@ -129,6 +137,11 @@
|
|
|
129
137
|
}
|
|
130
138
|
el.__twRefs = [];
|
|
131
139
|
var html = "";
|
|
140
|
+
// The loop var is BORROWED state space: save the user's own value
|
|
141
|
+
// (if any) and restore it after -- the old code deleted it, so a
|
|
142
|
+
// loop over a var named like an existing state key destroyed it.
|
|
143
|
+
var __twPrevV = state[v];
|
|
144
|
+
var __twHadV = Object.prototype.hasOwnProperty.call(state, v);
|
|
132
145
|
for (var k = 0; k < arr.length; k++) {
|
|
133
146
|
var ref = "__tw_item_" + (++twItemSeq);
|
|
134
147
|
state[ref] = arr[k];
|
|
@@ -136,7 +149,8 @@
|
|
|
136
149
|
state[v] = arr[k];
|
|
137
150
|
html += renderTemplateString(tpl, v, ref);
|
|
138
151
|
}
|
|
139
|
-
|
|
152
|
+
if (__twHadV) state[v] = __twPrevV;
|
|
153
|
+
else delete state[v];
|
|
140
154
|
if (el.__twLen !== arr.length + ":" + html.length || el.__twHtml !== html) {
|
|
141
155
|
el.innerHTML = html;
|
|
142
156
|
el.__twLen = arr.length + ":" + html.length;
|
|
@@ -157,7 +171,15 @@
|
|
|
157
171
|
var key = inputs[j].getAttribute("data-tw-model");
|
|
158
172
|
if (key in state) {
|
|
159
173
|
var v = state[key];
|
|
160
|
-
|
|
174
|
+
var isCheck = inputs[j].type === "checkbox" || inputs[j].type === "radio";
|
|
175
|
+
if (isCheck) {
|
|
176
|
+
// Boolean binding for checkboxes/radios (round 4: these used to
|
|
177
|
+
// read .value ("on") and never reflect boolean state).
|
|
178
|
+
if (inputs[j].type === "radio") inputs[j].checked = String(v) === inputs[j].value;
|
|
179
|
+
else inputs[j].checked = !!v;
|
|
180
|
+
} else if (String(inputs[j].value) !== String(v)) {
|
|
181
|
+
inputs[j].value = v;
|
|
182
|
+
}
|
|
161
183
|
}
|
|
162
184
|
}
|
|
163
185
|
refreshLoops();
|
|
@@ -170,6 +192,11 @@
|
|
|
170
192
|
"keyup", "keypress", "focus", "blur", "mouseenter", "mouseleave", "wheel"];
|
|
171
193
|
|
|
172
194
|
EVENTS.forEach(function (ev) {
|
|
195
|
+
// Delegate on the BUBBLE phase so the innermost handler runs first
|
|
196
|
+
// (round 4: capture=true inverted the expected order for nested
|
|
197
|
+
// data-tw-event elements). focus/blur do not bubble -- those two
|
|
198
|
+
// keep the capture phase.
|
|
199
|
+
var useCapture = ev === "focus" || ev === "blur";
|
|
173
200
|
document.addEventListener(ev, function (e) {
|
|
174
201
|
var t = e.target;
|
|
175
202
|
if (!t || !t.closest) return;
|
|
@@ -180,16 +207,28 @@
|
|
|
180
207
|
if (host.getAttribute("data-tw-event-" + ev + "-prevent") === "true") e.preventDefault();
|
|
181
208
|
if (host.getAttribute("data-tw-event-" + ev + "-stop") === "true") e.stopPropagation();
|
|
182
209
|
window.__twEvent = { target: t, value: t.value, type: ev };
|
|
183
|
-
runHandler(code);
|
|
210
|
+
try { runHandler(code); } finally {
|
|
211
|
+
// Event context is scoped to THIS handler only (round 4: the
|
|
212
|
+
// stale window.__twEvent leaked the previous event's target and
|
|
213
|
+
// value into later handlers that read it).
|
|
214
|
+
try { delete window.__twEvent; } catch (ee) { window.__twEvent = undefined; }
|
|
215
|
+
}
|
|
184
216
|
refresh();
|
|
185
|
-
},
|
|
217
|
+
}, useCapture);
|
|
186
218
|
});
|
|
187
219
|
|
|
188
220
|
// Two-way: input with data-tw-model writes to state
|
|
189
221
|
document.addEventListener("input", function (e) {
|
|
190
222
|
var t = e.target;
|
|
191
223
|
if (t && t.getAttribute && t.getAttribute("data-tw-model")) {
|
|
192
|
-
|
|
224
|
+
var mk = t.getAttribute("data-tw-model");
|
|
225
|
+
if (t.type === "checkbox" || t.type === "radio") {
|
|
226
|
+
// checkbox -> boolean; radio -> its value when checked (round 4:
|
|
227
|
+
// the old code wrote the literal string "on").
|
|
228
|
+
state[mk] = t.type === "checkbox" ? !!t.checked : (t.checked ? t.value : state[mk]);
|
|
229
|
+
} else {
|
|
230
|
+
state[mk] = t.value;
|
|
231
|
+
}
|
|
193
232
|
refresh();
|
|
194
233
|
}
|
|
195
234
|
});
|
|
@@ -260,6 +299,22 @@
|
|
|
260
299
|
if (oldRoot.replaceWith) oldRoot.replaceWith(imported);
|
|
261
300
|
else oldRoot.innerHTML = newRoot.innerHTML;
|
|
262
301
|
}
|
|
302
|
+
// innerHTML-inserted <script> tags never execute (round 4: pages
|
|
303
|
+
// relying on scripts silently died after an SPA swap). Re-create
|
|
304
|
+
// each script node so the browser runs it.
|
|
305
|
+
try {
|
|
306
|
+
var liveRoot = document.querySelector("[data-tw-root]") || document.body;
|
|
307
|
+
var swapped = liveRoot.querySelectorAll("script");
|
|
308
|
+
for (var si = 0; si < swapped.length; si++) {
|
|
309
|
+
var os = swapped[si];
|
|
310
|
+
var ns = document.createElement("script");
|
|
311
|
+
for (var ai = 0; ai < os.attributes.length; ai++) {
|
|
312
|
+
ns.setAttribute(os.attributes[ai].name, os.attributes[ai].value);
|
|
313
|
+
}
|
|
314
|
+
ns.textContent = os.textContent;
|
|
315
|
+
if (os.parentNode) os.parentNode.replaceChild(ns, os);
|
|
316
|
+
}
|
|
317
|
+
} catch (e) { /* script re-run is best-effort */ }
|
|
263
318
|
|
|
264
319
|
var t = newDoc ? newDoc.querySelector("title") : null;
|
|
265
320
|
if (t) document.title = t.textContent;
|
|
@@ -329,9 +384,16 @@
|
|
|
329
384
|
// data-tw-prefetch="always" -> fetched immediately on load
|
|
330
385
|
// data-tw-prefetch="viewport" -> fetched when the link scrolls into view
|
|
331
386
|
// data-tw-no-prefetch -> never fetched
|
|
387
|
+
var prefetchOrder = [];
|
|
332
388
|
function twPrefetch(href) {
|
|
333
389
|
if (!href || !sameOrigin(href) || prefetched[href]) return;
|
|
334
390
|
prefetched[href] = true;
|
|
391
|
+
// Bound the cache (round 4: it grew forever on long SPA sessions).
|
|
392
|
+
prefetchOrder.push(href);
|
|
393
|
+
while (prefetchOrder.length > 60) {
|
|
394
|
+
var old2 = prefetchOrder.shift();
|
|
395
|
+
delete prefetched[old2];
|
|
396
|
+
}
|
|
335
397
|
try { fetch(href).catch(function () {}); } catch (e) { /* ignore */ }
|
|
336
398
|
}
|
|
337
399
|
|