micra.js 2.1.0 → 2.2.1
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/CHANGELOG.md +244 -0
- package/README.md +39 -14
- package/dist/core/mount.d.ts +8 -2
- package/dist/core/reactive.d.ts +1 -1
- package/dist/core/registry.d.ts +13 -4
- package/dist/dom/directives.d.ts +11 -15
- package/dist/dom/each.d.ts +10 -7
- package/dist/dom/events.d.ts +15 -5
- package/dist/dom/refs.d.ts +5 -5
- package/dist/dom/scan.d.ts +34 -0
- package/dist/index.d.ts +1 -1
- package/dist/micra.cjs.js +302 -177
- package/dist/micra.cjs.js.map +4 -4
- package/dist/micra.esm.js +302 -177
- package/dist/micra.esm.js.map +4 -4
- package/dist/micra.js +302 -177
- package/dist/micra.js.map +4 -4
- package/dist/micra.min.js +2 -2
- package/dist/types.d.ts +67 -22
- package/llms-full.txt +600 -0
- package/llms.txt +148 -0
- package/package.json +11 -3
- package/src/core/mount.ts +136 -99
- package/src/core/reactive.ts +2 -1
- package/src/core/registry.ts +19 -9
- package/src/dom/directives.ts +39 -122
- package/src/dom/each.ts +133 -37
- package/src/dom/events.ts +23 -31
- package/src/dom/refs.ts +7 -7
- package/src/dom/scan.ts +189 -0
- package/src/index.ts +2 -0
- package/src/types.ts +80 -22
- package/src/utils/expr.ts +34 -21
package/dist/micra.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Micra.js v2.1
|
|
1
|
+
/* Micra.js v2.2.1 — https://github.com/micra-js/micra — MIT */
|
|
2
2
|
|
|
3
3
|
// src/utils/fetch.ts
|
|
4
4
|
function getCSRF() {
|
|
@@ -141,27 +141,32 @@ function safeStateHas(state, key) {
|
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
143
143
|
function evalExpr(expr, state) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
144
|
+
let cached = exprCache.get(expr);
|
|
145
|
+
if (!cached) {
|
|
146
|
+
if (SIMPLE_PATH.test(expr)) {
|
|
147
|
+
cached = { kind: "path", parts: expr.split(".") };
|
|
148
|
+
} else {
|
|
149
|
+
try {
|
|
150
|
+
cached = {
|
|
151
|
+
kind: "fn",
|
|
152
|
+
fn: new Function("$s", "$safe", `with($safe){with($s){return (${expr})}}`)
|
|
153
|
+
};
|
|
154
|
+
} catch {
|
|
155
|
+
warn(`invalid expression "${expr}"`);
|
|
156
|
+
cached = { kind: "fn", fn: () => void 0 };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exprCache.set(expr, cached);
|
|
160
|
+
}
|
|
161
|
+
if (cached.kind === "path") {
|
|
162
|
+
if (!safeStateHas(state, cached.parts[0])) return void 0;
|
|
163
|
+
return cached.parts.reduce(
|
|
148
164
|
(obj, key) => obj != null ? obj[key] : void 0,
|
|
149
165
|
state
|
|
150
166
|
);
|
|
151
167
|
}
|
|
152
|
-
if (!exprCache.has(expr)) {
|
|
153
|
-
try {
|
|
154
|
-
exprCache.set(
|
|
155
|
-
expr,
|
|
156
|
-
new Function("$s", "$safe", `with($safe){with($s){return (${expr})}}`)
|
|
157
|
-
);
|
|
158
|
-
} catch {
|
|
159
|
-
warn(`invalid expression "${expr}"`);
|
|
160
|
-
exprCache.set(expr, () => void 0);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
168
|
try {
|
|
164
|
-
return
|
|
169
|
+
return cached.fn(safeStateWrap(state), SAFE_OUTER);
|
|
165
170
|
} catch (e) {
|
|
166
171
|
if (!warnedRuntime.has(expr)) {
|
|
167
172
|
warnedRuntime.add(expr);
|
|
@@ -199,11 +204,12 @@ function emit(event, payload) {
|
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
// src/core/reactive.ts
|
|
202
|
-
function createReactiveState(obj, schedule) {
|
|
207
|
+
function createReactiveState(obj, schedule, onKey) {
|
|
203
208
|
return new Proxy(obj, {
|
|
204
209
|
set(target, key, value) {
|
|
205
210
|
;
|
|
206
211
|
target[key] = value;
|
|
212
|
+
onKey == null ? void 0 : onKey(key);
|
|
207
213
|
schedule();
|
|
208
214
|
return true;
|
|
209
215
|
}
|
|
@@ -221,27 +227,6 @@ function createScheduler(render) {
|
|
|
221
227
|
};
|
|
222
228
|
}
|
|
223
229
|
|
|
224
|
-
// src/dom/query.ts
|
|
225
|
-
function queryAll(root, sel) {
|
|
226
|
-
return Array.from(root.querySelectorAll(sel));
|
|
227
|
-
}
|
|
228
|
-
function queryOwn(root, attr) {
|
|
229
|
-
return filterOwn(root, queryAll(root, `[${attr}]`));
|
|
230
|
-
}
|
|
231
|
-
function queryOwnAll(root, sel) {
|
|
232
|
-
return filterOwn(root, queryAll(root, sel));
|
|
233
|
-
}
|
|
234
|
-
function filterOwn(root, els) {
|
|
235
|
-
return els.filter((el) => {
|
|
236
|
-
let node = el.parentElement;
|
|
237
|
-
while (node && node !== root) {
|
|
238
|
-
if (node.hasAttribute("data-component")) return false;
|
|
239
|
-
node = node.parentElement;
|
|
240
|
-
}
|
|
241
|
-
return true;
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
230
|
// src/dom/directives.ts
|
|
246
231
|
function applyText(el, expr, state) {
|
|
247
232
|
var _a;
|
|
@@ -250,7 +235,8 @@ function applyText(el, expr, state) {
|
|
|
250
235
|
}
|
|
251
236
|
function applyHtml(el, expr, state) {
|
|
252
237
|
var _a;
|
|
253
|
-
|
|
238
|
+
const html = String((_a = evalExpr(expr, state)) != null ? _a : "");
|
|
239
|
+
if (el.innerHTML !== html) el.innerHTML = html;
|
|
254
240
|
}
|
|
255
241
|
function applyIf(binding, state) {
|
|
256
242
|
const el = binding.el;
|
|
@@ -267,7 +253,9 @@ function applyIf(binding, state) {
|
|
|
267
253
|
}
|
|
268
254
|
}
|
|
269
255
|
function applyShow(el, expr, state) {
|
|
270
|
-
|
|
256
|
+
const desired = evalExpr(expr, state) ? "" : "none";
|
|
257
|
+
const htmlEl = el;
|
|
258
|
+
if (htmlEl.style.display !== desired) htmlEl.style.display = desired;
|
|
271
259
|
}
|
|
272
260
|
function applyBind(el, pairs, state) {
|
|
273
261
|
for (const [attr, valExpr] of pairs) {
|
|
@@ -295,92 +283,37 @@ function applyClass(el, pairs, state) {
|
|
|
295
283
|
el.classList.toggle(cls, Boolean(evalExpr(valExpr, state)));
|
|
296
284
|
}
|
|
297
285
|
}
|
|
298
|
-
function parsePairs(expr) {
|
|
299
|
-
const out = [];
|
|
300
|
-
for (const part of expr.split(",")) {
|
|
301
|
-
const colonIdx = part.indexOf(":");
|
|
302
|
-
if (colonIdx === -1) continue;
|
|
303
|
-
const left = part.slice(0, colonIdx).trim();
|
|
304
|
-
const right = part.slice(colonIdx + 1).trim();
|
|
305
|
-
if (!left) continue;
|
|
306
|
-
out.push([left, right]);
|
|
307
|
-
}
|
|
308
|
-
return out;
|
|
309
|
-
}
|
|
310
286
|
function applyModel(el, key, rawState) {
|
|
311
287
|
const html = el;
|
|
312
288
|
const stateVal = rawState[key];
|
|
313
289
|
const desired = stateVal == null ? "" : String(stateVal);
|
|
314
290
|
if (html.value !== desired) html.value = desired;
|
|
315
291
|
}
|
|
316
|
-
function
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if: pick("data-if"),
|
|
328
|
-
show: pick("data-show"),
|
|
329
|
-
bind: pickPairs("data-bind"),
|
|
330
|
-
model: pick("data-model"),
|
|
331
|
-
class: pickPairs("data-class")
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
function applyDirectives(root, state, rawState, _instance) {
|
|
335
|
-
if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
336
|
-
applyFromList(buildFragmentList(root), state, rawState);
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
const el = root;
|
|
340
|
-
if (!el.__micraCache) el.__micraCache = buildCache(el);
|
|
341
|
-
applyFromList(el.__micraCache, state, rawState);
|
|
342
|
-
}
|
|
343
|
-
function applyFromList(cache, state, rawState) {
|
|
344
|
-
cache.if.forEach((b) => applyIf(b, state));
|
|
345
|
-
cache.text.forEach((b) => applyText(b.el, b.expr, state));
|
|
346
|
-
cache.html.forEach((b) => applyHtml(b.el, b.expr, state));
|
|
347
|
-
cache.show.forEach((b) => applyShow(b.el, b.expr, state));
|
|
348
|
-
cache.bind.forEach((b) => applyBind(b.el, b.pairs, state));
|
|
349
|
-
cache.model.forEach((b) => applyModel(b.el, b.expr.trim(), rawState));
|
|
350
|
-
cache.class.forEach((b) => applyClass(b.el, b.pairs, state));
|
|
351
|
-
}
|
|
352
|
-
function buildFragmentList(frag) {
|
|
353
|
-
const pick = (attr) => queryAll(frag, `[${attr}]`).filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
|
|
354
|
-
const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
|
|
355
|
-
return {
|
|
356
|
-
text: pick("data-text"),
|
|
357
|
-
html: pick("data-html"),
|
|
358
|
-
if: pick("data-if"),
|
|
359
|
-
show: pick("data-show"),
|
|
360
|
-
bind: pickPairs("data-bind"),
|
|
361
|
-
model: pick("data-model"),
|
|
362
|
-
class: pickPairs("data-class")
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
function validateDirectives(root) {
|
|
366
|
-
var _a, _b;
|
|
367
|
-
queryOwn(root, "data-each").forEach((el) => {
|
|
292
|
+
function applyDirectives(scan, state, rawState, _instance) {
|
|
293
|
+
for (const b of scan.if) applyIf(b, state);
|
|
294
|
+
for (const b of scan.text) applyText(b.el, b.expr, state);
|
|
295
|
+
for (const b of scan.html) applyHtml(b.el, b.expr, state);
|
|
296
|
+
for (const b of scan.show) applyShow(b.el, b.expr, state);
|
|
297
|
+
for (const b of scan.bind) applyBind(b.el, b.pairs, state);
|
|
298
|
+
for (const b of scan.model) applyModel(b.el, b.expr.trim(), rawState);
|
|
299
|
+
for (const b of scan.class) applyClass(b.el, b.pairs, state);
|
|
300
|
+
}
|
|
301
|
+
function validateDirectives(scan) {
|
|
302
|
+
for (const el of scan.each) {
|
|
368
303
|
const tmpl = el;
|
|
369
304
|
if (!el.hasAttribute("data-key") && !tmpl.__micraNoKeyWarned) {
|
|
370
305
|
tmpl.__micraNoKeyWarned = true;
|
|
371
|
-
warn(
|
|
306
|
+
warn(
|
|
307
|
+
`data-each="${el.getAttribute("data-each")}" has no data-key \u2014 keyed diff disabled. Add data-key="id" for better performance.`
|
|
308
|
+
);
|
|
372
309
|
}
|
|
373
|
-
}
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return ((_a2 = p.trim().split(":")[0]) == null ? void 0 : _a2.trim()) === "class";
|
|
381
|
-
});
|
|
382
|
-
if (hasClassBind && el.hasAttribute("data-class")) {
|
|
383
|
-
warn(`element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.`);
|
|
310
|
+
}
|
|
311
|
+
for (const b of scan.bind) {
|
|
312
|
+
const hasClassBind = b.pairs.some((p) => p[0] === "class");
|
|
313
|
+
if (hasClassBind && b.el.hasAttribute("data-class")) {
|
|
314
|
+
warn(
|
|
315
|
+
`element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.`
|
|
316
|
+
);
|
|
384
317
|
}
|
|
385
318
|
}
|
|
386
319
|
}
|
|
@@ -391,17 +324,13 @@ function track(instance, el, type, fn) {
|
|
|
391
324
|
el.addEventListener(type, fn);
|
|
392
325
|
((_a = instance.__micraListeners) != null ? _a : instance.__micraListeners = []).push({ el, type, fn });
|
|
393
326
|
}
|
|
394
|
-
function bindDataOn(
|
|
395
|
-
var _a
|
|
396
|
-
const isFragment = root.nodeType === 11;
|
|
397
|
-
const els = isFragment ? queryAll(root, "[data-on]") : queryOwn(root, "data-on");
|
|
398
|
-
if (!isFragment && ((_a = root.hasAttribute) == null ? void 0 : _a.call(root, "data-on")) && !els.includes(root))
|
|
399
|
-
els.unshift(root);
|
|
327
|
+
function bindDataOn(els, instance) {
|
|
328
|
+
var _a;
|
|
400
329
|
for (const el of els) {
|
|
401
330
|
const mEl = el;
|
|
402
331
|
if (mEl.__micraEvents) continue;
|
|
403
332
|
mEl.__micraEvents = true;
|
|
404
|
-
const spec = (
|
|
333
|
+
const spec = (_a = mEl.dataset["on"]) != null ? _a : "";
|
|
405
334
|
for (const part of spec.split(",")) {
|
|
406
335
|
const [evSpec, method] = part.trim().split(":");
|
|
407
336
|
if (!evSpec || !method) continue;
|
|
@@ -417,11 +346,8 @@ function bindDataOn(root, instance) {
|
|
|
417
346
|
}
|
|
418
347
|
}
|
|
419
348
|
}
|
|
420
|
-
function bindAtEvents(
|
|
421
|
-
const
|
|
422
|
-
const all = isFragment ? queryAll(root, "*") : queryOwnAll(root, "*");
|
|
423
|
-
if (!isFragment && !all.includes(root)) all.unshift(root);
|
|
424
|
-
for (const el of all) {
|
|
349
|
+
function bindAtEvents(els, instance) {
|
|
350
|
+
for (const el of els) {
|
|
425
351
|
const mEl = el;
|
|
426
352
|
if (mEl.__micraAtBound) continue;
|
|
427
353
|
let bound = false;
|
|
@@ -442,15 +368,12 @@ function bindAtEvents(root, instance) {
|
|
|
442
368
|
if (bound) mEl.__micraAtBound = true;
|
|
443
369
|
}
|
|
444
370
|
}
|
|
445
|
-
function bindModels(
|
|
446
|
-
|
|
447
|
-
const isFragment = root.nodeType === 11;
|
|
448
|
-
const els = isFragment ? queryAll(root, "[data-model]") : queryOwn(root, "data-model");
|
|
449
|
-
for (const el of els) {
|
|
371
|
+
function bindModels(bindings, instance) {
|
|
372
|
+
for (const { el, expr } of bindings) {
|
|
450
373
|
const mEl = el;
|
|
451
374
|
if (mEl.__micraModel) continue;
|
|
452
375
|
mEl.__micraModel = true;
|
|
453
|
-
const key = (
|
|
376
|
+
const key = expr.trim();
|
|
454
377
|
const tag = el.tagName;
|
|
455
378
|
const inputEl = el;
|
|
456
379
|
const inputType = inputEl.type;
|
|
@@ -471,11 +394,132 @@ function bindModels(root, instance) {
|
|
|
471
394
|
}
|
|
472
395
|
}
|
|
473
396
|
|
|
397
|
+
// src/dom/scan.ts
|
|
398
|
+
function emptyScan() {
|
|
399
|
+
return {
|
|
400
|
+
text: [],
|
|
401
|
+
html: [],
|
|
402
|
+
if: [],
|
|
403
|
+
show: [],
|
|
404
|
+
bind: [],
|
|
405
|
+
model: [],
|
|
406
|
+
class: [],
|
|
407
|
+
each: [],
|
|
408
|
+
on: [],
|
|
409
|
+
atEvents: [],
|
|
410
|
+
refs: []
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function parsePairs(expr) {
|
|
414
|
+
const out = [];
|
|
415
|
+
for (const part of expr.split(",")) {
|
|
416
|
+
const colon = part.indexOf(":");
|
|
417
|
+
if (colon === -1) continue;
|
|
418
|
+
const left = part.slice(0, colon).trim();
|
|
419
|
+
const right = part.slice(colon + 1).trim();
|
|
420
|
+
if (!left) continue;
|
|
421
|
+
out.push([left, right]);
|
|
422
|
+
}
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
function classify(el, scan) {
|
|
426
|
+
if (el.tagName === "TEMPLATE") {
|
|
427
|
+
if (el.hasAttribute("data-each")) scan.each.push(el);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const attrs = el.attributes;
|
|
431
|
+
let atEventSeen = false;
|
|
432
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
433
|
+
const a = attrs[i];
|
|
434
|
+
const name = a.name;
|
|
435
|
+
const first = name.charCodeAt(0);
|
|
436
|
+
if (first === 64) {
|
|
437
|
+
if (!atEventSeen) {
|
|
438
|
+
scan.atEvents.push(el);
|
|
439
|
+
atEventSeen = true;
|
|
440
|
+
}
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (first === 100 && name.length >= 6 && name.charCodeAt(4) === 45) {
|
|
444
|
+
const rest = name.slice(5);
|
|
445
|
+
switch (rest) {
|
|
446
|
+
case "text":
|
|
447
|
+
scan.text.push({ el, expr: a.value });
|
|
448
|
+
break;
|
|
449
|
+
case "html":
|
|
450
|
+
scan.html.push({ el, expr: a.value });
|
|
451
|
+
break;
|
|
452
|
+
case "if":
|
|
453
|
+
scan.if.push({ el, expr: a.value });
|
|
454
|
+
break;
|
|
455
|
+
case "show":
|
|
456
|
+
scan.show.push({ el, expr: a.value });
|
|
457
|
+
break;
|
|
458
|
+
case "bind": {
|
|
459
|
+
const pairs = parsePairs(a.value);
|
|
460
|
+
scan.bind.push({ el, expr: a.value, pairs });
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case "model":
|
|
464
|
+
scan.model.push({ el, expr: a.value });
|
|
465
|
+
break;
|
|
466
|
+
case "class": {
|
|
467
|
+
const pairs = parsePairs(a.value);
|
|
468
|
+
scan.class.push({ el, expr: a.value, pairs });
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case "on":
|
|
472
|
+
scan.on.push(el);
|
|
473
|
+
break;
|
|
474
|
+
case "ref":
|
|
475
|
+
scan.refs.push(el);
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
var NESTED_COMPONENT_FILTER = {
|
|
482
|
+
acceptNode(node) {
|
|
483
|
+
if (node.hasAttribute("data-component"))
|
|
484
|
+
return NodeFilter.FILTER_REJECT;
|
|
485
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
function scanComponent(root) {
|
|
489
|
+
const scan = emptyScan();
|
|
490
|
+
classify(root, scan);
|
|
491
|
+
const walker = document.createTreeWalker(
|
|
492
|
+
root,
|
|
493
|
+
NodeFilter.SHOW_ELEMENT,
|
|
494
|
+
NESTED_COMPONENT_FILTER
|
|
495
|
+
);
|
|
496
|
+
let node = walker.nextNode();
|
|
497
|
+
while (node) {
|
|
498
|
+
classify(node, scan);
|
|
499
|
+
node = walker.nextNode();
|
|
500
|
+
}
|
|
501
|
+
return scan;
|
|
502
|
+
}
|
|
503
|
+
function scanFragment(frag) {
|
|
504
|
+
const scan = emptyScan();
|
|
505
|
+
const walker = document.createTreeWalker(
|
|
506
|
+
frag,
|
|
507
|
+
NodeFilter.SHOW_ELEMENT,
|
|
508
|
+
NESTED_COMPONENT_FILTER
|
|
509
|
+
);
|
|
510
|
+
let node = walker.nextNode();
|
|
511
|
+
while (node) {
|
|
512
|
+
classify(node, scan);
|
|
513
|
+
node = walker.nextNode();
|
|
514
|
+
}
|
|
515
|
+
return scan;
|
|
516
|
+
}
|
|
517
|
+
|
|
474
518
|
// src/dom/each.ts
|
|
475
|
-
function renderList(
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if (tmplEl.tagName !== "TEMPLATE")
|
|
519
|
+
function renderList(templates, state, rawState, instance, triggerKey) {
|
|
520
|
+
var _a;
|
|
521
|
+
for (const tmplEl of templates) {
|
|
522
|
+
if (tmplEl.tagName !== "TEMPLATE") continue;
|
|
479
523
|
const tmpl = tmplEl;
|
|
480
524
|
const itemsExpr = tmpl.getAttribute("data-each");
|
|
481
525
|
const keyAttr = (_a = tmpl.getAttribute("data-key")) != null ? _a : null;
|
|
@@ -489,22 +533,23 @@ function renderList(root, state, rawState, instance) {
|
|
|
489
533
|
}
|
|
490
534
|
const marker = tmpl.__micraMarker;
|
|
491
535
|
const keyMap = tmpl.__micraNodes;
|
|
492
|
-
|
|
493
|
-
if (!parent) return;
|
|
536
|
+
if (!marker.parentNode) continue;
|
|
494
537
|
if (!Array.isArray(items)) {
|
|
495
538
|
tmpl.__micraList.forEach((n) => n.remove());
|
|
496
539
|
tmpl.__micraList = [];
|
|
497
540
|
keyMap.clear();
|
|
498
|
-
|
|
541
|
+
continue;
|
|
499
542
|
}
|
|
543
|
+
const canSkipUnchanged = triggerKey !== null && triggerKey !== "MULTIPLE" && triggerKey === itemsExpr;
|
|
500
544
|
if (keyAttr) {
|
|
501
|
-
renderKeyed(tmpl, items, keyAttr, marker, keyMap,
|
|
545
|
+
renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, instance, canSkipUnchanged);
|
|
502
546
|
} else {
|
|
503
|
-
renderNoKey(tmpl, items, marker,
|
|
547
|
+
renderNoKey(tmpl, items, marker, state, rawState, instance);
|
|
504
548
|
}
|
|
505
|
-
}
|
|
549
|
+
}
|
|
506
550
|
}
|
|
507
|
-
function renderKeyed(tmpl, items, keyAttr, marker, keyMap,
|
|
551
|
+
function renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, instance, canSkipUnchanged) {
|
|
552
|
+
var _a;
|
|
508
553
|
const nextKeys = /* @__PURE__ */ new Set();
|
|
509
554
|
const nextNodes = [];
|
|
510
555
|
let warnedNullKey = false;
|
|
@@ -532,14 +577,24 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawSta
|
|
|
532
577
|
}
|
|
533
578
|
node.__micraKey = key;
|
|
534
579
|
keyMap.set(key, node);
|
|
535
|
-
|
|
536
|
-
|
|
580
|
+
const rowScan2 = scanComponent(node);
|
|
581
|
+
node.__micraScan = rowScan2;
|
|
582
|
+
bindDataOn(rowScan2.on, instance);
|
|
583
|
+
bindAtEvents(rowScan2.atEvents, instance);
|
|
584
|
+
bindModels(rowScan2.model, instance);
|
|
585
|
+
node._itemState = Object.create(state);
|
|
586
|
+
} else if (canSkipUnchanged && node.__micraItem === item && node.__micraIndex === index) {
|
|
587
|
+
nextNodes.push(node);
|
|
588
|
+
continue;
|
|
537
589
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
590
|
+
node.__micraItem = item;
|
|
591
|
+
node.__micraIndex = index;
|
|
592
|
+
const itemState = node._itemState;
|
|
593
|
+
itemState.item = item;
|
|
594
|
+
itemState.index = index;
|
|
595
|
+
itemState.$index = index;
|
|
596
|
+
const rowScan = (_a = node.__micraScan) != null ? _a : node.__micraScan = scanComponent(node);
|
|
597
|
+
applyDirectives(rowScan, itemState, rawState, instance);
|
|
543
598
|
nextNodes.push(node);
|
|
544
599
|
}
|
|
545
600
|
for (const [key, node] of keyMap) {
|
|
@@ -548,14 +603,64 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawSta
|
|
|
548
603
|
keyMap.delete(key);
|
|
549
604
|
}
|
|
550
605
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
|
|
606
|
+
const prevList = tmpl.__micraList;
|
|
607
|
+
if (prevList.length === 0) {
|
|
608
|
+
if (nextNodes.length) {
|
|
609
|
+
const frag = document.createDocumentFragment();
|
|
610
|
+
for (const node of nextNodes) frag.append(node);
|
|
611
|
+
marker.after(frag);
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
let orderChanged = nextNodes.length !== prevList.length;
|
|
615
|
+
if (!orderChanged) {
|
|
616
|
+
for (let i = 0; i < nextNodes.length; i++) {
|
|
617
|
+
if (nextNodes[i] !== prevList[i]) {
|
|
618
|
+
orderChanged = true;
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (orderChanged) reorderKeyed(nextNodes, prevList, marker);
|
|
555
624
|
}
|
|
556
625
|
tmpl.__micraList = nextNodes;
|
|
557
626
|
}
|
|
558
|
-
function
|
|
627
|
+
function reorderKeyed(nextNodes, prevList, marker) {
|
|
628
|
+
const prevPos = /* @__PURE__ */ new Map();
|
|
629
|
+
for (let i = 0; i < prevList.length; i++) prevPos.set(prevList[i], i);
|
|
630
|
+
const n = nextNodes.length;
|
|
631
|
+
const tails = [];
|
|
632
|
+
const tailIdx = [];
|
|
633
|
+
const prev = new Array(n).fill(-1);
|
|
634
|
+
for (let i = 0; i < n; i++) {
|
|
635
|
+
const p = prevPos.get(nextNodes[i]);
|
|
636
|
+
if (p === void 0) continue;
|
|
637
|
+
let lo = 0, hi = tails.length;
|
|
638
|
+
while (lo < hi) {
|
|
639
|
+
const m = lo + hi >> 1;
|
|
640
|
+
tails[m] < p ? lo = m + 1 : hi = m;
|
|
641
|
+
}
|
|
642
|
+
if (lo > 0) prev[i] = tailIdx[lo - 1];
|
|
643
|
+
tails[lo] = p;
|
|
644
|
+
tailIdx[lo] = i;
|
|
645
|
+
}
|
|
646
|
+
const stable = /* @__PURE__ */ new Set();
|
|
647
|
+
let idx = tailIdx[tails.length - 1];
|
|
648
|
+
while (idx >= 0) {
|
|
649
|
+
stable.add(idx);
|
|
650
|
+
idx = prev[idx];
|
|
651
|
+
}
|
|
652
|
+
let anchor = marker;
|
|
653
|
+
for (let i = 0; i < n; i++) {
|
|
654
|
+
const node = nextNodes[i];
|
|
655
|
+
if (stable.has(i)) {
|
|
656
|
+
anchor = node;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
anchor.after(node);
|
|
660
|
+
anchor = node;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function renderNoKey(tmpl, items, marker, state, rawState, instance) {
|
|
559
664
|
tmpl.__micraList.forEach((n) => n.remove());
|
|
560
665
|
tmpl.__micraList = [];
|
|
561
666
|
const frag = document.createDocumentFragment();
|
|
@@ -565,9 +670,11 @@ function renderNoKey(tmpl, items, marker, parent, state, rawState, instance) {
|
|
|
565
670
|
Object.create(state),
|
|
566
671
|
{ item, index, $index: index }
|
|
567
672
|
);
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
673
|
+
const fragScan = scanFragment(clone);
|
|
674
|
+
applyDirectives(fragScan, itemState, rawState, instance);
|
|
675
|
+
bindDataOn(fragScan.on, instance);
|
|
676
|
+
bindAtEvents(fragScan.atEvents, instance);
|
|
677
|
+
bindModels(fragScan.model, instance);
|
|
571
678
|
const nodes = Array.from(clone.childNodes);
|
|
572
679
|
nodes.forEach((n) => {
|
|
573
680
|
n.__micraEach = true;
|
|
@@ -575,13 +682,14 @@ function renderNoKey(tmpl, items, marker, parent, state, rawState, instance) {
|
|
|
575
682
|
});
|
|
576
683
|
tmpl.__micraList.push(...nodes);
|
|
577
684
|
}
|
|
578
|
-
|
|
685
|
+
marker.after(frag);
|
|
579
686
|
}
|
|
580
687
|
|
|
581
688
|
// src/dom/refs.ts
|
|
582
|
-
function collectRefs(
|
|
689
|
+
function collectRefs(els, instance) {
|
|
690
|
+
if (!els.length) return;
|
|
583
691
|
instance.refs = {};
|
|
584
|
-
for (const el of
|
|
692
|
+
for (const el of els) {
|
|
585
693
|
const name = el.dataset["ref"];
|
|
586
694
|
if (name) instance.refs[name] = el;
|
|
587
695
|
}
|
|
@@ -595,10 +703,13 @@ function mount(selector, definition) {
|
|
|
595
703
|
warn(`"${selector}" not found`);
|
|
596
704
|
return null;
|
|
597
705
|
}
|
|
598
|
-
if (_instances.has(root))
|
|
706
|
+
if (_instances.has(root))
|
|
707
|
+
return _instances.get(root);
|
|
599
708
|
const rawState = { ...(_a = definition.state) != null ? _a : {} };
|
|
600
709
|
const instance = { $el: root, refs: {} };
|
|
601
|
-
for (const [key, val] of Object.entries(
|
|
710
|
+
for (const [key, val] of Object.entries(
|
|
711
|
+
definition
|
|
712
|
+
)) {
|
|
602
713
|
if (key === "state" || key === "onCreate" || key === "onDestroy") continue;
|
|
603
714
|
if (typeof val === "function") instance[key] = val;
|
|
604
715
|
}
|
|
@@ -619,8 +730,12 @@ function mount(selector, definition) {
|
|
|
619
730
|
return unsub;
|
|
620
731
|
};
|
|
621
732
|
let isRendering = false;
|
|
733
|
+
let _triggerKey = null;
|
|
622
734
|
const schedule = createScheduler(() => instance.render());
|
|
623
|
-
instance.state = createReactiveState(rawState, schedule)
|
|
735
|
+
instance.state = createReactiveState(rawState, schedule, (key) => {
|
|
736
|
+
if (_triggerKey === null) _triggerKey = key;
|
|
737
|
+
else if (_triggerKey !== key) _triggerKey = "MULTIPLE";
|
|
738
|
+
});
|
|
624
739
|
const boundMethods = /* @__PURE__ */ new Map();
|
|
625
740
|
const exprState = new Proxy(rawState, {
|
|
626
741
|
get(target, key) {
|
|
@@ -642,22 +757,29 @@ function mount(selector, definition) {
|
|
|
642
757
|
});
|
|
643
758
|
let warnedReentry = false;
|
|
644
759
|
instance.render = function() {
|
|
760
|
+
var _a2;
|
|
645
761
|
if (instance.__micraDestroyed) return;
|
|
762
|
+
const triggerKey = _triggerKey;
|
|
763
|
+
_triggerKey = null;
|
|
646
764
|
if (isRendering) {
|
|
647
765
|
if (!warnedReentry) {
|
|
648
|
-
warn(
|
|
766
|
+
warn(
|
|
767
|
+
"render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method."
|
|
768
|
+
);
|
|
649
769
|
warnedReentry = true;
|
|
650
770
|
}
|
|
651
771
|
return;
|
|
652
772
|
}
|
|
653
773
|
isRendering = true;
|
|
654
774
|
try {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
775
|
+
const mRoot2 = root;
|
|
776
|
+
const scan = (_a2 = mRoot2.__micraScan) != null ? _a2 : mRoot2.__micraScan = scanComponent(root);
|
|
777
|
+
applyDirectives(scan, exprState, rawState, instance);
|
|
778
|
+
renderList(scan.each, exprState, rawState, instance, triggerKey);
|
|
779
|
+
bindDataOn(scan.on, instance);
|
|
780
|
+
bindAtEvents(scan.atEvents, instance);
|
|
781
|
+
bindModels(scan.model, instance);
|
|
782
|
+
collectRefs(scan.refs, instance);
|
|
661
783
|
} finally {
|
|
662
784
|
isRendering = false;
|
|
663
785
|
}
|
|
@@ -666,14 +788,16 @@ function mount(selector, definition) {
|
|
|
666
788
|
var _a2, _b;
|
|
667
789
|
if (instance.__micraDestroyed) return;
|
|
668
790
|
instance.__micraDestroyed = true;
|
|
669
|
-
(_a2 = instance.__micraListeners) == null ? void 0 : _a2.forEach(
|
|
791
|
+
(_a2 = instance.__micraListeners) == null ? void 0 : _a2.forEach(
|
|
792
|
+
({ el, type, fn }) => el.removeEventListener(type, fn)
|
|
793
|
+
);
|
|
670
794
|
instance.__micraListeners = [];
|
|
671
795
|
const clearFlags = (el) => {
|
|
672
796
|
const m = el;
|
|
673
797
|
delete m.__micraEvents;
|
|
674
798
|
delete m.__micraAtBound;
|
|
675
799
|
delete m.__micraModel;
|
|
676
|
-
delete m.
|
|
800
|
+
delete m.__micraScan;
|
|
677
801
|
};
|
|
678
802
|
clearFlags(root);
|
|
679
803
|
root.querySelectorAll("*").forEach(clearFlags);
|
|
@@ -685,7 +809,8 @@ function mount(selector, definition) {
|
|
|
685
809
|
};
|
|
686
810
|
_instances.set(root, instance);
|
|
687
811
|
instance.render();
|
|
688
|
-
|
|
812
|
+
const mRoot = root;
|
|
813
|
+
if (mRoot.__micraScan) validateDirectives(mRoot.__micraScan);
|
|
689
814
|
if (typeof definition.onCreate === "function")
|
|
690
815
|
Promise.resolve().then(
|
|
691
816
|
() => definition.onCreate.call(instance)
|