@source-crafter/xzor-runtime 0.1.14 → 0.1.16

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/model.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  $inSvg,
3
3
  $isFragment,
4
+ $leave,
4
5
  ComponentBase,
5
6
  FINE,
6
7
  SVG,
@@ -8,7 +9,7 @@ import {
8
9
  patchIsland,
9
10
  setFineHooks,
10
11
  setRenderObserver
11
- } from "./index-k290gfdd.js";
12
+ } from "./index-w2yxrgd1.js";
12
13
 
13
14
  // runtime/entity-set.ts
14
15
  class EntitySet extends Array {
@@ -226,18 +227,27 @@ var ATTR = 1;
226
227
  var REGION = 2;
227
228
  var LIST = 3;
228
229
  var ISLAND = 4;
230
+ var KEYED = 5;
231
+ var EFFECT = 6;
232
+ var MEMO = 7;
229
233
  var KIND = 7;
230
234
  var QUEUED = 8;
231
235
  var MANY = 16;
232
236
  var DEAD = 32;
233
237
  var LOOSE = 64;
234
238
  var ITEM = 128;
239
+ var OWNED = 256;
240
+ var FRESH = 512;
235
241
  var bitOf = (index) => index === -1 ? 1 << 31 : index < 30 ? 1 << index : 1 << 30;
242
+ var MEMOS = Symbol("xzor.fine.memos");
243
+ var EMPTY = Symbol("xzor.fine.empty");
236
244
  var collecting;
237
245
  var holder;
238
246
  function collect(source, index) {
247
+ collectBits(source, bitOf(index));
248
+ }
249
+ function collectBits(source, bit) {
239
250
  const b = collecting;
240
- const bit = bitOf(index);
241
251
  const own = b.model;
242
252
  if (own === source) {
243
253
  b.mask |= bit;
@@ -296,8 +306,31 @@ class Binding {
296
306
  return (this.flags & (QUEUED | DEAD)) !== 0 ? 0 : -1;
297
307
  }
298
308
  set at(value) {
299
- if ((this.flags & DEAD) === 0)
300
- this.flags = value < 0 ? this.flags & ~QUEUED : this.flags | QUEUED;
309
+ if ((this.flags & DEAD) !== 0)
310
+ return;
311
+ if ((this.flags & KIND) === MEMO) {
312
+ if (value >= 0)
313
+ this.flags &= ~FRESH;
314
+ return;
315
+ }
316
+ this.flags = value < 0 ? this.flags & ~QUEUED : this.flags | QUEUED;
317
+ }
318
+ $memo() {
319
+ if ((this.flags & FRESH) === 0) {
320
+ this.data = this.#run();
321
+ if (this.model !== undefined && (this.flags & DEAD) === 0)
322
+ this.flags |= FRESH;
323
+ }
324
+ if (collecting !== undefined) {
325
+ const own = this.model;
326
+ if ((this.flags & MANY) === 0) {
327
+ if (own !== undefined)
328
+ collectBits(own, this.mask);
329
+ } else
330
+ for (const dep of own.values())
331
+ collectBits(dep.model, dep.mask);
332
+ }
333
+ return this.data;
301
334
  }
302
335
  #reset() {
303
336
  this.mask = 0;
@@ -330,7 +363,7 @@ class Binding {
330
363
  }
331
364
  }
332
365
  #loose() {
333
- if ((this.flags & (LOOSE | DEAD)) !== 0 || holder === undefined)
366
+ if ((this.flags & (LOOSE | DEAD)) !== 0 || (this.flags & KIND) >= EFFECT || holder === undefined)
334
367
  return;
335
368
  this.flags |= LOOSE;
336
369
  holder.ctx.loose.push(this);
@@ -365,14 +398,19 @@ class Binding {
365
398
  return;
366
399
  }
367
400
  case ATTR:
401
+ if ((flags & OWNED) !== 0)
402
+ return ownAttr(this.node, this.data, this.#run());
368
403
  return setAttr(this.node, (flags & ITEM) !== 0 ? this.read.$n : this.data, this.#run());
369
404
  case REGION: {
370
405
  const region = this.data;
371
406
  disposeAll(region);
372
407
  const end = this.node;
373
408
  const parent = end.parentNode;
374
- while (region.start.nextSibling !== end)
375
- parent.removeChild(region.start.nextSibling);
409
+ for (let node = region.start.nextSibling;node !== end; ) {
410
+ const next = node.nextSibling;
411
+ $leave(node);
412
+ node = next;
413
+ }
376
414
  const frag = document.createDocumentFragment();
377
415
  within(region, () => this.#run(frag));
378
416
  parent.insertBefore(frag, end);
@@ -380,9 +418,17 @@ class Binding {
380
418
  }
381
419
  case LIST:
382
420
  return reconcile(this, this.#run());
421
+ case KEYED: {
422
+ const keyed = this.data;
423
+ keyed.order = this.#run();
424
+ return;
425
+ }
383
426
  case ISLAND:
427
+ case EFFECT:
384
428
  this.#run();
385
429
  return;
430
+ case MEMO:
431
+ return;
386
432
  }
387
433
  }
388
434
  #becomeIsland() {
@@ -409,13 +455,14 @@ class Binding {
409
455
  const kind = this.flags & KIND;
410
456
  if (kind === REGION)
411
457
  disposeAll(this.data);
412
- else if (kind === LIST)
458
+ else if (kind === LIST || kind === KEYED)
413
459
  for (const row of this.data.order)
414
460
  disposeAll(row);
415
461
  else if (kind === ISLAND) {
416
462
  disposeIsland(this.data.tree);
417
463
  this.data.tree = [];
418
- }
464
+ } else if ((this.flags & OWNED) !== 0)
465
+ undoAttr(this.node, this.data);
419
466
  this.#reset();
420
467
  this.#sweep();
421
468
  this.flags = DEAD;
@@ -448,6 +495,18 @@ function afterHandler(ctx, result) {
448
495
  if (result instanceof Promise)
449
496
  result.then(() => refreshLoose(ctx));
450
497
  }
498
+ function untracked(run) {
499
+ const prev = collecting;
500
+ const reader = Model.$reader;
501
+ collecting = undefined;
502
+ Model.$reader = undefined;
503
+ try {
504
+ return run();
505
+ } finally {
506
+ collecting = prev;
507
+ Model.$reader = reader;
508
+ }
509
+ }
451
510
  function within(h, run) {
452
511
  const prev = holder;
453
512
  holder = h;
@@ -471,13 +530,18 @@ function disposeAll(h) {
471
530
  function refreshAll(h) {
472
531
  for (let b = h.bindings;b !== undefined; b = b.next) {
473
532
  b.stateHasChanged();
474
- if ((b.flags & KIND) === LIST)
533
+ const kind = b.flags & KIND;
534
+ if (kind === LIST || kind === KEYED)
475
535
  for (const row of b.data.order)
476
536
  refreshAll(row);
477
537
  }
478
538
  }
479
539
  var textOf = (value) => value === null || value === undefined || value === false ? "" : String(value);
480
540
  function setAttr(el, name, value) {
541
+ if (typeof value === "boolean" && name.startsWith("aria-"))
542
+ value = String(value);
543
+ if (name.startsWith("class:"))
544
+ return void el.classList.toggle(name.slice(6), !!value);
481
545
  if (name.charCodeAt(0) === 45 && name.charCodeAt(1) === 45) {
482
546
  const style = el.style;
483
547
  const text2 = value === null || value === undefined || value === false ? "" : String(value);
@@ -504,48 +568,68 @@ function setAttr(el, name, value) {
504
568
  else if (name === "checked" && "checked" in el)
505
569
  el.checked = true;
506
570
  }
571
+ function ownAttr(el, s, value) {
572
+ if (s.name !== "class") {
573
+ if (s.prev === undefined)
574
+ s.prev = el.getAttribute(s.name);
575
+ return setAttr(el, s.name, value);
576
+ }
577
+ const text = value === null || value === undefined || value === false || value === true ? "" : String(value);
578
+ if (text === s.text)
579
+ return;
580
+ s.text = text;
581
+ for (const t of s.added)
582
+ el.classList.remove(t);
583
+ s.added = [];
584
+ for (const t of text.split(/\s+/))
585
+ if (t !== "" && !el.classList.contains(t)) {
586
+ el.classList.add(t);
587
+ s.added.push(t);
588
+ }
589
+ }
590
+ function undoAttr(el, s) {
591
+ if (s.name === "class")
592
+ for (const t of s.added)
593
+ el.classList.remove(t);
594
+ else if (s.prev !== undefined)
595
+ setAttr(el, s.name, s.prev);
596
+ }
507
597
  function removeRange(head, tail) {
508
- const parent = head.parentNode;
509
598
  for (let node = head;; ) {
510
599
  const next = node.nextSibling;
511
- parent.removeChild(node);
600
+ $leave(node);
512
601
  if (node === tail)
513
602
  return;
514
603
  node = next;
515
604
  }
516
605
  }
517
- function reconcile(list, items) {
518
- const state = list.data;
519
- const end = list.node;
606
+ function newRow(ctx, item, reads, build) {
607
+ const frag = document.createDocumentFragment();
608
+ const row = { bindings: undefined, ctx, head: frag, tail: frag, item, reads };
609
+ within(row, () => build(frag, item));
610
+ seal(row, frag);
611
+ return row;
612
+ }
613
+ function seal(row, frag) {
614
+ if (frag.firstChild === null)
615
+ frag.appendChild(document.createComment(""));
616
+ row.head = frag.firstChild;
617
+ row.tail = frag.lastChild;
618
+ }
619
+ function dropRow(row) {
620
+ disposeAll(row);
621
+ removeRange(row.head, row.tail);
622
+ }
623
+ function place(end, old, order) {
520
624
  const parent = end.parentNode;
521
- const next = Array.isArray(items) ? items : [...items];
522
- const keep = new Set(next);
523
- for (const [item, row] of state.rows)
524
- if (!keep.has(item)) {
525
- disposeAll(row);
526
- removeRange(row.head, row.tail);
527
- state.rows.delete(item);
528
- }
529
625
  let cursor = end;
530
- for (const row of state.order)
626
+ for (const row of old)
531
627
  if (row.head.parentNode === parent) {
532
628
  cursor = row.head;
533
629
  break;
534
630
  }
535
- const order = [];
536
- for (const item of next) {
537
- let row = state.rows.get(item);
538
- if (row === undefined) {
539
- const frag = document.createDocumentFragment();
540
- const made = { bindings: undefined, ctx: state.ctx, head: frag, tail: frag, item, reads: state.reads };
541
- within(made, () => state.build(frag, item));
542
- if (frag.firstChild === null)
543
- frag.appendChild(document.createComment(""));
544
- made.head = frag.firstChild;
545
- made.tail = frag.lastChild;
546
- parent.insertBefore(frag, cursor);
547
- state.rows.set(item, row = made);
548
- } else if (row.head !== cursor) {
631
+ for (const row of order) {
632
+ if (row.head !== cursor) {
549
633
  for (let node = row.head;; ) {
550
634
  const after = node.nextSibling;
551
635
  parent.insertBefore(node, cursor);
@@ -555,10 +639,114 @@ function reconcile(list, items) {
555
639
  }
556
640
  } else
557
641
  cursor = row.tail.nextSibling;
642
+ }
643
+ }
644
+ var duplicate = (key) => new Error(`xzor: duplicate @key ${String(key)}`);
645
+ function reconcile(list, items) {
646
+ const state = list.data;
647
+ const rows = state.rows;
648
+ let next = Array.isArray(items) ? items : [...items];
649
+ if (next.length === 0 && state.empty !== undefined)
650
+ next = [EMPTY];
651
+ const named = state.key === undefined ? $keyName(next[0]) : undefined;
652
+ const keyOf = state.key ?? (named === undefined ? undefined : (item) => item[named]);
653
+ let keys = next;
654
+ if (keyOf === undefined) {
655
+ const keep = new Set(next);
656
+ for (const [item, row] of rows)
657
+ if (!keep.has(item)) {
658
+ dropRow(row);
659
+ rows.delete(item);
660
+ }
661
+ } else {
662
+ const at = new Map;
663
+ keys = next.map((item) => item === EMPTY ? EMPTY : keyOf(item));
664
+ for (let i = 0;i < next.length; i++) {
665
+ if (at.has(keys[i]))
666
+ throw duplicate(keys[i]);
667
+ at.set(keys[i], next[i]);
668
+ }
669
+ for (const [key, row] of rows)
670
+ if (!at.has(key) || at.get(key) !== row.item) {
671
+ dropRow(row);
672
+ rows.delete(key);
673
+ }
674
+ }
675
+ const order = [];
676
+ for (let i = 0;i < next.length; i++) {
677
+ const item = next[i];
678
+ let row = rows.get(keys[i]);
679
+ if (row === undefined) {
680
+ row = item === EMPTY ? newRow(state.ctx, item, undefined, state.empty) : newRow(state.ctx, item, state.reads, state.build);
681
+ rows.set(keys[i], row);
682
+ }
558
683
  order.push(row);
559
684
  }
685
+ place(list.node, state.order, order);
560
686
  state.order = order;
561
687
  }
688
+ function sameValues(a, b) {
689
+ if (a.length !== b.length)
690
+ return false;
691
+ for (let i = 0;i < a.length; i++)
692
+ if (!Object.is(a[i], b[i]))
693
+ return false;
694
+ return true;
695
+ }
696
+ function runKeyed(s, end, run) {
697
+ const order = [];
698
+ const seen = new Set;
699
+ const outer = holder;
700
+ let building;
701
+ const finish = () => {
702
+ if (building === undefined)
703
+ return;
704
+ seal(building, building.head);
705
+ building = undefined;
706
+ holder = outer;
707
+ };
708
+ try {
709
+ run((key, values) => {
710
+ finish();
711
+ if (seen.has(key))
712
+ throw duplicate(key);
713
+ seen.add(key);
714
+ let row = s.rows.get(key);
715
+ if (row !== undefined && !sameValues(row.item, values)) {
716
+ dropRow(row);
717
+ row = undefined;
718
+ }
719
+ if (row !== undefined) {
720
+ order.push(row);
721
+ return;
722
+ }
723
+ const frag = document.createDocumentFragment();
724
+ row = { bindings: undefined, ctx: s.ctx, head: frag, tail: frag, item: values, reads: undefined };
725
+ s.rows.set(key, row);
726
+ order.push(row);
727
+ building = row;
728
+ holder = row;
729
+ return frag;
730
+ });
731
+ finish();
732
+ } finally {
733
+ holder = outer;
734
+ }
735
+ if (order.length === 0 && s.empty !== undefined) {
736
+ let row = s.rows.get(EMPTY);
737
+ if (row === undefined)
738
+ s.rows.set(EMPTY, row = newRow(s.ctx, EMPTY, undefined, s.empty));
739
+ order.push(row);
740
+ seen.add(EMPTY);
741
+ }
742
+ for (const [key, row] of s.rows)
743
+ if (!seen.has(key)) {
744
+ dropRow(row);
745
+ s.rows.delete(key);
746
+ }
747
+ place(end, s.order, order);
748
+ return order;
749
+ }
562
750
  function make(spec, svg = false) {
563
751
  if (spec === null)
564
752
  return document.createComment("");
@@ -616,8 +804,8 @@ function region(start, end, build, item, flags) {
616
804
  const region2 = { bindings: undefined, ctx: holder.ctx, start, item };
617
805
  track(new Binding(REGION | flags, end, build, region2));
618
806
  }
619
- function list(end, items, reads, build) {
620
- const state = { build, ctx: holder.ctx, rows: new Map, order: [], reads };
807
+ function list(end, items, reads, build, empty, key) {
808
+ const state = { build, ctx: holder.ctx, rows: new Map, order: [], reads, empty, key };
621
809
  track(new Binding(LIST, end, items, state));
622
810
  }
623
811
  var anchor = (parent) => parent.appendChild(document.createComment(""));
@@ -682,12 +870,100 @@ var fine = {
682
870
  read.$n = name;
683
871
  track(new Binding(ATTR | ITEM, el, read, row));
684
872
  },
873
+ attrOf(el, name, read) {
874
+ track(new Binding(ATTR | OWNED, el, read, { name, text: undefined, prev: undefined, added: [] }));
875
+ },
685
876
  region(parent, build) {
686
877
  region(anchor(parent), anchor(parent), build, undefined, 0);
687
878
  },
688
879
  regionAt(end, build) {
689
880
  region(end.previousSibling, end, build, undefined, 0);
690
881
  },
882
+ textOnce(node, read) {
883
+ node.data = textOf(untracked(read));
884
+ },
885
+ onceAt(end, build) {
886
+ const frag = document.createDocumentFragment();
887
+ untracked(() => build(frag));
888
+ end.parentNode.insertBefore(frag, end);
889
+ },
890
+ set(el, name, value) {
891
+ setAttr(el, name, value);
892
+ },
893
+ setText(node, value) {
894
+ node.data = textOf(value);
895
+ },
896
+ anchor,
897
+ memo(cls, names) {
898
+ const proto = cls.prototype;
899
+ names.forEach((name, i) => {
900
+ const desc = Object.getOwnPropertyDescriptor(proto, name);
901
+ const get = desc?.get;
902
+ if (get === undefined)
903
+ return;
904
+ const reader = Symbol(name);
905
+ Object.defineProperty(proto, name, {
906
+ ...desc,
907
+ get() {
908
+ const memo = this[MEMOS]?.[i];
909
+ if (memo !== undefined)
910
+ return memo.$memo();
911
+ const by = collecting;
912
+ if (by === undefined)
913
+ return get.call(this);
914
+ const first = this[reader];
915
+ if (first === undefined || first === by || (first.flags & DEAD) !== 0) {
916
+ this[reader] = by;
917
+ return get.call(this);
918
+ }
919
+ this[reader] = undefined;
920
+ const b = new Binding(MEMO | QUEUED, null, () => get.call(this), undefined);
921
+ (this[MEMOS] ??= [])[i] = b;
922
+ return b.$memo();
923
+ }
924
+ });
925
+ });
926
+ },
927
+ awaitAt(end, read, pending, then, fail) {
928
+ const state = { phase: 0, value: undefined, seq: 0 };
929
+ const view = new Binding(REGION, end, (p) => {
930
+ if (state.phase === 0)
931
+ pending?.(p);
932
+ else if (state.phase === 1)
933
+ then?.(p, state.value);
934
+ else
935
+ fail?.(p, state.value);
936
+ }, { bindings: undefined, ctx: holder.ctx, start: end.previousSibling, item: undefined });
937
+ const show = (phase, value) => {
938
+ if ((view.flags & DEAD) !== 0)
939
+ return;
940
+ state.phase = phase;
941
+ state.value = value;
942
+ view.stateHasChanged();
943
+ };
944
+ track(view);
945
+ track(new Binding(EFFECT, end, () => {
946
+ const result = read();
947
+ const seq = ++state.seq;
948
+ if (typeof result?.then !== "function")
949
+ return show(1, result);
950
+ if (state.phase !== 0)
951
+ show(0, undefined);
952
+ result.then((value) => seq === state.seq && show(1, value), (error) => {
953
+ if (seq !== state.seq)
954
+ return;
955
+ show(2, error);
956
+ if (fail === undefined)
957
+ queueMicrotask(() => {
958
+ throw error;
959
+ });
960
+ });
961
+ }, undefined));
962
+ },
963
+ anchors(parent) {
964
+ anchor(parent);
965
+ return anchor(parent);
966
+ },
691
967
  regionItem(end, i) {
692
968
  const row = holder;
693
969
  region(end.previousSibling, end, row.reads[i], row.item, ITEM);
@@ -695,8 +971,12 @@ var fine = {
695
971
  list(parent, items, build) {
696
972
  list(anchor(parent), items, undefined, build);
697
973
  },
698
- listAt(end, items, reads, build) {
699
- list(end, items, reads, build);
974
+ listAt(end, items, reads, build, empty, key) {
975
+ list(end, items, reads, build, empty, key);
976
+ },
977
+ keyedAt(end, run, empty) {
978
+ const state = { ctx: holder.ctx, rows: new Map, order: [], empty };
979
+ track(new Binding(KEYED, end, () => runKeyed(state, end, run), state));
700
980
  },
701
981
  island(parent, build) {
702
982
  fine.islandAt(anchor(parent), build);
@@ -765,8 +1045,22 @@ setFineHooks({
765
1045
  root.end = frag.appendChild(document.createComment(""));
766
1046
  return root;
767
1047
  },
768
- refresh: (root) => refreshAll(root),
769
- dispose: (root) => disposeAll(root),
1048
+ refresh(root) {
1049
+ const memos = root.ctx.owner[MEMOS];
1050
+ if (memos !== undefined) {
1051
+ for (const m of memos)
1052
+ if (m !== undefined)
1053
+ m.flags &= ~FRESH;
1054
+ }
1055
+ refreshAll(root);
1056
+ },
1057
+ dispose(root) {
1058
+ const memos = root.ctx.owner[MEMOS];
1059
+ if (memos !== undefined)
1060
+ for (const m of memos)
1061
+ m?.dispose();
1062
+ disposeAll(root);
1063
+ },
770
1064
  handled(owner, result) {
771
1065
  const ctx = owner[CTX];
772
1066
  if (ctx !== undefined)
@@ -804,6 +1098,7 @@ var meta = Symbol("xzor.model.meta");
804
1098
  var empty = { names: [], tracked: new Set, persisted: [], ignored: new Set, validators: [], serialized: [] };
805
1099
  var pendingPersist;
806
1100
  var pendingIgnored;
1101
+ var pendingKey;
807
1102
  var pendingValidators;
808
1103
  var silent = 0;
809
1104
  function metaOf(model) {
@@ -835,8 +1130,10 @@ class Model extends EventTarget {
835
1130
  tracked: new Set(all),
836
1131
  persisted: base ? base.persisted.slice() : [],
837
1132
  ignored: new Set(base?.ignored),
838
- validators: base ? base.validators.map(([name, list2]) => [name, list2.slice()]) : []
1133
+ validators: base ? base.validators.map(([name, list2]) => [name, list2.slice()]) : [],
1134
+ key: pendingKey ?? base?.key
839
1135
  };
1136
+ pendingKey = undefined;
840
1137
  if (pendingValidators) {
841
1138
  for (const [name, list2] of pendingValidators) {
842
1139
  const known = own.validators.find((entry) => entry[0] === name);
@@ -1230,6 +1527,12 @@ function Ignore(_member, context) {
1230
1527
  (pendingIgnored ??= []).push(context.name);
1231
1528
  }
1232
1529
  function Untracked(_value, _context) {}
1530
+ function Key(_member, context) {
1531
+ pendingKey = context.name;
1532
+ }
1533
+ function $keyName(item) {
1534
+ return item instanceof Model ? metaOf(item).key : undefined;
1535
+ }
1233
1536
  function isLike(a, b) {
1234
1537
  return Model.same(a, b);
1235
1538
  }
@@ -1440,10 +1743,12 @@ setRenderObserver({
1440
1743
  });
1441
1744
  export {
1442
1745
  $flush,
1746
+ $keyName,
1443
1747
  $notify,
1444
1748
  EntitySet,
1445
1749
  FineComponent,
1446
1750
  Ignore,
1751
+ Key,
1447
1752
  Model,
1448
1753
  ModelChangeEvent,
1449
1754
  Persist,
@@ -1465,5 +1770,5 @@ export {
1465
1770
  requiredIdValue
1466
1771
  };
1467
1772
 
1468
- //# debugId=1D6B4E24F03EAD3F64756E2164756E21
1773
+ //# debugId=55A54F7D726CA15364756E2164756E21
1469
1774
  //# sourceMappingURL=model.js.map