@pascal-app/core 0.8.0 → 0.9.1

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 (272) hide show
  1. package/dist/events/bus.d.ts +39 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
  4. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  5. package/dist/hooks/scene-registry/scene-registry.js +33 -26
  6. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  7. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  8. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  9. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
  12. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
  15. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
  18. package/dist/index.d.ts +22 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +19 -2
  21. package/dist/lib/polygon-relations.d.ts +10 -0
  22. package/dist/lib/polygon-relations.d.ts.map +1 -0
  23. package/dist/lib/polygon-relations.js +73 -0
  24. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  25. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  26. package/dist/lib/space-detection-pause.test.js +42 -0
  27. package/dist/lib/space-detection.d.ts +30 -1
  28. package/dist/lib/space-detection.d.ts.map +1 -1
  29. package/dist/lib/space-detection.js +215 -36
  30. package/dist/lib/space-detection.test.d.ts +2 -0
  31. package/dist/lib/space-detection.test.d.ts.map +1 -0
  32. package/dist/lib/space-detection.test.js +77 -0
  33. package/dist/material-library.d.ts +1 -1
  34. package/dist/material-library.d.ts.map +1 -1
  35. package/dist/material-library.js +1940 -143
  36. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  37. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  38. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  39. package/dist/registry/handles.d.ts +321 -0
  40. package/dist/registry/handles.d.ts.map +1 -0
  41. package/dist/registry/handles.js +24 -0
  42. package/dist/registry/index.d.ts +7 -0
  43. package/dist/registry/index.d.ts.map +1 -0
  44. package/dist/registry/index.js +4 -0
  45. package/dist/registry/registry.d.ts +93 -0
  46. package/dist/registry/registry.d.ts.map +1 -0
  47. package/dist/registry/registry.js +201 -0
  48. package/dist/registry/registry.test.d.ts +2 -0
  49. package/dist/registry/registry.test.d.ts.map +1 -0
  50. package/dist/registry/registry.test.js +183 -0
  51. package/dist/registry/relations-resolver.d.ts +42 -0
  52. package/dist/registry/relations-resolver.d.ts.map +1 -0
  53. package/dist/registry/relations-resolver.js +77 -0
  54. package/dist/registry/relations-resolver.test.d.ts +2 -0
  55. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  56. package/dist/registry/relations-resolver.test.js +183 -0
  57. package/dist/registry/scene-api.d.ts +44 -0
  58. package/dist/registry/scene-api.d.ts.map +1 -0
  59. package/dist/registry/scene-api.js +111 -0
  60. package/dist/registry/scene-api.test.d.ts +2 -0
  61. package/dist/registry/scene-api.test.d.ts.map +1 -0
  62. package/dist/registry/scene-api.test.js +183 -0
  63. package/dist/registry/subtree.d.ts +67 -0
  64. package/dist/registry/subtree.d.ts.map +1 -0
  65. package/dist/registry/subtree.js +119 -0
  66. package/dist/registry/subtree.test.d.ts +2 -0
  67. package/dist/registry/subtree.test.d.ts.map +1 -0
  68. package/dist/registry/subtree.test.js +133 -0
  69. package/dist/registry/types.d.ts +1420 -0
  70. package/dist/registry/types.d.ts.map +1 -0
  71. package/dist/registry/types.js +1 -0
  72. package/dist/schema/index.d.ts +17 -2
  73. package/dist/schema/index.d.ts.map +1 -1
  74. package/dist/schema/index.js +17 -3
  75. package/dist/schema/material.d.ts +10 -0
  76. package/dist/schema/material.d.ts.map +1 -1
  77. package/dist/schema/material.js +10 -0
  78. package/dist/schema/nodes/box-vent.d.ts +74 -0
  79. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  80. package/dist/schema/nodes/box-vent.js +63 -0
  81. package/dist/schema/nodes/building.d.ts +1 -1
  82. package/dist/schema/nodes/building.d.ts.map +1 -1
  83. package/dist/schema/nodes/building.js +3 -2
  84. package/dist/schema/nodes/ceiling.d.ts +2 -0
  85. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  86. package/dist/schema/nodes/ceiling.js +1 -1
  87. package/dist/schema/nodes/chimney.d.ts +158 -0
  88. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  89. package/dist/schema/nodes/chimney.js +67 -0
  90. package/dist/schema/nodes/column.d.ts +597 -5
  91. package/dist/schema/nodes/column.d.ts.map +1 -1
  92. package/dist/schema/nodes/column.js +586 -6
  93. package/dist/schema/nodes/cupola.d.ts +66 -0
  94. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  95. package/dist/schema/nodes/cupola.js +28 -0
  96. package/dist/schema/nodes/dormer.d.ts +228 -0
  97. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  98. package/dist/schema/nodes/dormer.js +119 -0
  99. package/dist/schema/nodes/downspout.d.ts +75 -0
  100. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  101. package/dist/schema/nodes/downspout.js +63 -0
  102. package/dist/schema/nodes/elevator.d.ts +105 -0
  103. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  104. package/dist/schema/nodes/elevator.js +54 -0
  105. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  106. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  107. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  108. package/dist/schema/nodes/fence.d.ts +1 -0
  109. package/dist/schema/nodes/fence.d.ts.map +1 -1
  110. package/dist/schema/nodes/fence.js +2 -0
  111. package/dist/schema/nodes/gutter.d.ts +84 -0
  112. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  113. package/dist/schema/nodes/gutter.js +76 -0
  114. package/dist/schema/nodes/item.d.ts +4 -0
  115. package/dist/schema/nodes/item.d.ts.map +1 -1
  116. package/dist/schema/nodes/item.js +9 -0
  117. package/dist/schema/nodes/level.d.ts +1 -1
  118. package/dist/schema/nodes/level.d.ts.map +1 -1
  119. package/dist/schema/nodes/level.js +2 -0
  120. package/dist/schema/nodes/ridge-vent.d.ts +67 -0
  121. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  122. package/dist/schema/nodes/ridge-vent.js +30 -0
  123. package/dist/schema/nodes/roof-segment.d.ts +195 -1
  124. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  125. package/dist/schema/nodes/roof-segment.js +254 -3
  126. package/dist/schema/nodes/roof.d.ts.map +1 -1
  127. package/dist/schema/nodes/roof.js +4 -16
  128. package/dist/schema/nodes/shelf.d.ts +105 -0
  129. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  130. package/dist/schema/nodes/shelf.js +84 -0
  131. package/dist/schema/nodes/site.d.ts +1 -122
  132. package/dist/schema/nodes/site.d.ts.map +1 -1
  133. package/dist/schema/nodes/site.js +2 -6
  134. package/dist/schema/nodes/skylight.d.ts +279 -0
  135. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  136. package/dist/schema/nodes/skylight.js +152 -0
  137. package/dist/schema/nodes/slab.d.ts +2 -0
  138. package/dist/schema/nodes/slab.d.ts.map +1 -1
  139. package/dist/schema/nodes/slab.js +1 -1
  140. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  141. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  142. package/dist/schema/nodes/solar-panel.js +47 -0
  143. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  144. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  145. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  146. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  147. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  148. package/dist/schema/nodes/turbine-vent.js +41 -0
  149. package/dist/schema/types.d.ts +1325 -125
  150. package/dist/schema/types.d.ts.map +1 -1
  151. package/dist/schema/types.js +26 -0
  152. package/dist/services/alignment-anchors.d.ts +100 -0
  153. package/dist/services/alignment-anchors.d.ts.map +1 -0
  154. package/dist/services/alignment-anchors.js +283 -0
  155. package/dist/services/alignment-anchors.test.d.ts +2 -0
  156. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  157. package/dist/services/alignment-anchors.test.js +383 -0
  158. package/dist/services/alignment.d.ts +126 -0
  159. package/dist/services/alignment.d.ts.map +1 -0
  160. package/dist/services/alignment.js +169 -0
  161. package/dist/services/alignment.test.d.ts +2 -0
  162. package/dist/services/alignment.test.d.ts.map +1 -0
  163. package/dist/services/alignment.test.js +92 -0
  164. package/dist/services/drag-session.d.ts +47 -0
  165. package/dist/services/drag-session.d.ts.map +1 -0
  166. package/dist/services/drag-session.js +100 -0
  167. package/dist/services/drag-session.test.d.ts +2 -0
  168. package/dist/services/drag-session.test.d.ts.map +1 -0
  169. package/dist/services/drag-session.test.js +219 -0
  170. package/dist/services/hosting.d.ts +80 -0
  171. package/dist/services/hosting.d.ts.map +1 -0
  172. package/dist/services/hosting.js +122 -0
  173. package/dist/services/hosting.test.d.ts +2 -0
  174. package/dist/services/hosting.test.d.ts.map +1 -0
  175. package/dist/services/hosting.test.js +199 -0
  176. package/dist/services/index.d.ts +7 -0
  177. package/dist/services/index.d.ts.map +1 -0
  178. package/dist/services/index.js +6 -0
  179. package/dist/services/movement.d.ts +48 -0
  180. package/dist/services/movement.d.ts.map +1 -0
  181. package/dist/services/movement.js +65 -0
  182. package/dist/services/movement.test.d.ts +2 -0
  183. package/dist/services/movement.test.d.ts.map +1 -0
  184. package/dist/services/movement.test.js +133 -0
  185. package/dist/services/single-undo-dance.test.d.ts +2 -0
  186. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  187. package/dist/services/single-undo-dance.test.js +210 -0
  188. package/dist/services/snap.d.ts +72 -0
  189. package/dist/services/snap.d.ts.map +1 -0
  190. package/dist/services/snap.js +120 -0
  191. package/dist/services/snap.test.d.ts +2 -0
  192. package/dist/services/snap.test.d.ts.map +1 -0
  193. package/dist/services/snap.test.js +95 -0
  194. package/dist/solar-panel-presets.d.ts +17 -0
  195. package/dist/solar-panel-presets.d.ts.map +1 -0
  196. package/dist/solar-panel-presets.js +43 -0
  197. package/dist/store/actions/node-actions.d.ts +14 -2
  198. package/dist/store/actions/node-actions.d.ts.map +1 -1
  199. package/dist/store/actions/node-actions.js +492 -15
  200. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  201. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  202. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  203. package/dist/store/actions/reparent.test.d.ts +2 -0
  204. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  205. package/dist/store/actions/reparent.test.js +212 -0
  206. package/dist/store/use-interactive.d.ts +39 -0
  207. package/dist/store/use-interactive.d.ts.map +1 -1
  208. package/dist/store/use-interactive.js +75 -0
  209. package/dist/store/use-live-node-overrides.d.ts +22 -0
  210. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  211. package/dist/store/use-live-node-overrides.js +59 -0
  212. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  213. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  214. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  215. package/dist/store/use-scene.d.ts +11 -0
  216. package/dist/store/use-scene.d.ts.map +1 -1
  217. package/dist/store/use-scene.js +196 -6
  218. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  219. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  220. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  221. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  222. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  223. package/dist/systems/elevator/elevator-geometry.js +50 -0
  224. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  225. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  226. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  227. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  228. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  229. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  230. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  231. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  232. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  233. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  234. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  235. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  236. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  237. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  238. package/dist/systems/elevator/elevator-runtime.js +225 -0
  239. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  240. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  241. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  242. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  243. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  244. package/dist/systems/elevator/elevator-service.js +111 -0
  245. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  246. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  247. package/dist/systems/stair/stair-footprint.js +168 -0
  248. package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
  249. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  250. package/dist/systems/stair/stair-opening-preview.js +93 -0
  251. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  252. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  253. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  254. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  255. package/dist/systems/stair/stair-opening-sync.js +172 -147
  256. package/dist/systems/stair/stair-opening-sync.test.js +385 -2
  257. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  258. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  259. package/dist/systems/stair/stair-opening-system.js +104 -0
  260. package/dist/systems/wall/wall-move.d.ts +48 -0
  261. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  262. package/dist/systems/wall/wall-move.js +193 -0
  263. package/dist/utils/clone-scene-graph.d.ts +6 -4
  264. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  265. package/dist/utils/clone-scene-graph.js +6 -4
  266. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  267. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  268. package/dist/utils/clone-scene-graph.test.js +59 -0
  269. package/dist/validation/validate-build-json.d.ts +43 -0
  270. package/dist/validation/validate-build-json.d.ts.map +1 -0
  271. package/dist/validation/validate-build-json.js +227 -0
  272. package/package.json +9 -2
@@ -1,4 +1,346 @@
1
- import { getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, } from '../../schema';
1
+ import { AnyNode as AnyNodeSchema, getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, } from '../../schema';
2
+ const NUMBER_FORMAT_BOUNDS = {
3
+ safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
4
+ int32: [-2147483648, 2147483647],
5
+ uint32: [0, 4294967295],
6
+ float32: [-3.4028234663852886e38, 3.4028234663852886e38],
7
+ };
8
+ const INTEGER_NUMBER_FORMATS = new Set(['safeint', 'int32', 'uint32']);
9
+ function getSchemaDef(schema) {
10
+ return schema?._zod?.def ?? schema?.def;
11
+ }
12
+ function getSchemaDefault(schema) {
13
+ const def = getSchemaDef(schema);
14
+ if (!(def?.type === 'default' || def?.type === 'prefault'))
15
+ return undefined;
16
+ return def.defaultValue;
17
+ }
18
+ function unwrapSchema(schema) {
19
+ let current = schema ?? null;
20
+ while (current) {
21
+ const def = getSchemaDef(current);
22
+ if (!(def?.type === 'default' ||
23
+ def?.type === 'prefault' ||
24
+ def?.type === 'optional' ||
25
+ def?.type === 'nullable' ||
26
+ def?.type === 'catch' ||
27
+ def?.type === 'readonly' ||
28
+ def?.type === 'nonoptional')) {
29
+ return current;
30
+ }
31
+ current = def.innerType ?? null;
32
+ }
33
+ return null;
34
+ }
35
+ function getObjectShape(schema) {
36
+ const unwrapped = unwrapSchema(schema);
37
+ const def = getSchemaDef(unwrapped);
38
+ if (def?.type !== 'object')
39
+ return null;
40
+ return unwrapped?.shape ?? def.shape ?? null;
41
+ }
42
+ function schemaAllowsValue(schema, value) {
43
+ const unwrapped = unwrapSchema(schema);
44
+ if (!unwrapped)
45
+ return false;
46
+ const def = getSchemaDef(unwrapped);
47
+ if (unwrapped._zod?.values?.has(value))
48
+ return true;
49
+ if (Array.isArray(def?.values) && def.values.includes(value))
50
+ return true;
51
+ if (def?.entries && Object.values(def.entries).includes(value))
52
+ return true;
53
+ return false;
54
+ }
55
+ function getNodeSchemaForType(type) {
56
+ const schema = AnyNodeSchema;
57
+ const options = getSchemaDef(schema)?.options;
58
+ if (!options)
59
+ return null;
60
+ for (const option of options) {
61
+ const shape = getObjectShape(option);
62
+ if (shape?.type && schemaAllowsValue(shape.type, type)) {
63
+ return option;
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ function applyLowerLimit(current, candidate) {
69
+ if (!current)
70
+ return candidate;
71
+ if (candidate.value > current.value)
72
+ return candidate;
73
+ if (candidate.value === current.value && !candidate.inclusive)
74
+ return candidate;
75
+ return current;
76
+ }
77
+ function applyUpperLimit(current, candidate) {
78
+ if (!current)
79
+ return candidate;
80
+ if (candidate.value < current.value)
81
+ return candidate;
82
+ if (candidate.value === current.value && !candidate.inclusive)
83
+ return candidate;
84
+ return current;
85
+ }
86
+ function getNumberConstraints(schema) {
87
+ const unwrapped = unwrapSchema(schema) ?? schema;
88
+ const def = getSchemaDef(unwrapped);
89
+ const constraints = { integer: false };
90
+ const minValue = unwrapped.minValue;
91
+ if (typeof minValue === 'number') {
92
+ constraints.min = applyLowerLimit(constraints.min, { value: minValue, inclusive: true });
93
+ }
94
+ const maxValue = unwrapped.maxValue;
95
+ if (typeof maxValue === 'number') {
96
+ constraints.max = applyUpperLimit(constraints.max, { value: maxValue, inclusive: true });
97
+ }
98
+ for (const check of def?.checks ?? []) {
99
+ const checkDef = check._zod?.def;
100
+ if (!checkDef)
101
+ continue;
102
+ if (checkDef.check === 'greater_than' && typeof checkDef.value === 'number') {
103
+ constraints.min = applyLowerLimit(constraints.min, {
104
+ value: checkDef.value,
105
+ inclusive: checkDef.inclusive !== false,
106
+ });
107
+ }
108
+ else if (checkDef.check === 'less_than' && typeof checkDef.value === 'number') {
109
+ constraints.max = applyUpperLimit(constraints.max, {
110
+ value: checkDef.value,
111
+ inclusive: checkDef.inclusive !== false,
112
+ });
113
+ }
114
+ else if (checkDef.check === 'number_format' && checkDef.format) {
115
+ constraints.integer ||= INTEGER_NUMBER_FORMATS.has(checkDef.format);
116
+ const bounds = NUMBER_FORMAT_BOUNDS[checkDef.format];
117
+ if (bounds) {
118
+ constraints.min = applyLowerLimit(constraints.min, {
119
+ value: bounds[0],
120
+ inclusive: true,
121
+ });
122
+ constraints.max = applyUpperLimit(constraints.max, {
123
+ value: bounds[1],
124
+ inclusive: true,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ const bag = unwrapped._zod?.bag;
130
+ if (typeof bag?.minimum === 'number') {
131
+ constraints.min = applyLowerLimit(constraints.min, { value: bag.minimum, inclusive: true });
132
+ }
133
+ if (typeof bag?.exclusiveMinimum === 'number') {
134
+ constraints.min = applyLowerLimit(constraints.min, {
135
+ value: bag.exclusiveMinimum,
136
+ inclusive: false,
137
+ });
138
+ }
139
+ if (typeof bag?.maximum === 'number') {
140
+ constraints.max = applyUpperLimit(constraints.max, { value: bag.maximum, inclusive: true });
141
+ }
142
+ if (typeof bag?.exclusiveMaximum === 'number') {
143
+ constraints.max = applyUpperLimit(constraints.max, {
144
+ value: bag.exclusiveMaximum,
145
+ inclusive: false,
146
+ });
147
+ }
148
+ return constraints;
149
+ }
150
+ function nextAbove(value) {
151
+ return value === 0 ? Number.EPSILON : value + Math.abs(value) * Number.EPSILON;
152
+ }
153
+ function nextBelow(value) {
154
+ return value === 0 ? -Number.EPSILON : value - Math.abs(value) * Number.EPSILON;
155
+ }
156
+ function clampNumber(value, constraints) {
157
+ let next = value;
158
+ let rounded = false;
159
+ let clamped = false;
160
+ if (constraints.integer && !Number.isInteger(next)) {
161
+ next = Math.round(next);
162
+ rounded = true;
163
+ }
164
+ if (constraints.min) {
165
+ const min = constraints.min.inclusive ? constraints.min.value : nextAbove(constraints.min.value);
166
+ if (next < min) {
167
+ next = min;
168
+ clamped = true;
169
+ }
170
+ }
171
+ if (constraints.max) {
172
+ const max = constraints.max.inclusive ? constraints.max.value : nextBelow(constraints.max.value);
173
+ if (next > max) {
174
+ next = max;
175
+ clamped = true;
176
+ }
177
+ }
178
+ return {
179
+ value: next,
180
+ action: clamped ? 'clamped' : rounded ? 'rounded' : undefined,
181
+ };
182
+ }
183
+ function getFiniteFallbackNumber(fallback, constraints) {
184
+ if (typeof fallback !== 'number' || !Number.isFinite(fallback))
185
+ return undefined;
186
+ return clampNumber(fallback, constraints).value;
187
+ }
188
+ function sanitizeNumber(schema, value, fallback, path) {
189
+ const constraints = schema ? getNumberConstraints(schema) : { integer: false };
190
+ if (!Number.isFinite(value)) {
191
+ const replacement = getFiniteFallbackNumber(fallback, constraints);
192
+ if (replacement === undefined) {
193
+ return {
194
+ value,
195
+ omit: true,
196
+ issues: [{ path, from: value, action: 'dropped' }],
197
+ };
198
+ }
199
+ return {
200
+ value: replacement,
201
+ issues: [{ path, from: value, to: replacement, action: 'dropped' }],
202
+ };
203
+ }
204
+ const clamped = clampNumber(value, constraints);
205
+ if (!Object.is(clamped.value, value)) {
206
+ return {
207
+ value: clamped.value,
208
+ issues: [
209
+ {
210
+ path,
211
+ from: value,
212
+ to: clamped.value,
213
+ action: clamped.action ?? 'clamped',
214
+ },
215
+ ],
216
+ };
217
+ }
218
+ return { value, issues: [] };
219
+ }
220
+ function sanitizeNumericValue(schema, value, fallback, path) {
221
+ const defaultFallback = schema ? getSchemaDefault(schema) : undefined;
222
+ const effectiveFallback = fallback === undefined ? defaultFallback : fallback;
223
+ const unwrapped = unwrapSchema(schema);
224
+ const def = getSchemaDef(unwrapped);
225
+ if (def?.type === 'number') {
226
+ if (typeof value !== 'number')
227
+ return { value, issues: [] };
228
+ return sanitizeNumber(unwrapped, value, effectiveFallback, path);
229
+ }
230
+ if (typeof value === 'number') {
231
+ return sanitizeNumber(null, value, effectiveFallback, path);
232
+ }
233
+ if (def?.type === 'tuple' && Array.isArray(value)) {
234
+ const fallbackItems = Array.isArray(effectiveFallback) ? effectiveFallback : [];
235
+ const next = [...value];
236
+ const issues = [];
237
+ for (let index = 0; index < next.length; index += 1) {
238
+ const itemSchema = def.items?.[index] ?? def.rest ?? null;
239
+ const child = sanitizeNumericValue(itemSchema, next[index], fallbackItems[index], [
240
+ ...path,
241
+ index,
242
+ ]);
243
+ issues.push(...child.issues);
244
+ if (child.omit) {
245
+ return { value, omit: true, issues };
246
+ }
247
+ next[index] = child.value;
248
+ }
249
+ return { value: issues.length > 0 ? next : value, issues };
250
+ }
251
+ if (def?.type === 'array' && Array.isArray(value)) {
252
+ const fallbackItems = Array.isArray(effectiveFallback) ? effectiveFallback : [];
253
+ const next = [];
254
+ const issues = [];
255
+ let omitted = false;
256
+ for (let index = 0; index < value.length; index += 1) {
257
+ const child = sanitizeNumericValue(def.element ?? null, value[index], fallbackItems[index], [
258
+ ...path,
259
+ index,
260
+ ]);
261
+ issues.push(...child.issues);
262
+ if (child.omit) {
263
+ omitted = true;
264
+ continue;
265
+ }
266
+ next.push(child.value);
267
+ }
268
+ return { value: issues.length > 0 || omitted ? next : value, issues };
269
+ }
270
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
271
+ const shape = def?.type === 'object' ? (unwrapped?.shape ?? def.shape ?? {}) : {};
272
+ const fallbackObject = effectiveFallback &&
273
+ typeof effectiveFallback === 'object' &&
274
+ !Array.isArray(effectiveFallback)
275
+ ? effectiveFallback
276
+ : {};
277
+ const input = value;
278
+ const next = { ...input };
279
+ const issues = [];
280
+ for (const key of Object.keys(input)) {
281
+ const child = sanitizeNumericValue(shape[key] ?? null, input[key], fallbackObject[key], [
282
+ ...path,
283
+ key,
284
+ ]);
285
+ issues.push(...child.issues);
286
+ if (child.omit) {
287
+ delete next[key];
288
+ }
289
+ else {
290
+ next[key] = child.value;
291
+ }
292
+ }
293
+ return { value: issues.length > 0 ? next : value, issues };
294
+ }
295
+ return { value, issues: [] };
296
+ }
297
+ function formatNumericValue(value) {
298
+ if (Number.isNaN(value))
299
+ return 'NaN';
300
+ if (value === Infinity)
301
+ return 'Infinity';
302
+ if (value === -Infinity)
303
+ return '-Infinity';
304
+ return String(value);
305
+ }
306
+ function numericSanitizeIssuesToMessage(issues) {
307
+ return issues
308
+ .map((issue) => {
309
+ const path = issue.path.map(String).join('.') || '<root>';
310
+ const to = issue.to === undefined ? '' : ` -> ${formatNumericValue(issue.to)}`;
311
+ return `${path}: ${formatNumericValue(issue.from)} ${issue.action}${to}`;
312
+ })
313
+ .join('; ');
314
+ }
315
+ function warnSanitizedNodeMutation(mutation, nodeId, issues) {
316
+ console.warn(`[Scene] Sanitized invalid numeric node ${mutation}`, nodeId, numericSanitizeIssuesToMessage(issues));
317
+ }
318
+ function parseCreatedNode(node, parentId) {
319
+ const candidate = { ...node, parentId };
320
+ const parsed = AnyNodeSchema.safeParse(candidate);
321
+ if (parsed.success)
322
+ return parsed.data;
323
+ const schema = getNodeSchemaForType(candidate.type);
324
+ const sanitized = sanitizeNumericValue(schema, candidate, undefined, []);
325
+ if (sanitized.issues.length === 0) {
326
+ return candidate;
327
+ }
328
+ warnSanitizedNodeMutation('create', node.id, sanitized.issues);
329
+ return sanitized.value;
330
+ }
331
+ function parseUpdatedNode(currentNode, data) {
332
+ const candidate = { ...currentNode, ...data };
333
+ const parsed = AnyNodeSchema.safeParse(candidate);
334
+ if (parsed.success)
335
+ return parsed.data;
336
+ const schema = getNodeSchemaForType(candidate.type);
337
+ const sanitized = sanitizeNumericValue(schema, data, currentNode, []);
338
+ if (sanitized.issues.length === 0) {
339
+ return candidate;
340
+ }
341
+ warnSanitizedNodeMutation('update', currentNode.id, sanitized.issues);
342
+ return { ...currentNode, ...sanitized.value };
343
+ }
2
344
  // Track pending RAF for updateNodesAction to prevent multiple queued callbacks
3
345
  let pendingRafId = null;
4
346
  let pendingUpdates = new Set();
@@ -154,21 +496,25 @@ export const createNodesAction = (set, get, ops) => {
154
496
  const nextRootIds = [...state.rootNodeIds];
155
497
  for (const { node, parentId } of ops) {
156
498
  const effectiveParentId = parentId ?? node.parentId ?? null;
157
- // 1. Assign parentId to the child (Safe because BaseNode has parentId)
158
- const newNode = {
159
- ...node,
160
- parentId: effectiveParentId,
161
- };
499
+ const newNode = parseCreatedNode(node, effectiveParentId);
162
500
  nextNodes[newNode.id] = newNode;
163
- // 2. Update the Parent's children list
501
+ // 2. Update the Parent's children list. We append to ANY container
502
+ // parent (kind has `children` in its schema) — if the field is
503
+ // present but undefined (e.g. an old saved scene from before the
504
+ // kind gained children), we initialise to `[]` first so the
505
+ // reparenting goes through. Without this, hosting items on an
506
+ // old shelf (v1, before `children` was added) silently no-ops:
507
+ // the item is reparented to the shelf but the shelf's children
508
+ // array is never updated, so `ParametricNodeRenderer` doesn't
509
+ // mount it and the item "disappears".
164
510
  if (effectiveParentId && nextNodes[effectiveParentId]) {
165
511
  const parent = nextNodes[effectiveParentId];
166
- // Type Guard: Check if the parent node is a container that supports children
167
- if ('children' in parent && Array.isArray(parent.children)) {
512
+ if ('children' in parent) {
513
+ const existing = parent.children;
514
+ const children = Array.isArray(existing) ? existing : [];
168
515
  nextNodes[effectiveParentId] = {
169
516
  ...parent,
170
- // Use Set to prevent duplicate IDs if createNode is called twice
171
- children: Array.from(new Set([...parent.children, newNode.id])), // We don't verify child types here
517
+ children: Array.from(new Set([...children, newNode.id])),
172
518
  };
173
519
  }
174
520
  }
@@ -190,6 +536,125 @@ export const createNodesAction = (set, get, ops) => {
190
536
  get().markDirty(node.parentId);
191
537
  });
192
538
  };
539
+ export const applyNodeChangesAction = (set, get, changes) => {
540
+ if (get().readOnly)
541
+ return;
542
+ const createOps = changes.create ?? [];
543
+ const updateOps = changes.update ?? [];
544
+ const deleteOps = changes.delete ?? [];
545
+ const nodesToMarkDirty = new Set();
546
+ const parentsToMarkDirty = new Set();
547
+ set((state) => {
548
+ const nextNodes = { ...state.nodes };
549
+ const nextCollections = { ...state.collections };
550
+ const nextRootIds = [...state.rootNodeIds];
551
+ let resolvedRootIds = nextRootIds;
552
+ for (const { id, data } of updateOps) {
553
+ const currentNode = nextNodes[id];
554
+ if (!currentNode)
555
+ continue;
556
+ const updatedNode = parseUpdatedNode(currentNode, data);
557
+ if (data.parentId !== undefined && data.parentId !== currentNode.parentId) {
558
+ const oldParentId = currentNode.parentId;
559
+ if (oldParentId && nextNodes[oldParentId]) {
560
+ const oldParent = nextNodes[oldParentId];
561
+ nextNodes[oldParent.id] = {
562
+ ...oldParent,
563
+ children: oldParent.children.filter((childId) => childId !== id),
564
+ };
565
+ parentsToMarkDirty.add(oldParent.id);
566
+ }
567
+ const newParentId = data.parentId;
568
+ if (newParentId && nextNodes[newParentId]) {
569
+ const newParent = nextNodes[newParentId];
570
+ nextNodes[newParent.id] = {
571
+ ...newParent,
572
+ children: Array.from(new Set([...newParent.children, id])),
573
+ };
574
+ parentsToMarkDirty.add(newParent.id);
575
+ }
576
+ }
577
+ nextNodes[id] = updatedNode;
578
+ nodesToMarkDirty.add(id);
579
+ }
580
+ for (const { node, parentId } of createOps) {
581
+ const effectiveParentId = parentId ?? node.parentId ?? null;
582
+ const newNode = parseCreatedNode(node, effectiveParentId);
583
+ nextNodes[newNode.id] = newNode;
584
+ nodesToMarkDirty.add(newNode.id);
585
+ if (effectiveParentId && nextNodes[effectiveParentId]) {
586
+ const parent = nextNodes[effectiveParentId];
587
+ if ('children' in parent && Array.isArray(parent.children)) {
588
+ nextNodes[effectiveParentId] = {
589
+ ...parent,
590
+ children: Array.from(new Set([...parent.children, newNode.id])),
591
+ };
592
+ parentsToMarkDirty.add(effectiveParentId);
593
+ }
594
+ }
595
+ else if (!effectiveParentId && !nextRootIds.includes(newNode.id)) {
596
+ nextRootIds.push(newNode.id);
597
+ }
598
+ }
599
+ const allIdsToDelete = new Set();
600
+ const collectDelete = (id) => {
601
+ if (allIdsToDelete.has(id))
602
+ return;
603
+ allIdsToDelete.add(id);
604
+ const node = nextNodes[id];
605
+ if (node && 'children' in node && Array.isArray(node.children)) {
606
+ for (const childId of node.children) {
607
+ collectDelete(childId);
608
+ }
609
+ }
610
+ };
611
+ for (const id of deleteOps) {
612
+ collectDelete(id);
613
+ }
614
+ for (const id of allIdsToDelete) {
615
+ const node = nextNodes[id];
616
+ if (!node)
617
+ continue;
618
+ const parentId = node.parentId;
619
+ if (parentId && nextNodes[parentId] && !allIdsToDelete.has(parentId)) {
620
+ const parent = nextNodes[parentId];
621
+ if (parent.children) {
622
+ nextNodes[parent.id] = {
623
+ ...parent,
624
+ children: parent.children.filter((childId) => childId !== id),
625
+ };
626
+ parentsToMarkDirty.add(parent.id);
627
+ }
628
+ }
629
+ resolvedRootIds = resolvedRootIds.filter((rootId) => rootId !== id);
630
+ if ('collectionIds' in node && node.collectionIds) {
631
+ for (const collectionId of node.collectionIds) {
632
+ const collection = nextCollections[collectionId];
633
+ if (collection) {
634
+ nextCollections[collectionId] = {
635
+ ...collection,
636
+ nodeIds: collection.nodeIds.filter((nodeId) => nodeId !== id),
637
+ };
638
+ }
639
+ }
640
+ }
641
+ delete nextNodes[id];
642
+ }
643
+ return { nodes: nextNodes, rootNodeIds: resolvedRootIds, collections: nextCollections };
644
+ });
645
+ for (const id of nodesToMarkDirty) {
646
+ get().markDirty(id);
647
+ }
648
+ for (const id of parentsToMarkDirty) {
649
+ get().markDirty(id);
650
+ const parent = get().nodes[id];
651
+ if (parent && 'children' in parent && Array.isArray(parent.children)) {
652
+ for (const childId of parent.children) {
653
+ get().markDirty(childId);
654
+ }
655
+ }
656
+ }
657
+ };
193
658
  export const updateNodesAction = (set, get, updates) => {
194
659
  if (get().readOnly)
195
660
  return;
@@ -200,31 +665,43 @@ export const updateNodesAction = (set, get, updates) => {
200
665
  const currentNode = nextNodes[id];
201
666
  if (!currentNode)
202
667
  continue;
668
+ const updatedNode = parseUpdatedNode(currentNode, data);
203
669
  // Handle Reparenting Logic
204
670
  if (data.parentId !== undefined && data.parentId !== currentNode.parentId) {
205
671
  // 1. Remove from old parent
206
672
  const oldParentId = currentNode.parentId;
207
673
  if (oldParentId && nextNodes[oldParentId]) {
208
674
  const oldParent = nextNodes[oldParentId];
675
+ const oldChildren = Array.isArray(oldParent.children)
676
+ ? oldParent.children
677
+ : [];
209
678
  nextNodes[oldParent.id] = {
210
679
  ...oldParent,
211
- children: oldParent.children.filter((childId) => childId !== id),
680
+ children: oldChildren.filter((childId) => childId !== id),
212
681
  };
213
682
  parentsToUpdate.add(oldParent.id);
214
683
  }
215
- // 2. Add to new parent
684
+ // 2. Add to new parent. Defensive against parents that don't yet
685
+ // carry a `children` array — older saved scenes can predate the
686
+ // schema field on a particular kind (shelf v1 → v2 added one),
687
+ // and a spread of `undefined` here throws and aborts the entire
688
+ // `set` callback. Initialising to `[]` matches what the schema's
689
+ // default would have produced.
216
690
  const newParentId = data.parentId;
217
691
  if (newParentId && nextNodes[newParentId]) {
218
692
  const newParent = nextNodes[newParentId];
693
+ const newChildren = Array.isArray(newParent.children)
694
+ ? newParent.children
695
+ : [];
219
696
  nextNodes[newParent.id] = {
220
697
  ...newParent,
221
- children: Array.from(new Set([...newParent.children, id])),
698
+ children: Array.from(new Set([...newChildren, id])),
222
699
  };
223
700
  parentsToUpdate.add(newParent.id);
224
701
  }
225
702
  }
226
703
  // Apply the update
227
- nextNodes[id] = { ...nextNodes[id], ...data };
704
+ nextNodes[id] = updatedNode;
228
705
  }
229
706
  return { nodes: nextNodes };
230
707
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=node-mutation-sanitize.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-mutation-sanitize.test.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-mutation-sanitize.test.ts"],"names":[],"mappings":""}