ivue 1.5.7 → 1.6.1
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 +5 -5
- package/dist/env.d.ts +1 -0
- package/dist/index.es.js +212 -86
- package/dist/index.umd.js +1 -1
- package/dist/ivue.d.ts +134 -71
- package/lib/Reactive.ts +338 -0
- package/lib/__tests__/ivue.vitest.spec.ts +1403 -150
- package/lib/env.d.ts +1 -0
- package/lib/index.ts +1 -0
- package/lib/ivue.ts +667 -252
- package/lib/kernel.ts +18 -0
- package/package.json +16 -7
package/lib/ivue.ts
CHANGED
|
@@ -1,40 +1,65 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/ban-types */
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
1
|
import type { ComputedRef, ExtractPropTypes, Ref, ToRef } from 'vue';
|
|
4
|
-
import { computed, reactive, ref, shallowRef, toRef } from 'vue';
|
|
2
|
+
import { computed, markRaw, reactive, ref, shallowRef, toRef } from 'vue';
|
|
5
3
|
import type { Ref as DemiRef } from 'vue-demi';
|
|
6
4
|
|
|
5
|
+
/** Cached global methods. */
|
|
6
|
+
const isArray = Array.isArray;
|
|
7
|
+
const createObject = Object.create;
|
|
8
|
+
const defineProperty = Object.defineProperty;
|
|
9
|
+
const getPrototypeOf = Object.getPrototypeOf;
|
|
10
|
+
const getOwnPropertyNames = Object.getOwnPropertyNames;
|
|
11
|
+
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
12
|
+
const getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors;
|
|
13
|
+
|
|
14
|
+
|
|
7
15
|
/** Types */
|
|
8
16
|
/**
|
|
9
17
|
* IVue core reactive instance type with an extended .toRefs() method added.
|
|
10
18
|
*/
|
|
11
|
-
export type IVue<T extends AnyClass> = InstanceType<T> &
|
|
19
|
+
export type IVue<T extends AnyClass> = InstanceType<T> & {
|
|
20
|
+
toRefs: IVueToRefsFn<T>;
|
|
21
|
+
clone: IVueClone<T>;
|
|
22
|
+
};
|
|
12
23
|
|
|
13
24
|
/**
|
|
14
|
-
*
|
|
25
|
+
* Type definition for `.clone()` method creates a deep clone of the ivue instance.
|
|
15
26
|
*/
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
export type IVueClone<T extends AnyClass> = (
|
|
28
|
+
...cloneArgs: CloneArgsFor<InstanceType<T>>
|
|
29
|
+
) => IVue<T>;
|
|
19
30
|
|
|
20
31
|
/**
|
|
21
32
|
* Type definition for `.toRefs()` method converts reactive class properties to composable .value properties.
|
|
22
33
|
* But if props = true OR unwrap = true is specified, the refs will be unwrapped refs to be able to be merged with the the root class properties without losing reactivity.
|
|
23
34
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
/** Extract only non-method keys from a type */
|
|
36
|
+
type NonMethodKeys<T> = {
|
|
37
|
+
[K in keyof T]: T[K] extends AnyFn ? never : K;
|
|
38
|
+
}[keyof T];
|
|
39
|
+
|
|
40
|
+
/** Create a type with only data properties and accessors */
|
|
41
|
+
type DataProperties<T> = Pick<T, NonMethodKeys<T>>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Simplified IVueToRefsFn that only works with data properties
|
|
45
|
+
*/
|
|
46
|
+
interface IVueToRefsFn<T extends AnyClass> {
|
|
47
|
+
<P extends keyof IVueRefs<InstanceType<T>>>(props: P[]): Pick<
|
|
26
48
|
IVueRefs<InstanceType<T>>,
|
|
27
49
|
P
|
|
28
50
|
>;
|
|
29
|
-
|
|
51
|
+
|
|
52
|
+
<P extends keyof IVueRefs<InstanceType<T>>>(props: P[], unwrap: false): Pick<
|
|
30
53
|
IVueRefs<InstanceType<T>>,
|
|
31
54
|
P
|
|
32
55
|
>;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
P
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
|
|
57
|
+
<P extends keyof DataProperties<InstanceType<T>>>(
|
|
58
|
+
props: P[],
|
|
59
|
+
unwrap: true
|
|
60
|
+
): Pick<DataProperties<InstanceType<T>>, P>;
|
|
61
|
+
|
|
62
|
+
(props: true): DataProperties<InstanceType<T>>;
|
|
38
63
|
(props: false): IVueRefs<InstanceType<T>>;
|
|
39
64
|
(): IVueRefs<InstanceType<T>>;
|
|
40
65
|
}
|
|
@@ -44,7 +69,7 @@ export interface IVueToRefsFn<T extends AnyClass> {
|
|
|
44
69
|
* also knows NOT to convert functions to .value Refs but to leave them as is.
|
|
45
70
|
*/
|
|
46
71
|
export type IVueRefs<T> = {
|
|
47
|
-
[K in keyof T
|
|
72
|
+
[K in keyof T as T[K] extends AnyFn ? never : K]: ToRef<T[K]>;
|
|
48
73
|
};
|
|
49
74
|
|
|
50
75
|
/**
|
|
@@ -54,14 +79,14 @@ export type IVueRefs<T> = {
|
|
|
54
79
|
* are difficult to fully resolve to bare values without this utility.
|
|
55
80
|
* Also support Vue 3 Demi for vue-use library support.
|
|
56
81
|
*/
|
|
57
|
-
|
|
82
|
+
type UnwrapRefRecursively<T = any> = T extends Ref | DemiRef
|
|
58
83
|
? UnwrapRefRecursively<T['value']>
|
|
59
84
|
: T;
|
|
60
85
|
|
|
61
86
|
/**
|
|
62
|
-
* Helper type for Use type, unwraps any type of Vue 3 composable down to its bare types.
|
|
87
|
+
* Helper type for Use type, unwraps any type of Vue 3 composable return object down to its bare types.
|
|
63
88
|
*/
|
|
64
|
-
|
|
89
|
+
type UnwrapComposableRefs<T> = T extends Ref | DemiRef
|
|
65
90
|
? UnwrapRefRecursively<T>
|
|
66
91
|
: {
|
|
67
92
|
[K in keyof T]: T[K] extends Ref | DemiRef
|
|
@@ -74,10 +99,10 @@ export type Unwrap<T> = T extends Ref | DemiRef
|
|
|
74
99
|
*/
|
|
75
100
|
export type Use<T = any> = T extends Ref | DemiRef
|
|
76
101
|
? UnwrapRefRecursively<T>
|
|
77
|
-
:
|
|
102
|
+
: UnwrapComposableRefs<T extends AnyFn ? ReturnType<T> : T>;
|
|
78
103
|
|
|
79
104
|
/**
|
|
80
|
-
* Extracts object defined emit types by converting them to a plain interface
|
|
105
|
+
* Extracts object defined emit types by converting them to a plain interface.
|
|
81
106
|
*/
|
|
82
107
|
export type ExtractEmitTypes<T extends Record<string, any>> =
|
|
83
108
|
UnionToIntersection<
|
|
@@ -109,17 +134,17 @@ export type AnyFn = (...args: any[]) => any;
|
|
|
109
134
|
/**
|
|
110
135
|
* Any JavaScript class of any type.
|
|
111
136
|
*/
|
|
112
|
-
export type AnyClass =
|
|
137
|
+
export type AnyClass = new (...args: any[]) => any;
|
|
113
138
|
|
|
114
139
|
/**
|
|
115
|
-
*
|
|
140
|
+
* Accessors map type.
|
|
116
141
|
*/
|
|
117
|
-
|
|
142
|
+
type Accessors = Map<string, PropertyDescriptor>;
|
|
118
143
|
|
|
119
144
|
/**
|
|
120
145
|
* Computeds hash map type.
|
|
121
146
|
*/
|
|
122
|
-
|
|
147
|
+
type Computeds = Record<string, ComputedRef>;
|
|
123
148
|
|
|
124
149
|
/**
|
|
125
150
|
* Infer paramaters of a constructor function of a Class.
|
|
@@ -135,23 +160,6 @@ export type PrefixKeys<T, P extends string | undefined = undefined> = {
|
|
|
135
160
|
[K in Extract<keyof T, string> as P extends string ? `${P}${K}` : K]: T[K];
|
|
136
161
|
};
|
|
137
162
|
|
|
138
|
-
/**
|
|
139
|
-
* Get Interface's property's function arguments Parmeters<F>
|
|
140
|
-
*/
|
|
141
|
-
export type IFnParameters<
|
|
142
|
-
T extends Record<any, any>,
|
|
143
|
-
K extends string
|
|
144
|
-
> = Parameters<Required<Pick<T, K>>[K]>;
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K]
|
|
148
|
-
*/
|
|
149
|
-
export type IFnParameter<
|
|
150
|
-
T extends Record<any, any>,
|
|
151
|
-
P extends keyof T,
|
|
152
|
-
K extends number
|
|
153
|
-
> = FnParameter<ValueOf<T, P>, K>;
|
|
154
|
-
|
|
155
163
|
/**
|
|
156
164
|
* Get function arguments Parmeters<F> parameter by key K
|
|
157
165
|
*/
|
|
@@ -160,80 +168,226 @@ export type FnParameter<F extends AnyFn, K extends number> = Parameters<F>[K];
|
|
|
160
168
|
/**
|
|
161
169
|
* Convert Union Type to Intersection Type.
|
|
162
170
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
)
|
|
171
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
|
172
|
+
k: infer I
|
|
173
|
+
) => void
|
|
166
174
|
? I
|
|
167
175
|
: never;
|
|
168
176
|
|
|
169
177
|
/**
|
|
170
178
|
* Convert Record to Union Type.
|
|
171
179
|
*/
|
|
172
|
-
|
|
180
|
+
type RecordToUnion<T extends Record<string, any>> = T[keyof T];
|
|
173
181
|
|
|
174
182
|
/**
|
|
175
183
|
* Gets object T property by key [K].
|
|
176
184
|
*/
|
|
177
|
-
|
|
185
|
+
type ValueOf<T extends Record<any, any>, K> = T[K];
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Props Map Value type.
|
|
189
|
+
*/
|
|
190
|
+
type PropsMapValue = {
|
|
191
|
+
allProps: Map<string, PropKind>;
|
|
192
|
+
onlyProps: Set<string>;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Accessors and Methods map type.
|
|
197
|
+
*/
|
|
198
|
+
type AccessorsMethodsMap = {
|
|
199
|
+
accessors: Accessors;
|
|
200
|
+
methods: Set<string>;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export type IVueInstanceBase = {
|
|
204
|
+
init?: (...args: any[]) => void;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* IVue static configuration interface.
|
|
209
|
+
*/
|
|
210
|
+
export interface IVueStaticConfig<T> {
|
|
211
|
+
ivueGlobalStore?: boolean;
|
|
212
|
+
ivueCloneByReference?: Set<keyof T>;
|
|
213
|
+
ivueDisableReactivity?: Set<keyof T>;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* IVue constructor type.
|
|
218
|
+
*/
|
|
219
|
+
export type IVueConstructor<T> = new (...args: any[]) => T;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* IVue class type with static configuration.
|
|
223
|
+
*/
|
|
224
|
+
export type IVueClass<T> = IVueConstructor<T> & IVueStaticConfig<T>;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get IVue init() method arguments IVueInitArgs<T>
|
|
228
|
+
*/
|
|
229
|
+
export type IVueInitArgs<T extends IVueInstanceBase> = T['init'] extends (
|
|
230
|
+
...args: any[]
|
|
231
|
+
) => any
|
|
232
|
+
? Parameters<T['init']>
|
|
233
|
+
: never;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get Interface's method keys MethodKeys<T>
|
|
237
|
+
*/
|
|
238
|
+
type MethodKeys<T> = {
|
|
239
|
+
[P in keyof T]: T[P] extends (...args: any[]) => any ? P : never;
|
|
240
|
+
}[keyof T];
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get Interface's property's function arguments Parameters<F>
|
|
244
|
+
*/
|
|
245
|
+
export type IFnParameters<
|
|
246
|
+
T extends Record<any, any> | InstanceType<AnyClass>,
|
|
247
|
+
K extends MethodKeys<T>
|
|
248
|
+
> = Parameters<Extract<Required<Pick<T, K>>[K], AnyFn>>;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K]
|
|
252
|
+
*/
|
|
253
|
+
export type IFnParameter<
|
|
254
|
+
T extends Record<any, any>,
|
|
255
|
+
P extends keyof T,
|
|
256
|
+
K extends number
|
|
257
|
+
> = FnParameter<ValueOf<T, P>, K>;
|
|
258
|
+
|
|
259
|
+
export type IVueDeepCloneArgsMap = Map<AnyClass, any[]>;
|
|
260
|
+
|
|
261
|
+
type CloneArgsFor<T> = T extends {
|
|
262
|
+
init: (isClone: boolean, ...rest: infer A) => any;
|
|
263
|
+
}
|
|
264
|
+
? A
|
|
265
|
+
: [];
|
|
178
266
|
/** Types End. */
|
|
179
267
|
|
|
268
|
+
export const IVUE_INSTANCE_SYMBOL = Symbol('IVUE_INSTANCE');
|
|
269
|
+
|
|
180
270
|
/**
|
|
181
|
-
*
|
|
182
|
-
* @see {
|
|
271
|
+
* Stores accessors for each class prototype processed by
|
|
272
|
+
* @see {getClassAccessorsMethodsMap}. Uses WeakMap to allow garbage collection
|
|
273
|
+
* of unused class accessors, ensuring memory efficiency in long-running applications.
|
|
183
274
|
*/
|
|
184
|
-
export
|
|
275
|
+
export let accessorsMethodsMaps = new WeakMap<object, AccessorsMethodsMap>();
|
|
185
276
|
|
|
186
277
|
/**
|
|
187
|
-
* Get
|
|
278
|
+
* Get accessors of an entire class prototype ancestors chain as a Map.
|
|
188
279
|
* Completely emulates JavaScript class inheritance chain for getters and setters.
|
|
189
280
|
*
|
|
190
281
|
* @param className
|
|
191
|
-
* @return {
|
|
282
|
+
* @return {AccessorsMethodsMap}
|
|
192
283
|
*/
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
284
|
+
const getClassAccessorsMethodsMap = (
|
|
285
|
+
className: AnyClass
|
|
286
|
+
): AccessorsMethodsMap => {
|
|
287
|
+
if (accessorsMethodsMaps.has(className)) {
|
|
288
|
+
return accessorsMethodsMaps.get(className) as AccessorsMethodsMap;
|
|
197
289
|
}
|
|
198
290
|
|
|
199
|
-
const
|
|
291
|
+
const savedAccessors: Accessors = new Map();
|
|
292
|
+
const savedMethods: Set<string> = new Set();
|
|
200
293
|
|
|
201
294
|
let prototype = className.prototype;
|
|
202
|
-
while (prototype
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
/** Overwrite descriptors if they were already set. */
|
|
212
|
-
if (savedDescriptor?.get)
|
|
213
|
-
currentDescriptor.get = savedDescriptor?.get;
|
|
214
|
-
if (savedDescriptor?.set)
|
|
215
|
-
currentDescriptor.set = savedDescriptor?.set;
|
|
216
|
-
|
|
217
|
-
/** Store descriptors. */
|
|
218
|
-
savedDescriptors.set(propertyName, currentDescriptor);
|
|
295
|
+
while (prototype && prototype !== Object.prototype) {
|
|
296
|
+
const accessors = getOwnPropertyDescriptors(prototype);
|
|
297
|
+
for (const propertyName in accessors) {
|
|
298
|
+
if (propertyName === 'constructor') continue;
|
|
299
|
+
const descriptor = accessors[propertyName];
|
|
300
|
+
if (descriptor.get || descriptor.set) {
|
|
301
|
+
// Only add if it hasn't been defined yet (i.e. subclass overrides win)
|
|
302
|
+
if (!savedAccessors.has(propertyName)) {
|
|
303
|
+
savedAccessors.set(propertyName, descriptor);
|
|
219
304
|
}
|
|
305
|
+
} else if (typeof descriptor.value === 'function') {
|
|
306
|
+
savedMethods.add(propertyName);
|
|
220
307
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
prototype = Object.getPrototypeOf(prototype);
|
|
308
|
+
}
|
|
309
|
+
prototype = getPrototypeOf(prototype);
|
|
224
310
|
}
|
|
225
311
|
|
|
226
|
-
|
|
227
|
-
|
|
312
|
+
const propertyMap = {
|
|
313
|
+
accessors: savedAccessors,
|
|
314
|
+
methods: savedMethods,
|
|
315
|
+
};
|
|
316
|
+
accessorsMethodsMaps.set(className, propertyMap);
|
|
228
317
|
|
|
229
|
-
return
|
|
318
|
+
return propertyMap;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Property kind enum for distinguishing between data properties and accessors.
|
|
323
|
+
*/
|
|
324
|
+
const enum PropKind {
|
|
325
|
+
Data = 0,
|
|
326
|
+
Accessor = 2,
|
|
230
327
|
}
|
|
231
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Stores properties for each class prototype processed by
|
|
331
|
+
* @see {getClassPropertiesAccessorsMap}. Uses WeakMap to allow garbage collection
|
|
332
|
+
* of unused class properties, ensuring memory efficiency in long-running applications.
|
|
333
|
+
*/
|
|
334
|
+
export let propertiesAccessorsMaps: WeakMap<object, PropsMapValue> =
|
|
335
|
+
new WeakMap();
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 'caller', 'callee', 'arguments', 'constructor' are
|
|
339
|
+
* special object properties, so should be skipped.
|
|
340
|
+
* Also skip 'toRefs' as it's added by ivue and not part of the class itself.
|
|
341
|
+
*/
|
|
342
|
+
const skipProps = new Set(['caller', 'callee', 'arguments', 'constructor']);
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get properties of an entire class prototype ancestors chain as a Map.
|
|
346
|
+
*/
|
|
347
|
+
const getClassPropertiesAccessorsMap = (obj: object): PropsMapValue => {
|
|
348
|
+
const constructor = obj.constructor;
|
|
349
|
+
/* Retrieve props from cache. */
|
|
350
|
+
if (propertiesAccessorsMaps.has(constructor)) {
|
|
351
|
+
return propertiesAccessorsMaps.get(constructor) as PropsMapValue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const { accessors, methods } =
|
|
355
|
+
accessorsMethodsMaps.get(constructor) ??
|
|
356
|
+
getClassAccessorsMethodsMap(constructor as AnyClass);
|
|
357
|
+
|
|
358
|
+
const onlyProps = new Set<string>();
|
|
359
|
+
const allProps: Map<string, PropKind> = new Map();
|
|
360
|
+
|
|
361
|
+
do {
|
|
362
|
+
const propertyNames = getOwnPropertyNames(obj);
|
|
363
|
+
for (let i = 0, j = propertyNames.length; i < j; i++) {
|
|
364
|
+
const property = propertyNames[i];
|
|
365
|
+
|
|
366
|
+
if (skipProps.has(property)) continue; // Skip special properties
|
|
367
|
+
if (allProps.has(property)) continue; // Already defined in subclass, skip it
|
|
368
|
+
|
|
369
|
+
if (accessors.has(property)) {
|
|
370
|
+
allProps.set(property, PropKind.Accessor);
|
|
371
|
+
} else if (!methods.has(property)) {
|
|
372
|
+
onlyProps.add(property);
|
|
373
|
+
allProps.set(property, PropKind.Data);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
obj = getPrototypeOf(obj);
|
|
378
|
+
} while (obj && obj.constructor !== Object);
|
|
379
|
+
|
|
380
|
+
const result = { allProps, onlyProps };
|
|
381
|
+
propertiesAccessorsMaps.set(constructor, result);
|
|
382
|
+
|
|
383
|
+
return result;
|
|
384
|
+
};
|
|
385
|
+
|
|
232
386
|
/**
|
|
233
387
|
* Infinite Vue (ivue) class reactive initializer.
|
|
234
388
|
*
|
|
235
389
|
* Converts class instance to a reactive object,
|
|
236
|
-
* where
|
|
390
|
+
* where accessors are converted to computeds.
|
|
237
391
|
*
|
|
238
392
|
* You can turn off computed behaviour by adding static
|
|
239
393
|
* ivue object and setting the getter props to false.
|
|
@@ -249,91 +403,209 @@ export function getAllClassDescriptors(className: AnyClass): Descriptors {
|
|
|
249
403
|
* @param args Class constructor arguments that you would pass to a `new AnyClass(args...)`
|
|
250
404
|
* @returns {IVue<T>}
|
|
251
405
|
*/
|
|
252
|
-
export
|
|
406
|
+
export const ivue = <T extends AnyClass>(
|
|
253
407
|
className: T,
|
|
254
408
|
...args: InferredArgs<T>
|
|
255
|
-
): IVue<T> {
|
|
256
|
-
const
|
|
257
|
-
const computeds: Computeds | any =
|
|
409
|
+
): IVue<T> => {
|
|
410
|
+
const { accessors, methods } = getClassAccessorsMethodsMap(className);
|
|
411
|
+
const computeds: Computeds | any = accessors?.size
|
|
412
|
+
? createObject(null)
|
|
413
|
+
: null;
|
|
258
414
|
|
|
259
|
-
|
|
415
|
+
/** Create a reactive instance of the class. */
|
|
260
416
|
const vue = reactive(new className(...args));
|
|
261
417
|
|
|
262
|
-
/** Setup
|
|
263
|
-
for (const [prop, descriptor] of
|
|
264
|
-
/* If prop exists on static getter className.ivue[prop]
|
|
265
|
-
* We do not convert it to computed. Because sometimes
|
|
266
|
-
* we want a normal getter. */
|
|
267
|
-
if ((className as any)?.ivue?.[prop] === false) continue;
|
|
418
|
+
/** Setup accessors as computeds. */
|
|
419
|
+
for (const [prop, descriptor] of accessors) {
|
|
268
420
|
/** Convert descriptor to computed. */
|
|
269
|
-
|
|
270
|
-
get:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
set: descriptor.set?.bind(vue),
|
|
279
|
-
} as any))
|
|
280
|
-
: undefined,
|
|
281
|
-
set: descriptor.set?.bind(vue),
|
|
282
|
-
enumerable: false,
|
|
421
|
+
defineProperty(vue, prop, {
|
|
422
|
+
get: () =>
|
|
423
|
+
computeds[prop] ??
|
|
424
|
+
(computeds[prop] = computed({
|
|
425
|
+
get: descriptor.get?.bind(vue) as unknown,
|
|
426
|
+
set: descriptor.set?.bind(vue),
|
|
427
|
+
} as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
|
|
428
|
+
enumerable: descriptor.enumerable,
|
|
429
|
+
configurable: descriptor.configurable,
|
|
283
430
|
});
|
|
284
431
|
}
|
|
285
432
|
|
|
286
|
-
|
|
287
|
-
|
|
433
|
+
/** Disable reactivity for specified properties. */
|
|
434
|
+
const nonReactiveProps = (className as any)?.ivueDisableReactivity;
|
|
435
|
+
if (nonReactiveProps) {
|
|
436
|
+
for (const prop of nonReactiveProps) {
|
|
437
|
+
const descriptor = accessors.get(prop);
|
|
438
|
+
if (descriptor) {
|
|
439
|
+
defineProperty(vue, prop, {
|
|
440
|
+
get: descriptor.get?.bind(vue),
|
|
441
|
+
set: descriptor.set?.bind(vue),
|
|
442
|
+
enumerable: descriptor.enumerable,
|
|
443
|
+
configurable: descriptor.configurable,
|
|
444
|
+
});
|
|
445
|
+
} else {
|
|
446
|
+
console.log('des criptor', prop, descriptor);
|
|
447
|
+
vue[prop] = markRaw(vue[prop]);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// In ivue(), for method binding:
|
|
453
|
+
const methodDescriptor: PropertyDescriptor = {
|
|
454
|
+
writable: true,
|
|
455
|
+
configurable: true,
|
|
288
456
|
enumerable: false,
|
|
457
|
+
};
|
|
458
|
+
/** Bind all methods to the vue instance. */
|
|
459
|
+
for (const methodName of methods) {
|
|
460
|
+
methodDescriptor.value = vue[methodName].bind(vue);
|
|
461
|
+
defineProperty(vue, methodName, methodDescriptor);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Define .toRefs() method on the vue instance. */
|
|
465
|
+
defineProperty(vue, 'toRefs', {
|
|
466
|
+
get: (() => {
|
|
467
|
+
/** Lazy loaded toRefs function cache. */
|
|
468
|
+
let toRefsFn: IVueToRefsFn<T> | null = null;
|
|
469
|
+
return () =>
|
|
470
|
+
toRefsFn ?? (toRefsFn = ivueToRefs(vue, accessors, computeds));
|
|
471
|
+
})(),
|
|
472
|
+
enumerable: false,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
/** Define .clone() method on the vue instance. */
|
|
476
|
+
defineProperty(vue, 'clone', {
|
|
477
|
+
value: (...cloneArgs: CloneArgsFor<InstanceType<T>>) =>
|
|
478
|
+
ivueClone(className, args, vue, accessors, methods, cloneArgs),
|
|
479
|
+
enumerable: false,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
/** Mark as ivue instance */
|
|
483
|
+
defineProperty(vue, IVUE_INSTANCE_SYMBOL, {
|
|
484
|
+
value: true,
|
|
485
|
+
enumerable: false,
|
|
486
|
+
configurable: false,
|
|
487
|
+
writable: false,
|
|
289
488
|
});
|
|
290
489
|
|
|
291
490
|
/** Run ivue .init() initializer method, if it exists in the class. */
|
|
292
|
-
|
|
491
|
+
vue?.init?.(false);
|
|
293
492
|
|
|
294
493
|
return vue;
|
|
295
|
-
}
|
|
494
|
+
};
|
|
296
495
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
496
|
+
const ivueClone = <T extends AnyClass>(
|
|
497
|
+
className: T,
|
|
498
|
+
args: InferredArgs<T>,
|
|
499
|
+
vueSrc: IVue<T>,
|
|
500
|
+
accessors: Accessors,
|
|
501
|
+
methods: Set<string>,
|
|
502
|
+
cloneArgs: CloneArgsFor<InstanceType<T>> = [] as CloneArgsFor<InstanceType<T>>
|
|
503
|
+
): IVue<T> => {
|
|
504
|
+
const computeds: Computeds | any = accessors?.size
|
|
505
|
+
? createObject(null)
|
|
506
|
+
: null;
|
|
507
|
+
|
|
508
|
+
/** Create a reactive instance of the class. */
|
|
509
|
+
const vue = reactive(new className(...args));
|
|
510
|
+
const { onlyProps } = getClassPropertiesAccessorsMap(vue);
|
|
302
511
|
|
|
303
|
-
/**
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
512
|
+
/** Clone properties. */
|
|
513
|
+
const cloneByRef = (className as any).ivueCloneByReference;
|
|
514
|
+
const deepCloneArgs = activeDeepCloneArgsMap.get(className);
|
|
515
|
+
|
|
516
|
+
if (cloneByRef) {
|
|
517
|
+
for (const prop of onlyProps) {
|
|
518
|
+
vue[prop] = cloneByRef.has(prop)
|
|
519
|
+
? vueSrc[prop]
|
|
520
|
+
: deepClone(vueSrc[prop], deepCloneArgs);
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
for (const prop of onlyProps) {
|
|
524
|
+
vue[prop] = deepClone(vueSrc[prop], deepCloneArgs);
|
|
525
|
+
}
|
|
310
526
|
}
|
|
311
527
|
|
|
312
|
-
|
|
528
|
+
/** Setup accessors as computeds. */
|
|
529
|
+
for (const [prop, descriptor] of accessors) {
|
|
530
|
+
/** Convert descriptor to computed. */
|
|
531
|
+
defineProperty(vue, prop, {
|
|
532
|
+
get: () =>
|
|
533
|
+
computeds[prop] ??
|
|
534
|
+
(computeds[prop] = computed({
|
|
535
|
+
get: descriptor.get?.bind(vue) as unknown,
|
|
536
|
+
set: descriptor.set?.bind(vue),
|
|
537
|
+
} as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
|
|
538
|
+
enumerable: descriptor.enumerable,
|
|
539
|
+
configurable: descriptor.configurable,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
313
542
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
543
|
+
/** Disable reactivity for specified properties. */
|
|
544
|
+
const nonReactiveProps = (className as any)?.ivueDisableReactivity;
|
|
545
|
+
if (nonReactiveProps) {
|
|
546
|
+
for (const prop of nonReactiveProps) {
|
|
547
|
+
const descriptor = accessors.get(prop);
|
|
548
|
+
if (descriptor) {
|
|
549
|
+
defineProperty(vue, prop, {
|
|
550
|
+
get: descriptor.get?.bind(vue),
|
|
551
|
+
set: descriptor.set?.bind(vue),
|
|
552
|
+
enumerable: descriptor.enumerable,
|
|
553
|
+
configurable: descriptor.configurable,
|
|
554
|
+
});
|
|
555
|
+
} else {
|
|
556
|
+
vue[prop] = markRaw(vue[prop]);
|
|
321
557
|
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
} while (obj.constructor !== Object);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
325
560
|
|
|
326
|
-
/**
|
|
327
|
-
|
|
561
|
+
/** Bind all methods to the vue instance. */
|
|
562
|
+
const methodDescriptor: PropertyDescriptor = {
|
|
563
|
+
writable: true,
|
|
564
|
+
configurable: true,
|
|
565
|
+
enumerable: false,
|
|
566
|
+
};
|
|
567
|
+
for (const methodName of methods) {
|
|
568
|
+
methodDescriptor.value = vue[methodName].bind(vue);
|
|
569
|
+
defineProperty(vue, methodName, methodDescriptor);
|
|
570
|
+
}
|
|
328
571
|
|
|
329
|
-
|
|
330
|
-
|
|
572
|
+
/** Define .toRefs() method on the vue instance. */
|
|
573
|
+
defineProperty(vue, 'toRefs', {
|
|
574
|
+
get: (() => {
|
|
575
|
+
/** Lazy loaded toRefs function cache. */
|
|
576
|
+
let toRefsFn: IVueToRefsFn<T> | null = null;
|
|
577
|
+
return () =>
|
|
578
|
+
toRefsFn ?? (toRefsFn = ivueToRefs(vue, accessors, computeds));
|
|
579
|
+
})(),
|
|
580
|
+
enumerable: false,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
/** Define .clone() method on the vue instance. */
|
|
584
|
+
defineProperty(vue, 'clone', {
|
|
585
|
+
value: (...cloneArgs: CloneArgsFor<InstanceType<T>>) =>
|
|
586
|
+
ivueClone(className, args, vue, accessors, methods, cloneArgs),
|
|
587
|
+
enumerable: false,
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
/** Mark as ivue instance */
|
|
591
|
+
defineProperty(vue, IVUE_INSTANCE_SYMBOL, {
|
|
592
|
+
value: true,
|
|
593
|
+
enumerable: false,
|
|
594
|
+
configurable: false,
|
|
595
|
+
writable: false,
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
/** Run ivue .init() initializer method, if it exists in the class. */
|
|
599
|
+
vue?.init?.(true, ...cloneArgs);
|
|
600
|
+
|
|
601
|
+
return vue;
|
|
602
|
+
};
|
|
331
603
|
|
|
332
604
|
/**
|
|
333
605
|
* `iref()` is an alias for Vue ref() function but returns an unwrapped type without the .value
|
|
334
606
|
* `iref()` does not alter the behavior of ref(), but simply transforms the type to an unwrapped raw value.
|
|
335
607
|
* @param val T
|
|
336
|
-
* @returns {T}
|
|
608
|
+
* @returns {T} Ref but with type unwrapped
|
|
337
609
|
*/
|
|
338
610
|
export const iref = ref as <T = any>(value?: T) => T;
|
|
339
611
|
|
|
@@ -341,132 +613,87 @@ export const iref = ref as <T = any>(value?: T) => T;
|
|
|
341
613
|
* `ishallowRef()` is an alias for Vue shallowRef() function but returns an unwrapped type without the .value
|
|
342
614
|
* `ishallowRef()` does not alter the behavior of shallowRef(), but simply transforms the type to an unwrapped raw value.
|
|
343
615
|
* @param val T
|
|
344
|
-
* @returns {T}
|
|
616
|
+
* @returns {T} ShallowRef but with type unwrapped
|
|
345
617
|
*/
|
|
346
618
|
export const ishallowRef = shallowRef as <T = any>(value?: T) => T;
|
|
347
619
|
|
|
348
620
|
/**
|
|
349
|
-
*
|
|
350
|
-
* 1. `iuse(useComposable(arg, arg2, arg3, ...))` converts the return types of a Composable / Ref to pure raw unwrapped type definition.
|
|
351
|
-
* Returns for all properties of an object an unwrapped raw type definition,
|
|
352
|
-
* unwraps direct Refs & ComputedRefs as well down to their raw types.
|
|
621
|
+
* `iuse()` is a helper function that unwraps any Vue 3 composable return type down to its bare types.
|
|
353
622
|
*
|
|
354
|
-
*
|
|
355
|
-
* here the TypeScript inference works for composable function arguments to assist you with intellisence,
|
|
356
|
-
* like they work for constructor arguments in the cause of `ivue()` core function,
|
|
357
|
-
* making the API cleaner to look at and make it compatible with how this function operates with classes, see #3.
|
|
623
|
+
* It does not alter the behavior of the composable, but simply transforms the type to an unwrapped raw value.
|
|
358
624
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
classFunctionObject?: T,
|
|
365
|
-
...args: T extends AnyClass
|
|
366
|
-
? InferredArgs<T>
|
|
367
|
-
: T extends AnyFn
|
|
368
|
-
? Parameters<T extends (...args: any[]) => any ? T : any>
|
|
369
|
-
: any
|
|
370
|
-
): T extends AnyClass ? InstanceType<T> : Use<T> {
|
|
371
|
-
return typeof classFunctionObject === 'function'
|
|
372
|
-
? isClass(classFunctionObject)
|
|
373
|
-
? /** Run IVUE but return full Refs, yet property 'true' makes `.toRefs(true)` cast the type to the unwrapped raw type definition instead of a `.value` Ref. */
|
|
374
|
-
ivue(
|
|
375
|
-
classFunctionObject as T extends AnyClass ? T : any,
|
|
376
|
-
...(args as InferredArgs<T extends AnyClass ? T : any>)
|
|
377
|
-
).toRefs(true)
|
|
378
|
-
: /** Run Vue 3 Standard Composable but also unwrap it to bare raw types. */
|
|
379
|
-
(classFunctionObject as AnyFn)(
|
|
380
|
-
...(args as Parameters<T extends AnyFn ? AnyFn : any>)
|
|
381
|
-
)
|
|
382
|
-
: (classFunctionObject as unknown as Unwrap<T>) /** Unwrap any other Object or any type down to its bare types. */;
|
|
383
|
-
}
|
|
625
|
+
* @param obj Any Vue 3 composable return type or any object or any type
|
|
626
|
+
* @returns {Use<T>} Composable with types unwrapped
|
|
627
|
+
*/
|
|
628
|
+
export const iuse = <T extends object>(obj: T): Use<T> =>
|
|
629
|
+
obj as unknown as Use<T>; /** Unwrap any other Object or any type down to its bare types. */
|
|
384
630
|
|
|
385
631
|
/**
|
|
386
632
|
* Convert reactive ivue class to Vue 3 refs.
|
|
387
633
|
*
|
|
388
634
|
* @param vue @see IVue
|
|
389
|
-
* @param
|
|
635
|
+
* @param accessors @see Accessors
|
|
390
636
|
* @param computeds @see Computeds
|
|
391
637
|
* @returns {ExtendWithToRefs<T>['toRefs']}
|
|
392
638
|
*/
|
|
393
|
-
|
|
639
|
+
const ivueToRefs = <T extends AnyClass>(
|
|
394
640
|
vue: IVue<T>,
|
|
395
|
-
|
|
641
|
+
accessors: Accessors,
|
|
396
642
|
computeds: Computeds
|
|
397
|
-
):
|
|
643
|
+
): IVueToRefsFn<T> => {
|
|
644
|
+
/** Caches for toRefs function */
|
|
645
|
+
/** Cached accessor function */
|
|
646
|
+
const getAccessor = accessors.get.bind(accessors);
|
|
647
|
+
/** Get all class properties */
|
|
648
|
+
const { allProps } = getClassPropertiesAccessorsMap(vue);
|
|
649
|
+
/** The actual toRefs function returned. */
|
|
398
650
|
return function (
|
|
399
|
-
props
|
|
651
|
+
props: (string & keyof InstanceType<T>)[] | boolean = false,
|
|
400
652
|
unwrap?: boolean /** This property helps with TypeScript function definition overloading of return types and is not being used inside the function itself. */
|
|
401
653
|
): any /** @see {ReturnType<IVueToRefsFn<T>>} */ {
|
|
402
654
|
/** Resulting refs store. */
|
|
403
|
-
const result: Record<string
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
if (
|
|
407
|
-
for (
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
if (descriptors.has(prop)) {
|
|
411
|
-
if (prop in computeds) {
|
|
412
|
-
/** Return vue computed with .value from computeds store. */
|
|
413
|
-
result[prop] = computeds[prop];
|
|
414
|
-
} else {
|
|
415
|
-
/** Initialize & store vue computed. */
|
|
416
|
-
const descriptor = descriptors.get(prop);
|
|
417
|
-
result[prop] = computeds[prop] = computed({
|
|
418
|
-
get: descriptor?.get?.bind(vue) as any,
|
|
419
|
-
set: descriptor?.set?.bind(vue),
|
|
420
|
-
} as any);
|
|
421
|
-
}
|
|
422
|
-
} else {
|
|
423
|
-
/** Handle methods. */
|
|
424
|
-
if (typeof vue[prop] === 'function') {
|
|
425
|
-
/** Bind method to vue, makes method destructuring point to right instance. */
|
|
426
|
-
result[prop] = vue[prop].bind(vue);
|
|
427
|
-
} else {
|
|
428
|
-
/** Convert simple reactive prop to a Ref. */
|
|
429
|
-
result[prop] = toRef(vue, prop);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
655
|
+
const result: Record<string, any> = {};
|
|
656
|
+
|
|
657
|
+
/** Convert all props to refs and leave functions as is. */
|
|
658
|
+
if (isArray(props)) {
|
|
659
|
+
for (const prop of props) {
|
|
660
|
+
const kind = allProps.get(prop);
|
|
661
|
+
resolveIvueToRefs(result, prop, kind, vue, getAccessor, computeds);
|
|
432
662
|
}
|
|
433
663
|
} else {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
/** Convert descriptors (non enumerable by default in JS). */
|
|
438
|
-
descriptors.forEach((descriptor, prop) => {
|
|
439
|
-
if (prop in computeds) {
|
|
440
|
-
/** Return vue computed with .value from computeds store. */
|
|
441
|
-
result[prop] = computeds[prop];
|
|
442
|
-
} else {
|
|
443
|
-
/** Initialize vue computed ref & store it in result. */
|
|
444
|
-
result[prop] = computeds[prop] = computed({
|
|
445
|
-
get: descriptor.get?.bind(vue) as any,
|
|
446
|
-
set: descriptor.set?.bind(vue),
|
|
447
|
-
} as any);
|
|
448
|
-
}
|
|
449
|
-
/** Delete descriptor from props as it was already processed. */
|
|
450
|
-
allProps?.delete(prop as string);
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
allProps.forEach((prop) => {
|
|
454
|
-
if (typeof vue[prop] === 'function') {
|
|
455
|
-
/** Bind method to vue, makes method destructuring point to right instance. */
|
|
456
|
-
result[prop] = vue[prop].bind(vue);
|
|
457
|
-
} else {
|
|
458
|
-
/** Convert simple reactive prop to a Ref. */
|
|
459
|
-
result[prop] = toRef(vue, prop);
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
/** Memory optimization. */
|
|
464
|
-
allProps = null;
|
|
664
|
+
for (const [prop, kind] of allProps.entries()) {
|
|
665
|
+
resolveIvueToRefs(result, prop, kind, vue, getAccessor, computeds);
|
|
666
|
+
}
|
|
465
667
|
}
|
|
466
668
|
|
|
467
|
-
return result as any;
|
|
669
|
+
return result as any; /** @see {ReturnType<IVueToRefsFn<T>>} */
|
|
468
670
|
};
|
|
469
|
-
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Resolve property to ref, method or computed and assign it to result.
|
|
675
|
+
* @private
|
|
676
|
+
*/
|
|
677
|
+
const resolveIvueToRefs = (
|
|
678
|
+
result: Record<string, any>,
|
|
679
|
+
prop: string,
|
|
680
|
+
kind: PropKind | undefined,
|
|
681
|
+
vue: IVue<any>,
|
|
682
|
+
getAccessor: Accessors['get'],
|
|
683
|
+
computeds: Record<string, ComputedRef<any>>
|
|
684
|
+
): void => {
|
|
685
|
+
if (kind === PropKind.Accessor) {
|
|
686
|
+
const descriptor = getAccessor(prop) as PropertyDescriptor;
|
|
687
|
+
result[prop] =
|
|
688
|
+
computeds[prop] ??
|
|
689
|
+
(computeds[prop] = computed({
|
|
690
|
+
get: descriptor.get?.bind(vue) as unknown,
|
|
691
|
+
set: descriptor.set?.bind(vue),
|
|
692
|
+
} as any));
|
|
693
|
+
} else if (kind === PropKind.Data) {
|
|
694
|
+
result[prop] = toRef(vue, prop);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
470
697
|
|
|
471
698
|
/**
|
|
472
699
|
* Vue props interface in defineComponent() style.
|
|
@@ -494,19 +721,19 @@ export type VuePropsWithDefaults<T extends VuePropsObject> = {
|
|
|
494
721
|
* @param val Any value
|
|
495
722
|
* @returns boolean If it's a JavaScript Class returns true
|
|
496
723
|
*/
|
|
497
|
-
export
|
|
724
|
+
export const isClass = (val: any): boolean => {
|
|
498
725
|
if (typeof val !== 'function') return false; // Not a function, so not a class function either
|
|
499
726
|
|
|
500
727
|
if (!val.prototype) return false; // Arrow function, so not a class
|
|
501
728
|
|
|
502
729
|
// Finally -> distinguish between a normal function and a class function
|
|
503
|
-
if (
|
|
730
|
+
if (getOwnPropertyDescriptor(val, 'prototype')?.writable) {
|
|
504
731
|
// Has writable prototype
|
|
505
732
|
return false; // Normal function
|
|
506
733
|
} else {
|
|
507
734
|
return true; // Class -> Not a function
|
|
508
735
|
}
|
|
509
|
-
}
|
|
736
|
+
};
|
|
510
737
|
|
|
511
738
|
/**
|
|
512
739
|
* Creates props with defaults in defineComponent() style.
|
|
@@ -533,26 +760,214 @@ export function isClass(val: any): boolean {
|
|
|
533
760
|
* @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
|
|
534
761
|
* @returns Props declared in defineComponent() style with all properties having default property declared.
|
|
535
762
|
*/
|
|
536
|
-
export
|
|
763
|
+
export const propsWithDefaults = <T extends VuePropsObject>(
|
|
537
764
|
defaults: Record<string, any>,
|
|
538
|
-
typedProps: T
|
|
539
|
-
|
|
765
|
+
typedProps: T,
|
|
766
|
+
deepCloneArgs: IVueDeepCloneArgsMap | undefined = undefined
|
|
767
|
+
): VuePropsWithDefaults<T> => {
|
|
540
768
|
for (const prop in typedProps) {
|
|
541
|
-
|
|
769
|
+
const def = defaults?.[prop];
|
|
770
|
+
const typed = typedProps[prop];
|
|
771
|
+
|
|
772
|
+
/** Skip if prop is required — Vue will enforce it at runtime */
|
|
773
|
+
if (typed.required) continue;
|
|
774
|
+
|
|
775
|
+
/** Skip if default is undefined */
|
|
776
|
+
if (def === undefined) continue;
|
|
777
|
+
|
|
778
|
+
if (typeof def === 'object' && def !== null) {
|
|
542
779
|
/** Handle Arrays & Objects -> wrap them with an arrow function. */
|
|
543
|
-
typedProps[prop].default = () =>
|
|
780
|
+
typedProps[prop].default = () => deepClone(def, deepCloneArgs);
|
|
544
781
|
} else {
|
|
545
|
-
if (isClass(
|
|
782
|
+
if (isClass(def)) {
|
|
546
783
|
/** Handle JavaScript Classes -> wrap them with an arrow function */
|
|
547
|
-
typedProps[prop].default = () =>
|
|
784
|
+
typedProps[prop].default = () => def;
|
|
548
785
|
} else {
|
|
549
786
|
/** Handle JavaScript Function And All primitive properties -> output directly */
|
|
550
|
-
typedProps[prop].default =
|
|
787
|
+
typedProps[prop].default = def;
|
|
551
788
|
}
|
|
552
789
|
}
|
|
553
790
|
}
|
|
554
791
|
return typedProps as VuePropsWithDefaults<T>;
|
|
555
|
-
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Copies own properties (including symbols and non-enumerable) from source to target.
|
|
796
|
+
* Recursively deep clones values, but preserves getters/setters as-is.
|
|
797
|
+
*/
|
|
798
|
+
export const copyOwnProps = (
|
|
799
|
+
source: any,
|
|
800
|
+
target: any,
|
|
801
|
+
deepCloneArgs: IVueDeepCloneArgsMap | undefined,
|
|
802
|
+
seen: WeakMap<object, any>
|
|
803
|
+
) => {
|
|
804
|
+
// 1. Get all descriptors (String keys AND Symbol keys)
|
|
805
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
806
|
+
|
|
807
|
+
// 2. Iterate over all keys returned by Reflect (safety for Proxies/Environments)
|
|
808
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
809
|
+
const desc = descriptors[key as any];
|
|
810
|
+
|
|
811
|
+
// 3. If it is a data descriptor (has a value), we must deep clone the value.
|
|
812
|
+
// We do NOT clone getters/setters (we copy the function reference).
|
|
813
|
+
if ('value' in desc) {
|
|
814
|
+
desc.value = deepClone(desc.value, deepCloneArgs, seen);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// 4. Define on target
|
|
818
|
+
Object.defineProperty(target, key, desc);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
export let activeDeepCloneArgsMap = new WeakMap<
|
|
823
|
+
AnyClass,
|
|
824
|
+
IVueDeepCloneArgsMap
|
|
825
|
+
>();
|
|
826
|
+
|
|
827
|
+
export const deepClone = (
|
|
828
|
+
source: any,
|
|
829
|
+
deepCloneArgs?: IVueDeepCloneArgsMap,
|
|
830
|
+
seen: WeakMap<object, any> = new WeakMap()
|
|
831
|
+
): any => {
|
|
832
|
+
// --- TIER 1: The "Every Call" Checks (Fastest) ---
|
|
833
|
+
if (source == null || typeof source !== 'object') return source;
|
|
834
|
+
if (seen.has(source)) return seen.get(source);
|
|
835
|
+
|
|
836
|
+
// --- TIER 2: The "Hottest" Collection (Arrays) ---
|
|
837
|
+
if (isArray(source)) {
|
|
838
|
+
const len = source.length;
|
|
839
|
+
const out = new Array(len);
|
|
840
|
+
seen.set(source, out);
|
|
841
|
+
for (let i = 0; i < len; i++) {
|
|
842
|
+
out[i] = deepClone(source[i], deepCloneArgs, seen);
|
|
843
|
+
}
|
|
844
|
+
return out;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// --- TIER 3: The "Hottest" Objects (Plain Objects) ---
|
|
848
|
+
const prototype = getPrototypeOf(source);
|
|
849
|
+
if (prototype === Object.prototype || prototype === null) {
|
|
850
|
+
const out = createObject(prototype);
|
|
851
|
+
seen.set(source, out);
|
|
852
|
+
copyOwnProps(source, out, deepCloneArgs, seen);
|
|
853
|
+
return out;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// --- TIER 4: IVue Special Logic (Your Framework) ---
|
|
857
|
+
// Checked here because it's specific to your domain
|
|
858
|
+
if (source[IVUE_INSTANCE_SYMBOL]) {
|
|
859
|
+
const srcClass = source.constructor as IVueClass<any>;
|
|
860
|
+
if (srcClass.ivueGlobalStore) {
|
|
861
|
+
seen.set(source, source);
|
|
862
|
+
return source;
|
|
863
|
+
}
|
|
864
|
+
let out;
|
|
865
|
+
if (deepCloneArgs?.has(srcClass)) {
|
|
866
|
+
activeDeepCloneArgsMap.set(srcClass, deepCloneArgs);
|
|
867
|
+
try {
|
|
868
|
+
out = source.clone(...(deepCloneArgs.get(srcClass) ?? []));
|
|
869
|
+
} finally {
|
|
870
|
+
activeDeepCloneArgsMap.delete(srcClass);
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
out = source.clone();
|
|
874
|
+
}
|
|
875
|
+
seen.set(source, out);
|
|
876
|
+
return out;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// --- TIER 5: Common Built-ins (Sorted by Likelihood) ---
|
|
880
|
+
// instanceof is fast, but order matters slightly for CPU branch prediction
|
|
881
|
+
if (source instanceof Date) {
|
|
882
|
+
return new Date(source.getTime());
|
|
883
|
+
}
|
|
884
|
+
if (source instanceof Map) {
|
|
885
|
+
const out = new (source.constructor as any)();
|
|
886
|
+
seen.set(source, out);
|
|
887
|
+
for (const [k, v] of source) {
|
|
888
|
+
out.set(
|
|
889
|
+
deepClone(k, deepCloneArgs, seen),
|
|
890
|
+
deepClone(v, deepCloneArgs, seen)
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
return out;
|
|
894
|
+
}
|
|
895
|
+
if (source instanceof Set) {
|
|
896
|
+
const out = new (source.constructor as any)();
|
|
897
|
+
seen.set(source, out);
|
|
898
|
+
for (const v of source) {
|
|
899
|
+
out.add(deepClone(v, deepCloneArgs, seen));
|
|
900
|
+
}
|
|
901
|
+
return out;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// --- TIER 6: Rare / Heavy Objects (The "Slow" Tail) ---
|
|
905
|
+
if (source instanceof RegExp) {
|
|
906
|
+
const out = new RegExp(source.source, source.flags);
|
|
907
|
+
out.lastIndex = source.lastIndex;
|
|
908
|
+
return out;
|
|
909
|
+
}
|
|
910
|
+
if (source instanceof Error) {
|
|
911
|
+
const out = new (source.constructor as any)(source.message);
|
|
912
|
+
seen.set(source, out);
|
|
913
|
+
copyOwnProps(source, out, deepCloneArgs, seen);
|
|
914
|
+
return out;
|
|
915
|
+
}
|
|
916
|
+
if (
|
|
917
|
+
source instanceof Promise ||
|
|
918
|
+
source instanceof WeakMap ||
|
|
919
|
+
source instanceof WeakSet
|
|
920
|
+
) {
|
|
921
|
+
seen.set(source, source);
|
|
922
|
+
return source;
|
|
923
|
+
}
|
|
924
|
+
if (source instanceof DataView) {
|
|
925
|
+
// Clone the underlying buffer to ensure deep copy
|
|
926
|
+
const out = new DataView(
|
|
927
|
+
source.buffer.slice(0),
|
|
928
|
+
source.byteOffset,
|
|
929
|
+
source.byteLength
|
|
930
|
+
);
|
|
931
|
+
seen.set(source, out);
|
|
932
|
+
return out;
|
|
933
|
+
}
|
|
934
|
+
// Binary data (less common in UI state, but supported)
|
|
935
|
+
if (ArrayBuffer.isView(source) && !(source instanceof DataView)) {
|
|
936
|
+
const out = new (source.constructor as any)(source);
|
|
937
|
+
seen.set(source, out);
|
|
938
|
+
return out;
|
|
939
|
+
}
|
|
940
|
+
if (source instanceof ArrayBuffer) {
|
|
941
|
+
const out = source.slice(0);
|
|
942
|
+
seen.set(source, out);
|
|
943
|
+
return out;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// --- TIER 7: Final Fallback (Custom User Classes) ---
|
|
947
|
+
// If it's a class instance not handled above
|
|
948
|
+
const out = createObject(prototype);
|
|
949
|
+
seen.set(source, out);
|
|
950
|
+
copyOwnProps(source, out, deepCloneArgs, seen);
|
|
951
|
+
return out;
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Clears the accessorsMethodsMaps, and propertiesAccessorsMaps WeakMaps.
|
|
956
|
+
* Useful for SSR scenarios where you want to reset the cache between requests.
|
|
957
|
+
* Also useful for testing purposes to ensure a clean state.
|
|
958
|
+
*/
|
|
959
|
+
export const clearCache = () => {
|
|
960
|
+
accessorsMethodsMaps = new WeakMap();
|
|
961
|
+
propertiesAccessorsMaps = new WeakMap();
|
|
962
|
+
activeDeepCloneArgsMap = new WeakMap();
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
/** Exported for testing purposes. */
|
|
966
|
+
export const __test__ = {
|
|
967
|
+
copyOwnProps,
|
|
968
|
+
getClassAccessorsMethodsMap,
|
|
969
|
+
getClassPropertiesAccessorsMap,
|
|
970
|
+
};
|
|
556
971
|
|
|
557
972
|
/** Necessary ivue.ts to be treated as a module. */
|
|
558
973
|
export {};
|