sprae 9.0.1 → 9.1.1
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/core.js +18 -21
- package/directive/aria.js +2 -3
- package/directive/class.js +3 -3
- package/directive/data.js +2 -4
- package/directive/default.js +8 -4
- package/directive/each.js +39 -39
- package/directive/fx.js +2 -3
- package/directive/html.js +3 -3
- package/directive/if.js +2 -4
- package/directive/ref.js +4 -3
- package/directive/scope.js +2 -3
- package/directive/style.js +3 -3
- package/directive/text.js +2 -3
- package/directive/value.js +2 -4
- package/dist/sprae.js +237 -492
- package/dist/sprae.min.js +1 -1
- package/package.json +5 -4
- package/readme.md +63 -40
- package/signal.js +43 -0
- package/sprae.js +17 -2
package/core.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import swapdom from 'swapdom'
|
|
2
|
-
import * as signals from 'ulive'
|
|
3
|
-
import justin from 'subscript/justin'
|
|
4
|
-
|
|
5
1
|
// polyfill
|
|
6
2
|
const _dispose = (Symbol.dispose ||= Symbol("dispose"));
|
|
7
3
|
|
|
@@ -9,7 +5,7 @@ const _dispose = (Symbol.dispose ||= Symbol("dispose"));
|
|
|
9
5
|
const SPRAE = `∴`
|
|
10
6
|
|
|
11
7
|
// signals impl
|
|
12
|
-
export let
|
|
8
|
+
export let signal, effect, batch, computed, untracked
|
|
13
9
|
|
|
14
10
|
// reserved directives - order matters!
|
|
15
11
|
export const directive = {};
|
|
@@ -49,7 +45,9 @@ export default function sprae(container, values) {
|
|
|
49
45
|
|
|
50
46
|
// NOTE: secondary directives don't stop flow nor extend state, so no need to check
|
|
51
47
|
for (let name of names) {
|
|
52
|
-
let
|
|
48
|
+
let dir = directive[name] || directive.default
|
|
49
|
+
let evaluate = (dir.parse || parse)(attr.value, parse)
|
|
50
|
+
let update = dir(el, evaluate, state, name);
|
|
53
51
|
if (update) {
|
|
54
52
|
update[_dispose] = effect(update);
|
|
55
53
|
effects.push(update);
|
|
@@ -83,6 +81,7 @@ export default function sprae(container, values) {
|
|
|
83
81
|
while (effects.length) effects.pop()[_dispose]();
|
|
84
82
|
container.classList.remove(SPRAE)
|
|
85
83
|
memo.delete(container);
|
|
84
|
+
// NOTE: each child disposes own children etc.
|
|
86
85
|
let els = container.getElementsByClassName(SPRAE);
|
|
87
86
|
while (els.length) els[0][_dispose]?.()
|
|
88
87
|
}
|
|
@@ -93,29 +92,26 @@ export default function sprae(container, values) {
|
|
|
93
92
|
// default compiler
|
|
94
93
|
const evalMemo = {};
|
|
95
94
|
|
|
96
|
-
|
|
97
|
-
if (
|
|
95
|
+
const parse = (expr, dir, fn) => {
|
|
96
|
+
if (fn = evalMemo[expr = expr.trim()]) return fn
|
|
98
97
|
|
|
99
98
|
// static-time errors
|
|
100
|
-
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
catch (e) { throw Object.assign(e, { message: `${SPRAE} ${e.message}\n\n${dir}${expr ? `="${expr}"\n\n` : ""}`, expr }) }
|
|
99
|
+
try { fn = compile(expr); }
|
|
100
|
+
catch (e) { throw Object.assign(e, { message: `∴ ${e.message}\n\n${dir}${expr ? `="${expr}"\n\n` : ""}`, expr }) }
|
|
101
|
+
|
|
102
|
+
fn.expr = expr
|
|
105
103
|
|
|
106
104
|
// runtime errors
|
|
107
|
-
return evalMemo[expr] =
|
|
105
|
+
return evalMemo[expr] = fn
|
|
108
106
|
}
|
|
109
107
|
|
|
110
|
-
//
|
|
111
|
-
export let
|
|
108
|
+
// compiler
|
|
109
|
+
export let compile
|
|
112
110
|
|
|
113
|
-
//
|
|
114
|
-
export
|
|
115
|
-
return v?.replace ? v.replace(/\$<([^>]+)>/g, (match, field) => state[field]?.valueOf?.() ?? '') : v
|
|
116
|
-
};
|
|
111
|
+
// DOM swapper
|
|
112
|
+
export let swap
|
|
117
113
|
|
|
118
|
-
// configure signals/
|
|
114
|
+
// configure signals/compile/differ
|
|
119
115
|
// it's more compact than using sprae.signal = signal etc.
|
|
120
116
|
sprae.use = s => {
|
|
121
117
|
s.signal && (
|
|
@@ -126,4 +122,5 @@ sprae.use = s => {
|
|
|
126
122
|
untracked = s.untracked || batch
|
|
127
123
|
);
|
|
128
124
|
s.swap && (swap = s.swap)
|
|
125
|
+
s.compile && (compile = s.compile)
|
|
129
126
|
}
|
package/directive/aria.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
2
|
import { attr, dashcase } from './default.js'
|
|
3
3
|
|
|
4
|
-
directive['aria'] = (el,
|
|
5
|
-
let evaluate = compile(expr, 'aria')
|
|
4
|
+
directive['aria'] = (el, evaluate, state) => {
|
|
6
5
|
const update = (value) => {
|
|
7
6
|
for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
|
|
8
7
|
}
|
package/directive/class.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
|
+
import { ipol } from './default.js';
|
|
2
3
|
|
|
3
|
-
directive.class = (el,
|
|
4
|
-
let evaluate = compile(expr, 'class');
|
|
4
|
+
directive.class = (el, evaluate, state) => {
|
|
5
5
|
let cur = new Set
|
|
6
6
|
return () => {
|
|
7
7
|
let v = evaluate(state);
|
package/directive/data.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { directive
|
|
2
|
-
|
|
3
|
-
directive['data'] = (el, expr, state) => {
|
|
4
|
-
let evaluate = compile(expr, 'data')
|
|
1
|
+
import { directive } from "../core.js";
|
|
5
2
|
|
|
3
|
+
directive['data'] = (el, evaluate, state) => {
|
|
6
4
|
return () => {
|
|
7
5
|
let value = evaluate(state)?.valueOf()
|
|
8
6
|
for (let key in value) el.dataset[key] = value[key];
|
package/directive/default.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
2
|
|
|
3
3
|
// set generic property directive
|
|
4
|
-
directive.default = (el,
|
|
4
|
+
directive.default = (el, evaluate, state, name) => {
|
|
5
5
|
let evt = name.startsWith("on") && name.slice(2);
|
|
6
|
-
let evaluate = compile(expr, name);
|
|
7
6
|
|
|
8
7
|
if (evt) {
|
|
9
8
|
let off
|
|
10
9
|
return () => (
|
|
11
10
|
off?.(), // intermediate teardown
|
|
12
|
-
off = on(el, evt, evaluate(state))
|
|
11
|
+
off = on(el, evt, evaluate(state)?.valueOf())
|
|
13
12
|
);
|
|
14
13
|
}
|
|
15
14
|
|
|
@@ -146,3 +145,8 @@ const debounce = (fn, wait) => {
|
|
|
146
145
|
export const dashcase = (str) => {
|
|
147
146
|
return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => "-" + match.toLowerCase());
|
|
148
147
|
}
|
|
148
|
+
|
|
149
|
+
// interpolate a$<b> fields from context
|
|
150
|
+
export const ipol = (v, state) => {
|
|
151
|
+
return v?.replace ? v.replace(/\$<([^>]+)>/g, (match, field) => state[field]?.valueOf?.() ?? '') : v
|
|
152
|
+
};
|
package/directive/each.js
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import sprae, { directive,
|
|
1
|
+
import sprae, { directive, swap } from "../core.js";
|
|
2
2
|
|
|
3
3
|
export const _each = Symbol(":each");
|
|
4
4
|
|
|
5
|
-
const keys = {}
|
|
5
|
+
const keys = {}, _key = Symbol('key');
|
|
6
6
|
|
|
7
7
|
// :each must init before :ref, :id or any others, since it defines scope
|
|
8
|
-
directive.each = (tpl,
|
|
9
|
-
let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
|
|
10
|
-
let [itemVar, idxVar = "_$"] = leftSide.split(/\s*,\s*/);
|
|
11
|
-
|
|
8
|
+
(directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
|
|
12
9
|
// we need :if to be able to replace holder instead of tpl for :if :each case
|
|
13
|
-
const holder = (tpl[_each] = document.createTextNode(""));
|
|
10
|
+
const holder = (tpl[_each] = document.createTextNode("")), parent = tpl.parentNode;
|
|
14
11
|
tpl.replaceWith(holder);
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
tpl.removeAttribute(':key')
|
|
13
|
+
// key -> el
|
|
14
|
+
const elCache = new WeakMap, stateCache = new WeakMap
|
|
20
15
|
|
|
21
16
|
let cur = [];
|
|
22
17
|
|
|
18
|
+
const remove = el => {
|
|
19
|
+
el.remove()
|
|
20
|
+
el[Symbol.dispose]?.()
|
|
21
|
+
if (el[_key]) {
|
|
22
|
+
elCache.delete(el[_key])
|
|
23
|
+
stateCache.delete(el[_key])
|
|
24
|
+
}
|
|
25
|
+
}, { insert, replace } = swap
|
|
26
|
+
|
|
27
|
+
const options = { remove, insert, replace }
|
|
28
|
+
|
|
29
|
+
// naive approach: whenever items change we replace full list
|
|
23
30
|
return () => {
|
|
24
|
-
// naive approach: whenever items change we replace full list
|
|
25
31
|
let items = evaluate(state)?.valueOf(), els = [];
|
|
26
|
-
if (typeof items === "number") items = Array.from({ length: items }, (_, i) => i);
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
for (let idx in items) {
|
|
30
|
-
let item = items[idx]
|
|
31
|
-
// creating via prototype is faster in both creation time & reading time
|
|
32
|
-
let substate = Object.create(state, { [idxVar]: { value: idx } });
|
|
33
|
-
substate[itemVar] = item; // can be changed by subsequent updates, need to be writable
|
|
34
|
-
let el, key = (item.key ?? item.id ?? item); // NOTE: no need to unwrap singnal, since item fallback covers it
|
|
33
|
+
if (typeof items === "number") items = Array.from({ length: items }, (_, i) => i)
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
// let c = 0, inc = () => { if (c++ > 100) throw 'Inf recursion' }
|
|
36
|
+
const count = new WeakMap
|
|
37
|
+
for (let idx in items) {
|
|
38
|
+
let el, item = items[idx], key = item?.key ?? item?.id ?? item ?? idx
|
|
39
|
+
key = (Object(key) !== key) ? (keys[key] ||= Object(key)) : item
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
count.add(key);
|
|
46
|
-
el = memo.get(key) || memo.set(key, tpl.cloneNode(true)).get(key);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
41
|
+
if (key == null || count.has(key) || tpl.content) el = (tpl.content || tpl).cloneNode(true)
|
|
42
|
+
else count.set(key, 1), (el = elCache.get(key) || (elCache.set(key, tpl.cloneNode(true)), elCache.get(key)))[_key] = key;
|
|
49
43
|
|
|
50
|
-
|
|
44
|
+
// creating via prototype is faster in both creation time & reading time
|
|
45
|
+
let substate = stateCache.get(key) || (stateCache.set(key, Object.create(state, { [idxVar]: { value: idx } })), stateCache.get(key));
|
|
46
|
+
substate[itemVar] = item; // can be changed by subsequent updates, need to be writable
|
|
51
47
|
|
|
52
|
-
sprae(el, substate)
|
|
48
|
+
sprae(el, substate);
|
|
53
49
|
|
|
54
50
|
// document fragment
|
|
55
|
-
if (el.nodeType === 11) els.push(...el.childNodes);
|
|
56
|
-
else els.push(el);
|
|
51
|
+
if (el.nodeType === 11) els.push(...el.childNodes); else els.push(el);
|
|
57
52
|
}
|
|
58
53
|
|
|
59
|
-
swap(
|
|
60
|
-
}
|
|
61
|
-
}
|
|
54
|
+
swap(parent, cur, cur = els, holder, options);
|
|
55
|
+
}
|
|
56
|
+
}).parse = (expr, parse) => {
|
|
57
|
+
let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
|
|
58
|
+
let [itemVar, idxVar = "$"] = leftSide.split(/\s*,\s*/);
|
|
59
|
+
|
|
60
|
+
return [itemVar, idxVar, parse(itemsExpr)]
|
|
61
|
+
}
|
package/directive/fx.js
CHANGED
package/directive/html.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import sprae, { directive
|
|
1
|
+
import sprae, { directive } from "../core.js";
|
|
2
2
|
|
|
3
|
-
directive.html = (el,
|
|
4
|
-
let
|
|
3
|
+
directive.html = (el, evaluate, state) => {
|
|
4
|
+
let tpl = evaluate(state);
|
|
5
5
|
|
|
6
6
|
if (!tpl) return
|
|
7
7
|
|
package/directive/if.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import sprae, {
|
|
1
|
+
import sprae, { directive, swap } from "../core.js";
|
|
2
2
|
import { _each } from './each.js';
|
|
3
3
|
|
|
4
4
|
// :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
|
|
5
5
|
// as for :if :scope - :if must init first, since it is lazy, to avoid initializing component ahead of time by :scope
|
|
6
6
|
// we consider :scope={x} :if={x} case insignificant
|
|
7
7
|
const _prevIf = Symbol("if");
|
|
8
|
-
directive.if = (ifEl,
|
|
8
|
+
directive.if = (ifEl, evaluate, state) => {
|
|
9
9
|
let parent = ifEl.parentNode,
|
|
10
10
|
next = ifEl.nextElementSibling,
|
|
11
11
|
holder = document.createTextNode(''),
|
|
12
12
|
|
|
13
|
-
evaluate = compile(expr, name),
|
|
14
|
-
|
|
15
13
|
// actual replaceable els (takes <template>)
|
|
16
14
|
cur, ifs, elses, none = [];
|
|
17
15
|
|
package/directive/ref.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
|
+
import { ipol } from './default.js';
|
|
2
3
|
|
|
3
4
|
// ref must be last within primaries, since that must be skipped by :each, but before secondaries
|
|
4
|
-
directive.ref = (el, expr, state) => {
|
|
5
|
+
(directive.ref = (el, expr, state) => {
|
|
5
6
|
let prev;
|
|
6
7
|
return () => {
|
|
7
8
|
if (prev) delete state[prev]
|
|
8
9
|
state[prev = ipol(expr, state)] = el;
|
|
9
10
|
}
|
|
10
|
-
}
|
|
11
|
+
}).parse = expr => expr
|
package/directive/scope.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import sprae, { directive
|
|
1
|
+
import sprae, { directive } from "../core.js";
|
|
2
2
|
|
|
3
3
|
// `:each` can redefine scope as `:each="a in {myScope}"`,
|
|
4
4
|
// same time per-item scope as `:each="..." :scope="{collapsed:true}"` is useful
|
|
5
|
-
directive.scope = (el,
|
|
6
|
-
let evaluate = compile(expr, name);
|
|
5
|
+
directive.scope = (el, evaluate, rootState) => {
|
|
7
6
|
// local state may contain signals that update, so we take them over
|
|
8
7
|
return () => {
|
|
9
8
|
sprae(el, { ...rootState, ...(evaluate(rootState)?.valueOf?.() || {}) });
|
package/directive/style.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
|
+
import { ipol } from './default.js';
|
|
2
3
|
|
|
3
|
-
directive.style = (el,
|
|
4
|
-
let evaluate = compile(expr, 'style');
|
|
4
|
+
directive.style = (el, evaluate, state) => {
|
|
5
5
|
let initStyle = el.getAttribute("style") || "";
|
|
6
6
|
if (!initStyle.endsWith(";")) initStyle += "; ";
|
|
7
7
|
|
package/directive/text.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
2
|
|
|
3
3
|
// set text content
|
|
4
|
-
directive.text = (el,
|
|
5
|
-
let evaluate = compile(expr, 'text');
|
|
4
|
+
directive.text = (el, evaluate, state) => {
|
|
6
5
|
if (el.content) el.replaceWith(el = document.createTextNode('')) // <template :text="abc"/>
|
|
7
6
|
|
|
8
7
|
return () => {
|
package/directive/value.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
2
|
import { attr } from './default.js';
|
|
3
3
|
|
|
4
4
|
// connect expr to element value
|
|
5
|
-
directive.value = (el,
|
|
6
|
-
let evaluate = compile(expr, 'value');
|
|
7
|
-
|
|
5
|
+
directive.value = (el, evaluate, state) => {
|
|
8
6
|
let from, to;
|
|
9
7
|
let update = el.type === "text" || el.type === ""
|
|
10
8
|
? (value) => el.setAttribute("value", (el.value = value == null ? "" : value))
|