ivue 1.5.8 → 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.
@@ -0,0 +1,338 @@
1
+ import { toRaw, isRef, type Ref } from 'vue';
2
+
3
+ /**
4
+ * Constants & Helpers
5
+ */
6
+ const prototypeHasOwnProperty = Object.prototype.hasOwnProperty.call.bind(
7
+ Object.prototype.hasOwnProperty
8
+ );
9
+ const getPrototypeOf = Object.getPrototypeOf;
10
+ const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
11
+ const defineProperty = Object.defineProperty;
12
+ const getOwnPropertyNames = Object.getOwnPropertyNames;
13
+ const objectPrototype = Object.prototype;
14
+ const getOwnPropertySymbols = Object.getOwnPropertySymbols;
15
+
16
+ const $stopEffects = '$stopEffects';
17
+ const fn = 'function';
18
+ const RAW = Symbol('ivue_raw');
19
+ const SK_MAP = '__ivue_sk'; // Store for cache keys
20
+ const PROCESSED = Symbol('ivue_processed'); // Flag to mark prototype as "Reactified"
21
+
22
+ /**
23
+ * Get unique symbol key for super method/property.
24
+ */
25
+ function getSuperKey(proto: any, key: string): symbol {
26
+ // Ensure the map exists on this specific prototype (shadowing parents)
27
+ if (!prototypeHasOwnProperty(proto, SK_MAP)) {
28
+ defineProperty(proto, SK_MAP, {
29
+ value: {},
30
+ configurable: true,
31
+ enumerable: false,
32
+ writable: true,
33
+ });
34
+ }
35
+ const map = proto[SK_MAP];
36
+ return map[key] || (map[key] = Symbol(key));
37
+ }
38
+
39
+ /**
40
+ * Convert method to a lazy-bound prototype method.
41
+ */
42
+ function convertToLazyBoundMethod(proto: any, key: string, superKey: symbol) {
43
+ const desc = getOwnPropertyDescriptor(proto, key);
44
+ if (!desc || typeof desc.value !== fn) return;
45
+
46
+ const originalFn = desc.value;
47
+
48
+ defineProperty(proto, key, {
49
+ configurable: true,
50
+ enumerable: false,
51
+ get(this: any) {
52
+ const raw = this[RAW] ?? (this[RAW] = toRaw(this));
53
+ return raw[superKey] ?? (raw[superKey] = originalFn.bind(raw));
54
+ },
55
+ set(this: any, newFn: any) {
56
+ toRaw(this)[superKey] = newFn;
57
+ },
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Convert a property to a lazy-computed property.
63
+ */
64
+ function convertToLazyComputed<T extends object>(
65
+ proto: T,
66
+ key: string,
67
+ superKey: symbol
68
+ ) {
69
+ const desc = getOwnPropertyDescriptor(proto, key);
70
+ const originalGetter = desc?.get;
71
+ const originalSetter = desc?.set;
72
+
73
+ // Optimization: Properties starting with $ are assumed singletons
74
+ const cacheWhole = key[0] === '$';
75
+
76
+ // DEV WARNING: Setter/Getter mismatch
77
+ if (import.meta.env.DEV && originalGetter && originalSetter) {
78
+ try {
79
+ const testVal = originalGetter.call({});
80
+ if (isRef(testVal)) {
81
+ console.warn(
82
+ `[ivue] API conflict on "${key}": Getter returns Ref but Setter is standard.`
83
+ );
84
+ }
85
+ } catch (e) {}
86
+ }
87
+
88
+ const newGetter = function (this: any) {
89
+ const raw = toRaw(this);
90
+
91
+ // 1. Check Cache
92
+ if (superKey in raw) return raw[superKey];
93
+
94
+ // 2. Execute Original
95
+ const result = originalGetter!.call(raw);
96
+
97
+ // 3. Handle Result
98
+ if (cacheWhole) {
99
+ // Cache result forever (Singleton pattern)
100
+ raw[superKey] = result;
101
+ return result;
102
+ }
103
+
104
+ if (isRef(result)) {
105
+ // Cache Ref instance (Reactivity pattern)
106
+ raw[superKey] = result;
107
+ } else {
108
+ // DE-OPTIMIZATION: It's just a value. Restore original getter on prototype.
109
+ // This removes overhead for all future calls on this prototype.
110
+ defineProperty(proto, key, {
111
+ configurable: true,
112
+ enumerable: false,
113
+ get: originalGetter
114
+ ? function (this: any) {
115
+ return originalGetter.call(toRaw(this));
116
+ }
117
+ : undefined,
118
+ set: originalSetter
119
+ ? function (this: any, v: any) {
120
+ return originalSetter.call(toRaw(this), v);
121
+ }
122
+ : undefined,
123
+ });
124
+ }
125
+
126
+ return result;
127
+ };
128
+
129
+ defineProperty(proto, key, {
130
+ configurable: true,
131
+ enumerable: false,
132
+ get: originalGetter ? newGetter : undefined,
133
+ set: originalSetter
134
+ ? function (this: any, v: any) {
135
+ return originalSetter.call(toRaw(this), v);
136
+ }
137
+ : undefined,
138
+ });
139
+ }
140
+
141
+ /**
142
+ * Create a reactive class.
143
+ * @param targetClass The class to make reactive.
144
+ * @returns A reactive version of the class.
145
+ */
146
+ export function Reactive<C extends new (...args: any) => any>(
147
+ targetClass: C
148
+ ): ReactiveClass<C> & { Instance: ReactiveInstance<InstanceType<C>> } {
149
+ const chain: any[] = [];
150
+
151
+ let targetPrototype = targetClass.prototype;
152
+ while (targetPrototype && targetPrototype !== objectPrototype) {
153
+ chain.push(targetPrototype);
154
+ targetPrototype = getPrototypeOf(targetPrototype);
155
+ }
156
+
157
+ // Process Base -> Child
158
+ chain.reverse();
159
+
160
+ for (const prototype of chain) {
161
+ // OPTIMIZATION: Skip if this prototype layer is already "Reactified"
162
+ // This handles diamond inheritance and multiple Reactive children safely.
163
+ if (prototypeHasOwnProperty(prototype, PROCESSED)) continue;
164
+
165
+ const names = getOwnPropertyNames(prototype);
166
+
167
+ for (const key of names) {
168
+ if (key === 'constructor') continue;
169
+ const desc = getOwnPropertyDescriptor(prototype, key);
170
+ if (!desc) continue;
171
+
172
+ const superKey = getSuperKey(prototype, key);
173
+
174
+ if (typeof desc.value === fn) {
175
+ convertToLazyBoundMethod(prototype, key, superKey);
176
+ } else if (desc.get) {
177
+ convertToLazyComputed(prototype, key, superKey);
178
+ }
179
+ }
180
+
181
+ // Mark this specific prototype level as processed
182
+ defineProperty(prototype, PROCESSED, {
183
+ configurable: false,
184
+ enumerable: false,
185
+ value: true,
186
+ });
187
+ }
188
+
189
+ // Inject cleanup
190
+ if (!prototypeHasOwnProperty(targetClass.prototype, $stopEffects)) {
191
+ defineProperty(targetClass.prototype, $stopEffects, {
192
+ enumerable: false,
193
+ configurable: true,
194
+ writable: true,
195
+ value: function (this: any) {
196
+ const raw = toRaw(this);
197
+ if (typeof raw.stopEffects === fn) raw.stopEffects();
198
+
199
+ const symbols = getOwnPropertySymbols(raw);
200
+ for (const symbol of symbols) {
201
+ if (symbol === RAW) continue;
202
+ const cached = raw[symbol];
203
+ // Stop computed/effect if present
204
+ if (cached && cached.effect && typeof cached.effect.stop === fn) {
205
+ cached.effect.stop();
206
+ }
207
+ delete raw[symbol];
208
+ }
209
+ },
210
+ });
211
+ }
212
+
213
+ return targetClass as any;
214
+ }
215
+
216
+ /**
217
+ * Vue props interface in defineComponent() style.
218
+ */
219
+ export type VuePropsObject = Record<
220
+ string,
221
+ { type: any; default?: any; required?: boolean }
222
+ >;
223
+
224
+ /**
225
+ * Vue Props with default properties declared as existing and having values.
226
+ */
227
+ export type VuePropsWithDefaults<T extends VuePropsObject> = {
228
+ [K in keyof T]: {
229
+ type: T[K]['type'];
230
+ default: T[K]['default'];
231
+ required?: boolean;
232
+ };
233
+ };
234
+
235
+ /**
236
+ * Determines if the value is a JavaScript Class.
237
+ * Note that class is a class function in JavaScript.
238
+ *
239
+ * @param val Any value
240
+ * @returns boolean If it's a JavaScript Class returns true
241
+ */
242
+ export const isClass = (val: any): boolean => {
243
+ if (typeof val !== 'function') return false; // Not a function, so not a class function either
244
+
245
+ if (!val.prototype) return false; // Arrow function, so not a class
246
+
247
+ // Finally -> distinguish between a normal function and a class function
248
+ if (getOwnPropertyDescriptor(val, 'prototype')?.writable) {
249
+ // Has writable prototype
250
+ return false; // Normal function
251
+ } else {
252
+ return true; // Class -> Not a function
253
+ }
254
+ };
255
+ /**
256
+ * Creates props with defaults in defineComponent() style.
257
+ *
258
+ * Merge defaults regular object with Vue types object
259
+ * declared in defineComponent() style.
260
+ *
261
+ * This is made so that the defaults can be declared "as they are"
262
+ * without requiring objects to be function callbacks returning an object.
263
+ *
264
+ * // You don't need to wrap objects in () => ({ nest: { nest :{} } })
265
+ * // You can just delcare them normally.
266
+ * const defaults = {
267
+ * nest: {
268
+ * nest
269
+ * }
270
+ * }
271
+ *
272
+ * This function will create the Vue expected callbacks for Objects, Arrays & Classes
273
+ * but leave primitive properties and functions intact so that
274
+ * the final object is fully defineComponent() style compatible.
275
+ *
276
+ * @param defaults Regular object of default key -> values
277
+ * @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
278
+ * @returns Props declared in defineComponent() style with all properties having default property declared.
279
+ */
280
+ export const propsWithDefaults = <T extends VuePropsObject>(
281
+ defaults: Record<string, any>,
282
+ typedProps: T,
283
+ // Optional: Allows user to pass a custom cloner if structuredClone isn't enough
284
+ customCloner?: (val: any) => any
285
+ ): VuePropsWithDefaults<T> => {
286
+ for (const prop in typedProps) {
287
+ const def = defaults?.[prop];
288
+ const typed = typedProps[prop];
289
+
290
+ if (typed.required || def === undefined) continue;
291
+
292
+ if (typeof def === 'object' && def !== null) {
293
+ typedProps[prop].default = () =>
294
+ customCloner ? customCloner(def) : structuredClone(def);
295
+ } else {
296
+ if (isClass(def)) {
297
+ typedProps[prop].default = () => def;
298
+ } else {
299
+ typedProps[prop].default = def;
300
+ }
301
+ }
302
+ }
303
+ return typedProps as VuePropsWithDefaults<T>;
304
+ };
305
+
306
+ /**
307
+ * Type Utilities
308
+ */
309
+ type GetterKeys<T> = {
310
+ [K in keyof T]: T[K] extends (...args: any[]) => any
311
+ ? never
312
+ : T[K] extends undefined
313
+ ? never
314
+ : K;
315
+ }[keyof T];
316
+
317
+ type GetterReturn<T, K extends keyof T> = T[K] extends (...args: any[]) => any
318
+ ? never
319
+ : T[K];
320
+ type WritableComputedLike = Ref<any> & { set: (...args: any[]) => any };
321
+ type IsWritableGetter<R> = R extends Ref<any>
322
+ ? true
323
+ : R extends WritableComputedLike
324
+ ? true
325
+ : false;
326
+
327
+ type WritableGetters<T> = {
328
+ [K in GetterKeys<T> as IsWritableGetter<GetterReturn<T, K>> extends true
329
+ ? K
330
+ : never]-?: T[K];
331
+ };
332
+
333
+ export type ReactiveInstance<T> = T &
334
+ WritableGetters<T> & { $stopEffects: () => void };
335
+
336
+ export type ReactiveClass<C extends new (...args: any) => any> = new (
337
+ ...args: ConstructorParameters<C>
338
+ ) => ReactiveInstance<InstanceType<C>>;