UNPKG

@rjsf/utils

Version:
90 lines (81 loc) 3.83 kB
import isEmpty from 'lodash/isEmpty'; import { ANY_OF_KEY, ONE_OF_KEY, REF_KEY, RJSF_REF_KEY } from './constants'; import findSchemaDefinition from './findSchemaDefinition'; import mergeObjects from './mergeObjects'; import type { FormContextType, GenericObjectType, Registry, RJSFSchema, StrictRJSFSchema, UiSchema } from './types'; /** Resolves the uiSchema for a given schema, considering `ui:definitions` stored in the registry. * * Called at runtime for each field. When the schema contains a `$ref`, looks up the corresponding * uiSchema definition from `registry.uiSchemaDefinitions` and merges it with local overrides. * For schemas with `oneOf`/`anyOf` branches, also populates `uiSchema[keyword][i]` for branches * whose `$ref` matches a definition, so `MultiSchemaField` can read dropdown option titles. * * Resolution order (later sources override earlier): * 1. `ui:definitions[$ref]` - base definition from registry * 2. `localUiSchema` - local overrides at current path * * @param schema - The JSON schema (may contain `$ref` or `RJSF_REF_KEY`) * @param localUiSchema - The uiSchema at the current path (local overrides) * @param registry - The registry containing `uiSchemaDefinitions` * @returns - The resolved uiSchema with definitions merged in */ export default function resolveUiSchema< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any, >(schema: S, localUiSchema: UiSchema<T, S, F> | undefined, registry: Registry<T, S, F>): UiSchema<T, S, F> { const ref = ((schema as GenericObjectType)[RJSF_REF_KEY] ?? schema[REF_KEY]) as string | undefined; const definitions = registry.uiSchemaDefinitions; const definitionUiSchema = ref && definitions ? definitions[ref] : undefined; let result: UiSchema<T, S, F>; if (!definitionUiSchema) { result = localUiSchema || {}; } else if (!localUiSchema || isEmpty(localUiSchema)) { result = { ...definitionUiSchema }; } else { result = mergeObjects(definitionUiSchema as GenericObjectType, localUiSchema as GenericObjectType) as UiSchema< T, S, F >; } // Walk oneOf/anyOf branches to populate uiSchema[keyword][i] so MultiSchemaField // can read dropdown option titles at the parent level. if (definitions) { let resolvedSchema: S = schema; if (ref && schema[REF_KEY] && !(schema as GenericObjectType)[RJSF_REF_KEY]) { try { resolvedSchema = findSchemaDefinition<S>(ref, registry.rootSchema); } catch (e) { // oxlint-disable-next-line no-console console.warn('could not resolve $ref in resolveUiSchema:\n', e); return result; } } for (const keyword of [ONE_OF_KEY, ANY_OF_KEY] as const) { const schemaOptions = resolvedSchema[keyword]; if (Array.isArray(schemaOptions) && schemaOptions.length > 0) { const currentUiSchemaArray = (result as GenericObjectType)[keyword]; const uiSchemaArray: UiSchema<T, S, F>[] = Array.isArray(currentUiSchemaArray) ? [...currentUiSchemaArray] : []; let hasExpanded = false; for (let i = 0; i < schemaOptions.length; i++) { const option = schemaOptions[i] as GenericObjectType | undefined; const optionRef = (option?.[RJSF_REF_KEY] ?? option?.[REF_KEY]) as string | undefined; if (optionRef && optionRef in definitions) { const optionUiSchema = (uiSchemaArray[i] || {}) as GenericObjectType; uiSchemaArray[i] = mergeObjects(definitions[optionRef] as GenericObjectType, optionUiSchema) as UiSchema< T, S, F >; hasExpanded = true; } } if (hasExpanded) { (result as GenericObjectType)[keyword] = uiSchemaArray; } } } } return result; }