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