# @json-render/solid

SolidJS components, providers, and hooks for rendering json-render specs.

## Installation

```bash
pnpm add @json-render/core @json-render/solid
```


Peer dependencies: `solid-js ^1.9.0` and `zod ^4.0.0`.

```bash
pnpm add solid-js zod
```


## Providers

### StateProvider

```tsx
<StateProvider
  initialState={{}}
  onStateChange={(changes) => console.log(changes)}
>
  {/* children */}
</StateProvider>
```

<table>
  <thead>
    <tr>
      <th>Prop</th>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <code>store</code>
      </td>
      <td>
        <code>StateStore</code>
      </td>
      <td>
        External store (controlled mode). When provided,{" "}
        <code>initialState</code> and <code>onStateChange</code> are ignored.
      </td>
    </tr>
    <tr>
      <td>
        <code>initialState</code>
      </td>
      <td>
        <code>Record&lt;string, unknown&gt;</code>
      </td>
      <td>Initial state model for uncontrolled mode.</td>
    </tr>
    <tr>
      <td>
        <code>onStateChange</code>
      </td>
      <td>
        <code>
          {"(changes: Array<{ path: string; value: unknown }>) => void"}
        </code>
      </td>
      <td>Called for uncontrolled state updates.</td>
    </tr>
  </tbody>
</table>

### ActionProvider

```tsx
<ActionProvider
  handlers={{ submit: async (params) => {} }}
  navigate={(path) => {}}
>
  {/* children */}
</ActionProvider>
```

### VisibilityProvider

```tsx
<VisibilityProvider>{/* children */}</VisibilityProvider>
```

### ValidationProvider

```tsx
<ValidationProvider customFunctions={{ custom: (value) => Boolean(value) }}>
  {/* children */}
</ValidationProvider>
```

### JSONUIProvider

Combined provider wrapper for state, visibility, validation, and actions.

```tsx
<JSONUIProvider
  registry={registry}
  initialState={{}}
  handlers={handlers}
  validationFunctions={validationFunctions}
>
  <Renderer spec={spec} registry={registry} />
</JSONUIProvider>
```

## defineRegistry

Create a typed component registry and action helpers from a catalog.

```tsx
const { registry, handlers, executeAction } = defineRegistry(catalog, {
  components: {
    Card: (renderProps) => <div>{renderProps.children}</div>,
    Button: (renderProps) => (
      <button onClick={() => renderProps.emit("press")}>
        {renderProps.props.label}
      </button>
    ),
  },
  actions: {
    submit: async (params, setState, state) => {
      // custom action logic
    },
  },
});
```

## Components

### Renderer

```tsx
<Renderer spec={spec} registry={registry} loading={false} />
```

Renders a `Spec` tree using your registry.

### createRenderer

Build an app-level renderer from catalog + components:

```tsx
const AppRenderer = createRenderer(catalog, {
  Card: (renderProps) => <div>{renderProps.children}</div>,
});

<AppRenderer spec={spec} state={{}} onAction={(name, params) => {}} />;
```

## Hooks

- `useStateStore()`
- `useStateValue(path)` - returns an accessor
- `useStateBinding(path)` - returns `[Accessor<T | undefined>, setValue]`
- `useVisibility()` / `useIsVisible(condition)`
- `useActions()` / `useAction(binding)`
- `useValidation()` / `useOptionalValidation()`
- `useFieldValidation(path, config)` - returns accessor-backed `state`, `errors`, and `isValid`
- `useBoundProp(value, bindingPath)` - returns `[value, setValue]`, where `value` is the resolved prop and not an accessor
- `useUIStream(options)`
- `useChatUI(options)`

## Built-in Actions

`ActionProvider` handles these built-in actions:

- `setState`
- `pushState`
- `removeState`
- `validateForm`

## Component Props

Registry components receive:

```ts
interface ComponentRenderProps<P = Record<string, unknown>> {
  element: UIElement<string, P>;
  children?: JSX.Element;
  emit: (event: string) => void;
  on: (event: string) => EventHandle;
  bindings?: Record<string, string>;
  loading?: boolean;
}
```

Use `emit("event")` to dispatch event bindings. Use `on("event")` to access `EventHandle` metadata (`bound`, `shouldPreventDefault`, `emit`).

## Reactivity Notes

- Keep changing reads in JSX expressions, `createMemo`, or `createEffect`.
- Avoid props destructuring in component signatures when you need live updates.
- `StateProvider` and other contexts expose getter-backed values so consumers read live signals.
- `useStateValue`, `useStateBinding`, and `useFieldValidation` expose reactive accessors; call them as functions.