/** * Types exposed through the public API */ import type { VectorSettings } from '../components/Vector/vector-types' import { StoreType, Data, DataInput } from './internal' import type { BeautifyUnionType, UnionToIntersection } from './utils' export type RenderFn = (get: (key: string) => any) => boolean /** * Utility types that joins a value with its settings */ export type InputWithSettings = { [key in K]: V } & { type?: LevaInputs } & Settings /** * Either the raw value, either the value with its settings * In other words => value || { value, ...settings } */ export type MergedInputWithSettings = | V | InputWithSettings /** * Special Inputs */ export enum SpecialInputs { BUTTON = 'BUTTON', BUTTON_GROUP = 'BUTTON_GROUP', MONITOR = 'MONITOR', FOLDER = 'FOLDER', } export enum LevaInputs { SELECT = 'SELECT', IMAGE = 'IMAGE', NUMBER = 'NUMBER', COLOR = 'COLOR', STRING = 'STRING', BOOLEAN = 'BOOLEAN', INTERVAL = 'INTERVAL', VECTOR3D = 'VECTOR3D', VECTOR2D = 'VECTOR2D', } export type ButtonSettings = { disabled?: boolean } export type ButtonInput = { type: SpecialInputs.BUTTON onClick: (get: (path: string) => any) => void settings: ButtonSettings } export type ButtonGroupOpts = { [title: string]: (get: (path: string) => any) => void } export type ButtonGroupInputOpts = | ButtonGroupOpts | { label?: string | React.JSX.Element | null opts: ButtonGroupOpts } export type ButtonGroupInput = { type: SpecialInputs.BUTTON_GROUP opts: ButtonGroupInputOpts } export type MonitorSettings = { graph?: boolean; interval?: number } export type MonitorInput = { type: SpecialInputs.MONITOR objectOrFn: React.MutableRefObject | Function settings: MonitorSettings } export type SpecialInput = MonitorInput | ButtonInput | ButtonGroupInput export type FolderSettings = { collapsed?: boolean render?: RenderFn color?: string /** works similar to css order property */ order?: number } export type NumberSettings = { min?: number; max?: number; step?: number } export type VectorObj = Record export type Vector2dArray = [number, number] export type Vector2d = Vector2dArray | VectorObj export type Vector2dSettings = VectorSettings & { joystick?: boolean | 'invertY'; lock?: boolean } export type Vector2dInput = MergedInputWithSettings export type Vector3dArray = [number, number, number] export type Vector3d = Vector3dArray | VectorObj export type Vector3dSettings = VectorSettings & { lock?: boolean } export type Vector3dInput = MergedInputWithSettings export type IntervalInput = { value: [number, number]; min: number; max: number } export type ImageInput = { image: undefined | string } type SelectInput = { options: any[] | Record; value?: any } type SelectWithValueInput = { options: T[] | Record; value: K } type SelectWithoutValueInput = { options: T[] | Record } type ColorRgbaInput = { r: number; g: number; b: number; a?: number } type ColorHslaInput = { h: number; s: number; l: number; a?: number } type ColorHsvaInput = { h: number; s: number; v: number; a?: number } export type ColorVectorInput = ColorRgbaInput | ColorHslaInput | ColorHsvaInput type BooleanInput = boolean type StringSettings = { rows?: boolean | number; editable?: boolean } type StringInput = InputWithSettings export type FolderInput = { type: SpecialInputs.FOLDER schema: Schema settings: FolderSettings } export type CustomInput = { type: string; __customInput: Value } type SchemaItem = | InputWithSettings | InputWithSettings | InputWithSettings | IntervalInput | ColorVectorInput | Vector2dInput | Vector3dInput | ImageInput | SelectInput | BooleanInput | StringInput | CustomInput type GenericSchemaItemOptions = { render?: RenderFn label?: string | React.JSX.Element hint?: string order?: number } type OnHandlerContext = DataInput & { get(path: string): any } type OnChangeHandlerContext = OnHandlerContext & { /** * Whether the onChange handler is invoked initially. */ initial: boolean } export type OnChangeHandler = (value: any, path: string, context: OnChangeHandlerContext) => void type TransientOnChangeSchemaItemOptions = { onChange: OnChangeHandler transient?: true } type NonTransientOnChangeSchemaItemOptions = { onChange: OnChangeHandler transient: false } type NoOnChangeSchemaItemOptions = { onChange?: undefined transient?: undefined } type OnChangeSchemaItemOptions = | TransientOnChangeSchemaItemOptions | NonTransientOnChangeSchemaItemOptions | NoOnChangeSchemaItemOptions export type InputOptions = GenericSchemaItemOptions & OnChangeSchemaItemOptions & { optional?: boolean disabled?: boolean onEditStart?: (value: any, path: string, context: OnHandlerContext) => void onEditEnd?: (value: any, path: string, context: OnHandlerContext) => void } type SchemaItemWithOptions = | number | boolean | string | (SchemaItem & InputOptions) | (SpecialInput & GenericSchemaItemOptions) | FolderInput export type Schema = Record /** * Dummy type used internally to flag non compatible input types. * @internal */ type NotAPrimitiveType = { ____: 'NotAPrimitiveType' } type PrimitiveToValue

= P extends CustomInput ? BeautifyUnionType : P extends ImageInput ? string | undefined : P extends SelectWithValueInput ? SelectValue | Options : P extends SelectWithoutValueInput ? Options : P extends IntervalInput ? [number, number] : P extends { value: infer Value } ? PrimitiveToValue : P extends VectorObj ? P : P extends Vector3dArray ? [number, number, number] : P extends Vector2dArray ? [number, number] : P extends number ? number : P extends string ? string : P extends boolean ? boolean : NotAPrimitiveType export type SchemaToValues = BeautifyUnionType< UnionToIntersection> > type EndLeaf = { ___leaf: 'leaf' } type Join = EndLeaf extends Leaf2 ? { [i in Leaf1Key]: Leaf1[Leaf1Key] } : Leaf2 type Tree = { // if it's a folder we run the type check on it's schema key 0: Leaf extends { schema: infer Schema } ? { [Key in keyof Schema]: Join } : never 1: never // if the leaf is an object, we run the type check on each of its keys 2: { [Key in LeafKey]: Leaf extends { optional: true } | { disabled: true } ? PrimitiveToValue | undefined : PrimitiveToValue } // recursivity 3: { [Key in keyof Leaf]: Join> }[keyof Leaf] // dead end 4: EndLeaf }[LeafKey extends '' ? 3 : Leaf extends FolderInput ? 0 : Leaf extends SpecialInput ? 1 : PrimitiveToValue extends NotAPrimitiveType ? Leaf extends object ? 3 : 4 : // if an input has the onChange property then it's transient and isn't returned Leaf extends TransientOnChangeSchemaItemOptions ? IncludeTransient extends true ? 2 : 1 : 2] /** * If P is '' then T is the whole schema and we shouldn't run any type check * on the schema, to the risk that { a: 1, b: 2 } is recognized as Vector * instead of a two number inputs. */ /** * Interface to build a plugin. * * @public */ export interface Plugin { /** * The component that shows the input value; */ component: React.ComponentType /** * Normalizes the input into a { value, settings } object. * * @example * Let's consider a color with an inverted settings option that computes the negative * of that color. The plugin could look something like: * ```ts * myColorPlugin({ color: '#fff', inverted: true }) * ``` * * In that case, your normalize funciton would be something like: * ```ts * function normalize({ color, inverted }) { * return { value: color, settings: { inverted }} * } * ``` */ normalize?: (input: Input, path: string, data: Data) => { value: Value; settings?: InternalSettings } /** * Sanitizes the user value before registering it to the store. For * example, the Number plugin would santize "3.00" into 3. If the provided * value isn't formatted properly, the sanitize function should throw. */ sanitize?: (value: any, settings: InternalSettings, prevValue: any, path: string, store: StoreType) => Value /** * Formats the value into the value that will be displayed by the component. * If the input value of the Number plugin, then format will add proper * padding and show "3.00". * (Prop name in useInputContext context hook is `displayedValue`) */ format?: (value: any, settings: InternalSettings) => any } export type InputContextProps = { id: string label: string | React.JSX.Element hint?: string path: string key: string optional: boolean disabled: boolean disable: (flag: boolean) => void storeId: string value: unknown displayValue: unknown onChange: React.Dispatch emitOnEditStart: () => void emitOnEditEnd: () => void onUpdate: (v: any | ((v: any) => any)) => void settings: unknown setSettings: (v: any) => void } /** * Interface consumed by the useInputContext hook so that its returned values * are properly typed. * * @example * ```ts * useInputContext>() * ``` * @public */ export interface LevaInputProps { path?: string id?: string hint?: string disabled?: boolean displayValue: DisplayValue value: V onChange: React.Dispatch emitOnEditStart: () => void emitOnEditEnd: () => void onUpdate: (v: any | ((v: any) => any)) => void settings: InternalSettings setSettings: (v: Partial) => void }