diff --git a/src/components/settings/settings-context.tsx b/src/components/settings/settings-context.tsx index 4fc8e6a..7d4a4d8 100644 --- a/src/components/settings/settings-context.tsx +++ b/src/components/settings/settings-context.tsx @@ -10,6 +10,7 @@ import { } from 'react'; import type { SaveButtonRenderProps, SettingsElement } from './settings-types'; import { + buildIdIndex, evaluateDependencies, extractValues, formatSettingsData, @@ -355,12 +356,18 @@ export function SettingsProvider({ [schema, onChange, keyToScopeMap, activeSubpage, activePage] ); + // Field-id → dependency_key index, used as a fallback when a dependency + // declares its `key` as a plain field id instead of the reconstructed + // dot-path. Memoized on the schema since ids and dep_keys don't change + // at runtime. + const idIndex = useMemo(() => buildIdIndex(schema), [schema]); + // Dependency evaluation const shouldDisplay = useCallback( (element: SettingsElement): boolean => { - return evaluateDependencies(element, values); + return evaluateDependencies(element, values, idIndex); }, - [values] + [values, idIndex] ); // Navigation helpers diff --git a/src/components/settings/settings-formatter.ts b/src/components/settings/settings-formatter.ts index 3e288e3..9e8b78d 100644 --- a/src/components/settings/settings-formatter.ts +++ b/src/components/settings/settings-formatter.ts @@ -294,12 +294,64 @@ export function extractValues( return values; } +/** + * Builds a `{ field_id: dependency_key }` map from a hierarchical schema. + * + * Used by `evaluateDependencies` to resolve a dependency `key` that is + * declared as a plain field id (e.g. `"product_info_generate"`) instead + * of the full reconstructed dot-path (e.g. + * `"product_generation.product_image_section.product_info_generate"`). + * + * Consumers may pass id-keyed dependencies when their backend already + * guarantees globally-unique field ids; the dot-path remains supported + * for backwards compatibility. + */ +export function buildIdIndex( + schema: SettingsElement[] +): Record { + const idIndex: Record = {}; + + const walk = (elements: SettingsElement[]) => { + for (const el of elements) { + if ( + el.type === 'field' && + el.id && + el.dependency_key && + el.id !== el.dependency_key + ) { + // First writer wins — if two fields share an id (a schema + // bug consumers should detect with their own validator), + // we don't silently clobber the earlier mapping. + if (!(el.id in idIndex)) { + idIndex[el.id] = el.dependency_key; + } + } + if (el.children && el.children.length > 0) { + walk(el.children); + } + } + }; + + walk(schema); + return idIndex; +} + /** * Evaluates whether a field should be displayed based on its dependencies. + * + * Dependency `key` resolution: + * 1. Look up `values[dep.key]` directly (existing dot-path behavior). + * 2. If that yields `undefined` and an `idIndex` is supplied, treat + * `dep.key` as a plain field id and resolve via `idIndex[dep.key]` + * to its real `dependency_key` before reading from `values`. + * + * The `idIndex` is optional. When omitted, behavior is identical to the + * previous version of this function. */ export function evaluateDependencies( element: SettingsElement, - values: Record + values: Record, + idIndex?: Record ): boolean { if (!element.dependencies || element.dependencies.length === 0) { return element.display !== false; @@ -308,7 +360,18 @@ export function evaluateDependencies( return element.dependencies.every((dep) => { if (!dep.key) return true; - const currentValue = values[dep.key]; + let currentValue = values[dep.key]; + + // Field-id fallback: dep.key may be a plain id (not a dot-path). + // Resolve it through the idIndex to the underlying dependency_key + // and re-read from values. + if (currentValue === undefined && idIndex) { + const resolved = idIndex[dep.key]; + if (resolved !== undefined) { + currentValue = values[resolved]; + } + } + const comparison = dep.comparison || '=='; const expectedValue = dep.value; const effect = dep.effect || 'show';