@yiin/reactive-proxy-state 1.0.1 → 1.0.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/LICENSE +21 -0
- package/README.md +13 -17
- package/dist/computed.d.ts +16 -0
- package/dist/computed.js +59 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/reactive.d.ts +2 -0
- package/dist/reactive.js +99 -0
- package/dist/ref.d.ts +22 -0
- package/dist/ref.js +60 -0
- package/dist/state.d.ts +2 -0
- package/dist/state.js +207 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +25 -0
- package/dist/utils.js +257 -0
- package/dist/watch.d.ts +19 -0
- package/dist/watch.js +74 -0
- package/dist/watchEffect.d.ts +49 -0
- package/dist/watchEffect.js +139 -0
- package/dist/wrapArray.d.ts +2 -0
- package/dist/wrapArray.js +237 -0
- package/dist/wrapMap.d.ts +2 -0
- package/dist/wrapMap.js +241 -0
- package/dist/wrapSet.d.ts +2 -0
- package/dist/wrapSet.js +184 -0
- package/package.json +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
|
|
2
|
+
import { reactive } from './reactive';
|
|
3
|
+
import { wrapMap } from './wrapMap';
|
|
4
|
+
import { wrapSet } from './wrapSet';
|
|
5
|
+
import { track, trigger } from './watchEffect';
|
|
6
|
+
// Pre-allocate type check function
|
|
7
|
+
function isObject(v) {
|
|
8
|
+
return v && typeof v === 'object';
|
|
9
|
+
}
|
|
10
|
+
export function wrapArray(arr, emit, path) {
|
|
11
|
+
// Check wrapper cache first
|
|
12
|
+
const cachedProxy = wrapperCache.get(arr);
|
|
13
|
+
if (cachedProxy)
|
|
14
|
+
return cachedProxy;
|
|
15
|
+
const proxy = new Proxy(arr, {
|
|
16
|
+
get(target, prop, receiver) {
|
|
17
|
+
// Original track call - might be redundant if handled below but keep for now
|
|
18
|
+
track(target, prop);
|
|
19
|
+
// Handle specific array mutation methods first
|
|
20
|
+
switch (prop) {
|
|
21
|
+
case 'push':
|
|
22
|
+
track(target, 'length');
|
|
23
|
+
return function (...items) {
|
|
24
|
+
const oldLength = target.length;
|
|
25
|
+
const result = target.push(...items);
|
|
26
|
+
const newLength = target.length;
|
|
27
|
+
if (items.length > 0) {
|
|
28
|
+
const event = {
|
|
29
|
+
action: 'array-push',
|
|
30
|
+
path: path,
|
|
31
|
+
key: oldLength, // Start index was the old length
|
|
32
|
+
items: items
|
|
33
|
+
};
|
|
34
|
+
emit(event);
|
|
35
|
+
trigger(target, Symbol.iterator);
|
|
36
|
+
if (oldLength !== newLength) {
|
|
37
|
+
trigger(target, 'length');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
case 'pop':
|
|
43
|
+
track(target, 'length');
|
|
44
|
+
return function () {
|
|
45
|
+
if (target.length === 0)
|
|
46
|
+
return undefined;
|
|
47
|
+
const oldLength = target.length;
|
|
48
|
+
const poppedIndex = oldLength - 1;
|
|
49
|
+
const oldValue = target[poppedIndex];
|
|
50
|
+
const result = target.pop();
|
|
51
|
+
const newLength = target.length;
|
|
52
|
+
const event = {
|
|
53
|
+
action: 'array-pop',
|
|
54
|
+
path: path,
|
|
55
|
+
key: poppedIndex,
|
|
56
|
+
oldValue: oldValue
|
|
57
|
+
};
|
|
58
|
+
emit(event);
|
|
59
|
+
trigger(target, Symbol.iterator);
|
|
60
|
+
if (oldLength !== newLength) {
|
|
61
|
+
trigger(target, 'length');
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
65
|
+
case 'shift':
|
|
66
|
+
track(target, 'length');
|
|
67
|
+
return function () {
|
|
68
|
+
if (target.length === 0)
|
|
69
|
+
return undefined;
|
|
70
|
+
const oldLength = target.length;
|
|
71
|
+
const oldValue = target[0];
|
|
72
|
+
const result = target.shift();
|
|
73
|
+
const newLength = target.length;
|
|
74
|
+
const event = {
|
|
75
|
+
action: 'array-shift',
|
|
76
|
+
path: path,
|
|
77
|
+
key: 0,
|
|
78
|
+
oldValue: oldValue
|
|
79
|
+
};
|
|
80
|
+
emit(event);
|
|
81
|
+
trigger(target, Symbol.iterator);
|
|
82
|
+
if (oldLength !== newLength) {
|
|
83
|
+
trigger(target, 'length');
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
};
|
|
87
|
+
case 'unshift':
|
|
88
|
+
track(target, 'length');
|
|
89
|
+
return function (...items) {
|
|
90
|
+
const oldLength = target.length;
|
|
91
|
+
const result = target.unshift(...items);
|
|
92
|
+
const newLength = target.length;
|
|
93
|
+
if (items.length > 0) {
|
|
94
|
+
const event = {
|
|
95
|
+
action: 'array-unshift',
|
|
96
|
+
path: path,
|
|
97
|
+
key: 0,
|
|
98
|
+
items: items
|
|
99
|
+
};
|
|
100
|
+
emit(event);
|
|
101
|
+
trigger(target, Symbol.iterator);
|
|
102
|
+
if (oldLength !== newLength) {
|
|
103
|
+
trigger(target, 'length');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
};
|
|
108
|
+
case 'splice':
|
|
109
|
+
track(target, 'length');
|
|
110
|
+
return function (start, deleteCount, ...items) {
|
|
111
|
+
const oldLength = target.length;
|
|
112
|
+
const actualStart = start < 0 ? Math.max(target.length + start, 0) : Math.min(start, target.length);
|
|
113
|
+
const deleteCountNum = deleteCount === undefined ? target.length - actualStart : Number(deleteCount);
|
|
114
|
+
const actualDeleteCount = Math.min(deleteCountNum, target.length - actualStart);
|
|
115
|
+
const deletedItems = target.slice(actualStart, actualStart + actualDeleteCount);
|
|
116
|
+
const result = target.splice(start, deleteCountNum, ...items);
|
|
117
|
+
const newLength = target.length;
|
|
118
|
+
if (actualDeleteCount > 0 || items.length > 0) {
|
|
119
|
+
const event = {
|
|
120
|
+
action: 'array-splice',
|
|
121
|
+
path: path,
|
|
122
|
+
key: actualStart,
|
|
123
|
+
deleteCount: actualDeleteCount,
|
|
124
|
+
items: items.length > 0 ? items : undefined,
|
|
125
|
+
oldValues: deletedItems.length > 0 ? deletedItems : undefined
|
|
126
|
+
};
|
|
127
|
+
emit(event);
|
|
128
|
+
trigger(target, Symbol.iterator);
|
|
129
|
+
if (oldLength !== newLength) {
|
|
130
|
+
trigger(target, 'length');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
// Handle iteration methods
|
|
136
|
+
case Symbol.iterator:
|
|
137
|
+
case 'values': // values() returns an iterator
|
|
138
|
+
case 'keys': // keys() returns an iterator
|
|
139
|
+
case 'entries': // entries() returns an iterator
|
|
140
|
+
// Track dependency on iteration
|
|
141
|
+
track(target, Symbol.iterator);
|
|
142
|
+
// Fall through to Reflect.get and bind below
|
|
143
|
+
break;
|
|
144
|
+
case 'forEach':
|
|
145
|
+
case 'map':
|
|
146
|
+
case 'filter':
|
|
147
|
+
case 'reduce':
|
|
148
|
+
case 'reduceRight':
|
|
149
|
+
case 'find':
|
|
150
|
+
case 'findIndex':
|
|
151
|
+
case 'every':
|
|
152
|
+
case 'some':
|
|
153
|
+
case 'join': // join depends on iteration
|
|
154
|
+
// These methods depend on iteration
|
|
155
|
+
track(target, Symbol.iterator);
|
|
156
|
+
// Fall through to Reflect.get and bind below
|
|
157
|
+
break;
|
|
158
|
+
case 'length':
|
|
159
|
+
// Explicitly track length access
|
|
160
|
+
track(target, 'length');
|
|
161
|
+
return Reflect.get(target, prop, receiver);
|
|
162
|
+
}
|
|
163
|
+
// Fallback for index access and other properties
|
|
164
|
+
const value = Reflect.get(target, prop, receiver);
|
|
165
|
+
// Handle index access: wrap retrieved element if it's an object
|
|
166
|
+
const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(prop, 10)));
|
|
167
|
+
if (isNumericIndex) {
|
|
168
|
+
// Track access to specific index
|
|
169
|
+
track(target, String(prop));
|
|
170
|
+
if (!isObject(value))
|
|
171
|
+
return value;
|
|
172
|
+
// Check wrapper cache for the element
|
|
173
|
+
const cachedValueProxy = wrapperCache.get(value);
|
|
174
|
+
if (cachedValueProxy)
|
|
175
|
+
return cachedValueProxy;
|
|
176
|
+
// Calculate path for the element
|
|
177
|
+
const propKey = String(prop);
|
|
178
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey; // Fix pathKey generation for index 0
|
|
179
|
+
let newPath = getPathConcat(pathKey);
|
|
180
|
+
if (newPath === undefined) {
|
|
181
|
+
newPath = path.concat(propKey);
|
|
182
|
+
setPathConcat(pathKey, newPath);
|
|
183
|
+
}
|
|
184
|
+
// Wrap based on type (no longer passing seen)
|
|
185
|
+
if (Array.isArray(value))
|
|
186
|
+
return wrapArray(value, emit, newPath);
|
|
187
|
+
if (value instanceof Map)
|
|
188
|
+
return wrapMap(value, emit, newPath);
|
|
189
|
+
if (value instanceof Set)
|
|
190
|
+
return wrapSet(value, emit, newPath);
|
|
191
|
+
if (value instanceof Date)
|
|
192
|
+
return new Date(value.getTime()); // Dates are not proxied
|
|
193
|
+
// Default to reactive for plain objects
|
|
194
|
+
return reactive(value, emit, newPath);
|
|
195
|
+
}
|
|
196
|
+
// For non-numeric properties or properties that aren't objects, return value directly
|
|
197
|
+
// Also handle functions bound to the target
|
|
198
|
+
if (typeof value === 'function') {
|
|
199
|
+
return value.bind(target);
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
},
|
|
203
|
+
set(target, prop, value, receiver) {
|
|
204
|
+
const oldValue = target[prop];
|
|
205
|
+
// Fast path for primitive equality
|
|
206
|
+
if (oldValue === value)
|
|
207
|
+
return true;
|
|
208
|
+
// Deep equality check with new WeakMap
|
|
209
|
+
if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value, new WeakMap()))
|
|
210
|
+
return true;
|
|
211
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
212
|
+
const result = Reflect.set(target, prop, value, receiver);
|
|
213
|
+
const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(String(prop))));
|
|
214
|
+
if (result && (!descriptor || !descriptor.set || isNumericIndex)) {
|
|
215
|
+
const propKey = String(prop);
|
|
216
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey; // Fix pathKey generation for index 0
|
|
217
|
+
let newPath = getPathConcat(pathKey);
|
|
218
|
+
if (newPath === undefined) {
|
|
219
|
+
newPath = path.concat(propKey);
|
|
220
|
+
setPathConcat(pathKey, newPath);
|
|
221
|
+
}
|
|
222
|
+
const event = {
|
|
223
|
+
action: 'set',
|
|
224
|
+
path: newPath,
|
|
225
|
+
oldValue,
|
|
226
|
+
newValue: value
|
|
227
|
+
};
|
|
228
|
+
emit(event);
|
|
229
|
+
trigger(target, prop);
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
// Cache the newly created proxy before returning
|
|
235
|
+
wrapperCache.set(arr, proxy);
|
|
236
|
+
return proxy;
|
|
237
|
+
}
|
package/dist/wrapMap.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
|
|
2
|
+
import { reactive } from './reactive';
|
|
3
|
+
import { wrapArray } from './wrapArray';
|
|
4
|
+
import { wrapSet } from './wrapSet';
|
|
5
|
+
import { track, trigger } from './watchEffect';
|
|
6
|
+
export function wrapMap(map, emit, path) {
|
|
7
|
+
// Check wrapper cache first
|
|
8
|
+
const cachedProxy = wrapperCache.get(map);
|
|
9
|
+
if (cachedProxy)
|
|
10
|
+
return cachedProxy;
|
|
11
|
+
const proxy = new Proxy(map, {
|
|
12
|
+
get(target, prop, receiver) {
|
|
13
|
+
// Original track call - Keep for properties not explicitly handled
|
|
14
|
+
track(target, prop);
|
|
15
|
+
// --- Specific Method Handlers ---
|
|
16
|
+
if (prop === 'set') {
|
|
17
|
+
return function (key, value) {
|
|
18
|
+
const existed = target.has(key);
|
|
19
|
+
const oldValue = target.get(key);
|
|
20
|
+
const oldSize = target.size;
|
|
21
|
+
if (oldValue === value)
|
|
22
|
+
return receiver;
|
|
23
|
+
if (oldValue && typeof oldValue === 'object' && value && typeof value === 'object' && deepEqual(oldValue, value, new WeakMap()))
|
|
24
|
+
return receiver;
|
|
25
|
+
target.set(key, value);
|
|
26
|
+
const newSize = target.size;
|
|
27
|
+
const pathKey = path.join('.');
|
|
28
|
+
let cachedPath = getPathConcat(pathKey);
|
|
29
|
+
if (cachedPath === undefined) {
|
|
30
|
+
cachedPath = path;
|
|
31
|
+
setPathConcat(pathKey, cachedPath);
|
|
32
|
+
}
|
|
33
|
+
const event = {
|
|
34
|
+
action: 'map-set',
|
|
35
|
+
path: cachedPath,
|
|
36
|
+
key,
|
|
37
|
+
oldValue,
|
|
38
|
+
newValue: value
|
|
39
|
+
};
|
|
40
|
+
emit(event);
|
|
41
|
+
if (!existed) {
|
|
42
|
+
trigger(target, Symbol.iterator);
|
|
43
|
+
if (oldSize !== newSize) {
|
|
44
|
+
trigger(target, 'size');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
trigger(target, String(key));
|
|
49
|
+
}
|
|
50
|
+
return receiver;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (prop === 'delete') {
|
|
54
|
+
return function (key) {
|
|
55
|
+
const existed = target.has(key);
|
|
56
|
+
if (!existed)
|
|
57
|
+
return false;
|
|
58
|
+
const oldValue = target.get(key);
|
|
59
|
+
const oldSize = target.size;
|
|
60
|
+
const result = target.delete(key);
|
|
61
|
+
const newSize = target.size;
|
|
62
|
+
if (result) {
|
|
63
|
+
const pathKey = path.join('.');
|
|
64
|
+
let cachedPath = getPathConcat(pathKey);
|
|
65
|
+
if (cachedPath === undefined) {
|
|
66
|
+
cachedPath = path;
|
|
67
|
+
setPathConcat(pathKey, cachedPath);
|
|
68
|
+
}
|
|
69
|
+
const event = {
|
|
70
|
+
action: 'map-delete',
|
|
71
|
+
path: cachedPath,
|
|
72
|
+
key,
|
|
73
|
+
oldValue
|
|
74
|
+
};
|
|
75
|
+
emit(event);
|
|
76
|
+
trigger(target, Symbol.iterator);
|
|
77
|
+
if (oldSize !== newSize) {
|
|
78
|
+
trigger(target, 'size');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (prop === 'clear') {
|
|
85
|
+
return function () {
|
|
86
|
+
const oldSize = target.size;
|
|
87
|
+
if (oldSize === 0)
|
|
88
|
+
return;
|
|
89
|
+
target.clear();
|
|
90
|
+
const newSize = target.size;
|
|
91
|
+
const event = {
|
|
92
|
+
action: 'map-clear',
|
|
93
|
+
path: path,
|
|
94
|
+
key: null
|
|
95
|
+
};
|
|
96
|
+
emit(event);
|
|
97
|
+
// Clear triggers both iterator and size
|
|
98
|
+
trigger(target, Symbol.iterator);
|
|
99
|
+
if (oldSize !== newSize) {
|
|
100
|
+
trigger(target, 'size');
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (prop === 'get') {
|
|
105
|
+
// Return function that tracks the specific key upon execution
|
|
106
|
+
return function (key) {
|
|
107
|
+
// Track access to this specific key when 'get' is called
|
|
108
|
+
track(target, String(key));
|
|
109
|
+
const value = target.get(key);
|
|
110
|
+
if (!value || typeof value !== 'object')
|
|
111
|
+
return value; // Fast path for non-objects
|
|
112
|
+
// Check wrapper cache for the value
|
|
113
|
+
const cachedValueProxy = wrapperCache.get(value);
|
|
114
|
+
if (cachedValueProxy)
|
|
115
|
+
return cachedValueProxy;
|
|
116
|
+
// Calculate path for the value
|
|
117
|
+
const keyString = String(key);
|
|
118
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
|
|
119
|
+
let newPath = getPathConcat(pathKey);
|
|
120
|
+
if (newPath === undefined) {
|
|
121
|
+
newPath = path.concat(keyString);
|
|
122
|
+
setPathConcat(pathKey, newPath);
|
|
123
|
+
}
|
|
124
|
+
// Wrap based on type (no longer passing seen)
|
|
125
|
+
if (value instanceof Map)
|
|
126
|
+
return wrapMap(value, emit, newPath);
|
|
127
|
+
if (value instanceof Set)
|
|
128
|
+
return wrapSet(value, emit, newPath);
|
|
129
|
+
if (Array.isArray(value))
|
|
130
|
+
return wrapArray(value, emit, newPath);
|
|
131
|
+
if (value instanceof Date)
|
|
132
|
+
return new Date(value.getTime()); // Dates are not proxied
|
|
133
|
+
// Default to reactive for plain objects
|
|
134
|
+
return reactive(value, emit, newPath);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (prop === 'has') {
|
|
138
|
+
// Track dependency on iteration/structure when 'has' is accessed
|
|
139
|
+
track(target, Symbol.iterator);
|
|
140
|
+
return function (key) {
|
|
141
|
+
// Track specific key when 'has' is called
|
|
142
|
+
track(target, String(key));
|
|
143
|
+
return target.has(key);
|
|
144
|
+
}.bind(target); // Bind to original target for correct 'this'
|
|
145
|
+
}
|
|
146
|
+
// --- Iteration Methods ---
|
|
147
|
+
if (prop === Symbol.iterator || prop === 'entries' || prop === 'values' || prop === 'keys' || prop === 'forEach') {
|
|
148
|
+
// Track dependency on iteration when these methods are accessed
|
|
149
|
+
track(target, Symbol.iterator);
|
|
150
|
+
const originalMethod = Reflect.get(target, prop, receiver);
|
|
151
|
+
// Return custom iterators/forEach that wrap values
|
|
152
|
+
if (prop === 'forEach') {
|
|
153
|
+
return (callbackfn, thisArg) => {
|
|
154
|
+
// Use proxied .entries() to get wrapped values + tracking
|
|
155
|
+
const entriesIterator = proxy.entries();
|
|
156
|
+
for (const [key, value] of entriesIterator) {
|
|
157
|
+
callbackfn.call(thisArg, value, key, proxy);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Handle Symbol.iterator, entries, values, keys
|
|
162
|
+
return function* (...args) {
|
|
163
|
+
const iterator = originalMethod.apply(target, args);
|
|
164
|
+
for (const entry of iterator) {
|
|
165
|
+
let keyToWrap = entry; // Default for keys()
|
|
166
|
+
let valueToWrap = entry; // Default for values()
|
|
167
|
+
let isEntry = false;
|
|
168
|
+
if (prop === 'entries' || prop === Symbol.iterator) {
|
|
169
|
+
keyToWrap = entry[0];
|
|
170
|
+
valueToWrap = entry[1];
|
|
171
|
+
isEntry = true;
|
|
172
|
+
}
|
|
173
|
+
// Wrap key if object
|
|
174
|
+
let wrappedKey = keyToWrap;
|
|
175
|
+
if (isEntry && keyToWrap && typeof keyToWrap === 'object') {
|
|
176
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${String(keyToWrap)}` : String(keyToWrap); // Or find better key path?
|
|
177
|
+
let keyPath = getPathConcat(pathKey); // Reuse paths where possible
|
|
178
|
+
if (keyPath === undefined) {
|
|
179
|
+
keyPath = path.concat(String(keyToWrap)); // Simplification for key path
|
|
180
|
+
setPathConcat(pathKey, keyPath);
|
|
181
|
+
}
|
|
182
|
+
// TODO: Decide if Map keys should be deeply reactive
|
|
183
|
+
wrappedKey = reactive(keyToWrap, emit, keyPath);
|
|
184
|
+
}
|
|
185
|
+
// Wrap value if object
|
|
186
|
+
let wrappedValue = valueToWrap;
|
|
187
|
+
if (valueToWrap && typeof valueToWrap === 'object') {
|
|
188
|
+
const cachedValueProxy = wrapperCache.get(valueToWrap);
|
|
189
|
+
if (cachedValueProxy) {
|
|
190
|
+
wrappedValue = cachedValueProxy;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const keyString = String(keyToWrap);
|
|
194
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
|
|
195
|
+
let newPath = getPathConcat(pathKey);
|
|
196
|
+
if (newPath === undefined) {
|
|
197
|
+
newPath = path.concat(keyString);
|
|
198
|
+
setPathConcat(pathKey, newPath);
|
|
199
|
+
}
|
|
200
|
+
if (valueToWrap instanceof Map)
|
|
201
|
+
wrappedValue = wrapMap(valueToWrap, emit, newPath);
|
|
202
|
+
else if (valueToWrap instanceof Set)
|
|
203
|
+
wrappedValue = wrapSet(valueToWrap, emit, newPath);
|
|
204
|
+
else if (Array.isArray(valueToWrap))
|
|
205
|
+
wrappedValue = wrapArray(valueToWrap, emit, newPath);
|
|
206
|
+
else if (valueToWrap instanceof Date)
|
|
207
|
+
wrappedValue = new Date(valueToWrap.getTime());
|
|
208
|
+
else
|
|
209
|
+
wrappedValue = reactive(valueToWrap, emit, newPath);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Yield based on iterator type
|
|
213
|
+
if (prop === 'entries' || prop === Symbol.iterator) {
|
|
214
|
+
yield [wrappedKey, wrappedValue];
|
|
215
|
+
}
|
|
216
|
+
else if (prop === 'values') {
|
|
217
|
+
yield wrappedValue;
|
|
218
|
+
}
|
|
219
|
+
else { // keys
|
|
220
|
+
yield wrappedKey;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
// --- Size Property ---
|
|
226
|
+
if (prop === 'size') {
|
|
227
|
+
// Explicitly track size access
|
|
228
|
+
track(target, 'size');
|
|
229
|
+
// Return the size directly from the target to avoid potential 'this' issues with Reflect.get
|
|
230
|
+
return target.size;
|
|
231
|
+
}
|
|
232
|
+
// --- Fallback for other properties/methods ---
|
|
233
|
+
const value = Reflect.get(target, prop, receiver);
|
|
234
|
+
// For non-function properties or bound functions, return the value as is.
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
// Cache the newly created proxy before returning
|
|
239
|
+
wrapperCache.set(map, proxy);
|
|
240
|
+
return proxy;
|
|
241
|
+
}
|
package/dist/wrapSet.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { getPathConcat, setPathConcat, wrapperCache } from './utils';
|
|
2
|
+
import { reactive } from './reactive';
|
|
3
|
+
import { wrapArray } from './wrapArray';
|
|
4
|
+
import { wrapMap } from './wrapMap';
|
|
5
|
+
import { track, trigger } from './watchEffect';
|
|
6
|
+
export function wrapSet(set, emit, path) {
|
|
7
|
+
// Check wrapper cache first
|
|
8
|
+
const cachedProxy = wrapperCache.get(set);
|
|
9
|
+
if (cachedProxy)
|
|
10
|
+
return cachedProxy;
|
|
11
|
+
const proxy = new Proxy(set, {
|
|
12
|
+
get(target, prop, receiver) {
|
|
13
|
+
// Original track call
|
|
14
|
+
track(target, prop);
|
|
15
|
+
if (prop === 'add') {
|
|
16
|
+
return function (value) {
|
|
17
|
+
const existed = target.has(value);
|
|
18
|
+
const oldSize = target.size;
|
|
19
|
+
if (!existed) {
|
|
20
|
+
target.add(value);
|
|
21
|
+
const newSize = target.size;
|
|
22
|
+
const event = {
|
|
23
|
+
action: 'set-add',
|
|
24
|
+
path: path,
|
|
25
|
+
value: value
|
|
26
|
+
};
|
|
27
|
+
emit(event);
|
|
28
|
+
trigger(target, Symbol.iterator);
|
|
29
|
+
if (oldSize !== newSize) {
|
|
30
|
+
trigger(target, 'size');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return receiver;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (prop === 'delete') {
|
|
37
|
+
return function (value) {
|
|
38
|
+
const existed = target.has(value);
|
|
39
|
+
const oldSize = target.size;
|
|
40
|
+
if (existed) {
|
|
41
|
+
const oldValue = value; // The value being deleted is the oldValue
|
|
42
|
+
const result = target.delete(value);
|
|
43
|
+
const newSize = target.size;
|
|
44
|
+
if (result) {
|
|
45
|
+
const event = {
|
|
46
|
+
action: 'set-delete',
|
|
47
|
+
path: path,
|
|
48
|
+
value: value, // value being deleted
|
|
49
|
+
oldValue: oldValue // Add oldValue to the event
|
|
50
|
+
};
|
|
51
|
+
emit(event);
|
|
52
|
+
trigger(target, Symbol.iterator);
|
|
53
|
+
if (oldSize !== newSize) {
|
|
54
|
+
trigger(target, 'size');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (prop === 'clear') {
|
|
63
|
+
return function () {
|
|
64
|
+
const oldSize = target.size;
|
|
65
|
+
if (oldSize === 0)
|
|
66
|
+
return;
|
|
67
|
+
target.clear();
|
|
68
|
+
const newSize = target.size;
|
|
69
|
+
const event = {
|
|
70
|
+
action: 'set-clear', // Create 'set-clear' action
|
|
71
|
+
path: path,
|
|
72
|
+
value: null
|
|
73
|
+
};
|
|
74
|
+
emit(event);
|
|
75
|
+
// Clear triggers both iterator and size
|
|
76
|
+
trigger(target, Symbol.iterator);
|
|
77
|
+
if (oldSize !== newSize) {
|
|
78
|
+
trigger(target, 'size');
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (prop === 'has') {
|
|
83
|
+
// 'has' depends on the specific value and iteration/size
|
|
84
|
+
track(target, Symbol.iterator); // Track iteration implicitly
|
|
85
|
+
return function (value) {
|
|
86
|
+
// Also track the specific value when 'has' is called
|
|
87
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol') {
|
|
88
|
+
track(target, String(value));
|
|
89
|
+
}
|
|
90
|
+
// How to track non-primitive values?
|
|
91
|
+
// We might need a different strategy for object keys in Sets/Maps if deep reactivity on keys is needed
|
|
92
|
+
return target.has(value);
|
|
93
|
+
}.bind(target); // Bind to original target
|
|
94
|
+
}
|
|
95
|
+
// Handle iteration methods - custom implementation already tracks
|
|
96
|
+
if (prop === 'values' || prop === Symbol.iterator || prop === 'entries' || prop === 'keys' || prop === 'forEach') {
|
|
97
|
+
// Track dependency on iteration
|
|
98
|
+
track(target, Symbol.iterator);
|
|
99
|
+
const originalMethod = Reflect.get(target, prop, receiver);
|
|
100
|
+
// Return a function that yields wrapped values (or calls forEach)
|
|
101
|
+
if (prop === 'forEach') {
|
|
102
|
+
return (callbackfn, thisArg) => {
|
|
103
|
+
// Simplified forEach based on values iterator
|
|
104
|
+
const valuesIterator = proxy.values(); // Use the proxied values() to get wrapped values + tracking
|
|
105
|
+
for (const value of valuesIterator) {
|
|
106
|
+
callbackfn.call(thisArg, value, value, proxy);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// Handle Symbol.iterator, values, keys, entries
|
|
111
|
+
return function* (...args) {
|
|
112
|
+
let index = 0; // Index for path generation
|
|
113
|
+
const iterator = originalMethod.apply(target, args);
|
|
114
|
+
for (const entry of iterator) {
|
|
115
|
+
let valueToWrap = entry; // Default for values(), keys()
|
|
116
|
+
let mapKey = undefined; // For entries()
|
|
117
|
+
// For entries(), the entry is [key, value]
|
|
118
|
+
if (prop === 'entries') {
|
|
119
|
+
mapKey = entry[0];
|
|
120
|
+
valueToWrap = entry[1];
|
|
121
|
+
}
|
|
122
|
+
// Track access to each element during iteration
|
|
123
|
+
track(target, String(index));
|
|
124
|
+
let wrappedValue = valueToWrap;
|
|
125
|
+
if (valueToWrap && typeof valueToWrap === 'object') {
|
|
126
|
+
// Check wrapper cache first
|
|
127
|
+
const cachedValueProxy = wrapperCache.get(valueToWrap);
|
|
128
|
+
if (cachedValueProxy) {
|
|
129
|
+
wrappedValue = cachedValueProxy;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Calculate path using index (or mapKey if available and primitive)
|
|
133
|
+
const keyForPath = (mapKey !== undefined && (typeof mapKey !== 'object') ? String(mapKey) : String(index));
|
|
134
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${keyForPath}` : keyForPath;
|
|
135
|
+
let newPath = getPathConcat(pathKey);
|
|
136
|
+
if (newPath === undefined) {
|
|
137
|
+
newPath = path.concat(keyForPath);
|
|
138
|
+
setPathConcat(pathKey, newPath);
|
|
139
|
+
}
|
|
140
|
+
// Wrap based on type
|
|
141
|
+
if (valueToWrap instanceof Map)
|
|
142
|
+
wrappedValue = wrapMap(valueToWrap, emit, newPath);
|
|
143
|
+
else if (valueToWrap instanceof Set)
|
|
144
|
+
wrappedValue = wrapSet(valueToWrap, emit, newPath);
|
|
145
|
+
else if (Array.isArray(valueToWrap))
|
|
146
|
+
wrappedValue = wrapArray(valueToWrap, emit, newPath);
|
|
147
|
+
else if (valueToWrap instanceof Date)
|
|
148
|
+
wrappedValue = new Date(valueToWrap.getTime());
|
|
149
|
+
else
|
|
150
|
+
wrappedValue = reactive(valueToWrap, emit, newPath);
|
|
151
|
+
// Note: We don't cache the *wrapped* value here, wrap functions handle caching
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Yield the original or wrapped value/entry
|
|
155
|
+
if (prop === 'entries') {
|
|
156
|
+
yield [mapKey, wrappedValue];
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
yield wrappedValue;
|
|
160
|
+
}
|
|
161
|
+
index++;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (prop === 'size') {
|
|
166
|
+
// Explicitly track size access
|
|
167
|
+
track(target, 'size');
|
|
168
|
+
// Return the size directly from the target to avoid potential 'this' issues with Reflect.get
|
|
169
|
+
return target.size;
|
|
170
|
+
}
|
|
171
|
+
// Fallback for other properties (should be minimal for Set)
|
|
172
|
+
const value = Reflect.get(target, prop, receiver);
|
|
173
|
+
// REMOVED - Tracking handled above
|
|
174
|
+
// if (prop === 'size') { ... }
|
|
175
|
+
// If the property is a function (and not handled above), return it directly.
|
|
176
|
+
// Reflect.get preserves the correct 'this' binding (receiver).
|
|
177
|
+
// REMOVED: return value.bind(target);
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
// Cache the newly created proxy before returning
|
|
182
|
+
wrapperCache.set(set, proxy);
|
|
183
|
+
return proxy;
|
|
184
|
+
}
|