micra.js 2.1.0 → 2.2.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/dist/micra.cjs.js CHANGED
@@ -1,4 +1,4 @@
1
- /* Micra.js v2.1.0 — https://github.com/micra-js/micra — MIT */
1
+ /* Micra.js v2.2.0 — https://github.com/micra-js/micra — MIT */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -256,27 +256,6 @@ function createScheduler(render) {
256
256
  };
257
257
  }
258
258
 
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
259
  // src/dom/directives.ts
281
260
  function applyText(el, expr, state) {
282
261
  var _a;
@@ -330,92 +309,37 @@ function applyClass(el, pairs, state) {
330
309
  el.classList.toggle(cls, Boolean(evalExpr(valExpr, state)));
331
310
  }
332
311
  }
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
312
  function applyModel(el, key, rawState) {
346
313
  const html = el;
347
314
  const stateVal = rawState[key];
348
315
  const desired = stateVal == null ? "" : String(stateVal);
349
316
  if (html.value !== desired) html.value = desired;
350
317
  }
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) => {
318
+ function applyDirectives(scan, state, rawState, _instance) {
319
+ for (const b of scan.if) applyIf(b, state);
320
+ for (const b of scan.text) applyText(b.el, b.expr, state);
321
+ for (const b of scan.html) applyHtml(b.el, b.expr, state);
322
+ for (const b of scan.show) applyShow(b.el, b.expr, state);
323
+ for (const b of scan.bind) applyBind(b.el, b.pairs, state);
324
+ for (const b of scan.model) applyModel(b.el, b.expr.trim(), rawState);
325
+ for (const b of scan.class) applyClass(b.el, b.pairs, state);
326
+ }
327
+ function validateDirectives(scan) {
328
+ for (const el of scan.each) {
403
329
  const tmpl = el;
404
330
  if (!el.hasAttribute("data-key") && !tmpl.__micraNoKeyWarned) {
405
331
  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.`);
332
+ warn(
333
+ `data-each="${el.getAttribute("data-each")}" has no data-key \u2014 keyed diff disabled. Add data-key="id" for better performance.`
334
+ );
407
335
  }
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.`);
336
+ }
337
+ for (const b of scan.bind) {
338
+ const hasClassBind = b.pairs.some((p) => p[0] === "class");
339
+ if (hasClassBind && b.el.hasAttribute("data-class")) {
340
+ warn(
341
+ `element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.`
342
+ );
419
343
  }
420
344
  }
421
345
  }
@@ -426,17 +350,13 @@ function track(instance, el, type, fn) {
426
350
  el.addEventListener(type, fn);
427
351
  ((_a = instance.__micraListeners) != null ? _a : instance.__micraListeners = []).push({ el, type, fn });
428
352
  }
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);
353
+ function bindDataOn(els, instance) {
354
+ var _a;
435
355
  for (const el of els) {
436
356
  const mEl = el;
437
357
  if (mEl.__micraEvents) continue;
438
358
  mEl.__micraEvents = true;
439
- const spec = (_b = mEl.dataset["on"]) != null ? _b : "";
359
+ const spec = (_a = mEl.dataset["on"]) != null ? _a : "";
440
360
  for (const part of spec.split(",")) {
441
361
  const [evSpec, method] = part.trim().split(":");
442
362
  if (!evSpec || !method) continue;
@@ -452,11 +372,8 @@ function bindDataOn(root, instance) {
452
372
  }
453
373
  }
454
374
  }
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) {
375
+ function bindAtEvents(els, instance) {
376
+ for (const el of els) {
460
377
  const mEl = el;
461
378
  if (mEl.__micraAtBound) continue;
462
379
  let bound = false;
@@ -477,15 +394,12 @@ function bindAtEvents(root, instance) {
477
394
  if (bound) mEl.__micraAtBound = true;
478
395
  }
479
396
  }
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) {
397
+ function bindModels(bindings, instance) {
398
+ for (const { el, expr } of bindings) {
485
399
  const mEl = el;
486
400
  if (mEl.__micraModel) continue;
487
401
  mEl.__micraModel = true;
488
- const key = (_a = el.dataset["model"]) != null ? _a : "";
402
+ const key = expr.trim();
489
403
  const tag = el.tagName;
490
404
  const inputEl = el;
491
405
  const inputType = inputEl.type;
@@ -506,11 +420,132 @@ function bindModels(root, instance) {
506
420
  }
507
421
  }
508
422
 
423
+ // src/dom/scan.ts
424
+ function emptyScan() {
425
+ return {
426
+ text: [],
427
+ html: [],
428
+ if: [],
429
+ show: [],
430
+ bind: [],
431
+ model: [],
432
+ class: [],
433
+ each: [],
434
+ on: [],
435
+ atEvents: [],
436
+ refs: []
437
+ };
438
+ }
439
+ function parsePairs(expr) {
440
+ const out = [];
441
+ for (const part of expr.split(",")) {
442
+ const colon = part.indexOf(":");
443
+ if (colon === -1) continue;
444
+ const left = part.slice(0, colon).trim();
445
+ const right = part.slice(colon + 1).trim();
446
+ if (!left) continue;
447
+ out.push([left, right]);
448
+ }
449
+ return out;
450
+ }
451
+ function classify(el, scan) {
452
+ if (el.tagName === "TEMPLATE") {
453
+ if (el.hasAttribute("data-each")) scan.each.push(el);
454
+ return;
455
+ }
456
+ const attrs = el.attributes;
457
+ let atEventSeen = false;
458
+ for (let i = 0; i < attrs.length; i++) {
459
+ const a = attrs[i];
460
+ const name = a.name;
461
+ const first = name.charCodeAt(0);
462
+ if (first === 64) {
463
+ if (!atEventSeen) {
464
+ scan.atEvents.push(el);
465
+ atEventSeen = true;
466
+ }
467
+ continue;
468
+ }
469
+ if (first === 100 && name.length >= 6 && name.charCodeAt(4) === 45) {
470
+ const rest = name.slice(5);
471
+ switch (rest) {
472
+ case "text":
473
+ scan.text.push({ el, expr: a.value });
474
+ break;
475
+ case "html":
476
+ scan.html.push({ el, expr: a.value });
477
+ break;
478
+ case "if":
479
+ scan.if.push({ el, expr: a.value });
480
+ break;
481
+ case "show":
482
+ scan.show.push({ el, expr: a.value });
483
+ break;
484
+ case "bind": {
485
+ const pairs = parsePairs(a.value);
486
+ scan.bind.push({ el, expr: a.value, pairs });
487
+ break;
488
+ }
489
+ case "model":
490
+ scan.model.push({ el, expr: a.value });
491
+ break;
492
+ case "class": {
493
+ const pairs = parsePairs(a.value);
494
+ scan.class.push({ el, expr: a.value, pairs });
495
+ break;
496
+ }
497
+ case "on":
498
+ scan.on.push(el);
499
+ break;
500
+ case "ref":
501
+ scan.refs.push(el);
502
+ break;
503
+ }
504
+ }
505
+ }
506
+ }
507
+ var NESTED_COMPONENT_FILTER = {
508
+ acceptNode(node) {
509
+ if (node.hasAttribute("data-component"))
510
+ return NodeFilter.FILTER_REJECT;
511
+ return NodeFilter.FILTER_ACCEPT;
512
+ }
513
+ };
514
+ function scanComponent(root) {
515
+ const scan = emptyScan();
516
+ classify(root, scan);
517
+ const walker = document.createTreeWalker(
518
+ root,
519
+ NodeFilter.SHOW_ELEMENT,
520
+ NESTED_COMPONENT_FILTER
521
+ );
522
+ let node = walker.nextNode();
523
+ while (node) {
524
+ classify(node, scan);
525
+ node = walker.nextNode();
526
+ }
527
+ return scan;
528
+ }
529
+ function scanFragment(frag) {
530
+ const scan = emptyScan();
531
+ const walker = document.createTreeWalker(
532
+ frag,
533
+ NodeFilter.SHOW_ELEMENT,
534
+ NESTED_COMPONENT_FILTER
535
+ );
536
+ let node = walker.nextNode();
537
+ while (node) {
538
+ classify(node, scan);
539
+ node = walker.nextNode();
540
+ }
541
+ return scan;
542
+ }
543
+
509
544
  // 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;
545
+ function renderList(templates, state, rawState, instance) {
546
+ var _a;
547
+ for (const tmplEl of templates) {
548
+ if (tmplEl.tagName !== "TEMPLATE") continue;
514
549
  const tmpl = tmplEl;
515
550
  const itemsExpr = tmpl.getAttribute("data-each");
516
551
  const keyAttr = (_a = tmpl.getAttribute("data-key")) != null ? _a : null;
@@ -525,21 +560,22 @@ function renderList(root, state, rawState, instance) {
525
560
  const marker = tmpl.__micraMarker;
526
561
  const keyMap = tmpl.__micraNodes;
527
562
  const parent = marker.parentNode;
528
- if (!parent) return;
563
+ if (!parent) continue;
529
564
  if (!Array.isArray(items)) {
530
565
  tmpl.__micraList.forEach((n) => n.remove());
531
566
  tmpl.__micraList = [];
532
567
  keyMap.clear();
533
- return;
568
+ continue;
534
569
  }
535
570
  if (keyAttr) {
536
571
  renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance);
537
572
  } else {
538
573
  renderNoKey(tmpl, items, marker, parent, state, rawState, instance);
539
574
  }
540
- });
575
+ }
541
576
  }
542
577
  function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance) {
578
+ var _a;
543
579
  const nextKeys = /* @__PURE__ */ new Set();
544
580
  const nextNodes = [];
545
581
  let warnedNullKey = false;
@@ -567,14 +603,18 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawSta
567
603
  }
568
604
  node.__micraKey = key;
569
605
  keyMap.set(key, node);
570
- bindDataOn(node, instance);
571
- bindAtEvents(node, instance);
606
+ const rowScan2 = scanComponent(node);
607
+ node.__micraScan = rowScan2;
608
+ bindDataOn(rowScan2.on, instance);
609
+ bindAtEvents(rowScan2.atEvents, instance);
610
+ bindModels(rowScan2.model, instance);
572
611
  }
573
612
  const itemState = Object.assign(
574
613
  Object.create(state),
575
614
  { item, index, $index: index }
576
615
  );
577
- applyDirectives(node, itemState, rawState, instance);
616
+ const rowScan = (_a = node.__micraScan) != null ? _a : node.__micraScan = scanComponent(node);
617
+ applyDirectives(rowScan, itemState, rawState, instance);
578
618
  nextNodes.push(node);
579
619
  }
580
620
  for (const [key, node] of keyMap) {
@@ -600,9 +640,11 @@ function renderNoKey(tmpl, items, marker, parent, state, rawState, instance) {
600
640
  Object.create(state),
601
641
  { item, index, $index: index }
602
642
  );
603
- applyDirectives(clone, itemState, rawState, instance);
604
- bindDataOn(clone, instance);
605
- bindAtEvents(clone, instance);
643
+ const fragScan = scanFragment(clone);
644
+ applyDirectives(fragScan, itemState, rawState, instance);
645
+ bindDataOn(fragScan.on, instance);
646
+ bindAtEvents(fragScan.atEvents, instance);
647
+ bindModels(fragScan.model, instance);
606
648
  const nodes = Array.from(clone.childNodes);
607
649
  nodes.forEach((n) => {
608
650
  n.__micraEach = true;
@@ -614,9 +656,9 @@ function renderNoKey(tmpl, items, marker, parent, state, rawState, instance) {
614
656
  }
615
657
 
616
658
  // src/dom/refs.ts
617
- function collectRefs(root, instance) {
659
+ function collectRefs(els, instance) {
618
660
  instance.refs = {};
619
- for (const el of queryOwn(root, "data-ref")) {
661
+ for (const el of els) {
620
662
  const name = el.dataset["ref"];
621
663
  if (name) instance.refs[name] = el;
622
664
  }
@@ -630,10 +672,13 @@ function mount(selector, definition) {
630
672
  warn(`"${selector}" not found`);
631
673
  return null;
632
674
  }
633
- if (_instances.has(root)) return _instances.get(root);
675
+ if (_instances.has(root))
676
+ return _instances.get(root);
634
677
  const rawState = { ...(_a = definition.state) != null ? _a : {} };
635
678
  const instance = { $el: root, refs: {} };
636
- for (const [key, val] of Object.entries(definition)) {
679
+ for (const [key, val] of Object.entries(
680
+ definition
681
+ )) {
637
682
  if (key === "state" || key === "onCreate" || key === "onDestroy") continue;
638
683
  if (typeof val === "function") instance[key] = val;
639
684
  }
@@ -677,22 +722,27 @@ function mount(selector, definition) {
677
722
  });
678
723
  let warnedReentry = false;
679
724
  instance.render = function() {
725
+ var _a2;
680
726
  if (instance.__micraDestroyed) return;
681
727
  if (isRendering) {
682
728
  if (!warnedReentry) {
683
- warn("render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method.");
729
+ warn(
730
+ "render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method."
731
+ );
684
732
  warnedReentry = true;
685
733
  }
686
734
  return;
687
735
  }
688
736
  isRendering = true;
689
737
  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);
738
+ const mRoot2 = root;
739
+ const scan = (_a2 = mRoot2.__micraScan) != null ? _a2 : mRoot2.__micraScan = scanComponent(root);
740
+ applyDirectives(scan, exprState, rawState, instance);
741
+ renderList(scan.each, exprState, rawState, instance);
742
+ bindDataOn(scan.on, instance);
743
+ bindAtEvents(scan.atEvents, instance);
744
+ bindModels(scan.model, instance);
745
+ collectRefs(scan.refs, instance);
696
746
  } finally {
697
747
  isRendering = false;
698
748
  }
@@ -701,14 +751,16 @@ function mount(selector, definition) {
701
751
  var _a2, _b;
702
752
  if (instance.__micraDestroyed) return;
703
753
  instance.__micraDestroyed = true;
704
- (_a2 = instance.__micraListeners) == null ? void 0 : _a2.forEach(({ el, type, fn }) => el.removeEventListener(type, fn));
754
+ (_a2 = instance.__micraListeners) == null ? void 0 : _a2.forEach(
755
+ ({ el, type, fn }) => el.removeEventListener(type, fn)
756
+ );
705
757
  instance.__micraListeners = [];
706
758
  const clearFlags = (el) => {
707
759
  const m = el;
708
760
  delete m.__micraEvents;
709
761
  delete m.__micraAtBound;
710
762
  delete m.__micraModel;
711
- delete m.__micraCache;
763
+ delete m.__micraScan;
712
764
  };
713
765
  clearFlags(root);
714
766
  root.querySelectorAll("*").forEach(clearFlags);
@@ -720,7 +772,8 @@ function mount(selector, definition) {
720
772
  };
721
773
  _instances.set(root, instance);
722
774
  instance.render();
723
- validateDirectives(root);
775
+ const mRoot = root;
776
+ if (mRoot.__micraScan) validateDirectives(mRoot.__micraScan);
724
777
  if (typeof definition.onCreate === "function")
725
778
  Promise.resolve().then(
726
779
  () => definition.onCreate.call(instance)