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 +70 -1
- package/dist/tutuca-dev.js +57 -71
- package/dist/tutuca-dev.min.js +3 -3
- package/dist/tutuca-extra.js +57 -71
- package/dist/tutuca-extra.min.js +3 -3
- package/dist/tutuca.js +57 -71
- package/dist/tutuca.min.js +3 -3
- package/package.json +22 -1
package/README.md
CHANGED
|
@@ -1 +1,70 @@
|
|
|
1
|
-
#
|
|
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)
|
package/dist/tutuca-dev.js
CHANGED
|
@@ -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,
|
|
6963
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
proto[`deleteIn${uname}
|
|
7032
|
-
|
|
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
|
|
7073
|
-
proto[
|
|
7074
|
-
return this.get(name, defaultEmpty)
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
7472
|
-
|
|
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 (
|
|
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 &&
|
|
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 (
|
|
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
|
-
|
|
7533
|
-
|
|
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
|
-
|
|
7593
|
-
addChild(
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
7805
|
-
|
|
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
|
-
|
|
7817
|
-
|
|
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
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
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
|
}
|