@hookform/lenses
Version:
Type-safe lenses for React Hook Form that enable precise control over nested form state. Build reusable form components with composable operations, array handling, and full TypeScript support.
1 lines • 13.3 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/LensCore.ts","../src/LensesStorage.ts","../src/useLens.ts"],"sourcesContent":["export * from './LensCore';\nexport * from './LensesStorage';\nexport type * from './types';\nexport * from './useLens';\n","import { type Control, type FieldValues, get, set } from 'react-hook-form';\n\nimport type { LensesStorage } from './LensesStorage';\nimport type { Lens } from './types';\n\nexport interface LensCoreInteropBinding<T extends FieldValues> {\n control: Control<T>;\n name: string | undefined;\n getTransformer?: (value: unknown) => unknown;\n setTransformer?: (value: unknown) => unknown;\n}\n\n/**\n * Runtime lens implementation.\n */\nexport class LensCore<T extends FieldValues> {\n public control: Control<T>;\n public path: string;\n public cache?: LensesStorage<T> | undefined;\n\n private isArrayItemReflection?: boolean;\n private override?: Record<string, LensCore<T>> | [Record<string, LensCore<T>>];\n private interopCache?: LensCoreInteropBinding<T>;\n\n constructor(control: Control<T>, path: string, cache?: LensesStorage<T> | undefined) {\n this.control = control;\n this.path = path;\n this.cache = cache;\n }\n\n public static create<TFieldValues extends FieldValues = FieldValues>(\n control: Control<TFieldValues>,\n cache?: LensesStorage<TFieldValues>,\n ): Lens<TFieldValues> {\n return new LensCore(control, '', cache) as unknown as Lens<TFieldValues>;\n }\n\n public focus(prop: string | number): LensCore<T> {\n const propString = prop.toString();\n const nestedPath = this.path ? `${this.path}.${propString}` : propString;\n\n const fromCache = this.cache?.get(nestedPath);\n\n if (fromCache) {\n return fromCache;\n }\n\n if (Array.isArray(this.override)) {\n const [template] = this.override;\n const result = new LensCore(this.control, nestedPath, this.cache);\n result.isArrayItemReflection = true;\n result.override = template;\n\n this.cache?.set(result, nestedPath);\n\n return result;\n } else if (this.override) {\n const overriddenLens: LensCore<T> | undefined = get(this.override, propString);\n\n if (!overriddenLens) {\n const result = new LensCore(this.control, nestedPath, this.cache);\n this.cache?.set(result, nestedPath);\n return result;\n }\n\n if (this.isArrayItemReflection) {\n const arrayItemNestedPath = `${this.path}.${overriddenLens.path}`;\n const result = new LensCore(this.control, arrayItemNestedPath, this.cache);\n this.cache?.set(result, arrayItemNestedPath);\n return result;\n } else {\n this.cache?.set(overriddenLens, nestedPath);\n return overriddenLens;\n }\n }\n\n const result = new LensCore(this.control, nestedPath, this.cache);\n this.cache?.set(result, nestedPath);\n return result;\n }\n\n public reflect(\n getter: (\n dictionary: ProxyHandler<Record<string, LensCore<T>>>,\n lens: LensCore<T>,\n ) => Record<string, LensCore<T>> | [Record<string, LensCore<T>>],\n ): LensCore<T> {\n const fromCache = this.cache?.get(this.path, getter);\n\n if (fromCache) {\n return fromCache;\n }\n\n const template = new LensCore(this.control, this.path, this.cache);\n\n const dictionary = new Proxy(\n {},\n {\n get: (target, prop) => {\n if (typeof prop === 'string') {\n return template.focus(prop);\n }\n\n return target;\n },\n },\n );\n\n const override = getter(dictionary, template);\n\n if (Array.isArray(override)) {\n const result = new LensCore(this.control, this.path, this.cache);\n template.path = '';\n result.override = getter(dictionary, template);\n this.cache?.set(result, this.path, getter);\n return result;\n } else {\n template.override = override;\n template.path = this.path;\n this.cache?.set(template, this.path, getter);\n return template;\n }\n }\n\n public map<R>(\n fields: Record<string, any>[],\n mapper: (value: unknown, item: LensCore<T>, index: number, array: unknown[], lens: this) => R,\n ): R[] {\n return fields.map((value, index, array) => {\n const item = this.focus(index.toString());\n const res = mapper(value, item, index, array, this);\n return res;\n });\n }\n\n public interop(cb?: (control: Control<T>, name: string | undefined) => any): LensCoreInteropBinding<T> | undefined {\n if (cb) {\n return cb(this.control, this.path);\n }\n\n this.interopCache ??= {\n control: this.control,\n name: this.path,\n ...(this.override ? { getTransformer: this.getTransformer.bind(this), setTransformer: this.setTransformer.bind(this) } : {}),\n };\n\n return this.interopCache;\n }\n\n private getTransformer(value: unknown): unknown {\n const [template] = Array.isArray(this.override) ? this.override : [this.override];\n\n if (!value || !template) {\n return value;\n }\n\n const newValue = {} as typeof value;\n\n Object.entries(template).forEach(([key, valueTemplate]) => {\n const restructuredLens = valueTemplate;\n\n if (!restructuredLens) {\n return;\n }\n\n const v = get(value, restructuredLens.path);\n set(newValue, key, v);\n });\n\n return newValue;\n }\n\n private setTransformer(value: unknown): unknown {\n const [template] = Array.isArray(this.override) ? this.override : [this.override];\n\n if (!value || !template) {\n return value;\n }\n\n const newValue = {} as typeof value;\n\n Object.entries(value).forEach(([key, value]) => {\n const restructuredLens = template[key];\n\n if (!restructuredLens) {\n return;\n }\n\n set(newValue, restructuredLens.path, value);\n });\n\n return newValue;\n }\n}\n","import type { Control, FieldValues } from 'react-hook-form';\n\nimport type { LensCore } from './LensCore';\n\nexport type LensesStorageComplexKey = (...args: any[]) => any;\n\nexport interface LensesStorageValue<T extends FieldValues> {\n plain?: LensCore<T>;\n complex: WeakMap<LensesStorageComplexKey, LensCore<T>>;\n}\n\nexport type LensCache<T extends FieldValues> = Map<string, LensesStorageValue<T>>;\n\n/**\n * Cache storage for lenses.\n */\nexport class LensesStorage<T extends FieldValues> {\n private cache: LensCache<T>;\n\n constructor(control: Control<T>) {\n this.cache = new Map();\n\n control?._subjects?.values?.subscribe?.({\n next: () => {\n control._names.unMount.forEach((name) => {\n this.delete(name);\n });\n },\n });\n }\n\n public get(path: string, complexKey?: LensesStorageComplexKey): LensCore<T> | undefined {\n const cached = this.cache.get(path);\n\n if (cached) {\n if (complexKey) {\n return cached.complex.get(complexKey);\n }\n\n return cached.plain;\n }\n\n return undefined;\n }\n\n public set(lens: LensCore<T>, path: string, complexKey?: LensesStorageComplexKey): void {\n let cached = this.cache.get(path);\n\n if (!cached) {\n cached = {\n complex: new WeakMap(),\n };\n\n this.cache.set(path, cached);\n }\n\n if (complexKey) {\n cached.complex.set(complexKey, lens);\n } else {\n cached.plain = lens;\n }\n }\n\n public has(path: string, complexKey?: LensesStorageComplexKey): boolean {\n if (complexKey) {\n return this.cache.get(path)?.complex.has(complexKey) ?? false;\n }\n\n return this.cache.has(path);\n }\n\n public delete(path: string): void {\n for (const key of this.cache.keys()) {\n if (key.startsWith(path)) {\n this.cache.delete(key);\n }\n }\n }\n\n public clear(): void {\n this.cache.clear();\n }\n}\n","import { type DependencyList, useMemo } from 'react';\nimport type { Control, FieldValues } from 'react-hook-form';\n\nimport { LensCore } from './LensCore';\nimport { LensesStorage } from './LensesStorage';\nimport type { Lens } from './types';\n\nexport interface UseLensProps<TFieldValues extends FieldValues = FieldValues> {\n control: Control<TFieldValues>;\n}\n\n/**\n * Creates lens from react-hook-form control.\n *\n * @example\n * ```tsx\n * function App() {\n * const { control } = useForm<{\n * firstName: string;\n * }>();\n *\n * const lens = useLens({ control });\n * }\n * ```\n */\nexport function useLens<TFieldValues extends FieldValues = FieldValues>(\n props: UseLensProps<TFieldValues>,\n deps: DependencyList = [],\n): Lens<TFieldValues> {\n return useMemo(() => {\n const cache = new LensesStorage(props.control);\n const lens = LensCore.create<TFieldValues>(props.control, cache);\n\n return lens;\n }, [props.control, ...deps]);\n}\n"],"mappings":"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","names":["result","value"]}