diff --git a/packages/core/src/orchestrator/renderer-orchestrator.ts b/packages/core/src/orchestrator/renderer-orchestrator.ts index 2b92954..4342b98 100644 --- a/packages/core/src/orchestrator/renderer-orchestrator.ts +++ b/packages/core/src/orchestrator/renderer-orchestrator.ts @@ -8,19 +8,16 @@ import type { RuntimeAdapter, ComponentSpec, DebugContextValue } from '../runtime/types'; import type { FormAdapter } from '../forms/types'; import type { MiddlewareFn, MiddlewareContext } from '../middleware/types'; -import { getComponentSpec } from '../registries/component-registry'; import { getRendererForType } from '../registries/renderer-registry'; import { applyMiddlewares } from '../middleware/types'; import { processValue } from '../expressions/template-processor'; import { createDefaultResolver } from '../expressions/variable-resolver'; -import { FormSchema } from '../schema/schema-types'; /** * Resolution result - successful component resolution */ export interface ResolutionSuccess { - renderSpec: ComponentSpec; - componentToRender: ComponentSpec; + componentSpec: ComponentSpec; rendererFn: ReturnType; } @@ -48,18 +45,25 @@ export interface FactorySetupResult { * Resolve component spec from schema */ export function resolveSpec( - schema: FormSchema, + schema: any, componentKey: string, components: Record, + customComponents?: Record, localRenderers?: Partial>, debugEnabled?: boolean ): ResolutionResult { const componentName = schema['x-component'] || componentKey; - // components already has provider components merged in the factory - // Pass components as globalComponents (includes provider components) and undefined as localComponents - const renderSpec = getComponentSpec(componentName, components, undefined, debugEnabled); - - if (!renderSpec) { + + const isCustomComponent = schema['x-custom'] === true; + let componentSpec = null; + + if (isCustomComponent && customComponents) { + componentSpec = customComponents[componentKey]; + } else { + componentSpec = components[componentName]; + } + + if (!componentSpec) { if (debugEnabled) { console.warn(`Component not found: ${componentName}`); } @@ -67,7 +71,7 @@ export function resolveSpec( return null; } - const componentType = renderSpec.type || 'field'; + const componentType = componentSpec.type || 'field'; const rendererFn = getRendererForType( componentType, undefined, @@ -76,8 +80,7 @@ export function resolveSpec( ); return { - renderSpec, - componentToRender: renderSpec, + componentSpec, rendererFn, }; } @@ -97,18 +100,18 @@ export function createRendererOrchestrator( namePath: string[] = [], isDirectRootProperty: boolean = false ): any { - const { + const { components, customComponents, - renderers: localRenderers, - externalContext, - state, - middlewares, + renderers: localRenderers, + externalContext, + state, + middlewares, onSubmit, debug, formAdapter } = getFactorySetup(); - + // Process schema with template expressions BEFORE extracting props // This ensures that $formValues.* and $externalContext.* are replaced // in any property of the schema (x-ui, x-content, x-component-props, etc.) @@ -116,12 +119,12 @@ export function createRendererOrchestrator( externalContext, formValues: state, }); - + const processedSchema = processValue(schema, resolver, { externalContext, formValues: state, }) as any; - + // Check visibility via x-ui.visible // If visible === false, don't render this component (and its children) // By default, visible is true @@ -130,97 +133,21 @@ export function createRendererOrchestrator( return null; } - // Check for x-custom flag - if true, use custom component instead - const isCustomComponent = processedSchema['x-custom'] === true; - - if (isCustomComponent && customComponents) { - // Look up custom component by the property key name - const customSpec = customComponents[componentKey]; - - if (customSpec) { - // Get renderer for the custom component type - const customRendererFn = getRendererForType( - customSpec.type || 'field', - undefined, - localRenderers as any, - debug?.isEnabled - ); - - // Build props for custom component - const customProps = { - ...customSpec.defaultProps, - // Injected for E2E: identifies component key in DOM - 'data-test-id': `${componentKey}`, - // Pass the original schema so custom component can access x-component-props, etc. - schema: processedSchema, - // Pass the component key - componentKey, - // Pass the name path for form binding - name: parentProps.name ? `${parentProps.name}.${componentKey}` : componentKey, - // Pass external context - externalContext, - // Pass x-component-props if present - ...(processedSchema['x-component-props'] || {}), - }; - - // Render children if schema has properties - const customChildren: any[] = []; - if (processedSchema.properties && typeof processedSchema.properties === 'object') { - const childParentProps = { - ...customProps, - name: customProps.name, - }; - - const sortedEntries = Object.entries(processedSchema.properties).sort( - ([, a], [, b]) => { - const orderA = (a as any)?.['x-ui']?.order ?? Infinity; - const orderB = (b as any)?.['x-ui']?.order ?? Infinity; - return orderA - orderB; - } - ); - - for (const [key, childSchema] of sortedEntries) { - const childResult = render(key, childSchema as any, childParentProps, namePath, false); - if (childResult !== null && childResult !== undefined) { - customChildren.push(childResult); - } - } - } - - // Render the custom component - return customRendererFn(customSpec, customProps, runtime, customChildren.length > 0 ? customChildren : undefined); - } else { - // Custom component not found - log warning and fall back to normal rendering - if (debug?.isEnabled) { - console.warn(`Custom component not found for key: ${componentKey}. Falling back to standard component.`); - } - } - } - // Parse schema (now using processed schema) const { 'x-component-props': componentProps = {} } = processedSchema; - // const componentSpec = getComponentSpec(componentKey, components, undefined, debug?.isEnabled); - - // if(!componentSpec) { - // if (debug?.isEnabled) { - // console.warn(`Component not found: ${componentKey}`); - // } - // // Return error element (framework adapter will handle) - // return null; - // } - // Resolve component and renderer // Use processedSchema for x-component resolution (in case x-component has templates) // components already has provider components merged in the factory const resolution = resolveSpec( - processedSchema, - componentKey, - components, - localRenderers, + processedSchema, + componentKey, + components, + customComponents, + localRenderers, debug?.isEnabled ); - + // Check if resolution failed if (!resolution || resolution === null) { // Return error component (framework adapter will provide) @@ -228,15 +155,14 @@ export function createRendererOrchestrator( } // Extract successful resolution - const { renderSpec, componentToRender, rendererFn } = resolution as ResolutionSuccess; + const { componentSpec, rendererFn } = resolution as ResolutionSuccess; // Construct name path for nested fields // ONLY components with type: 'field' are included in the name path // EXCEPTION: componentKeys that are direct properties of root schema (isDirectRootProperty) // All other components (containers, FormContainer, content, etc.) are ignored - const componentType = renderSpec.type || 'field'; - const isFieldComponent = componentType === 'field'; - + const isFieldComponent = componentSpec.type === 'field'; + // If field OR direct root property: add componentKey to name path // If not field and not root property: keep parentProps.name (don't add this component's key) const shouldIncludeInPath = isFieldComponent || isDirectRootProperty; @@ -246,10 +172,11 @@ export function createRendererOrchestrator( // Props Processing (using processedSchema) const baseProps = { - ...renderSpec.defaultProps, + ...componentSpec.defaultProps, ...parentProps, // Injected for E2E: identifies component key in DOM 'data-test-id': `${componentKey}`, + schema, // Add name prop ONLY for field components ...(isFieldComponent && currentName ? { name: currentName } : {}), ...(Object.keys(componentProps).length > 0 ? { 'x-component-props': componentProps } : {}), @@ -271,7 +198,7 @@ export function createRendererOrchestrator( debug, formAdapter, }; - + const mergedProps = applyMiddlewares(baseProps, processedSchema, middlewares, middlewareContext); // Render children if schema has properties (use processedSchema) @@ -292,7 +219,7 @@ export function createRendererOrchestrator( return orderA - orderB; } ); - + for (const [key, childSchema] of sortedEntries) { // If this component is the root (FormContainer) and has no name, // then its direct children are root properties and should be included in name path @@ -305,7 +232,7 @@ export function createRendererOrchestrator( } // Final Rendering using renderer function with children - return rendererFn(componentToRender, mergedProps, runtime, children.length > 0 ? children : undefined); + return rendererFn(componentSpec, mergedProps, runtime, children.length > 0 ? children : undefined); }; } diff --git a/packages/core/src/registries/component-registry.ts b/packages/core/src/registries/component-registry.ts index 79e1501..703a2b4 100644 --- a/packages/core/src/registries/component-registry.ts +++ b/packages/core/src/registries/component-registry.ts @@ -46,81 +46,9 @@ export function setFactoryDefaultComponents( factoryDefaultComponents = components; } -/** - * Get factory default components - */ export function getFactoryDefaultComponents(): Record { return factoryDefaultComponents; -} - -/** - * Get unified component registry - * - * Priority order: local > global > registry overrides > factory defaults - */ -export function getComponentRegistry( - globalComponents?: Record, - localComponents?: Record -): Record { - // Start with factory default components - const merged: Record = { ...factoryDefaultComponents }; - - // Apply global components (from provider) - if (globalComponents) { - Object.keys(globalComponents).forEach(componentName => { - const component = globalComponents[componentName]; - if (component) { - merged[componentName] = component; - } - }); - } - - // Apply local components (maximum priority) - if (localComponents) { - Object.keys(localComponents).forEach(componentName => { - const component = localComponents[componentName]; - if (component) { - merged[componentName] = component; - } - }); - } - - return merged; -} - -/** - * Get effective component configuration - * Includes fallback logic and debug logging - */ -export function getComponentSpec( - componentName: string, - globalComponents?: Record, - localComponents?: Record, - debugEnabled?: boolean -): ComponentSpec | null { - // Local components have maximum priority - if (localComponents?.[componentName]) { - if (debugEnabled) { - console.log(`Component resolved from local: ${componentName}`); - } - return localComponents[componentName]; - } - - const globalRegistry = getComponentRegistry(globalComponents, localComponents); - - if (globalRegistry[componentName]) { - if (debugEnabled) { - console.log(`Component resolved from registry: ${componentName}`); - } - return globalRegistry[componentName]; - } - - if (debugEnabled) { - console.warn(`Component not found: ${componentName}`); - } - - return null; -} +}; /** * Create a component spec from a factory function @@ -128,7 +56,7 @@ export function getComponentSpec( export function createComponentSpec(config: { id: string; factory: ComponentSpec['factory']; - type?: ComponentType; + type: ComponentType; displayName?: string; defaultProps?: Record; }): ComponentSpec { diff --git a/packages/core/src/runtime/types.ts b/packages/core/src/runtime/types.ts index 19b8a46..b9c4789 100644 --- a/packages/core/src/runtime/types.ts +++ b/packages/core/src/runtime/types.ts @@ -17,7 +17,7 @@ export interface ComponentSpec { /** Component identifier */ id: string; /** Component type (field, container, etc.) */ - type?: ComponentType; + type: ComponentType; /** Display name for debugging */ displayName?: string; /** Default props to apply */ diff --git a/packages/factories/react/src/defaults/register-default-components.ts b/packages/factories/react/src/defaults/register-default-components.ts new file mode 100644 index 0000000..ee0ae99 --- /dev/null +++ b/packages/factories/react/src/defaults/register-default-components.ts @@ -0,0 +1,105 @@ +import { ComponentSpec, createComponentSpec } from "@schepta/core"; +import { + DefaultFieldWrapper, + DefaultFormContainer, + DefaultFormField, + DefaultFormSectionContainer, + DefaultFormSectionGroup, + DefaultFormSectionGroupContainer, + DefaultFormSectionTitle, + DefaultInputAutocomplete, + DefaultInputCheckbox, + DefaultInputDate, + DefaultInputNumber, + DefaultInputPhone, + DefaultInputSelect, + DefaultInputText, + DefaultInputTextarea, + DefaultSubmitButton +} from "../components"; + +export const defaultComponents: Record = { + FormContainer: createComponentSpec({ + id: 'FormContainer', + type: 'container', + factory: () => DefaultFormContainer, + }), + SubmitButton: createComponentSpec({ + id: 'SubmitButton', + type: 'content', + factory: () => DefaultSubmitButton, + }), + FieldWrapper: createComponentSpec({ + id: 'FieldWrapper', + type: 'field-wrapper', + factory: () => DefaultFieldWrapper, + }), + // Input components + InputText: createComponentSpec({ + id: 'InputText', + type: 'field', + factory: () => DefaultInputText, + }), + InputSelect: createComponentSpec({ + id: 'InputSelect', + type: 'field', + factory: () => DefaultInputSelect, + }), + InputCheckbox: createComponentSpec({ + id: 'InputCheckbox', + type: 'field', + factory: () => DefaultInputCheckbox, + }), + InputDate: createComponentSpec({ + id: 'InputDate', + type: 'field', + factory: () => DefaultInputDate, + }), + InputPhone: createComponentSpec({ + id: 'InputPhone', + type: 'field', + factory: () => DefaultInputPhone, + }), + InputAutocomplete: createComponentSpec({ + id: 'InputAutocomplete', + type: 'field', + factory: () => DefaultInputAutocomplete, + }), + InputTextarea: createComponentSpec({ + id: 'InputTextarea', + type: 'field', + factory: () => DefaultInputTextarea, + }), + InputNumber: createComponentSpec({ + id: 'InputNumber', + type: 'field', + factory: () => DefaultInputNumber, + }), + // Container components + FormField: createComponentSpec({ + id: 'FormField', + type: 'container', + factory: () => DefaultFormField, + }), + FormSectionContainer: createComponentSpec({ + id: 'FormSectionContainer', + type: 'container', + factory: () => DefaultFormSectionContainer, + }), + FormSectionTitle: createComponentSpec({ + id: 'FormSectionTitle', + type: 'content', + factory: () => DefaultFormSectionTitle, + }), + FormSectionGroup: createComponentSpec({ + id: 'FormSectionGroup', + type: 'container', + factory: () => DefaultFormSectionGroup, + }), + FormSectionGroupContainer: createComponentSpec({ + id: 'FormSectionGroupContainer', + type: 'container', + factory: () => DefaultFormSectionGroupContainer, + }), + +} \ No newline at end of file diff --git a/packages/factories/react/src/form-factory.tsx b/packages/factories/react/src/form-factory.tsx index a285d94..00655e0 100644 --- a/packages/factories/react/src/form-factory.tsx +++ b/packages/factories/react/src/form-factory.tsx @@ -1,140 +1,50 @@ /** * React Form Factory - * + * * Factory component for rendering forms from JSON schemas. */ -import { useMemo, forwardRef, useImperativeHandle } from 'react'; -import type { FormSchema, ComponentSpec, MiddlewareFn, FormAdapter } from '@schepta/core'; -import { createReactRuntimeAdapter } from '@schepta/adapter-react'; -import { - createRendererOrchestrator, +import { useMemo, forwardRef, useImperativeHandle } from "react"; +import type { + FormSchema, + ComponentSpec, + MiddlewareFn, + FormAdapter, +} from "@schepta/core"; +import { createReactRuntimeAdapter } from "@schepta/adapter-react"; +import { + createRendererOrchestrator, type FactorySetupResult, setFactoryDefaultComponents, - createComponentSpec, -} from '@schepta/core'; -import { createTemplateExpressionMiddleware } from '@schepta/core'; -import { FormRenderer } from './form-renderer'; -import { useMergedScheptaConfig } from './hooks/use-merged-config'; -import { useScheptaForm } from './hooks/use-schepta-form'; -import { useSchemaValidation } from './hooks/use-schema-validation'; -import { createDebugContext } from './utils/debug'; -import { createFieldRenderer } from './renderers/field-renderer'; -import formSchemaDefinition from '../../src/schemas/form-schema.json'; -import { ScheptaFormProvider } from './context/schepta-form-context'; +} from "@schepta/core"; +import { createTemplateExpressionMiddleware } from "@schepta/core"; +import { FormRenderer } from "./form-renderer"; +import { useMergedScheptaConfig } from "./hooks/use-merged-config"; +import { useScheptaForm } from "./hooks/use-schepta-form"; +import { useSchemaValidation } from "./hooks/use-schema-validation"; +import { createDebugContext } from "./utils/debug"; +import { createFieldRenderer } from "./renderers/field-renderer"; +import formSchemaDefinition from "../../src/schemas/form-schema.json"; +import { ScheptaFormProvider } from "./context/schepta-form-context"; import { - DefaultFormContainer, DefaultSubmitButton, DefaultFieldWrapper, - DefaultInputText, - DefaultInputSelect, - DefaultInputCheckbox, - DefaultInputDate, - DefaultInputPhone, - DefaultInputAutocomplete, - DefaultInputTextarea, - DefaultInputNumber, - DefaultFormField, - DefaultFormSectionContainer, - DefaultFormSectionTitle, - DefaultFormSectionGroup, - DefaultFormSectionGroupContainer, type SubmitButtonComponentType, type FieldWrapperType, -} from './components'; +} from "./components"; +import { defaultComponents } from "./defaults/register-default-components"; // Register factory default components (called once on module load) -setFactoryDefaultComponents({ - FormContainer: createComponentSpec({ - id: 'FormContainer', - type: 'container', - factory: () => DefaultFormContainer, - }), - SubmitButton: createComponentSpec({ - id: 'SubmitButton', - type: 'content', - factory: () => DefaultSubmitButton, - }), - FieldWrapper: createComponentSpec({ - id: 'FieldWrapper', - type: 'field-wrapper', - factory: () => DefaultFieldWrapper, - }), - // Input components - InputText: createComponentSpec({ - id: 'InputText', - type: 'field', - factory: () => DefaultInputText, - }), - InputSelect: createComponentSpec({ - id: 'InputSelect', - type: 'field', - factory: () => DefaultInputSelect, - }), - InputCheckbox: createComponentSpec({ - id: 'InputCheckbox', - type: 'field', - factory: () => DefaultInputCheckbox, - }), - InputDate: createComponentSpec({ - id: 'InputDate', - type: 'field', - factory: () => DefaultInputDate, - }), - InputPhone: createComponentSpec({ - id: 'InputPhone', - type: 'field', - factory: () => DefaultInputPhone, - }), - InputAutocomplete: createComponentSpec({ - id: 'InputAutocomplete', - type: 'field', - factory: () => DefaultInputAutocomplete, - }), - InputTextarea: createComponentSpec({ - id: 'InputTextarea', - type: 'field', - factory: () => DefaultInputTextarea, - }), - InputNumber: createComponentSpec({ - id: 'InputNumber', - type: 'field', - factory: () => DefaultInputNumber, - }), - // Container components - FormField: createComponentSpec({ - id: 'FormField', - type: 'container', - factory: () => DefaultFormField, - }), - FormSectionContainer: createComponentSpec({ - id: 'FormSectionContainer', - type: 'container', - factory: () => DefaultFormSectionContainer, - }), - FormSectionTitle: createComponentSpec({ - id: 'FormSectionTitle', - type: 'content', - factory: () => DefaultFormSectionTitle, - }), - FormSectionGroup: createComponentSpec({ - id: 'FormSectionGroup', - type: 'container', - factory: () => DefaultFormSectionGroup, - }), - FormSectionGroupContainer: createComponentSpec({ - id: 'FormSectionGroupContainer', - type: 'container', - factory: () => DefaultFormSectionGroupContainer, - }), -}); +setFactoryDefaultComponents(defaultComponents); /** * Ref interface for external form control */ export interface FormFactoryRef { /** Submit the form with the provided handler */ - submit: (onSubmit: (values: Record) => void | Promise) => void; + submit: ( + onSubmit: (values: Record) => void | Promise + ) => void; /** Reset form to initial or provided values */ reset: (values?: Record) => void; /** Get current form values */ @@ -154,151 +64,176 @@ export interface FormFactoryProps { debug?: boolean; } -export const FormFactory = forwardRef(function FormFactory({ - schema, - components, - customComponents, - renderers, - externalContext, - middlewares, - adapter: providedAdapter, - initialValues, - onSubmit, - debug = false, -}: FormFactoryProps, ref) { - // Validate schema instance before rendering - const validation = useSchemaValidation(schema, { - formSchema: formSchemaDefinition, - }); - - // Merge provider config with local props - const mergedConfig = useMergedScheptaConfig({ - components, - customComponents, - renderers, - externalContext, - middlewares, - debug, - }); - - const { formAdapter, formValues, reset } = useScheptaForm(schema, { - initialValues, - adapter: providedAdapter, - }); +export const FormFactory = forwardRef( + function FormFactory( + { + schema, + components, + customComponents, + renderers, + externalContext, + middlewares, + adapter: providedAdapter, + initialValues, + onSubmit, + debug = false, + }: FormFactoryProps, + ref + ) { + // Validate schema instance before rendering + const validation = useSchemaValidation(schema, { + formSchema: formSchemaDefinition, + }); + + // Merge provider config with local props + const mergedConfig = useMergedScheptaConfig({ + components, + customComponents, + renderers, + externalContext, + middlewares, + debug, + }); + + const { formAdapter, formValues, reset } = useScheptaForm(schema, { + initialValues, + adapter: providedAdapter, + }); + + // Expose form control methods via ref for external submit scenarios + useImperativeHandle( + ref, + () => ({ + submit: (submitFn) => formAdapter.handleSubmit(submitFn)(), + reset: (values) => reset(values), + getValues: () => formAdapter.getValues(), + }), + [formAdapter, reset] + ); - // Expose form control methods via ref for external submit scenarios - useImperativeHandle(ref, () => ({ - submit: (submitFn) => formAdapter.handleSubmit(submitFn)(), - reset: (values) => reset(values), - getValues: () => formAdapter.getValues(), - }), [formAdapter, reset]); + // Create runtime adapter + const runtime = useMemo(() => createReactRuntimeAdapter(), []); + + // If schema validation failed, render error UI + if (!validation.valid) { + return ( +
+

+ Schema Validation Error +

+
+            {validation.formattedErrors}
+          
+
+ ); + } + + // Get root component key from schema + const rootComponentKey = (schema as any)["x-component"] || "FormContainer"; + + // Resolve SubmitButton component from registry (provider or local) or use default + const SubmitButtonComponent = useMemo((): SubmitButtonComponentType => { + const customComponent = mergedConfig.components.SubmitButton?.factory?.( + {}, + runtime + ); + return ( + (customComponent as SubmitButtonComponentType) || DefaultSubmitButton + ); + }, [mergedConfig.components.SubmitButton, runtime]); + + // Resolve FieldWrapper component from registry (provider or local) or use default + const FieldWrapperComponent = useMemo((): FieldWrapperType => { + const customComponent = mergedConfig.components.FieldWrapper?.factory?.( + {}, + runtime + ); + return (customComponent as FieldWrapperType) || DefaultFieldWrapper; + }, [mergedConfig.components.FieldWrapper, runtime]); + + // Create renderer orchestrator + const renderer = useMemo(() => { + const getFactorySetup = (): FactorySetupResult => { + // Create debug context + const debugContext = createDebugContext(mergedConfig.debug); + + // Create custom renderers with field renderer (passing resolved FieldWrapper) + const customRenderers = { + ...mergedConfig.renderers, + field: createFieldRenderer({ FieldWrapper: FieldWrapperComponent }), + }; + + // Create template expression middleware with current form values (always first) + const templateMiddleware = createTemplateExpressionMiddleware({ + externalContext: mergedConfig.externalContext, + formValues, + debug: debugContext, + }); + + // Build middlewares: template middleware first, then provider, then local + const updatedMiddlewares = [ + templateMiddleware, + ...mergedConfig.baseMiddlewares, + ]; + + return { + components: mergedConfig.components, + customComponents: mergedConfig.customComponents, + renderers: customRenderers, + externalContext: { + ...mergedConfig.externalContext, + }, + state: formValues, + middlewares: updatedMiddlewares, + onSubmit, + debug: debugContext, + formAdapter, + }; + }; - // Create runtime adapter - const runtime = useMemo(() => createReactRuntimeAdapter(), []); + return createRendererOrchestrator(getFactorySetup, runtime); + }, [ + mergedConfig.components, + mergedConfig.customComponents, + mergedConfig.renderers, + mergedConfig.externalContext, + mergedConfig.baseMiddlewares, + mergedConfig.debug, + formAdapter, + runtime, + onSubmit, + formValues, + SubmitButtonComponent, + FieldWrapperComponent, + ]); - // If schema validation failed, render error UI - if (!validation.valid) { return ( -
-

- Schema Validation Error -

-
-          {validation.formattedErrors}
-        
-
+ + + ); } - - // Get root component key from schema - const rootComponentKey = (schema as any)['x-component'] || 'FormContainer'; - - // Resolve SubmitButton component from registry (provider or local) or use default - const SubmitButtonComponent = useMemo((): SubmitButtonComponentType => { - const customComponent = mergedConfig.components.SubmitButton?.factory?.({}, runtime); - return (customComponent as SubmitButtonComponentType) || DefaultSubmitButton; - }, [mergedConfig.components.SubmitButton, runtime]); - - // Resolve FieldWrapper component from registry (provider or local) or use default - const FieldWrapperComponent = useMemo((): FieldWrapperType => { - const customComponent = mergedConfig.components.FieldWrapper?.factory?.({}, runtime); - return (customComponent as FieldWrapperType) || DefaultFieldWrapper; - }, [mergedConfig.components.FieldWrapper, runtime]); - - // Create renderer orchestrator - const renderer = useMemo(() => { - const getFactorySetup = (): FactorySetupResult => { - // Create debug context - const debugContext = createDebugContext(mergedConfig.debug); - - // Create custom renderers with field renderer (passing resolved FieldWrapper) - const customRenderers = { - ...mergedConfig.renderers, - field: createFieldRenderer({ FieldWrapper: FieldWrapperComponent }), - }; - - // Create template expression middleware with current form values (always first) - const templateMiddleware = createTemplateExpressionMiddleware({ - externalContext: mergedConfig.externalContext, - formValues, - debug: debugContext, - }); - - // Build middlewares: template middleware first, then provider, then local - const updatedMiddlewares = [ - templateMiddleware, - ...mergedConfig.baseMiddlewares, - ]; - - return { - components: mergedConfig.components, - customComponents: mergedConfig.customComponents, - renderers: customRenderers, - externalContext: { - ...mergedConfig.externalContext, - }, - state: formValues, - middlewares: updatedMiddlewares, - onSubmit, - debug: debugContext, - formAdapter, - }; - }; - - return createRendererOrchestrator(getFactorySetup, runtime); - }, [ - mergedConfig.components, - mergedConfig.customComponents, - mergedConfig.renderers, - mergedConfig.externalContext, - mergedConfig.baseMiddlewares, - mergedConfig.debug, - formAdapter, - runtime, - onSubmit, - formValues, - SubmitButtonComponent, - FieldWrapperComponent, - ]); - - return ( - - - - ); -}); +); diff --git a/packages/factories/react/src/hooks/use-merged-config.ts b/packages/factories/react/src/hooks/use-merged-config.ts index 0e6e0fd..2f25a49 100644 --- a/packages/factories/react/src/hooks/use-merged-config.ts +++ b/packages/factories/react/src/hooks/use-merged-config.ts @@ -6,7 +6,7 @@ */ import { useMemo } from 'react'; -import type { ComponentSpec, MiddlewareFn } from '@schepta/core'; +import { getFactoryDefaultComponents, type ComponentSpec, type MiddlewareFn } from '@schepta/core'; import { useScheptaContext } from '@schepta/adapter-react'; export interface MergedConfigInput { @@ -38,6 +38,7 @@ export function useMergedScheptaConfig(props: MergedConfigInput): MergedConfig { return useMemo(() => ({ components: { + ...getFactoryDefaultComponents(), ...(providerConfig?.components || {}), ...(props.components || {}), }, @@ -57,8 +58,8 @@ export function useMergedScheptaConfig(props: MergedConfigInput): MergedConfig { ...(providerConfig?.middlewares || []), ...(props.middlewares || []), ], - debug: props.debug !== undefined - ? props.debug + debug: props.debug !== undefined + ? props.debug : (providerConfig?.debug?.enabled || false), }), [ providerConfig?.components,