{"id":34646295,"url":"https://github.com/lodado/sdui-template","last_synced_at":"2026-01-13T20:38:15.005Z","repository":{"id":330312162,"uuid":"1122095302","full_name":"lodado/sdui-template","owner":"lodado","description":"Server-Driven UI Template Library for React. A flexible and powerful template system for building server-driven user interfaces with dynamic layouts and components.","archived":false,"fork":false,"pushed_at":"2025-12-26T06:30:22.000Z","size":1089,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-26T06:33:59.669Z","etag":null,"topics":["ab-testing","app-router","cms","dashboard-builder","dynamic-js","javascript","layout","nextjs","normalize","react","reactjs","sdui","server-driven-ui","ssr","type-safe","typescript","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lodado.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-24T05:11:59.000Z","updated_at":"2025-12-26T06:30:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lodado/sdui-template","commit_stats":null,"previous_names":["lodado/sdui-template"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/lodado/sdui-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lodado%2Fsdui-template","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lodado%2Fsdui-template/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lodado%2Fsdui-template/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lodado%2Fsdui-template/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lodado","download_url":"https://codeload.github.com/lodado/sdui-template/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lodado%2Fsdui-template/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28399879,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ab-testing","app-router","cms","dashboard-builder","dynamic-js","javascript","layout","nextjs","normalize","react","reactjs","sdui","server-driven-ui","ssr","type-safe","typescript","zod"],"created_at":"2025-12-24T17:46:37.842Z","updated_at":"2026-01-13T20:38:14.999Z","avatar_url":"https://github.com/lodado.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @lodado/sdui-template\n\n\u003e ⚠️ **For personal use - Feel free to use as you like**\n\n**Server-Driven UI Template Library for React** - A flexible and powerful template system for building server-driven user interfaces with dynamic layouts and components.\n\n## 📖 What is This?\n\n**SDUI (Server-Driven UI)** is a pattern where your server defines the UI structure, and your React app renders it dynamically. Instead of hardcoding components, you receive a JSON document from your server that describes what to render.\n\n### Real-World Examples\n\n- **Dashboard Builders**: Users drag-and-drop widgets, layouts are saved to the server, then loaded and rendered\n- **Dynamic Forms**: Form structure comes from the server, client just renders it\n- **CMS Page Builder**: Admins design pages, users see the same layout\n- **A/B Testing**: Server sends different UI layouts for experiments\n\n### The Problem\n\nBuilding these features from scratch means:\n\n- ❌ Reimplementing state management for each project\n- ❌ Building subscription systems for efficient updates\n- ❌ Writing component rendering logic repeatedly\n- ❌ Dealing with performance issues (unnecessary re-renders)\n\n### The Solution\n\nThis library provides:\n\n- ✅ **Reusable**: Write once, use everywhere\n- ✅ **Fast**: Only changed components re-render (subscription-based)\n- ✅ **Flexible**: Override components per project\n- ✅ **Type Safe**: Full TypeScript + optional Zod validation\n- ✅ **Next.js Ready**: Works seamlessly with App Router\n\n## ✨ Features\n\n- 🎯 **Server-Driven UI**: Define layouts from server JSON\n- ⚡ **Performance**: ID-based subscriptions = minimal re-renders\n- 🔄 **Normalize/Denormalize**: Efficient data structure (normalizr)\n- 🎨 **Type Safe**: TypeScript + Zod schema validation\n- 🧩 **Modular**: Clean, separated architecture\n- 🚀 **Next.js Compatible**: Built for App Router\n- 🔧 **State Management**: Update component state programmatically\n- 🔗 **Node References**: Reference other nodes to access their state and subscribe to changes\n\n## 📦 Installation\n\n```bash\nnpm install @lodado/sdui-template\n# or\npnpm add @lodado/sdui-template\n# or\nyarn add @lodado/sdui-template\n```\n\n## 🚀 Quick Start\n\n### Step 1: Basic Rendering\n\nThe simplest way to use this library - just pass a document:\n\n```tsx\n'use client'\n\nimport { SduiLayoutRenderer } from '@lodado/sdui-template'\nimport type { SduiLayoutDocument } from '@lodado/sdui-template'\n\n// This document typically comes from your server API\nconst document: SduiLayoutDocument = {\n  version: '1.0.0',\n  root: {\n    id: 'root',\n    type: 'Container',\n    children: [\n      {\n        id: 'card-1',\n        type: 'Card',\n        state: {\n          title: 'Hello World',\n          content: 'This is a card component',\n        },\n      },\n    ],\n  },\n}\n\nexport default function Page() {\n  return \u003cSduiLayoutRenderer document={document} /\u003e\n}\n```\n\n**What's happening?**\n\n- The `document` describes your UI structure\n- `SduiLayoutRenderer` reads it and renders components\n- Each node has an `id` (unique identifier) and `type` (component name)\n- `state` contains data your component needs\n\n### Step 2: Create Your Own Components\n\nNow let's create a custom component that can handle state:\n\n```tsx\n'use client'\n\nimport {\n  SduiLayoutRenderer,\n  useSduiNodeSubscription,\n  useSduiLayoutAction,\n  type ComponentFactory,\n} from '@lodado/sdui-template'\nimport { z } from 'zod'\n\n// 1. Define what your component's state looks like\nconst cardStateSchema = z.object({\n  title: z.string(),\n  content: z.string(),\n  count: z.number().optional(),\n})\n\n// 2. Create your component\nfunction Card({ id }: { id: string }) {\n  // Subscribe to this node's state changes\n  const { state } = useSduiNodeSubscription({\n    nodeId: id,\n    schema: cardStateSchema, // Validates and types the state\n  })\n\n  // Get the store to update state\n  const store = useSduiLayoutAction()\n\n  const handleClick = () =\u003e {\n    // Update state - only this card re-renders!\n    store.updateNodeState(id, {\n      count: (state.count || 0) + 1,\n    })\n  }\n\n  return (\n    \u003cdiv className=\"card\"\u003e\n      \u003ch2\u003e{state.title}\u003c/h2\u003e\n      \u003cp\u003e{state.content}\u003c/p\u003e\n      {state.count !== undefined \u0026\u0026 \u003cp\u003eClicked: {state.count} times\u003c/p\u003e}\n      \u003cbutton onClick={handleClick}\u003eClick me\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\n// 3. Create a factory function (tells the library how to render your component)\nconst CardFactory: ComponentFactory = (id) =\u003e \u003cCard id={id} /\u003e\n\n// 4. Define your document\nconst document: SduiLayoutDocument = {\n  version: '1.0.0',\n  root: {\n    id: 'root',\n    type: 'Container',\n    children: [\n      {\n        id: 'card-1',\n        type: 'Card',\n        state: {\n          title: 'My First Card',\n          content: 'This card can update its own state!',\n          count: 0,\n        },\n      },\n    ],\n  },\n}\n\n// 5. Render with your component map\nexport default function Page() {\n  return \u003cSduiLayoutRenderer document={document} components={{ Card: CardFactory }} /\u003e\n}\n```\n\n**Key Points:**\n\n- `useSduiNodeSubscription`: Subscribe to a node's state (auto re-renders on changes)\n- `useSduiLayoutAction`: Get the store to update state\n- `ComponentFactory`: Function that creates your component\n- `components` prop: Maps component types to factories\n\n### Step 3: Complete Example - Toggle Component\n\nHere's a full example with a toggle switch:\n\n```tsx\n'use client'\n\nimport {\n  SduiLayoutRenderer,\n  useSduiNodeSubscription,\n  useSduiLayoutAction,\n  type ComponentFactory,\n} from '@lodado/sdui-template'\nimport { z } from 'zod'\n\n// Define state schema\nconst toggleStateSchema = z.object({\n  checked: z.boolean(),\n  label: z.string().optional(),\n})\n\n// Create toggle component\nfunction Toggle({ id }: { id: string }) {\n  const { state } = useSduiNodeSubscription({\n    nodeId: id,\n    schema: toggleStateSchema,\n  })\n  const store = useSduiLayoutAction()\n\n  const handleToggle = () =\u003e {\n    store.updateNodeState(id, {\n      checked: !state.checked,\n    })\n  }\n\n  return (\n    \u003cdiv className=\"flex items-center gap-2 p-3\"\u003e\n      {state.label \u0026\u0026 \u003clabel\u003e{state.label}\u003c/label\u003e}\n      \u003cbutton\n        onClick={handleToggle}\n        className={`w-11 h-6 rounded-full transition-colors ${state.checked ? 'bg-blue-600' : 'bg-gray-400'}`}\n      \u003e\n        \u003cspan\n          className={`block w-5 h-5 bg-white rounded-full transition-transform ${\n            state.checked ? 'translate-x-5' : 'translate-x-0'\n          }`}\n        /\u003e\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\n// Create factory\nconst ToggleFactory: ComponentFactory = (id) =\u003e \u003cToggle id={id} /\u003e\n\n// Document with multiple toggles\nconst document: SduiLayoutDocument = {\n  version: '1.0.0',\n  root: {\n    id: 'root',\n    type: 'Container',\n    children: [\n      {\n        id: 'toggle-1',\n        type: 'Toggle',\n        state: {\n          checked: false,\n          label: 'Enable notifications',\n        },\n      },\n      {\n        id: 'toggle-2',\n        type: 'Toggle',\n        state: {\n          checked: true,\n          label: 'Dark mode',\n        },\n      },\n    ],\n  },\n}\n\nexport default function Page() {\n  return (\n    \u003cSduiLayoutRenderer\n      document={document}\n      components={{ Toggle: ToggleFactory }}\n      onError={(error) =\u003e console.error('SDUI Error:', error)}\n    /\u003e\n  )\n}\n```\n\n**Why This Works:**\n\n- ✅ Clicking one toggle only re-renders that toggle (performance!)\n- ✅ Type-safe state with Zod validation\n- ✅ Server controls initial state, client handles interactions\n- ✅ Easy to add more components\n\n### Step 4: Node References\n\nNodes can reference other nodes to access their state and subscribe to changes. This is useful for components that need to display or react to other components' state.\n\n```tsx\n'use client'\n\nimport {\n  SduiLayoutRenderer,\n  useSduiNodeSubscription,\n  useSduiNodeReference,\n  useSduiLayoutAction,\n  type ComponentFactory,\n} from '@lodado/sdui-template'\nimport { z } from 'zod'\n\n// Toggle state schema\nconst toggleStateSchema = z.object({\n  checked: z.boolean(),\n  label: z.string().optional(),\n})\n\n// Toggle component: manages its own state\nfunction Toggle({ id }: { id: string }) {\n  const { state } = useSduiNodeSubscription({\n    nodeId: id,\n    schema: toggleStateSchema,\n  })\n  const store = useSduiLayoutAction()\n\n  const handleClick = () =\u003e {\n    store.updateNodeState(id, { checked: !state.checked })\n  }\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={handleClick}\u003e{state.label || 'Toggle'}\u003c/button\u003e\n      \u003cspan\u003e{state.checked ? 'ON' : 'OFF'}\u003c/span\u003e\n    \u003c/div\u003e\n  )\n}\n\n// StatusDisplay component: displays state from referenced toggle\nfunction StatusDisplay({ id }: { id: string }) {\n  const { referencedNodesMap } = useSduiNodeReference({\n    nodeId: id,\n    schema: toggleStateSchema, // Validates referenced node's state\n  })\n\n  // Access referenced node by ID\n  // Note: Consider managing node IDs as constants instead of hardcoded strings\n  const toggleNode = referencedNodesMap['toggle-node']\n\n  if (!toggleNode) {\n    return \u003cdiv\u003eNo reference\u003c/div\u003e\n  }\n\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003eStatus: {toggleNode.state.checked ? 'ON' : 'OFF'}\u003c/div\u003e\n      {toggleNode.state.label \u0026\u0026 \u003cdiv\u003eLabel: {toggleNode.state.label}\u003c/div\u003e}\n    \u003c/div\u003e\n  )\n}\n\nconst document: SduiLayoutDocument = {\n  version: '1.0.0',\n  root: {\n    id: 'root',\n    type: 'Container',\n    children: [\n      {\n        id: 'toggle-node',\n        type: 'Toggle',\n        state: {\n          checked: false,\n          label: 'Power',\n        },\n      },\n      {\n        id: 'status-display',\n        type: 'StatusDisplay',\n        reference: 'toggle-node', // Reference to toggle-node\n      },\n    ],\n  },\n}\n\nexport default function Page() {\n  return (\n    \u003cSduiLayoutRenderer\n      document={document}\n      components={{\n        Toggle: (id) =\u003e \u003cToggle id={id} /\u003e,\n        StatusDisplay: (id) =\u003e \u003cStatusDisplay id={id} /\u003e,\n      }}\n    /\u003e\n  )\n}\n```\n\n**Multiple References:**\n\nYou can also reference multiple nodes:\n\n```tsx\nconst document: SduiLayoutDocument = {\n  version: '1.0.0',\n  root: {\n    id: 'root',\n    type: 'Container',\n    children: [\n      {\n        id: 'source-node',\n        type: 'Card',\n        reference: ['target-1', 'target-2'], // Multiple references\n      },\n      {\n        id: 'target-1',\n        type: 'Card',\n        state: { count: 10 },\n      },\n      {\n        id: 'target-2',\n        type: 'Card',\n        state: { count: 20 },\n      },\n    ],\n  },\n}\n\nfunction Card({ id }: { id: string }) {\n  const { referencedNodesMap, referencedNodes } = useSduiNodeReference({ nodeId: id })\n\n  // Access by ID (O(1))\n  // Note: Consider managing node IDs as constants instead of hardcoded strings\n  const node1 = referencedNodesMap['target-1']\n  const node2 = referencedNodesMap['target-2']\n\n  // Or iterate over array\n  return (\n    \u003cdiv\u003e\n      {referencedNodes.map((node) =\u003e (\n        \u003cdiv key={node.id}\u003eCount: {String(node.state.count)}\u003c/div\u003e\n      ))}\n    \u003c/div\u003e\n  )\n}\n```\n\n**Key Benefits:**\n\n- ✅ Automatically subscribes to referenced nodes' changes\n- ✅ Type-safe access to referenced nodes' state\n- ✅ Efficient O(1) access via `referencedNodesMap`\n- ✅ Supports both single and multiple references\n\n### Step 5: Recursive Rendering - Container with Children\n\nWhen you have nested structures (like a Container with Cards inside), you need to recursively render children. Here's how:\n\n```tsx\n'use client'\n\nimport {\n  SduiLayoutRenderer,\n  useSduiNodeSubscription,\n  useRenderNode,\n  type ComponentFactory,\n} from '@lodado/sdui-template'\n\n// 1. Create a Container component that renders its children\nfunction Container({ id, parentPath = [] }: { id: string; parentPath?: string[] }) {\n  const { childrenIds } = useSduiNodeSubscription({ nodeId: id })\n  const { renderNode, currentPath } = useRenderNode({ nodeId: id, parentPath })\n\n  return (\n    \u003cdiv className=\"container p-4 border-2 border-gray-300 rounded-lg\"\u003e\n      \u003ch3 className=\"mb-2\"\u003eContainer: {id}\u003c/h3\u003e\n      \u003cdiv className=\"flex flex-col gap-2\"\u003e\n        {/* Recursively render each child */}\n        {childrenIds.map((childId) =\u003e (\n          \u003cdiv key={childId}\u003e{renderNode(childId, currentPath)}\u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n\n// 2. Create a simple Card component\nfunction Card({ id }: { id: string }) {\n  const { state } = useSduiNodeSubscription({ nodeId: id })\n\n  return (\n    \u003cdiv className=\"card p-3 bg-blue-100 rounded\"\u003e\n      \u003ch4\u003e{state.title || `Card ${id}`}\u003c/h4\u003e\n      {state.content \u0026\u0026 \u003cp\u003e{state.content}\u003c/p\u003e}\n    \u003c/div\u003e\n  )\n}\n\n// 3. Create factories\nconst ContainerFactory: ComponentFactory = (id, _renderNode, parentPath) =\u003e (\n  \u003cContainer id={id} parentPath={parentPath} /\u003e\n)\nconst CardFactory: ComponentFactory = (id) =\u003e \u003cCard id={id} /\u003e\n\n// 4. Document with nested structure\nconst document: SduiLayoutDocument = {\n  version: '1.0.0',\n  root: {\n    id: 'root',\n    type: 'Container',\n    children: [\n      {\n        id: 'card-1',\n        type: 'Card',\n        state: {\n          title: 'First Card',\n          content: 'This is inside the root container',\n        },\n      },\n      {\n        id: 'container-1',\n        type: 'Container',\n        children: [\n          {\n            id: 'card-2',\n            type: 'Card',\n            state: {\n              title: 'Nested Card',\n              content: 'This card is inside a nested container',\n            },\n          },\n          {\n            id: 'card-3',\n            type: 'Card',\n            state: {\n              title: 'Another Nested Card',\n              content: 'Also inside the nested container',\n            },\n          },\n        ],\n      },\n    ],\n  },\n}\n\nexport default function Page() {\n  return (\n    \u003cSduiLayoutRenderer\n      document={document}\n      components={{\n        Container: ContainerFactory,\n        Card: CardFactory,\n      }}\n    /\u003e\n  )\n}\n```\n\n**How Recursive Rendering Works:**\n\n1. **`useSduiNodeSubscription`**: Gets `childrenIds` array for the current node\n2. **`useRenderNode`**: Returns an object with `renderNode` function and `currentPath` (automatically calculated)\n3. **Map over children**: Loop through `childrenIds` and call `renderNode(childId, currentPath)` for each\n4. **Automatic recursion**: Each child renders itself, and if it has children, it renders them too!\n\n**Result Structure:**\n\n```text\nContainer (root)\n  ├── Card (card-1)\n  └── Container (container-1)\n      ├── Card (card-2)\n      └── Card (card-3)\n```\n\n**Key Points:**\n\n- ✅ `useRenderNode({ nodeId, parentPath })` returns an object with `renderNode` function and `currentPath` (automatically calculated)\n- ✅ `childrenIds` tells you which nodes are children of the current node\n- ✅ Pass `currentPath` to `renderNode` to maintain parent path tracking\n- ✅ Each component handles its own children, creating natural recursion\n- ✅ Works with any nesting depth automatically\n\n## 📋 Understanding the Schema\n\n### Document Structure\n\nYour SDUI document is a JSON object that describes your entire UI:\n\n```typescript\ninterface SduiLayoutDocument {\n  version: string // Required: Schema version (e.g., \"1.0.0\")\n  metadata?: {\n    // Optional: Document info\n    id?: string // Document identifier\n    name?: string // Document name\n    description?: string // Description\n    createdAt?: string // ISO 8601 timestamp\n    updatedAt?: string // ISO 8601 timestamp\n    author?: string // Author info\n  }\n  root: SduiLayoutNode // Required: Root node (your UI tree starts here)\n  variables?: Record\u003cstring, unknown\u003e // Optional: Global variables for all nodes\n}\n```\n\n### Node Structure\n\nEach node in your UI tree:\n\n```typescript\ninterface SduiLayoutNode {\n  id: string // Required: Unique ID (must be unique in document)\n  type: string // Required: Component type (must match your component map)\n  state?: Record\u003cstring, unknown\u003e // Optional: Component data (auto-filled as {} if omitted)\n  attributes?: Record\u003cstring, unknown\u003e // Optional: CSS/styling (auto-filled as {} if omitted)\n  children?: SduiLayoutNode[] // Optional: Child nodes (for nested structures)\n  reference?: string | string[] // Optional: Reference to other node(s) by ID\n}\n```\n\n### What Each Field Does\n\n#### Required Fields\n\n- **`id`** (string): Unique identifier. Like a React `key`, but for your entire document.\n- **`type`** (string): Component name. Must match a key in your `components` prop.\n\n#### Optional Fields (Auto-filled if omitted)\n\n- **`state`**: Component data. Examples:\n\n  ```tsx\n  state: {\n    title: 'Card Title',        // Text content\n    count: 42,                  // Numbers\n    checked: true,              // Booleans\n    items: ['a', 'b', 'c'],    // Arrays\n    config: { theme: 'dark' },  // Objects\n  }\n  ```\n\n  If you omit it, it becomes `{}` automatically.\n\n- **`attributes`**: CSS styling. Examples:\n\n  ```tsx\n  attributes: {\n    className: 'my-card primary',           // CSS classes\n    style: { padding: '20px', color: 'red' }, // Inline styles\n    'data-testid': 'card-1',               // Data attributes\n  }\n  ```\n\n  If you omit it, it becomes `{}` automatically.\n\n- **`children`**: Child nodes. Omit for leaf nodes (components with no children).\n\n### Minimal Example\n\nYou only need `id` and `type` - everything else is optional:\n\n```tsx\n// ✅ This works! state and attributes are auto-filled as {}\nconst minimalNode = {\n  id: 'card-1',\n  type: 'Card',\n}\n\n// ✅ With state (for components that need data)\nconst nodeWithData = {\n  id: 'card-1',\n  type: 'Card',\n  state: {\n    title: 'My Card',\n  },\n}\n\n// ✅ With children (for nested structures)\nconst nodeWithChildren = {\n  id: 'container-1',\n  type: 'Container',\n  children: [\n    { id: 'card-1', type: 'Card' },\n    { id: 'card-2', type: 'Card' },\n  ],\n}\n```\n\n### Best Practices\n\n1. **Keep IDs unique**: Every node needs a unique `id` within the document\n2. **Match types**: Node `type` must exist in your `components` prop\n3. **Use state for data**: Put component data in `state`, not `attributes`\n4. **Use attributes for styling**: CSS classes and styles go in `attributes`\n\n## 🎛️ Component Overrides\n\nOverride components by ID or type:\n\n```tsx\n'use client'\n\nimport { SduiLayoutRenderer, type ComponentFactory } from '@lodado/sdui-template'\n\n// Special component for a specific node\nconst SpecialCardFactory: ComponentFactory = (id) =\u003e \u003cdiv className=\"special-card\"\u003eSpecial: {id}\u003c/div\u003e\n\n// Override for all Card types\nconst CustomCardFactory: ComponentFactory = (id) =\u003e \u003cdiv className=\"custom-card\"\u003eCustom: {id}\u003c/div\u003e\n\nexport default function Page() {\n  return (\n    \u003cSduiLayoutRenderer\n      document={document}\n      componentOverrides={{\n        // Highest priority: Override by node ID\n        byNodeId: {\n          'special-card-1': SpecialCardFactory,\n        },\n        // Second priority: Override by node type\n        byNodeType: {\n          Card: CustomCardFactory,\n        },\n      }}\n    /\u003e\n  )\n}\n```\n\n**Priority Order:**\n\n1. `byNodeId` (highest) - Specific node overrides\n2. `byNodeType` - Type-based overrides\n3. `components` prop - Default component map\n4. `defaultComponentFactory` - Fallback (shows node info)\n\n## 📚 API Reference\n\n### Components\n\n#### `SduiLayoutRenderer`\n\nMain component that renders your SDUI document.\n\n**Props:**\n\n| Prop                 | Type                                | Required | Description                   |\n| -------------------- | ----------------------------------- | -------- | ----------------------------- |\n| `document`           | `SduiLayoutDocument`                | ✅       | Your SDUI document            |\n| `components`         | `Record\u003cstring, ComponentFactory\u003e`  | ❌       | Component factory map         |\n| `componentOverrides` | `{ byNodeId?, byNodeType? }`        | ❌       | Override specific nodes/types |\n| `onLayoutChange`     | `(doc: SduiLayoutDocument) =\u003e void` | ❌       | Called when layout changes    |\n| `onError`            | `(error: Error) =\u003e void`            | ❌       | Called on errors              |\n\n**Example:**\n\n```tsx\n\u003cSduiLayoutRenderer\n  document={myDocument}\n  components={{ Card: CardFactory, Toggle: ToggleFactory }}\n  onError={(error) =\u003e console.error(error)}\n/\u003e\n```\n\n#### `SduiLayoutProvider`\n\nContext provider (usually used internally, but available if needed).\n\n**Props:**\n\n- `store: SduiLayoutStore` - Store instance\n- `children: React.ReactNode` - Child components\n\n### Hooks\n\n#### `useSduiLayoutAction(): SduiLayoutStore`\n\nGet the store to update state and access store data.\n\n```tsx\nconst store = useSduiLayoutAction()\n\n// Update a node's state\nstore.updateNodeState('node-1', { count: 5 })\n\n// Access store state\nconst { rootId, nodes } = store.state\n```\n\n#### `useSduiNodeSubscription\u003cT\u003e(params): NodeData`\n\nSubscribe to a node's changes. Returns node info and auto re-renders when the node changes.\n\n**Parameters:**\n\n- `nodeId: string` - The node ID to subscribe to\n- `schema?: ZodSchema` - Optional Zod schema for validation and type inference\n\n**Returns:**\n\n```typescript\n{\n  node: SduiLayoutNode | undefined      // Full node object\n  type: string | undefined              // Node type\n  state: T                              // Node state (typed if schema provided)\n  childrenIds: string[]                 // Array of child node IDs\n  attributes: Record\u003cstring, unknown\u003e   // Node attributes\n  reference: string | string[] | undefined // Reference to other node(s)\n  exists: boolean                       // Whether node exists\n}\n```\n\n**Example:**\n\n```tsx\nconst { state, childrenIds, attributes } = useSduiNodeSubscription({\n  nodeId: 'card-1',\n  schema: cardStateSchema, // Optional: validates and types state\n})\n\n// state is now typed based on your schema!\nconsole.log(state.title) // TypeScript knows this exists\n```\n\n#### `useSduiNodeReference\u003cT\u003e(params): ReferencedNodesData`\n\nAccesses referenced nodes' information and subscribes to their changes. Use this hook when a node has a `reference` field pointing to other nodes.\n\n**Parameters:**\n\n- `nodeId: string` - Node ID that has a reference field\n- `schema?: ZodSchema` - Optional Zod schema for referenced nodes' state validation and type inference\n\n**Returns:**\n\n- `referencedNodes: Array\u003cReferencedNodeInfo\u003e` - Array of referenced node information (for iteration)\n- `referencedNodesMap: Record\u003cstring, ReferencedNodeInfo\u003e` - Map of referenced nodes by ID (for O(1) access)\n- `reference: string | string[] | undefined` - Original reference value\n- `hasReference: boolean` - Whether the node has any references\n\n**ReferencedNodeInfo includes:**\n\n- `id: string` - Referenced node ID\n- `node: SduiLayoutNode | undefined` - Node entity\n- `type: string | undefined` - Node type\n- `state: T` - Layout state (inferred from schema if provided)\n- `attributes: Record\u003cstring, unknown\u003e | undefined` - Node attributes\n- `exists: boolean` - Whether the node exists\n\n**Example:**\n\n```tsx\n// Single reference\nconst { referencedNodesMap } = useSduiNodeReference({ nodeId: 'source-node' })\n// Note: Consider managing node IDs as constants instead of hardcoded strings\nconst targetNode = referencedNodesMap['target-node-id']\nif (targetNode) {\n  console.log(targetNode.state.title)\n}\n\n// Multiple references\nconst { referencedNodes, referencedNodesMap } = useSduiNodeReference({\n  nodeId: 'source-node',\n  schema: cardStateSchema, // optional - validates referenced nodes' state\n})\nreferencedNodes.forEach((node) =\u003e {\n  console.log(node.state.title)\n})\n// Or access by ID\n// Note: Consider managing node IDs as constants instead of hardcoded strings\nconst node1 = referencedNodesMap['target-1']\nconst node2 = referencedNodesMap['target-2']\n```\n\n#### `useRenderNode(params): UseRenderNodeReturn`\n\nReturns an object with `renderNode` function and node information including automatically calculated `currentPath`.\n\n**Parameters:**\n\n- `params.nodeId: string` - Current node ID (required)\n- `params.componentMap?: Record\u003cstring, ComponentFactory\u003e` - Optional component map\n- `params.parentPath?: ParentPath` - Parent node ID path (default: `[]`)\n\n**Returns:**\n\n```typescript\n{\n  renderNode: RenderNodeFn // Function to render child nodes\n  currentPath: ParentPath // Current path array (automatically calculated)\n  pathString: string // Current path string (automatically calculated)\n  nodeId: string // Current node ID\n  parentPath: ParentPath // Parent node ID path\n}\n```\n\n**Example:**\n\n```tsx\nfunction Container({ id }: { id: string }) {\n  const { childrenIds } = useSduiNodeSubscription({ nodeId: id })\n  const { renderNode, currentPath } = useRenderNode({ nodeId: id, parentPath: [] })\n\n  return (\n    \u003cdiv\u003e\n      {childrenIds.map((childId) =\u003e (\n        \u003cdiv key={childId}\u003e{renderNode(childId, currentPath)}\u003c/div\u003e\n      ))}\n    \u003c/div\u003e\n  )\n}\n```\n\n### Store Methods\n\n#### `SduiLayoutStore`\n\nThe main store class. Usually accessed via `useSduiLayoutAction()` hook.\n\n**Query Methods (throw errors if not found):**\n\n```tsx\nstore.getNodeById(nodeId) // Get node (throws if not found)\nstore.getNodeTypeById(nodeId) // Get node type (throws if not found)\nstore.getChildrenIdsById(nodeId) // Get children IDs (throws if not found)\nstore.getLayoutStateById(nodeId) // Get state (returns {} if not set)\nstore.getAttributesById(nodeId) // Get attributes (returns {} if not set)\nstore.getReferenceById(nodeId) // Get reference (returns undefined if not set)\nstore.getRootId() // Get root ID (throws if not found)\nstore.getDocument() // Convert store to document\n```\n\n**Update Methods:**\n\n```tsx\nstore.updateLayout(document)                    // Update entire layout\nstore.updateNodeState(nodeId, partialState)     // Update node state\nstore.updateNodeAttributes(nodeId, attributes) // Update node attributes\nstore.updateNodeReference(nodeId, reference)   // Update node reference (string | string[] | undefined)\nstore.updateVariables(variables)                // Update global variables\nstore.updateVariable(key, value)                // Update single variable\nstore.deleteVariable(key)                       // Delete variable\nstore.cancelEdit(documentId?)                  // Cancel edits, restore original\n```\n\n**Subscription Methods:**\n\n```tsx\nconst unsubscribe = store.subscribeNode(nodeId, callback) // Subscribe to node changes\nconst unsubscribe = store.subscribeVersion(callback) // Subscribe to global changes\n```\n\n**Utility Methods:**\n\n```tsx\nstore.reset() // Reset to initial state\nstore.clearCache() // Clear cache and reset\n```\n\n## 🔧 TypeScript Types\n\nAll types are exported from the package:\n\n```tsx\nimport type {\n  SduiLayoutDocument, // Root document type\n  SduiLayoutNode, // Node type\n  SduiDocument, // Base document type\n  SduiNode, // Base node type\n  ComponentFactory, // Component factory function type\n  RenderNodeFn, // Render node function type\n  SduiLayoutStoreState, // Store state type\n  SduiLayoutStoreOptions, // Store options type\n  UseSduiNodeSubscriptionParams, // Hook params type\n  NormalizedSduiEntities, // Normalized entities type\n} from '@lodado/sdui-template'\n```\n\n## 🏗️ Architecture\n\nThis library uses a clean architecture:\n\n- **SubscriptionManager**: Manages observer pattern for efficient updates\n- **LayoutStateRepository**: Handles state storage and retrieval\n- **DocumentManager**: Manages document caching and serialization\n- **VariablesManager**: Manages global variables\n\n## ⚡ Performance\n\n- **Subscription-based re-renders**: Only changed nodes update\n- **Normalized data**: Efficient lookups using normalizr\n- **Minimal bundle**: \u003c 50KB gzipped\n\n## 🚀 Next.js App Router\n\nThis library is designed for Next.js App Router. All React components include `\"use client\"`:\n\n```tsx\n// app/page.tsx\n'use client'\n\nimport { SduiLayoutRenderer } from '@lodado/sdui-template'\n\nexport default function Page() {\n  return \u003cSduiLayoutRenderer document={document} /\u003e\n}\n```\n\n## 📝 License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flodado%2Fsdui-template","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flodado%2Fsdui-template","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flodado%2Fsdui-template/lists"}