sprae 12.0.2 → 12.2.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 +42 -33
- package/directive/else.js +1 -4
- package/directive/fx.js +1 -1
- package/directive/ref.js +8 -7
- package/directive/scope.js +25 -13
- package/dist/sprae.js +3 -3
- package/dist/sprae.js.map +4 -4
- package/dist/sprae.umd.js +3 -3
- package/dist/sprae.umd.js.map +4 -4
- package/package.json +1 -1
- package/readme.md +26 -23
- package/sprae.js +8 -3
- package/store.js +72 -24
package/store.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
// signals-based proxy
|
|
2
|
-
import { signal, computed, batch, untracked } from './core.js'
|
|
2
|
+
import { signal, computed, batch, untracked, _add } from './core.js'
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// _signals allows both storing signals and checking instance, which would be difficult with WeakMap
|
|
6
6
|
export const _signals = Symbol('signals'),
|
|
7
|
+
// _change is a signal that tracks changes to the object keys or array length
|
|
7
8
|
_change = Symbol('change'),
|
|
9
|
+
// _set is stashed setter for computed values
|
|
8
10
|
_set = Symbol('set')
|
|
9
11
|
|
|
12
|
+
// a hack to simulate sandbox for `with` in evaluator
|
|
13
|
+
let sandbox = true
|
|
14
|
+
|
|
10
15
|
// object store is not lazy
|
|
11
16
|
// parent defines parent scope or sandbox
|
|
12
|
-
export const store = (values, parent
|
|
17
|
+
export const store = (values, parent) => {
|
|
13
18
|
if (!values) return values
|
|
14
19
|
|
|
15
20
|
// ignore globals
|
|
21
|
+
// FIXME: handle via has trap
|
|
16
22
|
if (values[Symbol.toStringTag]) return values;
|
|
17
23
|
|
|
18
24
|
// bypass existing store
|
|
@@ -22,23 +28,64 @@ export const store = (values, parent = globalThis) => {
|
|
|
22
28
|
if (values.constructor !== Object) return Array.isArray(values) ? list(values) : values
|
|
23
29
|
|
|
24
30
|
// _change stores total number of keys to track new props
|
|
25
|
-
// NOTE: be careful
|
|
26
31
|
let keyCount = Object.keys(values).length,
|
|
27
|
-
|
|
32
|
+
signals = {}
|
|
28
33
|
|
|
29
34
|
// proxy conducts prop access to signals
|
|
30
|
-
let state = new Proxy(Object.assign(signals, {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
let state = new Proxy(Object.assign(signals, {
|
|
36
|
+
[_change]: signal(keyCount),
|
|
37
|
+
[_signals]: signals
|
|
38
|
+
}), {
|
|
39
|
+
get: (_, k) => {
|
|
40
|
+
// console.log('GET', k, signals)
|
|
41
|
+
if (k in signals) return (signals[k] ? signals[k].valueOf() : signals[k])
|
|
42
|
+
return parent ? parent[k] : globalThis[k]
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
set: (_, k, v, _s) => {
|
|
46
|
+
// console.group('SET', k, v, signals, k in signals)
|
|
47
|
+
if (k in signals) return set(signals, k, v), 1
|
|
48
|
+
|
|
49
|
+
// turn off sandbox to check if parents have the prop - we don't want to create new prop in global scope
|
|
50
|
+
sandbox = false
|
|
51
|
+
|
|
52
|
+
// write transparency for parent scope, unlike prototype chain
|
|
53
|
+
// if prop is defined in parent scope (except global) - write there
|
|
54
|
+
if (parent && k in parent) {
|
|
55
|
+
parent[k] = v
|
|
56
|
+
}
|
|
57
|
+
// else create in current scope
|
|
58
|
+
else {
|
|
59
|
+
create(signals, k, v)
|
|
60
|
+
signals[_change].value = ++keyCount
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
sandbox = true
|
|
64
|
+
|
|
65
|
+
// console.groupEnd()
|
|
66
|
+
// bump length for new signal
|
|
67
|
+
return 1
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// FIXME: try to avild calling Symbol.dispose here. Maybe _delete method?
|
|
71
|
+
deleteProperty: (_, k) => {
|
|
72
|
+
k in signals && (k[0] != '_' && signals[k]?.[Symbol.dispose]?.(), delete signals[k], signals[_change].value = --keyCount)
|
|
73
|
+
return 1
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// subscribe to length when spreading
|
|
36
77
|
ownKeys: () => (signals[_change].value, Reflect.ownKeys(signals)),
|
|
37
|
-
has: _ => 1 // sandbox prevents writing to global
|
|
38
|
-
}),
|
|
39
78
|
|
|
40
|
-
//
|
|
41
|
-
|
|
79
|
+
// sandbox prevents writing to global
|
|
80
|
+
has: (_, k) => {
|
|
81
|
+
if (k in signals) return true
|
|
82
|
+
if (parent) return k in parent
|
|
83
|
+
return sandbox
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// init signals for values
|
|
88
|
+
const descs = Object.getOwnPropertyDescriptors(values)
|
|
42
89
|
|
|
43
90
|
for (let k in values) {
|
|
44
91
|
// getter turns into computed
|
|
@@ -53,8 +100,6 @@ export const store = (values, parent = globalThis) => {
|
|
|
53
100
|
return state
|
|
54
101
|
}
|
|
55
102
|
|
|
56
|
-
const mut = ['push', 'pop', 'shift', 'unshift', 'splice']
|
|
57
|
-
|
|
58
103
|
// array store - signals are lazy since arrays can be very large & expensive
|
|
59
104
|
const list = (values, parent = globalThis) => {
|
|
60
105
|
|
|
@@ -65,15 +110,16 @@ const list = (values, parent = globalThis) => {
|
|
|
65
110
|
isMut = false,
|
|
66
111
|
|
|
67
112
|
// since array mutator methods read .length internally only once, we disable it on the moment of call, allowing rest of operations to be reactive
|
|
68
|
-
mut = fn => function () {isMut = true; return fn.apply(this, arguments); },
|
|
113
|
+
mut = fn => function () { isMut = true; return fn.apply(this, arguments); },
|
|
69
114
|
|
|
70
115
|
length = signal(values.length),
|
|
71
116
|
|
|
72
|
-
// proxy
|
|
117
|
+
// proxy passes prop access to signals
|
|
73
118
|
state = new Proxy(
|
|
74
119
|
Object.assign(signals, {
|
|
75
120
|
[_change]: length,
|
|
76
121
|
[_signals]: signals,
|
|
122
|
+
// patch mutators
|
|
77
123
|
push: mut(signals.push),
|
|
78
124
|
pop: mut(signals.pop),
|
|
79
125
|
shift: mut(signals.shift),
|
|
@@ -124,7 +170,7 @@ const list = (values, parent = globalThis) => {
|
|
|
124
170
|
}
|
|
125
171
|
|
|
126
172
|
// create signal value, skip untracked
|
|
127
|
-
const create = (signals, k, v) => (signals[k] = k[0] == '_' || v?.peek ? v : signal(store(v)))
|
|
173
|
+
const create = (signals, k, v) => (signals[k] = (k[0] == '_' || v?.peek) ? v : signal(store(v)))
|
|
128
174
|
|
|
129
175
|
// set/update signal value
|
|
130
176
|
const set = (signals, k, v, _s, _v) => {
|
|
@@ -138,10 +184,12 @@ const set = (signals, k, v, _s, _v) => {
|
|
|
138
184
|
// if we update plain array (stored in signal) - take over value instead
|
|
139
185
|
// since input value can be store, we have to make sure we don't subscribe to its length or values
|
|
140
186
|
// FIXME: generalize to objects
|
|
141
|
-
_change in _v ?
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
187
|
+
_change in _v ?
|
|
188
|
+
untracked(() => batch(() => {
|
|
189
|
+
for (let i = 0; i < v.length; i++) _v[i] = v[i]
|
|
190
|
+
_v.length = v.length // forces deleting tail signals
|
|
191
|
+
})) :
|
|
192
|
+
(_s.value = v) :
|
|
145
193
|
// .x = y
|
|
146
194
|
(_s.value = store(v))
|
|
147
195
|
)
|
|
@@ -150,6 +198,6 @@ const set = (signals, k, v, _s, _v) => {
|
|
|
150
198
|
|
|
151
199
|
// make sure state contains first element of path, eg. `a` from `a.b[c]`
|
|
152
200
|
// NOTE: we don't need since we force proxy sandbox
|
|
153
|
-
// export const ensure = (state, expr,
|
|
201
|
+
// export const ensure = (state, expr, _name = expr.match(/^\w+(?=\s*(?:\.|\[|$))/)) => _name && (state[_signals][_name[0]] ??= null)
|
|
154
202
|
|
|
155
203
|
export default store
|