micra.js 1.0.0 → 2.0.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/README.md +7 -4
- package/dist/dom/events.d.ts +9 -4
- package/dist/dom/query.d.ts +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/micra.cjs.js +219 -53
- package/dist/micra.cjs.js.map +3 -3
- package/dist/micra.esm.js +219 -53
- package/dist/micra.esm.js.map +3 -3
- package/dist/micra.js +219 -53
- package/dist/micra.js.map +3 -3
- package/dist/micra.min.js +2 -2
- package/dist/types.d.ts +33 -4
- package/dist/utils/expr.d.ts +12 -1
- package/package.json +2 -2
- package/src/core/bus.ts +4 -1
- package/src/core/mount.ts +54 -3
- package/src/dom/directives.ts +107 -29
- package/src/dom/each.ts +14 -2
- package/src/dom/events.ts +50 -20
- package/src/dom/query.ts +15 -1
- package/src/index.ts +1 -1
- package/src/types.ts +36 -4
- package/src/utils/expr.ts +119 -7
- package/src/utils/fetch.ts +2 -2
package/dist/micra.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Micra.js
|
|
1
|
+
/* Micra.js v2.0.0 — https://github.com/micra-js/micra — MIT */
|
|
2
2
|
|
|
3
3
|
// src/utils/fetch.ts
|
|
4
4
|
function getCSRF() {
|
|
@@ -31,9 +31,9 @@ async function micraFetch(url, options = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
if (Object.keys(params).length)
|
|
33
33
|
finalUrl += (url.includes("?") ? "&" : "?") + new URLSearchParams(params);
|
|
34
|
-
} else {
|
|
34
|
+
} else if (options.body !== void 0) {
|
|
35
35
|
headers["Content-Type"] = "application/json";
|
|
36
|
-
body = JSON.stringify(options.body
|
|
36
|
+
body = JSON.stringify(options.body);
|
|
37
37
|
}
|
|
38
38
|
const res = await fetch(finalUrl, {
|
|
39
39
|
method,
|
|
@@ -80,10 +80,69 @@ function debug() {
|
|
|
80
80
|
|
|
81
81
|
// src/utils/expr.ts
|
|
82
82
|
var exprCache = /* @__PURE__ */ new Map();
|
|
83
|
+
var warnedRuntime = /* @__PURE__ */ new Set();
|
|
83
84
|
var SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/;
|
|
85
|
+
var ALLOWED_GLOBALS = /* @__PURE__ */ new Set([
|
|
86
|
+
"Math",
|
|
87
|
+
"JSON",
|
|
88
|
+
"Date",
|
|
89
|
+
"String",
|
|
90
|
+
"Number",
|
|
91
|
+
"Boolean",
|
|
92
|
+
"Array",
|
|
93
|
+
"Object",
|
|
94
|
+
"parseInt",
|
|
95
|
+
"parseFloat",
|
|
96
|
+
"isNaN",
|
|
97
|
+
"isFinite",
|
|
98
|
+
"NaN",
|
|
99
|
+
"Infinity",
|
|
100
|
+
"undefined"
|
|
101
|
+
]);
|
|
102
|
+
var PARAM_S = "$s";
|
|
103
|
+
var PARAM_SAFE = "$safe";
|
|
104
|
+
var SAFE_OUTER = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
105
|
+
has(_target, key) {
|
|
106
|
+
if (typeof key !== "string") return false;
|
|
107
|
+
if (key === PARAM_S || key === PARAM_SAFE) return false;
|
|
108
|
+
return !ALLOWED_GLOBALS.has(key);
|
|
109
|
+
},
|
|
110
|
+
get() {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
var safeWrapCache = /* @__PURE__ */ new WeakMap();
|
|
115
|
+
var OBJ_PROTO_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
|
|
116
|
+
function safeStateWrap(state) {
|
|
117
|
+
const cached = safeWrapCache.get(state);
|
|
118
|
+
if (cached) return cached;
|
|
119
|
+
const wrapped = new Proxy(state, {
|
|
120
|
+
has(target, key) {
|
|
121
|
+
return safeStateHas(target, key);
|
|
122
|
+
},
|
|
123
|
+
get(target, key) {
|
|
124
|
+
return Reflect.get(target, key);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
safeWrapCache.set(state, wrapped);
|
|
128
|
+
return wrapped;
|
|
129
|
+
}
|
|
130
|
+
function safeStateHas(state, key) {
|
|
131
|
+
if (typeof key !== "string") return false;
|
|
132
|
+
if (!Reflect.has(state, key)) return false;
|
|
133
|
+
if (!OBJ_PROTO_KEYS.has(key)) return true;
|
|
134
|
+
let obj = state;
|
|
135
|
+
while (obj && obj !== Object.prototype) {
|
|
136
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) return true;
|
|
137
|
+
obj = Object.getPrototypeOf(obj);
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
84
141
|
function evalExpr(expr, state) {
|
|
85
142
|
if (SIMPLE_PATH.test(expr)) {
|
|
86
|
-
|
|
143
|
+
const parts = expr.split(".");
|
|
144
|
+
if (!safeStateHas(state, parts[0])) return void 0;
|
|
145
|
+
return parts.reduce(
|
|
87
146
|
(obj, key) => obj != null ? obj[key] : void 0,
|
|
88
147
|
state
|
|
89
148
|
);
|
|
@@ -92,7 +151,7 @@ function evalExpr(expr, state) {
|
|
|
92
151
|
try {
|
|
93
152
|
exprCache.set(
|
|
94
153
|
expr,
|
|
95
|
-
new Function("$s", `with($s){return (${expr})}`)
|
|
154
|
+
new Function("$s", "$safe", `with($safe){with($s){return (${expr})}}`)
|
|
96
155
|
);
|
|
97
156
|
} catch {
|
|
98
157
|
warn(`invalid expression "${expr}"`);
|
|
@@ -100,8 +159,12 @@ function evalExpr(expr, state) {
|
|
|
100
159
|
}
|
|
101
160
|
}
|
|
102
161
|
try {
|
|
103
|
-
return exprCache.get(expr)(state);
|
|
104
|
-
} catch {
|
|
162
|
+
return exprCache.get(expr)(safeStateWrap(state), SAFE_OUTER);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
if (!warnedRuntime.has(expr)) {
|
|
165
|
+
warnedRuntime.add(expr);
|
|
166
|
+
warn(`runtime error in "${expr}": ${e.message}`);
|
|
167
|
+
}
|
|
105
168
|
return void 0;
|
|
106
169
|
}
|
|
107
170
|
}
|
|
@@ -117,8 +180,10 @@ function on(event, handler) {
|
|
|
117
180
|
return () => off(event, handler);
|
|
118
181
|
}
|
|
119
182
|
function off(event, handler) {
|
|
120
|
-
|
|
121
|
-
|
|
183
|
+
const set = _bus.get(event);
|
|
184
|
+
if (!set) return;
|
|
185
|
+
set.delete(handler);
|
|
186
|
+
if (set.size === 0) _bus.delete(event);
|
|
122
187
|
}
|
|
123
188
|
function emit(event, payload) {
|
|
124
189
|
var _a;
|
|
@@ -159,7 +224,13 @@ function queryAll(root, sel) {
|
|
|
159
224
|
return Array.from(root.querySelectorAll(sel));
|
|
160
225
|
}
|
|
161
226
|
function queryOwn(root, attr) {
|
|
162
|
-
return queryAll(root, `[${attr}]`)
|
|
227
|
+
return filterOwn(root, queryAll(root, `[${attr}]`));
|
|
228
|
+
}
|
|
229
|
+
function queryOwnAll(root, sel) {
|
|
230
|
+
return filterOwn(root, queryAll(root, sel));
|
|
231
|
+
}
|
|
232
|
+
function filterOwn(root, els) {
|
|
233
|
+
return els.filter((el) => {
|
|
163
234
|
let node = el.parentElement;
|
|
164
235
|
while (node && node !== root) {
|
|
165
236
|
if (node.hasAttribute("data-component")) return false;
|
|
@@ -179,15 +250,25 @@ function applyHtml(el, expr, state) {
|
|
|
179
250
|
var _a;
|
|
180
251
|
el.innerHTML = String((_a = evalExpr(expr, state)) != null ? _a : "");
|
|
181
252
|
}
|
|
182
|
-
function applyIf(
|
|
253
|
+
function applyIf(binding, state) {
|
|
254
|
+
const el = binding.el;
|
|
255
|
+
const truthy = !!evalExpr(binding.expr, state);
|
|
256
|
+
if (truthy) {
|
|
257
|
+
const ph = binding.placeholder;
|
|
258
|
+
if (ph && ph.parentNode) ph.parentNode.replaceChild(el, ph);
|
|
259
|
+
} else {
|
|
260
|
+
const parent = el.parentNode;
|
|
261
|
+
if (parent) {
|
|
262
|
+
if (!binding.placeholder) binding.placeholder = document.createComment("if");
|
|
263
|
+
parent.replaceChild(binding.placeholder, el);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function applyShow(el, expr, state) {
|
|
183
268
|
el.style.display = evalExpr(expr, state) ? "" : "none";
|
|
184
269
|
}
|
|
185
|
-
function applyBind(el,
|
|
186
|
-
for (const
|
|
187
|
-
const colonIdx = pair.indexOf(":");
|
|
188
|
-
if (colonIdx === -1) continue;
|
|
189
|
-
const attr = pair.slice(0, colonIdx).trim();
|
|
190
|
-
const valExpr = pair.slice(colonIdx + 1).trim();
|
|
270
|
+
function applyBind(el, pairs, state) {
|
|
271
|
+
for (const [attr, valExpr] of pairs) {
|
|
191
272
|
const val = evalExpr(valExpr, state);
|
|
192
273
|
if (attr === "class") {
|
|
193
274
|
el.className = String(val != null ? val : "");
|
|
@@ -207,21 +288,28 @@ function applyBind(el, expr, state) {
|
|
|
207
288
|
}
|
|
208
289
|
}
|
|
209
290
|
}
|
|
210
|
-
function applyClass(el,
|
|
211
|
-
for (const
|
|
212
|
-
const colonIdx = pair.indexOf(":");
|
|
213
|
-
if (colonIdx === -1) continue;
|
|
214
|
-
const cls = pair.slice(0, colonIdx).trim();
|
|
215
|
-
const valExpr = pair.slice(colonIdx + 1).trim();
|
|
216
|
-
if (!cls) continue;
|
|
291
|
+
function applyClass(el, pairs, state) {
|
|
292
|
+
for (const [cls, valExpr] of pairs) {
|
|
217
293
|
el.classList.toggle(cls, Boolean(evalExpr(valExpr, state)));
|
|
218
294
|
}
|
|
219
295
|
}
|
|
296
|
+
function parsePairs(expr) {
|
|
297
|
+
const out = [];
|
|
298
|
+
for (const part of expr.split(",")) {
|
|
299
|
+
const colonIdx = part.indexOf(":");
|
|
300
|
+
if (colonIdx === -1) continue;
|
|
301
|
+
const left = part.slice(0, colonIdx).trim();
|
|
302
|
+
const right = part.slice(colonIdx + 1).trim();
|
|
303
|
+
if (!left) continue;
|
|
304
|
+
out.push([left, right]);
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
220
308
|
function applyModel(el, key, rawState) {
|
|
221
309
|
const html = el;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
310
|
+
const stateVal = rawState[key];
|
|
311
|
+
const desired = stateVal == null ? "" : String(stateVal);
|
|
312
|
+
if (html.value !== desired) html.value = desired;
|
|
225
313
|
}
|
|
226
314
|
function buildCache(root) {
|
|
227
315
|
const pick = (attr) => {
|
|
@@ -230,14 +318,15 @@ function buildCache(root) {
|
|
|
230
318
|
if ((_a = root.hasAttribute) == null ? void 0 : _a.call(root, attr)) els.unshift(root);
|
|
231
319
|
return els.filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
|
|
232
320
|
};
|
|
321
|
+
const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
|
|
233
322
|
return {
|
|
234
323
|
text: pick("data-text"),
|
|
235
324
|
html: pick("data-html"),
|
|
236
325
|
if: pick("data-if"),
|
|
237
326
|
show: pick("data-show"),
|
|
238
|
-
bind:
|
|
327
|
+
bind: pickPairs("data-bind"),
|
|
239
328
|
model: pick("data-model"),
|
|
240
|
-
class:
|
|
329
|
+
class: pickPairs("data-class")
|
|
241
330
|
};
|
|
242
331
|
}
|
|
243
332
|
function applyDirectives(root, state, rawState, _instance) {
|
|
@@ -250,35 +339,56 @@ function applyDirectives(root, state, rawState, _instance) {
|
|
|
250
339
|
applyFromList(el.__micraCache, state, rawState);
|
|
251
340
|
}
|
|
252
341
|
function applyFromList(cache, state, rawState) {
|
|
342
|
+
cache.if.forEach((b) => applyIf(b, state));
|
|
253
343
|
cache.text.forEach((b) => applyText(b.el, b.expr, state));
|
|
254
344
|
cache.html.forEach((b) => applyHtml(b.el, b.expr, state));
|
|
255
|
-
cache.
|
|
256
|
-
cache.
|
|
257
|
-
cache.bind.forEach((b) => applyBind(b.el, b.expr, state));
|
|
345
|
+
cache.show.forEach((b) => applyShow(b.el, b.expr, state));
|
|
346
|
+
cache.bind.forEach((b) => applyBind(b.el, b.pairs, state));
|
|
258
347
|
cache.model.forEach((b) => applyModel(b.el, b.expr.trim(), rawState));
|
|
259
|
-
cache.class.forEach((b) => applyClass(b.el, b.
|
|
348
|
+
cache.class.forEach((b) => applyClass(b.el, b.pairs, state));
|
|
260
349
|
}
|
|
261
350
|
function buildFragmentList(frag) {
|
|
262
351
|
const pick = (attr) => queryAll(frag, `[${attr}]`).filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
|
|
352
|
+
const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
|
|
263
353
|
return {
|
|
264
354
|
text: pick("data-text"),
|
|
265
355
|
html: pick("data-html"),
|
|
266
356
|
if: pick("data-if"),
|
|
267
357
|
show: pick("data-show"),
|
|
268
|
-
bind:
|
|
358
|
+
bind: pickPairs("data-bind"),
|
|
269
359
|
model: pick("data-model"),
|
|
270
|
-
class:
|
|
360
|
+
class: pickPairs("data-class")
|
|
271
361
|
};
|
|
272
362
|
}
|
|
273
363
|
function validateDirectives(root) {
|
|
364
|
+
var _a, _b;
|
|
274
365
|
queryOwn(root, "data-each").forEach((el) => {
|
|
275
|
-
|
|
366
|
+
const tmpl = el;
|
|
367
|
+
if (!el.hasAttribute("data-key") && !tmpl.__micraNoKeyWarned) {
|
|
368
|
+
tmpl.__micraNoKeyWarned = true;
|
|
276
369
|
warn(`data-each="${el.getAttribute("data-each")}" has no data-key \u2014 keyed diff disabled. Add data-key="id" for better performance.`);
|
|
277
370
|
}
|
|
278
371
|
});
|
|
372
|
+
const bindEls = queryOwn(root, "data-bind");
|
|
373
|
+
if (((_a = root.hasAttribute) == null ? void 0 : _a.call(root, "data-bind")) && !bindEls.includes(root)) bindEls.unshift(root);
|
|
374
|
+
for (const el of bindEls) {
|
|
375
|
+
const spec = (_b = el.getAttribute("data-bind")) != null ? _b : "";
|
|
376
|
+
const hasClassBind = spec.split(",").some((p) => {
|
|
377
|
+
var _a2;
|
|
378
|
+
return ((_a2 = p.trim().split(":")[0]) == null ? void 0 : _a2.trim()) === "class";
|
|
379
|
+
});
|
|
380
|
+
if (hasClassBind && el.hasAttribute("data-class")) {
|
|
381
|
+
warn(`element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
279
384
|
}
|
|
280
385
|
|
|
281
386
|
// src/dom/events.ts
|
|
387
|
+
function track(instance, el, type, fn) {
|
|
388
|
+
var _a;
|
|
389
|
+
el.addEventListener(type, fn);
|
|
390
|
+
((_a = instance.__micraListeners) != null ? _a : instance.__micraListeners = []).push({ el, type, fn });
|
|
391
|
+
}
|
|
282
392
|
function bindDataOn(root, instance) {
|
|
283
393
|
var _a, _b;
|
|
284
394
|
const isFragment = root.nodeType === 11;
|
|
@@ -294,7 +404,7 @@ function bindDataOn(root, instance) {
|
|
|
294
404
|
const [evSpec, method] = part.trim().split(":");
|
|
295
405
|
if (!evSpec || !method) continue;
|
|
296
406
|
const [evName, ...mods] = evSpec.split(".");
|
|
297
|
-
el
|
|
407
|
+
track(instance, el, evName, (e) => {
|
|
298
408
|
if (mods.includes("prevent")) e.preventDefault();
|
|
299
409
|
if (mods.includes("stop")) e.stopPropagation();
|
|
300
410
|
if (mods.includes("self") && e.target !== el) return;
|
|
@@ -306,16 +416,18 @@ function bindDataOn(root, instance) {
|
|
|
306
416
|
}
|
|
307
417
|
}
|
|
308
418
|
function bindAtEvents(root, instance) {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const all = queryAll(root, "*");
|
|
419
|
+
const isFragment = root.nodeType === 11;
|
|
420
|
+
const all = isFragment ? queryAll(root, "*") : queryOwnAll(root, "*");
|
|
421
|
+
if (!isFragment && !all.includes(root)) all.unshift(root);
|
|
313
422
|
for (const el of all) {
|
|
423
|
+
const mEl = el;
|
|
424
|
+
if (mEl.__micraAtBound) continue;
|
|
425
|
+
let bound = false;
|
|
314
426
|
for (const attr of Array.from(el.attributes)) {
|
|
315
427
|
if (!attr.name.startsWith("@")) continue;
|
|
316
428
|
const [evSpec, ...rest] = attr.name.slice(1).split(".");
|
|
317
429
|
const method = attr.value.trim();
|
|
318
|
-
el
|
|
430
|
+
track(instance, el, evSpec, (e) => {
|
|
319
431
|
if (rest.includes("prevent")) e.preventDefault();
|
|
320
432
|
if (rest.includes("stop")) e.stopPropagation();
|
|
321
433
|
if (rest.includes("self") && e.target !== el) return;
|
|
@@ -323,7 +435,9 @@ function bindAtEvents(root, instance) {
|
|
|
323
435
|
if (typeof fn === "function") fn.call(instance, e);
|
|
324
436
|
else warn(`method "${method}" not found`);
|
|
325
437
|
});
|
|
438
|
+
bound = true;
|
|
326
439
|
}
|
|
440
|
+
if (bound) mEl.__micraAtBound = true;
|
|
327
441
|
}
|
|
328
442
|
}
|
|
329
443
|
function bindModels(root, instance) {
|
|
@@ -336,14 +450,22 @@ function bindModels(root, instance) {
|
|
|
336
450
|
mEl.__micraModel = true;
|
|
337
451
|
const key = (_a = el.dataset["model"]) != null ? _a : "";
|
|
338
452
|
const tag = el.tagName;
|
|
453
|
+
const inputEl = el;
|
|
454
|
+
const inputType = inputEl.type;
|
|
339
455
|
const update = () => {
|
|
340
|
-
|
|
456
|
+
let val;
|
|
457
|
+
if (tag === "INPUT" && inputType === "checkbox") {
|
|
458
|
+
val = inputEl.checked;
|
|
459
|
+
} else if (tag === "INPUT" && (inputType === "number" || inputType === "range")) {
|
|
460
|
+
val = inputEl.value === "" ? null : inputEl.valueAsNumber;
|
|
461
|
+
} else {
|
|
462
|
+
val = inputEl.value;
|
|
463
|
+
}
|
|
464
|
+
;
|
|
341
465
|
instance.state[key] = val;
|
|
342
466
|
};
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
update
|
|
346
|
-
);
|
|
467
|
+
const evType = tag === "SELECT" || inputType === "radio" ? "change" : "input";
|
|
468
|
+
track(instance, el, evType, update);
|
|
347
469
|
}
|
|
348
470
|
}
|
|
349
471
|
|
|
@@ -366,6 +488,7 @@ function renderList(root, state, rawState, instance) {
|
|
|
366
488
|
const marker = tmpl.__micraMarker;
|
|
367
489
|
const keyMap = tmpl.__micraNodes;
|
|
368
490
|
const parent = marker.parentNode;
|
|
491
|
+
if (!parent) return;
|
|
369
492
|
if (!Array.isArray(items)) {
|
|
370
493
|
tmpl.__micraList.forEach((n) => n.remove());
|
|
371
494
|
tmpl.__micraList = [];
|
|
@@ -382,9 +505,18 @@ function renderList(root, state, rawState, instance) {
|
|
|
382
505
|
function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance) {
|
|
383
506
|
const nextKeys = /* @__PURE__ */ new Set();
|
|
384
507
|
const nextNodes = [];
|
|
508
|
+
let warnedNullKey = false;
|
|
509
|
+
let warnedDupKey = false;
|
|
385
510
|
for (const [index, item] of items.entries()) {
|
|
386
511
|
const key = item[keyAttr];
|
|
387
|
-
if (key == null
|
|
512
|
+
if (key == null && !warnedNullKey) {
|
|
513
|
+
warn(`data-key="${keyAttr}" is null/undefined on item at index ${index}`);
|
|
514
|
+
warnedNullKey = true;
|
|
515
|
+
}
|
|
516
|
+
if (nextKeys.has(key) && !warnedDupKey) {
|
|
517
|
+
warn(`data-key="${keyAttr}" has duplicate value ${JSON.stringify(key)} \u2014 rows will collide`);
|
|
518
|
+
warnedDupKey = true;
|
|
519
|
+
}
|
|
388
520
|
nextKeys.add(key);
|
|
389
521
|
let node = keyMap.get(key);
|
|
390
522
|
if (!node) {
|
|
@@ -487,15 +619,35 @@ function mount(selector, definition) {
|
|
|
487
619
|
let isRendering = false;
|
|
488
620
|
const schedule = createScheduler(() => instance.render());
|
|
489
621
|
instance.state = createReactiveState(rawState, schedule);
|
|
622
|
+
const boundMethods = /* @__PURE__ */ new Map();
|
|
490
623
|
const exprState = new Proxy(rawState, {
|
|
491
624
|
get(target, key) {
|
|
492
|
-
if (key
|
|
493
|
-
if (key
|
|
625
|
+
if (Object.prototype.hasOwnProperty.call(target, key)) return target[key];
|
|
626
|
+
if (Object.prototype.hasOwnProperty.call(instance, key) && typeof instance[key] === "function") {
|
|
627
|
+
const cached = boundMethods.get(key);
|
|
628
|
+
if (cached) return cached;
|
|
629
|
+
const bound = instance[key].bind(instance);
|
|
630
|
+
boundMethods.set(key, bound);
|
|
631
|
+
return bound;
|
|
632
|
+
}
|
|
494
633
|
return void 0;
|
|
634
|
+
},
|
|
635
|
+
has(target, key) {
|
|
636
|
+
if (typeof key !== "string") return false;
|
|
637
|
+
if (Object.prototype.hasOwnProperty.call(target, key)) return true;
|
|
638
|
+
return Object.prototype.hasOwnProperty.call(instance, key) && typeof instance[key] === "function";
|
|
495
639
|
}
|
|
496
640
|
});
|
|
641
|
+
let warnedReentry = false;
|
|
497
642
|
instance.render = function() {
|
|
498
|
-
if (
|
|
643
|
+
if (instance.__micraDestroyed) return;
|
|
644
|
+
if (isRendering) {
|
|
645
|
+
if (!warnedReentry) {
|
|
646
|
+
warn("render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method.");
|
|
647
|
+
warnedReentry = true;
|
|
648
|
+
}
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
499
651
|
isRendering = true;
|
|
500
652
|
try {
|
|
501
653
|
applyDirectives(root, exprState, rawState, instance);
|
|
@@ -509,8 +661,22 @@ function mount(selector, definition) {
|
|
|
509
661
|
}
|
|
510
662
|
};
|
|
511
663
|
instance.destroy = function() {
|
|
512
|
-
var _a2;
|
|
513
|
-
(
|
|
664
|
+
var _a2, _b;
|
|
665
|
+
if (instance.__micraDestroyed) return;
|
|
666
|
+
instance.__micraDestroyed = true;
|
|
667
|
+
(_a2 = instance.__micraListeners) == null ? void 0 : _a2.forEach(({ el, type, fn }) => el.removeEventListener(type, fn));
|
|
668
|
+
instance.__micraListeners = [];
|
|
669
|
+
const clearFlags = (el) => {
|
|
670
|
+
const m = el;
|
|
671
|
+
delete m.__micraEvents;
|
|
672
|
+
delete m.__micraAtBound;
|
|
673
|
+
delete m.__micraModel;
|
|
674
|
+
delete m.__micraCache;
|
|
675
|
+
};
|
|
676
|
+
clearFlags(root);
|
|
677
|
+
root.querySelectorAll("*").forEach(clearFlags);
|
|
678
|
+
(_b = instance.__micraSubs) == null ? void 0 : _b.forEach((unsub) => unsub());
|
|
679
|
+
instance.__micraSubs = [];
|
|
514
680
|
if (typeof definition.onDestroy === "function")
|
|
515
681
|
definition.onDestroy.call(instance);
|
|
516
682
|
_instances.delete(root);
|