page.mdx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import { pageMetadata } from "@/lib/page-metadata"
  2. export const metadata = pageMetadata("docs/api/vue")
  3. # @json-render/vue
  4. Vue 3 components, providers, and composables.
  5. ## Providers
  6. ### StateProvider
  7. ```vue
  8. <StateProvider :initial-state="object" :on-state-change="fn">
  9. <!-- children -->
  10. </StateProvider>
  11. ```
  12. | Prop | Type | Description |
  13. |------|------|-------------|
  14. | `store` | `StateStore` | External store (controlled mode). When provided, `initialState` and `onStateChange` are ignored. |
  15. | `initialState` | `Record<string, unknown>` | Initial state model (uncontrolled mode). |
  16. | `onStateChange` | `(changes: Array<{ path: string; value: unknown }>) => void` | Callback when state changes (uncontrolled mode). Called once per `set` or `update` with all changed entries. |
  17. #### External Store (Controlled Mode)
  18. Pass a `StateStore` to bypass the internal state and wire json-render to any state management library:
  19. ```typescript
  20. import { createStateStore, type StateStore } from "@json-render/vue";
  21. const store = createStateStore({ count: 0 });
  22. ```
  23. ```vue
  24. <StateProvider :store="store">
  25. <!-- children -->
  26. </StateProvider>
  27. ```
  28. ```typescript
  29. // Mutate from anywhere — Vue re-renders automatically:
  30. store.set("/count", 1);
  31. ```
  32. ### ActionProvider
  33. ```vue
  34. <ActionProvider :handlers="Record<string, ActionHandler>" :navigate="fn">
  35. <!-- children -->
  36. </ActionProvider>
  37. // type ActionHandler = (params: Record<string, unknown>) => void | Promise<void>;
  38. ```
  39. ### VisibilityProvider
  40. ```vue
  41. <VisibilityProvider>
  42. <!-- children -->
  43. </VisibilityProvider>
  44. ```
  45. `VisibilityProvider` reads state from the parent `StateProvider` automatically. Conditions in specs use the `VisibilityCondition` format with `$state` paths (e.g. `{ "$state": "/path" }`, `{ "$state": "/path", "eq": value }`). See [visibility](/docs/visibility) for the full syntax.
  46. ### ValidationProvider
  47. ```vue
  48. <ValidationProvider :custom-functions="Record<string, ValidationFunction>">
  49. <!-- children -->
  50. </ValidationProvider>
  51. // type ValidationFunction = (value: unknown, args?: object) => boolean | Promise<boolean>;
  52. ```
  53. ## defineRegistry
  54. Create a type-safe component registry from a catalog. Components receive `props`, `children`, `emit`, `on`, and `loading` with catalog-inferred types.
  55. When the catalog declares actions, the `actions` field is required. When the catalog has no actions (e.g. `actions: {}`), the field is optional. When passing stubs, any `async () => {}` is sufficient.
  56. ```typescript
  57. import { h } from "vue";
  58. import { defineRegistry } from "@json-render/vue";
  59. const { registry } = defineRegistry(catalog, {
  60. components: {
  61. Card: ({ props, children }) =>
  62. h("div", { class: "card" }, [h("h3", null, props.title), children]),
  63. Button: ({ props, emit }) =>
  64. h("button", { onClick: () => emit("press") }, props.label),
  65. },
  66. // Required when catalog declares actions:
  67. actions: {
  68. submit: async (params) => { /* ... */ },
  69. },
  70. });
  71. // Pass to <Renderer>
  72. // <Renderer :spec="spec" :registry="registry" />
  73. ```
  74. ## Components
  75. ### Renderer
  76. ```vue
  77. <Renderer
  78. :spec="Spec" // The UI spec to render
  79. :registry="Registry" // Component registry (from defineRegistry)
  80. :loading="boolean" // Optional loading state
  81. :fallback="Component" // Optional fallback for unknown types
  82. />
  83. ```
  84. ### Component Props (via defineRegistry)
  85. ```typescript
  86. import type { VNode } from "vue";
  87. interface ComponentContext<P> {
  88. props: P; // Typed props from catalog
  89. children?: VNode | VNode[]; // Rendered children (for container components)
  90. emit: (event: string) => void; // Emit a named event (always defined)
  91. on: (event: string) => EventHandle; // Get event handle with metadata
  92. loading?: boolean;
  93. bindings?: Record<string, string>; // State paths from $bindState/$bindItem expressions
  94. }
  95. interface EventHandle {
  96. emit: () => void; // Fire the event
  97. shouldPreventDefault: boolean; // Whether any binding requested preventDefault
  98. bound: boolean; // Whether any handler is bound
  99. }
  100. ```
  101. Use `emit("press")` for simple event firing. Use `on("click")` when you need metadata like `shouldPreventDefault`:
  102. ```typescript
  103. Link: ({ props, on }) => {
  104. const click = on("click");
  105. return h("a", {
  106. href: props.href,
  107. onClick: (e: MouseEvent) => {
  108. if (click.shouldPreventDefault) e.preventDefault();
  109. click.emit();
  110. },
  111. }, props.label);
  112. },
  113. ```
  114. ### BaseComponentProps
  115. Catalog-agnostic base type for building reusable component libraries that are not tied to a specific catalog:
  116. ```typescript
  117. import type { BaseComponentProps } from "@json-render/vue";
  118. const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) =>
  119. h("div", null, [props.title, children]);
  120. ```
  121. ## Composables
  122. ### useStateStore
  123. ```typescript
  124. const {
  125. state, // ShallowRef<StateModel> — access with state.value
  126. get, // (path: string) => unknown
  127. set, // (path: string, value: unknown) => void
  128. update, // (updates: Record<string, unknown>) => void
  129. } = useStateStore();
  130. ```
  131. > **Note:** `state` is a `ShallowRef<StateModel>`, not a plain object. Use `state.value` to read the current state. This differs from the React renderer.
  132. ### useStateValue
  133. ```typescript
  134. const value = useStateValue(path: string); // ComputedRef<T | undefined>
  135. ```
  136. Returns a `ComputedRef` that automatically updates when the state at `path` changes. Use `.value` to access the current value.
  137. ### useStateBinding (deprecated)
  138. > **Deprecated.** Use `$bindState` expressions with `bindings` prop instead.
  139. ```typescript
  140. const [value, setValue] = useStateBinding(path: string);
  141. // value: ComputedRef<T | undefined>
  142. // setValue: (value: T) => void
  143. ```
  144. ### useActions
  145. ```typescript
  146. const { execute } = useActions();
  147. // execute(binding: ActionBinding) => Promise<void>
  148. ```
  149. ### useAction
  150. ```typescript
  151. const { execute, isLoading } = useAction(binding: ActionBinding);
  152. // execute: () => Promise<void>
  153. // isLoading: ComputedRef<boolean>
  154. ```
  155. ### useIsVisible
  156. ```typescript
  157. const isVisible = useIsVisible(condition?: VisibilityCondition);
  158. ```
  159. ### useFieldValidation
  160. ```typescript
  161. const {
  162. state, // ComputedRef<FieldValidationState>
  163. validate, // () => ValidationResult
  164. touch, // () => void
  165. clear, // () => void
  166. errors, // ComputedRef<string[]>
  167. isValid, // ComputedRef<boolean>
  168. } = useFieldValidation(path: string, config?: ValidationConfig);
  169. ```
  170. `ValidationConfig` is `{ checks?: ValidationCheck[], validateOn?: 'change' | 'blur' | 'submit' }`.
  171. ## Differences from `@json-render/react`
  172. | API | React | Vue | Note |
  173. |-----|-------|-----|------|
  174. | `useStateStore().state` | `StateModel` (plain object) | `ShallowRef<StateModel>` | Vue reactivity; use `state.value` |
  175. | `useStateValue()` | `T \| undefined` | `ComputedRef<T \| undefined>` | Vue reactivity; use `.value` |
  176. | `useStateBinding()` | `[T \| undefined, setter]` | `[ComputedRef<T \| undefined>, setter]` | Vue reactivity; use `value.value` |
  177. | `useAction().isLoading` | `boolean` | `ComputedRef<boolean>` | Vue reactivity; use `.value` |
  178. | `useFieldValidation().state` | `FieldValidationState` | `ComputedRef<FieldValidationState>` | Vue reactivity; use `.value` |
  179. | `useFieldValidation().errors` | `string[]` | `ComputedRef<string[]>` | Vue reactivity; use `.value` |
  180. | `useFieldValidation().isValid` | `boolean` | `ComputedRef<boolean>` | Vue reactivity; use `.value` |
  181. | `VisibilityContextValue.ctx` | `CoreVisibilityContext` | `ComputedRef<CoreVisibilityContext>` | Vue reactivity; use `ctx.value` |
  182. | `children` type | `React.ReactNode` | `VNode \| VNode[]` | Platform-specific |
  183. | `useBoundProp` | exported | not available | React-specific; use `bindings` directly in Vue |
  184. | `VisibilityProviderProps` | exported | not exported (no props) | Vue uses slot, no prop needed |
  185. | Streaming hooks | `useUIStream`, `useChatUI` | not available | Vue package is UI-only for now |