sprae 12.2.4 → 12.3.0
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 +49 -145
- package/directive/_.js +3 -0
- package/directive/class.js +9 -5
- package/directive/each.js +9 -11
- package/directive/else.js +2 -2
- package/directive/event.js +17 -0
- package/directive/if.js +3 -6
- package/directive/ref.js +6 -5
- package/directive/scope.js +7 -7
- package/directive/sequence.js +35 -0
- package/directive/style.js +11 -6
- package/directive/text.js +1 -1
- package/directive/value.js +1 -1
- package/dist/sprae.js +3 -3
- package/dist/sprae.js.map +4 -4
- package/dist/sprae.micro.js +5 -0
- package/dist/sprae.micro.js.map +7 -0
- package/dist/sprae.umd.js +3 -3
- package/dist/sprae.umd.js.map +4 -4
- package/micro.js +55 -1
- package/package.json +3 -2
- package/readme.md +16 -690
- package/signal.js +12 -11
- package/sprae.js +92 -58
- package/store.js +1 -1
- package/directive/default.js +0 -3
package/signal.js
CHANGED
|
@@ -6,7 +6,7 @@ let current, depth = 0, batched;
|
|
|
6
6
|
export const signal = (v, _s, _obs = new Set, _v = () => _s.value) => (
|
|
7
7
|
_s = {
|
|
8
8
|
get value() {
|
|
9
|
-
current?.deps.
|
|
9
|
+
current?.deps.add(_obs.add(current));
|
|
10
10
|
return v
|
|
11
11
|
},
|
|
12
12
|
set value(val) {
|
|
@@ -15,23 +15,24 @@ export const signal = (v, _s, _obs = new Set, _v = () => _s.value) => (
|
|
|
15
15
|
for (let sub of _obs) batched ? batched.add(sub) : sub(); // notify effects
|
|
16
16
|
},
|
|
17
17
|
peek() { return v },
|
|
18
|
-
toJSON: _v,
|
|
18
|
+
toJSON: _v, toString: _v, valueOf: _v
|
|
19
19
|
}
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
export const effect = (fn, _teardown, _fx, _deps
|
|
22
|
+
export const effect = (fn, _teardown, _fx, _deps) => (
|
|
23
23
|
_fx = (prev) => {
|
|
24
|
-
|
|
24
|
+
let tmp = _teardown;
|
|
25
25
|
_teardown = null; // we null _teardown to avoid repeated call in case of recursive update
|
|
26
|
-
|
|
26
|
+
tmp?.call?.();
|
|
27
27
|
prev = current, current = _fx
|
|
28
28
|
if (depth++ > 10) throw 'Cycle detected';
|
|
29
|
-
try { _teardown = fn()
|
|
29
|
+
try { _teardown = fn() } finally { current = prev; depth-- }
|
|
30
30
|
},
|
|
31
|
-
|
|
31
|
+
_fx.fn = fn,
|
|
32
|
+
_deps = _fx.deps = new Set(),
|
|
32
33
|
|
|
33
34
|
_fx(),
|
|
34
|
-
(dep) => { _teardown?.call?.();
|
|
35
|
+
(dep) => { _teardown?.call?.(); for (dep of _deps) dep.delete(_fx); _deps.clear() }
|
|
35
36
|
)
|
|
36
37
|
|
|
37
38
|
export const computed = (fn, _s = signal(), _c, _e, _v = () => _c.value) => (
|
|
@@ -41,14 +42,14 @@ export const computed = (fn, _s = signal(), _c, _e, _v = () => _c.value) => (
|
|
|
41
42
|
return _s.value
|
|
42
43
|
},
|
|
43
44
|
peek: _s.peek,
|
|
44
|
-
toJSON: _v,
|
|
45
|
+
toJSON: _v, toString: _v, valueOf: _v
|
|
45
46
|
}
|
|
46
47
|
)
|
|
47
48
|
|
|
48
|
-
export const batch = (fn, _first = !batched) => {
|
|
49
|
+
export const batch = (fn, _first = !batched, _list) => {
|
|
49
50
|
batched ??= new Set;
|
|
50
51
|
try { fn(); }
|
|
51
|
-
finally { if (_first) { for (const fx of
|
|
52
|
+
finally { if (_first) { [batched, _list] = [null, batched]; for (const fx of _list) fx(); } }
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
export const untracked = (fn, _prev, _v) => (_prev = current, current = null, _v = fn(), current = _prev, _v)
|
package/sprae.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import store from "./store.js";
|
|
2
|
-
import { batch, computed, effect, signal, untracked } from './
|
|
3
|
-
import
|
|
2
|
+
import { batch, computed, effect, signal, untracked } from './core.js';
|
|
3
|
+
import * as signals from './signal.js';
|
|
4
|
+
import sprae, { use, decorate, directive, modifier, parse, throttle, debounce, _off, _state, _on, _dispose, _add, call } from './core.js';
|
|
5
|
+
import pkg from './package.json' with { type: 'json' };
|
|
4
6
|
|
|
5
7
|
import _if from "./directive/if.js";
|
|
6
8
|
import _else from "./directive/else.js";
|
|
@@ -12,71 +14,64 @@ import _value from "./directive/value.js";
|
|
|
12
14
|
import _ref from "./directive/ref.js";
|
|
13
15
|
import _scope from "./directive/scope.js";
|
|
14
16
|
import _each from "./directive/each.js";
|
|
15
|
-
import _default from "./directive/
|
|
17
|
+
import _default from "./directive/_.js";
|
|
16
18
|
import _spread from "./directive/spread.js";
|
|
19
|
+
import _event from "./directive/event.js";
|
|
20
|
+
import _seq from "./directive/sequence.js";
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
Object.assign(directive, {
|
|
20
|
-
|
|
21
|
-
'*': _default,
|
|
22
|
-
|
|
23
|
-
// FIXME
|
|
24
|
-
// 'on*': _on,
|
|
25
|
-
|
|
26
|
-
// :="{a,b,c}"
|
|
24
|
+
_: (el, state, expr, name) => (name.startsWith('on') ? _event : _default)(el, state, expr, name),
|
|
27
25
|
'': _spread,
|
|
28
|
-
|
|
29
|
-
// :class="[a, b, c]"
|
|
30
26
|
class: _class,
|
|
31
|
-
|
|
32
|
-
// :text="..."
|
|
33
27
|
text: _text,
|
|
34
|
-
|
|
35
|
-
// :style="..."
|
|
36
28
|
style: _style,
|
|
37
|
-
|
|
38
|
-
// :fx="..."
|
|
39
29
|
fx: _fx,
|
|
40
|
-
|
|
41
|
-
// :value - 2 way binding like x-model
|
|
42
30
|
value: _value,
|
|
43
|
-
|
|
44
|
-
// :ref="..."
|
|
45
31
|
ref: _ref,
|
|
46
|
-
|
|
47
|
-
// :scope creates variables scope for a subtree
|
|
48
32
|
scope: _scope,
|
|
49
|
-
|
|
50
33
|
if: _if,
|
|
51
34
|
else: _else,
|
|
52
|
-
|
|
53
|
-
// :each="v,k in src"
|
|
54
35
|
each: _each
|
|
55
36
|
})
|
|
56
37
|
|
|
57
|
-
Object.assign(modifier, {
|
|
58
|
-
debounce: (fn,
|
|
59
|
-
_how = 250,
|
|
60
|
-
_schedule = _how === "tick" ? queueMicrotask : _how === "raf" ? requestAnimationFrame : _how === "idle" ? requestIdleCallback : ((fn) => setTimeout(fn, _how)),
|
|
61
|
-
_count = 0
|
|
62
|
-
) =>
|
|
63
|
-
debounce(fn, _schedule),
|
|
64
|
-
|
|
65
|
-
throttle: (fn, _how = 250, _schedule = _how === "tick" ? queueMicrotask : _how === "raf" ? requestAnimationFrame : ((fn) => setTimeout(fn, _how))) => (
|
|
66
|
-
throttle(fn, _schedule)
|
|
67
|
-
),
|
|
68
38
|
|
|
69
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Directive initializer (with modifiers support)
|
|
41
|
+
* @type {(el: HTMLElement, name:string, value:string, state:Object) => Function}
|
|
42
|
+
* */
|
|
43
|
+
const dir = (target, name, expr, state) => {
|
|
44
|
+
let [dirName, ...mods] = name.split('.'), create = directive[dirName] || directive._
|
|
70
45
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
prevent: (fn) => (e) => (e?.preventDefault(), fn(e)),
|
|
74
|
-
stop: (fn) => (e) => (e?.stopPropagation(), fn(e)),
|
|
75
|
-
immediate: (fn) => (e) => (e?.stopImmediatePropagation(), fn(e)),
|
|
46
|
+
return () => {
|
|
47
|
+
let update = create(target, state, expr, name)
|
|
76
48
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
49
|
+
if (!update?.call) return update?.[_dispose]
|
|
50
|
+
|
|
51
|
+
// throttle prevents multiple updates within one tick as well as isolates stack for each update
|
|
52
|
+
let trigger = decorate(Object.assign(throttle(() => change.value++), { target }), mods),
|
|
53
|
+
change = signal(0), // signal authorized to trigger effect: 0 = init; >0 = trigger
|
|
54
|
+
count = 0, // called effect count
|
|
55
|
+
evaluate = update.eval ?? parse(expr).bind(target),
|
|
56
|
+
_out, out = () => (_out && call(_out), _out=null) // effect trigger and invoke may happen in the same tick, so it will be effect-within-effect call - we need to store output of evaluate to return from trigger effect
|
|
57
|
+
|
|
58
|
+
state = target[_state] ?? state
|
|
59
|
+
|
|
60
|
+
return effect(() => (
|
|
61
|
+
// if planned count is same as actual count - plan new update, else update right away
|
|
62
|
+
change.value == count ? (trigger()) : (count = change.value, _out = evaluate(state, update)),
|
|
63
|
+
out
|
|
64
|
+
))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Object.assign(modifier, {
|
|
69
|
+
// timing
|
|
70
|
+
debounce: (fn, _how = 250) => debounce(fn, (_how ||= 0, (fn) => setTimeout(fn, _how))),
|
|
71
|
+
throttle: (fn, _how = 250) => throttle(fn, (_how ||= 0, (fn) => setTimeout(fn, _how))),
|
|
72
|
+
tick: (fn) => (e) => (queueMicrotask(() => fn(e))),
|
|
73
|
+
raf: (fn) => (e) => requestAnimationFrame(() => fn(e)),
|
|
74
|
+
once: (fn, _done, _fn) => (_fn = (e) => !_done && (_done = 1, fn(e)), _fn.once = true, _fn),
|
|
80
75
|
|
|
81
76
|
// target
|
|
82
77
|
window: fn => (fn.target = fn.target.ownerDocument.defaultView, fn),
|
|
@@ -84,14 +79,18 @@ Object.assign(modifier, {
|
|
|
84
79
|
root: fn => (fn.target = fn.target.ownerDocument.documentElement, fn),
|
|
85
80
|
body: fn => (fn.target = fn.target.ownerDocument.body, fn),
|
|
86
81
|
parent: fn => (fn.target = fn.target.parentNode, fn),
|
|
87
|
-
|
|
88
|
-
// testers
|
|
89
82
|
self: (fn) => (e) => (e.target === fn.target && fn(e)),
|
|
90
|
-
|
|
91
83
|
outside: (fn) => (e, _target) => (
|
|
92
84
|
_target = fn.target,
|
|
93
85
|
!_target.contains(e.target) && e.target.isConnected && (_target.offsetWidth || _target.offsetHeight)
|
|
94
86
|
),
|
|
87
|
+
|
|
88
|
+
// events
|
|
89
|
+
prevent: (fn) => (e) => (e?.preventDefault(), fn(e)),
|
|
90
|
+
stop: (fn) => (e) => (e?.stopPropagation(), fn(e)),
|
|
91
|
+
immediate: (fn) => (e) => (e?.stopImmediatePropagation(), fn(e)),
|
|
92
|
+
passive: fn => (fn.passive = true, fn),
|
|
93
|
+
capture: fn => (fn.capture = true, fn),
|
|
95
94
|
})
|
|
96
95
|
|
|
97
96
|
// key testers
|
|
@@ -99,7 +98,8 @@ const keys = {
|
|
|
99
98
|
ctrl: e => e.ctrlKey || e.key === "Control" || e.key === "Ctrl",
|
|
100
99
|
shift: e => e.shiftKey || e.key === "Shift",
|
|
101
100
|
alt: e => e.altKey || e.key === "Alt",
|
|
102
|
-
meta: e => e.metaKey || e.key === "Meta"
|
|
101
|
+
meta: e => e.metaKey || e.key === "Meta",
|
|
102
|
+
cmd: e => e.metaKey || e.key === "Command",
|
|
103
103
|
arrow: e => e.key.startsWith("Arrow"),
|
|
104
104
|
enter: e => e.key === "Enter",
|
|
105
105
|
esc: e => e.key.startsWith("Esc"),
|
|
@@ -112,23 +112,57 @@ const keys = {
|
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
// augment modifiers with key testers
|
|
115
|
-
for (let k in keys) modifier[k] = (fn,
|
|
115
|
+
for (let k in keys) modifier[k] = (fn, a, b) => (e) => keys[k](e) && (!a || keys[a]?.(e)) && (!b || keys[b]?.(e)) && fn(e)
|
|
116
|
+
|
|
116
117
|
|
|
117
118
|
use({
|
|
118
|
-
compile: expr => {
|
|
119
|
-
|
|
119
|
+
compile: expr => sprae.constructor(`with (arguments[0]) { ${expr} }`),
|
|
120
|
+
dir: (el, name, expr, state) => {
|
|
121
|
+
// sequences shortcut
|
|
122
|
+
if (name.includes('..')) return () => _seq(el, state, expr, name)[_dispose]
|
|
123
|
+
return name.split(':').reduce((prev, str) => {
|
|
124
|
+
let start = dir(el, str, expr, state)
|
|
125
|
+
return !prev ? start : (p, s) => (p = prev(), s = start(), () => { p(); s() })
|
|
126
|
+
}, null)
|
|
120
127
|
},
|
|
121
|
-
|
|
122
|
-
// signals
|
|
123
|
-
signal, effect, computed, batch, untracked
|
|
128
|
+
...signals
|
|
124
129
|
})
|
|
125
130
|
|
|
131
|
+
|
|
126
132
|
// expose for runtime config
|
|
127
133
|
sprae.use = use
|
|
128
134
|
sprae.store = store
|
|
129
135
|
sprae.directive = directive
|
|
130
136
|
sprae.modifier = modifier
|
|
131
|
-
sprae.
|
|
137
|
+
sprae.version = pkg.version;
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Lifecycle hanger: spraes automatically any new nodes
|
|
142
|
+
*/
|
|
143
|
+
const start = sprae.start = (root = document.body, values) => {
|
|
144
|
+
const state = store(values)
|
|
145
|
+
sprae(root, state);
|
|
146
|
+
const mo = new MutationObserver(mutations => {
|
|
147
|
+
for (const m of mutations) {
|
|
148
|
+
for (const el of m.addedNodes) {
|
|
149
|
+
// el can be spraed or removed by subsprae (like within :each/:if)
|
|
150
|
+
if (el.nodeType === 1 && el[_state] === undefined && root.contains(el)) {
|
|
151
|
+
// even if element has no spraeable attrs, some of its children can have
|
|
152
|
+
root[_add](el)
|
|
153
|
+
// sprae(el, state, root);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// for (const el of m.removedNodes) el[Symbol.dispose]?.()
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
mo.observe(root, { childList: true, subtree: true });
|
|
160
|
+
return state
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
// version placeholder for bundler
|
|
165
|
+
sprae.version = "[VI]{{inject}}[/VI]"
|
|
132
166
|
|
|
133
167
|
export default sprae
|
|
134
168
|
export { sprae, store, signal, effect, computed, batch, untracked, start, use }
|
package/store.js
CHANGED
|
@@ -43,7 +43,7 @@ export const store = (values, parent) => {
|
|
|
43
43
|
},
|
|
44
44
|
|
|
45
45
|
set: (_, k, v, _s) => {
|
|
46
|
-
// console.group('SET', k, v
|
|
46
|
+
// console.group('SET', k, v)
|
|
47
47
|
if (k in signals) return set(signals, k, v), 1
|
|
48
48
|
|
|
49
49
|
// turn off sandbox to check if parents have the prop - we don't want to create new prop in global scope
|
package/directive/default.js
DELETED