ivue 1.5.8 → 2.0.0
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 +170 -33
- package/bin/ivue.mjs +152 -0
- package/dist/Reactive.d.ts +131 -0
- package/dist/env.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +90 -81
- package/dist/index.umd.js +1 -1
- package/lib/Reactive.ts +509 -0
- package/lib/__tests__/Reactive.vitest.spec.ts +1056 -0
- package/lib/__tests__/ReactiveAdversarial.vitest.spec.ts +182 -0
- package/lib/__tests__/coverage-completion.vitest.spec.ts +24 -0
- package/lib/__tests__/ivue.vitest.spec.ts +1460 -150
- package/lib/env.d.ts +1 -0
- package/lib/index.ts +1 -1
- package/lib/ivue.ts +661 -241
- package/lib/kernel.ts +18 -0
- package/package.json +51 -13
- package/skills/ivue/SKILL.md +726 -0
- package/dist/ivue.d.ts +0 -234
package/lib/Reactive.ts
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import {
|
|
2
|
+
effectScope,
|
|
3
|
+
isRef,
|
|
4
|
+
toRaw,
|
|
5
|
+
watch,
|
|
6
|
+
watchEffect,
|
|
7
|
+
type ExtractPropTypes,
|
|
8
|
+
type Ref,
|
|
9
|
+
} from 'vue';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Constants & Helpers
|
|
13
|
+
*/
|
|
14
|
+
const hasOwn = Object.hasOwn;
|
|
15
|
+
const getPrototypeOf = Object.getPrototypeOf;
|
|
16
|
+
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
17
|
+
const getOwnPropertyNames = Object.getOwnPropertyNames;
|
|
18
|
+
const defineProperty = Object.defineProperty;
|
|
19
|
+
const objectPrototype = Object.prototype;
|
|
20
|
+
|
|
21
|
+
// Identity symbols are global so every bundled copy agrees with objects
|
|
22
|
+
// stamped by another copy of the engine.
|
|
23
|
+
const RAW = Symbol.for('ivue.raw'); // Per-instance back-pointer to the raw object
|
|
24
|
+
const SCOPE = Symbol.for('ivue.scope'); // Lazily-created per-instance effect scope
|
|
25
|
+
// Marks a prototype level as "Reactified"; its VALUE is the list of
|
|
26
|
+
// engine-created instance-cache symbols for that level, so teardown can
|
|
27
|
+
// remove exactly the engine's cells and nothing else.
|
|
28
|
+
const PROCESSED = Symbol.for('ivue.processed');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the TRUE raw instance from whatever `this` the engine was entered
|
|
32
|
+
* with — the raw object itself, a Vue `reactive()` proxy, or a foreign proxy
|
|
33
|
+
* chain such as Vue's component expose proxy.
|
|
34
|
+
*
|
|
35
|
+
* Neither primitive is sufficient alone:
|
|
36
|
+
*
|
|
37
|
+
* - `toRaw(this)` cannot unwrap a foreign (non-Vue-reactive) proxy — e.g.
|
|
38
|
+
* Vue's component expose proxy — so it can return the proxy unchanged.
|
|
39
|
+
* - The RAW back-pointer read through a Vue reactive proxy comes back
|
|
40
|
+
* DEEP-WRAPPED: a reactive proxy wraps symbol-keyed object reads in
|
|
41
|
+
* `reactive(raw)`. Binding a method (or computed closure) to that wrapped
|
|
42
|
+
* value poisons the per-instance cache with ref-unwrapping `this`
|
|
43
|
+
* semantics — `this.x.value` then crashes, because `this.x` auto-unwraps
|
|
44
|
+
* to the plain value.
|
|
45
|
+
*
|
|
46
|
+
* So: try `toRaw()` first (one step for the common reactive-proxy path —
|
|
47
|
+
* consulting the pointer first costs a wrap+unwrap round-trip per access),
|
|
48
|
+
* and fall back to the pointer, normalized with `toRaw()`, for everything
|
|
49
|
+
* `toRaw()` cannot see through.
|
|
50
|
+
*/
|
|
51
|
+
function resolveRaw(self: any) {
|
|
52
|
+
const unwrapped = toRaw(self);
|
|
53
|
+
if (unwrapped !== self) {
|
|
54
|
+
// Genuine Vue reactive proxy. Stamp the back-pointer (once, directly on
|
|
55
|
+
// the raw — no proxy set traps) so foreign proxy chains can still
|
|
56
|
+
// resolve the true raw through it.
|
|
57
|
+
return unwrapped[RAW] ?? (unwrapped[RAW] = unwrapped);
|
|
58
|
+
}
|
|
59
|
+
// `self` is the raw object itself, or a foreign proxy over it.
|
|
60
|
+
const viaPointer = self[RAW];
|
|
61
|
+
if (viaPointer) {
|
|
62
|
+
// A pointer read through a proxy chain may come back deep-wrapped —
|
|
63
|
+
// normalize; on the raw object it is already the raw itself.
|
|
64
|
+
return viaPointer === self ? viaPointer : toRaw(viaPointer);
|
|
65
|
+
}
|
|
66
|
+
// First-ever engine access on a plain raw instance (direct `new Class()`).
|
|
67
|
+
return (self[RAW] = self);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert a method to a lazy-bound prototype method.
|
|
72
|
+
*
|
|
73
|
+
* The bound function is created once, on first access, and cached on the raw
|
|
74
|
+
* object under a unique per-(prototype,key) symbol — giving referentially
|
|
75
|
+
* stable, correctly-bound methods with zero per-instance construction cost.
|
|
76
|
+
*/
|
|
77
|
+
function convertToLazyBoundMethod(
|
|
78
|
+
proto: any,
|
|
79
|
+
key: string,
|
|
80
|
+
superKey: symbol,
|
|
81
|
+
originalFn: (...args: any[]) => any,
|
|
82
|
+
) {
|
|
83
|
+
defineProperty(proto, key, {
|
|
84
|
+
configurable: true,
|
|
85
|
+
enumerable: false,
|
|
86
|
+
get(this: any) {
|
|
87
|
+
const raw = resolveRaw(this);
|
|
88
|
+
return raw[superKey] ?? (raw[superKey] = originalFn.bind(raw));
|
|
89
|
+
},
|
|
90
|
+
set(this: any, newFn: any) {
|
|
91
|
+
resolveRaw(this)[superKey] = newFn;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convert a getter to a lazily-cached Ref cell.
|
|
98
|
+
*
|
|
99
|
+
* Only ever called when the descriptor has a getter, so `originalGetter` is
|
|
100
|
+
* always defined here. A getter that returns any Ref — ref(), shallowRef(),
|
|
101
|
+
* computed() (a ComputedRef IS a Ref) — is cached under the instance symbol
|
|
102
|
+
* (stable reactive identity); a getter that returns a plain value
|
|
103
|
+
* de-optimizes back to a native getter on the prototype, removing all
|
|
104
|
+
* overhead for future instances.
|
|
105
|
+
*/
|
|
106
|
+
function convertToLazyRef(
|
|
107
|
+
proto: any,
|
|
108
|
+
key: string,
|
|
109
|
+
superKey: symbol,
|
|
110
|
+
originalGetter: (this: any) => any,
|
|
111
|
+
originalSetter: ((this: any, v: any) => any) | undefined,
|
|
112
|
+
) {
|
|
113
|
+
// Optimization: Properties starting with $ are assumed singletons.
|
|
114
|
+
const cacheWhole = key[0] === '$';
|
|
115
|
+
|
|
116
|
+
const newGetter = function (this: any) {
|
|
117
|
+
const raw = resolveRaw(this);
|
|
118
|
+
|
|
119
|
+
// 1. Check cache
|
|
120
|
+
if (superKey in raw) return raw[superKey];
|
|
121
|
+
|
|
122
|
+
// 2. Execute original
|
|
123
|
+
const result = originalGetter.call(raw);
|
|
124
|
+
|
|
125
|
+
// 3. Handle result
|
|
126
|
+
if (cacheWhole) {
|
|
127
|
+
// Cache result forever (Singleton pattern)
|
|
128
|
+
raw[superKey] = result;
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (isRef(result)) {
|
|
133
|
+
// Cache Ref instance (Reactivity pattern)
|
|
134
|
+
raw[superKey] = result;
|
|
135
|
+
} else {
|
|
136
|
+
// DE-OPTIMIZATION: It's just a value. Restore a native getter on the
|
|
137
|
+
// prototype, removing the wrapper overhead for all future instances.
|
|
138
|
+
defineProperty(proto, key, {
|
|
139
|
+
configurable: true,
|
|
140
|
+
enumerable: false,
|
|
141
|
+
get(this: any) {
|
|
142
|
+
return originalGetter.call(resolveRaw(this));
|
|
143
|
+
},
|
|
144
|
+
set: originalSetter
|
|
145
|
+
? function (this: any, v: any) {
|
|
146
|
+
return originalSetter.call(resolveRaw(this), v);
|
|
147
|
+
}
|
|
148
|
+
: undefined,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return result;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
defineProperty(proto, key, {
|
|
156
|
+
configurable: true,
|
|
157
|
+
enumerable: false,
|
|
158
|
+
get: newGetter,
|
|
159
|
+
set: originalSetter
|
|
160
|
+
? function (this: any, v: any) {
|
|
161
|
+
return originalSetter.call(resolveRaw(this), v);
|
|
162
|
+
}
|
|
163
|
+
: undefined,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create a reactive class.
|
|
169
|
+
* @param targetClass The class to make reactive.
|
|
170
|
+
* @returns A reactive version of the class (the same class, transformed in place).
|
|
171
|
+
*/
|
|
172
|
+
export function Reactive<C extends new (...args: any) => any>(
|
|
173
|
+
targetClass: C,
|
|
174
|
+
): ReactiveClass<C> & { Instance: ReactiveInstance<InstanceType<C>> } {
|
|
175
|
+
const chain: any[] = [];
|
|
176
|
+
|
|
177
|
+
let targetPrototype = targetClass.prototype;
|
|
178
|
+
while (targetPrototype && targetPrototype !== objectPrototype) {
|
|
179
|
+
chain.push(targetPrototype);
|
|
180
|
+
targetPrototype = getPrototypeOf(targetPrototype);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Process Base -> Child
|
|
184
|
+
chain.reverse();
|
|
185
|
+
|
|
186
|
+
for (const prototype of chain) {
|
|
187
|
+
// OPTIMIZATION: Skip if this prototype layer is already "Reactified".
|
|
188
|
+
// This handles diamond inheritance and multiple Reactive children safely.
|
|
189
|
+
if (hasOwn(prototype, PROCESSED)) continue;
|
|
190
|
+
|
|
191
|
+
const names = getOwnPropertyNames(prototype);
|
|
192
|
+
const cacheKeys: symbol[] = [];
|
|
193
|
+
|
|
194
|
+
for (const key of names) {
|
|
195
|
+
if (key === 'constructor') continue;
|
|
196
|
+
const desc = getOwnPropertyDescriptor(prototype, key)!;
|
|
197
|
+
|
|
198
|
+
if (typeof desc.value === 'function') {
|
|
199
|
+
// A fresh symbol per (prototype,key). Because each prototype level gets
|
|
200
|
+
// its own symbol, a child override and its `super` counterpart cache
|
|
201
|
+
// under different keys and never collide.
|
|
202
|
+
const superKey = Symbol(key);
|
|
203
|
+
cacheKeys.push(superKey);
|
|
204
|
+
convertToLazyBoundMethod(prototype, key, superKey, desc.value);
|
|
205
|
+
} else if (desc.get) {
|
|
206
|
+
const superKey = Symbol(key);
|
|
207
|
+
cacheKeys.push(superKey);
|
|
208
|
+
convertToLazyRef(prototype, key, superKey, desc.get, desc.set);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Mark this prototype level as processed; the marker carries the
|
|
213
|
+
// level's engine cache keys (see PROCESSED above).
|
|
214
|
+
defineProperty(prototype, PROCESSED, {
|
|
215
|
+
value: cacheKeys,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Inject the $watch/$watchEffect/$stopEffects helpers. The guard is an
|
|
220
|
+
// IDEMPOTENCY SENTINEL only — one key stands for the whole trio, so a
|
|
221
|
+
// repeated Reactive() call (diamond imports, duplicate bundled engine
|
|
222
|
+
// copies) skips re-injection. It is NOT override protection: the $-helper
|
|
223
|
+
// names are reserved engine API (richer cleanup is an ordinary method
|
|
224
|
+
// that calls $stopEffects() itself — ivue never auto-calls user code).
|
|
225
|
+
if (!hasOwn(targetClass.prototype, '$stopEffects')) {
|
|
226
|
+
/**
|
|
227
|
+
* Register a watcher in this instance's lazily-created effect scope.
|
|
228
|
+
* The scope is allocated only on first use, so pure-data classes that
|
|
229
|
+
* never watch pay nothing. Has the same signature as Vue's `watch`.
|
|
230
|
+
*/
|
|
231
|
+
defineProperty(targetClass.prototype, '$watch', {
|
|
232
|
+
enumerable: false,
|
|
233
|
+
configurable: true,
|
|
234
|
+
writable: true,
|
|
235
|
+
value: function (this: any, ...args: any[]) {
|
|
236
|
+
const raw = resolveRaw(this);
|
|
237
|
+
const scope =
|
|
238
|
+
raw[SCOPE] ?? (raw[SCOPE] = effectScope(true /* detached */));
|
|
239
|
+
return scope.run(() => (watch as any)(...args));
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Register a watchEffect in the same lazy per-instance scope.
|
|
245
|
+
*/
|
|
246
|
+
defineProperty(targetClass.prototype, '$watchEffect', {
|
|
247
|
+
enumerable: false,
|
|
248
|
+
configurable: true,
|
|
249
|
+
writable: true,
|
|
250
|
+
value: function (this: any, ...args: any[]) {
|
|
251
|
+
const raw = resolveRaw(this);
|
|
252
|
+
const scope =
|
|
253
|
+
raw[SCOPE] ?? (raw[SCOPE] = effectScope(true /* detached */));
|
|
254
|
+
return scope.run(() => (watchEffect as any)(...args));
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Tear down the instance: stop its effect scope (any watchers created via
|
|
260
|
+
* $watch) and drop all cached cells so refs/computeds become collectable.
|
|
261
|
+
* No hooks — ivue never calls user code; compose richer cleanup as an
|
|
262
|
+
* ordinary method that does its own work and then calls $stopEffects().
|
|
263
|
+
*/
|
|
264
|
+
defineProperty(targetClass.prototype, '$stopEffects', {
|
|
265
|
+
enumerable: false,
|
|
266
|
+
configurable: true,
|
|
267
|
+
writable: true,
|
|
268
|
+
value: function (this: any) {
|
|
269
|
+
const raw = resolveRaw(this);
|
|
270
|
+
try {
|
|
271
|
+
const scope = raw[SCOPE];
|
|
272
|
+
if (scope) scope.stop();
|
|
273
|
+
} finally {
|
|
274
|
+
// SCOPE is ivue-owned but is not a method/getter cache key.
|
|
275
|
+
delete raw[SCOPE];
|
|
276
|
+
|
|
277
|
+
// Each processed prototype's PROCESSED marker carries the
|
|
278
|
+
// symbols it may cache on an instance. Walk Child -> Base and
|
|
279
|
+
// remove only those known keys.
|
|
280
|
+
let prototype = getPrototypeOf(raw);
|
|
281
|
+
while (prototype && prototype !== objectPrototype) {
|
|
282
|
+
const cacheKeys = prototype[PROCESSED] as
|
|
283
|
+
| readonly symbol[]
|
|
284
|
+
| undefined;
|
|
285
|
+
if (cacheKeys) {
|
|
286
|
+
for (const cacheKey of cacheKeys) delete raw[cacheKey];
|
|
287
|
+
}
|
|
288
|
+
prototype = getPrototypeOf(prototype);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return targetClass as any;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Vue props interface in defineComponent() style.
|
|
300
|
+
*/
|
|
301
|
+
export type VuePropsObject = Record<
|
|
302
|
+
string,
|
|
303
|
+
{ type: any; default?: any; required?: boolean }
|
|
304
|
+
>;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Vue Props with default properties declared as existing and having values.
|
|
308
|
+
*/
|
|
309
|
+
export type VuePropsWithDefaults<T extends VuePropsObject> = {
|
|
310
|
+
[K in keyof T]: {
|
|
311
|
+
type: T[K]['type'];
|
|
312
|
+
default: T[K]['default'];
|
|
313
|
+
required?: boolean;
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Determines if the value is a JavaScript Class.
|
|
319
|
+
* Note that class is a class function in JavaScript.
|
|
320
|
+
*
|
|
321
|
+
* @param val Any value
|
|
322
|
+
* @returns boolean If it's a JavaScript Class returns true
|
|
323
|
+
*/
|
|
324
|
+
export const isClass = (val: any): boolean => {
|
|
325
|
+
if (typeof val !== 'function') return false; // Not a function, so not a class function either
|
|
326
|
+
|
|
327
|
+
if (!val.prototype) return false; // Arrow function, so not a class
|
|
328
|
+
|
|
329
|
+
// Finally -> distinguish between a normal function and a class function
|
|
330
|
+
if (getOwnPropertyDescriptor(val, 'prototype')?.writable) {
|
|
331
|
+
// Has writable prototype
|
|
332
|
+
return false; // Normal function
|
|
333
|
+
} else {
|
|
334
|
+
return true; // Class -> Not a function
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Creates props with defaults in defineComponent() style.
|
|
339
|
+
*
|
|
340
|
+
* Merge defaults regular object with Vue types object
|
|
341
|
+
* declared in defineComponent() style.
|
|
342
|
+
*
|
|
343
|
+
* This is made so that the defaults can be declared "as they are"
|
|
344
|
+
* without requiring objects to be function callbacks returning an object.
|
|
345
|
+
*
|
|
346
|
+
* // You don't need to wrap objects in () => ({ nest: { nest :{} } })
|
|
347
|
+
* // You can just delcare them normally.
|
|
348
|
+
* const defaults = {
|
|
349
|
+
* nest: {
|
|
350
|
+
* nest
|
|
351
|
+
* }
|
|
352
|
+
* }
|
|
353
|
+
*
|
|
354
|
+
* This function will create the Vue expected callbacks for Objects, Arrays & Classes
|
|
355
|
+
* but leave primitive properties and functions intact so that
|
|
356
|
+
* the final object is fully defineComponent() style compatible.
|
|
357
|
+
*
|
|
358
|
+
* The default cloner is the native `structuredClone` (zero-dependency, handles
|
|
359
|
+
* plain data, Map/Set/Date/typed arrays, circular refs). For defaults that
|
|
360
|
+
* contain class instances or functions — which `structuredClone` cannot clone —
|
|
361
|
+
* pass a `customCloner` such as lodash `cloneDeep`.
|
|
362
|
+
*
|
|
363
|
+
* @param defaults Regular object of default key -> values
|
|
364
|
+
* @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
|
|
365
|
+
* @param customCloner Optional cloner used for object/array defaults (defaults to structuredClone)
|
|
366
|
+
* @returns Props declared in defineComponent() style with all properties having default property declared.
|
|
367
|
+
*/
|
|
368
|
+
export const propsWithDefaults = <T extends VuePropsObject>(
|
|
369
|
+
defaults: Record<string, any>,
|
|
370
|
+
typedProps: T,
|
|
371
|
+
// Optional: Allows user to pass a custom cloner if structuredClone isn't enough
|
|
372
|
+
customCloner?: (val: any) => any,
|
|
373
|
+
): VuePropsWithDefaults<T> => {
|
|
374
|
+
// NON-MUTATING: descriptor objects are routinely SHARED between props
|
|
375
|
+
// maps (`{ ...baseParamsTypes, extra }` — the spread copies the outer
|
|
376
|
+
// object but every inner `{ type }` descriptor stays the same reference).
|
|
377
|
+
// Writing `.default` in place would silently rewrite the base component's
|
|
378
|
+
// defaults; each descriptor is copied instead.
|
|
379
|
+
const result: Record<string, any> = {};
|
|
380
|
+
for (const prop in typedProps) {
|
|
381
|
+
const def = defaults?.[prop];
|
|
382
|
+
const typed = typedProps[prop];
|
|
383
|
+
result[prop] = { ...typed };
|
|
384
|
+
|
|
385
|
+
if (typed.required || def === undefined) continue;
|
|
386
|
+
|
|
387
|
+
if (typeof def === 'object' && def !== null) {
|
|
388
|
+
result[prop].default = () =>
|
|
389
|
+
customCloner ? customCloner(def) : structuredClone(def);
|
|
390
|
+
} else {
|
|
391
|
+
if (isClass(def)) {
|
|
392
|
+
result[prop].default = () => def;
|
|
393
|
+
} else {
|
|
394
|
+
result[prop].default = def;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return result as VuePropsWithDefaults<T>;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Type Utilities
|
|
403
|
+
*/
|
|
404
|
+
type GetterKeys<T> = {
|
|
405
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any
|
|
406
|
+
? never
|
|
407
|
+
: T[K] extends undefined
|
|
408
|
+
? never
|
|
409
|
+
: K;
|
|
410
|
+
}[keyof T];
|
|
411
|
+
|
|
412
|
+
type GetterReturn<T, K extends keyof T> = T[K] extends (...args: any[]) => any
|
|
413
|
+
? never
|
|
414
|
+
: T[K];
|
|
415
|
+
type WritableComputedLike = Ref<any> & { set: (...args: any[]) => any };
|
|
416
|
+
type IsWritableGetter<R> =
|
|
417
|
+
R extends Ref<any> ? true : R extends WritableComputedLike ? true : false;
|
|
418
|
+
|
|
419
|
+
type WritableGetters<T> = {
|
|
420
|
+
[
|
|
421
|
+
K in GetterKeys<T> as IsWritableGetter<GetterReturn<T, K>> extends true
|
|
422
|
+
? K
|
|
423
|
+
: never
|
|
424
|
+
]-?: T[K];
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
export type ReactiveInstance<T> = T &
|
|
428
|
+
WritableGetters<T> & {
|
|
429
|
+
/** Register a watcher in the instance's lazy effect scope (same signature as Vue `watch`). */
|
|
430
|
+
$watch: typeof watch;
|
|
431
|
+
/** Register a watchEffect in the instance's lazy effect scope (same signature as Vue `watchEffect`). */
|
|
432
|
+
$watchEffect: typeof watchEffect;
|
|
433
|
+
/** Stop the instance's effect scope and drop cached cells. */
|
|
434
|
+
$stopEffects: () => void;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
export type ReactiveClass<C extends new (...args: any) => any> = new (
|
|
438
|
+
...args: ConstructorParameters<C>
|
|
439
|
+
) => ReactiveInstance<InstanceType<C>>;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Component-authoring type utilities (types only — erased at build time).
|
|
443
|
+
* These complement `propsWithDefaults` for the params/defaults component
|
|
444
|
+
* architecture: object-declared emits, extensible slots, and precise
|
|
445
|
+
* handler-parameter extraction.
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
/** Any JavaScript function of any type. */
|
|
449
|
+
export type AnyFn = (...args: any[]) => any;
|
|
450
|
+
|
|
451
|
+
/** Convert Record to Union Type. */
|
|
452
|
+
export type RecordToUnion<T extends Record<string, any>> = T[keyof T];
|
|
453
|
+
|
|
454
|
+
/** Gets object T property by key K. */
|
|
455
|
+
export type ValueOf<T extends Record<any, any>, K extends keyof T> = T[K];
|
|
456
|
+
|
|
457
|
+
/** Convert Union Type to Intersection Type. */
|
|
458
|
+
export type UnionToIntersection<U> = (
|
|
459
|
+
U extends any ? (k: U) => void : never
|
|
460
|
+
) extends (k: infer I) => void
|
|
461
|
+
? I
|
|
462
|
+
: never;
|
|
463
|
+
|
|
464
|
+
/** Prefix keys of an interface T with a prefix P. */
|
|
465
|
+
export type PrefixKeys<T, P extends string | undefined = undefined> = {
|
|
466
|
+
[K in Extract<keyof T, string> as P extends string ? `${P}${K}` : K]: T[K];
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
/** Extracts object-declared emit validators into the emit-function interface. */
|
|
470
|
+
export type ExtractEmitTypes<T extends Record<string, any>> =
|
|
471
|
+
UnionToIntersection<
|
|
472
|
+
RecordToUnion<{
|
|
473
|
+
[K in keyof T]: (evt: K, ...args: Parameters<T[K]>) => void;
|
|
474
|
+
}>
|
|
475
|
+
>;
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Extract properties as all-assigned (non-optional) because every one of
|
|
479
|
+
* them carries a default.
|
|
480
|
+
*/
|
|
481
|
+
export type ExtractPropDefaultTypes<O> = {
|
|
482
|
+
[K in keyof O]: K extends keyof ExtractPropTypes<O>
|
|
483
|
+
? ExtractPropTypes<O>[K]
|
|
484
|
+
: never;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Extend a slots interface T with prefixed 'before--' & 'after--' slots to
|
|
489
|
+
* create fully extensible wrapped components.
|
|
490
|
+
*/
|
|
491
|
+
export type ExtendSlots<T> = PrefixKeys<T, 'before--'> &
|
|
492
|
+
T &
|
|
493
|
+
PrefixKeys<T, 'after--'>;
|
|
494
|
+
|
|
495
|
+
/** Get function arguments Parameters<F> parameter by index K. */
|
|
496
|
+
export type FnParameter<F extends AnyFn, K extends number> = Parameters<F>[K];
|
|
497
|
+
|
|
498
|
+
/** Get interface T property K's function arguments as Parameters. */
|
|
499
|
+
export type IFnParameters<
|
|
500
|
+
T extends Record<any, any>,
|
|
501
|
+
K extends string,
|
|
502
|
+
> = Parameters<Required<Pick<T, K>>[K]>;
|
|
503
|
+
|
|
504
|
+
/** Get interface T property P's function parameter by index K. */
|
|
505
|
+
export type IFnParameter<
|
|
506
|
+
T extends Record<any, any>,
|
|
507
|
+
P extends keyof T,
|
|
508
|
+
K extends number,
|
|
509
|
+
> = FnParameter<NonNullable<T[P]> extends AnyFn ? NonNullable<T[P]> : never, K>;
|