tutuca 0.9.7 → 0.9.8

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) {
@@ -6959,14 +6963,8 @@ class FieldString extends Field {
6959
6963
  constructor(name, defaultValue = "") {
6960
6964
  super("text", name, CHECK_TYPE_STRING, stringCoercer, defaultValue);
6961
6965
  }
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
- };
6966
+ extendProtoForType(proto, uname) {
6967
+ extendProtoSized(proto, this.name, uname, "", "length");
6970
6968
  }
6971
6969
  }
6972
6970
  var intCoercer = (v) => Number.isFinite(v) ? Math.trunc(v) : null;
@@ -7009,7 +7007,7 @@ class FieldComp extends Field {
7009
7007
  }
7010
7008
  var NONE2 = Symbol("NONE");
7011
7009
  function extendProtoForKeyed(proto, name, uname) {
7012
- extendProtoSeq(proto, name, EMPTY_LIST);
7010
+ extendProtoSized(proto, name, uname, EMPTY_LIST);
7013
7011
  proto[`setIn${uname}At`] = function(i, v) {
7014
7012
  return this.set(name, this.get(name).set(i, v));
7015
7013
  };
@@ -7025,11 +7023,13 @@ function extendProtoForKeyed(proto, name, uname) {
7025
7023
  console.warn("key", i, "not found in", name, col);
7026
7024
  return this;
7027
7025
  };
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;
7026
+ extendDeleteInAt(proto, name, `${uname}At`);
7027
+ }
7028
+ function extendDeleteInAt(proto, name, uname) {
7029
+ proto[`deleteIn${uname}`] = function(v) {
7030
+ return this.set(name, this.get(name).delete(v));
7031
+ };
7032
+ proto[`removeIn${uname}`] = proto[`deleteIn${uname}`];
7033
7033
  }
7034
7034
  var EMPTY_LIST = List();
7035
7035
  var listCoercer = (v) => Array.isArray(v) ? List(v) : null;
@@ -7069,12 +7069,12 @@ class FieldOMap extends Field {
7069
7069
  extendProtoForKeyed(proto, this.name, uname);
7070
7070
  }
7071
7071
  }
7072
- function extendProtoSeq(proto, name, defaultEmpty) {
7073
- proto[`${name}IsEmpty`] = function() {
7074
- return this.get(name, defaultEmpty).size === 0;
7072
+ function extendProtoSized(proto, name, uname, defaultEmpty, propName = "size") {
7073
+ proto[`is${uname}Empty`] = function() {
7074
+ return this.get(name, defaultEmpty)[propName] === 0;
7075
7075
  };
7076
7076
  proto[`${name}Len`] = function() {
7077
- return this.get(name, defaultEmpty).size;
7077
+ return this.get(name, defaultEmpty)[propName];
7078
7078
  };
7079
7079
  }
7080
7080
  var EMPTY_SET2 = Set2();
@@ -7086,14 +7086,11 @@ class FieldSet extends Field {
7086
7086
  }
7087
7087
  extendProtoForType(proto, uname) {
7088
7088
  const { name } = this;
7089
- extendProtoSeq(proto, name, EMPTY_SET2);
7089
+ extendProtoSized(proto, name, uname, EMPTY_SET2);
7090
7090
  proto[`addIn${uname}`] = function(v) {
7091
7091
  return this.set(name, this.get(name).add(v));
7092
7092
  };
7093
- proto[`deleteIn${uname}`] = function(v) {
7094
- return this.set(name, this.get(name).delete(v));
7095
- };
7096
- proto[`removeIn${uname}`] = proto[`deleteIn${uname}`];
7093
+ extendDeleteInAt(proto, name, uname);
7097
7094
  proto[`hasIn${uname}`] = function(v) {
7098
7095
  return this.get(name).has(v);
7099
7096
  };
@@ -7468,9 +7465,8 @@ function* klistEntries(seq) {
7468
7465
  }
7469
7466
  seqInfoByClass.set(KList, ["data-sk", klistEntries]);
7470
7467
  // src/vdom.js
7471
- function isHtmlAttribute(propName) {
7472
- return propName[4] === "-" && (propName[0] === "d" || propName[0] === "a");
7473
- }
7468
+ var isHtmlAttribute = (propName) => propName[4] === "-" && (propName[0] === "d" || propName[0] === "a");
7469
+ var isObject = (v) => v !== null && typeof v === "object";
7474
7470
  function applyProperties(node, props, previous) {
7475
7471
  for (const propName in props) {
7476
7472
  const propValue = props[propName];
@@ -7480,7 +7476,7 @@ function applyProperties(node, props, previous) {
7480
7476
  node.setAttribute(propName, propValue);
7481
7477
  } else if (propName === "dangerouslySetInnerHTML") {
7482
7478
  node.innerHTML = propValue.__html ?? "";
7483
- } else if (typeof propValue === "object" && propValue !== null) {
7479
+ } else if (isObject(propValue)) {
7484
7480
  patchObject(node, previous, propName, propValue);
7485
7481
  } else if (propName === "className") {
7486
7482
  node.setAttribute("class", propValue);
@@ -7493,11 +7489,7 @@ function removeProperty(node, propName, previous) {
7493
7489
  const previousValue = previous[propName];
7494
7490
  if (propName === "dangerouslySetInnerHTML") {
7495
7491
  node.innerHTML = "";
7496
- } else if (isHtmlAttribute(propName)) {
7497
- node.removeAttribute(propName);
7498
- } else if (typeof previousValue === "string") {
7499
- if (propName !== "className")
7500
- node[propName] = "";
7492
+ } else if (typeof previousValue === "string" || isHtmlAttribute(propName)) {
7501
7493
  const attrName = propName === "className" ? "class" : propName === "htmlFor" ? "for" : propName;
7502
7494
  node.removeAttribute(attrName);
7503
7495
  } else {
@@ -7506,12 +7498,12 @@ function removeProperty(node, propName, previous) {
7506
7498
  }
7507
7499
  function patchObject(node, previous, propName, propValue) {
7508
7500
  const previousValue = previous?.[propName];
7509
- if (previousValue && typeof previousValue === "object" && Object.getPrototypeOf(previousValue) !== Object.getPrototypeOf(propValue)) {
7501
+ if (isObject(previousValue) && Object.getPrototypeOf(previousValue) !== Object.getPrototypeOf(propValue)) {
7510
7502
  node[propName] = propValue;
7511
7503
  return;
7512
7504
  }
7513
7505
  let current = node[propName];
7514
- if (typeof current !== "object" || current === null) {
7506
+ if (!isObject(current)) {
7515
7507
  node[propName] = {};
7516
7508
  current = node[propName];
7517
7509
  }
@@ -7529,16 +7521,11 @@ class VBase {
7529
7521
  return null;
7530
7522
  }
7531
7523
  }
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
- }
7524
+ var getKey = (child) => child instanceof VNode2 ? child.key : undefined;
7525
+ var isIterable = (obj) => obj != null && typeof obj !== "string" && typeof obj[Symbol.iterator] === "function";
7538
7526
  function addChild(normalizedChildren, child) {
7539
- if (child == null) {
7527
+ if (child == null)
7540
7528
  return;
7541
- }
7542
7529
  if (isIterable(child)) {
7543
7530
  for (const c of child) {
7544
7531
  addChild(normalizedChildren, c);
@@ -7589,9 +7576,8 @@ class VComment extends VBase {
7589
7576
  class VFragment extends VBase {
7590
7577
  constructor(childs) {
7591
7578
  super();
7592
- const normalized = [];
7593
- addChild(normalized, childs);
7594
- this.childs = normalized;
7579
+ this.childs = [];
7580
+ addChild(this.childs, childs);
7595
7581
  }
7596
7582
  get nodeType() {
7597
7583
  return 11;
@@ -7675,7 +7661,7 @@ function diffProps(a, b) {
7675
7661
  }
7676
7662
  const aValue = a[aKey];
7677
7663
  const bValue = b[aKey];
7678
- if (aValue === bValue) {} else if (typeof aValue === "object" && aValue !== null && typeof bValue === "object" && bValue !== null) {
7664
+ if (aValue === bValue) {} else if (isObject(aValue) && isObject(bValue)) {
7679
7665
  if (Object.getPrototypeOf(bValue) !== Object.getPrototypeOf(aValue)) {
7680
7666
  diff ??= {};
7681
7667
  diff[aKey] = bValue;
@@ -7707,14 +7693,15 @@ function replaceNode(domNode, vnode, options) {
7707
7693
  }
7708
7694
  return newNode || domNode;
7709
7695
  }
7696
+ var bothInstanceOf = (a, b, C) => a instanceof C && b instanceof C;
7710
7697
  function morphNode(domNode, source, target, opts) {
7711
7698
  if (source === target || source.isEqualTo(target))
7712
7699
  return domNode;
7713
- if (source instanceof VText && target instanceof VText || source instanceof VComment && target instanceof VComment) {
7700
+ if (bothInstanceOf(source, target, VText) || bothInstanceOf(source, target, VComment)) {
7714
7701
  domNode.data = target.text;
7715
7702
  return domNode;
7716
7703
  }
7717
- if (source instanceof VNode2 && target instanceof VNode2 && source.tag === target.tag && source.namespace === target.namespace && source.key === target.key) {
7704
+ if (bothInstanceOf(source, target, VNode2) && source.tag === target.tag && source.namespace === target.namespace && source.key === target.key) {
7718
7705
  const propsDiff = diffProps(source.attrs, target.attrs);
7719
7706
  if (propsDiff) {
7720
7707
  applyProperties(domNode, propsDiff, source.attrs);
@@ -7724,7 +7711,7 @@ function morphNode(domNode, source, target, opts) {
7724
7711
  }
7725
7712
  return domNode;
7726
7713
  }
7727
- if (source instanceof VFragment && target instanceof VFragment) {
7714
+ if (bothInstanceOf(source, target, VFragment)) {
7728
7715
  morphChildren(domNode, source.childs, target.childs, null, opts);
7729
7716
  return domNode;
7730
7717
  }
@@ -7801,10 +7788,8 @@ function render(vnode, container, options) {
7801
7788
  if (wasFragment === isFragment) {
7802
7789
  const rootNode = wasFragment ? container : cached.dom;
7803
7790
  const newDom = morphNode(rootNode, cached.vnode, vnode, options);
7804
- renderCache.set(container, {
7805
- vnode,
7806
- dom: isFragment ? container : newDom
7807
- });
7791
+ const domToCache = isFragment ? container : newDom;
7792
+ renderCache.set(container, { vnode, dom: domToCache });
7808
7793
  return newDom;
7809
7794
  }
7810
7795
  renderCache.delete(container);
@@ -7813,32 +7798,33 @@ function render(vnode, container, options) {
7813
7798
  if (domNode) {
7814
7799
  container.innerHTML = "";
7815
7800
  container.appendChild(domNode);
7816
- renderCache.set(container, {
7817
- vnode,
7818
- dom: isFragment ? container : domNode
7819
- });
7801
+ const domToCache = isFragment ? container : domNode;
7802
+ renderCache.set(container, { vnode, dom: domToCache });
7820
7803
  }
7821
7804
  return domNode;
7822
7805
  }
7823
7806
  function h(tagName, properties, children) {
7824
7807
  const tag = tagName.toUpperCase();
7825
7808
  const props = {};
7826
- let key;
7827
- let namespace;
7809
+ let key, namespace;
7828
7810
  if (properties) {
7829
7811
  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];
7812
+ const propVal = properties[propName];
7813
+ switch (propName) {
7814
+ case "key":
7815
+ key = propVal;
7816
+ break;
7817
+ case "namespace":
7818
+ namespace = propVal;
7819
+ break;
7820
+ case "class":
7821
+ props.className = propVal;
7822
+ break;
7823
+ case "for":
7824
+ props.htmlFor = propVal;
7825
+ break;
7826
+ default:
7827
+ props[propName] = isHtmlAttribute(propName) ? String(propVal) : propVal;
7842
7828
  }
7843
7829
  }
7844
7830
  }