tutuca 0.9.6 → 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) {
@@ -2503,18 +2507,10 @@ class ParseCtxClassSetCollector extends ParseContext {
2503
2507
  }
2504
2508
  const { value, thenVal, elseVal } = attr;
2505
2509
  if (thenVal !== undefined) {
2506
- this._addClasses(thenVal.value);
2507
- if (typeof elseVal?.value === "string") {
2508
- this._addClasses(elseVal.value);
2509
- }
2510
- } else if (typeof value?.value === "string") {
2511
- this._addClasses(value.value);
2512
- } else if (value?.vals !== undefined) {
2513
- for (const val of value.vals) {
2514
- if (val instanceof ConstVal && val.value !== "") {
2515
- this._addClasses(val.value);
2516
- }
2517
- }
2510
+ this._maybeAddVal(thenVal);
2511
+ this._maybeAddVal(elseVal);
2512
+ } else {
2513
+ this._maybeAddVal(value);
2518
2514
  }
2519
2515
  }
2520
2516
  } else {
@@ -2524,6 +2520,22 @@ class ParseCtxClassSetCollector extends ParseContext {
2524
2520
  }
2525
2521
  }
2526
2522
  }
2523
+ _maybeAddVal(value) {
2524
+ if (!this._maybeAddStrTpl(value) && typeof value?.value === "string") {
2525
+ this._addClasses(value.value);
2526
+ }
2527
+ }
2528
+ _maybeAddStrTpl(value) {
2529
+ if (value?.vals !== undefined) {
2530
+ for (const val of value.vals) {
2531
+ if (val instanceof ConstVal && val.value !== "") {
2532
+ this._addClasses(val.value);
2533
+ }
2534
+ }
2535
+ return true;
2536
+ }
2537
+ return false;
2538
+ }
2527
2539
  }
2528
2540
 
2529
2541
  // deps/immutable.js
@@ -6951,14 +6963,8 @@ class FieldString extends Field {
6951
6963
  constructor(name, defaultValue = "") {
6952
6964
  super("text", name, CHECK_TYPE_STRING, stringCoercer, defaultValue);
6953
6965
  }
6954
- extendProtoForType(proto, _uname) {
6955
- const { name } = this;
6956
- proto[`${name}IsEmpty`] = function() {
6957
- return this.get(name, "").length === 0;
6958
- };
6959
- proto[`${name}Len`] = function() {
6960
- return this.get(name, "").length;
6961
- };
6966
+ extendProtoForType(proto, uname) {
6967
+ extendProtoSized(proto, this.name, uname, "", "length");
6962
6968
  }
6963
6969
  }
6964
6970
  var intCoercer = (v) => Number.isFinite(v) ? Math.trunc(v) : null;
@@ -7001,7 +7007,7 @@ class FieldComp extends Field {
7001
7007
  }
7002
7008
  var NONE2 = Symbol("NONE");
7003
7009
  function extendProtoForKeyed(proto, name, uname) {
7004
- extendProtoSeq(proto, name, EMPTY_LIST);
7010
+ extendProtoSized(proto, name, uname, EMPTY_LIST);
7005
7011
  proto[`setIn${uname}At`] = function(i, v) {
7006
7012
  return this.set(name, this.get(name).set(i, v));
7007
7013
  };
@@ -7017,11 +7023,13 @@ function extendProtoForKeyed(proto, name, uname) {
7017
7023
  console.warn("key", i, "not found in", name, col);
7018
7024
  return this;
7019
7025
  };
7020
- function deleteInAt(i) {
7021
- return this.set(name, this.get(name).delete(i));
7022
- }
7023
- proto[`deleteIn${uname}At`] = deleteInAt;
7024
- 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}`];
7025
7033
  }
7026
7034
  var EMPTY_LIST = List();
7027
7035
  var listCoercer = (v) => Array.isArray(v) ? List(v) : null;
@@ -7061,12 +7069,12 @@ class FieldOMap extends Field {
7061
7069
  extendProtoForKeyed(proto, this.name, uname);
7062
7070
  }
7063
7071
  }
7064
- function extendProtoSeq(proto, name, defaultEmpty) {
7065
- proto[`${name}IsEmpty`] = function() {
7066
- 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;
7067
7075
  };
7068
7076
  proto[`${name}Len`] = function() {
7069
- return this.get(name, defaultEmpty).size;
7077
+ return this.get(name, defaultEmpty)[propName];
7070
7078
  };
7071
7079
  }
7072
7080
  var EMPTY_SET2 = Set2();
@@ -7078,14 +7086,11 @@ class FieldSet extends Field {
7078
7086
  }
7079
7087
  extendProtoForType(proto, uname) {
7080
7088
  const { name } = this;
7081
- extendProtoSeq(proto, name, EMPTY_SET2);
7089
+ extendProtoSized(proto, name, uname, EMPTY_SET2);
7082
7090
  proto[`addIn${uname}`] = function(v) {
7083
7091
  return this.set(name, this.get(name).add(v));
7084
7092
  };
7085
- proto[`deleteIn${uname}`] = function(v) {
7086
- return this.set(name, this.get(name).delete(v));
7087
- };
7088
- proto[`removeIn${uname}`] = proto[`deleteIn${uname}`];
7093
+ extendDeleteInAt(proto, name, uname);
7089
7094
  proto[`hasIn${uname}`] = function(v) {
7090
7095
  return this.get(name).has(v);
7091
7096
  };
@@ -7460,9 +7465,8 @@ function* klistEntries(seq) {
7460
7465
  }
7461
7466
  seqInfoByClass.set(KList, ["data-sk", klistEntries]);
7462
7467
  // src/vdom.js
7463
- function isHtmlAttribute(propName) {
7464
- return propName[4] === "-" && (propName[0] === "d" || propName[0] === "a");
7465
- }
7468
+ var isHtmlAttribute = (propName) => propName[4] === "-" && (propName[0] === "d" || propName[0] === "a");
7469
+ var isObject = (v) => v !== null && typeof v === "object";
7466
7470
  function applyProperties(node, props, previous) {
7467
7471
  for (const propName in props) {
7468
7472
  const propValue = props[propName];
@@ -7472,7 +7476,7 @@ function applyProperties(node, props, previous) {
7472
7476
  node.setAttribute(propName, propValue);
7473
7477
  } else if (propName === "dangerouslySetInnerHTML") {
7474
7478
  node.innerHTML = propValue.__html ?? "";
7475
- } else if (typeof propValue === "object" && propValue !== null) {
7479
+ } else if (isObject(propValue)) {
7476
7480
  patchObject(node, previous, propName, propValue);
7477
7481
  } else if (propName === "className") {
7478
7482
  node.setAttribute("class", propValue);
@@ -7485,11 +7489,7 @@ function removeProperty(node, propName, previous) {
7485
7489
  const previousValue = previous[propName];
7486
7490
  if (propName === "dangerouslySetInnerHTML") {
7487
7491
  node.innerHTML = "";
7488
- } else if (isHtmlAttribute(propName)) {
7489
- node.removeAttribute(propName);
7490
- } else if (typeof previousValue === "string") {
7491
- if (propName !== "className")
7492
- node[propName] = "";
7492
+ } else if (typeof previousValue === "string" || isHtmlAttribute(propName)) {
7493
7493
  const attrName = propName === "className" ? "class" : propName === "htmlFor" ? "for" : propName;
7494
7494
  node.removeAttribute(attrName);
7495
7495
  } else {
@@ -7498,12 +7498,12 @@ function removeProperty(node, propName, previous) {
7498
7498
  }
7499
7499
  function patchObject(node, previous, propName, propValue) {
7500
7500
  const previousValue = previous?.[propName];
7501
- if (previousValue && typeof previousValue === "object" && Object.getPrototypeOf(previousValue) !== Object.getPrototypeOf(propValue)) {
7501
+ if (isObject(previousValue) && Object.getPrototypeOf(previousValue) !== Object.getPrototypeOf(propValue)) {
7502
7502
  node[propName] = propValue;
7503
7503
  return;
7504
7504
  }
7505
7505
  let current = node[propName];
7506
- if (typeof current !== "object" || current === null) {
7506
+ if (!isObject(current)) {
7507
7507
  node[propName] = {};
7508
7508
  current = node[propName];
7509
7509
  }
@@ -7521,16 +7521,11 @@ class VBase {
7521
7521
  return null;
7522
7522
  }
7523
7523
  }
7524
- function getKey(child) {
7525
- return child instanceof VNode2 ? child.key : undefined;
7526
- }
7527
- function isIterable(obj) {
7528
- return obj != null && typeof obj !== "string" && typeof obj[Symbol.iterator] === "function";
7529
- }
7524
+ var getKey = (child) => child instanceof VNode2 ? child.key : undefined;
7525
+ var isIterable = (obj) => obj != null && typeof obj !== "string" && typeof obj[Symbol.iterator] === "function";
7530
7526
  function addChild(normalizedChildren, child) {
7531
- if (child == null) {
7527
+ if (child == null)
7532
7528
  return;
7533
- }
7534
7529
  if (isIterable(child)) {
7535
7530
  for (const c of child) {
7536
7531
  addChild(normalizedChildren, c);
@@ -7581,9 +7576,8 @@ class VComment extends VBase {
7581
7576
  class VFragment extends VBase {
7582
7577
  constructor(childs) {
7583
7578
  super();
7584
- const normalized = [];
7585
- addChild(normalized, childs);
7586
- this.childs = normalized;
7579
+ this.childs = [];
7580
+ addChild(this.childs, childs);
7587
7581
  }
7588
7582
  get nodeType() {
7589
7583
  return 11;
@@ -7667,7 +7661,7 @@ function diffProps(a, b) {
7667
7661
  }
7668
7662
  const aValue = a[aKey];
7669
7663
  const bValue = b[aKey];
7670
- 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)) {
7671
7665
  if (Object.getPrototypeOf(bValue) !== Object.getPrototypeOf(aValue)) {
7672
7666
  diff ??= {};
7673
7667
  diff[aKey] = bValue;
@@ -7699,14 +7693,15 @@ function replaceNode(domNode, vnode, options) {
7699
7693
  }
7700
7694
  return newNode || domNode;
7701
7695
  }
7696
+ var bothInstanceOf = (a, b, C) => a instanceof C && b instanceof C;
7702
7697
  function morphNode(domNode, source, target, opts) {
7703
7698
  if (source === target || source.isEqualTo(target))
7704
7699
  return domNode;
7705
- if (source instanceof VText && target instanceof VText || source instanceof VComment && target instanceof VComment) {
7700
+ if (bothInstanceOf(source, target, VText) || bothInstanceOf(source, target, VComment)) {
7706
7701
  domNode.data = target.text;
7707
7702
  return domNode;
7708
7703
  }
7709
- 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) {
7710
7705
  const propsDiff = diffProps(source.attrs, target.attrs);
7711
7706
  if (propsDiff) {
7712
7707
  applyProperties(domNode, propsDiff, source.attrs);
@@ -7716,7 +7711,7 @@ function morphNode(domNode, source, target, opts) {
7716
7711
  }
7717
7712
  return domNode;
7718
7713
  }
7719
- if (source instanceof VFragment && target instanceof VFragment) {
7714
+ if (bothInstanceOf(source, target, VFragment)) {
7720
7715
  morphChildren(domNode, source.childs, target.childs, null, opts);
7721
7716
  return domNode;
7722
7717
  }
@@ -7793,10 +7788,8 @@ function render(vnode, container, options) {
7793
7788
  if (wasFragment === isFragment) {
7794
7789
  const rootNode = wasFragment ? container : cached.dom;
7795
7790
  const newDom = morphNode(rootNode, cached.vnode, vnode, options);
7796
- renderCache.set(container, {
7797
- vnode,
7798
- dom: isFragment ? container : newDom
7799
- });
7791
+ const domToCache = isFragment ? container : newDom;
7792
+ renderCache.set(container, { vnode, dom: domToCache });
7800
7793
  return newDom;
7801
7794
  }
7802
7795
  renderCache.delete(container);
@@ -7805,32 +7798,33 @@ function render(vnode, container, options) {
7805
7798
  if (domNode) {
7806
7799
  container.innerHTML = "";
7807
7800
  container.appendChild(domNode);
7808
- renderCache.set(container, {
7809
- vnode,
7810
- dom: isFragment ? container : domNode
7811
- });
7801
+ const domToCache = isFragment ? container : domNode;
7802
+ renderCache.set(container, { vnode, dom: domToCache });
7812
7803
  }
7813
7804
  return domNode;
7814
7805
  }
7815
7806
  function h(tagName, properties, children) {
7816
7807
  const tag = tagName.toUpperCase();
7817
7808
  const props = {};
7818
- let key;
7819
- let namespace;
7809
+ let key, namespace;
7820
7810
  if (properties) {
7821
7811
  for (const propName in properties) {
7822
- if (propName === "key") {
7823
- key = properties[propName];
7824
- } else if (propName === "namespace") {
7825
- namespace = properties[propName];
7826
- } else if (propName === "class") {
7827
- props.className = properties[propName];
7828
- } else if (propName === "for") {
7829
- props.htmlFor = properties[propName];
7830
- } else if (isHtmlAttribute(propName)) {
7831
- props[propName] = String(properties[propName]);
7832
- } else {
7833
- 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;
7834
7828
  }
7835
7829
  }
7836
7830
  }