mutts 1.0.2 → 1.0.4
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 +14 -6
- package/dist/chunks/{_tslib-C-cuVLvZ.js → _tslib-BgjropY9.js} +9 -1
- package/dist/chunks/_tslib-BgjropY9.js.map +1 -0
- package/dist/chunks/{_tslib-CMEnd0VE.esm.js → _tslib-Mzh1rNsX.esm.js} +9 -2
- package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +1 -0
- package/dist/chunks/{decorator-D4DU97Zg.js → decorator-DLvrD0UF.js} +42 -19
- package/dist/chunks/decorator-DLvrD0UF.js.map +1 -0
- package/dist/chunks/{decorator-GnHw1Az7.esm.js → decorator-DqiszP7i.esm.js} +42 -19
- package/dist/chunks/decorator-DqiszP7i.esm.js.map +1 -0
- package/dist/chunks/index-79Kk8D6e.esm.js +4857 -0
- package/dist/chunks/index-79Kk8D6e.esm.js.map +1 -0
- package/dist/chunks/index-GRBSx0mB.js +4908 -0
- package/dist/chunks/index-GRBSx0mB.js.map +1 -0
- package/dist/decorator.esm.js +1 -1
- package/dist/decorator.js +1 -1
- package/dist/destroyable.d.ts +1 -1
- package/dist/destroyable.esm.js +1 -1
- package/dist/destroyable.esm.js.map +1 -1
- package/dist/destroyable.js +1 -1
- package/dist/destroyable.js.map +1 -1
- package/dist/devtools/devtools.html +9 -0
- package/dist/devtools/devtools.js +5 -0
- package/dist/devtools/devtools.js.map +1 -0
- package/dist/devtools/manifest.json +8 -0
- package/dist/devtools/panel.css +72 -0
- package/dist/devtools/panel.html +31 -0
- package/dist/devtools/panel.js +13048 -0
- package/dist/devtools/panel.js.map +1 -0
- package/dist/eventful.esm.js +1 -1
- package/dist/eventful.js +1 -1
- package/dist/index.d.ts +18 -63
- package/dist/index.esm.js +4 -4
- package/dist/index.js +37 -11
- package/dist/index.js.map +1 -1
- package/dist/indexable.d.ts +187 -1
- package/dist/indexable.esm.js +197 -3
- package/dist/indexable.esm.js.map +1 -1
- package/dist/indexable.js +198 -2
- package/dist/indexable.js.map +1 -1
- package/dist/mutts.umd.js +1 -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/promiseChain.esm.js.map +1 -1
- package/dist/promiseChain.js.map +1 -1
- package/dist/reactive.d.ts +602 -97
- package/dist/reactive.esm.js +3 -3
- package/dist/reactive.js +32 -10
- package/dist/reactive.js.map +1 -1
- package/dist/std-decorators.esm.js +1 -1
- package/dist/std-decorators.js +1 -1
- package/docs/ai/api-reference.md +133 -0
- package/docs/ai/manual.md +105 -0
- package/docs/iterableWeak.md +646 -0
- package/docs/reactive/advanced.md +1280 -0
- package/docs/reactive/collections.md +767 -0
- package/docs/reactive/core.md +973 -0
- package/docs/reactive.md +21 -9545
- package/package.json +18 -5
- package/src/decorator.ts +266 -0
- package/src/destroyable.ts +199 -0
- package/src/eventful.ts +77 -0
- package/src/index.d.ts +9 -0
- package/src/index.ts +9 -0
- package/src/indexable.ts +484 -0
- package/src/introspection.ts +59 -0
- package/src/iterableWeak.ts +233 -0
- package/src/mixins.ts +123 -0
- package/src/promiseChain.ts +110 -0
- package/src/reactive/array.ts +414 -0
- package/src/reactive/change.ts +134 -0
- package/src/reactive/debug.ts +517 -0
- package/src/reactive/deep-touch.ts +268 -0
- package/src/reactive/deep-watch-state.ts +82 -0
- package/src/reactive/deep-watch.ts +168 -0
- package/src/reactive/effect-context.ts +94 -0
- package/src/reactive/effects.ts +1345 -0
- package/src/reactive/index.ts +76 -0
- package/src/reactive/interface.ts +223 -0
- package/src/reactive/map.ts +171 -0
- package/src/reactive/mapped.ts +130 -0
- package/src/reactive/memoize.ts +107 -0
- package/src/reactive/non-reactive-state.ts +49 -0
- package/src/reactive/non-reactive.ts +43 -0
- package/src/reactive/project.project.md +93 -0
- package/src/reactive/project.ts +335 -0
- package/src/reactive/proxy-state.ts +27 -0
- package/src/reactive/proxy.ts +289 -0
- package/src/reactive/record.ts +196 -0
- package/src/reactive/register.ts +421 -0
- package/src/reactive/set.ts +144 -0
- package/src/reactive/tracking.ts +101 -0
- package/src/reactive/types.ts +358 -0
- package/src/reactive/zone.ts +208 -0
- package/src/std-decorators.ts +217 -0
- package/src/utils.ts +117 -0
- package/dist/chunks/_tslib-C-cuVLvZ.js.map +0 -1
- package/dist/chunks/_tslib-CMEnd0VE.esm.js.map +0 -1
- package/dist/chunks/decorator-D4DU97Zg.js.map +0 -1
- package/dist/chunks/decorator-GnHw1Az7.esm.js.map +0 -1
- package/dist/chunks/index-DBScoeCX.esm.js +0 -1960
- package/dist/chunks/index-DBScoeCX.esm.js.map +0 -1
- package/dist/chunks/index-DOTmXL89.js +0 -1983
- package/dist/chunks/index-DOTmXL89.js.map +0 -1
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { Indexable } from '../indexable'
|
|
2
|
+
import { touched } from './change'
|
|
3
|
+
import { makeReactiveEntriesIterator, makeReactiveIterator } from './non-reactive'
|
|
4
|
+
import { reactive } from './proxy'
|
|
5
|
+
import { unwrap } from './proxy-state'
|
|
6
|
+
import { dependant } from './tracking'
|
|
7
|
+
import { prototypeForwarding } from './types'
|
|
8
|
+
|
|
9
|
+
export const native = Symbol('native')
|
|
10
|
+
const isArray = Array.isArray
|
|
11
|
+
Array.isArray = ((value: any) =>
|
|
12
|
+
isArray(value) ||
|
|
13
|
+
// biome-ignore lint/suspicious/useIsArray: We are defining it
|
|
14
|
+
(value &&
|
|
15
|
+
typeof value === 'object' &&
|
|
16
|
+
prototypeForwarding in value &&
|
|
17
|
+
Array.isArray(value[prototypeForwarding]))) as any
|
|
18
|
+
export class ReactiveBaseArray {
|
|
19
|
+
declare readonly [native]: any[]
|
|
20
|
+
|
|
21
|
+
// Safe array access with negative indices
|
|
22
|
+
at(index: number): any {
|
|
23
|
+
const actualIndex = index < 0 ? this[native].length + index : index
|
|
24
|
+
dependant(this, actualIndex)
|
|
25
|
+
if (actualIndex < 0 || actualIndex >= this[native].length) return undefined
|
|
26
|
+
return reactive(this[native][actualIndex])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Immutable versions of mutator methods
|
|
30
|
+
toReversed(): any[] {
|
|
31
|
+
dependant(this)
|
|
32
|
+
return reactive(this[native].toReversed())
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
toSorted(compareFn?: (a: any, b: any) => number): any[] {
|
|
36
|
+
dependant(this)
|
|
37
|
+
return reactive(this[native].toSorted(compareFn))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
toSpliced(start: number, deleteCount?: number, ...items: any[]): any[] {
|
|
41
|
+
dependant(this)
|
|
42
|
+
return deleteCount === undefined
|
|
43
|
+
? this[native].toSpliced(start)
|
|
44
|
+
: this[native].toSpliced(start, deleteCount, ...items)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
with(index: number, value: any): any[] {
|
|
48
|
+
dependant(this)
|
|
49
|
+
return reactive(this[native].with(index, value))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Iterator methods with reactivity tracking
|
|
53
|
+
entries() {
|
|
54
|
+
dependant(this)
|
|
55
|
+
return makeReactiveEntriesIterator(this[native].entries())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
keys() {
|
|
59
|
+
dependant(this, 'length')
|
|
60
|
+
return this[native].keys()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
values() {
|
|
64
|
+
dependant(this)
|
|
65
|
+
return makeReactiveIterator(this[native].values())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[Symbol.iterator]() {
|
|
69
|
+
dependant(this)
|
|
70
|
+
const nativeIterator = this[native][Symbol.iterator]()
|
|
71
|
+
return {
|
|
72
|
+
next() {
|
|
73
|
+
const result = nativeIterator.next()
|
|
74
|
+
if (result.done) {
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
77
|
+
return { value: reactive(result.value), done: false }
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
indexOf(searchElement: any, fromIndex?: number): number {
|
|
83
|
+
dependant(this)
|
|
84
|
+
const unwrappedSearch = unwrap(searchElement)
|
|
85
|
+
// Check both wrapped and unwrapped versions since array may contain either
|
|
86
|
+
const index = this[native].indexOf(unwrappedSearch, fromIndex)
|
|
87
|
+
if (index !== -1) return index
|
|
88
|
+
// If not found with unwrapped, try with wrapped (in case array contains wrapped version)
|
|
89
|
+
return this[native].indexOf(searchElement, fromIndex)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
lastIndexOf(searchElement: any, fromIndex?: number): number {
|
|
93
|
+
dependant(this)
|
|
94
|
+
const unwrappedSearch = unwrap(searchElement)
|
|
95
|
+
// Check both wrapped and unwrapped versions since array may contain either
|
|
96
|
+
const index = this[native].lastIndexOf(unwrappedSearch, fromIndex)
|
|
97
|
+
if (index !== -1) return index
|
|
98
|
+
// If not found with unwrapped, try with wrapped (in case array contains wrapped version)
|
|
99
|
+
return this[native].lastIndexOf(searchElement, fromIndex)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
includes(searchElement: any, fromIndex?: number): boolean {
|
|
103
|
+
dependant(this)
|
|
104
|
+
const unwrappedSearch = unwrap(searchElement)
|
|
105
|
+
// Check both wrapped and unwrapped versions since array may contain either
|
|
106
|
+
return (
|
|
107
|
+
this[native].includes(unwrappedSearch, fromIndex) ||
|
|
108
|
+
this[native].includes(searchElement, fromIndex)
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
find(predicate: (this: any, value: any, index: number, obj: any[]) => boolean, thisArg?: any): any
|
|
113
|
+
find(searchElement: any, fromIndex?: number): any
|
|
114
|
+
find(predicateOrElement: any, thisArg?: any): any {
|
|
115
|
+
dependant(this)
|
|
116
|
+
if (typeof predicateOrElement === 'function') {
|
|
117
|
+
const predicate = predicateOrElement as (
|
|
118
|
+
this: any,
|
|
119
|
+
value: any,
|
|
120
|
+
index: number,
|
|
121
|
+
obj: any[]
|
|
122
|
+
) => boolean
|
|
123
|
+
return reactive(
|
|
124
|
+
this[native].find(
|
|
125
|
+
(value, index, array) => predicate.call(thisArg, reactive(value), index, array),
|
|
126
|
+
thisArg
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
const fromIndex = typeof thisArg === 'number' ? thisArg : undefined
|
|
131
|
+
const index = this[native].indexOf(predicateOrElement, fromIndex)
|
|
132
|
+
if (index === -1) return undefined
|
|
133
|
+
return reactive(this[native][index])
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
findIndex(
|
|
137
|
+
predicate: (this: any, value: any, index: number, obj: any[]) => boolean,
|
|
138
|
+
thisArg?: any
|
|
139
|
+
): number
|
|
140
|
+
findIndex(searchElement: any, fromIndex?: number): number
|
|
141
|
+
findIndex(predicateOrElement: any, thisArg?: any): number {
|
|
142
|
+
dependant(this)
|
|
143
|
+
if (typeof predicateOrElement === 'function') {
|
|
144
|
+
const predicate = predicateOrElement as (
|
|
145
|
+
this: any,
|
|
146
|
+
value: any,
|
|
147
|
+
index: number,
|
|
148
|
+
obj: any[]
|
|
149
|
+
) => boolean
|
|
150
|
+
return this[native].findIndex(
|
|
151
|
+
(value, index, array) => predicate.call(thisArg, reactive(value), index, array),
|
|
152
|
+
thisArg
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
const fromIndex = typeof thisArg === 'number' ? thisArg : undefined
|
|
156
|
+
return this[native].indexOf(predicateOrElement, fromIndex)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
flat(): any[] {
|
|
160
|
+
dependant(this)
|
|
161
|
+
return reactive(this[native].flat())
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
flatMap(
|
|
165
|
+
callbackfn: (this: any, value: any, index: number, array: any[]) => any[],
|
|
166
|
+
thisArg?: any
|
|
167
|
+
): any[] {
|
|
168
|
+
dependant(this)
|
|
169
|
+
return reactive(this[native].flatMap(callbackfn, thisArg))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
filter(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): any[] {
|
|
173
|
+
dependant(this)
|
|
174
|
+
return reactive(
|
|
175
|
+
this[native].filter((item, index, array) => callbackfn(reactive(item), index, array), thisArg)
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
map(callbackfn: (value: any, index: number, array: any[]) => any, thisArg?: any): any[] {
|
|
180
|
+
dependant(this)
|
|
181
|
+
return reactive(
|
|
182
|
+
this[native].map((item, index, array) => callbackfn(reactive(item), index, array), thisArg)
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
reduce(
|
|
187
|
+
callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any,
|
|
188
|
+
initialValue?: any
|
|
189
|
+
): any {
|
|
190
|
+
dependant(this)
|
|
191
|
+
const result =
|
|
192
|
+
initialValue === undefined
|
|
193
|
+
? this[native].reduce(callbackfn as any)
|
|
194
|
+
: this[native].reduce(callbackfn as any, initialValue)
|
|
195
|
+
return reactive(result)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
reduceRight(
|
|
199
|
+
callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any,
|
|
200
|
+
initialValue?: any
|
|
201
|
+
): any {
|
|
202
|
+
dependant(this)
|
|
203
|
+
const result =
|
|
204
|
+
initialValue !== undefined
|
|
205
|
+
? this[native].reduceRight(callbackfn as any, initialValue)
|
|
206
|
+
: (this[native] as any).reduceRight(callbackfn as any)
|
|
207
|
+
return reactive(result)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
slice(start?: number, end?: number): any[] {
|
|
211
|
+
for (const i of range(start || 0, end || this[native].length - 1)) dependant(this, i)
|
|
212
|
+
return start === undefined
|
|
213
|
+
? this[native].slice()
|
|
214
|
+
: end === undefined
|
|
215
|
+
? this[native].slice(start)
|
|
216
|
+
: this[native].slice(start, end)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
concat(...items: any[]): any[] {
|
|
220
|
+
dependant(this)
|
|
221
|
+
return reactive(this[native].concat(...items))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
join(separator?: string): string {
|
|
225
|
+
dependant(this)
|
|
226
|
+
return this[native].join(separator as any)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
forEach(callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any): void {
|
|
230
|
+
dependant(this)
|
|
231
|
+
this[native].forEach((value, index, array) => {
|
|
232
|
+
callbackfn.call(thisArg, reactive(value), index, array)
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// TODO: re-implement for fun dependencies? (eg - every only check the first ones until it find some),
|
|
237
|
+
// no need to make it dependant on indexes after the found one
|
|
238
|
+
every(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean {
|
|
239
|
+
dependant(this)
|
|
240
|
+
return this[native].every(
|
|
241
|
+
(value, index, array) => callbackfn.call(thisArg, reactive(value), index, array),
|
|
242
|
+
thisArg
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
some(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean {
|
|
247
|
+
dependant(this)
|
|
248
|
+
return this[native].some(
|
|
249
|
+
(value, index, array) => callbackfn.call(thisArg, reactive(value), index, array),
|
|
250
|
+
thisArg
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function* index(i: number, { length = true } = {}): IterableIterator<number | 'length'> {
|
|
255
|
+
if (length) yield 'length'
|
|
256
|
+
yield i
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function* range(
|
|
260
|
+
a: number,
|
|
261
|
+
b: number,
|
|
262
|
+
{ length = false } = {}
|
|
263
|
+
): IterableIterator<number | 'length'> {
|
|
264
|
+
const start = Math.min(a, b)
|
|
265
|
+
const end = Math.max(a, b)
|
|
266
|
+
if (length) yield 'length'
|
|
267
|
+
for (let i = start; i <= end; i++) yield i
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Reactive wrapper around JavaScript's Array class with full array method support
|
|
271
|
+
* Tracks length changes, individual index operations, and collection-wide operations
|
|
272
|
+
*/
|
|
273
|
+
export class ReactiveArray extends Indexable(ReactiveBaseArray, {
|
|
274
|
+
get(i: number): any {
|
|
275
|
+
dependant(this, i)
|
|
276
|
+
return reactive(this[native][i])
|
|
277
|
+
},
|
|
278
|
+
set(i: number, value: any) {
|
|
279
|
+
const added = i >= this[native].length
|
|
280
|
+
this[native][i] = value
|
|
281
|
+
touched(this, { type: 'set', prop: i }, index(i, { length: added }))
|
|
282
|
+
},
|
|
283
|
+
getLength() {
|
|
284
|
+
dependant(this, 'length')
|
|
285
|
+
return this[native].length
|
|
286
|
+
},
|
|
287
|
+
setLength(value: number) {
|
|
288
|
+
const oldLength = this[native].length
|
|
289
|
+
try {
|
|
290
|
+
this[native].length = value
|
|
291
|
+
} finally {
|
|
292
|
+
touched(this, { type: 'set', prop: 'length' }, range(oldLength, value, { length: true }))
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
}) {
|
|
296
|
+
declare length: number
|
|
297
|
+
constructor(original: any[]) {
|
|
298
|
+
super()
|
|
299
|
+
Object.defineProperties(this, {
|
|
300
|
+
// We have to make it double, as [native] must be `unique symbol` - impossible through import
|
|
301
|
+
[native]: { value: original },
|
|
302
|
+
[prototypeForwarding]: { value: original },
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
push(...items: any[]) {
|
|
307
|
+
const oldLength = this[native].length
|
|
308
|
+
try {
|
|
309
|
+
return this[native].push(...items)
|
|
310
|
+
} finally {
|
|
311
|
+
touched(
|
|
312
|
+
this,
|
|
313
|
+
{ type: 'bunch', method: 'push' },
|
|
314
|
+
range(oldLength, oldLength + items.length - 1, { length: true })
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
pop() {
|
|
320
|
+
if (this[native].length === 0) return undefined
|
|
321
|
+
try {
|
|
322
|
+
return reactive(this[native].pop())
|
|
323
|
+
} finally {
|
|
324
|
+
touched(this, { type: 'bunch', method: 'pop' }, index(this[native].length))
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
shift() {
|
|
329
|
+
if (this[native].length === 0) return undefined
|
|
330
|
+
try {
|
|
331
|
+
return reactive(this[native].shift())
|
|
332
|
+
} finally {
|
|
333
|
+
touched(
|
|
334
|
+
this,
|
|
335
|
+
{ type: 'bunch', method: 'shift' },
|
|
336
|
+
range(0, this[native].length + 1, { length: true })
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
unshift(...items: any[]) {
|
|
342
|
+
try {
|
|
343
|
+
return this[native].unshift(...items)
|
|
344
|
+
} finally {
|
|
345
|
+
touched(
|
|
346
|
+
this,
|
|
347
|
+
{ type: 'bunch', method: 'unshift' },
|
|
348
|
+
range(0, this[native].length - items.length, { length: true })
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
splice(start: number, deleteCount?: number, ...items: any[]) {
|
|
354
|
+
const oldLength = this[native].length
|
|
355
|
+
if (deleteCount === undefined) deleteCount = oldLength - start
|
|
356
|
+
try {
|
|
357
|
+
if (deleteCount === undefined) return reactive(this[native].splice(start))
|
|
358
|
+
return reactive(this[native].splice(start, deleteCount, ...items))
|
|
359
|
+
} finally {
|
|
360
|
+
touched(
|
|
361
|
+
this,
|
|
362
|
+
{ type: 'bunch', method: 'splice' },
|
|
363
|
+
// TODO: edge cases
|
|
364
|
+
deleteCount === items.length
|
|
365
|
+
? range(start, start + deleteCount)
|
|
366
|
+
: range(start, oldLength + Math.max(items.length - deleteCount, 0), {
|
|
367
|
+
length: true,
|
|
368
|
+
})
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
reverse() {
|
|
374
|
+
try {
|
|
375
|
+
return this[native].reverse()
|
|
376
|
+
} finally {
|
|
377
|
+
touched(this, { type: 'bunch', method: 'reverse' }, range(0, this[native].length - 1))
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
sort(compareFn?: (a: any, b: any) => number) {
|
|
382
|
+
compareFn = compareFn || ((a, b) => a.toString().localeCompare(b.toString()))
|
|
383
|
+
try {
|
|
384
|
+
return this[native].sort((a, b) => compareFn(reactive(a), reactive(b))) as any
|
|
385
|
+
} finally {
|
|
386
|
+
touched(this, { type: 'bunch', method: 'sort' }, range(0, this[native].length - 1))
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
fill(value: any, start?: number, end?: number) {
|
|
391
|
+
try {
|
|
392
|
+
if (start === undefined) return this[native].fill(value) as any
|
|
393
|
+
if (end === undefined) return this[native].fill(value, start) as any
|
|
394
|
+
return this[native].fill(value, start, end) as any
|
|
395
|
+
} finally {
|
|
396
|
+
touched(this, { type: 'bunch', method: 'fill' }, range(0, this[native].length - 1))
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
copyWithin(target: number, start: number, end?: number) {
|
|
401
|
+
try {
|
|
402
|
+
if (end === undefined) return this[native].copyWithin(target, start) as any
|
|
403
|
+
return this[native].copyWithin(target, start, end) as any
|
|
404
|
+
} finally {
|
|
405
|
+
touched(
|
|
406
|
+
this,
|
|
407
|
+
{ type: 'bunch', method: 'copyWithin' },
|
|
408
|
+
// TODO: calculate the range properly
|
|
409
|
+
range(0, this[native].length - 1)
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
// Touch all affected indices with a single allProps call
|
|
413
|
+
}
|
|
414
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { recordTriggerLink } from './debug'
|
|
2
|
+
import { bubbleUpChange, objectsWithDeepWatchers } from './deep-watch-state'
|
|
3
|
+
import { getActiveEffect, isRunning } from './effect-context'
|
|
4
|
+
import { batch, effectTrackers, opaqueEffects } from './effects'
|
|
5
|
+
import { unwrap } from './proxy-state'
|
|
6
|
+
import { watchers } from './tracking'
|
|
7
|
+
import { allProps, type Evolution, options, type ScopedCallback, 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<ScopedCallback>,
|
|
38
|
+
objectWatchers: Map<any, Set<ScopedCallback>>,
|
|
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
|
+
if (deps)
|
|
46
|
+
for (const effect of deps) {
|
|
47
|
+
const runningChain = isRunning(effect)
|
|
48
|
+
if (runningChain) {
|
|
49
|
+
options.skipRunningEffect(effect, runningChain as any)
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
effects.add(effect)
|
|
53
|
+
const trackers = effectTrackers.get(effect)
|
|
54
|
+
recordTriggerLink(sourceEffect, effect, obj, key, evolution)
|
|
55
|
+
if (trackers) {
|
|
56
|
+
for (const tracker of trackers) tracker(obj, evolution, key)
|
|
57
|
+
trackers.delete(effect)
|
|
58
|
+
}
|
|
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<ScopedCallback>()
|
|
86
|
+
if (props) collectEffects(obj, evolution, effects, objectWatchers, [allProps], props)
|
|
87
|
+
else collectEffects(obj, evolution, effects, objectWatchers, objectWatchers.keys())
|
|
88
|
+
options.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<ScopedCallback>()
|
|
111
|
+
const sourceEffect = getActiveEffect()
|
|
112
|
+
|
|
113
|
+
for (const effect of deps) {
|
|
114
|
+
if (!opaqueEffects.has(effect)) continue
|
|
115
|
+
|
|
116
|
+
const runningChain = isRunning(effect)
|
|
117
|
+
if (runningChain) {
|
|
118
|
+
options.skipRunningEffect(effect, runningChain as any)
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
effects.add(effect)
|
|
122
|
+
const trackers = effectTrackers.get(effect)
|
|
123
|
+
recordTriggerLink(sourceEffect, effect, obj, prop, evolution)
|
|
124
|
+
if (trackers) {
|
|
125
|
+
for (const tracker of trackers) tracker(obj, evolution, prop)
|
|
126
|
+
trackers.delete(effect)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (effects.size > 0) {
|
|
131
|
+
options.touched(obj, evolution, [prop], effects)
|
|
132
|
+
batch(Array.from(effects))
|
|
133
|
+
}
|
|
134
|
+
}
|