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/dist/micra.js CHANGED
@@ -1,4 +1,4 @@
1
- /* Micra.js v2.1.0 — https://github.com/micra-js/micra — MIT */
1
+ /* Micra.js v2.2.1 — https://github.com/micra-js/micra — MIT */
2
2
  "use strict";
3
3
  var Micra = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -176,27 +176,32 @@ var Micra = (() => {
176
176
  return false;
177
177
  }
178
178
  function evalExpr(expr, state) {
179
- if (SIMPLE_PATH.test(expr)) {
180
- const parts = expr.split(".");
181
- if (!safeStateHas(state, parts[0])) return void 0;
182
- return parts.reduce(
179
+ let cached = exprCache.get(expr);
180
+ if (!cached) {
181
+ if (SIMPLE_PATH.test(expr)) {
182
+ cached = { kind: "path", parts: expr.split(".") };
183
+ } else {
184
+ try {
185
+ cached = {
186
+ kind: "fn",
187
+ fn: new Function("$s", "$safe", `with($safe){with($s){return (${expr})}}`)
188
+ };
189
+ } catch {
190
+ warn(`invalid expression "${expr}"`);
191
+ cached = { kind: "fn", fn: () => void 0 };
192
+ }
193
+ }
194
+ exprCache.set(expr, cached);
195
+ }
196
+ if (cached.kind === "path") {
197
+ if (!safeStateHas(state, cached.parts[0])) return void 0;
198
+ return cached.parts.reduce(
183
199
  (obj, key) => obj != null ? obj[key] : void 0,
184
200
  state
185
201
  );
186
202
  }
187
- if (!exprCache.has(expr)) {
188
- try {
189
- exprCache.set(
190
- expr,
191
- new Function("$s", "$safe", `with($safe){with($s){return (${expr})}}`)
192
- );
193
- } catch {
194
- warn(`invalid expression "${expr}"`);
195
- exprCache.set(expr, () => void 0);
196
- }
197
- }
198
203
  try {
199
- return exprCache.get(expr)(safeStateWrap(state), SAFE_OUTER);
204
+ return cached.fn(safeStateWrap(state), SAFE_OUTER);
200
205
  } catch (e) {
201
206
  if (!warnedRuntime.has(expr)) {
202
207
  warnedRuntime.add(expr);
@@ -234,11 +239,12 @@ var Micra = (() => {
234
239
  }
235
240
 
236
241
  // src/core/reactive.ts
237
- function createReactiveState(obj, schedule) {
242
+ function createReactiveState(obj, schedule, onKey) {
238
243
  return new Proxy(obj, {
239
244
  set(target, key, value) {
240
245
  ;
241
246
  target[key] = value;
247
+ onKey == null ? void 0 : onKey(key);
242
248
  schedule();
243
249
  return true;
244
250
  }
@@ -256,27 +262,6 @@ var Micra = (() => {
256
262
  };
257
263
  }
258
264
 
259
- // src/dom/query.ts
260
- function queryAll(root, sel) {
261
- return Array.from(root.querySelectorAll(sel));
262
- }
263
- function queryOwn(root, attr) {
264
- return filterOwn(root, queryAll(root, `[${attr}]`));
265
- }
266
- function queryOwnAll(root, sel) {
267
- return filterOwn(root, queryAll(root, sel));
268
- }
269
- function filterOwn(root, els) {
270
- return els.filter((el) => {
271
- let node = el.parentElement;
272
- while (node && node !== root) {
273
- if (node.hasAttribute("data-component")) return false;
274
- node = node.parentElement;
275
- }
276
- return true;
277
- });
278
- }
279
-
280
265
  // src/dom/directives.ts
281
266
  function applyText(el, expr, state) {
282
267
  var _a;
@@ -285,7 +270,8 @@ var Micra = (() => {
285
270
  }
286
271
  function applyHtml(el, expr, state) {
287
272
  var _a;
288
- el.innerHTML = String((_a = evalExpr(expr, state)) != null ? _a : "");
273
+ const html = String((_a = evalExpr(expr, state)) != null ? _a : "");
274
+ if (el.innerHTML !== html) el.innerHTML = html;
289
275
  }
290
276
  function applyIf(binding, state) {
291
277
  const el = binding.el;
@@ -302,7 +288,9 @@ var Micra = (() => {
302
288
  }
303
289
  }
304
290
  function applyShow(el, expr, state) {
305
- el.style.display = evalExpr(expr, state) ? "" : "none";
291
+ const desired = evalExpr(expr, state) ? "" : "none";
292
+ const htmlEl = el;
293
+ if (htmlEl.style.display !== desired) htmlEl.style.display = desired;
306
294
  }
307
295
  function applyBind(el, pairs, state) {
308
296
  for (const [attr, valExpr] of pairs) {
@@ -330,92 +318,37 @@ var Micra = (() => {
330
318
  el.classList.toggle(cls, Boolean(evalExpr(valExpr, state)));
331
319
  }
332
320
  }
333
- function parsePairs(expr) {
334
- const out = [];
335
- for (const part of expr.split(",")) {
336
- const colonIdx = part.indexOf(":");
337
- if (colonIdx === -1) continue;
338
- const left = part.slice(0, colonIdx).trim();
339
- const right = part.slice(colonIdx + 1).trim();
340
- if (!left) continue;
341
- out.push([left, right]);
342
- }
343
- return out;
344
- }
345
321
  function applyModel(el, key, rawState) {
346
322
  const html = el;
347
323
  const stateVal = rawState[key];
348
324
  const desired = stateVal == null ? "" : String(stateVal);
349
325
  if (html.value !== desired) html.value = desired;
350
326
  }
351
- function buildCache(root) {
352
- const pick = (attr) => {
353
- var _a;
354
- const els = queryOwn(root, attr);
355
- if ((_a = root.hasAttribute) == null ? void 0 : _a.call(root, attr)) els.unshift(root);
356
- return els.filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
357
- };
358
- const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
359
- return {
360
- text: pick("data-text"),
361
- html: pick("data-html"),
362
- if: pick("data-if"),
363
- show: pick("data-show"),
364
- bind: pickPairs("data-bind"),
365
- model: pick("data-model"),
366
- class: pickPairs("data-class")
367
- };
368
- }
369
- function applyDirectives(root, state, rawState, _instance) {
370
- if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
371
- applyFromList(buildFragmentList(root), state, rawState);
372
- return;
373
- }
374
- const el = root;
375
- if (!el.__micraCache) el.__micraCache = buildCache(el);
376
- applyFromList(el.__micraCache, state, rawState);
377
- }
378
- function applyFromList(cache, state, rawState) {
379
- cache.if.forEach((b) => applyIf(b, state));
380
- cache.text.forEach((b) => applyText(b.el, b.expr, state));
381
- cache.html.forEach((b) => applyHtml(b.el, b.expr, state));
382
- cache.show.forEach((b) => applyShow(b.el, b.expr, state));
383
- cache.bind.forEach((b) => applyBind(b.el, b.pairs, state));
384
- cache.model.forEach((b) => applyModel(b.el, b.expr.trim(), rawState));
385
- cache.class.forEach((b) => applyClass(b.el, b.pairs, state));
386
- }
387
- function buildFragmentList(frag) {
388
- const pick = (attr) => queryAll(frag, `[${attr}]`).filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
389
- const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
390
- return {
391
- text: pick("data-text"),
392
- html: pick("data-html"),
393
- if: pick("data-if"),
394
- show: pick("data-show"),
395
- bind: pickPairs("data-bind"),
396
- model: pick("data-model"),
397
- class: pickPairs("data-class")
398
- };
399
- }
400
- function validateDirectives(root) {
401
- var _a, _b;
402
- queryOwn(root, "data-each").forEach((el) => {
327
+ function applyDirectives(scan, state, rawState, _instance) {
328
+ for (const b of scan.if) applyIf(b, state);
329
+ for (const b of scan.text) applyText(b.el, b.expr, state);
330
+ for (const b of scan.html) applyHtml(b.el, b.expr, state);
331
+ for (const b of scan.show) applyShow(b.el, b.expr, state);
332
+ for (const b of scan.bind) applyBind(b.el, b.pairs, state);
333
+ for (const b of scan.model) applyModel(b.el, b.expr.trim(), rawState);
334
+ for (const b of scan.class) applyClass(b.el, b.pairs, state);
335
+ }
336
+ function validateDirectives(scan) {
337
+ for (const el of scan.each) {
403
338
  const tmpl = el;
404
339
  if (!el.hasAttribute("data-key") && !tmpl.__micraNoKeyWarned) {
405
340
  tmpl.__micraNoKeyWarned = true;
406
- warn(`data-each="${el.getAttribute("data-each")}" has no data-key \u2014 keyed diff disabled. Add data-key="id" for better performance.`);
341
+ warn(
342
+ `data-each="${el.getAttribute("data-each")}" has no data-key \u2014 keyed diff disabled. Add data-key="id" for better performance.`
343
+ );
407
344
  }
408
- });
409
- const bindEls = queryOwn(root, "data-bind");
410
- if (((_a = root.hasAttribute) == null ? void 0 : _a.call(root, "data-bind")) && !bindEls.includes(root)) bindEls.unshift(root);
411
- for (const el of bindEls) {
412
- const spec = (_b = el.getAttribute("data-bind")) != null ? _b : "";
413
- const hasClassBind = spec.split(",").some((p) => {
414
- var _a2;
415
- return ((_a2 = p.trim().split(":")[0]) == null ? void 0 : _a2.trim()) === "class";
416
- });
417
- if (hasClassBind && el.hasAttribute("data-class")) {
418
- warn(`element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.`);
345
+ }
346
+ for (const b of scan.bind) {
347
+ const hasClassBind = b.pairs.some((p) => p[0] === "class");
348
+ if (hasClassBind && b.el.hasAttribute("data-class")) {
349
+ warn(
350
+ `element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.`
351
+ );
419
352
  }
420
353
  }
421
354
  }
@@ -426,17 +359,13 @@ var Micra = (() => {
426
359
  el.addEventListener(type, fn);
427
360
  ((_a = instance.__micraListeners) != null ? _a : instance.__micraListeners = []).push({ el, type, fn });
428
361
  }
429
- function bindDataOn(root, instance) {
430
- var _a, _b;
431
- const isFragment = root.nodeType === 11;
432
- const els = isFragment ? queryAll(root, "[data-on]") : queryOwn(root, "data-on");
433
- if (!isFragment && ((_a = root.hasAttribute) == null ? void 0 : _a.call(root, "data-on")) && !els.includes(root))
434
- els.unshift(root);
362
+ function bindDataOn(els, instance) {
363
+ var _a;
435
364
  for (const el of els) {
436
365
  const mEl = el;
437
366
  if (mEl.__micraEvents) continue;
438
367
  mEl.__micraEvents = true;
439
- const spec = (_b = mEl.dataset["on"]) != null ? _b : "";
368
+ const spec = (_a = mEl.dataset["on"]) != null ? _a : "";
440
369
  for (const part of spec.split(",")) {
441
370
  const [evSpec, method] = part.trim().split(":");
442
371
  if (!evSpec || !method) continue;
@@ -452,11 +381,8 @@ var Micra = (() => {
452
381
  }
453
382
  }
454
383
  }
455
- function bindAtEvents(root, instance) {
456
- const isFragment = root.nodeType === 11;
457
- const all = isFragment ? queryAll(root, "*") : queryOwnAll(root, "*");
458
- if (!isFragment && !all.includes(root)) all.unshift(root);
459
- for (const el of all) {
384
+ function bindAtEvents(els, instance) {
385
+ for (const el of els) {
460
386
  const mEl = el;
461
387
  if (mEl.__micraAtBound) continue;
462
388
  let bound = false;
@@ -477,15 +403,12 @@ var Micra = (() => {
477
403
  if (bound) mEl.__micraAtBound = true;
478
404
  }
479
405
  }
480
- function bindModels(root, instance) {
481
- var _a;
482
- const isFragment = root.nodeType === 11;
483
- const els = isFragment ? queryAll(root, "[data-model]") : queryOwn(root, "data-model");
484
- for (const el of els) {
406
+ function bindModels(bindings, instance) {
407
+ for (const { el, expr } of bindings) {
485
408
  const mEl = el;
486
409
  if (mEl.__micraModel) continue;
487
410
  mEl.__micraModel = true;
488
- const key = (_a = el.dataset["model"]) != null ? _a : "";
411
+ const key = expr.trim();
489
412
  const tag = el.tagName;
490
413
  const inputEl = el;
491
414
  const inputType = inputEl.type;
@@ -506,11 +429,132 @@ var Micra = (() => {
506
429
  }
507
430
  }
508
431
 
432
+ // src/dom/scan.ts
433
+ function emptyScan() {
434
+ return {
435
+ text: [],
436
+ html: [],
437
+ if: [],
438
+ show: [],
439
+ bind: [],
440
+ model: [],
441
+ class: [],
442
+ each: [],
443
+ on: [],
444
+ atEvents: [],
445
+ refs: []
446
+ };
447
+ }
448
+ function parsePairs(expr) {
449
+ const out = [];
450
+ for (const part of expr.split(",")) {
451
+ const colon = part.indexOf(":");
452
+ if (colon === -1) continue;
453
+ const left = part.slice(0, colon).trim();
454
+ const right = part.slice(colon + 1).trim();
455
+ if (!left) continue;
456
+ out.push([left, right]);
457
+ }
458
+ return out;
459
+ }
460
+ function classify(el, scan) {
461
+ if (el.tagName === "TEMPLATE") {
462
+ if (el.hasAttribute("data-each")) scan.each.push(el);
463
+ return;
464
+ }
465
+ const attrs = el.attributes;
466
+ let atEventSeen = false;
467
+ for (let i = 0; i < attrs.length; i++) {
468
+ const a = attrs[i];
469
+ const name = a.name;
470
+ const first = name.charCodeAt(0);
471
+ if (first === 64) {
472
+ if (!atEventSeen) {
473
+ scan.atEvents.push(el);
474
+ atEventSeen = true;
475
+ }
476
+ continue;
477
+ }
478
+ if (first === 100 && name.length >= 6 && name.charCodeAt(4) === 45) {
479
+ const rest = name.slice(5);
480
+ switch (rest) {
481
+ case "text":
482
+ scan.text.push({ el, expr: a.value });
483
+ break;
484
+ case "html":
485
+ scan.html.push({ el, expr: a.value });
486
+ break;
487
+ case "if":
488
+ scan.if.push({ el, expr: a.value });
489
+ break;
490
+ case "show":
491
+ scan.show.push({ el, expr: a.value });
492
+ break;
493
+ case "bind": {
494
+ const pairs = parsePairs(a.value);
495
+ scan.bind.push({ el, expr: a.value, pairs });
496
+ break;
497
+ }
498
+ case "model":
499
+ scan.model.push({ el, expr: a.value });
500
+ break;
501
+ case "class": {
502
+ const pairs = parsePairs(a.value);
503
+ scan.class.push({ el, expr: a.value, pairs });
504
+ break;
505
+ }
506
+ case "on":
507
+ scan.on.push(el);
508
+ break;
509
+ case "ref":
510
+ scan.refs.push(el);
511
+ break;
512
+ }
513
+ }
514
+ }
515
+ }
516
+ var NESTED_COMPONENT_FILTER = {
517
+ acceptNode(node) {
518
+ if (node.hasAttribute("data-component"))
519
+ return NodeFilter.FILTER_REJECT;
520
+ return NodeFilter.FILTER_ACCEPT;
521
+ }
522
+ };
523
+ function scanComponent(root) {
524
+ const scan = emptyScan();
525
+ classify(root, scan);
526
+ const walker = document.createTreeWalker(
527
+ root,
528
+ NodeFilter.SHOW_ELEMENT,
529
+ NESTED_COMPONENT_FILTER
530
+ );
531
+ let node = walker.nextNode();
532
+ while (node) {
533
+ classify(node, scan);
534
+ node = walker.nextNode();
535
+ }
536
+ return scan;
537
+ }
538
+ function scanFragment(frag) {
539
+ const scan = emptyScan();
540
+ const walker = document.createTreeWalker(
541
+ frag,
542
+ NodeFilter.SHOW_ELEMENT,
543
+ NESTED_COMPONENT_FILTER
544
+ );
545
+ let node = walker.nextNode();
546
+ while (node) {
547
+ classify(node, scan);
548
+ node = walker.nextNode();
549
+ }
550
+ return scan;
551
+ }
552
+
509
553
  // src/dom/each.ts
510
- function renderList(root, state, rawState, instance) {
511
- queryOwn(root, "data-each").forEach((tmplEl) => {
512
- var _a;
513
- if (tmplEl.tagName !== "TEMPLATE") return;
554
+ function renderList(templates, state, rawState, instance, triggerKey) {
555
+ var _a;
556
+ for (const tmplEl of templates) {
557
+ if (tmplEl.tagName !== "TEMPLATE") continue;
514
558
  const tmpl = tmplEl;
515
559
  const itemsExpr = tmpl.getAttribute("data-each");
516
560
  const keyAttr = (_a = tmpl.getAttribute("data-key")) != null ? _a : null;
@@ -524,22 +568,23 @@ var Micra = (() => {
524
568
  }
525
569
  const marker = tmpl.__micraMarker;
526
570
  const keyMap = tmpl.__micraNodes;
527
- const parent = marker.parentNode;
528
- if (!parent) return;
571
+ if (!marker.parentNode) continue;
529
572
  if (!Array.isArray(items)) {
530
573
  tmpl.__micraList.forEach((n) => n.remove());
531
574
  tmpl.__micraList = [];
532
575
  keyMap.clear();
533
- return;
576
+ continue;
534
577
  }
578
+ const canSkipUnchanged = triggerKey !== null && triggerKey !== "MULTIPLE" && triggerKey === itemsExpr;
535
579
  if (keyAttr) {
536
- renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance);
580
+ renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, instance, canSkipUnchanged);
537
581
  } else {
538
- renderNoKey(tmpl, items, marker, parent, state, rawState, instance);
582
+ renderNoKey(tmpl, items, marker, state, rawState, instance);
539
583
  }
540
- });
584
+ }
541
585
  }
542
- function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance) {
586
+ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, instance, canSkipUnchanged) {
587
+ var _a;
543
588
  const nextKeys = /* @__PURE__ */ new Set();
544
589
  const nextNodes = [];
545
590
  let warnedNullKey = false;
@@ -567,14 +612,24 @@ var Micra = (() => {
567
612
  }
568
613
  node.__micraKey = key;
569
614
  keyMap.set(key, node);
570
- bindDataOn(node, instance);
571
- bindAtEvents(node, instance);
615
+ const rowScan2 = scanComponent(node);
616
+ node.__micraScan = rowScan2;
617
+ bindDataOn(rowScan2.on, instance);
618
+ bindAtEvents(rowScan2.atEvents, instance);
619
+ bindModels(rowScan2.model, instance);
620
+ node._itemState = Object.create(state);
621
+ } else if (canSkipUnchanged && node.__micraItem === item && node.__micraIndex === index) {
622
+ nextNodes.push(node);
623
+ continue;
572
624
  }
573
- const itemState = Object.assign(
574
- Object.create(state),
575
- { item, index, $index: index }
576
- );
577
- applyDirectives(node, itemState, rawState, instance);
625
+ node.__micraItem = item;
626
+ node.__micraIndex = index;
627
+ const itemState = node._itemState;
628
+ itemState.item = item;
629
+ itemState.index = index;
630
+ itemState.$index = index;
631
+ const rowScan = (_a = node.__micraScan) != null ? _a : node.__micraScan = scanComponent(node);
632
+ applyDirectives(rowScan, itemState, rawState, instance);
578
633
  nextNodes.push(node);
579
634
  }
580
635
  for (const [key, node] of keyMap) {
@@ -583,14 +638,64 @@ var Micra = (() => {
583
638
  keyMap.delete(key);
584
639
  }
585
640
  }
586
- let cursor = marker;
587
- for (const node of nextNodes) {
588
- if (cursor.nextSibling !== node) parent.insertBefore(node, cursor.nextSibling);
589
- cursor = node;
641
+ const prevList = tmpl.__micraList;
642
+ if (prevList.length === 0) {
643
+ if (nextNodes.length) {
644
+ const frag = document.createDocumentFragment();
645
+ for (const node of nextNodes) frag.append(node);
646
+ marker.after(frag);
647
+ }
648
+ } else {
649
+ let orderChanged = nextNodes.length !== prevList.length;
650
+ if (!orderChanged) {
651
+ for (let i = 0; i < nextNodes.length; i++) {
652
+ if (nextNodes[i] !== prevList[i]) {
653
+ orderChanged = true;
654
+ break;
655
+ }
656
+ }
657
+ }
658
+ if (orderChanged) reorderKeyed(nextNodes, prevList, marker);
590
659
  }
591
660
  tmpl.__micraList = nextNodes;
592
661
  }
593
- function renderNoKey(tmpl, items, marker, parent, state, rawState, instance) {
662
+ function reorderKeyed(nextNodes, prevList, marker) {
663
+ const prevPos = /* @__PURE__ */ new Map();
664
+ for (let i = 0; i < prevList.length; i++) prevPos.set(prevList[i], i);
665
+ const n = nextNodes.length;
666
+ const tails = [];
667
+ const tailIdx = [];
668
+ const prev = new Array(n).fill(-1);
669
+ for (let i = 0; i < n; i++) {
670
+ const p = prevPos.get(nextNodes[i]);
671
+ if (p === void 0) continue;
672
+ let lo = 0, hi = tails.length;
673
+ while (lo < hi) {
674
+ const m = lo + hi >> 1;
675
+ tails[m] < p ? lo = m + 1 : hi = m;
676
+ }
677
+ if (lo > 0) prev[i] = tailIdx[lo - 1];
678
+ tails[lo] = p;
679
+ tailIdx[lo] = i;
680
+ }
681
+ const stable = /* @__PURE__ */ new Set();
682
+ let idx = tailIdx[tails.length - 1];
683
+ while (idx >= 0) {
684
+ stable.add(idx);
685
+ idx = prev[idx];
686
+ }
687
+ let anchor = marker;
688
+ for (let i = 0; i < n; i++) {
689
+ const node = nextNodes[i];
690
+ if (stable.has(i)) {
691
+ anchor = node;
692
+ continue;
693
+ }
694
+ anchor.after(node);
695
+ anchor = node;
696
+ }
697
+ }
698
+ function renderNoKey(tmpl, items, marker, state, rawState, instance) {
594
699
  tmpl.__micraList.forEach((n) => n.remove());
595
700
  tmpl.__micraList = [];
596
701
  const frag = document.createDocumentFragment();
@@ -600,9 +705,11 @@ var Micra = (() => {
600
705
  Object.create(state),
601
706
  { item, index, $index: index }
602
707
  );
603
- applyDirectives(clone, itemState, rawState, instance);
604
- bindDataOn(clone, instance);
605
- bindAtEvents(clone, instance);
708
+ const fragScan = scanFragment(clone);
709
+ applyDirectives(fragScan, itemState, rawState, instance);
710
+ bindDataOn(fragScan.on, instance);
711
+ bindAtEvents(fragScan.atEvents, instance);
712
+ bindModels(fragScan.model, instance);
606
713
  const nodes = Array.from(clone.childNodes);
607
714
  nodes.forEach((n) => {
608
715
  n.__micraEach = true;
@@ -610,13 +717,14 @@ var Micra = (() => {
610
717
  });
611
718
  tmpl.__micraList.push(...nodes);
612
719
  }
613
- parent.insertBefore(frag, marker.nextSibling);
720
+ marker.after(frag);
614
721
  }
615
722
 
616
723
  // src/dom/refs.ts
617
- function collectRefs(root, instance) {
724
+ function collectRefs(els, instance) {
725
+ if (!els.length) return;
618
726
  instance.refs = {};
619
- for (const el of queryOwn(root, "data-ref")) {
727
+ for (const el of els) {
620
728
  const name = el.dataset["ref"];
621
729
  if (name) instance.refs[name] = el;
622
730
  }
@@ -630,10 +738,13 @@ var Micra = (() => {
630
738
  warn(`"${selector}" not found`);
631
739
  return null;
632
740
  }
633
- if (_instances.has(root)) return _instances.get(root);
741
+ if (_instances.has(root))
742
+ return _instances.get(root);
634
743
  const rawState = { ...(_a = definition.state) != null ? _a : {} };
635
744
  const instance = { $el: root, refs: {} };
636
- for (const [key, val] of Object.entries(definition)) {
745
+ for (const [key, val] of Object.entries(
746
+ definition
747
+ )) {
637
748
  if (key === "state" || key === "onCreate" || key === "onDestroy") continue;
638
749
  if (typeof val === "function") instance[key] = val;
639
750
  }
@@ -654,8 +765,12 @@ var Micra = (() => {
654
765
  return unsub;
655
766
  };
656
767
  let isRendering = false;
768
+ let _triggerKey = null;
657
769
  const schedule = createScheduler(() => instance.render());
658
- instance.state = createReactiveState(rawState, schedule);
770
+ instance.state = createReactiveState(rawState, schedule, (key) => {
771
+ if (_triggerKey === null) _triggerKey = key;
772
+ else if (_triggerKey !== key) _triggerKey = "MULTIPLE";
773
+ });
659
774
  const boundMethods = /* @__PURE__ */ new Map();
660
775
  const exprState = new Proxy(rawState, {
661
776
  get(target, key) {
@@ -677,22 +792,29 @@ var Micra = (() => {
677
792
  });
678
793
  let warnedReentry = false;
679
794
  instance.render = function() {
795
+ var _a2;
680
796
  if (instance.__micraDestroyed) return;
797
+ const triggerKey = _triggerKey;
798
+ _triggerKey = null;
681
799
  if (isRendering) {
682
800
  if (!warnedReentry) {
683
- warn("render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method.");
801
+ warn(
802
+ "render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method."
803
+ );
684
804
  warnedReentry = true;
685
805
  }
686
806
  return;
687
807
  }
688
808
  isRendering = true;
689
809
  try {
690
- applyDirectives(root, exprState, rawState, instance);
691
- renderList(root, exprState, rawState, instance);
692
- bindDataOn(root, instance);
693
- bindAtEvents(root, instance);
694
- bindModels(root, instance);
695
- collectRefs(root, instance);
810
+ const mRoot2 = root;
811
+ const scan = (_a2 = mRoot2.__micraScan) != null ? _a2 : mRoot2.__micraScan = scanComponent(root);
812
+ applyDirectives(scan, exprState, rawState, instance);
813
+ renderList(scan.each, exprState, rawState, instance, triggerKey);
814
+ bindDataOn(scan.on, instance);
815
+ bindAtEvents(scan.atEvents, instance);
816
+ bindModels(scan.model, instance);
817
+ collectRefs(scan.refs, instance);
696
818
  } finally {
697
819
  isRendering = false;
698
820
  }
@@ -701,14 +823,16 @@ var Micra = (() => {
701
823
  var _a2, _b;
702
824
  if (instance.__micraDestroyed) return;
703
825
  instance.__micraDestroyed = true;
704
- (_a2 = instance.__micraListeners) == null ? void 0 : _a2.forEach(({ el, type, fn }) => el.removeEventListener(type, fn));
826
+ (_a2 = instance.__micraListeners) == null ? void 0 : _a2.forEach(
827
+ ({ el, type, fn }) => el.removeEventListener(type, fn)
828
+ );
705
829
  instance.__micraListeners = [];
706
830
  const clearFlags = (el) => {
707
831
  const m = el;
708
832
  delete m.__micraEvents;
709
833
  delete m.__micraAtBound;
710
834
  delete m.__micraModel;
711
- delete m.__micraCache;
835
+ delete m.__micraScan;
712
836
  };
713
837
  clearFlags(root);
714
838
  root.querySelectorAll("*").forEach(clearFlags);
@@ -720,7 +844,8 @@ var Micra = (() => {
720
844
  };
721
845
  _instances.set(root, instance);
722
846
  instance.render();
723
- validateDirectives(root);
847
+ const mRoot = root;
848
+ if (mRoot.__micraScan) validateDirectives(mRoot.__micraScan);
724
849
  if (typeof definition.onCreate === "function")
725
850
  Promise.resolve().then(
726
851
  () => definition.onCreate.call(instance)