From 94018c0e3c2703497a1a9c2910d0db978609c0c8 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 02:31:19 +0000 Subject: [PATCH 1/4] Align JSON UI binding transforms --- src/lib/json-ui/component-renderer.tsx | 5 ++++- src/lib/json-ui/renderer.tsx | 6 ++++++ src/lib/json-ui/schema.ts | 1 + src/types/json-ui.ts | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/json-ui/component-renderer.tsx b/src/lib/json-ui/component-renderer.tsx index 51479dc..ebfbbc4 100644 --- a/src/lib/json-ui/component-renderer.tsx +++ b/src/lib/json-ui/component-renderer.tsx @@ -1,6 +1,7 @@ import { createElement, useMemo } from 'react' import { UIComponent, Binding, ComponentRendererProps } from '@/types/json-ui' import { getUIComponent } from './component-registry' +import { transformData } from './utils' function resolveBinding(binding: Binding, data: Record): unknown { let value: unknown = data[binding.source] @@ -18,7 +19,9 @@ function resolveBinding(binding: Binding, data: Record): unknow } if (binding.transform) { - value = binding.transform(value) + value = typeof binding.transform === 'string' + ? transformData(value, binding.transform) + : binding.transform(value) } return value diff --git a/src/lib/json-ui/renderer.tsx b/src/lib/json-ui/renderer.tsx index 53138e3..aefc299 100644 --- a/src/lib/json-ui/renderer.tsx +++ b/src/lib/json-ui/renderer.tsx @@ -86,6 +86,12 @@ export function JSONUIRenderer({ const props: Record = { ...component.props } + if (component.bindings) { + Object.entries(component.bindings).forEach(([propName, binding]) => { + props[propName] = resolveDataBinding(binding, dataMap, context) + }) + } + if (component.dataBinding) { const boundData = resolveDataBinding(component.dataBinding, dataMap, context) if (boundData !== undefined) { diff --git a/src/lib/json-ui/schema.ts b/src/lib/json-ui/schema.ts index f5b261c..862dd8f 100644 --- a/src/lib/json-ui/schema.ts +++ b/src/lib/json-ui/schema.ts @@ -33,6 +33,7 @@ export const UIComponentSchema: any = z.object({ props: z.record(z.string(), z.any()).optional(), className: z.string().optional(), style: z.record(z.string(), z.any()).optional(), + bindings: z.record(z.string(), DataBindingSchema).optional(), children: z.union([ z.string(), z.array(z.lazy(() => UIComponentSchema)), diff --git a/src/types/json-ui.ts b/src/types/json-ui.ts index 8b6889d..e2fbc3c 100644 --- a/src/types/json-ui.ts +++ b/src/types/json-ui.ts @@ -48,7 +48,7 @@ export interface Action { export interface Binding { source: string path?: string - transform?: (value: any) => any + transform?: string | ((value: any) => any) } export interface EventHandler { From 17ff0eaaea6d29f37a1f9097ba1c3b1fc09feb70 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 02:31:49 +0000 Subject: [PATCH 2/4] Enhance binding resolution in component renderer --- src/lib/json-ui/component-renderer.tsx | 28 ++++++++++++-------------- src/types/json-ui.ts | 1 + 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/lib/json-ui/component-renderer.tsx b/src/lib/json-ui/component-renderer.tsx index 51479dc..ef02c71 100644 --- a/src/lib/json-ui/component-renderer.tsx +++ b/src/lib/json-ui/component-renderer.tsx @@ -1,20 +1,16 @@ import { createElement, useMemo } from 'react' import { UIComponent, Binding, ComponentRendererProps } from '@/types/json-ui' import { getUIComponent } from './component-registry' +import { getNestedValue } from './utils' function resolveBinding(binding: Binding, data: Record): unknown { - let value: unknown = data[binding.source] + const sourceValue = binding.source.includes('.') + ? getNestedValue(data, binding.source) + : data[binding.source] + let value: unknown = sourceValue if (binding.path) { - const keys = binding.path.split('.') - for (const key of keys) { - if (value && typeof value === 'object') { - value = (value as Record)[key] - } else { - value = undefined - break - } - } + value = getNestedValue(value, binding.path) } if (binding.transform) { @@ -24,20 +20,21 @@ function resolveBinding(binding: Binding, data: Record): unknow return value } -export function ComponentRenderer({ component, data, onEvent }: ComponentRendererProps) { +export function ComponentRenderer({ component, data, context = {}, onEvent }: ComponentRendererProps) { + const mergedData = useMemo(() => ({ ...data, ...context }), [data, context]) const resolvedProps = useMemo(() => { const resolved: Record = { ...component.props } if (component.bindings) { Object.entries(component.bindings).forEach(([propName, binding]) => { - resolved[propName] = resolveBinding(binding, data) + resolved[propName] = resolveBinding(binding, mergedData) }) } if (component.events && onEvent) { component.events.forEach(handler => { resolved[`on${handler.event.charAt(0).toUpperCase()}${handler.event.slice(1)}`] = (e: unknown) => { - if (!handler.condition || handler.condition(data)) { + if (!handler.condition || handler.condition(mergedData as Record)) { onEvent(component.id, handler.event, e) } } @@ -45,7 +42,7 @@ export function ComponentRenderer({ component, data, onEvent }: ComponentRendere } return resolved - }, [component, data, onEvent]) + }, [component, mergedData, onEvent]) const Component = getUIComponent(component.type) @@ -55,7 +52,7 @@ export function ComponentRenderer({ component, data, onEvent }: ComponentRendere } if (component.condition) { - const conditionValue = resolveBinding(component.condition, data) + const conditionValue = resolveBinding(component.condition, mergedData) if (!conditionValue) { return null } @@ -66,6 +63,7 @@ export function ComponentRenderer({ component, data, onEvent }: ComponentRendere key={child.id || index} component={child} data={data} + context={context} onEvent={onEvent} /> )) diff --git a/src/types/json-ui.ts b/src/types/json-ui.ts index 8b6889d..f4c1782 100644 --- a/src/types/json-ui.ts +++ b/src/types/json-ui.ts @@ -98,6 +98,7 @@ export interface JSONUIContext { export interface ComponentRendererProps { component: UIComponent data: Record + context?: Record onEvent?: (componentId: string, event: string, eventData: unknown) => void } From 717871c673542573abe8d9a097767e2adcb093c7 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 02:34:10 +0000 Subject: [PATCH 3/4] Expand conditional rendering branches --- src/lib/json-ui/renderer.tsx | 66 ++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/lib/json-ui/renderer.tsx b/src/lib/json-ui/renderer.tsx index 53138e3..e74191f 100644 --- a/src/lib/json-ui/renderer.tsx +++ b/src/lib/json-ui/renderer.tsx @@ -10,32 +10,6 @@ export function JSONUIRenderer({ onAction, context = {} }: JSONUIRendererProps) { - - if (component.conditional) { - const conditionMet = evaluateCondition(component.conditional.if, { ...dataMap, ...context }) - if (conditionMet) { - if (component.conditional.then) { - return ( - - ) - } - } else { - return component.conditional.else ? ( - - ) : null - } - } - const renderChildren = ( children: UIComponent[] | string | undefined, renderContext: Record @@ -57,6 +31,46 @@ export function JSONUIRenderer({ )) } + const renderConditionalBranch = ( + branch: UIComponent | UIComponent[] | string | undefined, + renderContext: Record + ) => { + if (branch === undefined) return null + + if (typeof branch === 'string' || Array.isArray(branch)) { + return renderChildren(branch, renderContext) + } + + return ( + + ) + } + + if (component.conditional) { + const conditionMet = evaluateCondition(component.conditional.if, { ...dataMap, ...context }) + if (conditionMet) { + if (component.conditional.then !== undefined) { + return renderConditionalBranch( + component.conditional.then as UIComponent | UIComponent[] | string, + context + ) + } + } else { + if (component.conditional.else !== undefined) { + return renderConditionalBranch( + component.conditional.else as UIComponent | UIComponent[] | string, + context + ) + } + return null + } + } + if (component.loop) { const items = resolveDataBinding(component.loop.source, dataMap, context) || [] return ( From 5652d0222d348f661bfbca169d3e46932804b0a0 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 02:35:04 +0000 Subject: [PATCH 4/4] Render loop containers with props --- src/lib/json-ui/renderer.tsx | 45 +++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/lib/json-ui/renderer.tsx b/src/lib/json-ui/renderer.tsx index 53138e3..9822548 100644 --- a/src/lib/json-ui/renderer.tsx +++ b/src/lib/json-ui/renderer.tsx @@ -59,6 +59,13 @@ export function JSONUIRenderer({ if (component.loop) { const items = resolveDataBinding(component.loop.source, dataMap, context) || [] + const Component = getUIComponent(component.type) + + if (!Component) { + console.warn(`Component type "${component.type}" not found in registry`) + return null + } + return ( <> {items.map((item: any, index: number) => { @@ -67,9 +74,45 @@ export function JSONUIRenderer({ [component.loop!.itemVar]: item, ...(component.loop!.indexVar ? { [component.loop!.indexVar]: index } : {}), } + const props: Record = { ...component.props } + + if (component.dataBinding) { + const boundData = resolveDataBinding(component.dataBinding, dataMap, loopContext) + if (boundData !== undefined) { + props.value = boundData + props.data = boundData + } + } + + if (component.events) { + Object.entries(component.events).forEach(([eventName, handler]) => { + props[eventName] = (event?: any) => { + if (onAction) { + const eventHandler = typeof handler === 'string' + ? { action: handler } as EventHandler + : handler as EventHandler + onAction(eventHandler, event) + } + } + }) + } + + if (component.className) { + props.className = cn(props.className, component.className) + } + + if (component.style) { + props.style = { ...props.style, ...component.style } + } return ( - {renderChildren(component.children, loopContext)} + {typeof Component === 'string' + ? React.createElement(Component, props, renderChildren(component.children, loopContext)) + : ( + + {renderChildren(component.children, loopContext)} + + )} ) })}