@lodado/sdui-template 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENCE +8 -0
  2. package/README.md +462 -0
  3. package/dist/cjs/client/index.cjs +1 -0
  4. package/dist/es/client/src/components/componentMap.mjs +2 -0
  5. package/dist/es/client/src/index.mjs +1 -0
  6. package/dist/es/client/src/react-wrapper/components/SduiLayoutRenderer.mjs +2 -0
  7. package/dist/es/client/src/react-wrapper/context/SduiLayoutContext.mjs +2 -0
  8. package/dist/es/client/src/react-wrapper/hooks/useRenderNode.mjs +2 -0
  9. package/dist/es/client/src/react-wrapper/hooks/useSduiLayoutAction.mjs +2 -0
  10. package/dist/es/client/src/react-wrapper/hooks/useSduiNodeSubscription.mjs +2 -0
  11. package/dist/es/client/src/store/SduiLayoutStore.mjs +1 -0
  12. package/dist/es/client/src/store/errors.mjs +1 -0
  13. package/dist/es/client/src/store/managers/DocumentManager.mjs +1 -0
  14. package/dist/es/client/src/store/managers/LayoutStateRepository.mjs +1 -0
  15. package/dist/es/client/src/store/managers/SubscriptionManager.mjs +1 -0
  16. package/dist/es/client/src/store/managers/VariablesManager.mjs +1 -0
  17. package/dist/es/client/src/utils/normalize/denormalize.mjs +1 -0
  18. package/dist/es/client/src/utils/normalize/normalize.mjs +1 -0
  19. package/dist/types/index.d.ts +1 -0
  20. package/dist/types/setupTests.d.ts +1 -0
  21. package/dist/types/src/components/componentMap.d.ts +15 -0
  22. package/dist/types/src/components/index.d.ts +7 -0
  23. package/dist/types/src/components/types.d.ts +18 -0
  24. package/dist/types/src/index.d.ts +38 -0
  25. package/dist/types/src/react-wrapper/components/SduiLayoutRenderer.d.ts +62 -0
  26. package/dist/types/src/react-wrapper/components/index.d.ts +6 -0
  27. package/dist/types/src/react-wrapper/context/SduiLayoutContext.d.ts +37 -0
  28. package/dist/types/src/react-wrapper/context/index.d.ts +6 -0
  29. package/dist/types/src/react-wrapper/hooks/index.d.ts +8 -0
  30. package/dist/types/src/react-wrapper/hooks/useRenderNode.d.ts +10 -0
  31. package/dist/types/src/react-wrapper/hooks/useSduiLayoutAction.d.ts +15 -0
  32. package/dist/types/src/react-wrapper/hooks/useSduiNodeSubscription.d.ts +45 -0
  33. package/dist/types/src/react-wrapper/index.d.ts +8 -0
  34. package/dist/types/src/schema/base.d.ts +44 -0
  35. package/dist/types/src/schema/document.d.ts +16 -0
  36. package/dist/types/src/schema/index.d.ts +8 -0
  37. package/dist/types/src/schema/node.d.ts +19 -0
  38. package/dist/types/src/store/SduiLayoutStore.d.ts +192 -0
  39. package/dist/types/src/store/errors.d.ts +37 -0
  40. package/dist/types/src/store/index.d.ts +8 -0
  41. package/dist/types/src/store/managers/DocumentManager.d.ts +67 -0
  42. package/dist/types/src/store/managers/LayoutStateRepository.d.ts +110 -0
  43. package/dist/types/src/store/managers/SubscriptionManager.d.ts +48 -0
  44. package/dist/types/src/store/managers/VariablesManager.d.ts +38 -0
  45. package/dist/types/src/store/managers/index.d.ts +9 -0
  46. package/dist/types/src/store/types.d.ts +46 -0
  47. package/dist/types/src/utils/normalize/denormalize.d.ts +24 -0
  48. package/dist/types/src/utils/normalize/index.d.ts +8 -0
  49. package/dist/types/src/utils/normalize/normalize.d.ts +28 -0
  50. package/dist/types/src/utils/normalize/types.d.ts +15 -0
  51. package/package.json +89 -0
package/LICENCE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2024 lodado
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,462 @@
1
+ # @lodado/sdui-template
2
+
3
+ Server-Driven UI Template Library for React. A flexible and powerful template system for building server-driven user interfaces with dynamic layouts and components.
4
+
5
+ ## Problem
6
+
7
+ Many applications require dynamically controlling UI structure and layout from the server. Common use cases include:
8
+
9
+ - **Dashboard Builders**: Users configure dashboards via drag-and-drop, and saved layouts are loaded from the server and rendered
10
+ - **Dynamic Form Generators**: Form structure is defined on the server and dynamically rendered on the client
11
+ - **Content Management Systems**: Administrators configure page layouts, and users see the same layout
12
+ - **A/B Testing**: Server sends different UI layouts for experimentation
13
+
14
+ In these situations, implementing state management, subscription systems, and component rendering logic from scratch for each new project is inefficient and error-prone.
15
+
16
+ ## Solution
17
+
18
+ **SDUI (Server-Driven UI)** is a pattern where UI structure is defined on the server and dynamically rendered on the client. This library provides the core logic for implementing the SDUI pattern with:
19
+
20
+ - ✅ **Reusable**: Implement once, use across multiple projects
21
+ - ✅ **Performance Optimized**: Subscription-based re-rendering updates only changed nodes
22
+ - ✅ **Flexible**: Component overrides allow project-specific customization
23
+ - ✅ **Type Safe**: Full TypeScript support with optional Zod schema validation
24
+ - ✅ **Next.js Compatible**: Works seamlessly with Next.js App Router
25
+
26
+ ## Features
27
+
28
+ - 🎯 **Server-Driven UI**: Define layouts from server-side configuration
29
+ - ⚡ **Performance Optimized**: ID-based subscription system for minimal re-renders
30
+ - 🔄 **Normalize/Denormalize**: Efficient data structure using normalizr
31
+ - 🎨 **Type Safe**: Full TypeScript support with optional Zod schema validation
32
+ - 🧩 **Modular**: Clean architecture with separated concerns
33
+ - 🚀 **Next.js Compatible**: Works seamlessly with Next.js App Router
34
+ - 🔧 **Flexible State Management**: Update component state programmatically
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install @lodado/sdui-template
40
+ # or
41
+ pnpm add @lodado/sdui-template
42
+ # or
43
+ yarn add @lodado/sdui-template
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ### Basic Usage
49
+
50
+ ```tsx
51
+ 'use client'
52
+
53
+ import { SduiLayoutRenderer } from '@lodado/sdui-template'
54
+ import type { SduiLayoutDocument } from '@lodado/sdui-template'
55
+
56
+ // Define your SDUI document (typically received from server)
57
+ const document: SduiLayoutDocument = {
58
+ version: '1.0.0',
59
+ metadata: {
60
+ id: 'my-layout',
61
+ name: 'My Layout',
62
+ },
63
+ root: {
64
+ id: 'root',
65
+ type: 'Container',
66
+ state: {},
67
+ children: [
68
+ {
69
+ id: 'card-1',
70
+ type: 'Card',
71
+ state: {
72
+ title: 'Card 1',
73
+ content: 'First card content',
74
+ },
75
+ },
76
+ {
77
+ id: 'card-2',
78
+ type: 'Card',
79
+ state: {
80
+ title: 'Card 2',
81
+ content: 'Second card content',
82
+ },
83
+ },
84
+ ],
85
+ },
86
+ }
87
+
88
+ export default function Page() {
89
+ return <SduiLayoutRenderer document={document} />
90
+ }
91
+ ```
92
+
93
+ ### Custom Components with State Management
94
+
95
+ ```tsx
96
+ 'use client'
97
+
98
+ import {
99
+ SduiLayoutRenderer,
100
+ useSduiNodeSubscription,
101
+ useSduiLayoutAction,
102
+ type ComponentFactory,
103
+ } from '@lodado/sdui-template'
104
+ import { z } from 'zod'
105
+
106
+ // Define state schema for type safety
107
+ const toggleStateSchema = z.object({
108
+ checked: z.boolean(),
109
+ label: z.string().optional(),
110
+ })
111
+
112
+ // Create your component
113
+ function Toggle({ id }: { id: string }) {
114
+ const { state } = useSduiNodeSubscription({
115
+ nodeId: id,
116
+ schema: toggleStateSchema, // Optional: validates state structure
117
+ })
118
+ const store = useSduiLayoutAction()
119
+
120
+ const handleToggle = () => {
121
+ store.updateNodeState(id, {
122
+ checked: !state.checked,
123
+ })
124
+ }
125
+
126
+ return (
127
+ <div className="flex items-center gap-2">
128
+ {state.label && <label>{state.label}</label>}
129
+ <button onClick={handleToggle}>{state.checked ? 'ON' : 'OFF'}</button>
130
+ </div>
131
+ )
132
+ }
133
+
134
+ // Define component factory
135
+ const ToggleFactory: ComponentFactory = (id) => <Toggle id={id} />
136
+
137
+ const document = {
138
+ version: '1.0.0',
139
+ root: {
140
+ id: 'root',
141
+ type: 'Container',
142
+ state: {},
143
+ children: [
144
+ {
145
+ id: 'toggle-1',
146
+ type: 'Toggle',
147
+ state: {
148
+ checked: false,
149
+ label: 'Enable notifications',
150
+ },
151
+ },
152
+ ],
153
+ },
154
+ }
155
+
156
+ export default function Page() {
157
+ return <SduiLayoutRenderer document={document} components={{ Toggle: ToggleFactory }} />
158
+ }
159
+ ```
160
+
161
+ ### Complete Example: Toggle Component
162
+
163
+ Here's a complete example showing how to create an interactive component with state management:
164
+
165
+ ```tsx
166
+ 'use client'
167
+
168
+ import {
169
+ SduiLayoutRenderer,
170
+ useSduiNodeSubscription,
171
+ useSduiLayoutAction,
172
+ type ComponentFactory,
173
+ } from '@lodado/sdui-template'
174
+ import { z } from 'zod'
175
+
176
+ // 1. Define state schema
177
+ const toggleStateSchema = z.object({
178
+ checked: z.boolean(),
179
+ label: z.string().optional(),
180
+ })
181
+
182
+ // 2. Create component that subscribes to node state
183
+ function Toggle({ id }: { id: string }) {
184
+ const { state } = useSduiNodeSubscription({
185
+ nodeId: id,
186
+ schema: toggleStateSchema, // Validates and types state
187
+ })
188
+ const store = useSduiLayoutAction()
189
+
190
+ const handleToggle = () => {
191
+ // Update state - only this component re-renders
192
+ store.updateNodeState(id, {
193
+ checked: !state.checked,
194
+ })
195
+ }
196
+
197
+ return (
198
+ <div className="flex items-center gap-2 p-3">
199
+ {state.label && <label>{state.label}</label>}
200
+ <button
201
+ onClick={handleToggle}
202
+ className={`w-11 h-6 rounded-full transition-colors ${state.checked ? 'bg-blue-600' : 'bg-gray-400'}`}
203
+ >
204
+ <span
205
+ className={`block w-5 h-5 bg-white rounded-full transition-transform ${
206
+ state.checked ? 'translate-x-5' : 'translate-x-0'
207
+ }`}
208
+ />
209
+ </button>
210
+ </div>
211
+ )
212
+ }
213
+
214
+ // 3. Create component factory
215
+ const ToggleFactory: ComponentFactory = (id) => <Toggle id={id} />
216
+
217
+ // 4. Define SDUI document
218
+ const document = {
219
+ version: '1.0.0',
220
+ root: {
221
+ id: 'root',
222
+ type: 'Container',
223
+ state: {},
224
+ children: [
225
+ {
226
+ id: 'toggle-1',
227
+ type: 'Toggle',
228
+ state: {
229
+ checked: false,
230
+ label: 'Enable notifications',
231
+ },
232
+ },
233
+ {
234
+ id: 'toggle-2',
235
+ type: 'Toggle',
236
+ state: {
237
+ checked: true,
238
+ label: 'Dark mode',
239
+ },
240
+ },
241
+ ],
242
+ },
243
+ }
244
+
245
+ // 5. Render with component map
246
+ export default function Page() {
247
+ return (
248
+ <SduiLayoutRenderer
249
+ document={document}
250
+ components={{ Toggle: ToggleFactory }}
251
+ onError={(error) => console.error('SDUI Error:', error)}
252
+ />
253
+ )
254
+ }
255
+ ```
256
+
257
+ **Key Benefits:**
258
+
259
+ - ✅ Only the clicked toggle re-renders (performance optimized)
260
+ - ✅ Type-safe state with Zod validation
261
+ - ✅ Server controls initial state, client handles interactions
262
+ - ✅ Easy to extend with more components
263
+
264
+ ### Component Overrides
265
+
266
+ ```tsx
267
+ 'use client'
268
+
269
+ import { SduiLayoutRenderer, type ComponentFactory } from '@lodado/sdui-template'
270
+
271
+ const SpecialCardFactory: ComponentFactory = (id) => <div className="special-card">Special: {id}</div>
272
+
273
+ export default function Page() {
274
+ return (
275
+ <SduiLayoutRenderer
276
+ document={document}
277
+ componentOverrides={{
278
+ // Override by node ID (highest priority)
279
+ byNodeId: {
280
+ 'special-card-1': SpecialCardFactory,
281
+ },
282
+ // Override by node type
283
+ byNodeType: {
284
+ Card: CustomCardFactory,
285
+ },
286
+ }}
287
+ />
288
+ )
289
+ }
290
+ ```
291
+
292
+ ## API Reference
293
+
294
+ ### Components
295
+
296
+ #### `SduiLayoutRenderer`
297
+
298
+ Main component for rendering SDUI layouts.
299
+
300
+ **Props:**
301
+
302
+ - `document: SduiLayoutDocument` - SDUI Layout Document (required)
303
+ - `components?: Record<string, ComponentFactory>` - Custom component map
304
+ - `componentOverrides?: { byNodeId?: Record<string, ComponentFactory>, byNodeType?: Record<string, ComponentFactory> }` - Component overrides
305
+ - `onLayoutChange?: (document: SduiLayoutDocument) => void` - Layout change callback
306
+ - `onError?: (error: Error) => void` - Error callback
307
+
308
+ #### `SduiLayoutProvider`
309
+
310
+ Context provider for SDUI Layout Store.
311
+
312
+ **Props:**
313
+
314
+ - `store: SduiLayoutStore` - Store instance
315
+ - `children: React.ReactNode` - Child components
316
+
317
+ ### Hooks
318
+
319
+ #### `useSduiLayoutAction(): SduiLayoutStore`
320
+
321
+ Returns store instance for calling actions and accessing store state.
322
+
323
+ ```tsx
324
+ const store = useSduiLayoutAction()
325
+ store.updateNodeState(nodeId, { count: 5 })
326
+
327
+ // Access store state directly (if needed)
328
+ const { rootId, nodes } = store.state
329
+ ```
330
+
331
+ #### `useSduiNodeSubscription<T>(params: { nodeId: string, schema?: ZodSchema }): NodeData`
332
+
333
+ Subscribes to a specific node's changes and returns node information.
334
+
335
+ **Parameters:**
336
+
337
+ - `nodeId: string` - Node ID to subscribe to
338
+ - `schema?: ZodSchema` - Optional Zod schema for state validation and type inference
339
+
340
+ **Returns:**
341
+
342
+ - `node: SduiLayoutNode | undefined` - Node entity
343
+ - `type: string | undefined` - Node type
344
+ - `state: T` - Layout state (inferred from schema if provided, otherwise `BaseLayoutState`)
345
+ - `childrenIds: string[]` - Array of child node IDs
346
+ - `attributes: Record<string, unknown> | undefined` - Node attributes
347
+ - `exists: boolean` - Whether the node exists
348
+
349
+ ```tsx
350
+ const { node, state, childrenIds, attributes, exists } = useSduiNodeSubscription({
351
+ nodeId: 'node-1',
352
+ schema: baseLayoutStateSchema, // optional - validates and types state
353
+ })
354
+ ```
355
+
356
+ #### `useRenderNode(componentMap?: Record<string, ComponentFactory>): RenderNodeFn`
357
+
358
+ Returns a function to render child nodes (internal use).
359
+
360
+ ### Store
361
+
362
+ #### `SduiLayoutStore`
363
+
364
+ Main store class for managing SDUI layout state.
365
+
366
+ **Getters:**
367
+
368
+ - `state: SduiLayoutStoreState` - Current store state
369
+ - `nodes: Record<string, SduiLayoutNode>` - Node entities
370
+ - `layoutStates: Record<string, BaseLayoutState>` - Layout states
371
+ - `layoutAttributes: Record<string, Record<string, unknown>>` - Layout attributes
372
+ - `metadata: SduiLayoutDocument['metadata'] | undefined` - Document metadata
373
+ - `getComponentOverrides(): Record<string, ComponentFactory>` - Get component overrides
374
+
375
+ **Query Methods:**
376
+
377
+ - `getNodeById(nodeId: string): SduiLayoutNode | undefined` - Get node by ID
378
+ - `getNodeTypeById(nodeId: string): string | undefined` - Get node type by ID
379
+ - `getChildrenIdsById(nodeId: string): string[]` - Get children IDs by node ID
380
+ - `getLayoutStateById(nodeId: string): BaseLayoutState | undefined` - Get layout state by ID
381
+ - `getAttributesById(nodeId: string): Record<string, unknown> | undefined` - Get attributes by ID
382
+ - `getRootId(): string | undefined` - Get root node ID
383
+ - `getDocument(): SduiLayoutDocument | null` - Convert current state to document
384
+
385
+ **Update Methods:**
386
+
387
+ - `updateLayout(document: SduiLayoutDocument): void` - Update layout document
388
+ - `updateNodeState(nodeId: string, state: Partial<BaseLayoutState>): void` - Update node state
389
+ - `updateNodeAttributes(nodeId: string, attributes: Partial<Record<string, unknown>>): void` - Update node attributes
390
+ - `updateVariables(variables: Record<string, unknown>): void` - Update global variables
391
+ - `updateVariable(key: string, value: unknown): void` - Update single variable
392
+ - `deleteVariable(key: string): void` - Delete variable
393
+ - `cancelEdit(documentId?: string): void` - Cancel edits and restore original document
394
+
395
+ **Selection Methods:**
396
+
397
+ - `setSelectedNodeId(nodeId?: string): void` - Set selected node ID
398
+
399
+ **Subscription Methods:**
400
+
401
+ - `subscribeNode(nodeId: string, callback: () => void): () => void` - Subscribe to node changes
402
+ - `subscribeVersion(callback: () => void): () => void` - Subscribe to global changes
403
+
404
+ **Utility Methods:**
405
+
406
+ - `reset(): void` - Reset store to initial state
407
+ - `clearCache(): void` - Clear cache and reset store
408
+
409
+ ## TypeScript Types
410
+
411
+ All types are exported from the main package:
412
+
413
+ ```tsx
414
+ import type {
415
+ SduiLayoutDocument,
416
+ SduiLayoutNode,
417
+ BaseLayoutState,
418
+ LayoutPosition,
419
+ SduiDocument,
420
+ SduiNode,
421
+ ComponentFactory,
422
+ RenderNodeFn,
423
+ SduiLayoutStoreState,
424
+ SduiLayoutStoreOptions,
425
+ UseSduiNodeSubscriptionParams,
426
+ NormalizedSduiEntities,
427
+ } from '@lodado/sdui-template'
428
+ ```
429
+
430
+ ## Architecture
431
+
432
+ This library uses a clean architecture with separated concerns:
433
+
434
+ - **SubscriptionManager**: Manages observer pattern for state changes
435
+ - **LayoutStateRepository**: Handles state storage and retrieval
436
+ - **DocumentManager**: Manages document caching and serialization
437
+ - **VariablesManager**: Manages global variables
438
+
439
+ ## Performance
440
+
441
+ - Subscription-based re-renders ensure only changed nodes update
442
+ - Normalized data structure for efficient lookups
443
+ - Minimal bundle size (< 50KB gzipped)
444
+
445
+ ## Next.js App Router
446
+
447
+ This library is designed to work with Next.js App Router. All React components include the `"use client"` directive and should be used in client components.
448
+
449
+ ```tsx
450
+ // app/page.tsx
451
+ 'use client'
452
+
453
+ import { SduiLayoutRenderer } from '@lodado/sdui-template'
454
+
455
+ export default function Page() {
456
+ return <SduiLayoutRenderer document={document} />
457
+ }
458
+ ```
459
+
460
+ ## License
461
+
462
+ MIT
@@ -0,0 +1 @@
1
+ "use strict";var e=require("react/jsx-runtime"),t=require("react"),s=require("lodash-es"),r=require("normalizr");const i=t.createContext(null),o=({store:s,children:r})=>{const o=t.useMemo(()=>({store:s}),[s]);return e.jsx(i.Provider,{value:o,children:r})},n=()=>{const e=t.useContext(i);if(!e)throw new Error("useSduiLayoutContext must be used within SduiLayoutProvider");return e},d=()=>{const{store:e}=n();return e},a=e=>e+1;function c(e){const{nodeId:s,schema:r}=e,[,i]=t.useReducer(a,0),o=d();let n;t.useEffect(()=>o.subscribeNode(s,i),[o,s]);let c,u,h=[];try{n=o.getNodeById(s),h=(null==n?void 0:n.childrenIds)||[]}catch(e){n=void 0,h=[]}try{c=o.getLayoutStateById(s)}catch(e){c=void 0}try{u=o.getAttributesById(s)}catch(e){u=void 0}const l=t.useMemo(()=>{if(!r)return c;if(!c)throw new Error(`State not found for node "${s}". Schema was provided but state is missing.`);const e=r.safeParse(c);if(!e.success)throw new Error(`State validation failed for node "${s}": ${e.error.message}`);return e.data},[c,r,s]);return{node:n,type:null==n?void 0:n.type,state:l,childrenIds:h,attributes:u,exists:!!n}}const u={},h=({id:t,renderNode:s})=>{const{type:r,childrenIds:i}=c({nodeId:t});return r?e.jsxs("div",{"data-sdui-node-id":t,"data-sdui-node-type":r,children:[e.jsxs("div",{children:["Type: ",r]}),e.jsxs("div",{children:["ID: ",t]}),i&&i.length>0&&e.jsx("div",{children:i.map(t=>e.jsx("div",{children:s(t)},t))})]}):null},l=(t,s)=>e.jsx(h,{id:t,renderNode:s});class p extends Error{constructor(e){super(`Node not found: "${e}"`),this.name="NodeNotFoundError",Error.captureStackTrace&&Error.captureStackTrace(this,p)}}class y extends Error{constructor(){super("Root node ID not found"),this.name="RootNotFoundError",Error.captureStackTrace&&Error.captureStackTrace(this,y)}}class g extends Error{constructor(){super("Metadata not found"),this.name="MetadataNotFoundError",Error.captureStackTrace&&Error.captureStackTrace(this,g)}}function b(e,t){var s;const r=null===(s=t.nodes)||void 0===s?void 0:s[e];if(!r)return null;const i=[];r.childrenIds&&i.push(...r.childrenIds);const o=i.map(e=>b(e,t)).filter(e=>null!==e);return Object.assign({id:r.id,type:r.type,state:r.state||{},attributes:r.attributes||{}},o.length>0&&{children:o})}const _=new r.schema.Entity("nodes",{},{idAttribute:"id",processStrategy:e=>({id:e.id,type:e.type,state:e.state||{},attributes:e.attributes||{}})});function v(e){const t=e.children||[],s=t.length>0?r.normalize(t,[_]):{entities:{nodes:{}}},i={id:e.id,type:e.type,state:e.state||{},attributes:e.attributes||{}},o=r.normalize(i,_),n={nodes:Object.assign(Object.assign({},o.entities.nodes),s.entities.nodes)},d=e=>{var t,s;n.nodes&&n.nodes[e.id]?n.nodes[e.id]=Object.assign(Object.assign({},n.nodes[e.id]),{childrenIds:(null===(t=e.children)||void 0===t?void 0:t.map(e=>e.id))||[]}):n.nodes&&(n.nodes[e.id]={id:e.id,type:e.type,state:e.state||{},attributes:e.attributes||{},childrenIds:(null===(s=e.children)||void 0===s?void 0:s.map(e=>e.id))||[]}),e.children&&e.children.forEach(d)};return d(e),{result:o.result,entities:n}}function m(e){const{result:t,entities:s}=v(e.root);return{result:t,entities:s}}_.define({children:[_]});class I{constructor(){this._cached={},this._originalCached={}}cacheDocument(e){var t;const r=(null===(t=e.metadata)||void 0===t?void 0:t.id)||e.root.id;this._cached[r]=e,this._originalCached[r]||(this._originalCached[r]=s.cloneDeep(e))}setMetadata(e){this._metadata=e}getMetadata(){return this._metadata}getOriginalDocument(e){return this._originalCached[e]}getDocumentId(e){var t;return(null===(t=this._metadata)||void 0===t?void 0:t.id)||e}getDocument(e){const t=e.getRootId();if(!t)return null;const s=b(t,{nodes:e.nodes});return s?{version:"1.0.0",metadata:this._metadata,root:s}:null}clearCache(){this._cached={},this._originalCached={}}reset(){this._metadata=void 0,this.clearCache()}}class N{constructor(){this._state={version:0,rootId:void 0,nodes:{},selectedNodeId:void 0,isEdited:!1,variables:{}}}get state(){return this._state}get nodes(){return this._state.nodes}getNodeById(e){return this._state.nodes[e]}getNodeTypeById(e){var t;return null===(t=this._state.nodes[e])||void 0===t?void 0:t.type}getChildrenIdsById(e){var t;return(null===(t=this._state.nodes[e])||void 0===t?void 0:t.childrenIds)||[]}getRootId(){return this._state.rootId}initializeState(e){this._state=Object.assign({version:0,rootId:void 0,nodes:{},selectedNodeId:void 0,isEdited:!1,variables:{}},e)}updateNodes(e){this._state.nodes=e}updateNodeState(e,t){const s=this._state.nodes[e];s&&(this._state.nodes[e]=Object.assign(Object.assign({},s),{state:t}))}updateNodeAttributes(e,t){const s=this._state.nodes[e];s&&(this._state.nodes[e]=Object.assign(Object.assign({},s),{attributes:t}))}setRootId(e){this._state.rootId=e}setSelectedNodeId(e){const t=this._state.selectedNodeId;return this._state.selectedNodeId=e,t}setEdited(e){this._state.isEdited=e}updateVariables(e){this._state.variables=e}incrementVersion(){this._state.version+=1}reset(){this._state.nodes={},this._state.rootId=void 0,this._state.isEdited=!1,this._state.selectedNodeId=void 0,this._state.variables={},this._state.version+=1}}class f{constructor(){this._nodeListeners=new Map,this._versionListeners=new Set}subscribeNode(e,t){return this._nodeListeners.has(e)||this._nodeListeners.set(e,new Set),this._nodeListeners.get(e).add(t),()=>{var s,r;null===(s=this._nodeListeners.get(e))||void 0===s||s.delete(t),0===(null===(r=this._nodeListeners.get(e))||void 0===r?void 0:r.size)&&this._nodeListeners.delete(e)}}subscribeVersion(e){return this._versionListeners.add(e),()=>{this._versionListeners.delete(e)}}notifyNode(e){var t;null===(t=this._nodeListeners.get(e))||void 0===t||t.forEach(e=>e())}notifyNodes(e){[...new Set(e)].forEach(e=>this.notifyNode(e))}notifyVersion(){this._versionListeners.forEach(e=>e())}}class M{constructor(e,t){this.repository=e,this.subscriptionManager=t}updateVariables(e){this.repository.updateVariables(s.cloneDeep(e)),this.repository.setEdited(!0),this.repository.incrementVersion(),this.subscriptionManager.notifyVersion()}updateVariable(e,t){const r=this.repository.state.variables;this.repository.updateVariables(Object.assign(Object.assign({},s.cloneDeep(r)),{[e]:s.cloneDeep(t)})),this.repository.setEdited(!0),this.repository.incrementVersion(),this.subscriptionManager.notifyVersion()}deleteVariable(e){const t=s.cloneDeep(this.repository.state.variables);delete t[e],this.repository.updateVariables(t),this.repository.setEdited(!0),this.repository.incrementVersion(),this.subscriptionManager.notifyVersion()}}class E{constructor(e,t){this._subscriptionManager=new f,this._repository=new N,this._documentManager=new I,this._componentOverrides={},this._repository.initializeState(e),this._variablesManager=new M(this._repository,this._subscriptionManager),this._componentOverrides=(null==t?void 0:t.componentOverrides)||{}}subscribeNode(e,t){return this._subscriptionManager.subscribeNode(e,t)}subscribeVersion(e){return this._subscriptionManager.subscribeVersion(e)}get state(){return this._repository.state}get nodes(){return this._repository.nodes}get metadata(){const e=this._documentManager.getMetadata();if(!e)throw new g;return e}getComponentOverrides(){return this._componentOverrides}getNodeById(e){const t=this._repository.getNodeById(e);if(!t)throw new p(e);return t}getNodeTypeById(e){const t=this._repository.getNodeTypeById(e);if(!t)throw new p(e);return t}getChildrenIdsById(e){if(!this._repository.getNodeById(e))throw new p(e);return this._repository.getChildrenIdsById(e)}getLayoutStateById(e){return this.getNodeById(e).state||{}}getAttributesById(e){return this.getNodeById(e).attributes||{}}getRootId(){const e=this._repository.getRootId();if(!e)throw new y;return e}updateLayout(e){const{entities:t}=m(e);this._repository.updateNodes(t.nodes||{}),this._repository.setRootId(e.root.id),this._repository.setEdited(!1),this._repository.updateVariables(e.variables?s.cloneDeep(e.variables):{}),this._repository.incrementVersion(),this._documentManager.setMetadata(e.metadata),this._documentManager.cacheDocument(e),this._subscriptionManager.notifyVersion()}cancelEdit(e){const t=this._repository.getRootId(),s=e||(t?this._documentManager.getDocumentId(t):void 0);if(!s)return;const r=this._documentManager.getOriginalDocument(s);r&&this.updateLayout(r)}updateNodeState(e,t){const s=this.getNodeById(e);this._repository.updateNodeState(e,Object.assign(Object.assign({},s.state||{}),t)),this._repository.setEdited(!0),this._subscriptionManager.notifyNode(e)}updateNodeAttributes(e,t){const s=this.getNodeById(e);this._repository.updateNodeAttributes(e,Object.assign(Object.assign({},s.attributes||{}),t)),this._repository.setEdited(!0),this._subscriptionManager.notifyNode(e)}updateVariables(e){this._variablesManager.updateVariables(e)}updateVariable(e,t){this._variablesManager.updateVariable(e,t)}deleteVariable(e){this._variablesManager.deleteVariable(e)}setSelectedNodeId(e){const t=this._repository.setSelectedNodeId(e);t&&this._subscriptionManager.notifyNode(t),e&&this._subscriptionManager.notifyNode(e)}getDocument(){return this._documentManager.getDocument(this._repository)}clearCache(){this._documentManager.clearCache(),this.reset()}reset(){this._documentManager.reset(),this._repository.reset(),this._subscriptionManager.notifyVersion()}}const S=e=>{const{store:s}=n(),{nodes:r}=s,i=t.useRef(null),o=t.useCallback(t=>{const o=r[t];if(!o)return null;const n=s.getComponentOverrides(),d=e||{};return(n[t]||n[o.type]||d[o.type]||l)(t,i.current)},[r,s,e]);return i.current=o,o},w=({id:e,componentMap:t})=>S(t)(e);exports.SduiLayoutProvider=o,exports.SduiLayoutRenderer=({document:s,components:r,componentOverrides:i,onLayoutChange:n,onError:d})=>{var a;const c=t.useMemo(()=>{try{if(!s||!s.root)throw new Error("Invalid document: missing root");if(!s.root.id)throw new Error("Invalid document: root.id is required");const e={componentOverrides:Object.assign(Object.assign(Object.assign({},r),null==i?void 0:i.byNodeType),null==i?void 0:i.byNodeId)},t=new E(void 0,e);return t.updateLayout(s),t}catch(e){return d&&d(e instanceof Error?e:new Error(String(e))),new E}},[s,r,i,d]),h=t.useMemo(()=>Object.assign(Object.assign({},u),r),[r]),l=null===(a=null==s?void 0:s.root)||void 0===a?void 0:a.id;return l?e.jsx(o,{store:c,children:e.jsx(w,{id:l,componentMap:h})}):null},exports.SduiLayoutStore=E,exports.denormalizeSduiLayout=function(e,t,s){const r=b(e,t);return r?{version:"1.0.0",metadata:s,root:r}:null},exports.denormalizeSduiNode=b,exports.normalizeSduiLayout=m,exports.normalizeSduiNode=v,exports.useRenderNode=S,exports.useSduiLayoutAction=d,exports.useSduiNodeSubscription=c;
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{jsx as d,jsxs as e}from"react/jsx-runtime";import{useSduiNodeSubscription as r}from"../react-wrapper/hooks/useSduiNodeSubscription.mjs";const i={},n=({id:i,renderNode:n})=>{const{type:o,childrenIds:t}=r({nodeId:i});return o?e("div",{"data-sdui-node-id":i,"data-sdui-node-type":o,children:[e("div",{children:["Type: ",o]}),e("div",{children:["ID: ",i]}),t&&t.length>0&&d("div",{children:t.map(e=>d("div",{children:n(e)},e))})]}):null},o=(e,r)=>d(n,{id:e,renderNode:r});export{i as componentMap,o as defaultComponentFactory};
@@ -0,0 +1 @@
1
+ export{SduiLayoutRenderer}from"./react-wrapper/components/SduiLayoutRenderer.mjs";export{SduiLayoutProvider}from"./react-wrapper/context/SduiLayoutContext.mjs";export{useRenderNode}from"./react-wrapper/hooks/useRenderNode.mjs";export{useSduiLayoutAction}from"./react-wrapper/hooks/useSduiLayoutAction.mjs";export{useSduiNodeSubscription}from"./react-wrapper/hooks/useSduiNodeSubscription.mjs";export{SduiLayoutStore}from"./store/SduiLayoutStore.mjs";export{denormalizeSduiLayout,denormalizeSduiNode}from"./utils/normalize/denormalize.mjs";export{normalizeSduiLayout,normalizeSduiNode}from"./utils/normalize/normalize.mjs";
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{jsx as o}from"react/jsx-runtime";import{useMemo as r}from"react";import{componentMap as t}from"../../components/componentMap.mjs";import{SduiLayoutStore as n}from"../../store/SduiLayoutStore.mjs";import{SduiLayoutProvider as e}from"../context/SduiLayoutContext.mjs";import{useRenderNode as i}from"../hooks/useRenderNode.mjs";const s=({id:o,componentMap:r})=>i(r)(o),d=({document:i,components:d,componentOverrides:m,onLayoutChange:c,onError:a})=>{var u;const p=r(()=>{try{if(!i||!i.root)throw new Error("Invalid document: missing root");if(!i.root.id)throw new Error("Invalid document: root.id is required");const o={componentOverrides:Object.assign(Object.assign(Object.assign({},d),null==m?void 0:m.byNodeType),null==m?void 0:m.byNodeId)},r=new n(void 0,o);return r.updateLayout(i),r}catch(o){return a&&a(o instanceof Error?o:new Error(String(o))),new n}},[i,d,m,a]),l=r(()=>Object.assign(Object.assign({},t),d),[d]),v=null===(u=null==i?void 0:i.root)||void 0===u?void 0:u.id;return v?o(e,{store:p,children:o(s,{id:v,componentMap:l})}):null};export{d as SduiLayoutRenderer,s as SduiLayoutRendererInner};
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{jsx as r}from"react/jsx-runtime";import{createContext as t,useMemo as e,useContext as o}from"react";const i=t(null),n=({store:t,children:o})=>{const n=e(()=>({store:t}),[t]);return r(i.Provider,{value:n,children:o})},u=()=>{const r=o(i);if(!r)throw new Error("useSduiLayoutContext must be used within SduiLayoutProvider");return r};export{n as SduiLayoutProvider,u as useSduiLayoutContext};
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{useRef as t,useCallback as o}from"react";import{defaultComponentFactory as r}from"../../components/componentMap.mjs";import{useSduiLayoutContext as n}from"../context/SduiLayoutContext.mjs";const e=e=>{const{store:m}=n(),{nodes:c}=m,p=t(null),s=o(t=>{const o=c[t];if(!o)return null;const n=m.getComponentOverrides(),s=e||{};return(n[t]||n[o.type]||s[o.type]||r)(t,p.current)},[c,m,e]);return p.current=s,s};export{e as useRenderNode};
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{useSduiLayoutContext as t}from"../context/SduiLayoutContext.mjs";const o=()=>{const{store:o}=t();return o};export{o as useSduiLayoutAction};
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{useReducer as t,useEffect as e,useMemo as o}from"react";import{useSduiLayoutAction as r}from"./useSduiLayoutAction.mjs";const s=t=>t+1;function d(d){const{nodeId:i,schema:n}=d,[,a]=t(s,0),c=r();let u;e(()=>c.subscribeNode(i,a),[c,i]);let f,l,y=[];try{u=c.getNodeById(i),y=(null==u?void 0:u.childrenIds)||[]}catch(t){u=void 0,y=[]}try{f=c.getLayoutStateById(i)}catch(t){f=void 0}try{l=c.getAttributesById(i)}catch(t){l=void 0}const h=o(()=>{if(!n)return f;if(!f)throw new Error(`State not found for node "${i}". Schema was provided but state is missing.`);const t=n.safeParse(f);if(!t.success)throw new Error(`State validation failed for node "${i}": ${t.error.message}`);return t.data},[f,n,i]);return{node:u,type:null==u?void 0:u.type,state:h,childrenIds:y,attributes:l,exists:!!u}}export{d as useSduiNodeSubscription};
@@ -0,0 +1 @@
1
+ import{cloneDeep as t}from"lodash-es";import{normalizeSduiLayout as e}from"../utils/normalize/normalize.mjs";import{MetadataNotFoundError as r,NodeNotFoundError as s,RootNotFoundError as i}from"./errors.mjs";import{DocumentManager as o}from"./managers/DocumentManager.mjs";import{LayoutStateRepository as a}from"./managers/LayoutStateRepository.mjs";import{SubscriptionManager as n}from"./managers/SubscriptionManager.mjs";import{VariablesManager as d}from"./managers/VariablesManager.mjs";class u{constructor(t,e){this._subscriptionManager=new n,this._repository=new a,this._documentManager=new o,this._componentOverrides={},this._repository.initializeState(t),this._variablesManager=new d(this._repository,this._subscriptionManager),this._componentOverrides=(null==e?void 0:e.componentOverrides)||{}}subscribeNode(t,e){return this._subscriptionManager.subscribeNode(t,e)}subscribeVersion(t){return this._subscriptionManager.subscribeVersion(t)}get state(){return this._repository.state}get nodes(){return this._repository.nodes}get metadata(){const t=this._documentManager.getMetadata();if(!t)throw new r;return t}getComponentOverrides(){return this._componentOverrides}getNodeById(t){const e=this._repository.getNodeById(t);if(!e)throw new s(t);return e}getNodeTypeById(t){const e=this._repository.getNodeTypeById(t);if(!e)throw new s(t);return e}getChildrenIdsById(t){if(!this._repository.getNodeById(t))throw new s(t);return this._repository.getChildrenIdsById(t)}getLayoutStateById(t){return this.getNodeById(t).state||{}}getAttributesById(t){return this.getNodeById(t).attributes||{}}getRootId(){const t=this._repository.getRootId();if(!t)throw new i;return t}updateLayout(r){const{entities:s}=e(r);this._repository.updateNodes(s.nodes||{}),this._repository.setRootId(r.root.id),this._repository.setEdited(!1),this._repository.updateVariables(r.variables?t(r.variables):{}),this._repository.incrementVersion(),this._documentManager.setMetadata(r.metadata),this._documentManager.cacheDocument(r),this._subscriptionManager.notifyVersion()}cancelEdit(t){const e=this._repository.getRootId(),r=t||(e?this._documentManager.getDocumentId(e):void 0);if(!r)return;const s=this._documentManager.getOriginalDocument(r);s&&this.updateLayout(s)}updateNodeState(t,e){const r=this.getNodeById(t);this._repository.updateNodeState(t,Object.assign(Object.assign({},r.state||{}),e)),this._repository.setEdited(!0),this._subscriptionManager.notifyNode(t)}updateNodeAttributes(t,e){const r=this.getNodeById(t);this._repository.updateNodeAttributes(t,Object.assign(Object.assign({},r.attributes||{}),e)),this._repository.setEdited(!0),this._subscriptionManager.notifyNode(t)}updateVariables(t){this._variablesManager.updateVariables(t)}updateVariable(t,e){this._variablesManager.updateVariable(t,e)}deleteVariable(t){this._variablesManager.deleteVariable(t)}setSelectedNodeId(t){const e=this._repository.setSelectedNodeId(t);e&&this._subscriptionManager.notifyNode(e),t&&this._subscriptionManager.notifyNode(t)}getDocument(){return this._documentManager.getDocument(this._repository)}clearCache(){this._documentManager.clearCache(),this.reset()}reset(){this._documentManager.reset(),this._repository.reset(),this._subscriptionManager.notifyVersion()}}export{u as SduiLayoutStore};
@@ -0,0 +1 @@
1
+ class r extends Error{constructor(t){super(`Node not found: "${t}"`),this.name="NodeNotFoundError",Error.captureStackTrace&&Error.captureStackTrace(this,r)}}class t extends Error{constructor(){super("Root node ID not found"),this.name="RootNotFoundError",Error.captureStackTrace&&Error.captureStackTrace(this,t)}}class o extends Error{constructor(){super("Metadata not found"),this.name="MetadataNotFoundError",Error.captureStackTrace&&Error.captureStackTrace(this,o)}}export{o as MetadataNotFoundError,r as NodeNotFoundError,t as RootNotFoundError};
@@ -0,0 +1 @@
1
+ import{cloneDeep as t}from"lodash-es";import{denormalizeSduiNode as a}from"../../utils/normalize/denormalize.mjs";import"../../utils/normalize/normalize.mjs";class e{constructor(){this._cached={},this._originalCached={}}cacheDocument(a){var e;const i=(null===(e=a.metadata)||void 0===e?void 0:e.id)||a.root.id;this._cached[i]=a,this._originalCached[i]||(this._originalCached[i]=t(a))}setMetadata(t){this._metadata=t}getMetadata(){return this._metadata}getOriginalDocument(t){return this._originalCached[t]}getDocumentId(t){var a;return(null===(a=this._metadata)||void 0===a?void 0:a.id)||t}getDocument(t){const e=t.getRootId();if(!e)return null;const i=a(e,{nodes:t.nodes});return i?{version:"1.0.0",metadata:this._metadata,root:i}:null}clearCache(){this._cached={},this._originalCached={}}reset(){this._metadata=void 0,this.clearCache()}}export{e as DocumentManager};
@@ -0,0 +1 @@
1
+ class t{constructor(){this._state={version:0,rootId:void 0,nodes:{},selectedNodeId:void 0,isEdited:!1,variables:{}}}get state(){return this._state}get nodes(){return this._state.nodes}getNodeById(t){return this._state.nodes[t]}getNodeTypeById(t){var e;return null===(e=this._state.nodes[t])||void 0===e?void 0:e.type}getChildrenIdsById(t){var e;return(null===(e=this._state.nodes[t])||void 0===e?void 0:e.childrenIds)||[]}getRootId(){return this._state.rootId}initializeState(t){this._state=Object.assign({version:0,rootId:void 0,nodes:{},selectedNodeId:void 0,isEdited:!1,variables:{}},t)}updateNodes(t){this._state.nodes=t}updateNodeState(t,e){const s=this._state.nodes[t];s&&(this._state.nodes[t]=Object.assign(Object.assign({},s),{state:e}))}updateNodeAttributes(t,e){const s=this._state.nodes[t];s&&(this._state.nodes[t]=Object.assign(Object.assign({},s),{attributes:e}))}setRootId(t){this._state.rootId=t}setSelectedNodeId(t){const e=this._state.selectedNodeId;return this._state.selectedNodeId=t,e}setEdited(t){this._state.isEdited=t}updateVariables(t){this._state.variables=t}incrementVersion(){this._state.version+=1}reset(){this._state.nodes={},this._state.rootId=void 0,this._state.isEdited=!1,this._state.selectedNodeId=void 0,this._state.variables={},this._state.version+=1}}export{t as LayoutStateRepository};
@@ -0,0 +1 @@
1
+ class e{constructor(){this._nodeListeners=new Map,this._versionListeners=new Set}subscribeNode(e,s){return this._nodeListeners.has(e)||this._nodeListeners.set(e,new Set),this._nodeListeners.get(e).add(s),()=>{var t,i;null===(t=this._nodeListeners.get(e))||void 0===t||t.delete(s),0===(null===(i=this._nodeListeners.get(e))||void 0===i?void 0:i.size)&&this._nodeListeners.delete(e)}}subscribeVersion(e){return this._versionListeners.add(e),()=>{this._versionListeners.delete(e)}}notifyNode(e){var s;null===(s=this._nodeListeners.get(e))||void 0===s||s.forEach(e=>e())}notifyNodes(e){[...new Set(e)].forEach(e=>this.notifyNode(e))}notifyVersion(){this._versionListeners.forEach(e=>e())}}export{e as SubscriptionManager};
@@ -0,0 +1 @@
1
+ import{cloneDeep as s}from"lodash-es";class t{constructor(s,t){this.repository=s,this.subscriptionManager=t}updateVariables(t){this.repository.updateVariables(s(t)),this.repository.setEdited(!0),this.repository.incrementVersion(),this.subscriptionManager.notifyVersion()}updateVariable(t,i){const e=this.repository.state.variables;this.repository.updateVariables(Object.assign(Object.assign({},s(e)),{[t]:s(i)})),this.repository.setEdited(!0),this.repository.incrementVersion(),this.subscriptionManager.notifyVersion()}deleteVariable(t){const i=s(this.repository.state.variables);delete i[t],this.repository.updateVariables(i),this.repository.setEdited(!0),this.repository.incrementVersion(),this.subscriptionManager.notifyVersion()}}export{t as VariablesManager};
@@ -0,0 +1 @@
1
+ function t(n,e){var r;const i=null===(r=e.nodes)||void 0===r?void 0:r[n];if(!i)return null;const s=[];i.childrenIds&&s.push(...i.childrenIds);const l=s.map(n=>t(n,e)).filter(t=>null!==t);return Object.assign({id:i.id,type:i.type,state:i.state||{},attributes:i.attributes||{}},l.length>0&&{children:l})}function n(n,e,r){const i=t(n,e);return i?{version:"1.0.0",metadata:r,root:i}:null}export{n as denormalizeSduiLayout,t as denormalizeSduiNode};
@@ -0,0 +1 @@
1
+ import{schema as t,normalize as e}from"normalizr";const i=new t.Entity("nodes",{},{idAttribute:"id",processStrategy:t=>({id:t.id,type:t.type,state:t.state||{},attributes:t.attributes||{}})});function s(t){const s=t.children||[],n=s.length>0?e(s,[i]):{entities:{nodes:{}}},d={id:t.id,type:t.type,state:t.state||{},attributes:t.attributes||{}},r=e(d,i),o={nodes:Object.assign(Object.assign({},r.entities.nodes),n.entities.nodes)},a=t=>{var e,i;o.nodes&&o.nodes[t.id]?o.nodes[t.id]=Object.assign(Object.assign({},o.nodes[t.id]),{childrenIds:(null===(e=t.children)||void 0===e?void 0:e.map(t=>t.id))||[]}):o.nodes&&(o.nodes[t.id]={id:t.id,type:t.type,state:t.state||{},attributes:t.attributes||{},childrenIds:(null===(i=t.children)||void 0===i?void 0:i.map(t=>t.id))||[]}),t.children&&t.children.forEach(a)};return a(t),{result:r.result,entities:o}}function n(t){const{result:e,entities:i}=s(t.root);return{result:e,entities:i}}i.define({children:[i]});export{n as normalizeSduiLayout,s as normalizeSduiNode};
@@ -0,0 +1 @@
1
+ export * from './src';
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,15 @@
1
+ import type { ComponentFactory } from './types';
2
+ /**
3
+ * 컴포넌트 맵
4
+ *
5
+ * 노드 타입별로 컴포넌트 팩토리를 매핑합니다.
6
+ * 기본적으로 비어있으며, consumers가 components prop을 통해 제공합니다.
7
+ */
8
+ export declare const componentMap: Record<string, ComponentFactory>;
9
+ /**
10
+ * 기본 컴포넌트 팩토리
11
+ *
12
+ * 노드 타입이 componentMap에 없을 때 사용되는 기본 팩토리입니다.
13
+ * 노드 정보를 표시하고 자식을 렌더링합니다.
14
+ */
15
+ export declare const defaultComponentFactory: ComponentFactory;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Server-Driven UI - Component System
3
+ *
4
+ * Component factory system exports
5
+ */
6
+ export * from "./componentMap";
7
+ export * from "./types";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Server-Driven UI - Component System Types
3
+ *
4
+ * 컴포넌트 팩토리 및 렌더링 함수 타입 정의
5
+ */
6
+ import type { ReactNode } from "react";
7
+ /**
8
+ * 자식 노드 렌더링 함수 타입 (Render Props)
9
+ *
10
+ * 상위에서 주입되어 자식 노드를 렌더링할 때 사용합니다.
11
+ */
12
+ export type RenderNodeFn = (childId: string) => ReactNode;
13
+ /**
14
+ * 컴포넌트 팩토리 타입
15
+ *
16
+ * id, renderNode를 받아서 컴포넌트를 렌더링합니다.
17
+ */
18
+ export type ComponentFactory = (id: string, renderNode: RenderNodeFn) => ReactNode;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @lodado/sdui-template
3
+ *
4
+ * Server-Driven UI Template Library for React
5
+ *
6
+ * A flexible and powerful template system for building server-driven user interfaces
7
+ * with dynamic layouts and components.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * import { SduiLayoutRenderer } from '@lodado/sdui-template';
12
+ *
13
+ * function App() {
14
+ * const document = {
15
+ * version: "1.0.0",
16
+ * root: {
17
+ * id: "root",
18
+ * type: "Container",
19
+ * state: {
20
+ * layout: { x: 0, y: 0, w: 12, h: 1 }
21
+ * }
22
+ * }
23
+ * };
24
+ *
25
+ * return <SduiLayoutRenderer document={document} />;
26
+ * }
27
+ * ```
28
+ */
29
+ export { SduiLayoutRenderer } from './react-wrapper/components/SduiLayoutRenderer';
30
+ export { SduiLayoutProvider } from './react-wrapper/context/SduiLayoutContext';
31
+ export { useRenderNode, useSduiLayoutAction, useSduiNodeSubscription } from './react-wrapper/hooks';
32
+ export type { UseSduiNodeSubscriptionParams } from './react-wrapper/hooks/useSduiNodeSubscription';
33
+ export { SduiLayoutStore } from './store/SduiLayoutStore';
34
+ export type { SduiLayoutStoreOptions, SduiLayoutStoreState } from './store/types';
35
+ export type { SduiDocument, SduiLayoutDocument, SduiLayoutNode, SduiNode, } from './schema';
36
+ export type { ComponentFactory, RenderNodeFn } from './components/types';
37
+ export { denormalizeSduiLayout, denormalizeSduiNode, normalizeSduiLayout, normalizeSduiNode } from './utils/normalize';
38
+ export type { NormalizedSduiEntities } from './utils/normalize/types';