// corner case when use narrows type
// Ex. type RelativePath = string & { __brand: unknown }
// RelativePath extends object -> true
- type BaseTypes = string | number | boolean
+ type BaseTypes = string | number | boolean | Node | Window
-// Recursively unwraps nested value bindings.
-export type UnwrapRef<T> = {
- cRef: T extends ComputedRef<infer V> ? UnwrapRef<V> : T
- ref: T extends Ref<infer V> ? UnwrapRef<V> : T
- array: T
- object: { [K in keyof T]: UnwrapRef<T[K]> }
-}[T extends ComputedRef<any>
- ? 'cRef'
- : T extends Array<any>
- ? 'array'
- : T extends Ref | Function | CollectionTypes | BaseTypes
- ? 'ref' // bail out on types that shouldn't be unwrapped
- : T extends object ? 'object' : 'ref']
+// Super simple tuple checker
+type Tupple<T extends Array<any>> = T[0] extends T[1]
+ ? T[1] extends T[2] ? never : true
+ : true
+
+export type UnwrapRef<T> = T extends ComputedRef<infer V>
+ ? UnwrapRefSimple<V>
+ : T extends Ref<infer V> ? UnwrapRefSimple<V> : UnwrapRefSimple<T>
+
+type UnwrapRefSimple<T> = T extends
+ | Function
+ | CollectionTypes
+ | BaseTypes
+ | Ref
+ | Element
+ ? T
+ : T extends Array<infer V>
+ ? Tupple<T> extends never ? Array<V> : UnwrapTupple<T>
+ : T extends object ? UnwrappedObject<T> : T
+
+export type UnwrapTupple<T> = { [P in keyof T]: T[P] } & {
+ length: number
+ [Symbol.iterator]: any
+ [Symbol.unscopables]: any
+}
+
+// Extract all known symbols from an object
+// when unwrapping Object the symbols are not `in keyof`, this should cover all the
+// known symbols
+type SymbolExtract<T> = (T extends { [Symbol.asyncIterator]: infer V }
+ ? { [Symbol.asyncIterator]: V }
+ : {}) &
+ (T extends { [Symbol.hasInstance]: infer V }
+ ? { [Symbol.hasInstance]: V }
+ : {}) &
+ (T extends { [Symbol.isConcatSpreadable]: infer V }
+ ? { [Symbol.isConcatSpreadable]: V }
+ : {}) &
+ (T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) &
+ (T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) &
+ (T extends { [Symbol.matchAll]: infer V } ? { [Symbol.matchAll]: V } : {}) &
+ (T extends { [Symbol.observable]: infer V }
+ ? { [Symbol.observable]: V }
+ : {}) &
+ (T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) &
+ (T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) &
+ (T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) &
+ (T extends { [Symbol.split]: infer V } ? { [Symbol.split]: V } : {}) &
+ (T extends { [Symbol.toPrimitive]: infer V }
+ ? { [Symbol.toPrimitive]: V }
+ : {}) &
+ (T extends { [Symbol.toStringTag]: infer V }
+ ? { [Symbol.toStringTag]: V }
+ : {}) &
+ (T extends { [Symbol.unscopables]: infer V }
+ ? { [Symbol.unscopables]: V }
+ : {})
+
+type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>
import { expectType } from 'tsd'
-import { Ref, ref, isRef, unref } from './index'
+import { Ref, ref, isRef, unref, UnwrapRef } from './index'
- function foo(arg: number | Ref<number>) {
+ function plainType(arg: number | Ref<number>) {
// ref coercing
const coerced = ref(arg)
expectType<Ref<number>>(coerced)
})
expectType<Ref<{ foo: number }>>(nestedRef)
expectType<{ foo: number }>(nestedRef.value)
+
+ interface IteratorFoo {
+ [Symbol.iterator]: any
+ }
+ expectType<Ref<UnwrapRef<IteratorFoo>> | Ref<null>>(
+ ref<IteratorFoo | null>(null)
+ )
+
+ expectType<Ref<HTMLElement> | Ref<null>>(ref<HTMLElement | null>(null))
}
- foo(1)
+ plainType(1)
+
+ function bailType(arg: HTMLElement | Ref<HTMLElement>) {
+ // ref coercing
+ const coerced = ref(arg)
+ expectType<Ref<HTMLElement>>(coerced)
+
+ // isRef as type guard
+ if (isRef(arg)) {
+ expectType<Ref<HTMLElement>>(arg)
+ }
+
+ // ref unwrapping
+ expectType<HTMLElement>(unref(arg))
+
+ // ref inner type should be unwrapped
+ const nestedRef = ref({ foo: ref(document.createElement('DIV')) })
+
+ expectType<Ref<{ foo: HTMLElement }>>(nestedRef)
+ expectType<{ foo: HTMLElement }>(nestedRef.value)
+ }
+ const el = document.createElement('DIV')
+ bailType(el)