sprae 13.3.2 → 13.3.3

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/store.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @module sprae/store
4
4
  */
5
5
 
6
- import { signal, computed, batch, untracked } from './core.js'
6
+ import { signal, computed } from './core.js'
7
7
 
8
8
  /** Symbol for accessing the internal signals map */
9
9
  export const _signals = Symbol('signals')
@@ -143,8 +143,8 @@ export const store = (values, parent) => {
143
143
  */
144
144
  const list = (values, parent = globalThis) => {
145
145
 
146
- // gotta fill with null since proto methods like .reduce may fail
147
- let signals = Array(values.length).fill(null),
146
+ // init signals eagerly shallow() is cheap (1 Proxy + 1 signal per object item)
147
+ let signals = values.map(v => signal(shallow(v))),
148
148
 
149
149
  // if .length was accessed from mutator (.push/etc) method
150
150
  isMut = false,
@@ -177,9 +177,8 @@ const list = (values, parent = globalThis) => {
177
177
  // non-numeric
178
178
  if (typeof k === 'symbol' || isNaN(k)) return signals[k]?.valueOf() ?? parent[k];
179
179
 
180
- // create signal (lazy)
181
- // NOTE: if you decide to unlazy values, think about large arrays - init upfront can be costly
182
- return (signals[k] ??= signal(store(values[k]))).valueOf()
180
+ // signals are eagerly initialized; null slots from .length extension default to undefined
181
+ return (signals[k] ??= signal(undefined)).valueOf()
183
182
  },
184
183
 
185
184
  set(_, k, v) {
@@ -194,10 +193,10 @@ const list = (values, parent = globalThis) => {
194
193
  }
195
194
 
196
195
  // force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
197
- else if (k >= signals.length) create(signals, k, v), state.length = +k + 1
196
+ else if (k >= signals.length) create(signals, k, v, shallow), state.length = +k + 1
198
197
 
199
198
  // existing signal
200
- else signals[k] ? set(signals, k, v) : create(signals, k, v)
199
+ else signals[k] ? set(signals, k, v, shallow) : create(signals, k, v, shallow)
201
200
 
202
201
  return 1
203
202
  },
@@ -217,7 +216,18 @@ const list = (values, parent = globalThis) => {
217
216
  * @param {string} k - Property key
218
217
  * @param {any} v - Property value
219
218
  */
220
- const create = (signals, k, v) => (signals[k] = (k[0] == '_' || v?.peek || typeof v === 'function') ? v : signal(store(v)))
219
+ const create = (signals, k, v, wrap = store) => (signals[k] = (k[0] == '_' || v?.peek || typeof v === 'function') ? v : signal(wrap(v)))
220
+
221
+ /** Lightweight reactive wrapper for array items — avoids full store() per item. */
222
+ const shallow = (v) => {
223
+ if (!v || typeof v !== 'object' || v.constructor !== Object) return v
224
+ let ver = signal(0)
225
+ return new Proxy(v, {
226
+ get: (t, k) => k === _signals ? t : k === _change ? ver : (ver.value, t[k]),
227
+ set: (t, k, val) => { let prev = t[k]; t[k] = val; if (prev !== val) ver.value++; return 1 },
228
+ has: () => true
229
+ })
230
+ }
221
231
 
222
232
  /**
223
233
  * Updates a signal value, handling arrays specially for efficient patching.
@@ -225,31 +235,15 @@ const create = (signals, k, v) => (signals[k] = (k[0] == '_' || v?.peek || typeo
225
235
  * @param {string} k - Property key
226
236
  * @param {any} v - New value
227
237
  */
228
- const set = (signals, k, v, _s, _v) => {
229
- // skip unchanged (although can be handled by last condition - we skip a few checks this way)
230
- return k[0] === '_' || typeof signals[k] === 'function' ? (signals[k] = v) :
231
- (v !== (_v = (_s = signals[k]).peek?.() ?? _s)) && (
232
- // stashed _set for value with getter/setter
233
- _s[_set] ? _s[_set](v) :
234
- // patch array
235
- Array.isArray(v) && Array.isArray(_v) ?
236
- // if we update plain array (stored in signal) - take over value instead
237
- // since input value can be store, we have to make sure we don't subscribe to its length or values
238
- // FIXME: generalize to objects
239
- _change in _v ?
240
- untracked(() => batch(() => {
241
- for (let i = 0; i < v.length; i++) _v[i] = v[i]
242
- _v.length = v.length // forces deleting tail signals
243
- })) :
244
- (_s.value = v) :
245
- // .x = y
246
- (_s.value = store(v))
247
- )
238
+ const set = (signals, k, v, wrap = store) => {
239
+ if (k[0] === '_' || typeof signals[k] === 'function') return (signals[k] = v)
240
+ let _s = signals[k], _v = _s.peek?.() ?? _s
241
+ if (v === _v) return
242
+ // stashed _set for value with getter/setter
243
+ if (_s[_set]) return _s[_set](v)
244
+ // replace array: create new list store, let :each teardown+recreate
245
+ if (Array.isArray(v) && Array.isArray(_v)) _s.value = _change in v ? v : list(v)
246
+ else _s.value = wrap(v)
248
247
  }
249
248
 
250
-
251
- // make sure state contains first element of path, eg. `a` from `a.b[c]`
252
- // NOTE: we don't need since we force proxy sandbox
253
- // export const ensure = (state, expr, _name = expr.match(/^\w+(?=\s*(?:\.|\[|$))/)) => _name && (state[_signals][_name[0]] ??= null)
254
-
255
249
  export default store
@@ -1,7 +1,7 @@
1
1
  declare function _default(tpl: any, state: any, expr: any): {
2
2
  (value: any): () => void;
3
3
  eval: (state: any, cb?: (value: any) => any) => any;
4
- [_off]: () => void;
4
+ [_off]: any;
5
5
  };
6
6
  export default _default;
7
7
  import { _off } from "../core.js";
@@ -1 +1 @@
1
- {"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../directive/each.js"],"names":[],"mappings":"AAwBe;;;;EAqLd;;qBA7M6F,YAAY"}
1
+ {"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../directive/each.js"],"names":[],"mappings":"AA8Be;;;;EAuHd;;qBArJ6F,YAAY"}