micra.js 1.0.0 → 1.1.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 +2 -2
- package/dist/dom/events.d.ts +9 -4
- package/dist/micra.cjs.js +192 -47
- package/dist/micra.cjs.js.map +3 -3
- package/dist/micra.esm.js +192 -47
- package/dist/micra.esm.js.map +3 -3
- package/dist/micra.js +192 -47
- package/dist/micra.js.map +3 -3
- package/dist/micra.min.js +2 -2
- package/dist/types.d.ts +24 -3
- 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 +67 -24
- package/src/dom/each.ts +10 -1
- package/src/dom/events.ts +49 -19
- package/src/types.ts +26 -3
- package/src/utils/expr.ts +119 -7
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ npm install micra.js
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
|
-
import * as Micra from 'micra'
|
|
57
|
+
import * as Micra from 'micra.js'
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
## Basic usage
|
|
@@ -71,7 +71,7 @@ A counter mounted automatically from `data-component`:
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
|
-
import * as Micra from 'micra'
|
|
74
|
+
import * as Micra from 'micra.js'
|
|
75
75
|
|
|
76
76
|
Micra.define('counter', {
|
|
77
77
|
state: { count: 0 },
|
package/dist/dom/events.d.ts
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Responsibilities:
|
|
5
5
|
* - Bind `data-on="event:method"` listeners (once per element)
|
|
6
|
-
* - Bind `@event="method"` shorthand (
|
|
6
|
+
* - Bind `@event="method"` shorthand (once per element)
|
|
7
|
+
* - Bind `data-model` two-way input listeners (once per element)
|
|
7
8
|
*
|
|
8
|
-
* LLM NOTE:
|
|
9
|
-
*
|
|
9
|
+
* LLM NOTE: Every listener attached here is also recorded in
|
|
10
|
+
* instance.__micraListeners so destroy() can remove it cleanly.
|
|
11
|
+
* Re-render skips already-bound elements via per-element __micra* flags.
|
|
10
12
|
*/
|
|
11
13
|
import type { InternalInstance, StateRecord } from '../types';
|
|
12
14
|
/**
|
|
@@ -22,7 +24,7 @@ import type { InternalInstance, StateRecord } from '../types';
|
|
|
22
24
|
export declare function bindDataOn<S extends StateRecord>(root: Element, instance: InternalInstance<S>): void;
|
|
23
25
|
/**
|
|
24
26
|
* Bind `@event="method"` shorthand attributes (Stimulus-style).
|
|
25
|
-
*
|
|
27
|
+
* Bound once per element via `__micraAtBound` — re-renders are no-ops.
|
|
26
28
|
* Supports the same modifiers as data-on: `@click.prevent="submit"`.
|
|
27
29
|
*
|
|
28
30
|
* @example
|
|
@@ -34,6 +36,9 @@ export declare function bindAtEvents<S extends StateRecord>(root: Element, insta
|
|
|
34
36
|
* Two-way binding: `data-model="key"` wires <input>/<select>/<textarea>
|
|
35
37
|
* to `state[key]`. Binding is attached once per element.
|
|
36
38
|
*
|
|
39
|
+
* Numeric inputs (`type="number"` / `type="range"`) write numbers, not strings.
|
|
40
|
+
* Checkbox inputs write booleans. Everything else writes strings.
|
|
41
|
+
*
|
|
37
42
|
* @example
|
|
38
43
|
* <input data-model="search"> // updates state.search on every keystroke
|
|
39
44
|
* <select data-model="sortBy"> // updates state.sortBy on change
|
package/dist/micra.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Micra.js v1.
|
|
1
|
+
/* Micra.js v1.1.0 — https://github.com/micra-js/micra — MIT */
|
|
2
2
|
"use strict";
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -115,10 +115,69 @@ function debug() {
|
|
|
115
115
|
|
|
116
116
|
// src/utils/expr.ts
|
|
117
117
|
var exprCache = /* @__PURE__ */ new Map();
|
|
118
|
+
var warnedRuntime = /* @__PURE__ */ new Set();
|
|
118
119
|
var SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/;
|
|
120
|
+
var ALLOWED_GLOBALS = /* @__PURE__ */ new Set([
|
|
121
|
+
"Math",
|
|
122
|
+
"JSON",
|
|
123
|
+
"Date",
|
|
124
|
+
"String",
|
|
125
|
+
"Number",
|
|
126
|
+
"Boolean",
|
|
127
|
+
"Array",
|
|
128
|
+
"Object",
|
|
129
|
+
"parseInt",
|
|
130
|
+
"parseFloat",
|
|
131
|
+
"isNaN",
|
|
132
|
+
"isFinite",
|
|
133
|
+
"NaN",
|
|
134
|
+
"Infinity",
|
|
135
|
+
"undefined"
|
|
136
|
+
]);
|
|
137
|
+
var PARAM_S = "$s";
|
|
138
|
+
var PARAM_SAFE = "$safe";
|
|
139
|
+
var SAFE_OUTER = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
140
|
+
has(_target, key) {
|
|
141
|
+
if (typeof key !== "string") return false;
|
|
142
|
+
if (key === PARAM_S || key === PARAM_SAFE) return false;
|
|
143
|
+
return !ALLOWED_GLOBALS.has(key);
|
|
144
|
+
},
|
|
145
|
+
get() {
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
var safeWrapCache = /* @__PURE__ */ new WeakMap();
|
|
150
|
+
var OBJ_PROTO_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
|
|
151
|
+
function safeStateWrap(state) {
|
|
152
|
+
const cached = safeWrapCache.get(state);
|
|
153
|
+
if (cached) return cached;
|
|
154
|
+
const wrapped = new Proxy(state, {
|
|
155
|
+
has(target, key) {
|
|
156
|
+
return safeStateHas(target, key);
|
|
157
|
+
},
|
|
158
|
+
get(target, key) {
|
|
159
|
+
return Reflect.get(target, key);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
safeWrapCache.set(state, wrapped);
|
|
163
|
+
return wrapped;
|
|
164
|
+
}
|
|
165
|
+
function safeStateHas(state, key) {
|
|
166
|
+
if (typeof key !== "string") return false;
|
|
167
|
+
if (!Reflect.has(state, key)) return false;
|
|
168
|
+
if (!OBJ_PROTO_KEYS.has(key)) return true;
|
|
169
|
+
let obj = state;
|
|
170
|
+
while (obj && obj !== Object.prototype) {
|
|
171
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) return true;
|
|
172
|
+
obj = Object.getPrototypeOf(obj);
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
119
176
|
function evalExpr(expr, state) {
|
|
120
177
|
if (SIMPLE_PATH.test(expr)) {
|
|
121
|
-
|
|
178
|
+
const parts = expr.split(".");
|
|
179
|
+
if (!safeStateHas(state, parts[0])) return void 0;
|
|
180
|
+
return parts.reduce(
|
|
122
181
|
(obj, key) => obj != null ? obj[key] : void 0,
|
|
123
182
|
state
|
|
124
183
|
);
|
|
@@ -127,7 +186,7 @@ function evalExpr(expr, state) {
|
|
|
127
186
|
try {
|
|
128
187
|
exprCache.set(
|
|
129
188
|
expr,
|
|
130
|
-
new Function("$s", `with($s){return (${expr})}`)
|
|
189
|
+
new Function("$s", "$safe", `with($safe){with($s){return (${expr})}}`)
|
|
131
190
|
);
|
|
132
191
|
} catch {
|
|
133
192
|
warn(`invalid expression "${expr}"`);
|
|
@@ -135,8 +194,12 @@ function evalExpr(expr, state) {
|
|
|
135
194
|
}
|
|
136
195
|
}
|
|
137
196
|
try {
|
|
138
|
-
return exprCache.get(expr)(state);
|
|
139
|
-
} catch {
|
|
197
|
+
return exprCache.get(expr)(safeStateWrap(state), SAFE_OUTER);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
if (!warnedRuntime.has(expr)) {
|
|
200
|
+
warnedRuntime.add(expr);
|
|
201
|
+
warn(`runtime error in "${expr}": ${e.message}`);
|
|
202
|
+
}
|
|
140
203
|
return void 0;
|
|
141
204
|
}
|
|
142
205
|
}
|
|
@@ -152,8 +215,10 @@ function on(event, handler) {
|
|
|
152
215
|
return () => off(event, handler);
|
|
153
216
|
}
|
|
154
217
|
function off(event, handler) {
|
|
155
|
-
|
|
156
|
-
|
|
218
|
+
const set = _bus.get(event);
|
|
219
|
+
if (!set) return;
|
|
220
|
+
set.delete(handler);
|
|
221
|
+
if (set.size === 0) _bus.delete(event);
|
|
157
222
|
}
|
|
158
223
|
function emit(event, payload) {
|
|
159
224
|
var _a;
|
|
@@ -217,12 +282,8 @@ function applyHtml(el, expr, state) {
|
|
|
217
282
|
function applyIf(el, expr, state) {
|
|
218
283
|
el.style.display = evalExpr(expr, state) ? "" : "none";
|
|
219
284
|
}
|
|
220
|
-
function applyBind(el,
|
|
221
|
-
for (const
|
|
222
|
-
const colonIdx = pair.indexOf(":");
|
|
223
|
-
if (colonIdx === -1) continue;
|
|
224
|
-
const attr = pair.slice(0, colonIdx).trim();
|
|
225
|
-
const valExpr = pair.slice(colonIdx + 1).trim();
|
|
285
|
+
function applyBind(el, pairs, state) {
|
|
286
|
+
for (const [attr, valExpr] of pairs) {
|
|
226
287
|
const val = evalExpr(valExpr, state);
|
|
227
288
|
if (attr === "class") {
|
|
228
289
|
el.className = String(val != null ? val : "");
|
|
@@ -242,21 +303,28 @@ function applyBind(el, expr, state) {
|
|
|
242
303
|
}
|
|
243
304
|
}
|
|
244
305
|
}
|
|
245
|
-
function applyClass(el,
|
|
246
|
-
for (const
|
|
247
|
-
const colonIdx = pair.indexOf(":");
|
|
248
|
-
if (colonIdx === -1) continue;
|
|
249
|
-
const cls = pair.slice(0, colonIdx).trim();
|
|
250
|
-
const valExpr = pair.slice(colonIdx + 1).trim();
|
|
251
|
-
if (!cls) continue;
|
|
306
|
+
function applyClass(el, pairs, state) {
|
|
307
|
+
for (const [cls, valExpr] of pairs) {
|
|
252
308
|
el.classList.toggle(cls, Boolean(evalExpr(valExpr, state)));
|
|
253
309
|
}
|
|
254
310
|
}
|
|
311
|
+
function parsePairs(expr) {
|
|
312
|
+
const out = [];
|
|
313
|
+
for (const part of expr.split(",")) {
|
|
314
|
+
const colonIdx = part.indexOf(":");
|
|
315
|
+
if (colonIdx === -1) continue;
|
|
316
|
+
const left = part.slice(0, colonIdx).trim();
|
|
317
|
+
const right = part.slice(colonIdx + 1).trim();
|
|
318
|
+
if (!left) continue;
|
|
319
|
+
out.push([left, right]);
|
|
320
|
+
}
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
255
323
|
function applyModel(el, key, rawState) {
|
|
256
324
|
const html = el;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
325
|
+
const stateVal = rawState[key];
|
|
326
|
+
const desired = stateVal == null ? "" : String(stateVal);
|
|
327
|
+
if (html.value !== desired) html.value = desired;
|
|
260
328
|
}
|
|
261
329
|
function buildCache(root) {
|
|
262
330
|
const pick = (attr) => {
|
|
@@ -265,14 +333,15 @@ function buildCache(root) {
|
|
|
265
333
|
if ((_a = root.hasAttribute) == null ? void 0 : _a.call(root, attr)) els.unshift(root);
|
|
266
334
|
return els.filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
|
|
267
335
|
};
|
|
336
|
+
const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
|
|
268
337
|
return {
|
|
269
338
|
text: pick("data-text"),
|
|
270
339
|
html: pick("data-html"),
|
|
271
340
|
if: pick("data-if"),
|
|
272
341
|
show: pick("data-show"),
|
|
273
|
-
bind:
|
|
342
|
+
bind: pickPairs("data-bind"),
|
|
274
343
|
model: pick("data-model"),
|
|
275
|
-
class:
|
|
344
|
+
class: pickPairs("data-class")
|
|
276
345
|
};
|
|
277
346
|
}
|
|
278
347
|
function applyDirectives(root, state, rawState, _instance) {
|
|
@@ -289,31 +358,52 @@ function applyFromList(cache, state, rawState) {
|
|
|
289
358
|
cache.html.forEach((b) => applyHtml(b.el, b.expr, state));
|
|
290
359
|
cache.if.forEach((b) => applyIf(b.el, b.expr, state));
|
|
291
360
|
cache.show.forEach((b) => applyIf(b.el, b.expr, state));
|
|
292
|
-
cache.bind.forEach((b) => applyBind(b.el, b.
|
|
361
|
+
cache.bind.forEach((b) => applyBind(b.el, b.pairs, state));
|
|
293
362
|
cache.model.forEach((b) => applyModel(b.el, b.expr.trim(), rawState));
|
|
294
|
-
cache.class.forEach((b) => applyClass(b.el, b.
|
|
363
|
+
cache.class.forEach((b) => applyClass(b.el, b.pairs, state));
|
|
295
364
|
}
|
|
296
365
|
function buildFragmentList(frag) {
|
|
297
366
|
const pick = (attr) => queryAll(frag, `[${attr}]`).filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
|
|
367
|
+
const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
|
|
298
368
|
return {
|
|
299
369
|
text: pick("data-text"),
|
|
300
370
|
html: pick("data-html"),
|
|
301
371
|
if: pick("data-if"),
|
|
302
372
|
show: pick("data-show"),
|
|
303
|
-
bind:
|
|
373
|
+
bind: pickPairs("data-bind"),
|
|
304
374
|
model: pick("data-model"),
|
|
305
|
-
class:
|
|
375
|
+
class: pickPairs("data-class")
|
|
306
376
|
};
|
|
307
377
|
}
|
|
308
378
|
function validateDirectives(root) {
|
|
379
|
+
var _a, _b;
|
|
309
380
|
queryOwn(root, "data-each").forEach((el) => {
|
|
310
|
-
|
|
381
|
+
const tmpl = el;
|
|
382
|
+
if (!el.hasAttribute("data-key") && !tmpl.__micraNoKeyWarned) {
|
|
383
|
+
tmpl.__micraNoKeyWarned = true;
|
|
311
384
|
warn(`data-each="${el.getAttribute("data-each")}" has no data-key \u2014 keyed diff disabled. Add data-key="id" for better performance.`);
|
|
312
385
|
}
|
|
313
386
|
});
|
|
387
|
+
const bindEls = queryOwn(root, "data-bind");
|
|
388
|
+
if (((_a = root.hasAttribute) == null ? void 0 : _a.call(root, "data-bind")) && !bindEls.includes(root)) bindEls.unshift(root);
|
|
389
|
+
for (const el of bindEls) {
|
|
390
|
+
const spec = (_b = el.getAttribute("data-bind")) != null ? _b : "";
|
|
391
|
+
const hasClassBind = spec.split(",").some((p) => {
|
|
392
|
+
var _a2;
|
|
393
|
+
return ((_a2 = p.trim().split(":")[0]) == null ? void 0 : _a2.trim()) === "class";
|
|
394
|
+
});
|
|
395
|
+
if (hasClassBind && el.hasAttribute("data-class")) {
|
|
396
|
+
warn(`element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
314
399
|
}
|
|
315
400
|
|
|
316
401
|
// src/dom/events.ts
|
|
402
|
+
function track(instance, el, type, fn) {
|
|
403
|
+
var _a;
|
|
404
|
+
el.addEventListener(type, fn);
|
|
405
|
+
((_a = instance.__micraListeners) != null ? _a : instance.__micraListeners = []).push({ el, type, fn });
|
|
406
|
+
}
|
|
317
407
|
function bindDataOn(root, instance) {
|
|
318
408
|
var _a, _b;
|
|
319
409
|
const isFragment = root.nodeType === 11;
|
|
@@ -329,7 +419,7 @@ function bindDataOn(root, instance) {
|
|
|
329
419
|
const [evSpec, method] = part.trim().split(":");
|
|
330
420
|
if (!evSpec || !method) continue;
|
|
331
421
|
const [evName, ...mods] = evSpec.split(".");
|
|
332
|
-
el
|
|
422
|
+
track(instance, el, evName, (e) => {
|
|
333
423
|
if (mods.includes("prevent")) e.preventDefault();
|
|
334
424
|
if (mods.includes("stop")) e.stopPropagation();
|
|
335
425
|
if (mods.includes("self") && e.target !== el) return;
|
|
@@ -341,16 +431,18 @@ function bindDataOn(root, instance) {
|
|
|
341
431
|
}
|
|
342
432
|
}
|
|
343
433
|
function bindAtEvents(root, instance) {
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const all = queryAll(root, "*");
|
|
434
|
+
const isFragment = root.nodeType === 11;
|
|
435
|
+
const all = isFragment ? queryAll(root, "*") : queryAll(root, "*");
|
|
436
|
+
if (!isFragment && !all.includes(root)) all.unshift(root);
|
|
348
437
|
for (const el of all) {
|
|
438
|
+
const mEl = el;
|
|
439
|
+
if (mEl.__micraAtBound) continue;
|
|
440
|
+
let bound = false;
|
|
349
441
|
for (const attr of Array.from(el.attributes)) {
|
|
350
442
|
if (!attr.name.startsWith("@")) continue;
|
|
351
443
|
const [evSpec, ...rest] = attr.name.slice(1).split(".");
|
|
352
444
|
const method = attr.value.trim();
|
|
353
|
-
el
|
|
445
|
+
track(instance, el, evSpec, (e) => {
|
|
354
446
|
if (rest.includes("prevent")) e.preventDefault();
|
|
355
447
|
if (rest.includes("stop")) e.stopPropagation();
|
|
356
448
|
if (rest.includes("self") && e.target !== el) return;
|
|
@@ -358,7 +450,9 @@ function bindAtEvents(root, instance) {
|
|
|
358
450
|
if (typeof fn === "function") fn.call(instance, e);
|
|
359
451
|
else warn(`method "${method}" not found`);
|
|
360
452
|
});
|
|
453
|
+
bound = true;
|
|
361
454
|
}
|
|
455
|
+
if (bound) mEl.__micraAtBound = true;
|
|
362
456
|
}
|
|
363
457
|
}
|
|
364
458
|
function bindModels(root, instance) {
|
|
@@ -371,14 +465,22 @@ function bindModels(root, instance) {
|
|
|
371
465
|
mEl.__micraModel = true;
|
|
372
466
|
const key = (_a = el.dataset["model"]) != null ? _a : "";
|
|
373
467
|
const tag = el.tagName;
|
|
468
|
+
const inputEl = el;
|
|
469
|
+
const inputType = inputEl.type;
|
|
374
470
|
const update = () => {
|
|
375
|
-
|
|
471
|
+
let val;
|
|
472
|
+
if (tag === "INPUT" && inputType === "checkbox") {
|
|
473
|
+
val = inputEl.checked;
|
|
474
|
+
} else if (tag === "INPUT" && (inputType === "number" || inputType === "range")) {
|
|
475
|
+
val = inputEl.value === "" ? null : inputEl.valueAsNumber;
|
|
476
|
+
} else {
|
|
477
|
+
val = inputEl.value;
|
|
478
|
+
}
|
|
479
|
+
;
|
|
376
480
|
instance.state[key] = val;
|
|
377
481
|
};
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
update
|
|
381
|
-
);
|
|
482
|
+
const evType = tag === "SELECT" || inputType === "radio" ? "change" : "input";
|
|
483
|
+
track(instance, el, evType, update);
|
|
382
484
|
}
|
|
383
485
|
}
|
|
384
486
|
|
|
@@ -417,9 +519,18 @@ function renderList(root, state, rawState, instance) {
|
|
|
417
519
|
function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance) {
|
|
418
520
|
const nextKeys = /* @__PURE__ */ new Set();
|
|
419
521
|
const nextNodes = [];
|
|
522
|
+
let warnedNullKey = false;
|
|
523
|
+
let warnedDupKey = false;
|
|
420
524
|
for (const [index, item] of items.entries()) {
|
|
421
525
|
const key = item[keyAttr];
|
|
422
|
-
if (key == null
|
|
526
|
+
if (key == null && !warnedNullKey) {
|
|
527
|
+
warn(`data-key="${keyAttr}" is null/undefined on item at index ${index}`);
|
|
528
|
+
warnedNullKey = true;
|
|
529
|
+
}
|
|
530
|
+
if (nextKeys.has(key) && !warnedDupKey) {
|
|
531
|
+
warn(`data-key="${keyAttr}" has duplicate value ${JSON.stringify(key)} \u2014 rows will collide`);
|
|
532
|
+
warnedDupKey = true;
|
|
533
|
+
}
|
|
423
534
|
nextKeys.add(key);
|
|
424
535
|
let node = keyMap.get(key);
|
|
425
536
|
if (!node) {
|
|
@@ -522,15 +633,35 @@ function mount(selector, definition) {
|
|
|
522
633
|
let isRendering = false;
|
|
523
634
|
const schedule = createScheduler(() => instance.render());
|
|
524
635
|
instance.state = createReactiveState(rawState, schedule);
|
|
636
|
+
const boundMethods = /* @__PURE__ */ new Map();
|
|
525
637
|
const exprState = new Proxy(rawState, {
|
|
526
638
|
get(target, key) {
|
|
527
|
-
if (key
|
|
528
|
-
if (key
|
|
639
|
+
if (Object.prototype.hasOwnProperty.call(target, key)) return target[key];
|
|
640
|
+
if (Object.prototype.hasOwnProperty.call(instance, key) && typeof instance[key] === "function") {
|
|
641
|
+
const cached = boundMethods.get(key);
|
|
642
|
+
if (cached) return cached;
|
|
643
|
+
const bound = instance[key].bind(instance);
|
|
644
|
+
boundMethods.set(key, bound);
|
|
645
|
+
return bound;
|
|
646
|
+
}
|
|
529
647
|
return void 0;
|
|
648
|
+
},
|
|
649
|
+
has(target, key) {
|
|
650
|
+
if (typeof key !== "string") return false;
|
|
651
|
+
if (Object.prototype.hasOwnProperty.call(target, key)) return true;
|
|
652
|
+
return Object.prototype.hasOwnProperty.call(instance, key) && typeof instance[key] === "function";
|
|
530
653
|
}
|
|
531
654
|
});
|
|
655
|
+
let warnedReentry = false;
|
|
532
656
|
instance.render = function() {
|
|
533
|
-
if (
|
|
657
|
+
if (instance.__micraDestroyed) return;
|
|
658
|
+
if (isRendering) {
|
|
659
|
+
if (!warnedReentry) {
|
|
660
|
+
warn("render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method.");
|
|
661
|
+
warnedReentry = true;
|
|
662
|
+
}
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
534
665
|
isRendering = true;
|
|
535
666
|
try {
|
|
536
667
|
applyDirectives(root, exprState, rawState, instance);
|
|
@@ -544,8 +675,22 @@ function mount(selector, definition) {
|
|
|
544
675
|
}
|
|
545
676
|
};
|
|
546
677
|
instance.destroy = function() {
|
|
547
|
-
var _a2;
|
|
548
|
-
(
|
|
678
|
+
var _a2, _b;
|
|
679
|
+
if (instance.__micraDestroyed) return;
|
|
680
|
+
instance.__micraDestroyed = true;
|
|
681
|
+
(_a2 = instance.__micraListeners) == null ? void 0 : _a2.forEach(({ el, type, fn }) => el.removeEventListener(type, fn));
|
|
682
|
+
instance.__micraListeners = [];
|
|
683
|
+
const clearFlags = (el) => {
|
|
684
|
+
const m = el;
|
|
685
|
+
delete m.__micraEvents;
|
|
686
|
+
delete m.__micraAtBound;
|
|
687
|
+
delete m.__micraModel;
|
|
688
|
+
delete m.__micraCache;
|
|
689
|
+
};
|
|
690
|
+
clearFlags(root);
|
|
691
|
+
root.querySelectorAll("*").forEach(clearFlags);
|
|
692
|
+
(_b = instance.__micraSubs) == null ? void 0 : _b.forEach((unsub) => unsub());
|
|
693
|
+
instance.__micraSubs = [];
|
|
549
694
|
if (typeof definition.onDestroy === "function")
|
|
550
695
|
definition.onDestroy.call(instance);
|
|
551
696
|
_instances.delete(root);
|