Skip to content
ivue.ts used in the examples below (click to expand)
ts
import type { 
ComputedRef
,
ExtractPropTypes
,
Ref
,
ToRef
} from 'vue';
import {
computed
,
markRaw
,
reactive
,
ref
,
shallowRef
,
toRef
} from 'vue';
import type {
Ref
as
DemiRef
} from 'vue-demi';
/** Cached global methods. */ const
isArray
=
Array
.
isArray
;
const
createObject
=
Object
.
create
;
const
defineProperty
=
Object
.
defineProperty
;
const
getPrototypeOf
=
Object
.
getPrototypeOf
;
const
getOwnPropertyNames
=
Object
.
getOwnPropertyNames
;
const
getOwnPropertyDescriptor
=
Object
.
getOwnPropertyDescriptor
;
const
getOwnPropertyDescriptors
=
Object
.
getOwnPropertyDescriptors
;
/** Types */ /** * IVue core reactive instance type with an extended .toRefs() method added. */ export type
IVue
<
T
extends
AnyClass
> =
InstanceType
<
T
> & {
toRefs
:
IVueToRefsFn
<
T
>;
clone
:
IVueClone
<
T
>;
}; /** * Type definition for `.clone()` method creates a deep clone of the ivue instance. */ export type
IVueClone
<
T
extends
AnyClass
> = (
...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>
) =>
IVue
<
T
>;
/** * Type definition for `.toRefs()` method converts reactive class properties to composable .value properties. * 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. */ /** Extract only non-method keys from a type */ type
NonMethodKeys
<
T
> = {
[
K
in keyof
T
]:
T
[
K
] extends
AnyFn
? never :
K
;
}[keyof
T
];
/** Create a type with only data properties and accessors */ type
DataProperties
<
T
> =
Pick
<
T
,
NonMethodKeys
<
T
>>;
/** * Simplified IVueToRefsFn that only works with data properties */ interface
IVueToRefsFn
<
T
extends
AnyClass
> {
<
P
extends keyof
IVueRefs
<
InstanceType
<
T
>>>(
props
:
P
[]):
Pick
<
IVueRefs
<
InstanceType
<
T
>>,
P
>; <
P
extends keyof
IVueRefs
<
InstanceType
<
T
>>>(
props
:
P
[],
unwrap
: false):
Pick
<
IVueRefs
<
InstanceType
<
T
>>,
P
>; <
P
extends keyof
DataProperties
<
InstanceType
<
T
>>>(
props
:
P
[],
unwrap
: true
):
Pick
<
DataProperties
<
InstanceType
<
T
>>,
P
>;
(
props
: true):
DataProperties
<
InstanceType
<
T
>>;
(
props
: false):
IVueRefs
<
InstanceType
<
T
>>;
():
IVueRefs
<
InstanceType
<
T
>>;
} /** * Converts a class properties to a composable .value Refs using ToRef vue type, * also knows NOT to convert functions to .value Refs but to leave them as is. */ export type
IVueRefs
<
T
> = {
[
K
in keyof
T
as
T
[
K
] extends
AnyFn
? never :
K
]:
ToRef
<
T
[
K
]>;
}; /** * Unwraps Ref Recursively, helps resolve fully the bare value types of any type T * because sometimes the Refs are returned as they are with `.value` from computeds, * thus inferring nested ComputedRef<ComputedRef<ComputedRef<Ref>>> types, which * are difficult to fully resolve to bare values without this utility. * Also support Vue 3 Demi for vue-use library support. */ type
UnwrapRefRecursively
<
T
= any> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
['value']>
:
T
;
/** * Helper type for Use type, unwraps any type of Vue 3 composable return object down to its bare types. */ type
UnwrapComposableRefs
<
T
> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
>
: { [
K
in keyof
T
]:
T
[
K
] extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
[
K
]>
:
T
[
K
];
}; /** * Fully unwraps to bare value types any Vue 3 composable return definition type. */ export type
Use
<
T
= any> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
>
:
UnwrapComposableRefs
<
T
extends
AnyFn
?
ReturnType
<
T
> :
T
>;
/** * Extracts object defined emit types by converting them to a plain interface. */ export type
ExtractEmitTypes
<
T
extends
Record
<string, any>> =
UnionToIntersection
<
RecordToUnion
<{
[
K
in keyof
T
]: (
evt
:
K
, ...
args
:
Parameters
<
T
[
K
]>) => void;
}> >; /** * Extract properties as all assigned properties because they have defaults. */ export type
ExtractPropDefaultTypes
<
O
> = {
[
K
in keyof
O
]:
ValueOf
<
ExtractPropTypes
<
O
>,
K
>;
}; /** * Extend slots interface T with prefixed 'before--' & 'after--' slots to create fully extensible flexible slots. */ export type
ExtendSlots
<
T
> =
PrefixKeys
<
T
, 'before--'> &
T
&
PrefixKeys
<
T
, 'after--'>;
/** Helper Types. */ /** * Any JavaScript function of any type. */ export type
AnyFn
= (...
args
: any[]) => any;
/** * Any JavaScript class of any type. */ export type
AnyClass
= new (...
args
: any[]) => any;
/** * Accessors map type. */ type
Accessors
=
Map
<string, PropertyDescriptor>;
/** * Computeds hash map type. */ type
Computeds
=
Record
<string,
ComputedRef
>;
/** * Infer paramaters of a constructor function of a Class. */ export type
InferredArgs
<
T
> =
T
extends { new (...
args
: infer
P
): any }
?
P
: never[]; /** * Prefix keys of an interface T with a prefix P. */ export type
PrefixKeys
<
T
,
P
extends string | undefined = undefined> = {
[
K
in
Extract
<keyof
T
, string> as
P
extends string ? `${
P
}${
K
}` :
K
]:
T
[
K
];
}; /** * Get function arguments Parmeters<F> parameter by key K */ export type
FnParameter
<
F
extends
AnyFn
,
K
extends number> =
Parameters
<
F
>[
K
];
/** * Convert Union Type to Intersection Type. */ type
UnionToIntersection
<
U
> = (
U
extends any ? (
k
:
U
) => void : never) extends (
k
: infer
I
) => void ?
I
: never; /** * Convert Record to Union Type. */ type
RecordToUnion
<
T
extends
Record
<string, any>> =
T
[keyof
T
];
/** * Gets object T property by key [K]. */ type
ValueOf
<
T
extends
Record
<any, any>,
K
> =
T
[
K
];
/** * Props Map Value type. */ type
PropsMapValue
= {
allProps
:
Map
<string,
PropKind
>;
onlyProps
:
Set
<string>;
}; /** * Accessors and Methods map type. */ type
AccessorsMethodsMap
= {
accessors
:
Accessors
;
methods
:
Set
<string>;
}; export type
IVueInstanceBase
= {
init
?: (...
args
: any[]) => void;
}; /** * IVue static configuration interface. */ export interface
IVueStaticConfig
<
T
> {
ivueGlobalStore
?: boolean;
ivueCloneByReference
?:
Set
<keyof
T
>;
ivueDisableReactivity
?:
Set
<keyof
T
>;
} /** * IVue constructor type. */ export type
IVueConstructor
<
T
> = new (...
args
: any[]) =>
T
;
/** * IVue class type with static configuration. */ export type
IVueClass
<
T
> =
IVueConstructor
<
T
> &
IVueStaticConfig
<
T
>;
/** * Get IVue init() method arguments IVueInitArgs<T> */ export type
IVueInitArgs
<
T
extends
IVueInstanceBase
> =
T
['init'] extends (
...
args
: any[]
) => any ?
Parameters
<
T
['init']>
: never; /** * Get Interface's method keys MethodKeys<T> */ type
MethodKeys
<
T
> = {
[
P
in keyof
T
]:
T
[
P
] extends (...
args
: any[]) => any ?
P
: never;
}[keyof
T
];
/** * Get Interface's property's function arguments Parameters<F> */ export type
IFnParameters
<
T
extends
Record
<any, any> |
InstanceType
<
AnyClass
>,
K
extends
MethodKeys
<
T
>
> =
Parameters
<
Extract
<
Required
<
Pick
<
T
,
K
>>[
K
],
AnyFn
>>;
/** * Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K] */ export type
IFnParameter
<
T
extends
Record
<any, any>,
P
extends keyof
T
,
K
extends number
> =
FnParameter
<
ValueOf
<
T
,
P
>,
K
>;
export type
IVueDeepCloneArgsMap
=
Map
<
AnyClass
, any[]>;
type
CloneArgsFor
<
T
> =
T
extends {
init
: (
isClone
: boolean, ...
rest
: infer
A
) => any;
} ?
A
: []; /** Types End. */ export const
IVUE_INSTANCE_SYMBOL
=
Symbol
('IVUE_INSTANCE');
/** * Stores accessors for each class prototype processed by * @see {getClassAccessorsMethodsMap}. Uses WeakMap to allow garbage collection * of unused class accessors, ensuring memory efficiency in long-running applications. */ export let
accessorsMethodsMaps
= new
WeakMap
<object,
AccessorsMethodsMap
>();
/** * Get accessors of an entire class prototype ancestors chain as a Map. * Completely emulates JavaScript class inheritance chain for getters and setters. * * @param className * @return {AccessorsMethodsMap} */ const
getClassAccessorsMethodsMap
= (
className
:
AnyClass
):
AccessorsMethodsMap
=> {
if (
accessorsMethodsMaps
.
has
(
className
)) {
return
accessorsMethodsMaps
.
get
(
className
) as
AccessorsMethodsMap
;
} const
savedAccessors
:
Accessors
= new
Map
();
const
savedMethods
:
Set
<string> = new
Set
();
let
prototype
=
className
.
prototype
;
while (
prototype
&&
prototype
!==
Object
.
prototype
) {
const
accessors
=
getOwnPropertyDescriptors
(
prototype
);
for (const
propertyName
in
accessors
) {
if (
propertyName
=== 'constructor') continue;
const
descriptor
=
accessors
[
propertyName
];
if (
descriptor
.
get
||
descriptor
.
set
) {
// Only add if it hasn't been defined yet (i.e. subclass overrides win) if (!
savedAccessors
.
has
(
propertyName
)) {
savedAccessors
.
set
(
propertyName
,
descriptor
);
} } else if (typeof
descriptor
.
value
=== 'function') {
savedMethods
.
add
(
propertyName
);
} }
prototype
=
getPrototypeOf
(
prototype
);
} const
propertyMap
= {
accessors
:
savedAccessors
,
methods
:
savedMethods
,
};
accessorsMethodsMaps
.
set
(
className
,
propertyMap
);
return
propertyMap
;
}; /** * Property kind enum for distinguishing between data properties and accessors. */ const enum
PropKind
{
Data
= 0,
Accessor
= 2,
} /** * Stores properties for each class prototype processed by * @see {getClassPropertiesAccessorsMap}. Uses WeakMap to allow garbage collection * of unused class properties, ensuring memory efficiency in long-running applications. */ export let
propertiesAccessorsMaps
:
WeakMap
<object,
PropsMapValue
> =
new
WeakMap
();
/** * 'caller', 'callee', 'arguments', 'constructor' are * special object properties, so should be skipped. * Also skip 'toRefs' as it's added by ivue and not part of the class itself. */ const
skipProps
= new
Set
(['caller', 'callee', 'arguments', 'constructor']);
/** * Get properties of an entire class prototype ancestors chain as a Map. */ const
getClassPropertiesAccessorsMap
= (
obj
: object):
PropsMapValue
=> {
const
constructor
=
obj
.
constructor
;
/* Retrieve props from cache. */ if (
propertiesAccessorsMaps
.
has
(
constructor
)) {
return
propertiesAccessorsMaps
.
get
(
constructor
) as
PropsMapValue
;
} const {
accessors
,
methods
} =
accessorsMethodsMaps
.
get
(
constructor
) ??
getClassAccessorsMethodsMap
(
constructor
as
AnyClass
);
const
onlyProps
= new
Set
<string>();
const
allProps
:
Map
<string,
PropKind
> = new
Map
();
do { const
propertyNames
=
getOwnPropertyNames
(
obj
);
for (let
i
= 0,
j
=
propertyNames
.
length
;
i
<
j
;
i
++) {
const
property
=
propertyNames
[
i
];
if (
skipProps
.
has
(
property
)) continue; // Skip special properties
if (
allProps
.
has
(
property
)) continue; // Already defined in subclass, skip it
if (
accessors
.
has
(
property
)) {
allProps
.
set
(
property
,
PropKind
.
Accessor
);
} else if (!
methods
.
has
(
property
)) {
onlyProps
.
add
(
property
);
allProps
.
set
(
property
,
PropKind
.
Data
);
} }
obj
=
getPrototypeOf
(
obj
);
} while (
obj
&&
obj
.
constructor
!==
Object
);
const
result
= {
allProps
,
onlyProps
};
propertiesAccessorsMaps
.
set
(
constructor
,
result
);
return
result
;
}; /** * Infinite Vue (ivue) class reactive initializer. * * Converts class instance to a reactive object, * where accessors are converted to computeds. * * You can turn off computed behaviour by adding static * ivue object and setting the getter props to false. * class ClassName { * static ivue = { * getter: false * } * // .getter -> will be a standard JS non-computed getter * get getter () { return 'hello world'; } * } * * @param className Any Class * @param args Class constructor arguments that you would pass to a `new AnyClass(args...)` * @returns {IVue<T>} */ export const
ivue
= <
T
extends
AnyClass
>(
className
:
T
,
...
args
:
InferredArgs
<
T
>
):
IVue
<
T
> => {
const {
accessors
,
methods
} =
getClassAccessorsMethodsMap
(
className
);
const
computeds
:
Computeds
| any =
accessors
?.
size
?
createObject
(null)
: null; /** Create a reactive instance of the class. */ const
vue
=
reactive
(new
className
(...
args
));
/** Setup accessors as computeds. */ for (const [
prop
,
descriptor
] of
accessors
) {
/** Convert descriptor to computed. */
defineProperty
(
vue
,
prop
, {
get
: () =>
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } /** Disable reactivity for specified properties. */ const
nonReactiveProps
= (
className
as any)?.ivueDisableReactivity;
if (
nonReactiveProps
) {
for (const
prop
of
nonReactiveProps
) {
const
descriptor
=
accessors
.
get
(
prop
);
if (
descriptor
) {
defineProperty
(
vue
,
prop
, {
get
:
descriptor
.
get
?.
bind
(
vue
),
set
:
descriptor
.
set
?.
bind
(
vue
),
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } else {
console
.
log
('des criptor',
prop
,
descriptor
);
vue
[
prop
] =
markRaw
(
vue
[
prop
]);
} } } // In ivue(), for method binding: const
methodDescriptor
: PropertyDescriptor = {
writable
: true,
configurable
: true,
enumerable
: false,
}; /** Bind all methods to the vue instance. */ for (const
methodName
of
methods
) {
methodDescriptor
.
value
=
vue
[
methodName
].bind(
vue
);
defineProperty
(
vue
,
methodName
,
methodDescriptor
);
} /** Define .toRefs() method on the vue instance. */
defineProperty
(
vue
, 'toRefs', {
get
: (() => {
/** Lazy loaded toRefs function cache. */ let
toRefsFn
:
IVueToRefsFn
<
T
> | null = null;
return () =>
toRefsFn
?? (
toRefsFn
=
ivueToRefs
(
vue
,
accessors
,
computeds
));
})(),
enumerable
: false,
}); /** Define .clone() method on the vue instance. */
defineProperty
(
vue
, 'clone', {
value
: (...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>) =>
ivueClone
(
className
,
args
,
vue
,
accessors
,
methods
,
cloneArgs
),
enumerable
: false,
}); /** Mark as ivue instance */
defineProperty
(
vue
,
IVUE_INSTANCE_SYMBOL
, {
value
: true,
enumerable
: false,
configurable
: false,
writable
: false,
}); /** Run ivue .init() initializer method, if it exists in the class. */
vue
?.init?.(false);
return
vue
;
}; const
ivueClone
= <
T
extends
AnyClass
>(
className
:
T
,
args
:
InferredArgs
<
T
>,
vueSrc
:
IVue
<
T
>,
accessors
:
Accessors
,
methods
:
Set
<string>,
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>> = [] as
CloneArgsFor
<
InstanceType
<
T
>>
):
IVue
<
T
> => {
const
computeds
:
Computeds
| any =
accessors
?.
size
?
createObject
(null)
: null; /** Create a reactive instance of the class. */ const
vue
=
reactive
(new
className
(...
args
));
const {
onlyProps
} =
getClassPropertiesAccessorsMap
(
vue
);
/** Clone properties. */ const
cloneByRef
= (
className
as any).ivueCloneByReference;
const
deepCloneArgs
=
activeDeepCloneArgsMap
.
get
(
className
);
if (
cloneByRef
) {
for (const
prop
of
onlyProps
) {
vue
[
prop
] =
cloneByRef
.has(
prop
)
?
vueSrc
[
prop
]
:
deepClone
(
vueSrc
[
prop
],
deepCloneArgs
);
} } else { for (const
prop
of
onlyProps
) {
vue
[
prop
] =
deepClone
(
vueSrc
[
prop
],
deepCloneArgs
);
} } /** Setup accessors as computeds. */ for (const [
prop
,
descriptor
] of
accessors
) {
/** Convert descriptor to computed. */
defineProperty
(
vue
,
prop
, {
get
: () =>
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } /** Disable reactivity for specified properties. */ const
nonReactiveProps
= (
className
as any)?.ivueDisableReactivity;
if (
nonReactiveProps
) {
for (const
prop
of
nonReactiveProps
) {
const
descriptor
=
accessors
.
get
(
prop
);
if (
descriptor
) {
defineProperty
(
vue
,
prop
, {
get
:
descriptor
.
get
?.
bind
(
vue
),
set
:
descriptor
.
set
?.
bind
(
vue
),
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } else {
vue
[
prop
] =
markRaw
(
vue
[
prop
]);
} } } /** Bind all methods to the vue instance. */ const
methodDescriptor
: PropertyDescriptor = {
writable
: true,
configurable
: true,
enumerable
: false,
}; for (const
methodName
of
methods
) {
methodDescriptor
.
value
=
vue
[
methodName
].bind(
vue
);
defineProperty
(
vue
,
methodName
,
methodDescriptor
);
} /** Define .toRefs() method on the vue instance. */
defineProperty
(
vue
, 'toRefs', {
get
: (() => {
/** Lazy loaded toRefs function cache. */ let
toRefsFn
:
IVueToRefsFn
<
T
> | null = null;
return () =>
toRefsFn
?? (
toRefsFn
=
ivueToRefs
(
vue
,
accessors
,
computeds
));
})(),
enumerable
: false,
}); /** Define .clone() method on the vue instance. */
defineProperty
(
vue
, 'clone', {
value
: (...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>) =>
ivueClone
(
className
,
args
,
vue
,
accessors
,
methods
,
cloneArgs
),
enumerable
: false,
}); /** Mark as ivue instance */
defineProperty
(
vue
,
IVUE_INSTANCE_SYMBOL
, {
value
: true,
enumerable
: false,
configurable
: false,
writable
: false,
}); /** Run ivue .init() initializer method, if it exists in the class. */
vue
?.init?.(true, ...
cloneArgs
);
return
vue
;
}; /** * `iref()` is an alias for Vue ref() function but returns an unwrapped type without the .value * `iref()` does not alter the behavior of ref(), but simply transforms the type to an unwrapped raw value. * @param val T * @returns {T} Ref but with type unwrapped */ export const
iref
=
ref
as <
T
= any>(
value
?:
T
) =>
T
;
/** * `ishallowRef()` is an alias for Vue shallowRef() function but returns an unwrapped type without the .value * `ishallowRef()` does not alter the behavior of shallowRef(), but simply transforms the type to an unwrapped raw value. * @param val T * @returns {T} ShallowRef but with type unwrapped */ export const
ishallowRef
=
shallowRef
as <
T
= any>(
value
?:
T
) =>
T
;
/** * `iuse()` is a helper function that unwraps any Vue 3 composable return type down to its bare types. * * It does not alter the behavior of the composable, but simply transforms the type to an unwrapped raw value. * * @param obj Any Vue 3 composable return type or any object or any type * @returns {Use<T>} Composable with types unwrapped */ export const
iuse
= <
T
extends object>(
obj
:
T
):
Use
<
T
> =>
obj
as unknown as
Use
<
T
>; /** Unwrap any other Object or any type down to its bare types. */
/** * Convert reactive ivue class to Vue 3 refs. * * @param vue @see IVue * @param accessors @see Accessors * @param computeds @see Computeds * @returns {ExtendWithToRefs<T>['toRefs']} */ const
ivueToRefs
= <
T
extends
AnyClass
>(
vue
:
IVue
<
T
>,
accessors
:
Accessors
,
computeds
:
Computeds
):
IVueToRefsFn
<
T
> => {
/** Caches for toRefs function */ /** Cached accessor function */ const
getAccessor
=
accessors
.
get
.
bind
(
accessors
);
/** Get all class properties */ const {
allProps
} =
getClassPropertiesAccessorsMap
(
vue
);
/** The actual toRefs function returned. */ return function (
props
: (string & keyof
InstanceType
<
T
>)[] | boolean = false,
unwrap
?: boolean /** This property helps with TypeScript function definition overloading of return types and is not being used inside the function itself. */
): any /** @see {ReturnType<IVueToRefsFn<T>>} */ { /** Resulting refs store. */ const
result
:
Record
<string, any> = {};
/** Convert all props to refs and leave functions as is. */ if (
isArray
(
props
)) {
for (const
prop
of
props
) {
const
kind
=
allProps
.
get
(
prop
);
resolveIvueToRefs
(
result
,
prop
,
kind
,
vue
,
getAccessor
,
computeds
);
} } else { for (const [
prop
,
kind
] of
allProps
.
entries
()) {
resolveIvueToRefs
(
result
,
prop
,
kind
,
vue
,
getAccessor
,
computeds
);
} } return
result
as any; /** @see {ReturnType<IVueToRefsFn<T>>} */
}; }; /** * Resolve property to ref, method or computed and assign it to result. * @private */ const
resolveIvueToRefs
= (
result
:
Record
<string, any>,
prop
: string,
kind
:
PropKind
| undefined,
vue
:
IVue
<any>,
getAccessor
:
Accessors
['get'],
computeds
:
Record
<string,
ComputedRef
<any>>
): void => { if (
kind
===
PropKind
.
Accessor
) {
const
descriptor
=
getAccessor
(
prop
) as PropertyDescriptor;
result
[
prop
] =
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)); } else if (
kind
===
PropKind
.
Data
) {
result
[
prop
] =
toRef
(
vue
,
prop
);
} }; /** * Vue props interface in defineComponent() style. */ export type
VuePropsObject
=
Record
<
string, {
type
: any;
default
?: any;
required
?: boolean }
>; /** * Vue Props with default properties declared as existing and having values. */ export type
VuePropsWithDefaults
<
T
extends
VuePropsObject
> = {
[
K
in keyof
T
]: {
type
:
T
[
K
]['type'];
default
:
T
[
K
]['default'];
required
?: boolean;
}; }; /** * Determines if the value is a JavaScript Class. * Note that class is a class function in JavaScript. * * @param val Any value * @returns boolean If it's a JavaScript Class returns true */ export const
isClass
= (
val
: any): boolean => {
if (typeof
val
!== 'function') return false; // Not a function, so not a class function either
if (!
val
.prototype) return false; // Arrow function, so not a class
// Finally -> distinguish between a normal function and a class function if (
getOwnPropertyDescriptor
(
val
, 'prototype')?.
writable
) {
// Has writable prototype return false; // Normal function } else { return true; // Class -> Not a function } }; /** * Creates props with defaults in defineComponent() style. * * Merge defaults regular object with Vue types object * declared in defineComponent() style. * * This is made so that the defaults can be declared "as they are" * without requiring objects to be function callbacks returning an object. * * // You don't need to wrap objects in () => ({ nest: { nest :{} } }) * // You can just delcare them normally. * const defaults = { * nest: { * nest * } * } * * This function will create the Vue expected callbacks for Objects, Arrays & Classes * but leave primitive properties and functions intact so that * the final object is fully defineComponent() style compatible. * * @param defaults Regular object of default key -> values * @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default * @returns Props declared in defineComponent() style with all properties having default property declared. */ export const
propsWithDefaults
= <
T
extends
VuePropsObject
>(
defaults
:
Record
<string, any>,
typedProps
:
T
,
deepCloneArgs
:
IVueDeepCloneArgsMap
| undefined =
undefined
):
VuePropsWithDefaults
<
T
> => {
for (const
prop
in
typedProps
) {
const
def
=
defaults
?.[
prop
];
const
typed
=
typedProps
[
prop
];
/** Skip if prop is required — Vue will enforce it at runtime */ if (
typed
.
required
) continue;
/** Skip if default is undefined */ if (
def
===
undefined
) continue;
if (typeof
def
=== 'object' &&
def
!== null) {
/** Handle Arrays & Objects -> wrap them with an arrow function. */
typedProps
[
prop
].
default
= () =>
deepClone
(
def
,
deepCloneArgs
);
} else { if (
isClass
(
def
)) {
/** Handle JavaScript Classes -> wrap them with an arrow function */
typedProps
[
prop
].
default
= () =>
def
;
} else { /** Handle JavaScript Function And All primitive properties -> output directly */
typedProps
[
prop
].
default
=
def
;
} } } return
typedProps
as
VuePropsWithDefaults
<
T
>;
}; /** * Copies own properties (including symbols and non-enumerable) from source to target. * Recursively deep clones values, but preserves getters/setters as-is. */ export const
copyOwnProps
= (
source
: any,
target
: any,
deepCloneArgs
:
IVueDeepCloneArgsMap
| undefined,
seen
:
WeakMap
<object, any>
) => { // 1. Get all descriptors (String keys AND Symbol keys) const
descriptors
=
Object
.
getOwnPropertyDescriptors
(
source
);
// 2. Iterate over all keys returned by Reflect (safety for Proxies/Environments) for (const
key
of Reflect.
ownKeys
(
descriptors
)) {
const
desc
=
descriptors
[
key
as any];
// 3. If it is a data descriptor (has a value), we must deep clone the value. // We do NOT clone getters/setters (we copy the function reference). if ('value' in
desc
) {
desc
.
value
=
deepClone
(
desc
.
value
,
deepCloneArgs
,
seen
);
} // 4. Define on target
Object
.
defineProperty
(
target
,
key
,
desc
);
} }; export let
activeDeepCloneArgsMap
= new
WeakMap
<
AnyClass
,
IVueDeepCloneArgsMap
>(); export const
deepClone
= (
source
: any,
deepCloneArgs
?:
IVueDeepCloneArgsMap
,
seen
:
WeakMap
<object, any> = new
WeakMap
()
): any => { // --- TIER 1: The "Every Call" Checks (Fastest) --- if (
source
== null || typeof
source
!== 'object') return
source
;
if (
seen
.
has
(
source
)) return
seen
.
get
(
source
);
// --- TIER 2: The "Hottest" Collection (Arrays) --- if (
isArray
(
source
)) {
const
len
=
source
.
length
;
const
out
= new
Array
(
len
);
seen
.
set
(
source
,
out
);
for (let
i
= 0;
i
<
len
;
i
++) {
out
[
i
] =
deepClone
(
source
[
i
],
deepCloneArgs
,
seen
);
} return
out
;
} // --- TIER 3: The "Hottest" Objects (Plain Objects) --- const
prototype
=
getPrototypeOf
(
source
);
if (
prototype
===
Object
.
prototype
||
prototype
=== null) {
const
out
=
createObject
(
prototype
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
} // --- TIER 4: IVue Special Logic (Your Framework) --- // Checked here because it's specific to your domain if (
source
[
IVUE_INSTANCE_SYMBOL
]) {
const
srcClass
=
source
.constructor as
IVueClass
<any>;
if (
srcClass
.
ivueGlobalStore
) {
seen
.
set
(
source
,
source
);
return
source
;
} let
out
;
if (
deepCloneArgs
?.
has
(
srcClass
)) {
activeDeepCloneArgsMap
.
set
(
srcClass
,
deepCloneArgs
);
try {
out
=
source
.clone(...(
deepCloneArgs
.
get
(
srcClass
) ?? []));
} finally {
activeDeepCloneArgsMap
.
delete
(
srcClass
);
} } else {
out
=
source
.clone();
}
seen
.
set
(
source
,
out
);
return
out
;
} // --- TIER 5: Common Built-ins (Sorted by Likelihood) --- // instanceof is fast, but order matters slightly for CPU branch prediction if (
source
instanceof
Date
) {
return new
Date
(
source
.
getTime
());
} if (
source
instanceof
Map
) {
const
out
= new (
source
.
constructor
as any)();
seen
.
set
(
source
,
out
);
for (const [
k
,
v
] of
source
) {
out
.set(
deepClone
(
k
,
deepCloneArgs
,
seen
),
deepClone
(
v
,
deepCloneArgs
,
seen
)
); } return
out
;
} if (
source
instanceof
Set
) {
const
out
= new (
source
.
constructor
as any)();
seen
.
set
(
source
,
out
);
for (const
v
of
source
) {
out
.add(
deepClone
(
v
,
deepCloneArgs
,
seen
));
} return
out
;
} // --- TIER 6: Rare / Heavy Objects (The "Slow" Tail) --- if (
source
instanceof
RegExp
) {
const
out
= new
RegExp
(
source
.
source
,
source
.
flags
);
out
.
lastIndex
=
source
.
lastIndex
;
return
out
;
} if (
source
instanceof
Error
) {
const
out
= new (
source
.
constructor
as any)(
source
.
message
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
} if (
source
instanceof
Promise
||
source
instanceof
WeakMap
||
source
instanceof
WeakSet
) {
seen
.
set
(
source
,
source
);
return
source
;
} if (
source
instanceof
DataView
) {
// Clone the underlying buffer to ensure deep copy const
out
= new
DataView
(
source
.
buffer
.
slice
(0),
source
.
byteOffset
,
source
.
byteLength
);
seen
.
set
(
source
,
out
);
return
out
;
} // Binary data (less common in UI state, but supported) if (
ArrayBuffer
.
isView
(
source
) && !(
source
instanceof
DataView
)) {
const
out
= new (
source
.
constructor
as any)(
source
);
seen
.
set
(
source
,
out
);
return
out
;
} if (
source
instanceof
ArrayBuffer
) {
const
out
=
source
.
slice
(0);
seen
.
set
(
source
,
out
);
return
out
;
} // --- TIER 7: Final Fallback (Custom User Classes) --- // If it's a class instance not handled above const
out
=
createObject
(
prototype
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
}; /** * Clears the accessorsMethodsMaps, and propertiesAccessorsMaps WeakMaps. * Useful for SSR scenarios where you want to reset the cache between requests. * Also useful for testing purposes to ensure a clean state. */ export const
clearCache
= () => {
accessorsMethodsMaps
= new
WeakMap
();
propertiesAccessorsMaps
= new
WeakMap
();
activeDeepCloneArgsMap
= new
WeakMap
();
}; /** Exported for testing purposes. */ export const
__test__
= {
copyOwnProps
,
getClassAccessorsMethodsMap
,
getClassPropertiesAccessorsMap
,
}; /** Necessary ivue.ts to be treated as a module. */ export {};
vue
<script setup lang="ts">
import { 
ref
} from 'vue';
import {
ivue
,
iref
,
iuse
} from 'ivue';
class
Counter
{
constructor(public
span
?: HTMLElement) {}
count
=
iref
(0);
increment
() {
this.
count
++;
(this.
span
as HTMLElement).
innerHTML
=
String
(this.
count
+ 1);
} } const
span
=
ref
<HTMLElement>();
const
counter
=
ivue
(
Counter
,
iuse
(
span
));
defineExpose
<
Counter
>(
counter
);
</script> <template> <button @
click
="() => counter.increment()">
Count: {{ counter.count }} + 1 = <span
ref
="
span
"></span>
</button> </template>
vue
<script setup lang="ts">
import { 
ivue
,
iref
} from 'ivue';
class
Counter
{
count
=
iref
(0);
increment
() {
this.
count
++;
} } function
useCounter
() {
/** Convert ivue Counter to a composable with .toRefs() */ return
ivue
(
Counter
).
toRefs
();
} const {
count
,
increment
} =
useCounter
();
</script> <template> <button @
click
="() => increment()">
Count: {{ count }} </button> </template>
ts
import { 
computed
,
ref
, type
Ref
} from 'vue';
import {
useMouse
} from '@vueuse/core';
type
UseMouse
= {
x
:
Ref
<number>;
y
:
Ref
<number> };
export function
useCustomMouse
(
requiredProp
: number) {
const {
x
,
y
}:
UseMouse
=
useMouse
();
const
_sum
=
ref
(0);
function
sum
() {
_sum
.
value
=
x
.
value
+
y
.
value
+
requiredProp
;
} const
total
=
computed
(() => {
return
_sum
.
value
;
}); return {
x
,
y
,
_sum
,
sum
,
total
,
}; }
vue
<script setup lang="ts">
import { 
ivue
,
iuse
,
iref
, type
Use
} from 'ivue';
import {
useCustomMouse
} from '@/components/usage/functions/useCustomMouse';
/** * Use the ivue Utility Type: Use<typeof YourComposableFunctionName> * to get he resulting unwrapped composable properties and functions. */ type
UseCustomMouse
=
Use
<typeof
useCustomMouse
>;
class
Counter
{
count
=
iref
(0);
increment
() {
this.
count
++;
} /** * 'x', 'y', 'sum', 'total' are Refs that will be unwrapped to their bare raw types and destructured into the class. * Even though unwrapped (de-Refed), they will maintain their behavior as Refs and thus will maintain reactivity * and at the same time get destructured into this class root level scope because * Vue 3's `reactive()` Proxy will be able to resolve those Refs internally. */
x
:
UseCustomMouse
['x']; // Unwrapped Ref<number> becomes -> number
y
:
UseCustomMouse
['y']; // Unwrapped Ref<number> becomes -> number
sum
:
UseCustomMouse
['sum']; // 'sum' method that will be destructured into this class on construct()
total
:
UseCustomMouse
['total']; // 'total' computed Ref that will also be destructured into this class on construct()
constructor() { ({
x
: this.
x
,
y
: this.
y
,
sum
: this.
sum
,
total
: this.
total
,
} =
iuse
(
useCustomMouse
, 5));
} } const
counter
=
ivue
(
Counter
);
</script> <template> Count: {{
counter
.count }} <br />
<a
href
="javascript:void(0)" @
click
="() =>
counter
.increment()">
Increment </a><br /> Mouse X: {{
counter
.x }}, Y: {{
counter
.y }}
<br /> Total (computed): {{
counter
.total }}
<br /> <button
class
="button" @
click
="() =>
counter
.sum()">
Click This Big Sum Button To Total X + Y </button> </template>