@goodie-forms/react 1.2.5-alpha → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +61 -0
  2. package/dist/components/FieldRenderer.d.ts +9 -13
  3. package/dist/components/FieldRenderer.d.ts.map +1 -1
  4. package/dist/hooks/useFieldIssues.d.ts +4 -0
  5. package/dist/hooks/useFieldIssues.d.ts.map +1 -0
  6. package/dist/hooks/useFieldValue.d.ts +1 -1
  7. package/dist/hooks/useFieldValue.d.ts.map +1 -1
  8. package/dist/hooks/useForm.d.ts +18 -6
  9. package/dist/hooks/useForm.d.ts.map +1 -1
  10. package/dist/hooks/useFormField.d.ts +3 -2
  11. package/dist/hooks/useFormField.d.ts.map +1 -1
  12. package/dist/hooks/useSyncMutableStore.d.ts +2 -0
  13. package/dist/hooks/useSyncMutableStore.d.ts.map +1 -0
  14. package/dist/index.d.ts +1 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +140 -169
  17. package/dist/index.js.map +1 -1
  18. package/package.json +15 -2
  19. package/dist/hooks/useFieldErrors.d.ts +0 -4
  20. package/dist/hooks/useFieldErrors.d.ts.map +0 -1
  21. package/dist/hooks/useFormErrorObserver.d.ts +0 -7
  22. package/dist/hooks/useFormErrorObserver.d.ts.map +0 -1
  23. package/dist/hooks/useFormValuesObserver.d.ts +0 -7
  24. package/dist/hooks/useFormValuesObserver.d.ts.map +0 -1
  25. package/dist/hooks/useRenderControl.d.ts +0 -5
  26. package/dist/hooks/useRenderControl.d.ts.map +0 -1
  27. package/src/components/FieldRenderer.tsx +0 -166
  28. package/src/hooks/useFieldErrors.ts +0 -31
  29. package/src/hooks/useFieldValue.ts +0 -31
  30. package/src/hooks/useForm.tsx +0 -50
  31. package/src/hooks/useFormErrorObserver.ts +0 -45
  32. package/src/hooks/useFormField.tsx +0 -63
  33. package/src/hooks/useFormValuesObserver.ts +0 -50
  34. package/src/hooks/useRenderControl.tsx +0 -26
  35. package/src/index.ts +0 -9
  36. package/src/utils/composeFns.ts +0 -7
  37. package/src/utils/groupBy.ts +0 -13
  38. package/tsconfig.json +0 -8
  39. package/vite.config.ts +0 -23
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/hooks/useRenderControl.tsx","../src/utils/composeFns.ts","../src/hooks/useForm.tsx","../src/hooks/useFormField.tsx","../src/hooks/useFieldValue.ts","../src/hooks/useFieldErrors.ts","../src/hooks/useFormValuesObserver.ts","../src/utils/groupBy.ts","../src/hooks/useFormErrorObserver.ts","../src/components/FieldRenderer.tsx"],"sourcesContent":["import { startTransition, useRef, useState } from \"react\";\r\n\r\nexport function useRenderControl() {\r\n const [, rerender] = useState(0);\r\n const renderCount = useRef(0);\r\n const renderScheduled = useRef(false);\r\n renderCount.current++;\r\n\r\n const scheduleRerender = () => {\r\n if (renderScheduled.current) return;\r\n renderScheduled.current = true;\r\n\r\n queueMicrotask(() => {\r\n startTransition(() => {\r\n rerender((i) => i + 1);\r\n });\r\n\r\n renderScheduled.current = false;\r\n });\r\n };\r\n\r\n return {\r\n renderCount: renderCount.current,\r\n forceRerender: scheduleRerender,\r\n };\r\n}\r\n","export function composeFns<TFns extends (() => void)[]>(...fns: TFns) {\r\n return () => {\r\n for (const fn of fns) {\r\n fn();\r\n }\r\n };\r\n}\r\n","import { FieldPathBuilder, FormController } from \"@goodie-forms/core\";\r\nimport { useEffect, useState } from \"react\";\r\nimport { useRenderControl } from \"../hooks/useRenderControl\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\n\r\nexport function useForm<TOutput extends object>(\r\n formConfigs: FormController.Configs<TOutput>,\r\n hookConfigs?: {\r\n validateMode?: \"onChange\" | \"onBlur\" | \"onSubmit\";\r\n revalidateMode?: \"onChange\" | \"onBlur\" | \"onSubmit\";\r\n watchIssues?: boolean;\r\n watchValues?: boolean;\r\n },\r\n) {\r\n const [controller] = useState(() => new FormController(formConfigs));\r\n const [paths] = useState(() => new FieldPathBuilder<TOutput>());\r\n\r\n const renderControl = useRenderControl();\r\n\r\n useEffect(() => {\r\n const noop = () => {};\r\n\r\n return composeFns(\r\n controller.events.on(\"submissionStatusChange\", () => {\r\n renderControl.forceRerender();\r\n }),\r\n hookConfigs?.watchIssues\r\n ? controller.events.on(\"fieldIssuesUpdated\", () =>\r\n renderControl.forceRerender(),\r\n )\r\n : noop,\r\n hookConfigs?.watchValues\r\n ? controller.events.on(\"valueChanged\", () =>\r\n renderControl.forceRerender(),\r\n )\r\n : noop,\r\n );\r\n }, [controller]);\r\n\r\n return {\r\n formConfigs,\r\n paths,\r\n hookConfigs,\r\n controller,\r\n };\r\n}\r\n\r\nexport type UseForm<TOutput extends object> = ReturnType<\r\n typeof useForm<TOutput>\r\n>;\r\n","import { FieldPath } from \"@goodie-forms/core\";\r\nimport { useEffect, useState } from \"react\";\r\nimport { UseForm } from \"../hooks/useForm\";\r\nimport { useRenderControl } from \"../hooks/useRenderControl\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\n\r\nexport function useFormField<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n>(\r\n form: UseForm<TOutput>,\r\n path: TPath,\r\n bindingConfig?: Parameters<typeof form.controller.bindField<TPath>>[1],\r\n) {\r\n const renderControl = useRenderControl();\r\n\r\n const [field, setField] = useState(() => {\r\n let field = form.controller.getField(path);\r\n if (field == null && bindingConfig != null) {\r\n field = form.controller.bindField(path, bindingConfig);\r\n }\r\n return field;\r\n });\r\n\r\n useEffect(() => {\r\n const { events } = form.controller;\r\n\r\n setField(form.controller.getField(path));\r\n\r\n return composeFns(\r\n events.on(\"fieldBound\", (_path) => {\r\n if (!FieldPath.equals(_path, path)) return;\r\n setField(form.controller.getField(path));\r\n }),\r\n events.on(\"fieldUnbound\", (_path) => {\r\n if (!FieldPath.equals(_path, path)) return;\r\n setField(undefined);\r\n }),\r\n events.on(\"valueChanged\", (changedPath) => {\r\n if (\r\n FieldPath.equals(changedPath, path) ||\r\n FieldPath.isDescendant(changedPath, path)\r\n ) {\r\n renderControl.forceRerender();\r\n }\r\n }),\r\n events.on(\"fieldTouchUpdated\", (_path) => {\r\n if (!FieldPath.equals(_path, path)) return;\r\n renderControl.forceRerender();\r\n }),\r\n events.on(\"fieldDirtyUpdated\", (_path) => {\r\n if (!FieldPath.equals(_path, path)) return;\r\n renderControl.forceRerender();\r\n }),\r\n events.on(\"fieldIssuesUpdated\", (_path) => {\r\n if (!FieldPath.equals(_path, path)) return;\r\n renderControl.forceRerender();\r\n }),\r\n );\r\n }, []);\r\n\r\n return field;\r\n}\r\n","import { FieldPath } from \"@goodie-forms/core\";\r\nimport { useEffect } from \"react\";\r\nimport { UseForm } from \"../hooks/useForm\";\r\nimport { useRenderControl } from \"../hooks/useRenderControl\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\n\r\nexport function useFieldValue<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n>(form: UseForm<TOutput>, path: TPath) {\r\n const renderControl = useRenderControl();\r\n\r\n const value = form.controller.getField(path)?.value;\r\n\r\n useEffect(() => {\r\n return composeFns(\r\n form.controller.events.on(\"fieldBound\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n renderControl.forceRerender();\r\n }\r\n }),\r\n form.controller.events.on(\"valueChanged\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n renderControl.forceRerender();\r\n }\r\n }),\r\n );\r\n }, []);\r\n\r\n return value;\r\n}\r\n","import { FieldPath } from \"@goodie-forms/core\";\r\nimport { useEffect } from \"react\";\r\nimport { UseForm } from \"../hooks/useForm\";\r\nimport { useRenderControl } from \"../hooks/useRenderControl\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\n\r\nexport function useFieldErrors<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n>(form: UseForm<TOutput>, path: TPath) {\r\n const renderControl = useRenderControl();\r\n\r\n const issues = form.controller.getField(path)?.issues;\r\n\r\n useEffect(() => {\r\n return composeFns(\r\n form.controller.events.on(\"fieldBound\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n renderControl.forceRerender();\r\n }\r\n }),\r\n form.controller.events.on(\"fieldIssuesUpdated\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n renderControl.forceRerender();\r\n }\r\n }),\r\n );\r\n }, []);\r\n\r\n return issues;\r\n}\r\n","import { FieldPath } from \"@goodie-forms/core\";\r\nimport { useEffect } from \"react\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\nimport type { UseForm } from \"./useForm\";\r\nimport { useRenderControl } from \"./useRenderControl\";\r\n\r\n/** @deprecated */\r\nexport function useFormValuesObserver<\r\n TOutput extends object,\r\n TPaths extends FieldPath.Segments[] | undefined = undefined,\r\n>(\r\n form: UseForm<TOutput>,\r\n options?: {\r\n include?: TPaths;\r\n },\r\n) {\r\n const renderControl = useRenderControl();\r\n\r\n const observedValues =\r\n options?.include == null\r\n ? form.controller._data\r\n : options.include.reduce((data, path) => {\r\n const value = FieldPath.getValue(\r\n form.controller._data as TOutput,\r\n path,\r\n )!;\r\n FieldPath.setValue(data, path, value);\r\n return data;\r\n }, {} as TOutput);\r\n\r\n useEffect(() => {\r\n const { events } = form.controller;\r\n\r\n return composeFns(\r\n events.on(\"valueChanged\", (changedPath) => {\r\n const watchingChange =\r\n options?.include == null\r\n ? true\r\n : options.include.some(\r\n (path) =>\r\n FieldPath.equals(path, changedPath) ||\r\n FieldPath.isDescendant(path, changedPath),\r\n );\r\n if (watchingChange) renderControl.forceRerender();\r\n }),\r\n );\r\n }, []);\r\n\r\n return observedValues;\r\n}\r\n","export function groupBy<T, K extends PropertyKey>(\r\n items: readonly T[],\r\n key: (item: T) => K,\r\n): Record<K, T[]> {\r\n const result = {} as Record<K, T[]>;\r\n\r\n for (const item of items) {\r\n const k = key(item);\r\n (result[k] ??= []).push(item);\r\n }\r\n\r\n return result;\r\n}\r\n","import { FieldPath } from \"@goodie-forms/core\";\r\nimport { groupBy } from \"../utils/groupBy\";\r\nimport { useEffect } from \"react\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\nimport type { UseForm } from \"./useForm\";\r\nimport { useRenderControl } from \"./useRenderControl\";\r\n\r\n/** @deprecated */\r\nexport function useFormErrorObserver<\r\n TOutput extends object,\r\n TInclude extends FieldPath.Segments[] | undefined = undefined,\r\n>(\r\n form: UseForm<TOutput>,\r\n options?: {\r\n include?: TInclude;\r\n },\r\n) {\r\n const renderControl = useRenderControl();\r\n\r\n const observedIssues = form.controller._issues.filter((issue) => {\r\n if (options?.include == null) return true;\r\n const normalizedIssuePath = FieldPath.normalize(issue.path);\r\n return options.include.some((path) =>\r\n FieldPath.equals(path, normalizedIssuePath),\r\n );\r\n });\r\n\r\n useEffect(() => {\r\n const { events } = form.controller;\r\n\r\n return composeFns(\r\n events.on(\"fieldIssuesUpdated\", (path) => {\r\n if (options?.include?.includes?.(path) ?? true) {\r\n renderControl.forceRerender();\r\n }\r\n }),\r\n );\r\n }, []);\r\n\r\n return groupBy(observedIssues, (issue) =>\r\n issue.path == null\r\n ? \"$\"\r\n : FieldPath.toStringPath(FieldPath.normalize(issue.path)),\r\n );\r\n}\r\n","import { FieldPath, FormField, NonnullFormField } from \"@goodie-forms/core\";\r\nimport { ChangeEvent, ReactNode, Ref, useEffect, useRef } from \"react\";\r\nimport { UseForm } from \"../hooks/useForm\";\r\nimport { useFormField } from \"../hooks/useFormField\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\n\r\nexport interface RenderParams<TOutput extends object, TValue> {\r\n fieldProps: {\r\n ref: Ref<any | null>;\r\n\r\n value: TValue | undefined;\r\n\r\n onChange: (event: ChangeEvent<EventTarget> | TValue) => void;\r\n onFocus: () => void;\r\n onBlur: () => void;\r\n };\r\n\r\n field: undefined extends TValue\r\n ? FormField<TOutput, TValue>\r\n : NonnullFormField<TOutput, TValue>;\r\n\r\n form: UseForm<TOutput>;\r\n}\r\n\r\ntype DefaultValueProps<TValue> = undefined extends TValue\r\n ? { defaultValue?: TValue | (() => TValue) }\r\n : { defaultValue: TValue | (() => TValue) };\r\n\r\nexport type FieldRendererProps<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n> = {\r\n form: UseForm<TOutput>;\r\n path: TPath;\r\n overrideInitialValue?: boolean;\r\n unbindOnUnmount?: boolean;\r\n render: (\r\n params: RenderParams<TOutput, FieldPath.Resolve<TOutput, NoInfer<TPath>>>,\r\n ) => ReactNode;\r\n} & DefaultValueProps<FieldPath.Resolve<TOutput, NoInfer<TPath>>>;\r\n\r\nexport function FieldRenderer<\r\n TOutput extends object,\r\n const TPath extends FieldPath.Segments,\r\n>(props: FieldRendererProps<TOutput, TPath>) {\r\n type TValue = FieldPath.Resolve<TOutput, TPath>;\r\n\r\n const elementRef = useRef<HTMLElement>(null);\r\n\r\n const field = useFormField(props.form, props.path, {\r\n overrideInitialValue: props.overrideInitialValue ?? true,\r\n defaultValue:\r\n typeof props.defaultValue === \"function\"\r\n ? (props.defaultValue as any)()\r\n : props.defaultValue,\r\n })!;\r\n\r\n const renderedJsx = props.render({\r\n fieldProps: {\r\n ref: elementRef,\r\n value: field.value,\r\n onChange(arg) {\r\n let newValue: TValue;\r\n\r\n if (typeof arg === \"object\" && \"target\" in arg) {\r\n const { target } = arg;\r\n if (target !== field.boundElement) return;\r\n if (!(\"value\" in target)) return;\r\n if (typeof target.value !== \"string\") return;\r\n newValue = target.value as TValue;\r\n } else {\r\n newValue = arg;\r\n }\r\n\r\n field.setValue(newValue, {\r\n shouldTouch: true,\r\n shouldMarkDirty: true,\r\n });\r\n },\r\n onFocus() {\r\n field.touch();\r\n },\r\n onBlur() {\r\n if (\r\n props.form.hookConfigs?.validateMode === \"onBlur\" ||\r\n props.form.hookConfigs?.validateMode === \"onChange\"\r\n ) {\r\n props.form.controller.validateField(props.path);\r\n }\r\n },\r\n },\r\n field: field as any,\r\n form: props.form,\r\n });\r\n\r\n useEffect(() => {\r\n const { events } = props.form.controller;\r\n\r\n return composeFns(\r\n events.on(\"valueChanged\", (_path) => {\r\n if (\r\n !FieldPath.equals(_path, props.path) &&\r\n !FieldPath.isDescendant(_path, props.path)\r\n ) {\r\n return;\r\n }\r\n\r\n if (props.form.hookConfigs?.validateMode === \"onChange\") {\r\n props.form.controller.validateField(props.path);\r\n }\r\n }),\r\n );\r\n }, []);\r\n\r\n useEffect(() => {\r\n field.bindElement(elementRef.current!);\r\n\r\n return () => {\r\n if (props.unbindOnUnmount) {\r\n props.form.controller.unbindField(props.path);\r\n }\r\n };\r\n }, []);\r\n\r\n return <>{renderedJsx}</>;\r\n}\r\n\r\n/* ---- TESTS ---------------- */\r\n\r\n// function TestComp() {\r\n// const form = useForm<{ a?: { b: 99 } }>({});\r\n\r\n// const jsx = (\r\n// <>\r\n// <FieldRenderer\r\n// form={form}\r\n// path={form.paths.fromProxy((data) => data.a.b)}\r\n// defaultValue={() => 99 as const}\r\n// render={({ fieldProps, field }) => {\r\n// // ^?\r\n// return <input {...fieldProps} />;\r\n// }}\r\n// />\r\n\r\n// {/* defaultField olmayabilir, çünkü \"a\" nullable */}\r\n// <FieldRenderer\r\n// form={form}\r\n// path={form.paths.fromProxy((data) => data.a)}\r\n// render={({ ref, value, handlers, field }) => {\r\n// // ^?\r\n// return <></>;\r\n// }}\r\n// />\r\n\r\n// <FieldRenderer\r\n// form={form}\r\n// path={form.paths.fromStringPath(\"a.b\")}\r\n// defaultValue={() => 99 as const}\r\n// render={({ ref, value, handlers, field }) => {\r\n// // ^?\r\n// return <></>;\r\n// }}\r\n// />\r\n// </>\r\n// );\r\n// }\r\n"],"names":["useRenderControl","rerender","useState","renderCount","useRef","renderScheduled","scheduleRerender","startTransition","i","composeFns","fns","fn","useForm","formConfigs","hookConfigs","controller","FormController","paths","FieldPathBuilder","renderControl","useEffect","noop","useFormField","form","path","bindingConfig","field","setField","events","_path","FieldPath","changedPath","useFieldValue","value","fieldPath","useFieldErrors","issues","useFormValuesObserver","options","observedValues","data","groupBy","items","key","result","item","k","useFormErrorObserver","observedIssues","issue","normalizedIssuePath","FieldRenderer","props","elementRef","renderedJsx","arg","newValue","target"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/hooks/useSyncMutableStore.ts","../src/hooks/useForm.tsx","../src/utils/composeFns.ts","../src/hooks/useFormField.tsx","../src/hooks/useFieldValue.ts","../src/hooks/useFieldIssues.ts","../src/components/FieldRenderer.tsx"],"sourcesContent":["import { useCallback, useRef, useSyncExternalStore } from \"react\";\r\n\r\nexport function useSyncMutableStore<T>(\r\n subscribe: (onVersionChange: () => void) => () => void,\r\n getValue: () => T,\r\n) {\r\n const versionRef = useRef(0);\r\n\r\n const subscribeWithVersion = useCallback(\r\n (onStoreChange: () => void) => {\r\n return subscribe(() => {\r\n versionRef.current++;\r\n onStoreChange();\r\n });\r\n },\r\n [subscribe],\r\n );\r\n\r\n const getSnapshot = useCallback(() => {\r\n return versionRef.current;\r\n }, []);\r\n\r\n useSyncExternalStore(subscribeWithVersion, getSnapshot, getSnapshot);\r\n\r\n return getValue();\r\n}\r\n","import { FormController } from \"@goodie-forms/core\";\r\nimport { useCallback, useState, useSyncExternalStore } from \"react\";\r\nimport { useSyncMutableStore } from \"../hooks/useSyncMutableStore\";\r\n\r\nexport function useForm<TOutput extends object>(\r\n formConfigs: FormController.Configs<TOutput>,\r\n hookConfigs?: {\r\n validateMode?: \"onChange\" | \"onBlur\" | \"onSubmit\";\r\n revalidateMode?: \"onChange\" | \"onBlur\" | \"onSubmit\";\r\n },\r\n) {\r\n const [controller] = useState(() => new FormController(formConfigs));\r\n\r\n const subscribe = useCallback(\r\n (onVersionChange: () => void) => {\r\n return controller.events.on(\"submissionStatusChange\", onVersionChange);\r\n },\r\n [controller],\r\n );\r\n\r\n useSyncMutableStore(subscribe, () => controller);\r\n\r\n const useWatchValues = () => {\r\n return useSyncExternalStore(\r\n (onStoreChange) =>\r\n controller.events.on(\"fieldValueChanged\", onStoreChange),\r\n () => controller.data,\r\n () => controller.data,\r\n );\r\n };\r\n\r\n const useWatchIssues = () => {\r\n return useSyncExternalStore(\r\n (onStoreChange) =>\r\n controller.events.on(\"fieldIssuesUpdated\", onStoreChange),\r\n () => controller.issues,\r\n () => controller.issues,\r\n );\r\n };\r\n\r\n const useWatchEvent = <E extends keyof typeof controller.events.events>(\r\n eventName: E,\r\n listener?: NonNullable<(typeof controller.events.events)[E]>[number],\r\n ) => {\r\n return useSyncMutableStore(\r\n (onVersionChange) =>\r\n controller.events.on(eventName, (...args: any[]) => {\r\n (listener as any)?.(...args);\r\n onVersionChange();\r\n }),\r\n () => undefined,\r\n );\r\n };\r\n\r\n return {\r\n formConfigs,\r\n hookConfigs,\r\n controller,\r\n path: controller.path,\r\n watchValues: useWatchValues,\r\n watchIssues: useWatchIssues,\r\n watchEvent: useWatchEvent,\r\n };\r\n}\r\n\r\nexport type UseForm<TOutput extends object> = ReturnType<\r\n typeof useForm<TOutput>\r\n>;\r\n","export function composeFns<TFns extends (() => void)[]>(...fns: TFns) {\r\n return () => {\r\n for (const fn of fns) {\r\n fn();\r\n }\r\n };\r\n}\r\n","import { FieldPath, FormField } from \"@goodie-forms/core\";\r\nimport { useCallback, useState } from \"react\";\r\nimport { UseForm } from \"../hooks/useForm\";\r\nimport { useSyncMutableStore } from \"../hooks/useSyncMutableStore\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\n\r\nexport function useFormField<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n>(\r\n form: UseForm<TOutput>,\r\n path: TPath,\r\n): FormField<TOutput, FieldPath.Resolve<TOutput, TPath>> | undefined;\r\n\r\nexport function useFormField<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n>(\r\n form: UseForm<TOutput>,\r\n path: TPath,\r\n registerConfig: Parameters<typeof form.controller.registerField<TPath>>[1],\r\n): FormField<TOutput, FieldPath.Resolve<TOutput, TPath>>;\r\n\r\nexport function useFormField<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n>(\r\n form: UseForm<TOutput>,\r\n path: TPath,\r\n registerConfig?: Parameters<typeof form.controller.registerField<TPath>>[1],\r\n) {\r\n const { controller } = form;\r\n\r\n useState(() => {\r\n let existing = controller.getField(path);\r\n\r\n if (!existing && registerConfig) {\r\n controller.registerField(path, registerConfig);\r\n }\r\n\r\n return null;\r\n });\r\n\r\n const subscribe = useCallback(\r\n (onVersionChange: () => void) => {\r\n const { events } = controller;\r\n\r\n return composeFns(\r\n events.on(\"fieldRegistered\", (_path) => {\r\n if (FieldPath.equals(_path, path)) {\r\n onVersionChange();\r\n }\r\n }),\r\n events.on(\"fieldUnregistered\", (_path) => {\r\n if (FieldPath.equals(_path, path)) {\r\n onVersionChange();\r\n }\r\n }),\r\n events.on(\"fieldValueChanged\", (changedPath) => {\r\n if (\r\n FieldPath.equals(changedPath, path) ||\r\n FieldPath.isDescendant(changedPath, path)\r\n ) {\r\n onVersionChange();\r\n }\r\n }),\r\n events.on(\"fieldTouchUpdated\", (_path) => {\r\n if (FieldPath.equals(_path, path)) {\r\n onVersionChange();\r\n }\r\n }),\r\n events.on(\"fieldDirtyUpdated\", (_path) => {\r\n if (FieldPath.equals(_path, path)) {\r\n onVersionChange();\r\n }\r\n }),\r\n events.on(\"fieldIssuesUpdated\", (_path) => {\r\n if (FieldPath.equals(_path, path)) {\r\n onVersionChange();\r\n }\r\n }),\r\n );\r\n },\r\n [controller, path],\r\n );\r\n\r\n return useSyncMutableStore(subscribe, () => controller.getField(path));\r\n}\r\n","import { FieldPath } from \"@goodie-forms/core\";\r\nimport { useCallback, useSyncExternalStore } from \"react\";\r\nimport { UseForm } from \"../hooks/useForm\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\n\r\nexport function useFieldValue<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n>(form: UseForm<TOutput>, path: TPath) {\r\n const { controller } = form;\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => {\r\n const { events } = controller;\r\n\r\n return composeFns(\r\n events.on(\"fieldRegistered\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n onStoreChange();\r\n }\r\n }),\r\n events.on(\"fieldUnregistered\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n onStoreChange();\r\n }\r\n }),\r\n events.on(\"fieldValueChanged\", (changedPath) => {\r\n if (\r\n FieldPath.equals(changedPath, path) ||\r\n FieldPath.isDescendant(changedPath, path)\r\n ) {\r\n onStoreChange();\r\n }\r\n }),\r\n );\r\n },\r\n [controller, path],\r\n );\r\n\r\n const getSnapshot = useCallback(() => {\r\n return controller.getField(path)?.value;\r\n }, [controller, path]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\r\n}\r\n","import { FieldPath } from \"@goodie-forms/core\";\r\nimport { useCallback, useSyncExternalStore } from \"react\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\nimport { UseForm } from \"./useForm\";\r\n\r\nexport function useFieldIssues<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n>(form: UseForm<TOutput>, path: TPath) {\r\n const { controller } = form;\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => {\r\n const { events } = controller;\r\n\r\n return composeFns(\r\n events.on(\"fieldRegistered\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n onStoreChange();\r\n }\r\n }),\r\n events.on(\"fieldUnregistered\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n onStoreChange();\r\n }\r\n }),\r\n events.on(\"fieldIssuesUpdated\", (fieldPath) => {\r\n if (FieldPath.equals(path, fieldPath)) {\r\n onStoreChange();\r\n }\r\n }),\r\n );\r\n },\r\n [controller, path],\r\n );\r\n\r\n const getSnapshot = useCallback(() => {\r\n return controller.getField(path)?.issues ?? [];\r\n }, [controller, path]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\r\n}\r\n","import {\r\n DeepReadonly,\r\n FieldPath,\r\n FormField,\r\n Suppliable,\r\n} from \"@goodie-forms/core\";\r\nimport { ChangeEvent, ReactNode, Ref, useEffect, useRef } from \"react\";\r\nimport { UseForm } from \"../hooks/useForm\";\r\nimport { useFormField } from \"../hooks/useFormField\";\r\nimport { composeFns } from \"../utils/composeFns\";\r\n\r\nexport interface RenderParams<TOutput extends object, TValue> {\r\n fieldProps: {\r\n ref: Ref<any | null>;\r\n\r\n name: string;\r\n\r\n value: DeepReadonly<TValue> | undefined;\r\n\r\n onChange: (event: ChangeEvent<EventTarget> | TValue) => void;\r\n onFocus: () => void;\r\n onBlur: () => void;\r\n };\r\n\r\n field: FormField<TOutput, TValue>;\r\n\r\n form: UseForm<TOutput>;\r\n}\r\n\r\nexport interface FieldRendererProps<\r\n TOutput extends object,\r\n TPath extends FieldPath.Segments,\r\n> {\r\n form: UseForm<TOutput>;\r\n path: TPath;\r\n defaultValue?: Suppliable<FieldPath.Resolve<TOutput, TPath>>;\r\n overrideInitialValue?: boolean;\r\n unregisterOnUnmount?: boolean;\r\n render: (\r\n params: RenderParams<TOutput, FieldPath.Resolve<TOutput, TPath>>,\r\n ) => ReactNode;\r\n}\r\n\r\nexport function FieldRenderer<\r\n TOutput extends object,\r\n const TPath extends FieldPath.Segments,\r\n>(props: FieldRendererProps<TOutput, TPath>) {\r\n type TValue = FieldPath.Resolve<TOutput, TPath>;\r\n\r\n const elementRef = useRef<HTMLElement>(null);\r\n\r\n const field = useFormField(props.form, props.path, {\r\n overrideInitialValue: props.overrideInitialValue ?? true,\r\n defaultValue: props.defaultValue,\r\n })!;\r\n\r\n const currentValidateMode = props.form.controller.triedSubmitting\r\n ? (props.form.hookConfigs?.revalidateMode ??\r\n props.form.hookConfigs?.validateMode)\r\n : props.form.hookConfigs?.validateMode;\r\n\r\n const renderedJsx = props.render({\r\n fieldProps: {\r\n ref: elementRef,\r\n name: field.stringPath,\r\n value: field.value,\r\n onChange(arg) {\r\n let newValue: TValue;\r\n\r\n if (typeof arg === \"object\" && \"target\" in arg) {\r\n const { target } = arg;\r\n if (target !== field.boundElement) return;\r\n if (!(\"value\" in target)) return;\r\n if (typeof target.value !== \"string\") return;\r\n newValue = target.value as TValue;\r\n } else {\r\n newValue = arg;\r\n }\r\n\r\n field.setValue(newValue, {\r\n shouldTouch: true,\r\n shouldMarkDirty: true,\r\n });\r\n },\r\n onFocus() {\r\n field.touch();\r\n },\r\n onBlur() {\r\n if (\r\n field.issues.length !== 0 ||\r\n currentValidateMode === \"onBlur\" ||\r\n currentValidateMode === \"onChange\"\r\n ) {\r\n props.form.controller.validateField(props.path);\r\n }\r\n },\r\n },\r\n field: field as any,\r\n form: props.form,\r\n });\r\n\r\n useEffect(() => {\r\n const { events } = props.form.controller;\r\n\r\n return composeFns(\r\n events.on(\"fieldValueChanged\", (_path) => {\r\n if (\r\n !FieldPath.equals(_path, props.path) &&\r\n !FieldPath.isDescendant(_path, props.path)\r\n ) {\r\n return;\r\n }\r\n\r\n if (field.issues.length !== 0 || currentValidateMode === \"onChange\") {\r\n props.form.controller.validateField(props.path);\r\n }\r\n }),\r\n );\r\n }, [currentValidateMode]);\r\n\r\n useEffect(() => {\r\n field.bindElement(elementRef.current!);\r\n\r\n return () => {\r\n if (props.unregisterOnUnmount) {\r\n props.form.controller.unregisterField(props.path);\r\n }\r\n };\r\n }, []);\r\n\r\n return <>{renderedJsx}</>;\r\n}\r\n\r\n/* ---- TESTS ---------------- */\r\n\r\n// function TestComp() {\r\n// const form = useForm<{ a?: { b: 99 } }>({});\r\n\r\n// const jsx = (\r\n// <>\r\n// <FieldRenderer\r\n// form={form}\r\n// path={form.paths.fromProxy((data) => data.a.b)}\r\n// defaultValue={() => 99 as const}\r\n// render={({ fieldProps, field }) => {\r\n// // ^?\r\n// return <input {...fieldProps} />;\r\n// }}\r\n// />\r\n\r\n// {/* defaultField olmayabilir, çünkü \"a\" nullable */}\r\n// <FieldRenderer\r\n// form={form}\r\n// path={form.paths.fromProxy((data) => data.a)}\r\n// render={({ ref, value, handlers, field }) => {\r\n// // ^?\r\n// return <></>;\r\n// }}\r\n// />\r\n\r\n// <FieldRenderer\r\n// form={form}\r\n// path={form.paths.fromStringPath(\"a.b\")}\r\n// defaultValue={() => 99 as const}\r\n// render={({ ref, value, handlers, field }) => {\r\n// // ^?\r\n// return <></>;\r\n// }}\r\n// />\r\n// </>\r\n// );\r\n// }\r\n"],"names":["useSyncMutableStore","subscribe","getValue","versionRef","useRef","subscribeWithVersion","useCallback","onStoreChange","getSnapshot","useSyncExternalStore","useForm","formConfigs","hookConfigs","controller","useState","FormController","onVersionChange","useWatchValues","useWatchIssues","useWatchEvent","eventName","listener","args","composeFns","fns","fn","useFormField","form","path","registerConfig","events","_path","FieldPath","changedPath","useFieldValue","fieldPath","useFieldIssues","FieldRenderer","props","elementRef","field","currentValidateMode","renderedJsx","arg","newValue","target","useEffect"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,16 @@
1
1
  {
2
2
  "name": "@goodie-forms/react",
3
- "version": "1.2.5-alpha",
3
+ "version": "1.3.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/iGoodie/goodie-forms"
7
+ },
8
+ "author": {
9
+ "name": "Taha Anılcan Metinyurt",
10
+ "email": "igoodie@programmer.net",
11
+ "url": "https://github.com/iGoodie"
12
+ },
13
+ "license": "CC BY-SA 4.0",
4
14
  "type": "module",
5
15
  "main": "dist/index.js",
6
16
  "types": "dist/index.d.ts",
@@ -10,6 +20,9 @@
10
20
  "default": "./dist/index.js"
11
21
  }
12
22
  },
23
+ "files": [
24
+ "dist"
25
+ ],
13
26
  "publishConfig": {
14
27
  "access": "public"
15
28
  },
@@ -18,7 +31,7 @@
18
31
  "react-dom": "^18 || ^19"
19
32
  },
20
33
  "dependencies": {
21
- "@goodie-forms/core": "1.2.5-alpha"
34
+ "@goodie-forms/core": "1.3.0"
22
35
  },
23
36
  "devDependencies": {
24
37
  "@types/react": "^19.2.9",
@@ -1,4 +0,0 @@
1
- import { FieldPath } from '../../../core/src';
2
- import { UseForm } from '../hooks/useForm';
3
- export declare function useFieldErrors<TOutput extends object, TPath extends FieldPath.Segments>(form: UseForm<TOutput>, path: TPath): import("@standard-schema/spec").StandardSchemaV1.Issue[] | undefined;
4
- //# sourceMappingURL=useFieldErrors.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useFieldErrors.d.ts","sourceRoot":"","sources":["../../src/hooks/useFieldErrors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAI3C,wBAAgB,cAAc,CAC5B,OAAO,SAAS,MAAM,EACtB,KAAK,SAAS,SAAS,CAAC,QAAQ,EAChC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,wEAqBpC"}
@@ -1,7 +0,0 @@
1
- import { FieldPath } from '../../../core/src';
2
- import { UseForm } from './useForm';
3
- /** @deprecated */
4
- export declare function useFormErrorObserver<TOutput extends object, TInclude extends FieldPath.Segments[] | undefined = undefined>(form: UseForm<TOutput>, options?: {
5
- include?: TInclude;
6
- }): Record<string, import("@standard-schema/spec").StandardSchemaV1.Issue[]>;
7
- //# sourceMappingURL=useFormErrorObserver.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useFormErrorObserver.d.ts","sourceRoot":"","sources":["../../src/hooks/useFormErrorObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAI/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,kBAAkB;AAClB,wBAAgB,oBAAoB,CAClC,OAAO,SAAS,MAAM,EACtB,QAAQ,SAAS,SAAS,CAAC,QAAQ,EAAE,GAAG,SAAS,GAAG,SAAS,EAE7D,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,EACtB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,QAAQ,CAAC;CACpB,4EA6BF"}
@@ -1,7 +0,0 @@
1
- import { FieldPath } from '../../../core/src';
2
- import { UseForm } from './useForm';
3
- /** @deprecated */
4
- export declare function useFormValuesObserver<TOutput extends object, TPaths extends FieldPath.Segments[] | undefined = undefined>(form: UseForm<TOutput>, options?: {
5
- include?: TPaths;
6
- }): import('../../../core/src').DeepPartial<TOutput>;
7
- //# sourceMappingURL=useFormValuesObserver.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useFormValuesObserver.d.ts","sourceRoot":"","sources":["../../src/hooks/useFormValuesObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,kBAAkB;AAClB,wBAAgB,qBAAqB,CACnC,OAAO,SAAS,MAAM,EACtB,MAAM,SAAS,SAAS,CAAC,QAAQ,EAAE,GAAG,SAAS,GAAG,SAAS,EAE3D,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,EACtB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,qDAmCF"}
@@ -1,5 +0,0 @@
1
- export declare function useRenderControl(): {
2
- renderCount: number;
3
- forceRerender: () => void;
4
- };
5
- //# sourceMappingURL=useRenderControl.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useRenderControl.d.ts","sourceRoot":"","sources":["../../src/hooks/useRenderControl.tsx"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB;;;EAuB/B"}
@@ -1,166 +0,0 @@
1
- import { FieldPath, FormField, NonnullFormField } from "@goodie-forms/core";
2
- import { ChangeEvent, ReactNode, Ref, useEffect, useRef } from "react";
3
- import { UseForm } from "../hooks/useForm";
4
- import { useFormField } from "../hooks/useFormField";
5
- import { composeFns } from "../utils/composeFns";
6
-
7
- export interface RenderParams<TOutput extends object, TValue> {
8
- fieldProps: {
9
- ref: Ref<any | null>;
10
-
11
- value: TValue | undefined;
12
-
13
- onChange: (event: ChangeEvent<EventTarget> | TValue) => void;
14
- onFocus: () => void;
15
- onBlur: () => void;
16
- };
17
-
18
- field: undefined extends TValue
19
- ? FormField<TOutput, TValue>
20
- : NonnullFormField<TOutput, TValue>;
21
-
22
- form: UseForm<TOutput>;
23
- }
24
-
25
- type DefaultValueProps<TValue> = undefined extends TValue
26
- ? { defaultValue?: TValue | (() => TValue) }
27
- : { defaultValue: TValue | (() => TValue) };
28
-
29
- export type FieldRendererProps<
30
- TOutput extends object,
31
- TPath extends FieldPath.Segments,
32
- > = {
33
- form: UseForm<TOutput>;
34
- path: TPath;
35
- overrideInitialValue?: boolean;
36
- unbindOnUnmount?: boolean;
37
- render: (
38
- params: RenderParams<TOutput, FieldPath.Resolve<TOutput, NoInfer<TPath>>>,
39
- ) => ReactNode;
40
- } & DefaultValueProps<FieldPath.Resolve<TOutput, NoInfer<TPath>>>;
41
-
42
- export function FieldRenderer<
43
- TOutput extends object,
44
- const TPath extends FieldPath.Segments,
45
- >(props: FieldRendererProps<TOutput, TPath>) {
46
- type TValue = FieldPath.Resolve<TOutput, TPath>;
47
-
48
- const elementRef = useRef<HTMLElement>(null);
49
-
50
- const field = useFormField(props.form, props.path, {
51
- overrideInitialValue: props.overrideInitialValue ?? true,
52
- defaultValue:
53
- typeof props.defaultValue === "function"
54
- ? (props.defaultValue as any)()
55
- : props.defaultValue,
56
- })!;
57
-
58
- const renderedJsx = props.render({
59
- fieldProps: {
60
- ref: elementRef,
61
- value: field.value,
62
- onChange(arg) {
63
- let newValue: TValue;
64
-
65
- if (typeof arg === "object" && "target" in arg) {
66
- const { target } = arg;
67
- if (target !== field.boundElement) return;
68
- if (!("value" in target)) return;
69
- if (typeof target.value !== "string") return;
70
- newValue = target.value as TValue;
71
- } else {
72
- newValue = arg;
73
- }
74
-
75
- field.setValue(newValue, {
76
- shouldTouch: true,
77
- shouldMarkDirty: true,
78
- });
79
- },
80
- onFocus() {
81
- field.touch();
82
- },
83
- onBlur() {
84
- if (
85
- props.form.hookConfigs?.validateMode === "onBlur" ||
86
- props.form.hookConfigs?.validateMode === "onChange"
87
- ) {
88
- props.form.controller.validateField(props.path);
89
- }
90
- },
91
- },
92
- field: field as any,
93
- form: props.form,
94
- });
95
-
96
- useEffect(() => {
97
- const { events } = props.form.controller;
98
-
99
- return composeFns(
100
- events.on("valueChanged", (_path) => {
101
- if (
102
- !FieldPath.equals(_path, props.path) &&
103
- !FieldPath.isDescendant(_path, props.path)
104
- ) {
105
- return;
106
- }
107
-
108
- if (props.form.hookConfigs?.validateMode === "onChange") {
109
- props.form.controller.validateField(props.path);
110
- }
111
- }),
112
- );
113
- }, []);
114
-
115
- useEffect(() => {
116
- field.bindElement(elementRef.current!);
117
-
118
- return () => {
119
- if (props.unbindOnUnmount) {
120
- props.form.controller.unbindField(props.path);
121
- }
122
- };
123
- }, []);
124
-
125
- return <>{renderedJsx}</>;
126
- }
127
-
128
- /* ---- TESTS ---------------- */
129
-
130
- // function TestComp() {
131
- // const form = useForm<{ a?: { b: 99 } }>({});
132
-
133
- // const jsx = (
134
- // <>
135
- // <FieldRenderer
136
- // form={form}
137
- // path={form.paths.fromProxy((data) => data.a.b)}
138
- // defaultValue={() => 99 as const}
139
- // render={({ fieldProps, field }) => {
140
- // // ^?
141
- // return <input {...fieldProps} />;
142
- // }}
143
- // />
144
-
145
- // {/* defaultField olmayabilir, çünkü "a" nullable */}
146
- // <FieldRenderer
147
- // form={form}
148
- // path={form.paths.fromProxy((data) => data.a)}
149
- // render={({ ref, value, handlers, field }) => {
150
- // // ^?
151
- // return <></>;
152
- // }}
153
- // />
154
-
155
- // <FieldRenderer
156
- // form={form}
157
- // path={form.paths.fromStringPath("a.b")}
158
- // defaultValue={() => 99 as const}
159
- // render={({ ref, value, handlers, field }) => {
160
- // // ^?
161
- // return <></>;
162
- // }}
163
- // />
164
- // </>
165
- // );
166
- // }
@@ -1,31 +0,0 @@
1
- import { FieldPath } from "@goodie-forms/core";
2
- import { useEffect } from "react";
3
- import { UseForm } from "../hooks/useForm";
4
- import { useRenderControl } from "../hooks/useRenderControl";
5
- import { composeFns } from "../utils/composeFns";
6
-
7
- export function useFieldErrors<
8
- TOutput extends object,
9
- TPath extends FieldPath.Segments,
10
- >(form: UseForm<TOutput>, path: TPath) {
11
- const renderControl = useRenderControl();
12
-
13
- const issues = form.controller.getField(path)?.issues;
14
-
15
- useEffect(() => {
16
- return composeFns(
17
- form.controller.events.on("fieldBound", (fieldPath) => {
18
- if (FieldPath.equals(path, fieldPath)) {
19
- renderControl.forceRerender();
20
- }
21
- }),
22
- form.controller.events.on("fieldIssuesUpdated", (fieldPath) => {
23
- if (FieldPath.equals(path, fieldPath)) {
24
- renderControl.forceRerender();
25
- }
26
- }),
27
- );
28
- }, []);
29
-
30
- return issues;
31
- }
@@ -1,31 +0,0 @@
1
- import { FieldPath } from "@goodie-forms/core";
2
- import { useEffect } from "react";
3
- import { UseForm } from "../hooks/useForm";
4
- import { useRenderControl } from "../hooks/useRenderControl";
5
- import { composeFns } from "../utils/composeFns";
6
-
7
- export function useFieldValue<
8
- TOutput extends object,
9
- TPath extends FieldPath.Segments,
10
- >(form: UseForm<TOutput>, path: TPath) {
11
- const renderControl = useRenderControl();
12
-
13
- const value = form.controller.getField(path)?.value;
14
-
15
- useEffect(() => {
16
- return composeFns(
17
- form.controller.events.on("fieldBound", (fieldPath) => {
18
- if (FieldPath.equals(path, fieldPath)) {
19
- renderControl.forceRerender();
20
- }
21
- }),
22
- form.controller.events.on("valueChanged", (fieldPath) => {
23
- if (FieldPath.equals(path, fieldPath)) {
24
- renderControl.forceRerender();
25
- }
26
- }),
27
- );
28
- }, []);
29
-
30
- return value;
31
- }
@@ -1,50 +0,0 @@
1
- import { FieldPathBuilder, FormController } from "@goodie-forms/core";
2
- import { useEffect, useState } from "react";
3
- import { useRenderControl } from "../hooks/useRenderControl";
4
- import { composeFns } from "../utils/composeFns";
5
-
6
- export function useForm<TOutput extends object>(
7
- formConfigs: FormController.Configs<TOutput>,
8
- hookConfigs?: {
9
- validateMode?: "onChange" | "onBlur" | "onSubmit";
10
- revalidateMode?: "onChange" | "onBlur" | "onSubmit";
11
- watchIssues?: boolean;
12
- watchValues?: boolean;
13
- },
14
- ) {
15
- const [controller] = useState(() => new FormController(formConfigs));
16
- const [paths] = useState(() => new FieldPathBuilder<TOutput>());
17
-
18
- const renderControl = useRenderControl();
19
-
20
- useEffect(() => {
21
- const noop = () => {};
22
-
23
- return composeFns(
24
- controller.events.on("submissionStatusChange", () => {
25
- renderControl.forceRerender();
26
- }),
27
- hookConfigs?.watchIssues
28
- ? controller.events.on("fieldIssuesUpdated", () =>
29
- renderControl.forceRerender(),
30
- )
31
- : noop,
32
- hookConfigs?.watchValues
33
- ? controller.events.on("valueChanged", () =>
34
- renderControl.forceRerender(),
35
- )
36
- : noop,
37
- );
38
- }, [controller]);
39
-
40
- return {
41
- formConfigs,
42
- paths,
43
- hookConfigs,
44
- controller,
45
- };
46
- }
47
-
48
- export type UseForm<TOutput extends object> = ReturnType<
49
- typeof useForm<TOutput>
50
- >;
@@ -1,45 +0,0 @@
1
- import { FieldPath } from "@goodie-forms/core";
2
- import { groupBy } from "../utils/groupBy";
3
- import { useEffect } from "react";
4
- import { composeFns } from "../utils/composeFns";
5
- import type { UseForm } from "./useForm";
6
- import { useRenderControl } from "./useRenderControl";
7
-
8
- /** @deprecated */
9
- export function useFormErrorObserver<
10
- TOutput extends object,
11
- TInclude extends FieldPath.Segments[] | undefined = undefined,
12
- >(
13
- form: UseForm<TOutput>,
14
- options?: {
15
- include?: TInclude;
16
- },
17
- ) {
18
- const renderControl = useRenderControl();
19
-
20
- const observedIssues = form.controller._issues.filter((issue) => {
21
- if (options?.include == null) return true;
22
- const normalizedIssuePath = FieldPath.normalize(issue.path);
23
- return options.include.some((path) =>
24
- FieldPath.equals(path, normalizedIssuePath),
25
- );
26
- });
27
-
28
- useEffect(() => {
29
- const { events } = form.controller;
30
-
31
- return composeFns(
32
- events.on("fieldIssuesUpdated", (path) => {
33
- if (options?.include?.includes?.(path) ?? true) {
34
- renderControl.forceRerender();
35
- }
36
- }),
37
- );
38
- }, []);
39
-
40
- return groupBy(observedIssues, (issue) =>
41
- issue.path == null
42
- ? "$"
43
- : FieldPath.toStringPath(FieldPath.normalize(issue.path)),
44
- );
45
- }
@@ -1,63 +0,0 @@
1
- import { FieldPath } from "@goodie-forms/core";
2
- import { useEffect, useState } from "react";
3
- import { UseForm } from "../hooks/useForm";
4
- import { useRenderControl } from "../hooks/useRenderControl";
5
- import { composeFns } from "../utils/composeFns";
6
-
7
- export function useFormField<
8
- TOutput extends object,
9
- TPath extends FieldPath.Segments,
10
- >(
11
- form: UseForm<TOutput>,
12
- path: TPath,
13
- bindingConfig?: Parameters<typeof form.controller.bindField<TPath>>[1],
14
- ) {
15
- const renderControl = useRenderControl();
16
-
17
- const [field, setField] = useState(() => {
18
- let field = form.controller.getField(path);
19
- if (field == null && bindingConfig != null) {
20
- field = form.controller.bindField(path, bindingConfig);
21
- }
22
- return field;
23
- });
24
-
25
- useEffect(() => {
26
- const { events } = form.controller;
27
-
28
- setField(form.controller.getField(path));
29
-
30
- return composeFns(
31
- events.on("fieldBound", (_path) => {
32
- if (!FieldPath.equals(_path, path)) return;
33
- setField(form.controller.getField(path));
34
- }),
35
- events.on("fieldUnbound", (_path) => {
36
- if (!FieldPath.equals(_path, path)) return;
37
- setField(undefined);
38
- }),
39
- events.on("valueChanged", (changedPath) => {
40
- if (
41
- FieldPath.equals(changedPath, path) ||
42
- FieldPath.isDescendant(changedPath, path)
43
- ) {
44
- renderControl.forceRerender();
45
- }
46
- }),
47
- events.on("fieldTouchUpdated", (_path) => {
48
- if (!FieldPath.equals(_path, path)) return;
49
- renderControl.forceRerender();
50
- }),
51
- events.on("fieldDirtyUpdated", (_path) => {
52
- if (!FieldPath.equals(_path, path)) return;
53
- renderControl.forceRerender();
54
- }),
55
- events.on("fieldIssuesUpdated", (_path) => {
56
- if (!FieldPath.equals(_path, path)) return;
57
- renderControl.forceRerender();
58
- }),
59
- );
60
- }, []);
61
-
62
- return field;
63
- }
@@ -1,50 +0,0 @@
1
- import { FieldPath } from "@goodie-forms/core";
2
- import { useEffect } from "react";
3
- import { composeFns } from "../utils/composeFns";
4
- import type { UseForm } from "./useForm";
5
- import { useRenderControl } from "./useRenderControl";
6
-
7
- /** @deprecated */
8
- export function useFormValuesObserver<
9
- TOutput extends object,
10
- TPaths extends FieldPath.Segments[] | undefined = undefined,
11
- >(
12
- form: UseForm<TOutput>,
13
- options?: {
14
- include?: TPaths;
15
- },
16
- ) {
17
- const renderControl = useRenderControl();
18
-
19
- const observedValues =
20
- options?.include == null
21
- ? form.controller._data
22
- : options.include.reduce((data, path) => {
23
- const value = FieldPath.getValue(
24
- form.controller._data as TOutput,
25
- path,
26
- )!;
27
- FieldPath.setValue(data, path, value);
28
- return data;
29
- }, {} as TOutput);
30
-
31
- useEffect(() => {
32
- const { events } = form.controller;
33
-
34
- return composeFns(
35
- events.on("valueChanged", (changedPath) => {
36
- const watchingChange =
37
- options?.include == null
38
- ? true
39
- : options.include.some(
40
- (path) =>
41
- FieldPath.equals(path, changedPath) ||
42
- FieldPath.isDescendant(path, changedPath),
43
- );
44
- if (watchingChange) renderControl.forceRerender();
45
- }),
46
- );
47
- }, []);
48
-
49
- return observedValues;
50
- }
@@ -1,26 +0,0 @@
1
- import { startTransition, useRef, useState } from "react";
2
-
3
- export function useRenderControl() {
4
- const [, rerender] = useState(0);
5
- const renderCount = useRef(0);
6
- const renderScheduled = useRef(false);
7
- renderCount.current++;
8
-
9
- const scheduleRerender = () => {
10
- if (renderScheduled.current) return;
11
- renderScheduled.current = true;
12
-
13
- queueMicrotask(() => {
14
- startTransition(() => {
15
- rerender((i) => i + 1);
16
- });
17
-
18
- renderScheduled.current = false;
19
- });
20
- };
21
-
22
- return {
23
- renderCount: renderCount.current,
24
- forceRerender: scheduleRerender,
25
- };
26
- }