micra.js 2.0.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.js CHANGED
@@ -1,4 +1,4 @@
1
- /* Micra.js v2.0.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 Micra = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -62,7 +62,8 @@ var Micra = (() => {
62
62
  if (method === "GET" || method === "HEAD") {
63
63
  const params = {};
64
64
  for (const [k, v] of Object.entries(options)) {
65
- if (k !== "method" && k !== "headers" && v != null) params[k] = String(v);
65
+ if (k !== "method" && k !== "headers" && k !== "signal" && v != null)
66
+ params[k] = String(v);
66
67
  }
67
68
  if (Object.keys(params).length)
68
69
  finalUrl += (url.includes("?") ? "&" : "?") + new URLSearchParams(params);
@@ -73,6 +74,7 @@ var Micra = (() => {
73
74
  const res = await fetch(finalUrl, {
74
75
  method,
75
76
  headers,
77
+ ...options.signal !== void 0 ? { signal: options.signal } : {},
76
78
  ...body !== void 0 ? { body } : {}
77
79
  });
78
80
  if (!res.ok)
@@ -254,27 +256,6 @@ var Micra = (() => {
254
256
  };
255
257
  }
256
258
 
257
- // src/dom/query.ts
258
- function queryAll(root, sel) {
259
- return Array.from(root.querySelectorAll(sel));
260
- }
261
- function queryOwn(root, attr) {
262
- return filterOwn(root, queryAll(root, `[${attr}]`));
263
- }
264
- function queryOwnAll(root, sel) {
265
- return filterOwn(root, queryAll(root, sel));
266
- }
267
- function filterOwn(root, els) {
268
- return els.filter((el) => {
269
- let node = el.parentElement;
270
- while (node && node !== root) {
271
- if (node.hasAttribute("data-component")) return false;
272
- node = node.parentElement;
273
- }
274
- return true;
275
- });
276
- }
277
-
278
259
  // src/dom/directives.ts
279
260
  function applyText(el, expr, state) {
280
261
  var _a;
@@ -328,92 +309,37 @@ var Micra = (() => {
328
309
  el.classList.toggle(cls, Boolean(evalExpr(valExpr, state)));
329
310
  }
330
311
  }
331
- function parsePairs(expr) {
332
- const out = [];
333
- for (const part of expr.split(",")) {
334
- const colonIdx = part.indexOf(":");
335
- if (colonIdx === -1) continue;
336
- const left = part.slice(0, colonIdx).trim();
337
- const right = part.slice(colonIdx + 1).trim();
338
- if (!left) continue;
339
- out.push([left, right]);
340
- }
341
- return out;
342
- }
343
312
  function applyModel(el, key, rawState) {
344
313
  const html = el;
345
314
  const stateVal = rawState[key];
346
315
  const desired = stateVal == null ? "" : String(stateVal);
347
316
  if (html.value !== desired) html.value = desired;
348
317
  }
349
- function buildCache(root) {
350
- const pick = (attr) => {
351
- var _a;
352
- const els = queryOwn(root, attr);
353
- if ((_a = root.hasAttribute) == null ? void 0 : _a.call(root, attr)) els.unshift(root);
354
- return els.filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
355
- };
356
- const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
357
- return {
358
- text: pick("data-text"),
359
- html: pick("data-html"),
360
- if: pick("data-if"),
361
- show: pick("data-show"),
362
- bind: pickPairs("data-bind"),
363
- model: pick("data-model"),
364
- class: pickPairs("data-class")
365
- };
366
- }
367
- function applyDirectives(root, state, rawState, _instance) {
368
- if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
369
- applyFromList(buildFragmentList(root), state, rawState);
370
- return;
371
- }
372
- const el = root;
373
- if (!el.__micraCache) el.__micraCache = buildCache(el);
374
- applyFromList(el.__micraCache, state, rawState);
375
- }
376
- function applyFromList(cache, state, rawState) {
377
- cache.if.forEach((b) => applyIf(b, state));
378
- cache.text.forEach((b) => applyText(b.el, b.expr, state));
379
- cache.html.forEach((b) => applyHtml(b.el, b.expr, state));
380
- cache.show.forEach((b) => applyShow(b.el, b.expr, state));
381
- cache.bind.forEach((b) => applyBind(b.el, b.pairs, state));
382
- cache.model.forEach((b) => applyModel(b.el, b.expr.trim(), rawState));
383
- cache.class.forEach((b) => applyClass(b.el, b.pairs, state));
384
- }
385
- function buildFragmentList(frag) {
386
- const pick = (attr) => queryAll(frag, `[${attr}]`).filter((el) => !el.closest("template")).map((el) => ({ el, expr: el.getAttribute(attr) }));
387
- const pickPairs = (attr) => pick(attr).map((b) => ({ ...b, pairs: parsePairs(b.expr) }));
388
- return {
389
- text: pick("data-text"),
390
- html: pick("data-html"),
391
- if: pick("data-if"),
392
- show: pick("data-show"),
393
- bind: pickPairs("data-bind"),
394
- model: pick("data-model"),
395
- class: pickPairs("data-class")
396
- };
397
- }
398
- function validateDirectives(root) {
399
- var _a, _b;
400
- 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) {
401
329
  const tmpl = el;
402
330
  if (!el.hasAttribute("data-key") && !tmpl.__micraNoKeyWarned) {
403
331
  tmpl.__micraNoKeyWarned = true;
404
- 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
+ );
405
335
  }
406
- });
407
- const bindEls = queryOwn(root, "data-bind");
408
- if (((_a = root.hasAttribute) == null ? void 0 : _a.call(root, "data-bind")) && !bindEls.includes(root)) bindEls.unshift(root);
409
- for (const el of bindEls) {
410
- const spec = (_b = el.getAttribute("data-bind")) != null ? _b : "";
411
- const hasClassBind = spec.split(",").some((p) => {
412
- var _a2;
413
- return ((_a2 = p.trim().split(":")[0]) == null ? void 0 : _a2.trim()) === "class";
414
- });
415
- if (hasClassBind && el.hasAttribute("data-class")) {
416
- 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
+ );
417
343
  }
418
344
  }
419
345
  }
@@ -424,17 +350,13 @@ var Micra = (() => {
424
350
  el.addEventListener(type, fn);
425
351
  ((_a = instance.__micraListeners) != null ? _a : instance.__micraListeners = []).push({ el, type, fn });
426
352
  }
427
- function bindDataOn(root, instance) {
428
- var _a, _b;
429
- const isFragment = root.nodeType === 11;
430
- const els = isFragment ? queryAll(root, "[data-on]") : queryOwn(root, "data-on");
431
- if (!isFragment && ((_a = root.hasAttribute) == null ? void 0 : _a.call(root, "data-on")) && !els.includes(root))
432
- els.unshift(root);
353
+ function bindDataOn(els, instance) {
354
+ var _a;
433
355
  for (const el of els) {
434
356
  const mEl = el;
435
357
  if (mEl.__micraEvents) continue;
436
358
  mEl.__micraEvents = true;
437
- const spec = (_b = mEl.dataset["on"]) != null ? _b : "";
359
+ const spec = (_a = mEl.dataset["on"]) != null ? _a : "";
438
360
  for (const part of spec.split(",")) {
439
361
  const [evSpec, method] = part.trim().split(":");
440
362
  if (!evSpec || !method) continue;
@@ -450,11 +372,8 @@ var Micra = (() => {
450
372
  }
451
373
  }
452
374
  }
453
- function bindAtEvents(root, instance) {
454
- const isFragment = root.nodeType === 11;
455
- const all = isFragment ? queryAll(root, "*") : queryOwnAll(root, "*");
456
- if (!isFragment && !all.includes(root)) all.unshift(root);
457
- for (const el of all) {
375
+ function bindAtEvents(els, instance) {
376
+ for (const el of els) {
458
377
  const mEl = el;
459
378
  if (mEl.__micraAtBound) continue;
460
379
  let bound = false;
@@ -475,15 +394,12 @@ var Micra = (() => {
475
394
  if (bound) mEl.__micraAtBound = true;
476
395
  }
477
396
  }
478
- function bindModels(root, instance) {
479
- var _a;
480
- const isFragment = root.nodeType === 11;
481
- const els = isFragment ? queryAll(root, "[data-model]") : queryOwn(root, "data-model");
482
- for (const el of els) {
397
+ function bindModels(bindings, instance) {
398
+ for (const { el, expr } of bindings) {
483
399
  const mEl = el;
484
400
  if (mEl.__micraModel) continue;
485
401
  mEl.__micraModel = true;
486
- const key = (_a = el.dataset["model"]) != null ? _a : "";
402
+ const key = expr.trim();
487
403
  const tag = el.tagName;
488
404
  const inputEl = el;
489
405
  const inputType = inputEl.type;
@@ -504,11 +420,132 @@ var Micra = (() => {
504
420
  }
505
421
  }
506
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
+
507
544
  // src/dom/each.ts
508
- function renderList(root, state, rawState, instance) {
509
- queryOwn(root, "data-each").forEach((tmplEl) => {
510
- var _a;
511
- 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;
512
549
  const tmpl = tmplEl;
513
550
  const itemsExpr = tmpl.getAttribute("data-each");
514
551
  const keyAttr = (_a = tmpl.getAttribute("data-key")) != null ? _a : null;
@@ -523,21 +560,22 @@ var Micra = (() => {
523
560
  const marker = tmpl.__micraMarker;
524
561
  const keyMap = tmpl.__micraNodes;
525
562
  const parent = marker.parentNode;
526
- if (!parent) return;
563
+ if (!parent) continue;
527
564
  if (!Array.isArray(items)) {
528
565
  tmpl.__micraList.forEach((n) => n.remove());
529
566
  tmpl.__micraList = [];
530
567
  keyMap.clear();
531
- return;
568
+ continue;
532
569
  }
533
570
  if (keyAttr) {
534
571
  renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance);
535
572
  } else {
536
573
  renderNoKey(tmpl, items, marker, parent, state, rawState, instance);
537
574
  }
538
- });
575
+ }
539
576
  }
540
577
  function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance) {
578
+ var _a;
541
579
  const nextKeys = /* @__PURE__ */ new Set();
542
580
  const nextNodes = [];
543
581
  let warnedNullKey = false;
@@ -565,14 +603,18 @@ var Micra = (() => {
565
603
  }
566
604
  node.__micraKey = key;
567
605
  keyMap.set(key, node);
568
- bindDataOn(node, instance);
569
- 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);
570
611
  }
571
612
  const itemState = Object.assign(
572
613
  Object.create(state),
573
614
  { item, index, $index: index }
574
615
  );
575
- applyDirectives(node, itemState, rawState, instance);
616
+ const rowScan = (_a = node.__micraScan) != null ? _a : node.__micraScan = scanComponent(node);
617
+ applyDirectives(rowScan, itemState, rawState, instance);
576
618
  nextNodes.push(node);
577
619
  }
578
620
  for (const [key, node] of keyMap) {
@@ -598,9 +640,11 @@ var Micra = (() => {
598
640
  Object.create(state),
599
641
  { item, index, $index: index }
600
642
  );
601
- applyDirectives(clone, itemState, rawState, instance);
602
- bindDataOn(clone, instance);
603
- 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);
604
648
  const nodes = Array.from(clone.childNodes);
605
649
  nodes.forEach((n) => {
606
650
  n.__micraEach = true;
@@ -612,9 +656,9 @@ var Micra = (() => {
612
656
  }
613
657
 
614
658
  // src/dom/refs.ts
615
- function collectRefs(root, instance) {
659
+ function collectRefs(els, instance) {
616
660
  instance.refs = {};
617
- for (const el of queryOwn(root, "data-ref")) {
661
+ for (const el of els) {
618
662
  const name = el.dataset["ref"];
619
663
  if (name) instance.refs[name] = el;
620
664
  }
@@ -628,10 +672,13 @@ var Micra = (() => {
628
672
  warn(`"${selector}" not found`);
629
673
  return null;
630
674
  }
631
- if (_instances.has(root)) return _instances.get(root);
675
+ if (_instances.has(root))
676
+ return _instances.get(root);
632
677
  const rawState = { ...(_a = definition.state) != null ? _a : {} };
633
678
  const instance = { $el: root, refs: {} };
634
- for (const [key, val] of Object.entries(definition)) {
679
+ for (const [key, val] of Object.entries(
680
+ definition
681
+ )) {
635
682
  if (key === "state" || key === "onCreate" || key === "onDestroy") continue;
636
683
  if (typeof val === "function") instance[key] = val;
637
684
  }
@@ -675,22 +722,27 @@ var Micra = (() => {
675
722
  });
676
723
  let warnedReentry = false;
677
724
  instance.render = function() {
725
+ var _a2;
678
726
  if (instance.__micraDestroyed) return;
679
727
  if (isRendering) {
680
728
  if (!warnedReentry) {
681
- 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
+ );
682
732
  warnedReentry = true;
683
733
  }
684
734
  return;
685
735
  }
686
736
  isRendering = true;
687
737
  try {
688
- applyDirectives(root, exprState, rawState, instance);
689
- renderList(root, exprState, rawState, instance);
690
- bindDataOn(root, instance);
691
- bindAtEvents(root, instance);
692
- bindModels(root, instance);
693
- 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);
694
746
  } finally {
695
747
  isRendering = false;
696
748
  }
@@ -699,14 +751,16 @@ var Micra = (() => {
699
751
  var _a2, _b;
700
752
  if (instance.__micraDestroyed) return;
701
753
  instance.__micraDestroyed = true;
702
- (_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
+ );
703
757
  instance.__micraListeners = [];
704
758
  const clearFlags = (el) => {
705
759
  const m = el;
706
760
  delete m.__micraEvents;
707
761
  delete m.__micraAtBound;
708
762
  delete m.__micraModel;
709
- delete m.__micraCache;
763
+ delete m.__micraScan;
710
764
  };
711
765
  clearFlags(root);
712
766
  root.querySelectorAll("*").forEach(clearFlags);
@@ -718,7 +772,8 @@ var Micra = (() => {
718
772
  };
719
773
  _instances.set(root, instance);
720
774
  instance.render();
721
- validateDirectives(root);
775
+ const mRoot = root;
776
+ if (mRoot.__micraScan) validateDirectives(mRoot.__micraScan);
722
777
  if (typeof definition.onCreate === "function")
723
778
  Promise.resolve().then(
724
779
  () => definition.onCreate.call(instance)