Table of Contents
Type Declarations
View Transition
The startViewTransition
function is a method on the document
object which enables view transitions. The following types are based on the documentation at MDN. Since the API is currently experimental, you may need to add the following type to your type declaration file to avoid errors.
declare global {
interface ViewTransition {
/**
* A Promise that fulfills once the transition animation is finished,
* and the new page view is visible and interactive to the user.
*/
finished: Promise<void>;
/**
* A Promise that fulfills once the pseudo-element tree is created
* and the transition animation is about to start.
*/
ready: Promise<void>;
/**
* A Promise that fulfills when the promise returned by the
* document.startViewTransition()'s callback fulfills.
*/
updateCallbackDone: Promise<void>;
/**
* Skips the animation part of the view transition, but doesn't skip
* running the document.startViewTransition() callback that updates the DOM.
*/
skipTransition: () => void;
}
type ViewTransitionCallback = (() => Promise<void>) | (() => void);
interface Document {
startViewTransition: (callback: ViewTransitionCallback) => ViewTransition;
}
}
Using in SvelteKit
In SvelteKit, you can add the above type declaration to a file in the src
directory, such as src/types.d.ts
. Then you can add view transitions to your SvelteKit app by following the video below, ignoring the @ts-ignore
comments.
Set Nested Value
Below is a setValue
function which takes an object, a path, and a value. The path is a string of dot-separated keys. The function should set the value at the given path. The path parameter will have autocomplete and the value parameter will be required to be of the same type as the property at that path. See the playground for examples. This function works very similarly to lodash.set, but with stronger type safety.
The SchemaForm component for SvelteKit is also using a modified version of this function.
The sort of recursive conditional types with template literal type manipulation being done in Paths<T>
and PathValue<T, P>
are taxing on the compiler (you can easily get explicit recursion limit warnings, or worse, exponentially long compile times) and have various edge cases.
type Idx<T, K> = K extends keyof T ? T[K] :
number extends keyof T ? K extends `${number}` ? T[number] : never : never
type Join<K, P> = K extends string | number ?
P extends string | number ?
`${K}${"" extends P ? "" : "."}${P}`
: never : never;
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]
type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: K extends string | number ?
`${K}` | Join<K, Paths<T[K], Prev[D]>>
: never
}[keyof T] : ""
type PathValue<T, P extends Paths<T, 4>> = P extends `${infer Key}.${infer Rest}`
? Rest extends Paths<Idx<T, Key>, 4>
? PathValue<Idx<T, Key>, Rest>
: never
: Idx<T, P>
function setValue<
T extends { [key: string]: any },
P extends Paths<T, 4>
>(obj: T, path: P, value: PathValue<T, P>) {
if (typeof path === "string") {
const pList = path.split(".");
const lastKey = pList.pop();
const pointer = pList.reduce(
(accumulator: { [x: string]: any }, currentValue: string | number) => {
if (accumulator[currentValue] === undefined)
accumulator[currentValue] = {};
return accumulator[currentValue];
},
obj
);
if (typeof lastKey !== "undefined") {
pointer[lastKey] = value;
return obj;
}
}
return obj;
}
Update: Resolve Recusive Type Issue
Added 2023-09-15
To resolve the depth issue, you can use the following type to extend PathValue
and make it safer and more performant.
type LimitDepth<
T,
TLength = 5,
TDepth extends unknown[] = []
> = TDepth['length'] extends TLength
? never
: T extends (...args: infer Args) => infer R
? (...args: LimitDepth<Args, TLength, [unknown, ...TDepth]>) => LimitDepth<R, TLength, [unknown, ...TDepth]>
: T extends object
? {
[K in keyof T]: LimitDepth<T[K], TLength, [unknown, ...TDepth]>
}
: T extends Array<infer U>
? Array<LimitDepth<U, TLength, [unknown, ...TDepth]>>
: T;
type PathValue<T, P extends Paths<T, 10>, TLength = 10> = P extends `${infer Key}.${infer Rest}`
? Rest extends Paths<Idx<LimitDepth<T, TLength>, Key>, 10>
? PathValue<Idx<LimitDepth<T, TLength>, Key>, Rest>
: never
: Idx<LimitDepth<T, TLength>, P>;
Prettify
The Prettify
type is a simple type which takes an object type with intersections and combines them to make them easier to read.
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
Here's a quick thread on a super useful type helper you've probably never heard of (nope, not even advanced folks). pic.twitter.com/HSCQueVNXO
— Matt Pocock (@mattpocockuk) February 6, 2023
Imagine you've got some type that's got a bunch of intersections: pic.twitter.com/X9meJjyG1W
— Matt Pocock (@mattpocockuk) February 6, 2023
When you hover over it, it gives you a bunch of gross intersections instead of displaying the resolved type.
— Matt Pocock (@mattpocockuk) February 6, 2023
Wouldn't it be nice if we could Prettify this? pic.twitter.com/jSxbUKexnf
Well, with this funky type helper, we can.
— Matt Pocock (@mattpocockuk) February 6, 2023
We now get a beautiful readout of all of the members of that intersection. pic.twitter.com/PBlZjylU2R
Combine Two Object Types
The Combine
type is a simple type which takes 2 object types and combines them into one.
🔥 TypeScript Tip 🔥
— Matt Pocock (@mattpocockuk) October 12, 2023
Here's how to create a Combine type helper that merges together two objects and creates a partial of the keys that don't match.
Yes, I got nerd sniped again. pic.twitter.com/m9Ae9PAX1C