@pascal-app/core 0.1.13 → 0.3.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 (74) hide show
  1. package/dist/events/bus.d.ts +15 -2
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/scene-registry.d.ts +5 -1
  4. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  5. package/dist/hooks/scene-registry/scene-registry.js +10 -1
  6. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +8 -8
  7. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  8. package/dist/hooks/spatial-grid/spatial-grid-manager.js +88 -36
  9. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +1 -1
  10. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  11. package/dist/hooks/spatial-grid/spatial-grid-sync.js +16 -8
  12. package/dist/hooks/spatial-grid/spatial-grid.d.ts +3 -3
  13. package/dist/hooks/spatial-grid/spatial-grid.d.ts.map +1 -1
  14. package/dist/hooks/spatial-grid/spatial-grid.js +2 -2
  15. package/dist/hooks/spatial-grid/use-spatial-query.d.ts.map +1 -1
  16. package/dist/hooks/spatial-grid/wall-spatial-grid.d.ts +2 -2
  17. package/dist/hooks/spatial-grid/wall-spatial-grid.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/wall-spatial-grid.js +2 -2
  19. package/dist/index.d.ts +6 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -1
  22. package/dist/lib/space-detection.d.ts.map +1 -1
  23. package/dist/lib/space-detection.js +3 -1
  24. package/dist/schema/collections.d.ts +11 -0
  25. package/dist/schema/collections.d.ts.map +1 -0
  26. package/dist/schema/collections.js +2 -0
  27. package/dist/schema/index.d.ts +11 -8
  28. package/dist/schema/index.d.ts.map +1 -1
  29. package/dist/schema/index.js +11 -7
  30. package/dist/schema/nodes/door.d.ts +78 -0
  31. package/dist/schema/nodes/door.d.ts.map +1 -0
  32. package/dist/schema/nodes/door.js +67 -0
  33. package/dist/schema/nodes/item.d.ts +234 -0
  34. package/dist/schema/nodes/item.d.ts.map +1 -1
  35. package/dist/schema/nodes/item.js +65 -1
  36. package/dist/schema/nodes/level.d.ts.map +1 -1
  37. package/dist/schema/nodes/level.js +11 -1
  38. package/dist/schema/nodes/roof-segment.d.ts +51 -0
  39. package/dist/schema/nodes/roof-segment.d.ts.map +1 -0
  40. package/dist/schema/nodes/roof-segment.js +36 -0
  41. package/dist/schema/nodes/roof.d.ts +1 -4
  42. package/dist/schema/nodes/roof.d.ts.map +1 -1
  43. package/dist/schema/nodes/roof.js +9 -16
  44. package/dist/schema/nodes/site.d.ts +46 -0
  45. package/dist/schema/nodes/site.d.ts.map +1 -1
  46. package/dist/schema/types.d.ts +191 -4
  47. package/dist/schema/types.d.ts.map +1 -1
  48. package/dist/schema/types.js +4 -0
  49. package/dist/store/actions/node-actions.d.ts.map +1 -1
  50. package/dist/store/actions/node-actions.js +23 -4
  51. package/dist/store/use-interactive.d.ts +18 -0
  52. package/dist/store/use-interactive.d.ts.map +1 -0
  53. package/dist/store/use-interactive.js +50 -0
  54. package/dist/store/use-scene.d.ts +10 -1
  55. package/dist/store/use-scene.d.ts.map +1 -1
  56. package/dist/store/use-scene.js +190 -57
  57. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  58. package/dist/systems/ceiling/ceiling-system.js +5 -0
  59. package/dist/systems/door/door-system.d.ts +2 -0
  60. package/dist/systems/door/door-system.d.ts.map +1 -0
  61. package/dist/systems/door/door-system.js +211 -0
  62. package/dist/systems/item/item-system.js +3 -2
  63. package/dist/systems/roof/roof-system.d.ts +11 -3
  64. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  65. package/dist/systems/roof/roof-system.js +705 -210
  66. package/dist/systems/slab/slab-system.js +3 -3
  67. package/dist/systems/wall/wall-footprint.d.ts +7 -0
  68. package/dist/systems/wall/wall-footprint.d.ts.map +1 -0
  69. package/dist/systems/wall/wall-footprint.js +49 -0
  70. package/dist/systems/wall/wall-mitering.js +2 -2
  71. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  72. package/dist/systems/wall/wall-system.js +13 -50
  73. package/dist/systems/window/window-system.js +3 -3
  74. package/package.json +6 -6
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ export declare const RoofType: z.ZodEnum<{
3
+ hip: "hip";
4
+ gable: "gable";
5
+ shed: "shed";
6
+ gambrel: "gambrel";
7
+ dutch: "dutch";
8
+ mansard: "mansard";
9
+ flat: "flat";
10
+ }>;
11
+ export type RoofType = z.infer<typeof RoofType>;
12
+ export declare const RoofSegmentNode: z.ZodObject<{
13
+ object: z.ZodDefault<z.ZodLiteral<"node">>;
14
+ name: z.ZodOptional<z.ZodString>;
15
+ parentId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
16
+ visible: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
17
+ camera: z.ZodOptional<z.ZodObject<{
18
+ position: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
19
+ target: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
20
+ mode: z.ZodDefault<z.ZodEnum<{
21
+ perspective: "perspective";
22
+ orthographic: "orthographic";
23
+ }>>;
24
+ fov: z.ZodOptional<z.ZodNumber>;
25
+ zoom: z.ZodOptional<z.ZodNumber>;
26
+ }, z.core.$strip>>;
27
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
28
+ id: z.ZodDefault<z.ZodTemplateLiteral<`rseg_${string}`>>;
29
+ type: z.ZodDefault<z.ZodLiteral<"roof-segment">>;
30
+ position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
31
+ rotation: z.ZodDefault<z.ZodNumber>;
32
+ roofType: z.ZodDefault<z.ZodEnum<{
33
+ hip: "hip";
34
+ gable: "gable";
35
+ shed: "shed";
36
+ gambrel: "gambrel";
37
+ dutch: "dutch";
38
+ mansard: "mansard";
39
+ flat: "flat";
40
+ }>>;
41
+ width: z.ZodDefault<z.ZodNumber>;
42
+ depth: z.ZodDefault<z.ZodNumber>;
43
+ wallHeight: z.ZodDefault<z.ZodNumber>;
44
+ roofHeight: z.ZodDefault<z.ZodNumber>;
45
+ wallThickness: z.ZodDefault<z.ZodNumber>;
46
+ deckThickness: z.ZodDefault<z.ZodNumber>;
47
+ overhang: z.ZodDefault<z.ZodNumber>;
48
+ shingleThickness: z.ZodDefault<z.ZodNumber>;
49
+ }, z.core.$strip>;
50
+ export type RoofSegmentNode = z.infer<typeof RoofSegmentNode>;
51
+ //# sourceMappingURL=roof-segment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-segment.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof-segment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,QAAQ;;;;;;;;EAA0E,CAAA;AAE/F,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA;AAE/C,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiC3B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA"}
@@ -0,0 +1,36 @@
1
+ import dedent from 'dedent';
2
+ import { z } from 'zod';
3
+ import { BaseNode, nodeType, objectId } from '../base';
4
+ export const RoofType = z.enum(['hip', 'gable', 'shed', 'gambrel', 'dutch', 'mansard', 'flat']);
5
+ export const RoofSegmentNode = BaseNode.extend({
6
+ id: objectId('rseg'),
7
+ type: nodeType('roof-segment'),
8
+ // Position relative to parent roof group
9
+ position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
10
+ // Rotation around Y axis in radians
11
+ rotation: z.number().default(0),
12
+ // Roof shape type
13
+ roofType: RoofType.default('gable'),
14
+ // Footprint dimensions
15
+ width: z.number().default(8),
16
+ depth: z.number().default(6),
17
+ // Vertical dimensions
18
+ wallHeight: z.number().default(0.5),
19
+ roofHeight: z.number().default(2.5),
20
+ // Structure thicknesses
21
+ wallThickness: z.number().default(0.1),
22
+ deckThickness: z.number().default(0.1),
23
+ overhang: z.number().default(0.3),
24
+ shingleThickness: z.number().default(0.05),
25
+ }).describe(dedent `
26
+ Roof segment node - an individual roof module within a roof group.
27
+ Each segment generates a complete architectural volume (walls + roof).
28
+ Multiple segments can be combined to form complex roof shapes.
29
+ - roofType: hip, gable, shed, gambrel, dutch, mansard, flat
30
+ - width/depth: footprint dimensions
31
+ - wallHeight: height of walls below the roof
32
+ - roofHeight: height of the roof peak above the walls
33
+ - wallThickness/deckThickness: structural thicknesses
34
+ - overhang: eave overhang distance
35
+ - shingleThickness: outer shingle layer thickness
36
+ `);
@@ -19,10 +19,7 @@ export declare const RoofNode: z.ZodObject<{
19
19
  type: z.ZodDefault<z.ZodLiteral<"roof">>;
20
20
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
21
21
  rotation: z.ZodDefault<z.ZodNumber>;
22
- length: z.ZodDefault<z.ZodNumber>;
23
- height: z.ZodDefault<z.ZodNumber>;
24
- leftWidth: z.ZodDefault<z.ZodNumber>;
25
- rightWidth: z.ZodDefault<z.ZodNumber>;
22
+ children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`rseg_${string}`>>>>;
26
23
  }, z.core.$strip>;
27
24
  export type RoofNode = z.infer<typeof RoofNode>;
28
25
  //# sourceMappingURL=roof.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"roof.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;iBA0BpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
1
+ {"version":3,"file":"roof.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;iBAkBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
@@ -1,28 +1,21 @@
1
1
  import dedent from 'dedent';
2
2
  import { z } from 'zod';
3
3
  import { BaseNode, nodeType, objectId } from '../base';
4
+ import { RoofSegmentNode } from './roof-segment';
4
5
  export const RoofNode = BaseNode.extend({
5
6
  id: objectId('roof'),
6
7
  type: nodeType('roof'),
7
- // Position of the roof center (Y should typically be 0)
8
+ // Position of the roof group center
8
9
  position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
9
10
  // Rotation around Y axis in radians
10
11
  rotation: z.number().default(0),
11
- // Length of the roof along the ridge direction (in meters)
12
- length: z.number().default(4),
13
- // Height of the roof peak from the base
14
- height: z.number().default(1.5),
15
- // Width of the left slope (in meters, measured horizontally from ridge)
16
- leftWidth: z.number().default(1.5),
17
- // Width of the right slope (in meters, measured horizontally from ridge)
18
- rightWidth: z.number().default(1.5),
12
+ // Child roof segment IDs
13
+ children: z.array(RoofSegmentNode.shape.id).default([]),
19
14
  }).describe(dedent `
20
- Roof node - used to represent a gable roof in the building
21
- - position: center position of the roof (Y typically 0)
15
+ Roof node - a container for roof segments.
16
+ Acts as a group that holds one or more RoofSegmentNodes.
17
+ When not being edited, segments are visually combined into a single solid.
18
+ - position: center position of the roof group
22
19
  - rotation: rotation around Y axis
23
- - length: length of the roof along the ridge
24
- - height: height of the roof peak
25
- - leftWidth: horizontal width of the left slope (from ridge to eave)
26
- - rightWidth: horizontal width of the right slope (from ridge to eave)
27
- Total width = leftWidth + rightWidth
20
+ - children: array of RoofSegmentNode IDs
28
21
  `);
@@ -62,6 +62,7 @@ export declare const SiteNode: z.ZodObject<{
62
62
  type: z.ZodDefault<z.ZodLiteral<"item">>;
63
63
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
64
64
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
65
+ scale: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
65
66
  side: z.ZodOptional<z.ZodEnum<{
66
67
  front: "front";
67
68
  back: "back";
@@ -69,6 +70,7 @@ export declare const SiteNode: z.ZodObject<{
69
70
  children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
70
71
  wallId: z.ZodOptional<z.ZodString>;
71
72
  wallT: z.ZodOptional<z.ZodNumber>;
73
+ collectionIds: z.ZodOptional<z.ZodArray<z.ZodCustom<`collection_${string}`, `collection_${string}`>>>;
72
74
  asset: z.ZodObject<{
73
75
  id: z.ZodString;
74
76
  category: z.ZodString;
@@ -88,6 +90,50 @@ export declare const SiteNode: z.ZodObject<{
88
90
  surface: z.ZodOptional<z.ZodObject<{
89
91
  height: z.ZodNumber;
90
92
  }, z.core.$strip>>;
93
+ interactive: z.ZodOptional<z.ZodObject<{
94
+ controls: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
95
+ kind: z.ZodLiteral<"toggle">;
96
+ label: z.ZodOptional<z.ZodString>;
97
+ default: z.ZodOptional<z.ZodBoolean>;
98
+ }, z.core.$strip>, z.ZodObject<{
99
+ kind: z.ZodLiteral<"slider">;
100
+ label: z.ZodString;
101
+ min: z.ZodNumber;
102
+ max: z.ZodNumber;
103
+ step: z.ZodDefault<z.ZodNumber>;
104
+ unit: z.ZodOptional<z.ZodString>;
105
+ displayMode: z.ZodDefault<z.ZodEnum<{
106
+ slider: "slider";
107
+ stepper: "stepper";
108
+ dial: "dial";
109
+ }>>;
110
+ default: z.ZodOptional<z.ZodNumber>;
111
+ }, z.core.$strip>, z.ZodObject<{
112
+ kind: z.ZodLiteral<"temperature">;
113
+ label: z.ZodDefault<z.ZodString>;
114
+ min: z.ZodDefault<z.ZodNumber>;
115
+ max: z.ZodDefault<z.ZodNumber>;
116
+ unit: z.ZodDefault<z.ZodEnum<{
117
+ C: "C";
118
+ F: "F";
119
+ }>>;
120
+ default: z.ZodOptional<z.ZodNumber>;
121
+ }, z.core.$strip>], "kind">>>;
122
+ effects: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
123
+ kind: z.ZodLiteral<"animation">;
124
+ clips: z.ZodObject<{
125
+ on: z.ZodOptional<z.ZodString>;
126
+ off: z.ZodOptional<z.ZodString>;
127
+ loop: z.ZodOptional<z.ZodString>;
128
+ }, z.core.$strip>;
129
+ }, z.core.$strip>, z.ZodObject<{
130
+ kind: z.ZodLiteral<"light">;
131
+ color: z.ZodDefault<z.ZodString>;
132
+ intensityRange: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
133
+ distance: z.ZodOptional<z.ZodNumber>;
134
+ offset: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
135
+ }, z.core.$strip>], "kind">>>;
136
+ }, z.core.$strip>>;
91
137
  }, z.core.$strip>;
92
138
  }, z.core.$strip>], "type">>>;
93
139
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/site.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
1
+ {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/site.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
@@ -62,6 +62,7 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
62
62
  type: z.ZodDefault<z.ZodLiteral<"item">>;
63
63
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
64
64
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
65
+ scale: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
65
66
  side: z.ZodOptional<z.ZodEnum<{
66
67
  front: "front";
67
68
  back: "back";
@@ -69,6 +70,7 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
69
70
  children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
70
71
  wallId: z.ZodOptional<z.ZodString>;
71
72
  wallT: z.ZodOptional<z.ZodNumber>;
73
+ collectionIds: z.ZodOptional<z.ZodArray<z.ZodCustom<`collection_${string}`, `collection_${string}`>>>;
72
74
  asset: z.ZodObject<{
73
75
  id: z.ZodString;
74
76
  category: z.ZodString;
@@ -88,6 +90,50 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
88
90
  surface: z.ZodOptional<z.ZodObject<{
89
91
  height: z.ZodNumber;
90
92
  }, z.core.$strip>>;
93
+ interactive: z.ZodOptional<z.ZodObject<{
94
+ controls: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
95
+ kind: z.ZodLiteral<"toggle">;
96
+ label: z.ZodOptional<z.ZodString>;
97
+ default: z.ZodOptional<z.ZodBoolean>;
98
+ }, z.core.$strip>, z.ZodObject<{
99
+ kind: z.ZodLiteral<"slider">;
100
+ label: z.ZodString;
101
+ min: z.ZodNumber;
102
+ max: z.ZodNumber;
103
+ step: z.ZodDefault<z.ZodNumber>;
104
+ unit: z.ZodOptional<z.ZodString>;
105
+ displayMode: z.ZodDefault<z.ZodEnum<{
106
+ slider: "slider";
107
+ stepper: "stepper";
108
+ dial: "dial";
109
+ }>>;
110
+ default: z.ZodOptional<z.ZodNumber>;
111
+ }, z.core.$strip>, z.ZodObject<{
112
+ kind: z.ZodLiteral<"temperature">;
113
+ label: z.ZodDefault<z.ZodString>;
114
+ min: z.ZodDefault<z.ZodNumber>;
115
+ max: z.ZodDefault<z.ZodNumber>;
116
+ unit: z.ZodDefault<z.ZodEnum<{
117
+ C: "C";
118
+ F: "F";
119
+ }>>;
120
+ default: z.ZodOptional<z.ZodNumber>;
121
+ }, z.core.$strip>], "kind">>>;
122
+ effects: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
123
+ kind: z.ZodLiteral<"animation">;
124
+ clips: z.ZodObject<{
125
+ on: z.ZodOptional<z.ZodString>;
126
+ off: z.ZodOptional<z.ZodString>;
127
+ loop: z.ZodOptional<z.ZodString>;
128
+ }, z.core.$strip>;
129
+ }, z.core.$strip>, z.ZodObject<{
130
+ kind: z.ZodLiteral<"light">;
131
+ color: z.ZodDefault<z.ZodString>;
132
+ intensityRange: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
133
+ distance: z.ZodOptional<z.ZodNumber>;
134
+ offset: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
135
+ }, z.core.$strip>], "kind">>>;
136
+ }, z.core.$strip>>;
91
137
  }, z.core.$strip>;
92
138
  }, z.core.$strip>], "type">>>;
93
139
  }, z.core.$strip>, z.ZodObject<{
@@ -184,6 +230,7 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
184
230
  type: z.ZodDefault<z.ZodLiteral<"item">>;
185
231
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
186
232
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
233
+ scale: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
187
234
  side: z.ZodOptional<z.ZodEnum<{
188
235
  front: "front";
189
236
  back: "back";
@@ -191,6 +238,7 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
191
238
  children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
192
239
  wallId: z.ZodOptional<z.ZodString>;
193
240
  wallT: z.ZodOptional<z.ZodNumber>;
241
+ collectionIds: z.ZodOptional<z.ZodArray<z.ZodCustom<`collection_${string}`, `collection_${string}`>>>;
194
242
  asset: z.ZodObject<{
195
243
  id: z.ZodString;
196
244
  category: z.ZodString;
@@ -210,6 +258,50 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
210
258
  surface: z.ZodOptional<z.ZodObject<{
211
259
  height: z.ZodNumber;
212
260
  }, z.core.$strip>>;
261
+ interactive: z.ZodOptional<z.ZodObject<{
262
+ controls: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
263
+ kind: z.ZodLiteral<"toggle">;
264
+ label: z.ZodOptional<z.ZodString>;
265
+ default: z.ZodOptional<z.ZodBoolean>;
266
+ }, z.core.$strip>, z.ZodObject<{
267
+ kind: z.ZodLiteral<"slider">;
268
+ label: z.ZodString;
269
+ min: z.ZodNumber;
270
+ max: z.ZodNumber;
271
+ step: z.ZodDefault<z.ZodNumber>;
272
+ unit: z.ZodOptional<z.ZodString>;
273
+ displayMode: z.ZodDefault<z.ZodEnum<{
274
+ slider: "slider";
275
+ stepper: "stepper";
276
+ dial: "dial";
277
+ }>>;
278
+ default: z.ZodOptional<z.ZodNumber>;
279
+ }, z.core.$strip>, z.ZodObject<{
280
+ kind: z.ZodLiteral<"temperature">;
281
+ label: z.ZodDefault<z.ZodString>;
282
+ min: z.ZodDefault<z.ZodNumber>;
283
+ max: z.ZodDefault<z.ZodNumber>;
284
+ unit: z.ZodDefault<z.ZodEnum<{
285
+ C: "C";
286
+ F: "F";
287
+ }>>;
288
+ default: z.ZodOptional<z.ZodNumber>;
289
+ }, z.core.$strip>], "kind">>>;
290
+ effects: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
291
+ kind: z.ZodLiteral<"animation">;
292
+ clips: z.ZodObject<{
293
+ on: z.ZodOptional<z.ZodString>;
294
+ off: z.ZodOptional<z.ZodString>;
295
+ loop: z.ZodOptional<z.ZodString>;
296
+ }, z.core.$strip>;
297
+ }, z.core.$strip>, z.ZodObject<{
298
+ kind: z.ZodLiteral<"light">;
299
+ color: z.ZodDefault<z.ZodString>;
300
+ intensityRange: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
301
+ distance: z.ZodOptional<z.ZodNumber>;
302
+ offset: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
303
+ }, z.core.$strip>], "kind">>>;
304
+ }, z.core.$strip>>;
213
305
  }, z.core.$strip>;
214
306
  }, z.core.$strip>, z.ZodObject<{
215
307
  object: z.ZodDefault<z.ZodLiteral<"node">>;
@@ -294,10 +386,44 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
294
386
  type: z.ZodDefault<z.ZodLiteral<"roof">>;
295
387
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
296
388
  rotation: z.ZodDefault<z.ZodNumber>;
297
- length: z.ZodDefault<z.ZodNumber>;
298
- height: z.ZodDefault<z.ZodNumber>;
299
- leftWidth: z.ZodDefault<z.ZodNumber>;
300
- rightWidth: z.ZodDefault<z.ZodNumber>;
389
+ children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`rseg_${string}`>>>>;
390
+ }, z.core.$strip>, z.ZodObject<{
391
+ object: z.ZodDefault<z.ZodLiteral<"node">>;
392
+ name: z.ZodOptional<z.ZodString>;
393
+ parentId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
394
+ visible: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
395
+ camera: z.ZodOptional<z.ZodObject<{
396
+ position: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
397
+ target: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
398
+ mode: z.ZodDefault<z.ZodEnum<{
399
+ perspective: "perspective";
400
+ orthographic: "orthographic";
401
+ }>>;
402
+ fov: z.ZodOptional<z.ZodNumber>;
403
+ zoom: z.ZodOptional<z.ZodNumber>;
404
+ }, z.core.$strip>>;
405
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
406
+ id: z.ZodDefault<z.ZodTemplateLiteral<`rseg_${string}`>>;
407
+ type: z.ZodDefault<z.ZodLiteral<"roof-segment">>;
408
+ position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
409
+ rotation: z.ZodDefault<z.ZodNumber>;
410
+ roofType: z.ZodDefault<z.ZodEnum<{
411
+ hip: "hip";
412
+ gable: "gable";
413
+ shed: "shed";
414
+ gambrel: "gambrel";
415
+ dutch: "dutch";
416
+ mansard: "mansard";
417
+ flat: "flat";
418
+ }>>;
419
+ width: z.ZodDefault<z.ZodNumber>;
420
+ depth: z.ZodDefault<z.ZodNumber>;
421
+ wallHeight: z.ZodDefault<z.ZodNumber>;
422
+ roofHeight: z.ZodDefault<z.ZodNumber>;
423
+ wallThickness: z.ZodDefault<z.ZodNumber>;
424
+ deckThickness: z.ZodDefault<z.ZodNumber>;
425
+ overhang: z.ZodDefault<z.ZodNumber>;
426
+ shingleThickness: z.ZodDefault<z.ZodNumber>;
301
427
  }, z.core.$strip>, z.ZodObject<{
302
428
  object: z.ZodDefault<z.ZodLiteral<"node">>;
303
429
  name: z.ZodOptional<z.ZodString>;
@@ -380,6 +506,67 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
380
506
  sill: z.ZodDefault<z.ZodBoolean>;
381
507
  sillDepth: z.ZodDefault<z.ZodNumber>;
382
508
  sillThickness: z.ZodDefault<z.ZodNumber>;
509
+ }, z.core.$strip>, z.ZodObject<{
510
+ object: z.ZodDefault<z.ZodLiteral<"node">>;
511
+ name: z.ZodOptional<z.ZodString>;
512
+ parentId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
513
+ visible: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
514
+ camera: z.ZodOptional<z.ZodObject<{
515
+ position: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
516
+ target: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
517
+ mode: z.ZodDefault<z.ZodEnum<{
518
+ perspective: "perspective";
519
+ orthographic: "orthographic";
520
+ }>>;
521
+ fov: z.ZodOptional<z.ZodNumber>;
522
+ zoom: z.ZodOptional<z.ZodNumber>;
523
+ }, z.core.$strip>>;
524
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
525
+ id: z.ZodDefault<z.ZodTemplateLiteral<`door_${string}`>>;
526
+ type: z.ZodDefault<z.ZodLiteral<"door">>;
527
+ position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
528
+ rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
529
+ side: z.ZodOptional<z.ZodEnum<{
530
+ front: "front";
531
+ back: "back";
532
+ }>>;
533
+ wallId: z.ZodOptional<z.ZodString>;
534
+ width: z.ZodDefault<z.ZodNumber>;
535
+ height: z.ZodDefault<z.ZodNumber>;
536
+ frameThickness: z.ZodDefault<z.ZodNumber>;
537
+ frameDepth: z.ZodDefault<z.ZodNumber>;
538
+ threshold: z.ZodDefault<z.ZodBoolean>;
539
+ thresholdHeight: z.ZodDefault<z.ZodNumber>;
540
+ hingesSide: z.ZodDefault<z.ZodEnum<{
541
+ left: "left";
542
+ right: "right";
543
+ }>>;
544
+ swingDirection: z.ZodDefault<z.ZodEnum<{
545
+ inward: "inward";
546
+ outward: "outward";
547
+ }>>;
548
+ segments: z.ZodDefault<z.ZodArray<z.ZodObject<{
549
+ type: z.ZodEnum<{
550
+ panel: "panel";
551
+ glass: "glass";
552
+ empty: "empty";
553
+ }>;
554
+ heightRatio: z.ZodNumber;
555
+ columnRatios: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
556
+ dividerThickness: z.ZodDefault<z.ZodNumber>;
557
+ panelDepth: z.ZodDefault<z.ZodNumber>;
558
+ panelInset: z.ZodDefault<z.ZodNumber>;
559
+ }, z.core.$strip>>>;
560
+ handle: z.ZodDefault<z.ZodBoolean>;
561
+ handleHeight: z.ZodDefault<z.ZodNumber>;
562
+ handleSide: z.ZodDefault<z.ZodEnum<{
563
+ left: "left";
564
+ right: "right";
565
+ }>>;
566
+ contentPadding: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
567
+ doorCloser: z.ZodDefault<z.ZodBoolean>;
568
+ panicBar: z.ZodDefault<z.ZodBoolean>;
569
+ panicBarHeight: z.ZodDefault<z.ZodNumber>;
383
570
  }, z.core.$strip>], "type">;
384
571
  export type AnyNode = z.infer<typeof AnyNode>;
385
572
  export type AnyNodeType = AnyNode['type'];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAA;AAcnB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAalB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAA;AAC7C,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AACzC,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAA;AAgBnB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAelB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAA;AAC7C,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AACzC,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA"}
@@ -1,10 +1,12 @@
1
1
  import z from 'zod';
2
2
  import { BuildingNode } from './nodes/building';
3
3
  import { CeilingNode } from './nodes/ceiling';
4
+ import { DoorNode } from './nodes/door';
4
5
  import { GuideNode } from './nodes/guide';
5
6
  import { ItemNode } from './nodes/item';
6
7
  import { LevelNode } from './nodes/level';
7
8
  import { RoofNode } from './nodes/roof';
9
+ import { RoofSegmentNode } from './nodes/roof-segment';
8
10
  import { ScanNode } from './nodes/scan';
9
11
  import { SiteNode } from './nodes/site';
10
12
  import { SlabNode } from './nodes/slab';
@@ -21,7 +23,9 @@ export const AnyNode = z.discriminatedUnion('type', [
21
23
  SlabNode,
22
24
  CeilingNode,
23
25
  RoofNode,
26
+ RoofSegmentNode,
24
27
  ScanNode,
25
28
  GuideNode,
26
29
  WindowNode,
30
+ DoorNode,
27
31
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAI9C,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,SA2C/C,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SAoDrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SA2CjB,CAAA"}
1
+ {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAI9C,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,SA2C/C,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SAoDrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SA+DjB,CAAA"}
@@ -87,6 +87,7 @@ export const deleteNodesAction = (set, get, ids) => {
87
87
  const parentsToMarkDirty = new Set();
88
88
  set((state) => {
89
89
  const nextNodes = { ...state.nodes };
90
+ const nextCollections = { ...state.collections };
90
91
  let nextRootIds = [...state.rootNodeIds];
91
92
  for (const id of ids) {
92
93
  const node = nextNodes[id];
@@ -106,7 +107,16 @@ export const deleteNodesAction = (set, get, ids) => {
106
107
  }
107
108
  // 2. Remove from Root list
108
109
  nextRootIds = nextRootIds.filter((rid) => rid !== id);
109
- // 3. Delete the node itself
110
+ // 3. Remove from any collections it belongs to
111
+ if ('collectionIds' in node && node.collectionIds) {
112
+ for (const cid of node.collectionIds) {
113
+ const col = nextCollections[cid];
114
+ if (col) {
115
+ nextCollections[cid] = { ...col, nodeIds: col.nodeIds.filter((nid) => nid !== id) };
116
+ }
117
+ }
118
+ }
119
+ // 4. Delete the node itself
110
120
  delete nextNodes[id];
111
121
  // Inside the deleteNodes loop
112
122
  if ('children' in node && node.children.length > 0) {
@@ -114,8 +124,17 @@ export const deleteNodesAction = (set, get, ids) => {
114
124
  get().deleteNodes(node.children);
115
125
  }
116
126
  }
117
- return { nodes: nextNodes, rootNodeIds: nextRootIds };
127
+ return { nodes: nextNodes, rootNodeIds: nextRootIds, collections: nextCollections };
128
+ });
129
+ // Mark affected nodes dirty: parents of deleted nodes and their remaining children
130
+ // (e.g. deleting a slab affects sibling walls via level elevation changes)
131
+ parentsToMarkDirty.forEach((parentId) => {
132
+ get().markDirty(parentId);
133
+ const parent = get().nodes[parentId];
134
+ if (parent && 'children' in parent && Array.isArray(parent.children)) {
135
+ for (const childId of parent.children) {
136
+ get().markDirty(childId);
137
+ }
138
+ }
118
139
  });
119
- // Notify systems that the parent has changed (e.g. Wall needs to fill a window hole)
120
- parentsToMarkDirty.forEach((pId) => get().markDirty(pId));
121
140
  };
@@ -0,0 +1,18 @@
1
+ import type { Interactive } from '../schema/nodes/item';
2
+ import type { AnyNodeId } from '../schema/types';
3
+ export type ControlValue = boolean | number;
4
+ export type ItemInteractiveState = {
5
+ controlValues: ControlValue[];
6
+ };
7
+ type InteractiveStore = {
8
+ items: Record<AnyNodeId, ItemInteractiveState>;
9
+ /** Initialize a node's interactive state from its asset definition (idempotent) */
10
+ initItem: (itemId: AnyNodeId, interactive: Interactive) => void;
11
+ /** Set a single control value */
12
+ setControlValue: (itemId: AnyNodeId, index: number, value: ControlValue) => void;
13
+ /** Remove a node's state (e.g. on unmount) */
14
+ removeItem: (itemId: AnyNodeId) => void;
15
+ };
16
+ export declare const useInteractive: import("zustand").UseBoundStore<import("zustand").StoreApi<InteractiveStore>>;
17
+ export {};
18
+ //# sourceMappingURL=use-interactive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-interactive.d.ts","sourceRoot":"","sources":["../../src/store/use-interactive.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAGhD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,CAAA;AAE3C,MAAM,MAAM,oBAAoB,GAAG;IAEjC,aAAa,EAAE,YAAY,EAAE,CAAA;CAC9B,CAAA;AAED,KAAK,gBAAgB,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAA;IAE9C,mFAAmF;IACnF,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,KAAK,IAAI,CAAA;IAE/D,iCAAiC;IACjC,eAAe,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAEhF,8CAA8C;IAC9C,UAAU,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACxC,CAAA;AAeD,eAAO,MAAM,cAAc,+EAoCxB,CAAA"}
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+ import { create } from 'zustand';
3
+ const defaultControlValue = (interactive, index) => {
4
+ const control = interactive.controls[index];
5
+ if (!control)
6
+ return false;
7
+ switch (control.kind) {
8
+ case 'toggle':
9
+ return control.default ?? false;
10
+ case 'slider':
11
+ return control.default ?? control.min;
12
+ case 'temperature':
13
+ return control.default ?? control.min;
14
+ }
15
+ };
16
+ export const useInteractive = create((set, get) => ({
17
+ items: {},
18
+ initItem: (itemId, interactive) => {
19
+ const { controls } = interactive;
20
+ if (controls.length === 0)
21
+ return;
22
+ // Don't overwrite existing state (idempotent)
23
+ if (get().items[itemId])
24
+ return;
25
+ set((state) => ({
26
+ items: {
27
+ ...state.items,
28
+ [itemId]: {
29
+ controlValues: controls.map((_, i) => defaultControlValue(interactive, i)),
30
+ },
31
+ },
32
+ }));
33
+ },
34
+ setControlValue: (itemId, index, value) => {
35
+ set((state) => {
36
+ const item = state.items[itemId];
37
+ if (!item)
38
+ return state;
39
+ const next = [...item.controlValues];
40
+ next[index] = value;
41
+ return { items: { ...state.items, [itemId]: { controlValues: next } } };
42
+ });
43
+ },
44
+ removeItem: (itemId) => {
45
+ set((state) => {
46
+ const { [itemId]: _, ...rest } = state.items;
47
+ return { items: rest };
48
+ });
49
+ },
50
+ }));