tutuca 0.9.7 → 0.9.9

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/README.md CHANGED
@@ -1 +1,70 @@
1
- # tutuca
1
+ # Tutuca
2
+
3
+ Zero-dependency batteries included SPA framework.
4
+
5
+ - **Single file, no build, no dependencies, no setup** — a script tag is all you need
6
+ - **Batteries included** — state management, side effects, automatic memoization, drag and drop and more
7
+ - **Fits in your head** (and the context window)
8
+ - **View source friendly** — step through the whole stack
9
+ - **As much HTML as possible, as little JS as needed**
10
+ - ~107KB minified, ~29KB brotli compressed
11
+
12
+ ## Quick Start
13
+
14
+ ### CDN (no install)
15
+
16
+ ```html
17
+ <!doctype html>
18
+ <html lang="en">
19
+ <head>
20
+ <meta charset="UTF-8" />
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
22
+ <title>Tutuca: Getting Started</title>
23
+ </head>
24
+ <body>
25
+ <div id="app"></div>
26
+ <script type="module">
27
+ import { component, html, tutuca } from "https://esm.sh/tutuca";
28
+
29
+ const Counter = component({
30
+ name: "Counter",
31
+ fields: {
32
+ count: 0,
33
+ },
34
+ methods: {
35
+ inc() {
36
+ return this.setCount(this.count + 1);
37
+ },
38
+ dec() {
39
+ return this.setCount(this.count - 1);
40
+ },
41
+ },
42
+ view: html`<div>
43
+ <button @on.click=".dec">-</button>
44
+ <div @text=".count"></div>
45
+ <button @on.click=".inc">+</button>
46
+ </div>`,
47
+ });
48
+
49
+ function main() {
50
+ const app = tutuca("#app");
51
+ app.state.set(Counter.make({}));
52
+ app.registerComponents([Counter]);
53
+ app.start();
54
+ }
55
+
56
+ main();
57
+ </script>
58
+ </body>
59
+ </html>
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT
65
+
66
+ ## Links
67
+
68
+ - [Documentation & Playground](https://marianoguerra.github.io/tutuca/)
69
+ - [Tutorial](https://marianoguerra.github.io/tutuca/tutorial.html)
70
+ - [GitHub](https://github.com/marianoguerra/tutuca)
@@ -1089,6 +1089,10 @@ class ANode extends BaseNode {
1089
1089
  return px.addNodeIf(RenderItNode, vp.bindValIt, as);
1090
1090
  case "render-each":
1091
1091
  return RenderEachNode.parse(px, vp, value, as, attrs);
1092
+ case "show":
1093
+ return px.addNodeIf(ShowNode, vp.parseCondValue(value, px), maybeFragment(childs));
1094
+ case "hide":
1095
+ return px.addNodeIf(HideNode, vp.parseCondValue(value, px), maybeFragment(childs));
1092
1096
  }
1093
1097
  return new CommentNode(`Error: InvalidSpecialTagOp ${name}=${value}`);
1094
1098
  } else if (tag.charCodeAt(1) === 58 && tag.charCodeAt(0) === 88) {
@@ -6423,6 +6427,7 @@ var Record = (defaultValues, name) => {
6423
6427
  hasInitialized = true;
6424
6428
  const keys = Object.keys(defaultValues);
6425
6429
  const indices = RecordTypePrototype._indices = {};
6430
+ RecordTypePrototype._name = name;
6426
6431
  RecordTypePrototype._keys = keys;
6427
6432
  RecordTypePrototype._defaultValues = defaultValues;
6428
6433
  for (let i = 0;i < keys.length; i++) {
@@ -6816,6 +6821,62 @@ class RepeatImpl extends IndexedSeqImpl {
6816
6821
  this.prototype[Symbol.iterator] = this.prototype.values;
6817
6822
  }
6818
6823
  }
6824
+ var asValues = (collection) => isKeyed(collection) ? collection.valueSeq() : collection;
6825
+ function initCollectionConversions() {
6826
+ CollectionImpl.prototype.toMap = function toMap() {
6827
+ return Map2(this.toKeyedSeq());
6828
+ };
6829
+ CollectionImpl.prototype.toOrderedMap = function toOrderedMap() {
6830
+ return OrderedMap(this.toKeyedSeq());
6831
+ };
6832
+ CollectionImpl.prototype.toOrderedSet = function toOrderedSet() {
6833
+ return OrderedSet(asValues(this));
6834
+ };
6835
+ CollectionImpl.prototype.toSet = function toSet() {
6836
+ return Set2(asValues(this));
6837
+ };
6838
+ CollectionImpl.prototype.toStack = function toStack() {
6839
+ return Stack2(asValues(this));
6840
+ };
6841
+ CollectionImpl.prototype.toList = function toList() {
6842
+ return List(asValues(this));
6843
+ };
6844
+ CollectionImpl.prototype.countBy = function countBy(grouper, context) {
6845
+ const groups = Map2().asMutable();
6846
+ this.__iterate((v, k) => {
6847
+ groups.update(grouper.call(context, v, k, this), 0, (a) => a + 1);
6848
+ });
6849
+ return groups.asImmutable();
6850
+ };
6851
+ CollectionImpl.prototype.groupBy = function groupBy(grouper, context) {
6852
+ const isKeyedIter = isKeyed(this);
6853
+ const groups = (isOrdered(this) ? OrderedMap() : Map2()).asMutable();
6854
+ this.__iterate((v, k) => {
6855
+ groups.update(grouper.call(context, v, k, this), (a) => {
6856
+ a ??= [];
6857
+ a.push(isKeyedIter ? [k, v] : v);
6858
+ return a;
6859
+ });
6860
+ });
6861
+ return groups.map((arr) => reifyValues(this, arr)).asImmutable();
6862
+ };
6863
+ IndexedCollectionImpl.prototype.keySeq = function keySeq() {
6864
+ return Range(0, this.size);
6865
+ };
6866
+ MapImpl.prototype.sort = function sort(comparator) {
6867
+ return OrderedMap(sortFactory(this, comparator));
6868
+ };
6869
+ MapImpl.prototype.sortBy = function sortBy(mapper, comparator) {
6870
+ return OrderedMap(sortFactory(this, comparator, mapper));
6871
+ };
6872
+ SetImpl.prototype.sort = function sort(comparator) {
6873
+ return OrderedSet(sortFactory(this, comparator));
6874
+ };
6875
+ SetImpl.prototype.sortBy = function sortBy(mapper, comparator) {
6876
+ return OrderedSet(sortFactory(this, comparator, mapper));
6877
+ };
6878
+ }
6879
+ initCollectionConversions();
6819
6880
 
6820
6881
  // src/oo.js
6821
6882
  var BAD_VALUE = Symbol("BadValue");
@@ -6959,14 +7020,8 @@ class FieldString extends Field {
6959
7020
  constructor(name, defaultValue = "") {
6960
7021
  super("text", name, CHECK_TYPE_STRING, stringCoercer, defaultValue);
6961
7022
  }
6962
- extendProtoForType(proto, _uname) {
6963
- const { name } = this;
6964
- proto[`${name}IsEmpty`] = function() {
6965
- return this.get(name, "").length === 0;
6966
- };
6967
- proto[`${name}Len`] = function() {
6968
- return this.get(name, "").length;
6969
- };
7023
+ extendProtoForType(proto, uname) {
7024
+ extendProtoSized(proto, this.name, uname, "", "length");
6970
7025
  }
6971
7026
  }
6972
7027
  var intCoercer = (v) => Number.isFinite(v) ? Math.trunc(v) : null;
@@ -7009,7 +7064,7 @@ class FieldComp extends Field {
7009
7064
  }
7010
7065
  var NONE2 = Symbol("NONE");
7011
7066
  function extendProtoForKeyed(proto, name, uname) {
7012
- extendProtoSeq(proto, name, EMPTY_LIST);
7067
+ extendProtoSized(proto, name, uname, EMPTY_LIST);
7013
7068
  proto[`setIn${uname}At`] = function(i, v) {
7014
7069
  return this.set(name, this.get(name).set(i, v));
7015
7070
  };
@@ -7025,11 +7080,13 @@ function extendProtoForKeyed(proto, name, uname) {
7025
7080
  console.warn("key", i, "not found in", name, col);
7026
7081
  return this;
7027
7082
  };
7028
- function deleteInAt(i) {
7029
- return this.set(name, this.get(name).delete(i));
7030
- }
7031
- proto[`deleteIn${uname}At`] = deleteInAt;
7032
- proto[`removeIn${uname}At`] = deleteInAt;
7083
+ extendDeleteInAt(proto, name, `${uname}At`);
7084
+ }
7085
+ function extendDeleteInAt(proto, name, uname) {
7086
+ proto[`deleteIn${uname}`] = function(v) {
7087
+ return this.set(name, this.get(name).delete(v));
7088
+ };
7089
+ proto[`removeIn${uname}`] = proto[`deleteIn${uname}`];
7033
7090
  }
7034
7091
  var EMPTY_LIST = List();
7035
7092
  var listCoercer = (v) => Array.isArray(v) ? List(v) : null;
@@ -7069,12 +7126,12 @@ class FieldOMap extends Field {
7069
7126
  extendProtoForKeyed(proto, this.name, uname);
7070
7127
  }
7071
7128
  }
7072
- function extendProtoSeq(proto, name, defaultEmpty) {
7073
- proto[`${name}IsEmpty`] = function() {
7074
- return this.get(name, defaultEmpty).size === 0;
7129
+ function extendProtoSized(proto, name, uname, defaultEmpty, propName = "size") {
7130
+ proto[`is${uname}Empty`] = function() {
7131
+ return this.get(name, defaultEmpty)[propName] === 0;
7075
7132
  };
7076
7133
  proto[`${name}Len`] = function() {
7077
- return this.get(name, defaultEmpty).size;
7134
+ return this.get(name, defaultEmpty)[propName];
7078
7135
  };
7079
7136
  }
7080
7137
  var EMPTY_SET2 = Set2();
@@ -7086,14 +7143,11 @@ class FieldSet extends Field {
7086
7143
  }
7087
7144
  extendProtoForType(proto, uname) {
7088
7145
  const { name } = this;
7089
- extendProtoSeq(proto, name, EMPTY_SET2);
7146
+ extendProtoSized(proto, name, uname, EMPTY_SET2);
7090
7147
  proto[`addIn${uname}`] = function(v) {
7091
7148
  return this.set(name, this.get(name).add(v));
7092
7149
  };
7093
- proto[`deleteIn${uname}`] = function(v) {
7094
- return this.set(name, this.get(name).delete(v));
7095
- };
7096
- proto[`removeIn${uname}`] = proto[`deleteIn${uname}`];
7150
+ extendDeleteInAt(proto, name, uname);
7097
7151
  proto[`hasIn${uname}`] = function(v) {
7098
7152
  return this.get(name).has(v);
7099
7153
  };
@@ -7468,9 +7522,8 @@ function* klistEntries(seq) {
7468
7522
  }
7469
7523
  seqInfoByClass.set(KList, ["data-sk", klistEntries]);
7470
7524
  // src/vdom.js
7471
- function isHtmlAttribute(propName) {
7472
- return propName[4] === "-" && (propName[0] === "d" || propName[0] === "a");
7473
- }
7525
+ var isHtmlAttribute = (propName) => propName[4] === "-" && (propName[0] === "d" || propName[0] === "a");
7526
+ var isObject = (v) => v !== null && typeof v === "object";
7474
7527
  function applyProperties(node, props, previous) {
7475
7528
  for (const propName in props) {
7476
7529
  const propValue = props[propName];
@@ -7480,7 +7533,7 @@ function applyProperties(node, props, previous) {
7480
7533
  node.setAttribute(propName, propValue);
7481
7534
  } else if (propName === "dangerouslySetInnerHTML") {
7482
7535
  node.innerHTML = propValue.__html ?? "";
7483
- } else if (typeof propValue === "object" && propValue !== null) {
7536
+ } else if (isObject(propValue)) {
7484
7537
  patchObject(node, previous, propName, propValue);
7485
7538
  } else if (propName === "className") {
7486
7539
  node.setAttribute("class", propValue);
@@ -7493,11 +7546,7 @@ function removeProperty(node, propName, previous) {
7493
7546
  const previousValue = previous[propName];
7494
7547
  if (propName === "dangerouslySetInnerHTML") {
7495
7548
  node.innerHTML = "";
7496
- } else if (isHtmlAttribute(propName)) {
7497
- node.removeAttribute(propName);
7498
- } else if (typeof previousValue === "string") {
7499
- if (propName !== "className")
7500
- node[propName] = "";
7549
+ } else if (typeof previousValue === "string" || isHtmlAttribute(propName)) {
7501
7550
  const attrName = propName === "className" ? "class" : propName === "htmlFor" ? "for" : propName;
7502
7551
  node.removeAttribute(attrName);
7503
7552
  } else {
@@ -7506,12 +7555,12 @@ function removeProperty(node, propName, previous) {
7506
7555
  }
7507
7556
  function patchObject(node, previous, propName, propValue) {
7508
7557
  const previousValue = previous?.[propName];
7509
- if (previousValue && typeof previousValue === "object" && Object.getPrototypeOf(previousValue) !== Object.getPrototypeOf(propValue)) {
7558
+ if (isObject(previousValue) && Object.getPrototypeOf(previousValue) !== Object.getPrototypeOf(propValue)) {
7510
7559
  node[propName] = propValue;
7511
7560
  return;
7512
7561
  }
7513
7562
  let current = node[propName];
7514
- if (typeof current !== "object" || current === null) {
7563
+ if (!isObject(current)) {
7515
7564
  node[propName] = {};
7516
7565
  current = node[propName];
7517
7566
  }
@@ -7529,16 +7578,11 @@ class VBase {
7529
7578
  return null;
7530
7579
  }
7531
7580
  }
7532
- function getKey(child) {
7533
- return child instanceof VNode2 ? child.key : undefined;
7534
- }
7535
- function isIterable(obj) {
7536
- return obj != null && typeof obj !== "string" && typeof obj[Symbol.iterator] === "function";
7537
- }
7581
+ var getKey = (child) => child instanceof VNode2 ? child.key : undefined;
7582
+ var isIterable = (obj) => obj != null && typeof obj !== "string" && typeof obj[Symbol.iterator] === "function";
7538
7583
  function addChild(normalizedChildren, child) {
7539
- if (child == null) {
7584
+ if (child == null)
7540
7585
  return;
7541
- }
7542
7586
  if (isIterable(child)) {
7543
7587
  for (const c of child) {
7544
7588
  addChild(normalizedChildren, c);
@@ -7589,9 +7633,8 @@ class VComment extends VBase {
7589
7633
  class VFragment extends VBase {
7590
7634
  constructor(childs) {
7591
7635
  super();
7592
- const normalized = [];
7593
- addChild(normalized, childs);
7594
- this.childs = normalized;
7636
+ this.childs = [];
7637
+ addChild(this.childs, childs);
7595
7638
  }
7596
7639
  get nodeType() {
7597
7640
  return 11;
@@ -7675,7 +7718,7 @@ function diffProps(a, b) {
7675
7718
  }
7676
7719
  const aValue = a[aKey];
7677
7720
  const bValue = b[aKey];
7678
- if (aValue === bValue) {} else if (typeof aValue === "object" && aValue !== null && typeof bValue === "object" && bValue !== null) {
7721
+ if (aValue === bValue) {} else if (isObject(aValue) && isObject(bValue)) {
7679
7722
  if (Object.getPrototypeOf(bValue) !== Object.getPrototypeOf(aValue)) {
7680
7723
  diff ??= {};
7681
7724
  diff[aKey] = bValue;
@@ -7707,14 +7750,15 @@ function replaceNode(domNode, vnode, options) {
7707
7750
  }
7708
7751
  return newNode || domNode;
7709
7752
  }
7753
+ var bothInstanceOf = (a, b, C) => a instanceof C && b instanceof C;
7710
7754
  function morphNode(domNode, source, target, opts) {
7711
7755
  if (source === target || source.isEqualTo(target))
7712
7756
  return domNode;
7713
- if (source instanceof VText && target instanceof VText || source instanceof VComment && target instanceof VComment) {
7757
+ if (bothInstanceOf(source, target, VText) || bothInstanceOf(source, target, VComment)) {
7714
7758
  domNode.data = target.text;
7715
7759
  return domNode;
7716
7760
  }
7717
- if (source instanceof VNode2 && target instanceof VNode2 && source.tag === target.tag && source.namespace === target.namespace && source.key === target.key) {
7761
+ if (bothInstanceOf(source, target, VNode2) && source.tag === target.tag && source.namespace === target.namespace && source.key === target.key) {
7718
7762
  const propsDiff = diffProps(source.attrs, target.attrs);
7719
7763
  if (propsDiff) {
7720
7764
  applyProperties(domNode, propsDiff, source.attrs);
@@ -7724,7 +7768,7 @@ function morphNode(domNode, source, target, opts) {
7724
7768
  }
7725
7769
  return domNode;
7726
7770
  }
7727
- if (source instanceof VFragment && target instanceof VFragment) {
7771
+ if (bothInstanceOf(source, target, VFragment)) {
7728
7772
  morphChildren(domNode, source.childs, target.childs, null, opts);
7729
7773
  return domNode;
7730
7774
  }
@@ -7801,10 +7845,8 @@ function render(vnode, container, options) {
7801
7845
  if (wasFragment === isFragment) {
7802
7846
  const rootNode = wasFragment ? container : cached.dom;
7803
7847
  const newDom = morphNode(rootNode, cached.vnode, vnode, options);
7804
- renderCache.set(container, {
7805
- vnode,
7806
- dom: isFragment ? container : newDom
7807
- });
7848
+ const domToCache = isFragment ? container : newDom;
7849
+ renderCache.set(container, { vnode, dom: domToCache });
7808
7850
  return newDom;
7809
7851
  }
7810
7852
  renderCache.delete(container);
@@ -7813,32 +7855,33 @@ function render(vnode, container, options) {
7813
7855
  if (domNode) {
7814
7856
  container.innerHTML = "";
7815
7857
  container.appendChild(domNode);
7816
- renderCache.set(container, {
7817
- vnode,
7818
- dom: isFragment ? container : domNode
7819
- });
7858
+ const domToCache = isFragment ? container : domNode;
7859
+ renderCache.set(container, { vnode, dom: domToCache });
7820
7860
  }
7821
7861
  return domNode;
7822
7862
  }
7823
7863
  function h(tagName, properties, children) {
7824
7864
  const tag = tagName.toUpperCase();
7825
7865
  const props = {};
7826
- let key;
7827
- let namespace;
7866
+ let key, namespace;
7828
7867
  if (properties) {
7829
7868
  for (const propName in properties) {
7830
- if (propName === "key") {
7831
- key = properties[propName];
7832
- } else if (propName === "namespace") {
7833
- namespace = properties[propName];
7834
- } else if (propName === "class") {
7835
- props.className = properties[propName];
7836
- } else if (propName === "for") {
7837
- props.htmlFor = properties[propName];
7838
- } else if (isHtmlAttribute(propName)) {
7839
- props[propName] = String(properties[propName]);
7840
- } else {
7841
- props[propName] = properties[propName];
7869
+ const propVal = properties[propName];
7870
+ switch (propName) {
7871
+ case "key":
7872
+ key = propVal;
7873
+ break;
7874
+ case "namespace":
7875
+ namespace = propVal;
7876
+ break;
7877
+ case "class":
7878
+ props.className = propVal;
7879
+ break;
7880
+ case "for":
7881
+ props.htmlFor = propVal;
7882
+ break;
7883
+ default:
7884
+ props[propName] = isHtmlAttribute(propName) ? String(propVal) : propVal;
7842
7885
  }
7843
7886
  }
7844
7887
  }