diff --git a/apps/web/app/(main)/docs/api/react-native/page.mdx b/apps/web/app/(main)/docs/api/react-native/page.mdx index 1385b050..6f267802 100644 --- a/apps/web/app/(main)/docs/api/react-native/page.mdx +++ b/apps/web/app/(main)/docs/api/react-native/page.mdx @@ -149,6 +149,40 @@ Conditions in specs use the `VisibilityCondition` format with `$state` paths (e. ``` +### JSONUIProvider + +Convenience wrapper that combines `StateProvider`, `VisibilityProvider`, `ActionProvider`, and `ValidationProvider`. It accepts the same props as those providers, plus: + + + + + + + + + + + + + + + + +
PropTypeDescription
functionsRecord<string, ComputedFunction>Named functions for $computed expressions in props
+ +```tsx + `${args.first} ${args.last}` }} +> + + +``` + +The `functions` prop is also available on `createRenderer`. + ## defineRegistry Create a type-safe component registry. Standard components are built-in; only register custom components. diff --git a/packages/react-native/README.md b/packages/react-native/README.md index 3c09c67f..69d39321 100644 --- a/packages/react-native/README.md +++ b/packages/react-native/README.md @@ -195,6 +195,33 @@ Any prop value can be a dynamic expression resolved at render time: See [@json-render/core](../core/README.md) for full expression syntax. +### `$template` and `$computed` + +```json +{ + "label": { "$template": "Hello, ${/user/name}!" }, + "fullName": { + "$computed": "fullName", + "args": { + "first": { "$state": "/form/firstName" }, + "last": { "$state": "/form/lastName" } + } + } +} +``` + +Register named functions via the `functions` prop on `JSONUIProvider` or `createRenderer` (same as [@json-render/react](../react/README.md)): + +```tsx + `${args.first} ${args.last}` }} +> + + +``` + ## Tab Navigation Pattern Combine `Pressable`, `setState`, visibility conditions, and dynamic props for functional tabs: diff --git a/packages/react-native/src/renderer.tsx b/packages/react-native/src/renderer.tsx index f9601a8b..d7c96e69 100644 --- a/packages/react-native/src/renderer.tsx +++ b/packages/react-native/src/renderer.tsx @@ -12,6 +12,7 @@ import type { Catalog, SchemaDefinition, StateStore, + ComputedFunction, } from "@json-render/core"; import { resolveElementProps, @@ -136,6 +137,19 @@ class ElementErrorBoundary extends React.Component< } } +// --------------------------------------------------------------------------- +// FunctionsContext – provides $computed functions to the element tree +// --------------------------------------------------------------------------- + +const EMPTY_FUNCTIONS: Record = {}; + +const FunctionsContext = + React.createContext>(EMPTY_FUNCTIONS); + +function useFunctions(): Record { + return React.useContext(FunctionsContext); +} + interface ElementRendererProps { element: UIElement; spec: Spec; @@ -159,20 +173,21 @@ const ElementRenderer = React.memo(function ElementRenderer({ const { ctx } = useVisibility(); const { execute } = useActions(); const { getSnapshot } = useStateStore(); - - // Build context with repeat scope (used for both visibility and props) - const fullCtx: PropResolutionContext = useMemo( - () => - repeatScope - ? { - ...ctx, - repeatItem: repeatScope.item, - repeatIndex: repeatScope.index, - repeatBasePath: repeatScope.basePath, - } - : ctx, - [ctx, repeatScope], - ); + const functions = useFunctions(); + + // Build context with repeat scope and $computed functions (visibility + props) + const fullCtx: PropResolutionContext = useMemo(() => { + const base: PropResolutionContext = repeatScope + ? { + ...ctx, + repeatItem: repeatScope.item, + repeatIndex: repeatScope.index, + repeatBasePath: repeatScope.basePath, + } + : { ...ctx }; + base.functions = functions; + return base; + }, [ctx, repeatScope, functions]); // Evaluate visibility (now supports $item/$index inside repeat scopes) const isVisible = @@ -439,6 +454,8 @@ export interface JSONUIProviderProps { string, (value: unknown, args?: Record) => boolean >; + /** Named functions for `$computed` expressions in props */ + functions?: Record; /** Callback when state changes (uncontrolled mode) */ onStateChange?: (changes: Array<{ path: string; value: unknown }>) => void; children: ReactNode; @@ -454,6 +471,7 @@ export function JSONUIProvider({ handlers, navigate, validationFunctions, + functions, onStateChange, children, }: JSONUIProviderProps) { @@ -465,10 +483,12 @@ export function JSONUIProvider({ > - - {children} - - + + + {children} + + + @@ -663,6 +683,8 @@ export interface CreateRendererProps { onAction?: (actionName: string, params?: Record) => void; /** Callback when state changes (uncontrolled mode) */ onStateChange?: (changes: Array<{ path: string; value: unknown }>) => void; + /** Named functions for `$computed` expressions in props */ + functions?: Record; /** Whether the spec is currently loading/streaming */ loading?: boolean; /** Fallback component for unknown types */ @@ -716,6 +738,7 @@ export function createRenderer< state, onAction, onStateChange, + functions, loading, fallback, }: CreateRendererProps) { @@ -744,15 +767,17 @@ export function createRenderer< > - - - - + + + + + + diff --git a/skills/react-native/SKILL.md b/skills/react-native/SKILL.md index f43134b8..ff521ac0 100644 --- a/skills/react-native/SKILL.md +++ b/skills/react-native/SKILL.md @@ -118,6 +118,7 @@ Any prop value can be a data-driven expression resolved at render time: - **`{ "$bindState": "/path" }`** - two-way binding: use on the natural value prop (value, checked, pressed, etc.) of form components. - **`{ "$bindItem": "field" }`** - two-way binding to a repeat item field. Use inside repeat scopes. - **`{ "$cond": , "$then": , "$else": }`** - conditional value +- **`{ "$computed": "name", "args": { ... } }`** - call a named function; register implementations with the `functions` prop on `JSONUIProvider` or `createRenderer` (same as `@json-render/react`) ```json { @@ -147,6 +148,7 @@ The `setState` action is handled automatically by `ActionProvider` and updates t | `ActionProvider` | Handle actions dispatched from components | | `VisibilityProvider` | Enable conditional rendering based on state | | `ValidationProvider` | Form field validation | +| `JSONUIProvider` | Combines the above providers; also accepts `functions` for `$computed` props | ### External Store (Controlled Mode)