mutts 1.0.9 → 1.0.11
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/README.md +60 -35
- package/dist/browser.cjs +1333 -1760
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -1392
- package/dist/browser.dev.cjs +114 -0
- package/dist/browser.dev.cjs.map +1 -0
- package/dist/browser.dev.d.ts +2 -0
- package/dist/browser.dev.esm.js +5 -0
- package/dist/browser.dev.esm.js.map +1 -0
- package/dist/browser.esm.js +18 -97
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-Dgr5CreQ.cjs} +16 -95
- package/dist/chunks/async-browser-Dgr5CreQ.cjs.map +1 -0
- package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
- package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
- package/dist/chunks/index-Sf74wXTV.esm.js +2577 -0
- package/dist/chunks/index-Sf74wXTV.esm.js.map +1 -0
- package/dist/chunks/node-Bo7WU5S2.esm.js +96 -0
- package/dist/chunks/node-Bo7WU5S2.esm.js.map +1 -0
- package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Cc79Lrzj.cjs} +2593 -3216
- package/dist/chunks/proxy-Cc79Lrzj.cjs.map +1 -0
- package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-D2C49sXH.esm.js} +2570 -3174
- package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
- package/dist/debug.cjs +987 -28
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.d.ts +37 -38
- package/dist/debug.esm.js +963 -1
- package/dist/debug.esm.js.map +1 -1
- package/dist/devtools/manifest.json +1 -1
- package/dist/devtools/panel.html +1 -1
- package/dist/devtools/panel.js +107 -94
- package/dist/devtools/panel.js.map +1 -1
- package/dist/index.d.ts +1322 -1
- package/dist/mutts.umd.js +6647 -1
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +69 -63
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +114 -0
- package/dist/node.dev.cjs.map +1 -0
- package/dist/node.dev.d.ts +2 -0
- package/dist/node.dev.esm.js +6 -0
- package/dist/node.dev.esm.js.map +1 -0
- package/dist/node.esm.js +4 -97
- package/dist/node.esm.js.map +1 -1
- package/dist/{types-DaHFfhlN.d.ts → types-Bx2PhORg.d.ts} +134 -88
- package/docs/ai/api-reference.md +11 -32
- package/docs/ai/manual.md +297 -239
- package/docs/reactive/advanced.md +318 -9
- package/docs/reactive/attend.md +2 -4
- package/docs/reactive/collections.md +22 -187
- package/docs/reactive/core.md +223 -131
- package/docs/reactive/debugging.md +119 -12
- package/docs/reactive/error-handling.md +10 -10
- package/docs/reactive/resource.md +125 -0
- package/docs/reactive.md +3 -4
- package/docs/utils.md +70 -0
- package/docs/zone.md +1 -1
- package/package.json +76 -38
- package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
- package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
- package/dist/chunks/index-2vea86wD.esm.js +0 -3011
- package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
- package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
- package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
- package/dist/debug/debug.d.ts +0 -122
- package/dist/debug/debug.d.ts.map +0 -1
- package/dist/debug/index.d.ts +0 -4
- package/dist/debug/index.d.ts.map +0 -1
- package/dist/debug/lineage-panel.d.ts +0 -5
- package/dist/debug/lineage-panel.d.ts.map +0 -1
- package/dist/debug/lineage.d.ts +0 -79
- package/dist/debug/lineage.d.ts.map +0 -1
- package/dist/src/async/browser.d.ts +0 -2
- package/dist/src/async/browser.d.ts.map +0 -1
- package/dist/src/async/index.d.ts +0 -19
- package/dist/src/async/index.d.ts.map +0 -1
- package/dist/src/async/node.d.ts +0 -2
- package/dist/src/async/node.d.ts.map +0 -1
- package/dist/src/decorator.d.ts +0 -106
- package/dist/src/decorator.d.ts.map +0 -1
- package/dist/src/destroyable.d.ts +0 -87
- package/dist/src/destroyable.d.ts.map +0 -1
- package/dist/src/entry-browser.d.ts +0 -3
- package/dist/src/entry-browser.d.ts.map +0 -1
- package/dist/src/entry-node.d.ts +0 -3
- package/dist/src/entry-node.d.ts.map +0 -1
- package/dist/src/eventful.d.ts +0 -20
- package/dist/src/eventful.d.ts.map +0 -1
- package/dist/src/flavored.d.ts +0 -33
- package/dist/src/flavored.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/indexable.d.ts +0 -243
- package/dist/src/indexable.d.ts.map +0 -1
- package/dist/src/introspection.d.ts +0 -27
- package/dist/src/introspection.d.ts.map +0 -1
- package/dist/src/iterableWeak.d.ts +0 -53
- package/dist/src/iterableWeak.d.ts.map +0 -1
- package/dist/src/mixins.d.ts +0 -25
- package/dist/src/mixins.d.ts.map +0 -1
- package/dist/src/promiseChain.d.ts +0 -20
- package/dist/src/promiseChain.d.ts.map +0 -1
- package/dist/src/reactive/array.d.ts +0 -48
- package/dist/src/reactive/array.d.ts.map +0 -1
- package/dist/src/reactive/buffer.d.ts +0 -120
- package/dist/src/reactive/buffer.d.ts.map +0 -1
- package/dist/src/reactive/change.d.ts +0 -29
- package/dist/src/reactive/change.d.ts.map +0 -1
- package/dist/src/reactive/deep-touch.d.ts +0 -28
- package/dist/src/reactive/deep-touch.d.ts.map +0 -1
- package/dist/src/reactive/deep-watch-state.d.ts +0 -25
- package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
- package/dist/src/reactive/deep-watch.d.ts +0 -20
- package/dist/src/reactive/deep-watch.d.ts.map +0 -1
- package/dist/src/reactive/describe.d.ts +0 -12
- package/dist/src/reactive/describe.d.ts.map +0 -1
- package/dist/src/reactive/effect-context.d.ts +0 -34
- package/dist/src/reactive/effect-context.d.ts.map +0 -1
- package/dist/src/reactive/effects.d.ts +0 -164
- package/dist/src/reactive/effects.d.ts.map +0 -1
- package/dist/src/reactive/index.d.ts +0 -19
- package/dist/src/reactive/index.d.ts.map +0 -1
- package/dist/src/reactive/map.d.ts +0 -28
- package/dist/src/reactive/map.d.ts.map +0 -1
- package/dist/src/reactive/memoize.d.ts +0 -28
- package/dist/src/reactive/memoize.d.ts.map +0 -1
- package/dist/src/reactive/non-reactive-state.d.ts +0 -9
- package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
- package/dist/src/reactive/non-reactive.d.ts +0 -11
- package/dist/src/reactive/non-reactive.d.ts.map +0 -1
- package/dist/src/reactive/project.d.ts +0 -40
- package/dist/src/reactive/project.d.ts.map +0 -1
- package/dist/src/reactive/proxy-state.d.ts +0 -8
- package/dist/src/reactive/proxy-state.d.ts.map +0 -1
- package/dist/src/reactive/proxy.d.ts +0 -23
- package/dist/src/reactive/proxy.d.ts.map +0 -1
- package/dist/src/reactive/record.d.ts +0 -115
- package/dist/src/reactive/record.d.ts.map +0 -1
- package/dist/src/reactive/register.d.ts +0 -125
- package/dist/src/reactive/register.d.ts.map +0 -1
- package/dist/src/reactive/registry.d.ts +0 -21
- package/dist/src/reactive/registry.d.ts.map +0 -1
- package/dist/src/reactive/set.d.ts +0 -26
- package/dist/src/reactive/set.d.ts.map +0 -1
- package/dist/src/reactive/tracking.d.ts +0 -7
- package/dist/src/reactive/tracking.d.ts.map +0 -1
- package/dist/src/reactive/types.d.ts +0 -424
- package/dist/src/reactive/types.d.ts.map +0 -1
- package/dist/src/reactive/watch.d.ts +0 -48
- package/dist/src/reactive/watch.d.ts.map +0 -1
- package/dist/src/std-decorators.d.ts +0 -45
- package/dist/src/std-decorators.d.ts.map +0 -1
- package/dist/src/utils.d.ts +0 -49
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/zone.d.ts +0 -40
- package/dist/src/zone.d.ts.map +0 -1
- package/docs/reactive/describe.md +0 -85
- package/docs/reactive/project.md +0 -93
- package/docs/reactive/scan.md +0 -293
- package/src/async/browser.ts +0 -323
- package/src/async/index.ts +0 -27
- package/src/async/node.ts +0 -92
- package/src/decorator.ts +0 -272
- package/src/destroyable.ts +0 -199
- package/src/entry-browser.ts +0 -5
- package/src/entry-node.ts +0 -5
- package/src/eventful.ts +0 -110
- package/src/flavored.ts +0 -106
- package/src/index.d.ts +0 -12
- package/src/index.ts +0 -64
- package/src/indexable.ts +0 -526
- package/src/introspection.ts +0 -59
- package/src/iterableWeak.ts +0 -233
- package/src/mixins.ts +0 -123
- package/src/promiseChain.ts +0 -110
- package/src/reactive/array.ts +0 -500
- package/src/reactive/buffer.ts +0 -328
- package/src/reactive/change.ts +0 -131
- package/src/reactive/deep-touch.ts +0 -273
- package/src/reactive/deep-watch-state.ts +0 -82
- package/src/reactive/deep-watch.ts +0 -171
- package/src/reactive/describe.ts +0 -39
- package/src/reactive/effect-context.ts +0 -83
- package/src/reactive/effects.ts +0 -1434
- package/src/reactive/index.ts +0 -72
- package/src/reactive/map.ts +0 -142
- package/src/reactive/memoize.ts +0 -186
- package/src/reactive/non-reactive-state.ts +0 -49
- package/src/reactive/non-reactive.ts +0 -43
- package/src/reactive/project.md +0 -107
- package/src/reactive/project.ts +0 -430
- package/src/reactive/proxy-state.ts +0 -27
- package/src/reactive/proxy.ts +0 -282
- package/src/reactive/record.ts +0 -181
- package/src/reactive/register.ts +0 -538
- package/src/reactive/registry.ts +0 -72
- package/src/reactive/set.ts +0 -117
- package/src/reactive/tracking.ts +0 -41
- package/src/reactive/types.ts +0 -520
- package/src/reactive/watch.ts +0 -180
- package/src/std-decorators.ts +0 -256
- package/src/utils.ts +0 -300
- package/src/zone.ts +0 -142
package/src/reactive/buffer.ts
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import { FoolProof } from '../utils'
|
|
2
|
-
import { cleanedBy } from '.'
|
|
3
|
-
import { effect, untracked } from './effects'
|
|
4
|
-
import { memoize } from './memoize'
|
|
5
|
-
import { reactive } from './proxy'
|
|
6
|
-
import type { cleanup, EffectAccess, EffectCleanup, EffectCloser, ScopedCallback } from './types'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Reactively attends to each entry of a collection or each key yielded by an
|
|
10
|
-
* enumeration callback. For each key, an inner effect runs the callback. When a
|
|
11
|
-
* key disappears, its inner effect is disposed. The callback may return a cleanup
|
|
12
|
-
* (like a regular effect closer).
|
|
13
|
-
*
|
|
14
|
-
* Accepts arrays, records, Maps, Sets, or a raw `() => Iterable<Key>` callback.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```typescript
|
|
18
|
-
* // Record shorthand
|
|
19
|
-
* attend(record, (key) => { console.log(key, record[key]) })
|
|
20
|
-
*
|
|
21
|
-
* // Array shorthand
|
|
22
|
-
* attend(array, (index) => { console.log(index, array[index]) })
|
|
23
|
-
*
|
|
24
|
-
* // Raw enumeration callback
|
|
25
|
-
* attend(() => Object.keys(record), (key) => { ... })
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export function attend<T>(
|
|
29
|
-
source: readonly T[],
|
|
30
|
-
callback: (index: number) => EffectCloser | void
|
|
31
|
-
): ScopedCallback
|
|
32
|
-
export function attend<K, V>(
|
|
33
|
-
source: Map<K, V>,
|
|
34
|
-
callback: (key: K) => EffectCloser | void
|
|
35
|
-
): ScopedCallback
|
|
36
|
-
export function attend<T>(
|
|
37
|
-
source: Set<T>,
|
|
38
|
-
callback: (value: T) => EffectCloser | void
|
|
39
|
-
): ScopedCallback
|
|
40
|
-
export function attend<S extends Record<PropertyKey, any>>(
|
|
41
|
-
source: S,
|
|
42
|
-
callback: (key: keyof S & string) => EffectCloser | void
|
|
43
|
-
): ScopedCallback
|
|
44
|
-
export function attend<Key>(
|
|
45
|
-
enumerate: () => Iterable<Key>,
|
|
46
|
-
callback: (key: Key) => EffectCloser | void
|
|
47
|
-
): ScopedCallback
|
|
48
|
-
export function attend(
|
|
49
|
-
source: any,
|
|
50
|
-
callback: (key: any) => EffectCloser | void
|
|
51
|
-
): ScopedCallback {
|
|
52
|
-
const enumerate: () => Iterable<any> =
|
|
53
|
-
typeof source === 'function'
|
|
54
|
-
? source
|
|
55
|
-
: Array.isArray(source)
|
|
56
|
-
? () => Array.from({ length: source.length }, (_, i) => i)
|
|
57
|
-
: source instanceof Map
|
|
58
|
-
? () => source.keys()
|
|
59
|
-
: source instanceof Set
|
|
60
|
-
? () => source.values()
|
|
61
|
-
: () => Object.keys(source)
|
|
62
|
-
|
|
63
|
-
const keyEffects = new Map<any, ScopedCallback>()
|
|
64
|
-
|
|
65
|
-
const outer = effect(({ ascend }) => {
|
|
66
|
-
const keys = new Set<any>()
|
|
67
|
-
for (const key of enumerate()) keys.add(key)
|
|
68
|
-
|
|
69
|
-
for (const key of keys) {
|
|
70
|
-
if (keyEffects.has(key)) continue
|
|
71
|
-
keyEffects.set(key, ascend(() => effect(() => callback(key))))
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
for (const key of Array.from(keyEffects.keys())) {
|
|
75
|
-
if (!keys.has(key)) {
|
|
76
|
-
keyEffects.get(key)!()
|
|
77
|
-
keyEffects.delete(key)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
return () => {
|
|
83
|
-
outer()
|
|
84
|
-
for (const stop of keyEffects.values()) stop()
|
|
85
|
-
keyEffects.clear()
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Result of a reactive scan, which is a reactive array of accumulated values
|
|
91
|
-
* with an attached cleanup function.
|
|
92
|
-
*/
|
|
93
|
-
export type ScanResult<Output> = readonly Output[] & { [cleanup]: ScopedCallback }
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Perform a reactive scan over an array of items.
|
|
97
|
-
*
|
|
98
|
-
* This implementation is highly optimized for performance and fine-grained reactivity:
|
|
99
|
-
* - **Incremental Updates**: Changes to an item only trigger re-computation from that
|
|
100
|
-
* point onwards in the result chain.
|
|
101
|
-
* - **Move Optimization**: If items are moved within the array, their accumulated
|
|
102
|
-
* values are reused as long as their predecessor remains the same.
|
|
103
|
-
* - **Duplicate Support**: Correctly handles multiple occurrences of the same object
|
|
104
|
-
* instance using an internal occurrence tracking mechanism.
|
|
105
|
-
* - **Memory Efficient**: Uses `WeakMap` for caching intermediates, which are
|
|
106
|
-
* automatically cleared when source items are garbage collected.
|
|
107
|
-
*
|
|
108
|
-
* @example
|
|
109
|
-
* ```typescript
|
|
110
|
-
* const source = reactive([{ val: 1 }, { val: 2 }, { val: 3 }])
|
|
111
|
-
* const sum = scan(source, (acc, item) => acc + item.val, 0)
|
|
112
|
-
*
|
|
113
|
-
* expect([...sum]).toEqual([1, 3, 6])
|
|
114
|
-
*
|
|
115
|
-
* // Modifying an item only re-computes subsequent sums
|
|
116
|
-
* source[1].val = 10
|
|
117
|
-
* expect([...sum]).toEqual([1, 11, 14])
|
|
118
|
-
* ```
|
|
119
|
-
*
|
|
120
|
-
* @param source The source array of objects (will be made reactive)
|
|
121
|
-
* @param callback The accumulator function called with (accumulator, currentItem)
|
|
122
|
-
* @param initialValue The starting value for the accumulation
|
|
123
|
-
* @returns A reactive array of accumulated values, with a [cleanup] property to stop the tracking
|
|
124
|
-
*/
|
|
125
|
-
export function scan<Input extends object, Output>(
|
|
126
|
-
source: readonly Input[],
|
|
127
|
-
callback: (acc: Output, val: Input) => Output,
|
|
128
|
-
initialValue: Output
|
|
129
|
-
): ScanResult<Output> {
|
|
130
|
-
const observedSource = reactive(source)
|
|
131
|
-
const result = reactive([] as Output[])
|
|
132
|
-
|
|
133
|
-
// Track effects for each index to dispose them when the array shrinks
|
|
134
|
-
const indexEffects = new Map<number, EffectCleanup>()
|
|
135
|
-
// Mapping from index to its current intermediate object
|
|
136
|
-
const indexToIntermediate = reactive([] as Intermediate[])
|
|
137
|
-
const intermediaries = new WeakMap<Input, Intermediate[]>()
|
|
138
|
-
|
|
139
|
-
class Intermediate {
|
|
140
|
-
public prev: Intermediate | undefined
|
|
141
|
-
constructor(
|
|
142
|
-
public val: Input,
|
|
143
|
-
prev: Intermediate | undefined
|
|
144
|
-
) {
|
|
145
|
-
this.prev = prev
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@memoize
|
|
149
|
-
get acc(): Output {
|
|
150
|
-
const prevAcc = this.prev ? this.prev.acc : initialValue
|
|
151
|
-
return callback(prevAcc, this.val)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function disposeIndex(index: number) {
|
|
156
|
-
const stop = indexEffects.get(index)
|
|
157
|
-
if (stop) {
|
|
158
|
-
stop()
|
|
159
|
-
indexEffects.delete(index)
|
|
160
|
-
untracked(() => {
|
|
161
|
-
Reflect.deleteProperty(indexToIntermediate as any, index)
|
|
162
|
-
Reflect.deleteProperty(result as any, index)
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const mainEffect = effect(function scanMainEffect({ ascend }) {
|
|
168
|
-
const length = observedSource.length
|
|
169
|
-
const occurrenceCount = new Map<Input, number>()
|
|
170
|
-
let prev: Intermediate | undefined
|
|
171
|
-
|
|
172
|
-
for (let i = 0; i < length; i++) {
|
|
173
|
-
const val = FoolProof.get(observedSource as any, i, observedSource) as Input
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
!(val && (typeof val === 'object' || typeof val === 'function' || typeof val === 'symbol'))
|
|
177
|
-
) {
|
|
178
|
-
throw new Error('scan: items must be objects (WeakKey) for intermediate caching')
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const count = occurrenceCount.get(val) ?? 0
|
|
182
|
-
occurrenceCount.set(val, count + 1)
|
|
183
|
-
|
|
184
|
-
let list = intermediaries.get(val)
|
|
185
|
-
if (!list) {
|
|
186
|
-
list = []
|
|
187
|
-
intermediaries.set(val, list)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let intermediate = list[count]
|
|
191
|
-
if (!intermediate) {
|
|
192
|
-
intermediate = reactive(new Intermediate(val, prev))
|
|
193
|
-
list[count] = intermediate
|
|
194
|
-
} else {
|
|
195
|
-
// Update the link.
|
|
196
|
-
if (untracked(() => intermediate.prev) !== prev) {
|
|
197
|
-
intermediate.prev = prev
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Update the reactive mapping for this index
|
|
202
|
-
if (indexToIntermediate[i] !== intermediate) {
|
|
203
|
-
indexToIntermediate[i] = intermediate
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// If we don't have an effect for this index yet, create one
|
|
207
|
-
if (!indexEffects.has(i)) {
|
|
208
|
-
ascend(() => {
|
|
209
|
-
const index = i
|
|
210
|
-
const stop = effect(function scanIndexSyncEffect() {
|
|
211
|
-
const inter = indexToIntermediate[index]
|
|
212
|
-
if (inter) {
|
|
213
|
-
const accValue = inter.acc
|
|
214
|
-
untracked(() => {
|
|
215
|
-
result[index] = accValue
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
})
|
|
219
|
-
indexEffects.set(index, stop)
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
prev = intermediate
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Cleanup trailing indices
|
|
227
|
-
for (const index of Array.from(indexEffects.keys())) {
|
|
228
|
-
if (index >= length) disposeIndex(index)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Ensure result length matches source length
|
|
232
|
-
untracked(() => {
|
|
233
|
-
if (result.length !== length) {
|
|
234
|
-
FoolProof.set(result as any, 'length', length, result)
|
|
235
|
-
}
|
|
236
|
-
})
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
return cleanedBy(result, (() => {
|
|
240
|
-
mainEffect()
|
|
241
|
-
for (const stop of indexEffects.values()) stop()
|
|
242
|
-
indexEffects.clear()
|
|
243
|
-
}) as EffectCleanup) as ScanResult<Output>
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Lifts a callback that returns an array into a reactive array that automatically
|
|
248
|
-
* synchronizes with the source array returned by the callback.
|
|
249
|
-
*
|
|
250
|
-
* The returned reactive array will update whenever the callback's dependencies change,
|
|
251
|
-
* efficiently syncing only the elements that differ from the previous result.
|
|
252
|
-
*
|
|
253
|
-
* @example
|
|
254
|
-
* ```typescript
|
|
255
|
-
* const items = reactive([1, 2, 3])
|
|
256
|
-
* const doubled = lift(() => items.map(x => x * 2))
|
|
257
|
-
*
|
|
258
|
-
* console.log([...doubled]) // [2, 4, 6]
|
|
259
|
-
*
|
|
260
|
-
* items.push(4)
|
|
261
|
-
* console.log([...doubled]) // [2, 4, 6, 8]
|
|
262
|
-
* ```
|
|
263
|
-
*
|
|
264
|
-
* @param cb Callback function that returns an array
|
|
265
|
-
* @returns A reactive array synchronized with the callback's result, with a [cleanup] property to stop tracking
|
|
266
|
-
*/
|
|
267
|
-
export function lift<Output extends any[]>(
|
|
268
|
-
cb: (access: EffectAccess) => Output
|
|
269
|
-
): Output & { [cleanup]: ScopedCallback }
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Lifts a callback that returns an object into a reactive object that automatically
|
|
273
|
-
* synchronizes with the source object returned by the callback.
|
|
274
|
-
*
|
|
275
|
-
* The returned reactive object will update whenever the callback's dependencies change,
|
|
276
|
-
* efficiently syncing only the properties that differ from the previous result using
|
|
277
|
-
* Object.assign(). Properties that no longer exist in the source are automatically removed.
|
|
278
|
-
*
|
|
279
|
-
* @example
|
|
280
|
-
* ```typescript
|
|
281
|
-
* const user = reactive({ name: 'John', age: 30 })
|
|
282
|
-
* const profile = lift(() => ({
|
|
283
|
-
* displayName: user.name.toUpperCase(),
|
|
284
|
-
* isAdult: user.age >= 18,
|
|
285
|
-
* description: `${user.name} is ${user.age} years old`
|
|
286
|
-
* }))
|
|
287
|
-
*
|
|
288
|
-
* console.log(profile.displayName) // JOHN
|
|
289
|
-
* console.log(profile.isAdult) // true
|
|
290
|
-
*
|
|
291
|
-
* user.name = 'Jane'
|
|
292
|
-
* console.log(profile.displayName) // JANE
|
|
293
|
-
* console.log(profile.description) // Jane is 30 years old
|
|
294
|
-
* ```
|
|
295
|
-
*
|
|
296
|
-
* @param cb Callback function that returns an object
|
|
297
|
-
* @returns A reactive object synchronized with the callback's result, with a [cleanup] property to stop tracking
|
|
298
|
-
*/
|
|
299
|
-
export function lift<Output extends object>(
|
|
300
|
-
cb: (access: EffectAccess) => Output
|
|
301
|
-
): Output & { [cleanup]: ScopedCallback }
|
|
302
|
-
export function lift<Output extends any[] | object>(
|
|
303
|
-
cb: (access: EffectAccess) => Output
|
|
304
|
-
): Output & { [cleanup]: ScopedCallback } {
|
|
305
|
-
let result: Output
|
|
306
|
-
const liftCleanup = effect((access) => {
|
|
307
|
-
const source = cb(access)
|
|
308
|
-
if (!source || typeof source !== 'object')
|
|
309
|
-
throw new Error('lift callback must return an array or object')
|
|
310
|
-
if (!result) {
|
|
311
|
-
if (Array.isArray(source)) {
|
|
312
|
-
result = reactive([]) as Output
|
|
313
|
-
} else {
|
|
314
|
-
result = reactive({}) as Output
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
if (Array.isArray(source) && Array.isArray(result)) {
|
|
318
|
-
if (result.length !== source.length) result.length = source.length
|
|
319
|
-
for (let i = 0; i < source.length; i++) if (result[i] !== source[i]) result[i] = source[i]
|
|
320
|
-
} else if (!Array.isArray(source) && !Array.isArray(result)) {
|
|
321
|
-
Object.assign(result, source)
|
|
322
|
-
for (const key of Object.keys(result)) if (!(key in source)) delete result[key]
|
|
323
|
-
} else {
|
|
324
|
-
throw new Error('lift callback must return the same type as the previous result')
|
|
325
|
-
}
|
|
326
|
-
})
|
|
327
|
-
return cleanedBy(result as Output, liftCleanup)
|
|
328
|
-
}
|
package/src/reactive/change.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { recordTriggerLink } from '../../debug/debug'
|
|
2
|
-
import { bubbleUpChange, objectsWithDeepWatchers } from './deep-watch-state'
|
|
3
|
-
import { getActiveEffect, isRunning } from './effect-context'
|
|
4
|
-
import { batch, hasBatched, recordActivation } from './effects'
|
|
5
|
-
import { unwrap } from './proxy-state'
|
|
6
|
-
import { getEffectNode, watchers } from './registry'
|
|
7
|
-
import { allProps, type EffectTrigger, type Evolution, optionCall, type State } from './types'
|
|
8
|
-
|
|
9
|
-
const states = new WeakMap<object, State>()
|
|
10
|
-
|
|
11
|
-
export function addState(obj: any, evolution: Evolution) {
|
|
12
|
-
obj = unwrap(obj)
|
|
13
|
-
const next = {}
|
|
14
|
-
const state = getState(obj)
|
|
15
|
-
if (state) Object.assign(state, { evolution, next })
|
|
16
|
-
states.set(obj, next)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Gets the current state of a reactive object for evolution tracking
|
|
21
|
-
* @param obj - The reactive object
|
|
22
|
-
* @returns The current state object
|
|
23
|
-
*/
|
|
24
|
-
export function getState(obj: any) {
|
|
25
|
-
obj = unwrap(obj)
|
|
26
|
-
let state = states.get(obj)
|
|
27
|
-
if (!state) {
|
|
28
|
-
state = {}
|
|
29
|
-
states.set(obj, state)
|
|
30
|
-
}
|
|
31
|
-
return state
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function collectEffects(
|
|
35
|
-
obj: any,
|
|
36
|
-
evolution: Evolution,
|
|
37
|
-
effects: Set<EffectTrigger>,
|
|
38
|
-
objectWatchers: Map<any, Set<EffectTrigger>>,
|
|
39
|
-
...keyChains: Iterable<any>[]
|
|
40
|
-
) {
|
|
41
|
-
const sourceEffect = getActiveEffect()
|
|
42
|
-
for (const keys of keyChains)
|
|
43
|
-
for (const key of keys) {
|
|
44
|
-
const deps = objectWatchers.get(key)
|
|
45
|
-
// console.log(`[DEBUG] collectEffects: checking ${String(key)}. Found deps: ${deps ? deps.size : 'none'}`)
|
|
46
|
-
if (deps)
|
|
47
|
-
for (const effect of deps) {
|
|
48
|
-
// console.log(`[DEBUG] collectEffects: found dependency ${effect.name || 'anonymous'} for ${String(key)}`)
|
|
49
|
-
const runningChain = isRunning(effect)
|
|
50
|
-
if (runningChain) {
|
|
51
|
-
optionCall('skipRunningEffect', effect)
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
if (!effects.has(effect)) {
|
|
55
|
-
effects.add(effect)
|
|
56
|
-
if (!hasBatched(effect)) recordActivation(effect, obj, evolution, key)
|
|
57
|
-
}
|
|
58
|
-
recordTriggerLink(sourceEffect, effect, obj, key, evolution)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Triggers effects for a single property change
|
|
65
|
-
* @param obj - The object that changed
|
|
66
|
-
* @param evolution - The type of change
|
|
67
|
-
* @param prop - The property that changed
|
|
68
|
-
*/
|
|
69
|
-
export function touched1(obj: any, evolution: Evolution, prop: any) {
|
|
70
|
-
touched(obj, evolution, [prop])
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Triggers effects for property changes
|
|
75
|
-
* @param obj - The object that changed
|
|
76
|
-
* @param evolution - The type of change
|
|
77
|
-
* @param props - The properties that changed
|
|
78
|
-
*/
|
|
79
|
-
export function touched(obj: any, evolution: Evolution, props?: Iterable<any>) {
|
|
80
|
-
obj = unwrap(obj)
|
|
81
|
-
addState(obj, evolution)
|
|
82
|
-
const objectWatchers = watchers.get(obj)
|
|
83
|
-
if (objectWatchers) {
|
|
84
|
-
// Note: we have to collect effects to remove duplicates in the specific case when no batch is running
|
|
85
|
-
const effects = new Set<EffectTrigger>()
|
|
86
|
-
if (props) collectEffects(obj, evolution, effects, objectWatchers, [allProps], props)
|
|
87
|
-
else collectEffects(obj, evolution, effects, objectWatchers, objectWatchers.keys())
|
|
88
|
-
optionCall('touched', obj, evolution, props as any[] | undefined, effects)
|
|
89
|
-
batch(Array.from(effects))
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Bubble up changes if this object has deep watchers
|
|
93
|
-
if (objectsWithDeepWatchers.has(obj)) {
|
|
94
|
-
bubbleUpChange(obj, evolution)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Triggers only opaque effects for property changes
|
|
100
|
-
* Used by deep-touch to ensure opaque listeners are notified even when deep optimization is active
|
|
101
|
-
*/
|
|
102
|
-
export function touchedOpaque(obj: any, evolution: Evolution, prop: any) {
|
|
103
|
-
obj = unwrap(obj)
|
|
104
|
-
const objectWatchers = watchers.get(obj)
|
|
105
|
-
if (!objectWatchers) return
|
|
106
|
-
|
|
107
|
-
const deps = objectWatchers.get(prop)
|
|
108
|
-
if (!deps) return
|
|
109
|
-
|
|
110
|
-
const effects = new Set<EffectTrigger>()
|
|
111
|
-
const sourceEffect = getActiveEffect()
|
|
112
|
-
|
|
113
|
-
for (const effect of deps) {
|
|
114
|
-
const node = getEffectNode(effect)
|
|
115
|
-
if (!node.isOpaque) continue
|
|
116
|
-
|
|
117
|
-
const runningChain = isRunning(effect)
|
|
118
|
-
if (runningChain) {
|
|
119
|
-
optionCall('skipRunningEffect', effect)
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
effects.add(effect)
|
|
123
|
-
recordActivation(effect, obj, evolution, prop)
|
|
124
|
-
recordTriggerLink(sourceEffect, effect, obj, prop, evolution)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (effects.size > 0) {
|
|
128
|
-
optionCall('touched', obj, evolution, [prop], effects)
|
|
129
|
-
batch(Array.from(effects))
|
|
130
|
-
}
|
|
131
|
-
}
|