sprae 9.1.1 → 10.0.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 +66 -83
- package/directive/aria.js +2 -1
- package/directive/class.js +6 -5
- package/directive/data.js +4 -3
- package/directive/default.js +14 -16
- package/directive/each.js +84 -47
- package/directive/fx.js +2 -1
- package/directive/if.js +10 -8
- package/directive/ref.js +7 -7
- package/directive/style.js +4 -3
- package/directive/text.js +4 -3
- package/directive/value.js +2 -1
- package/directive/with.js +16 -0
- package/dist/sprae.js +326 -180
- package/dist/sprae.min.js +1 -1
- package/package.json +5 -4
- package/readme.md +67 -97
- package/signal.js +9 -42
- package/sprae.js +2 -3
- package/store.js +156 -0
- package/directive/scope.js +0 -10
package/store.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// signals-based proxy
|
|
2
|
+
import { signal, computed, effect, batch, untracked } from './signal.js'
|
|
3
|
+
|
|
4
|
+
export const _signals = Symbol('signals'), _change = Symbol('length');
|
|
5
|
+
|
|
6
|
+
// object store is not lazy
|
|
7
|
+
export default function store(values, signals) {
|
|
8
|
+
if (!values) return values
|
|
9
|
+
|
|
10
|
+
// ignore existing state as argument
|
|
11
|
+
if (values[_signals] && !signals) return values;
|
|
12
|
+
|
|
13
|
+
// redirect for optimized array store
|
|
14
|
+
if (Array.isArray(values)) return list(values)
|
|
15
|
+
|
|
16
|
+
// ignore non-objects
|
|
17
|
+
if (values.constructor !== Object) return values;
|
|
18
|
+
|
|
19
|
+
// NOTE: if you decide to unlazy values, think about large arrays - init upfront can be costly
|
|
20
|
+
let _len = signal(Object.values(values).length)
|
|
21
|
+
|
|
22
|
+
signals ||= {}
|
|
23
|
+
// proxy conducts prop access to signals
|
|
24
|
+
const state = new Proxy(signals, {
|
|
25
|
+
get: (_, key) => key === _change ? _len : key === _signals ? signals : signals[key]?.valueOf(),
|
|
26
|
+
set: (_, key, v, s) => (s = signals[key], set(signals, key, v), s || (++_len.value)), // bump length for new signal
|
|
27
|
+
deleteProperty: (_, key) => del(signals, key) && _len.value--,
|
|
28
|
+
ownKeys() {
|
|
29
|
+
// subscribe to length when object is spread
|
|
30
|
+
_len.value
|
|
31
|
+
return Reflect.ownKeys(signals);
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// take over existing store signals instead of creating new ones
|
|
36
|
+
if (values[_signals]) for (let key in values) signals[key] = values[_signals][key];
|
|
37
|
+
else for (let key in values) {
|
|
38
|
+
const desc = Object.getOwnPropertyDescriptor(values, key)
|
|
39
|
+
|
|
40
|
+
// getter turns into computed
|
|
41
|
+
if (desc?.get) {
|
|
42
|
+
// stash setter
|
|
43
|
+
(signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// init blank signal - make sure we don't take prototype one
|
|
47
|
+
signals[key] = null
|
|
48
|
+
set(signals, key, values[key]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return state
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// array store - signals are lazy since arrays can be very large & expensive
|
|
57
|
+
export function list(values) {
|
|
58
|
+
// track last accessed property to find out if .length was directly accessed from expression or via .push/etc method
|
|
59
|
+
let lastProp
|
|
60
|
+
|
|
61
|
+
// ignore existing state as argument
|
|
62
|
+
if (values[_signals]) return values;
|
|
63
|
+
|
|
64
|
+
// .length signal is stored separately, since it cannot be replaced on array
|
|
65
|
+
let _len = signal(values.length),
|
|
66
|
+
// gotta fill with null since proto methods like .reduce may fail
|
|
67
|
+
signals = Array(values.length).fill(null);
|
|
68
|
+
|
|
69
|
+
// proxy conducts prop access to signals
|
|
70
|
+
const state = new Proxy(signals, {
|
|
71
|
+
get(_, key) {
|
|
72
|
+
if (key === _change) return _len
|
|
73
|
+
if (key === _signals) return signals
|
|
74
|
+
|
|
75
|
+
// console.log('get', key)
|
|
76
|
+
// if .length is read within .push/etc - peek signal to avoid recursive subscription
|
|
77
|
+
if (key === 'length') return (Array.prototype[lastProp]) ? _len.peek() : _len.value;
|
|
78
|
+
|
|
79
|
+
lastProp = key;
|
|
80
|
+
|
|
81
|
+
if (signals[key]) return signals[key].valueOf()
|
|
82
|
+
|
|
83
|
+
// I hope reading values here won't diverge from signals
|
|
84
|
+
if (key < signals.length) return (signals[key] = signal(store(values[key]))).value
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
set(_, key, v) {
|
|
88
|
+
// .length
|
|
89
|
+
if (key === 'length') {
|
|
90
|
+
// force cleaning up tail
|
|
91
|
+
for (let i = v, l = signals.length; i < l; i++) delete state[i]
|
|
92
|
+
_len.value = signals.length = v;
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
set(signals, key, v)
|
|
97
|
+
|
|
98
|
+
// force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
|
|
99
|
+
if (key >= _len.peek()) _len.value = signals.length = Number(key) + 1
|
|
100
|
+
|
|
101
|
+
return true
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
deleteProperty: (_, key) => (del(signals, key), true),
|
|
105
|
+
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return state
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// set/update signal value
|
|
112
|
+
function set(signals, key, v) {
|
|
113
|
+
let s = signals[key]
|
|
114
|
+
|
|
115
|
+
// new property
|
|
116
|
+
if (!s) {
|
|
117
|
+
// preserve signal value as is
|
|
118
|
+
signals[key] = s = v?.peek ? v : signal(store(v))
|
|
119
|
+
}
|
|
120
|
+
// skip unchanged (although can be handled by last condition - we skip a few checks this way)
|
|
121
|
+
else if (v === s.peek());
|
|
122
|
+
// stashed _set for value with getter/setter
|
|
123
|
+
else if (s._set) s._set(v)
|
|
124
|
+
// patch array
|
|
125
|
+
else if (Array.isArray(v) && Array.isArray(s.peek())) {
|
|
126
|
+
const cur = s.peek()
|
|
127
|
+
// if we update plain array (stored in signal) - take over value instead
|
|
128
|
+
if (cur[_change]) untracked(() => {
|
|
129
|
+
batch(() => {
|
|
130
|
+
let i = 0, l = v.length;
|
|
131
|
+
for (; i < l; i++) cur[i] = v[i]
|
|
132
|
+
cur.length = l // forces deleting tail signals
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
else {
|
|
136
|
+
s.value = v
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// .x = y
|
|
140
|
+
else {
|
|
141
|
+
s.value = store(v)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// delete signal
|
|
146
|
+
function del(signals, key) {
|
|
147
|
+
// console.log('delete', key)
|
|
148
|
+
const s = signals[key]
|
|
149
|
+
if (s) {
|
|
150
|
+
const del = s[Symbol.dispose]
|
|
151
|
+
if (del) delete s[Symbol.dispose]
|
|
152
|
+
delete signals[key]
|
|
153
|
+
del?.()
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
}
|
package/directive/scope.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import sprae, { directive } from "../core.js";
|
|
2
|
-
|
|
3
|
-
// `:each` can redefine scope as `:each="a in {myScope}"`,
|
|
4
|
-
// same time per-item scope as `:each="..." :scope="{collapsed:true}"` is useful
|
|
5
|
-
directive.scope = (el, evaluate, rootState) => {
|
|
6
|
-
// local state may contain signals that update, so we take them over
|
|
7
|
-
return () => {
|
|
8
|
-
sprae(el, { ...rootState, ...(evaluate(rootState)?.valueOf?.() || {}) });
|
|
9
|
-
}
|
|
10
|
-
};
|