micra.js 2.2.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.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- /* Micra.js v2.2.0 — https://github.com/micra-js/micra — MIT */
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
- if (SIMPLE_PATH.test(expr)) {
145
- const parts = expr.split(".");
146
- if (!safeStateHas(state, parts[0])) return void 0;
147
- return parts.reduce(
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 exprCache.get(expr)(safeStateWrap(state), SAFE_OUTER);
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
  }
@@ -229,7 +235,8 @@ function applyText(el, expr, state) {
229
235
  }
230
236
  function applyHtml(el, expr, state) {
231
237
  var _a;
232
- el.innerHTML = String((_a = evalExpr(expr, state)) != null ? _a : "");
238
+ const html = String((_a = evalExpr(expr, state)) != null ? _a : "");
239
+ if (el.innerHTML !== html) el.innerHTML = html;
233
240
  }
234
241
  function applyIf(binding, state) {
235
242
  const el = binding.el;
@@ -246,7 +253,9 @@ function applyIf(binding, state) {
246
253
  }
247
254
  }
248
255
  function applyShow(el, expr, state) {
249
- el.style.display = evalExpr(expr, state) ? "" : "none";
256
+ const desired = evalExpr(expr, state) ? "" : "none";
257
+ const htmlEl = el;
258
+ if (htmlEl.style.display !== desired) htmlEl.style.display = desired;
250
259
  }
251
260
  function applyBind(el, pairs, state) {
252
261
  for (const [attr, valExpr] of pairs) {
@@ -507,7 +516,7 @@ function scanFragment(frag) {
507
516
  }
508
517
 
509
518
  // src/dom/each.ts
510
- function renderList(templates, state, rawState, instance) {
519
+ function renderList(templates, state, rawState, instance, triggerKey) {
511
520
  var _a;
512
521
  for (const tmplEl of templates) {
513
522
  if (tmplEl.tagName !== "TEMPLATE") continue;
@@ -524,22 +533,22 @@ function renderList(templates, state, rawState, instance) {
524
533
  }
525
534
  const marker = tmpl.__micraMarker;
526
535
  const keyMap = tmpl.__micraNodes;
527
- const parent = marker.parentNode;
528
- if (!parent) continue;
536
+ if (!marker.parentNode) continue;
529
537
  if (!Array.isArray(items)) {
530
538
  tmpl.__micraList.forEach((n) => n.remove());
531
539
  tmpl.__micraList = [];
532
540
  keyMap.clear();
533
541
  continue;
534
542
  }
543
+ const canSkipUnchanged = triggerKey !== null && triggerKey !== "MULTIPLE" && triggerKey === itemsExpr;
535
544
  if (keyAttr) {
536
- renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance);
545
+ renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, instance, canSkipUnchanged);
537
546
  } else {
538
- renderNoKey(tmpl, items, marker, parent, state, rawState, instance);
547
+ renderNoKey(tmpl, items, marker, state, rawState, instance);
539
548
  }
540
549
  }
541
550
  }
542
- function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawState, instance) {
551
+ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, instance, canSkipUnchanged) {
543
552
  var _a;
544
553
  const nextKeys = /* @__PURE__ */ new Set();
545
554
  const nextNodes = [];
@@ -573,11 +582,17 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawSta
573
582
  bindDataOn(rowScan2.on, instance);
574
583
  bindAtEvents(rowScan2.atEvents, instance);
575
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;
576
589
  }
577
- const itemState = Object.assign(
578
- Object.create(state),
579
- { item, index, $index: index }
580
- );
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;
581
596
  const rowScan = (_a = node.__micraScan) != null ? _a : node.__micraScan = scanComponent(node);
582
597
  applyDirectives(rowScan, itemState, rawState, instance);
583
598
  nextNodes.push(node);
@@ -588,14 +603,64 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, parent, state, rawSta
588
603
  keyMap.delete(key);
589
604
  }
590
605
  }
591
- let cursor = marker;
592
- for (const node of nextNodes) {
593
- if (cursor.nextSibling !== node) parent.insertBefore(node, cursor.nextSibling);
594
- cursor = node;
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);
595
624
  }
596
625
  tmpl.__micraList = nextNodes;
597
626
  }
598
- function renderNoKey(tmpl, items, marker, parent, state, rawState, instance) {
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) {
599
664
  tmpl.__micraList.forEach((n) => n.remove());
600
665
  tmpl.__micraList = [];
601
666
  const frag = document.createDocumentFragment();
@@ -617,11 +682,12 @@ function renderNoKey(tmpl, items, marker, parent, state, rawState, instance) {
617
682
  });
618
683
  tmpl.__micraList.push(...nodes);
619
684
  }
620
- parent.insertBefore(frag, marker.nextSibling);
685
+ marker.after(frag);
621
686
  }
622
687
 
623
688
  // src/dom/refs.ts
624
689
  function collectRefs(els, instance) {
690
+ if (!els.length) return;
625
691
  instance.refs = {};
626
692
  for (const el of els) {
627
693
  const name = el.dataset["ref"];
@@ -664,8 +730,12 @@ function mount(selector, definition) {
664
730
  return unsub;
665
731
  };
666
732
  let isRendering = false;
733
+ let _triggerKey = null;
667
734
  const schedule = createScheduler(() => instance.render());
668
- 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
+ });
669
739
  const boundMethods = /* @__PURE__ */ new Map();
670
740
  const exprState = new Proxy(rawState, {
671
741
  get(target, key) {
@@ -689,6 +759,8 @@ function mount(selector, definition) {
689
759
  instance.render = function() {
690
760
  var _a2;
691
761
  if (instance.__micraDestroyed) return;
762
+ const triggerKey = _triggerKey;
763
+ _triggerKey = null;
692
764
  if (isRendering) {
693
765
  if (!warnedReentry) {
694
766
  warn(
@@ -703,7 +775,7 @@ function mount(selector, definition) {
703
775
  const mRoot2 = root;
704
776
  const scan = (_a2 = mRoot2.__micraScan) != null ? _a2 : mRoot2.__micraScan = scanComponent(root);
705
777
  applyDirectives(scan, exprState, rawState, instance);
706
- renderList(scan.each, exprState, rawState, instance);
778
+ renderList(scan.each, exprState, rawState, instance, triggerKey);
707
779
  bindDataOn(scan.on, instance);
708
780
  bindAtEvents(scan.atEvents, instance);
709
781
  bindModels(scan.model, instance);