@tldraw/utils
Version:
tldraw infinite canvas SDK (private utilities).
1,550 lines (1,455 loc) • 62.3 kB
TypeScript
import { default as isEqual } from 'lodash.isequal';
import { default as isEqualWith } from 'lodash.isequalwith';
import { default as throttle } from 'lodash.throttle';
import { default as uniq } from 'lodash.uniq';
/* Excluded from this release type: annotateError */
/* Excluded from this release type: areArraysShallowEqual */
/* Excluded from this release type: areObjectsShallowEqual */
/* Excluded from this release type: assert_2 */
/* Excluded from this release type: assertExists */
/**
* Decorator that binds a method to its class instance (legacy stage-2 TypeScript decorators).
* When applied to a class method, ensures `this` always refers to the class instance,
* even when the method is called as a callback or event handler.
*
* @param target - The prototype of the class being decorated
* @param propertyKey - The name of the method being decorated
* @param descriptor - The property descriptor for the method being decorated
* @returns The modified property descriptor with bound method access
* @example
* ```typescript
* class MyClass {
* name = 'example';
*
* @bind
* getName() {
* return this.name;
* }
* }
*
* const instance = new MyClass();
* const callback = instance.getName;
* console.log(callback()); // 'example' (this is properly bound)
* ```
* @public
*/
export declare function bind<T extends (...args: any[]) => any>(target: object, propertyKey: string, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T>;
/**
* Decorator that binds a method to its class instance (TC39 decorators standard).
* When applied to a class method, ensures `this` always refers to the class instance,
* even when the method is called as a callback or event handler.
*
* @param originalMethod - The original method being decorated
* @param context - The decorator context containing metadata about the method
* @example
* ```typescript
* class EventHandler {
* message = 'Hello World';
*
* @bind
* handleClick() {
* console.log(this.message);
* }
* }
*
* const handler = new EventHandler();
* document.addEventListener('click', handler.handleClick); // 'this' is properly bound
* ```
* @public
*/
export declare function bind<This extends object, T extends (...args: any[]) => any>(originalMethod: T, context: ClassMethodDecoratorContext<This, T>): void;
/* Excluded from this release type: clearLocalStorage */
/* Excluded from this release type: clearSessionStorage */
/* Excluded from this release type: compact */
/**
* Create a debounced version of a function that delays execution until after a specified wait time.
*
* Debouncing ensures that a function is only executed once after a specified delay,
* even if called multiple times in rapid succession. Each new call resets the timer. The debounced
* function returns a Promise that resolves with the result of the original function. Includes a
* cancel method to prevent execution if needed.
*
* @param callback - The function to debounce (can be sync or async)
* @param wait - The delay in milliseconds before executing the function
* @returns A debounced function that returns a Promise and includes a cancel method
*
* @example
* ```ts
* // Debounce a search function
* const searchAPI = (query: string) => fetch(`/search?q=${query}`)
* const debouncedSearch = debounce(searchAPI, 300)
*
* // Multiple rapid calls will only execute the last one after 300ms
* debouncedSearch('react').then(result => console.log(result))
* debouncedSearch('react hooks') // This cancels the previous call
* debouncedSearch('react typescript') // Only this will execute
*
* // Cancel pending execution
* debouncedSearch.cancel()
*
* // With async/await
* const saveData = debounce(async (data: any) => {
* return await api.save(data)
* }, 1000)
*
* const result = await saveData({name: 'John'})
* ```
*
* @public
* @see source - https://gist.github.com/ca0v/73a31f57b397606c9813472f7493a940
*/
export declare function debounce<T extends unknown[], U>(callback: (...args: T) => PromiseLike<U> | U, wait: number): {
(...args: T): Promise<U>;
cancel(): void;
};
/**
* Remove duplicate items from an array.
*
* Creates a new array with duplicate items removed. Uses strict equality by default,
* or a custom equality function if provided. Order of first occurrence is preserved.
*
* @param input - The array to deduplicate
* @param equals - Optional custom equality function to compare items (defaults to strict equality)
* @returns A new array with duplicate items removed
*
* @example
* ```ts
* dedupe([1, 2, 2, 3, 1]) // [1, 2, 3]
* dedupe(['a', 'b', 'a', 'c']) // ['a', 'b', 'c']
*
* // With custom equality function
* const objects = [{id: 1}, {id: 2}, {id: 1}]
* dedupe(objects, (a, b) => a.id === b.id) // [{id: 1}, {id: 2}]
* ```
* @public
*/
export declare function dedupe<T>(input: T[], equals?: (a: any, b: any) => boolean): T[];
/**
* Array of supported video MIME types.
*
* @example
* ```ts
* import { DEFAULT_SUPPORT_VIDEO_TYPES } from '@tldraw/utils'
*
* const isVideo = DEFAULT_SUPPORT_VIDEO_TYPES.includes('video/mp4')
* console.log(isVideo) // true
* ```
* @public
*/
export declare const DEFAULT_SUPPORT_VIDEO_TYPES: readonly ("video/mp4" | "video/quicktime" | "video/webm")[];
/**
* Array of all supported image MIME types, combining static, vector, and animated types.
*
* @example
* ```ts
* import { DEFAULT_SUPPORTED_IMAGE_TYPES } from '@tldraw/utils'
*
* const isSupported = DEFAULT_SUPPORTED_IMAGE_TYPES.includes('image/png')
* console.log(isSupported) // true
* ```
* @public
*/
export declare const DEFAULT_SUPPORTED_IMAGE_TYPES: readonly ("image/apng" | "image/avif" | "image/gif" | "image/jpeg" | "image/png" | "image/svg+xml" | "image/webp")[];
/**
* Comma-separated string of all supported media MIME types, useful for HTML file input accept attributes.
*
* @example
* ```ts
* import { DEFAULT_SUPPORTED_MEDIA_TYPE_LIST } from '@tldraw/utils'
*
* // Use in HTML file input for media uploads
* const input = document.createElement('input')
* input.type = 'file'
* input.accept = DEFAULT_SUPPORTED_MEDIA_TYPE_LIST
* input.addEventListener('change', (e) => {
* const files = (e.target as HTMLInputElement).files
* if (files) console.log(`Selected ${files.length} file(s)`)
* })
* ```
* @public
*/
export declare const DEFAULT_SUPPORTED_MEDIA_TYPE_LIST: string;
/**
* Array of all supported media MIME types, combining images and videos.
*
* @example
* ```ts
* import { DEFAULT_SUPPORTED_MEDIA_TYPES } from '@tldraw/utils'
*
* const isMediaFile = DEFAULT_SUPPORTED_MEDIA_TYPES.includes('video/mp4')
* console.log(isMediaFile) // true
* ```
* @public
*/
export declare const DEFAULT_SUPPORTED_MEDIA_TYPES: readonly ("image/apng" | "image/avif" | "image/gif" | "image/jpeg" | "image/png" | "image/svg+xml" | "image/webp" | "video/mp4" | "video/quicktime" | "video/webm")[];
/* Excluded from this release type: deleteFromLocalStorage */
/* Excluded from this release type: deleteFromSessionStorage */
/** @public */
export declare interface ErrorAnnotations {
tags: Record<string, bigint | boolean | null | number | string | symbol | undefined>;
extras: Record<string, unknown>;
}
/**
* Represents a failed result containing an error.
*
* Interface for the error case of a Result type, containing the error information.
* Used in conjunction with OkResult to create a discriminated union for error handling.
*
* @example
* ```ts
* const failure: ErrorResult<string> = { ok: false, error: 'Something went wrong' }
* if (!failure.ok) {
* console.error(failure.error) // 'Something went wrong'
* }
* ```
* @public
*/
export declare interface ErrorResult<E> {
readonly ok: false;
readonly error: E;
}
/* Excluded from this release type: ExecutionQueue */
/* Excluded from this release type: exhaustiveSwitchError */
/**
* Expands a type definition to show its full structure in IDE tooltips and error messages.
* This utility type forces TypeScript to resolve and display the complete type structure
* instead of showing complex conditional types or intersections as-is.
*
* @example
* ```ts
* type User = { name: string }
* type WithId = { id: string }
* type UserWithId = User & WithId
*
* // Without Expand, IDE shows: User & WithId
* // With Expand, IDE shows: { name: string; id: string }
* type ExpandedUserWithId = Expand<UserWithId>
*
* // Useful for complex intersections
* type ComplexType = Expand<BaseType & Mixin1 & Mixin2>
* ```
*
* @public
*/
export declare type Expand<T> = T extends infer O ? {
[K in keyof O]: O[K];
} : never;
/* Excluded from this release type: fetch_2 */
/**
* Utility class providing helper methods for file and blob operations.
*
* FileHelpers contains static methods for common file operations including
* URL fetching, format conversion, and MIME type manipulation. All methods work with
* web APIs like fetch, FileReader, and Blob/File objects.
*
* @example
* ```ts
* // Fetch and convert a remote image to data URL
* const dataUrl = await FileHelpers.urlToDataUrl('https://example.com/image.png')
*
* // Convert user-selected file to text
* const text = await FileHelpers.blobToText(userFile)
*
* // Change file MIME type
* const newFile = FileHelpers.rewriteMimeType(originalFile, 'application/json')
* ```
*
* @public
*/
export declare class FileHelpers {
/**
* Converts a URL to an ArrayBuffer by fetching the resource.
*
* Fetches the resource at the given URL and returns its content as an ArrayBuffer.
* This is useful for loading binary data like images, videos, or other file types.
*
* @param url - The URL of the file to fetch
* @returns Promise that resolves to the file content as an ArrayBuffer
* @example
* ```ts
* const buffer = await FileHelpers.urlToArrayBuffer('https://example.com/image.png')
* console.log(buffer.byteLength) // Size of the file in bytes
* ```
* @public
*/
static urlToArrayBuffer(url: string): Promise<ArrayBuffer>;
/**
* Converts a URL to a Blob by fetching the resource.
*
* Fetches the resource at the given URL and returns its content as a Blob object.
* Blobs are useful for handling file data in web applications.
*
* @param url - The URL of the file to fetch
* @returns Promise that resolves to the file content as a Blob
* @example
* ```ts
* const blob = await FileHelpers.urlToBlob('https://example.com/document.pdf')
* console.log(blob.type) // 'application/pdf'
* console.log(blob.size) // Size in bytes
* ```
* @public
*/
static urlToBlob(url: string): Promise<Blob>;
/**
* Converts a URL to a data URL by fetching the resource.
*
* Fetches the resource at the given URL and converts it to a base64-encoded data URL.
* If the URL is already a data URL, it returns the URL unchanged. This is useful for embedding
* resources directly in HTML or CSS.
*
* @param url - The URL of the file to convert, or an existing data URL
* @returns Promise that resolves to a data URL string
* @example
* ```ts
* const dataUrl = await FileHelpers.urlToDataUrl('https://example.com/image.jpg')
* // Returns: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA...'
*
* const existing = await FileHelpers.urlToDataUrl('data:text/plain;base64,SGVsbG8=')
* // Returns the same data URL unchanged
* ```
* @public
*/
static urlToDataUrl(url: string): Promise<string>;
/**
* Convert a Blob to a base64 encoded data URL.
*
* Converts a Blob object to a base64-encoded data URL using the FileReader API.
* This is useful for displaying images or embedding file content directly in HTML.
*
* @param file - The Blob object to convert
* @returns Promise that resolves to a base64-encoded data URL string
* @example
* ```ts
* const blob = new Blob(['Hello World'], { type: 'text/plain' })
* const dataUrl = await FileHelpers.blobToDataUrl(blob)
* // Returns: 'data:text/plain;base64,SGVsbG8gV29ybGQ='
*
* // With an image file
* const imageDataUrl = await FileHelpers.blobToDataUrl(myImageFile)
* // Can be used directly in img src attribute
* ```
* @public
*/
static blobToDataUrl(file: Blob): Promise<string>;
/**
* Convert a Blob to a unicode text string.
*
* Reads the content of a Blob object as a UTF-8 text string using the FileReader API.
* This is useful for reading text files or extracting text content from blobs.
*
* @param file - The Blob object to convert to text
* @returns Promise that resolves to the text content as a string
* @example
* ```ts
* const textBlob = new Blob(['Hello World'], { type: 'text/plain' })
* const text = await FileHelpers.blobToText(textBlob)
* console.log(text) // 'Hello World'
*
* // With a text file from user input
* const content = await FileHelpers.blobToText(myTextFile)
* console.log(content) // File content as string
* ```
* @public
*/
static blobToText(file: Blob): Promise<string>;
/**
* Creates a new Blob or File with a different MIME type.
*
* Creates a copy of the given Blob or File with a new MIME type while preserving
* all other properties. If the current MIME type already matches the new one, returns the
* original object unchanged. For File objects, preserves the filename.
*
* @param blob - The Blob or File object to modify
* @param newMimeType - The new MIME type to assign
* @returns A new Blob or File with the updated MIME type
* @example
* ```ts
* // Change a generic blob to a specific image type
* const blob = new Blob([imageData])
* const imageBlob = FileHelpers.rewriteMimeType(blob, 'image/png')
*
* // Change a file's MIME type while preserving filename
* const file = new File([data], 'document.txt', { type: 'text/plain' })
* const jsonFile = FileHelpers.rewriteMimeType(file, 'application/json')
* console.log(jsonFile.name) // 'document.txt' (preserved)
* console.log(jsonFile.type) // 'application/json' (updated)
* ```
* @public
*/
static rewriteMimeType(blob: Blob, newMimeType: string): Blob;
static rewriteMimeType(blob: File, newMimeType: string): File;
}
/* Excluded from this release type: filterEntries */
/* Excluded from this release type: fpsThrottle */
/* Excluded from this release type: getChangedKeys */
/* Excluded from this release type: getErrorAnnotations */
/**
* Get the first item from an iterable Set or Map.
*
* @param value - The iterable Set or Map to get the first item from
* @returns The first value from the Set or Map
* @example
* ```ts
* const A = getFirstFromIterable(new Set([1, 2, 3])) // 1
* const B = getFirstFromIterable(
* new Map([
* ['a', 1],
* ['b', 2],
* ])
* ) // 1
* ```
* @public
*/
export declare function getFirstFromIterable<T = unknown>(set: Map<any, T> | Set<T>): T;
/* Excluded from this release type: getFromLocalStorage */
/* Excluded from this release type: getFromSessionStorage */
/**
* Hash an ArrayBuffer using the FNV-1a algorithm.
*
* Generates a deterministic hash value for binary data stored in an ArrayBuffer.
* Processes the buffer byte by byte using the same hashing algorithm as getHashForString.
* Useful for creating consistent identifiers for binary data like images or files.
*
* @param buffer - The ArrayBuffer containing binary data to hash
* @returns A string representation of the 32-bit hash value
* @example
* ```ts
* // Hash some binary data
* const data = new Uint8Array([1, 2, 3, 4, 5])
* const hash = getHashForBuffer(data.buffer)
* console.log(hash) // '123456789'
*
* // Hash image file data
* const fileBuffer = await file.arrayBuffer()
* const fileHash = getHashForBuffer(fileBuffer)
* console.log(fileHash) // Unique hash for the file
* ```
* @public
*/
export declare function getHashForBuffer(buffer: ArrayBuffer): string;
/**
* Hash an object by converting it to JSON and then hashing the resulting string.
*
* Converts the object to a JSON string using JSON.stringify and then applies the same
* hashing algorithm as getHashForString. Useful for creating consistent hash values
* for objects, though the hash depends on JSON serialization order.
*
* @param obj - The object to hash (any JSON-serializable value)
* @returns A string representation of the 32-bit hash value
* @example
* ```ts
* const hash1 = getHashForObject({ name: 'John', age: 30 })
* const hash2 = getHashForObject({ name: 'John', age: 30 })
* console.log(hash1 === hash2) // true
*
* // Arrays work too
* const arrayHash = getHashForObject([1, 2, 3, 'hello'])
* console.log(arrayHash) // '-123456789'
* ```
* @public
*/
export declare function getHashForObject(obj: any): string;
/**
* Hash a string using the FNV-1a algorithm.
*
* Generates a deterministic hash value for a given string using a variant of the FNV-1a
* (Fowler-Noll-Vo) algorithm. The hash is returned as a string representation of a 32-bit integer.
*
* @param string - The input string to hash
* @returns A string representation of the 32-bit hash value
* @example
* ```ts
* const hash = getHashForString('hello world')
* console.log(hash) // '-862545276'
*
* // Same input always produces same hash
* const hash2 = getHashForString('hello world')
* console.log(hash === hash2) // true
* ```
* @public
*/
export declare function getHashForString(string: string): string;
/**
* Get the index above a given index.
* @param below - The index below.
* @returns An IndexKey value above the given index.
* @example
* ```ts
* const index = getIndexAbove('a0' as IndexKey)
* console.log(index) // 'a1'
* ```
* @public
*/
export declare function getIndexAbove(below?: IndexKey | null | undefined): IndexKey;
/**
* Get the index below a given index.
* @param above - The index above.
* @returns An IndexKey value below the given index.
* @example
* ```ts
* const index = getIndexBelow('a2' as IndexKey)
* console.log(index) // 'a1'
* ```
* @public
*/
export declare function getIndexBelow(above?: IndexKey | null | undefined): IndexKey;
/**
* Get the index between two indices.
* @param below - The index below.
* @param above - The index above.
* @returns A single IndexKey value between below and above.
* @example
* ```ts
* const index = getIndexBetween('a0' as IndexKey, 'a2' as IndexKey)
* console.log(index) // 'a1'
* ```
* @public
*/
export declare function getIndexBetween(below: IndexKey | null | undefined, above: IndexKey | null | undefined): IndexKey;
/**
* Get n number of indices, starting at an index.
* @param n - The number of indices to get.
* @param start - The index to start at.
* @returns An array containing the start index plus n additional IndexKey values.
* @example
* ```ts
* const indices = getIndices(3, 'a1' as IndexKey)
* console.log(indices) // ['a1', 'a2', 'a3', 'a4']
* ```
* @public
*/
export declare function getIndices(n: number, start?: IndexKey): IndexKey[];
/**
* Get a number of indices above an index.
* @param below - The index below.
* @param n - The number of indices to get.
* @returns An array of n IndexKey values above the given index.
* @example
* ```ts
* const indices = getIndicesAbove('a0' as IndexKey, 3)
* console.log(indices) // ['a1', 'a2', 'a3']
* ```
* @public
*/
export declare function getIndicesAbove(below: IndexKey | null | undefined, n: number): IndexKey[];
/**
* Get a number of indices below an index.
* @param above - The index above.
* @param n - The number of indices to get.
* @returns An array of n IndexKey values below the given index.
* @example
* ```ts
* const indices = getIndicesBelow('a2' as IndexKey, 2)
* console.log(indices) // ['a1', 'a0V']
* ```
* @public
*/
export declare function getIndicesBelow(above: IndexKey | null | undefined, n: number): IndexKey[];
/**
* Get a number of indices between two indices.
* @param below - The index below.
* @param above - The index above.
* @param n - The number of indices to get.
* @returns An array of n IndexKey values between below and above.
* @example
* ```ts
* const indices = getIndicesBetween('a0' as IndexKey, 'a2' as IndexKey, 2)
* console.log(indices) // ['a0V', 'a1']
* ```
* @public
*/
export declare function getIndicesBetween(below: IndexKey | null | undefined, above: IndexKey | null | undefined, n: number): IndexKey[];
/* Excluded from this release type: getOwnProperty */
/* Excluded from this release type: groupBy */
/* Excluded from this release type: hasOwnProperty */
/* Excluded from this release type: Image_2 */
/**
* A string made up of an integer part followed by a fraction part. The fraction point consists of
* zero or more digits with no trailing zeros. Based on
* {@link https://observablehq.com/@dgreensp/implementing-fractional-indexing}.
*
* @public
*/
export declare type IndexKey = string & {
__brand: 'indexKey';
};
/**
* Inverse lerp between two values. Given a value `t` in the range [a, b], returns a number between
* 0 and 1.
*
* @param a - The start value of the range
* @param b - The end value of the range
* @param t - The value within the range [a, b]
* @returns The normalized position (0-1) of t within the range [a, b]
* @example
* ```ts
* const position = invLerp(0, 100, 25) // 0.25
* const normalized = invLerp(10, 20, 15) // 0.5
* ```
* @public
*/
export declare function invLerp(a: number, b: number, t: number): number;
/**
* Get whether a value is not undefined.
*
* @param value - The value to check.
* @returns True if the value is not undefined, with proper type narrowing.
* @example
* ```ts
* const maybeString: string | undefined = getValue()
*
* if (isDefined(maybeString)) {
* // TypeScript knows maybeString is string, not undefined
* console.log(maybeString.toUpperCase())
* }
*
* // Filter undefined values from arrays
* const values = [1, undefined, 2, undefined, 3]
* const definedValues = values.filter(isDefined) // [1, 2, 3]
* ```
* @public
*/
export declare function isDefined<T>(value: T): value is typeof value extends undefined ? never : T;
export { isEqual }
/* Excluded from this release type: isEqualAllowingForFloatingPointErrors */
export { isEqualWith }
/* Excluded from this release type: isNativeStructuredClone */
/**
* Get whether a value is not null.
*
* @param value - The value to check.
* @returns True if the value is not null, with proper type narrowing.
* @example
* ```ts
* const maybeString: string | null = getValue()
*
* if (isNonNull(maybeString)) {
* // TypeScript knows maybeString is string, not null
* console.log(maybeString.length)
* }
*
* // Filter null values from arrays
* const values = ["a", null, "b", null, "c"]
* const nonNullValues = values.filter(isNonNull) // ["a", "b", "c"]
* ```
* @public
*/
export declare function isNonNull<T>(value: T): value is typeof value extends null ? never : T;
/**
* Get whether a value is not nullish (not null and not undefined).
*
* @param value - The value to check.
* @returns True if the value is neither null nor undefined, with proper type narrowing.
* @example
* ```ts
* const maybeString: string | null | undefined = getValue()
*
* if (isNonNullish(maybeString)) {
* // TypeScript knows maybeString is string, not null or undefined
* console.log(maybeString.charAt(0))
* }
*
* // Filter nullish values from arrays
* const values = ["hello", null, "world", undefined, "!"]
* const cleanValues = values.filter(isNonNullish) // ["hello", "world", "!"]
* ```
* @public
*/
export declare function isNonNullish<T>(value: T): value is typeof value extends undefined ? never : typeof value extends null ? never : T;
/**
* A type representing a JSON array containing any valid JSON values.
* Arrays can contain mixed types of JSON values including nested arrays and objects.
*
* @example
* ```ts
* const jsonArray: JsonArray = [
* "text",
* 123,
* true,
* { nested: "object" },
* [1, 2, 3]
* ]
* ```
*
* @public
*/
export declare type JsonArray = JsonValue[];
/**
* A type representing a JSON object with string keys and JSON values.
* Object values can be undefined to handle optional properties safely.
*
* @example
* ```ts
* const jsonObject: JsonObject = {
* required: "value",
* optional: undefined,
* nested: {
* deep: "property"
* },
* array: [1, 2, 3]
* }
* ```
*
* @public
*/
export declare interface JsonObject {
[key: string]: JsonValue | undefined;
}
/**
* A type representing JSON primitive values: boolean, null, string, or number.
* These are the atomic values that can appear in JSON data.
*
* @example
* ```ts
* const primitives: JsonPrimitive[] = [
* true,
* null,
* "hello",
* 42
* ]
* ```
*
* @public
*/
export declare type JsonPrimitive = boolean | null | number | string;
/**
* A type that represents any valid JSON value. This includes primitives (boolean, null, string, number),
* arrays of JSON values, and objects with string keys and JSON values.
*
* @example
* ```ts
* const jsonData: JsonValue = {
* name: "Alice",
* age: 30,
* active: true,
* tags: ["user", "premium"],
* metadata: null
* }
* ```
*
* @public
*/
export declare type JsonValue = JsonArray | JsonObject | JsonPrimitive;
/* Excluded from this release type: last */
/**
* Linear interpolate between two values.
*
* @param a - The start value
* @param b - The end value
* @param t - The interpolation factor (0-1)
* @returns The interpolated value
* @example
* ```ts
* const halfway = lerp(0, 100, 0.5) // 50
* const quarter = lerp(10, 20, 0.25) // 12.5
* ```
* @public
*/
export declare function lerp(a: number, b: number, t: number): number;
/**
* Applies a string transformation algorithm that rearranges and modifies characters.
*
* Performs a series of character manipulations on the input string including
* character repositioning through splicing operations and numeric character transformations.
* This appears to be a custom encoding/obfuscation function.
*
* @param str - The input string to transform
* @returns The transformed string after applying all manipulations
* @example
* ```ts
* const result = lns('hello123')
* console.log(result) // Transformed string (exact output depends on algorithm)
*
* // Can be used for simple string obfuscation
* const obfuscated = lns('sensitive-data')
* console.log(obfuscated) // Obfuscated version
* ```
* @public
*/
export declare function lns(str: string): string;
/**
* Automatically makes properties optional if their type includes `undefined`.
* This transforms properties like `prop: string | undefined` to `prop?: string | undefined`,
* making the API more ergonomic by not requiring explicit undefined assignments.
*
* @example
* ```ts
* interface RawConfig {
* name: string
* theme: string | undefined
* debug: boolean | undefined
* version: number
* }
*
* type Config = MakeUndefinedOptional<RawConfig>
* // Result: {
* // name: string
* // theme?: string | undefined // now optional
* // debug?: boolean | undefined // now optional
* // version: number
* // }
*
* const config: Config = {
* name: 'MyApp',
* version: 1
* // theme and debug can be omitted instead of explicitly set to undefined
* }
* ```
*
* @public
*/
export declare type MakeUndefinedOptional<T extends object> = Expand<{
[P in {
[K in keyof T]: undefined extends T[K] ? never : K;
}[keyof T]]: T[P];
} & {
[P in {
[K in keyof T]: undefined extends T[K] ? K : never;
}[keyof T]]?: T[P];
}>;
/* Excluded from this release type: mapObjectMapValues */
/* Excluded from this release type: maxBy */
/* Excluded from this release type: measureAverageDuration */
/* Excluded from this release type: measureCbDuration */
/* Excluded from this release type: measureDuration */
/**
* Helpers for media
*
* @public
*/
export declare class MediaHelpers {
/**
* Load a video element from a URL with cross-origin support.
*
* @param src - The URL of the video to load
* @returns Promise that resolves to the loaded HTMLVideoElement
* @example
* ```ts
* const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')
* console.log(`Video dimensions: ${video.videoWidth}x${video.videoHeight}`)
* ```
* @public
*/
static loadVideo(src: string): Promise<HTMLVideoElement>;
/**
* Extract a frame from a video element as a data URL.
*
* @param video - The HTMLVideoElement to extract frame from
* @param time - The time in seconds to extract the frame from (default: 0)
* @returns Promise that resolves to a data URL of the video frame
* @example
* ```ts
* const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')
* const frameDataUrl = await MediaHelpers.getVideoFrameAsDataUrl(video, 5.0)
* // Use frameDataUrl as image thumbnail
* const img = document.createElement('img')
* img.src = frameDataUrl
* ```
* @public
*/
static getVideoFrameAsDataUrl(video: HTMLVideoElement, time?: number): Promise<string>;
/**
* Load an image from a URL and get its dimensions along with the image element.
*
* @param src - The URL of the image to load
* @returns Promise that resolves to an object with width, height, and the image element
* @example
* ```ts
* const { w, h, image } = await MediaHelpers.getImageAndDimensions('https://example.com/image.png')
* console.log(`Image size: ${w}x${h}`)
* // Image is ready to use
* document.body.appendChild(image)
* ```
* @public
*/
static getImageAndDimensions(src: string): Promise<{
h: number;
image: HTMLImageElement;
w: number;
}>;
/**
* Get the size of a video blob
*
* @param blob - A Blob containing the video
* @returns Promise that resolves to an object with width and height properties
* @example
* ```ts
* const file = new File([...], 'video.mp4', { type: 'video/mp4' })
* const { w, h } = await MediaHelpers.getVideoSize(file)
* console.log(`Video dimensions: ${w}x${h}`)
* ```
* @public
*/
static getVideoSize(blob: Blob): Promise<{
h: number;
w: number;
}>;
/**
* Get the size of an image blob
*
* @param blob - A Blob containing the image
* @returns Promise that resolves to an object with width and height properties
* @example
* ```ts
* const file = new File([...], 'image.png', { type: 'image/png' })
* const { w, h } = await MediaHelpers.getImageSize(file)
* console.log(`Image dimensions: ${w}x${h}`)
* ```
* @public
*/
static getImageSize(blob: Blob): Promise<{
h: number;
w: number;
}>;
/**
* Check if a media file blob contains animation data.
*
* @param file - The Blob to check for animation
* @returns Promise that resolves to true if the file is animated, false otherwise
* @example
* ```ts
* const file = new File([...], 'animation.gif', { type: 'image/gif' })
* const animated = await MediaHelpers.isAnimated(file)
* console.log(animated ? 'Animated' : 'Static')
* ```
* @public
*/
static isAnimated(file: Blob): Promise<boolean>;
/**
* Check if a MIME type represents an animated image format.
*
* @param mimeType - The MIME type to check
* @returns True if the MIME type is an animated image format, false otherwise
* @example
* ```ts
* const isAnimated = MediaHelpers.isAnimatedImageType('image/gif')
* console.log(isAnimated) // true
* ```
* @public
*/
static isAnimatedImageType(mimeType: null | string): boolean;
/**
* Check if a MIME type represents a static (non-animated) image format.
*
* @param mimeType - The MIME type to check
* @returns True if the MIME type is a static image format, false otherwise
* @example
* ```ts
* const isStatic = MediaHelpers.isStaticImageType('image/jpeg')
* console.log(isStatic) // true
* ```
* @public
*/
static isStaticImageType(mimeType: null | string): boolean;
/**
* Check if a MIME type represents a vector image format.
*
* @param mimeType - The MIME type to check
* @returns True if the MIME type is a vector image format, false otherwise
* @example
* ```ts
* const isVector = MediaHelpers.isVectorImageType('image/svg+xml')
* console.log(isVector) // true
* ```
* @public
*/
static isVectorImageType(mimeType: null | string): boolean;
/**
* Check if a MIME type represents any supported image format (static, animated, or vector).
*
* @param mimeType - The MIME type to check
* @returns True if the MIME type is a supported image format, false otherwise
* @example
* ```ts
* const isImage = MediaHelpers.isImageType('image/png')
* console.log(isImage) // true
* ```
* @public
*/
static isImageType(mimeType: string): boolean;
/**
* Utility function to create an object URL from a blob, execute a function with it, and automatically clean it up.
*
* @param blob - The Blob to create an object URL for
* @param fn - Function to execute with the object URL
* @returns Promise that resolves to the result of the function
* @example
* ```ts
* const result = await MediaHelpers.usingObjectURL(imageBlob, async (url) => {
* const { w, h } = await MediaHelpers.getImageAndDimensions(url)
* return { width: w, height: h }
* })
* // Object URL is automatically revoked after function completes
* console.log(`Image dimensions: ${result.width}x${result.height}`)
* ```
* @public
*/
static usingObjectURL<T>(blob: Blob, fn: (url: string) => Promise<T>): Promise<T>;
}
/* Excluded from this release type: mergeArraysAndReplaceDefaults */
/* Excluded from this release type: minBy */
/* Excluded from this release type: mockUniqueId */
/**
* Modulate a value between two ranges.
*
* @example
*
* ```ts
* const A = modulate(0, [0, 1], [0, 100])
* ```
*
* @param value - The interpolation value.
* @param rangeA - From [low, high]
* @param rangeB - To [low, high]
* @param clamp - Whether to clamp the the result to [low, high]
* @public
*/
export declare function modulate(value: number, rangeA: number[], rangeB: number[], clamp?: boolean): number;
/* Excluded from this release type: noop */
/* Excluded from this release type: objectMapEntries */
/* Excluded from this release type: objectMapEntriesIterable */
/* Excluded from this release type: objectMapFromEntries */
/* Excluded from this release type: objectMapKeys */
/* Excluded from this release type: objectMapValues */
/**
* Represents a successful result containing a value.
*
* Interface for the success case of a Result type, containing the computed value.
* Used in conjunction with ErrorResult to create a discriminated union for error handling.
*
* @example
* ```ts
* const success: OkResult<string> = { ok: true, value: 'Hello World' }
* if (success.ok) {
* console.log(success.value) // 'Hello World'
* }
* ```
* @public
*/
export declare interface OkResult<T> {
readonly ok: true;
readonly value: T;
}
/* Excluded from this release type: omit */
/* Excluded from this release type: omitFromStackTrace */
/* Excluded from this release type: partition */
/**
* A utility class for measuring and tracking frame rate performance during operations.
* Provides visual feedback in the browser console with color-coded FPS indicators.
*
* @example
* ```ts
* const tracker = new PerformanceTracker()
*
* tracker.start('render')
* renderShapes()
* tracker.stop() // Logs performance info to console
*
* // Check if tracking is active
* if (tracker.isStarted()) {
* console.log('Still tracking performance')
* }
* ```
*
* @public
*/
export declare class PerformanceTracker {
private startTime;
private name;
private frames;
private started;
private frame;
/**
* Records animation frames to calculate frame rate.
* Called automatically during performance tracking.
*/
recordFrame: () => void;
/**
* Starts performance tracking for a named operation.
*
* @param name - A descriptive name for the operation being tracked
*
* @example
* ```ts
* tracker.start('canvas-render')
* // ... perform rendering operations
* tracker.stop()
* ```
*/
start(name: string): void;
/**
* Stops performance tracking and logs results to the console.
*
* Displays the operation name, frame rate, and uses color coding:
* - Green background: \> 55 FPS (good performance)
* - Yellow background: 30-55 FPS (moderate performance)
* - Red background: \< 30 FPS (poor performance)
*
* @example
* ```ts
* tracker.start('interaction')
* handleUserInteraction()
* tracker.stop() // Logs: "Perf Interaction 60 fps"
* ```
*/
stop(): void;
/**
* Checks whether performance tracking is currently active.
*
* @returns True if tracking is in progress, false otherwise
*
* @example
* ```ts
* if (!tracker.isStarted()) {
* tracker.start('new-operation')
* }
* ```
*/
isStarted(): boolean;
}
/**
* Utility class for reading and manipulating PNG image files.
* Provides methods for parsing PNG chunks, validating PNG format, and modifying PNG metadata.
*
* @example
* ```ts
* // Validate PNG file from blob
* const blob = new Blob([pngData], { type: 'image/png' })
* const view = new DataView(await blob.arrayBuffer())
* const isPng = PngHelpers.isPng(view, 0)
*
* // Parse PNG metadata for image processing
* const chunks = PngHelpers.readChunks(view)
* const physChunk = PngHelpers.findChunk(view, 'pHYs')
*
* // Create high-DPI PNG for export
* const highDpiBlob = PngHelpers.setPhysChunk(view, 2, { type: 'image/png' })
* ```
*
* @public
*/
export declare class PngHelpers {
/**
* Checks if binary data at the specified offset contains a valid PNG file signature.
* Validates the 8-byte PNG signature: 89 50 4E 47 0D 0A 1A 0A.
*
* @param view - DataView containing the binary data to check
* @param offset - Byte offset where the PNG signature should start
* @returns True if the data contains a valid PNG signature, false otherwise
*
* @example
* ```ts
* // Validate PNG from file upload
* const file = event.target.files[0]
* const buffer = await file.arrayBuffer()
* const view = new DataView(buffer)
*
* if (PngHelpers.isPng(view, 0)) {
* console.log('Valid PNG file detected')
* // Process PNG file...
* } else {
* console.error('Not a valid PNG file')
* }
* ```
*/
static isPng(view: DataView, offset: number): boolean;
/**
* Reads the 4-character chunk type identifier from a PNG chunk header.
*
* @param view - DataView containing the PNG data
* @param offset - Byte offset of the chunk type field (after length field)
* @returns 4-character string representing the chunk type (e.g., 'IHDR', 'IDAT', 'IEND')
*
* @example
* ```ts
* // Read chunk type from PNG header (after 8-byte signature)
* const chunkType = PngHelpers.getChunkType(dataView, 8)
* console.log(chunkType) // 'IHDR' (Image Header)
*
* // Read chunk type at a specific position during parsing
* let offset = 8 // Skip PNG signature
* const chunkLength = dataView.getUint32(offset)
* const type = PngHelpers.getChunkType(dataView, offset + 4)
* ```
*/
static getChunkType(view: DataView, offset: number): string;
/**
* Parses all chunks in a PNG file and returns their metadata.
* Skips duplicate IDAT chunks but includes all other chunk types.
*
* @param view - DataView containing the complete PNG file data
* @param offset - Starting byte offset (defaults to 0)
* @returns Record mapping chunk types to their metadata (start position, data offset, and size)
* @throws Error if the data is not a valid PNG file
*
* @example
* ```ts
* // Parse PNG structure for metadata extraction
* const view = new DataView(await blob.arrayBuffer())
* const chunks = PngHelpers.readChunks(view)
*
* // Check for specific chunks
* const ihdrChunk = chunks['IHDR']
* const physChunk = chunks['pHYs']
*
* if (physChunk) {
* console.log(`Found pixel density info at byte ${physChunk.start}`)
* } else {
* console.log('No pixel density information found')
* }
* ```
*/
static readChunks(view: DataView, offset?: number): Record<string, {
dataOffset: number;
size: number;
start: number;
}>;
/**
* Parses the pHYs (physical pixel dimensions) chunk data.
* Reads pixels per unit for X and Y axes, and the unit specifier.
*
* @param view - DataView containing the PNG data
* @param offset - Byte offset of the pHYs chunk data
* @returns Object with ppux (pixels per unit X), ppuy (pixels per unit Y), and unit specifier
*
* @example
* ```ts
* // Extract pixel density information for DPI calculation
* const physChunk = PngHelpers.findChunk(dataView, 'pHYs')
* if (physChunk) {
* const physData = PngHelpers.parsePhys(dataView, physChunk.dataOffset)
*
* if (physData.unit === 1) { // meters
* const dpiX = Math.round(physData.ppux * 0.0254)
* const dpiY = Math.round(physData.ppuy * 0.0254)
* console.log(`DPI: ${dpiX} x ${dpiY}`)
* }
* }
* ```
*/
static parsePhys(view: DataView, offset: number): {
ppux: number;
ppuy: number;
unit: number;
};
/**
* Finds a specific chunk type in the PNG file and returns its metadata.
*
* @param view - DataView containing the PNG file data
* @param type - 4-character chunk type to search for (e.g., 'pHYs', 'IDAT')
* @returns Chunk metadata object if found, undefined otherwise
*
* @example
* ```ts
* // Look for pixel density information in PNG
* const physChunk = PngHelpers.findChunk(dataView, 'pHYs')
* if (physChunk) {
* const physData = PngHelpers.parsePhys(dataView, physChunk.dataOffset)
* console.log(`Found pHYs chunk with ${physData.ppux} x ${physData.ppuy} pixels per unit`)
* }
*
* // Check for text metadata
* const textChunk = PngHelpers.findChunk(dataView, 'tEXt')
* if (textChunk) {
* console.log(`Found text metadata at byte ${textChunk.start}`)
* }
* ```
*/
static findChunk(view: DataView, type: string): {
dataOffset: number;
size: number;
start: number;
};
/**
* Adds or replaces a pHYs chunk in a PNG file to set pixel density for high-DPI displays.
* The method determines insertion point by prioritizing IDAT chunk position over existing pHYs,
* creates a properly formatted pHYs chunk with CRC validation, and returns a new Blob.
*
* @param view - DataView containing the original PNG file data
* @param dpr - Device pixel ratio multiplier (defaults to 1)
* @param options - Optional Blob constructor options for MIME type and other properties
* @returns New Blob containing the PNG with updated pixel density information
*
* @example
* ```ts
* // Export PNG with proper pixel density for high-DPI displays
* const canvas = document.createElement('canvas')
* const ctx = canvas.getContext('2d')
* // ... draw content to canvas ...
*
* canvas.toBlob(async (blob) => {
* if (blob) {
* const view = new DataView(await blob.arrayBuffer())
* // Create 2x DPI version for Retina displays
* const highDpiBlob = PngHelpers.setPhysChunk(view, 2, { type: 'image/png' })
* // Download or use the blob...
* }
* }, 'image/png')
* ```
*/
static setPhysChunk(view: DataView, dpr?: number, options?: BlobPropertyBag): Blob;
}
/* Excluded from this release type: promiseWithResolve */
/**
* Makes all properties in a type and all nested properties optional recursively.
* This is useful for creating partial update objects where you only want to specify
* some deeply nested properties while leaving others unchanged.
*
* @example
* ```ts
* interface User {
* name: string
* settings: {
* theme: string
* notifications: {
* email: boolean
* push: boolean
* }
* }
* }
*
* type PartialUser = RecursivePartial<User>
* // Result: {
* // name?: string
* // settings?: {
* // theme?: string
* // notifications?: {
* // email?: boolean
* // push?: boolean
* // }
* // }
* // }
*
* const update: PartialUser = {
* settings: {
* notifications: {
* email: false
* }
* }
* }
* ```
*
* @public
*/
export declare type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
};
/* Excluded from this release type: registerTldrawLibraryVersion */
/* Excluded from this release type: Required_2 */
/* Excluded from this release type: restoreUniqueId */
/**
* A discriminated union type for handling success and error cases.
*
* Represents either a successful result with a value or a failed result with an error.
* This pattern provides type-safe error handling without throwing exceptions. The 'ok' property
* serves as the discriminant for type narrowing.
*
* @example
* ```ts
* function divide(a: number, b: number): Result<number, string> {
* if (b === 0) {
* return Result.err('Division by zero')
* }
* return Result.ok(a / b)
* }
*
* const result = divide(10, 2)
* if (result.ok) {
* console.log(`Result: ${result.value}`) // Result: 5
* } else {
* console.error(`Error: ${result.error}`)
* }
* ```
* @public
*/
export declare type Result<T, E> = ErrorResult<E> | OkResult<T>;
/**
* Utility object for creating Result instances.
*
* Provides factory methods for creating OkResult and ErrorResult instances.
* This is the preferred way to construct Result values for consistent structure.
*
* @example
* ```ts
* // Create success result
* const success = Result.ok(42)
* // success: OkResult<number> = { ok: true, value: 42 }
*
* // Create error result
* const failure = Result.err('Invalid input')
* // failure: ErrorResult<string> = { ok: false, error: 'Invalid input' }
* ```
* @public
*/
export declare const Result: {
/**
* Create a failed result containing an error.
*
* @param error - The error value to wrap
* @returns An ErrorResult containing the error
*/
err<E>(error: E): ErrorResult<E>;
/**
* Create a successful result containing a value.
*
* @param value - The success value to wrap
* @returns An OkResult containing the value
*/
ok<T>(value: T): OkResult<T>;
};
/* Excluded from this release type: retry */
/**
* Seeded random number generator, using [xorshift](https://en.wikipedia.org/wiki/Xorshift). The
* result will always be between -1 and 1.
*
* Adapted from [seedrandom](https://github.com/davidbau/seedrandom).
*
* @param seed - The seed string for deterministic random generation (defaults to empty string)
* @returns A function that will return a random number between -1 and 1 each time it is called
* @example
* ```ts
* const random = rng('my-seed')
* const num1 = random() // Always the same for this seed
* const num2 = random() // Next number in sequence
*
* // Different seed produces different sequence
* const otherRandom = rng('other-seed')
* const different = otherRandom() // Different value
* ```
* @public
*/
export declare function rng(seed?: string): () => number;
/**
* Rotate the contents of an array by a specified offset.
*
* Creates a new array with elements shifted to the left by the specified number of positions.
* Both positive and negative offsets result in left shifts (elements move left, with elements
* from the front wrapping to the back).
*
* @param arr - The array to rotate
* @param offset - The number of positions to shift left (both positive and negative values shift left)
* @returns A new array with elements shifted left by the specified offset
*
* @example
* ```ts
* rotateArray([1, 2, 3, 4], 1) // [2, 3, 4, 1]
* rotateArray([1, 2, 3, 4], -1) // [2, 3, 4, 1]
* rotateArray(['a', 'b', 'c'], 2) // ['c', 'a', 'b']
* ```
* @public
*/
export declare function rotateArray<T>(arr: T[], offset: number): T[];
/**
* Safely parses a URL string without throwing exceptions on invalid input.
* Returns a URL object for valid URLs or undefined for invalid ones.
*
* @param url - The URL string to parse
* @param baseUrl - Optional base URL to resolve relative URLs against
* @returns A URL object if parsing succeeds, undefined if it fails
*
* @example
* ```ts
* // Valid absolute URL
* const url1 = safeParseUrl('https://example.com')
* if (url1) {
* console.log(`Valid URL: ${url1.href}`) // "Valid URL: