@pascal-app/editor 1.0.0 → 1.0.3

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 (177) hide show
  1. package/package.json +12 -6
  2. package/src/components/editor/bake-exporter.tsx +4 -0
  3. package/src/components/editor/bake-thumbnail.tsx +4 -1
  4. package/src/components/editor/custom-camera-controls.tsx +22 -74
  5. package/src/components/editor/editor-layout-mobile.tsx +1 -0
  6. package/src/components/editor/floating-action-menu.tsx +14 -1
  7. package/src/components/editor/floorplan-panel.tsx +233 -166
  8. package/src/components/editor/grid.tsx +31 -7
  9. package/src/components/editor/group-actions.ts +5 -9
  10. package/src/components/editor/group-floating-action-menu.tsx +19 -9
  11. package/src/components/editor/group-move-3d.ts +4 -6
  12. package/src/components/editor/group-rotate-handle.tsx +81 -35
  13. package/src/components/editor/group-selection-box-3d.tsx +25 -17
  14. package/src/components/editor/group-transform-shared.test.ts +126 -1
  15. package/src/components/editor/group-transform-shared.ts +79 -73
  16. package/src/components/editor/handles/handle-arrow.tsx +72 -2
  17. package/src/components/editor/handles/handle-placement.test.ts +111 -0
  18. package/src/components/editor/handles/handle-placement.ts +69 -0
  19. package/src/components/editor/handles/linear-resize-drag.ts +13 -2
  20. package/src/components/editor/handles/use-handle-drag.ts +80 -8
  21. package/src/components/editor/index.tsx +36 -13
  22. package/src/components/editor/node-action-menu.tsx +2 -0
  23. package/src/components/editor/node-arrow-handles.tsx +162 -69
  24. package/src/components/editor/registry-action-contributions.tsx +53 -0
  25. package/src/components/editor/selection-manager.tsx +49 -0
  26. package/src/components/editor/thumbnail-generator.tsx +11 -2
  27. package/src/components/editor/wall-move-side-handles.tsx +71 -21
  28. package/src/components/editor/wall-opening-highlights.tsx +2 -3
  29. package/src/components/editor-2d/floorplan-group-move.tsx +17 -24
  30. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +33 -12
  31. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +12 -1
  32. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +154 -49
  33. package/src/components/editor-2d/renderers/floorplan-draft-wall-measurement.tsx +166 -0
  34. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +49 -0
  35. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +113 -24
  36. package/src/components/procedural-items/inspector.tsx +223 -0
  37. package/src/components/systems/ceiling/ceiling-system.tsx +3 -1
  38. package/src/components/systems/zone/zone-system.tsx +8 -4
  39. package/src/components/tools/__fixtures__/hosting.ts +10 -0
  40. package/src/components/tools/item/item-surface-placement.test.ts +169 -0
  41. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +204 -0
  42. package/src/components/tools/item/placement-strategies.ts +212 -200
  43. package/src/components/tools/item/placement-types.ts +4 -0
  44. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +257 -0
  45. package/src/components/tools/item/use-draft-node.ts +136 -47
  46. package/src/components/tools/item/use-placement-coordinator.tsx +391 -157
  47. package/src/components/tools/registry/item-surface-child-frame.test.ts +90 -0
  48. package/src/components/tools/registry/item-surface-move.test.ts +509 -0
  49. package/src/components/tools/registry/item-surface-move.ts +463 -0
  50. package/src/components/tools/registry/item-surface-resolver.test.ts +257 -0
  51. package/src/components/tools/registry/move-registry-node-tool.tsx +237 -78
  52. package/src/components/tools/shared/affordance-dispatch.test.ts +24 -0
  53. package/src/components/tools/shared/affordance-dispatch.ts +13 -0
  54. package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
  55. package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
  56. package/src/components/tools/shared/pointer-support-cap.ts +27 -13
  57. package/src/components/tools/shared/shelf-stickiness.ts +54 -0
  58. package/src/components/tools/shared/surface-hit.test.ts +56 -0
  59. package/src/components/tools/shared/surface-hit.ts +23 -0
  60. package/src/components/tools/shared/surface-rejection.test.tsx +107 -0
  61. package/src/components/tools/shared/surface-rejection.tsx +43 -0
  62. package/src/components/tools/tool-manager.tsx +36 -3
  63. package/src/components/tools/zone/zone-tool.tsx +13 -4
  64. package/src/components/ui/command-palette/editor-commands.tsx +54 -1
  65. package/src/components/ui/controls/material-paint-panel.tsx +6 -67
  66. package/src/components/ui/controls/material-picker.tsx +5 -67
  67. package/src/components/ui/controls/metric-control.tsx +29 -13
  68. package/src/components/ui/controls/slider-control.tsx +87 -35
  69. package/src/components/ui/floating-level-selector.tsx +81 -5
  70. package/src/components/ui/helpers/helper-manager.tsx +24 -6
  71. package/src/components/ui/helpers/registered-tool-helper.tsx +2 -32
  72. package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
  73. package/src/components/ui/panels/multi-height-mode.tsx +27 -21
  74. package/src/components/ui/panels/multi-parametric-inspector.tsx +6 -3
  75. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  76. package/src/components/ui/panels/parametric-inspector.tsx +9 -17
  77. package/src/components/ui/panels/reference-panel.tsx +3 -3
  78. package/src/components/ui/primitives/checkbox.tsx +34 -0
  79. package/src/components/ui/primitives/shortcut-token.tsx +4 -0
  80. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  81. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +669 -336
  82. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +2 -2
  83. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +31 -4
  84. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +30 -11
  85. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +8 -1
  86. package/src/components/ui/sidebar/panels/site-panel/index.tsx +106 -4
  87. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +2 -7
  88. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +3 -8
  89. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +6 -1
  90. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +224 -0
  91. package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +22 -0
  92. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +24 -7
  93. package/src/components/ui/snap-target-badge.tsx +2 -0
  94. package/src/components/viewer/floorplan-preview-visibility.test.ts +35 -0
  95. package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
  96. package/src/components/viewer/floorplan-preview.tsx +1 -12
  97. package/src/components/viewer/viewer-scene-header.tsx +3 -0
  98. package/src/components/viewer/viewer-units-panel.tsx +116 -0
  99. package/src/components/viewer-zone-system.tsx +7 -2
  100. package/src/hooks/use-auto-save.integration.test.tsx +197 -0
  101. package/src/hooks/use-auto-save.ts +21 -6
  102. package/src/hooks/use-ceiling-events.test.ts +25 -2
  103. package/src/hooks/use-ceiling-events.ts +7 -1
  104. package/src/hooks/use-grid-events.test.ts +2 -0
  105. package/src/hooks/use-grid-events.ts +16 -11
  106. package/src/hooks/use-keyboard.test.ts +7 -0
  107. package/src/hooks/use-keyboard.ts +43 -28
  108. package/src/index.tsx +68 -3
  109. package/src/lib/catalog-panel-model.ts +39 -0
  110. package/src/lib/catalog-selection.test.ts +27 -0
  111. package/src/lib/catalog-selection.ts +8 -0
  112. package/src/lib/contextual-help.test.ts +27 -1
  113. package/src/lib/contextual-help.ts +16 -9
  114. package/src/lib/export-texture-utils.test.ts +167 -0
  115. package/src/lib/export-texture-utils.ts +121 -0
  116. package/src/lib/export-user-data.test.ts +48 -0
  117. package/src/lib/export-user-data.ts +69 -0
  118. package/src/lib/floorplan/floorplan-export.test.ts +142 -2
  119. package/src/lib/floorplan/floorplan-export.tsx +9 -3
  120. package/src/lib/floorplan/floorplan-extension.ts +16 -0
  121. package/src/lib/floorplan/floorplan-readonly.ts +4 -0
  122. package/src/lib/fresh-planar-placement.ts +173 -33
  123. package/src/lib/glb-export-serialize.test.ts +280 -0
  124. package/src/lib/glb-export.test.ts +265 -16
  125. package/src/lib/glb-export.ts +197 -37
  126. package/src/lib/grid-frame.test.ts +19 -0
  127. package/src/lib/grid-frame.ts +18 -0
  128. package/src/lib/interaction/scope.ts +9 -1
  129. package/src/lib/keyboard-pan.test.ts +62 -0
  130. package/src/lib/keyboard-pan.ts +89 -0
  131. package/src/lib/level-duplication.test.ts +73 -0
  132. package/src/lib/level-duplication.ts +32 -2
  133. package/src/lib/linear-display.test.ts +59 -0
  134. package/src/lib/linear-display.ts +39 -0
  135. package/src/lib/local-guide-image.test.ts +24 -0
  136. package/src/lib/local-guide-image.ts +34 -1
  137. package/src/lib/material-catalog-model.ts +72 -0
  138. package/src/lib/material-paint-panel-model.ts +76 -0
  139. package/src/lib/node-action-movement.ts +21 -0
  140. package/src/lib/overlay-transform.test.ts +28 -0
  141. package/src/lib/overlay-transform.ts +12 -0
  142. package/src/lib/panel-rows.ts +75 -0
  143. package/src/lib/panel-tool-options.test.ts +34 -0
  144. package/src/lib/panel-tool-options.ts +46 -0
  145. package/src/lib/parametric-node-update.ts +37 -0
  146. package/src/lib/portable-export.test.ts +89 -0
  147. package/src/lib/portable-export.ts +84 -11
  148. package/src/lib/print-shell-compiler-manifold-core.ts +59 -10
  149. package/src/lib/print-shell-compiler-manifold-worker.ts +14 -1
  150. package/src/lib/print-shell-compiler-manifold.worker.ts +1 -1
  151. package/src/lib/print-shell-compiler-protocol.ts +8 -0
  152. package/src/lib/scene-clipboard.ts +1 -1
  153. package/src/lib/scene.test.ts +99 -0
  154. package/src/lib/scene.ts +21 -3
  155. package/src/lib/send-to-app.test.ts +108 -0
  156. package/src/lib/send-to-app.ts +121 -0
  157. package/src/lib/sfx/movement-tick.ts +37 -0
  158. package/src/lib/snapping-mode.test.ts +13 -2
  159. package/src/lib/snapping-mode.ts +11 -6
  160. package/src/lib/spatial-drag-plane.test.ts +53 -0
  161. package/src/lib/spatial-drag-plane.ts +19 -0
  162. package/src/lib/spatial-pointer-input.test.ts +72 -0
  163. package/src/lib/spatial-pointer-input.ts +62 -0
  164. package/src/lib/spatial-snap.test.ts +47 -0
  165. package/src/lib/surface-attachment.ts +77 -0
  166. package/src/lib/terrain-panel-model.ts +117 -0
  167. package/src/lib/units.ts +254 -0
  168. package/src/lib/usdz-export.ts +43 -16
  169. package/src/lib/use-linear-display.ts +8 -32
  170. package/src/lib/world-grid-snap.test.ts +48 -0
  171. package/src/lib/world-grid-snap.ts +9 -5
  172. package/src/store/rotation-snapping.test.ts +53 -0
  173. package/src/store/use-editor.tsx +10 -2
  174. package/src/store/use-floorplan-draft-preview.ts +10 -1
  175. package/src/store/use-handle-group.ts +4 -0
  176. package/src/store/use-interaction-scope.ts +155 -6
  177. package/src/store/use-send-to-app.ts +21 -0
@@ -0,0 +1,257 @@
1
+ import { afterEach, beforeEach, expect, spyOn, test } from 'bun:test'
2
+ import * as core from '@pascal-app/core'
3
+ import {
4
+ type AnyNodeId,
5
+ type GridEvent,
6
+ type ItemEvent,
7
+ ItemNode,
8
+ LevelNode,
9
+ nodeRegistry,
10
+ type SurfacePlacement,
11
+ sceneRegistry,
12
+ useLiveTransforms,
13
+ useScene,
14
+ } from '@pascal-app/core'
15
+ import { Group, Vector3 } from 'three'
16
+ import { commitFreshPlacementSubtree } from '../../../lib/fresh-planar-placement'
17
+ import useEditor from '../../../store/use-editor'
18
+ import useInteractionScope from '../../../store/use-interaction-scope'
19
+ import { registerHostingTestNode } from '../__fixtures__/hosting'
20
+ import { createRegistryItemSurfaceMove } from './item-surface-move'
21
+
22
+ const level = LevelNode.parse({ id: 'level_resolver' })
23
+ const asset = {
24
+ id: 'table',
25
+ name: 'Table',
26
+ category: 'furniture',
27
+ thumbnail: '',
28
+ src: '/table.glb',
29
+ dimensions: [3, 1, 3],
30
+ }
31
+ const host = ItemNode.parse({
32
+ id: 'item_resolver-host',
33
+ parentId: level.id,
34
+ asset,
35
+ position: [4, 0, 5],
36
+ rotation: [0, 0.6, 0],
37
+ })
38
+ const child = ItemNode.parse({
39
+ id: 'item_resolver-child',
40
+ parentId: level.id,
41
+ asset,
42
+ position: [1, 0, 2],
43
+ rotation: [0.1, 0.3, 0.2],
44
+ metadata: { isNew: true },
45
+ visible: false,
46
+ })
47
+ const dimensions: [number, number, number] = [1, 1, 0.5]
48
+ const savedRaf = globalThis.requestAnimationFrame
49
+ const savedCancelRaf = globalThis.cancelAnimationFrame
50
+ let savedNodes: ReturnType<typeof useScene.getState>['nodes']
51
+ let savedRoots: AnyNodeId[]
52
+ let savedEditor: ReturnType<typeof useEditor.getState>
53
+ let savedScope: ReturnType<typeof useInteractionScope.getState>
54
+ let restoreRegistry: () => void
55
+ let resolver: ReturnType<typeof spyOn<typeof core, 'resolveSurfacePlacement'>>
56
+
57
+ beforeEach(() => {
58
+ globalThis.requestAnimationFrame = (callback) => {
59
+ callback(0)
60
+ return 0
61
+ }
62
+ globalThis.cancelAnimationFrame = () => {}
63
+ savedEditor = useEditor.getState()
64
+ savedScope = useInteractionScope.getState()
65
+ useInteractionScope.setState({ scope: { kind: 'idle' } })
66
+ useEditor.setState({ mode: 'build', tool: 'item' })
67
+ useEditor.getState().setSnappingMode('item', 'off')
68
+ savedNodes = useScene.getState().nodes
69
+ savedRoots = useScene.getState().rootNodeIds
70
+ restoreRegistry = nodeRegistry._snapshot()
71
+ nodeRegistry._reset()
72
+ registerHostingTestNode({
73
+ kind: 'item',
74
+ schemaVersion: 1,
75
+ schema: ItemNode,
76
+ category: 'furnish',
77
+ defaults: () => ({}),
78
+ capabilities: {
79
+ hostable: { parents: ['level', 'item'], align: 'face' },
80
+ floorPlaced: {},
81
+ },
82
+ })
83
+ useScene.setState({
84
+ nodes: {
85
+ [level.id]: { ...level, children: [host.id, child.id] },
86
+ [host.id]: host,
87
+ [child.id]: child,
88
+ },
89
+ rootNodeIds: [level.id],
90
+ })
91
+ useScene.temporal.getState().clear()
92
+ useScene.temporal.getState().pause()
93
+ const levelMesh = new Group()
94
+ levelMesh.position.set(10, 3, 20)
95
+ levelMesh.rotation.y = 0.4
96
+ const hostMesh = new Group()
97
+ hostMesh.position.set(...host.position)
98
+ hostMesh.rotation.set(...host.rotation)
99
+ levelMesh.add(hostMesh)
100
+ levelMesh.updateMatrixWorld(true)
101
+ sceneRegistry.nodes.set(level.id, levelMesh)
102
+ sceneRegistry.nodes.set(host.id, hostMesh)
103
+ resolver = spyOn(core, 'resolveSurfacePlacement')
104
+ })
105
+
106
+ afterEach(() => {
107
+ resolver.mockRestore()
108
+ useEditor.setState(savedEditor)
109
+ useInteractionScope.setState(savedScope)
110
+ restoreRegistry()
111
+ useLiveTransforms.getState().clear(child.id)
112
+ sceneRegistry.nodes.delete(level.id)
113
+ sceneRegistry.nodes.delete(host.id)
114
+ useScene.setState({ nodes: savedNodes, rootNodeIds: savedRoots })
115
+ useScene.temporal.getState().clear()
116
+ useScene.temporal.getState().resume()
117
+ globalThis.requestAnimationFrame = savedRaf
118
+ globalThis.cancelAnimationFrame = savedCancelRaf
119
+ })
120
+
121
+ function hit(local: [number, number, number] = [0.2, 1, 0.3]): ItemEvent {
122
+ const object = sceneRegistry.nodes.get(host.id)!
123
+ return {
124
+ node: host,
125
+ object,
126
+ normal: [0, 1, 0],
127
+ localPosition: local,
128
+ position: object.localToWorld(new Vector3(...local)).toArray(),
129
+ nativeEvent: {},
130
+ stopPropagation() {},
131
+ } as ItemEvent
132
+ }
133
+
134
+ test('the registry asks the shared resolver with host-local input and keeps the frozen live output', () => {
135
+ const event = hit()
136
+ const session = createRegistryItemSurfaceMove(child)!
137
+ // Captured from resolveItemSurfacePlacement at ad2a57b8 with snapping off.
138
+ const expected: {
139
+ position: [number, number, number]
140
+ worldPosition: [number, number, number]
141
+ rotationY: number
142
+ } = {
143
+ position: [0.20000000000000284, 1, 0.29999999999999716],
144
+ worldPosition: [15.991837444170788, 4, 23.04142809557868],
145
+ rotationY: -0.30000000000000004,
146
+ }
147
+ expect(session.enter(event, dimensions, 0.3)).toEqual(expected)
148
+ expect(resolver).toHaveBeenCalledTimes(1)
149
+ const args = resolver.mock.calls[0]![0]
150
+ expect(args.host).toBe(host)
151
+ expect(args.childKind).toBe(child.type)
152
+ expect(args.childFootprint.size).toEqual(dimensions)
153
+ expect(args.childFootprint.rotationY).toBe(expected!.rotationY)
154
+ expect(args.hit.normalWorldY).toBe(1)
155
+ expect(args.scene.get(host.id)).toBe(useScene.getState().nodes[host.id])
156
+ expect(args.snapScalar).toBeFunction()
157
+ expect(args.checkFootprint).toBe(true)
158
+ })
159
+
160
+ test('a null resolver result leaves the fresh floor draft and live transform untouched', () => {
161
+ resolver.mockReturnValue(null)
162
+ const session = createRegistryItemSurfaceMove(child)!
163
+ const event = hit()
164
+ const stopped = spyOn(event, 'stopPropagation')
165
+ useLiveTransforms.getState().set(child.id, { position: [8, 0, 9], rotation: 0.3 })
166
+ const before = useScene.getState().nodes
167
+ expect(session.enter(event, dimensions, 0.3)).toBeNull()
168
+ expect(resolver).toHaveBeenCalledTimes(1)
169
+ expect(session.hosted).toBe(false)
170
+ expect(session.blocksGrid(event as unknown as GridEvent)).toBe(false)
171
+ expect(stopped).not.toHaveBeenCalled()
172
+ expect(useScene.getState().nodes).toBe(before)
173
+ expect(useLiveTransforms.getState().get(child.id)?.position).toEqual([8, 0, 9])
174
+ session.restore()
175
+ expect(useScene.getState().nodes[child.id]).toBe(child)
176
+ })
177
+
178
+ test('registry entry and subsequent moves keep checking fit; commit uses the accepted stored pose', () => {
179
+ const session = createRegistryItemSurfaceMove(child)!
180
+ const first = session.enter(hit(), dimensions, 0.3)!
181
+ const moved = session.enter(hit([0.7, 1, -0.4]), dimensions, first.rotationY)!
182
+ expect(moved).not.toBeNull()
183
+ const oversized = session.enter(hit(), [4, 1, 4], moved.rotationY)!
184
+ expect(oversized).not.toBeNull()
185
+ const accepted = useScene.getState().nodes[child.id]
186
+ expect(session.enter(hit([2, 1, 0]), [4, 1, 4], moved.rotationY)).toBeNull()
187
+ expect(useScene.getState().nodes[child.id]).toBe(accepted)
188
+ expect(resolver.mock.calls.map(([args]) => args.checkFootprint)).toEqual([true, true, true, true])
189
+ const finalId = commitFreshPlacementSubtree(child.id, { visible: true })!
190
+ expect(useScene.getState().nodes[finalId]).toMatchObject({
191
+ parentId: host.id,
192
+ position: oversized.position,
193
+ rotation: [0.1, oversized.rotationY, 0.2],
194
+ })
195
+ expect(resolver).toHaveBeenCalledTimes(4)
196
+ })
197
+
198
+ test('the mover rejects itself before asking the resolver', () => {
199
+ const session = createRegistryItemSurfaceMove(host)!
200
+ const before = useScene.getState().nodes
201
+ expect(session.enter(hit(), dimensions, 0.6)).toBeNull()
202
+ expect(resolver).not.toHaveBeenCalled()
203
+ expect(useScene.getState().nodes).toBe(before)
204
+ })
205
+
206
+ test('the mover rejects an indirect descendant before asking the resolver', () => {
207
+ const intermediate = ItemNode.parse({ id: 'item_resolver-middle', parentId: child.id, asset })
208
+ useScene.setState({
209
+ nodes: {
210
+ ...useScene.getState().nodes,
211
+ [intermediate.id]: intermediate,
212
+ [host.id]: { ...host, parentId: intermediate.id },
213
+ },
214
+ })
215
+ const session = createRegistryItemSurfaceMove(child)!
216
+ const before = useScene.getState().nodes
217
+ expect(session.enter(hit(), dimensions, 0.3)).toBeNull()
218
+ expect(resolver).not.toHaveBeenCalled()
219
+ expect(useScene.getState().nodes).toBe(before)
220
+ })
221
+
222
+ test.each([
223
+ 'host-local',
224
+ 'surface-local',
225
+ ] as const)('writes the resolver pose in its %s child frame', (childFrame) => {
226
+ const placement: SurfacePlacement = {
227
+ position: [0.25, 1.5, -0.75],
228
+ rotationY: 0.4,
229
+ surfaceId: 'test-surface',
230
+ childFrame,
231
+ surfaceLocal: { position: [-0.5, 0, 0.25], rotationY: -0.2, rotation: [0.7, -0.2, 0.8] },
232
+ }
233
+ resolver.mockReturnValue(placement)
234
+ const session = createRegistryItemSurfaceMove(child)!
235
+ const pose = session.enter(hit(), dimensions, 0.3)!
236
+ const expected = childFrame === 'surface-local' ? placement.surfaceLocal! : placement
237
+ expect(pose.position).toEqual([...expected.position])
238
+ expect(pose.rotationY).toBe(expected.rotationY)
239
+ expect(useScene.getState().nodes[child.id]).toMatchObject({
240
+ parentId: host.id,
241
+ position: expected.position,
242
+ rotation:
243
+ childFrame === 'surface-local'
244
+ ? placement.surfaceLocal!.rotation
245
+ : [0.1, placement.rotationY, 0.2],
246
+ })
247
+ })
248
+
249
+ test('non-item events never reach the resolver', () => {
250
+ const session = createRegistryItemSurfaceMove(child)!
251
+ const before = useScene.getState().nodes
252
+ expect(
253
+ session.enter({ ...hit(), node: level } as unknown as ItemEvent, dimensions, 0.3),
254
+ ).toBeNull()
255
+ expect(resolver).not.toHaveBeenCalled()
256
+ expect(useScene.getState().nodes).toBe(before)
257
+ })