From ed8ae03e571f7a578f8dee305e84c5929da1e611 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Thu, 14 May 2026 16:48:53 +0600 Subject: [PATCH] feat(settings): support plain field-id keys for dependency lookups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plugin-ui's formatSettingsData() rebuilds dependency_key as a dot-path (parent.child.field) and the values map is keyed by that dot-path. Dependencies declared with a plain field id (e.g., 'commission_type' instead of 'commission.commission.commission_type') therefore failed to resolve via values[dep.key]. Add an id-keyed fallback so both formats work side by side: - Export buildIdIndex(schema) from settings-formatter — produces a { field_id: dependency_key } map from the hierarchical schema. - evaluateDependencies() accepts an optional idIndex argument. When values[dep.key] is undefined and an idIndex is supplied, the function resolves dep.key as a field id and re-reads values via the index. - SettingsProvider builds the idIndex (memoised on schema) and threads it through shouldDisplay → evaluateDependencies. Backwards compatible: callers that don't pass idIndex see identical behavior. Consumers whose backend guarantees globally-unique field ids (e.g., flat-storage schemas) can now use id-keyed dependencies, which stay valid across structural moves (no parent path to update). Co-Authored-By: Claude Opus 4.7 --- src/components/settings/settings-context.tsx | 11 ++- src/components/settings/settings-formatter.ts | 67 ++++++++++++++++++- 2 files changed, 74 insertions(+), 4 deletions(-) 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';