@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.
- package/dist/events/bus.d.ts +39 -13
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +33 -26
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
- package/dist/index.d.ts +22 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/lib/polygon-relations.d.ts +10 -0
- package/dist/lib/polygon-relations.d.ts.map +1 -0
- package/dist/lib/polygon-relations.js +73 -0
- package/dist/lib/space-detection-pause.test.d.ts +2 -0
- package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
- package/dist/lib/space-detection-pause.test.js +42 -0
- package/dist/lib/space-detection.d.ts +30 -1
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +215 -36
- package/dist/lib/space-detection.test.d.ts +2 -0
- package/dist/lib/space-detection.test.d.ts.map +1 -0
- package/dist/lib/space-detection.test.js +77 -0
- package/dist/material-library.d.ts +1 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +1940 -143
- package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
- package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
- package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
- package/dist/registry/handles.d.ts +321 -0
- package/dist/registry/handles.d.ts.map +1 -0
- package/dist/registry/handles.js +24 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/registry.d.ts +93 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +201 -0
- package/dist/registry/registry.test.d.ts +2 -0
- package/dist/registry/registry.test.d.ts.map +1 -0
- package/dist/registry/registry.test.js +183 -0
- package/dist/registry/relations-resolver.d.ts +42 -0
- package/dist/registry/relations-resolver.d.ts.map +1 -0
- package/dist/registry/relations-resolver.js +77 -0
- package/dist/registry/relations-resolver.test.d.ts +2 -0
- package/dist/registry/relations-resolver.test.d.ts.map +1 -0
- package/dist/registry/relations-resolver.test.js +183 -0
- package/dist/registry/scene-api.d.ts +44 -0
- package/dist/registry/scene-api.d.ts.map +1 -0
- package/dist/registry/scene-api.js +111 -0
- package/dist/registry/scene-api.test.d.ts +2 -0
- package/dist/registry/scene-api.test.d.ts.map +1 -0
- package/dist/registry/scene-api.test.js +183 -0
- package/dist/registry/subtree.d.ts +67 -0
- package/dist/registry/subtree.d.ts.map +1 -0
- package/dist/registry/subtree.js +119 -0
- package/dist/registry/subtree.test.d.ts +2 -0
- package/dist/registry/subtree.test.d.ts.map +1 -0
- package/dist/registry/subtree.test.js +133 -0
- package/dist/registry/types.d.ts +1420 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +1 -0
- package/dist/schema/index.d.ts +17 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +17 -3
- package/dist/schema/material.d.ts +10 -0
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +10 -0
- package/dist/schema/nodes/box-vent.d.ts +74 -0
- package/dist/schema/nodes/box-vent.d.ts.map +1 -0
- package/dist/schema/nodes/box-vent.js +63 -0
- package/dist/schema/nodes/building.d.ts +1 -1
- package/dist/schema/nodes/building.d.ts.map +1 -1
- package/dist/schema/nodes/building.js +3 -2
- package/dist/schema/nodes/ceiling.d.ts +2 -0
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +1 -1
- package/dist/schema/nodes/chimney.d.ts +158 -0
- package/dist/schema/nodes/chimney.d.ts.map +1 -0
- package/dist/schema/nodes/chimney.js +67 -0
- package/dist/schema/nodes/column.d.ts +597 -5
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +586 -6
- package/dist/schema/nodes/cupola.d.ts +66 -0
- package/dist/schema/nodes/cupola.d.ts.map +1 -0
- package/dist/schema/nodes/cupola.js +28 -0
- package/dist/schema/nodes/dormer.d.ts +228 -0
- package/dist/schema/nodes/dormer.d.ts.map +1 -0
- package/dist/schema/nodes/dormer.js +119 -0
- package/dist/schema/nodes/downspout.d.ts +75 -0
- package/dist/schema/nodes/downspout.d.ts.map +1 -0
- package/dist/schema/nodes/downspout.js +63 -0
- package/dist/schema/nodes/elevator.d.ts +105 -0
- package/dist/schema/nodes/elevator.d.ts.map +1 -0
- package/dist/schema/nodes/elevator.js +54 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
- package/dist/schema/nodes/eyebrow-vent.js +40 -0
- package/dist/schema/nodes/fence.d.ts +1 -0
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +2 -0
- package/dist/schema/nodes/gutter.d.ts +84 -0
- package/dist/schema/nodes/gutter.d.ts.map +1 -0
- package/dist/schema/nodes/gutter.js +76 -0
- package/dist/schema/nodes/item.d.ts +4 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +9 -0
- package/dist/schema/nodes/level.d.ts +1 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +2 -0
- package/dist/schema/nodes/ridge-vent.d.ts +67 -0
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
- package/dist/schema/nodes/ridge-vent.js +30 -0
- package/dist/schema/nodes/roof-segment.d.ts +195 -1
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +254 -3
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +4 -16
- package/dist/schema/nodes/shelf.d.ts +105 -0
- package/dist/schema/nodes/shelf.d.ts.map +1 -0
- package/dist/schema/nodes/shelf.js +84 -0
- package/dist/schema/nodes/site.d.ts +1 -122
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +2 -6
- package/dist/schema/nodes/skylight.d.ts +279 -0
- package/dist/schema/nodes/skylight.d.ts.map +1 -0
- package/dist/schema/nodes/skylight.js +152 -0
- package/dist/schema/nodes/slab.d.ts +2 -0
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +1 -1
- package/dist/schema/nodes/solar-panel.d.ts +117 -0
- package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
- package/dist/schema/nodes/solar-panel.js +47 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
- package/dist/schema/nodes/surface-hole-metadata.js +3 -1
- package/dist/schema/nodes/turbine-vent.d.ts +68 -0
- package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
- package/dist/schema/nodes/turbine-vent.js +41 -0
- package/dist/schema/types.d.ts +1325 -125
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +26 -0
- package/dist/services/alignment-anchors.d.ts +100 -0
- package/dist/services/alignment-anchors.d.ts.map +1 -0
- package/dist/services/alignment-anchors.js +283 -0
- package/dist/services/alignment-anchors.test.d.ts +2 -0
- package/dist/services/alignment-anchors.test.d.ts.map +1 -0
- package/dist/services/alignment-anchors.test.js +383 -0
- package/dist/services/alignment.d.ts +126 -0
- package/dist/services/alignment.d.ts.map +1 -0
- package/dist/services/alignment.js +169 -0
- package/dist/services/alignment.test.d.ts +2 -0
- package/dist/services/alignment.test.d.ts.map +1 -0
- package/dist/services/alignment.test.js +92 -0
- package/dist/services/drag-session.d.ts +47 -0
- package/dist/services/drag-session.d.ts.map +1 -0
- package/dist/services/drag-session.js +100 -0
- package/dist/services/drag-session.test.d.ts +2 -0
- package/dist/services/drag-session.test.d.ts.map +1 -0
- package/dist/services/drag-session.test.js +219 -0
- package/dist/services/hosting.d.ts +80 -0
- package/dist/services/hosting.d.ts.map +1 -0
- package/dist/services/hosting.js +122 -0
- package/dist/services/hosting.test.d.ts +2 -0
- package/dist/services/hosting.test.d.ts.map +1 -0
- package/dist/services/hosting.test.js +199 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +6 -0
- package/dist/services/movement.d.ts +48 -0
- package/dist/services/movement.d.ts.map +1 -0
- package/dist/services/movement.js +65 -0
- package/dist/services/movement.test.d.ts +2 -0
- package/dist/services/movement.test.d.ts.map +1 -0
- package/dist/services/movement.test.js +133 -0
- package/dist/services/single-undo-dance.test.d.ts +2 -0
- package/dist/services/single-undo-dance.test.d.ts.map +1 -0
- package/dist/services/single-undo-dance.test.js +210 -0
- package/dist/services/snap.d.ts +72 -0
- package/dist/services/snap.d.ts.map +1 -0
- package/dist/services/snap.js +120 -0
- package/dist/services/snap.test.d.ts +2 -0
- package/dist/services/snap.test.d.ts.map +1 -0
- package/dist/services/snap.test.js +95 -0
- package/dist/solar-panel-presets.d.ts +17 -0
- package/dist/solar-panel-presets.d.ts.map +1 -0
- package/dist/solar-panel-presets.js +43 -0
- package/dist/store/actions/node-actions.d.ts +14 -2
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +492 -15
- package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
- package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
- package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
- package/dist/store/actions/reparent.test.d.ts +2 -0
- package/dist/store/actions/reparent.test.d.ts.map +1 -0
- package/dist/store/actions/reparent.test.js +212 -0
- package/dist/store/use-interactive.d.ts +39 -0
- package/dist/store/use-interactive.d.ts.map +1 -1
- package/dist/store/use-interactive.js +75 -0
- package/dist/store/use-live-node-overrides.d.ts +22 -0
- package/dist/store/use-live-node-overrides.d.ts.map +1 -0
- package/dist/store/use-live-node-overrides.js +59 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-elevator-migration.test.js +109 -0
- package/dist/store/use-scene.d.ts +11 -0
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +196 -6
- package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
- package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-dispatch.js +57 -0
- package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
- package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-geometry.js +50 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.js +218 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-system.js +48 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime-system.js +8 -0
- package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
- package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.js +225 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.test.js +64 -0
- package/dist/systems/elevator/elevator-service.d.ts +19 -0
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-service.js +111 -0
- package/dist/systems/stair/stair-footprint.d.ts +44 -0
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
- package/dist/systems/stair/stair-footprint.js +168 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.js +93 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.test.js +80 -0
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +172 -147
- package/dist/systems/stair/stair-opening-sync.test.js +385 -2
- package/dist/systems/stair/stair-opening-system.d.ts +2 -0
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-system.js +104 -0
- package/dist/systems/wall/wall-move.d.ts +48 -0
- package/dist/systems/wall/wall-move.d.ts.map +1 -0
- package/dist/systems/wall/wall-move.js +193 -0
- package/dist/utils/clone-scene-graph.d.ts +6 -4
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +6 -4
- package/dist/utils/clone-scene-graph.test.d.ts +2 -0
- package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
- package/dist/utils/clone-scene-graph.test.js +59 -0
- package/dist/validation/validate-build-json.d.ts +43 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -0
- package/dist/validation/validate-build-json.js +227 -0
- 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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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:
|
|
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([...
|
|
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] =
|
|
704
|
+
nextNodes[id] = updatedNode;
|
|
228
705
|
}
|
|
229
706
|
return { nodes: nextNodes };
|
|
230
707
|
});
|
|
@@ -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":""}
|