@pascal-app/editor 0.8.0 → 0.9.2
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/package.json +15 -13
- package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +795 -28
- package/src/components/editor/editor-layout-mobile.tsx +8 -3
- package/src/components/editor/editor-layout-v2.tsx +76 -51
- package/src/components/editor/export-manager.tsx +69 -32
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
- package/src/components/editor/first-person/build-collider-world.ts +228 -83
- package/src/components/editor/first-person-controls.tsx +943 -27
- package/src/components/editor/floating-action-menu.tsx +676 -208
- package/src/components/editor/floating-building-action-menu.tsx +2 -2
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +6916 -12137
- package/src/components/editor/grid.tsx +197 -36
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +420 -0
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +396 -0
- package/src/components/editor/group-transform-shared.ts +489 -0
- package/src/components/editor/handles/handle-arrow.tsx +581 -0
- package/src/components/editor/handles/use-handle-drag.ts +246 -0
- package/src/components/editor/index.tsx +412 -162
- package/src/components/editor/measurement-pill.tsx +115 -0
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +1646 -0
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +1106 -483
- package/src/components/editor/site-edge-labels.tsx +45 -21
- package/src/components/editor/slab-hole-highlights.tsx +474 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +140 -41
- package/src/components/editor/use-floorplan-background-placement.ts +190 -35
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +19 -7
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +14 -19
- package/src/components/editor/wall-move-side-handles.tsx +870 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
- package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +375 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
- package/src/components/tools/fence/fence-drafting.ts +141 -27
- package/src/components/tools/item/move-tool.tsx +45 -105
- package/src/components/tools/item/placement-math.test.ts +22 -0
- package/src/components/tools/item/placement-math.ts +59 -7
- package/src/components/tools/item/placement-strategies.ts +432 -16
- package/src/components/tools/item/placement-types.ts +22 -1
- package/src/components/tools/item/use-draft-node.ts +51 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
- package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
- package/src/components/tools/roof/roof-tool.tsx +350 -28
- package/src/components/tools/select/box-select-state.ts +88 -0
- package/src/components/tools/select/box-select-tool.tsx +442 -520
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/floor-stack-preview.ts +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +305 -0
- package/src/components/tools/shared/polygon-editor.tsx +866 -175
- package/src/components/tools/shared/segment-angle.ts +56 -0
- package/src/components/tools/site/site-boundary-editor.tsx +520 -16
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +2 -1
- package/src/components/tools/stair/stair-tool.tsx +342 -37
- package/src/components/tools/tool-manager.tsx +239 -62
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +248 -171
- package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +86 -82
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -170
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +3 -117
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +27 -93
- package/src/components/ui/action-menu/view-toggles.tsx +279 -90
- package/src/components/ui/command-palette/editor-commands.tsx +20 -15
- package/src/components/ui/command-palette/index.tsx +5 -3
- package/src/components/ui/controls/material-paint-panel.tsx +152 -0
- package/src/components/ui/controls/material-picker.tsx +87 -177
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +142 -46
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +47 -10
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +235 -23
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
- package/src/components/ui/level-duplicate-dialog.tsx +2 -1
- package/src/components/ui/panels/node-display.ts +17 -16
- package/src/components/ui/panels/panel-manager.tsx +55 -58
- package/src/components/ui/panels/panel-wrapper.tsx +238 -12
- package/src/components/ui/panels/parametric-inspector.tsx +482 -0
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
- package/src/components/ui/sidebar/tab-bar.tsx +75 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +253 -61
- package/src/components/viewer-zone-system.tsx +7 -2
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +64 -10
- package/src/hooks/use-ceiling-events.ts +176 -0
- package/src/hooks/use-drag-action.ts +127 -0
- package/src/hooks/use-keyboard.ts +493 -95
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +451 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/constants.ts +6 -2
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +50 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +117 -0
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +10 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +339 -0
- package/src/lib/fresh-planar-placement.ts +130 -0
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +11 -1
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-duplication.ts +54 -42
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +227 -48
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +100 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +7 -7
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +332 -0
- package/src/lib/scene.ts +40 -8
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +69 -10
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +148 -55
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +231 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +767 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +39 -0
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +34 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
- package/src/components/tools/fence/move-fence-tool.tsx +0 -302
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -364
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
- package/src/components/tools/wall/move-wall-tool.tsx +0 -358
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -249
- package/src/components/ui/panels/column-panel.tsx +0 -759
- package/src/components/ui/panels/door-panel.tsx +0 -1299
- package/src/components/ui/panels/fence-panel.tsx +0 -222
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -280
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -161
- package/src/components/ui/panels/stair-panel.tsx +0 -577
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -273
- package/src/components/ui/panels/window-panel.tsx +0 -968
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
|
@@ -6,25 +6,26 @@ export type NodeDisplay = {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const TYPE_DEFAULTS: Record<string, NodeDisplay> = {
|
|
9
|
-
item: { icon: '/icons/
|
|
10
|
-
wall: { icon: '/icons/wall.
|
|
11
|
-
door: { icon: '/icons/door.
|
|
12
|
-
window: { icon: '/icons/window.
|
|
13
|
-
slab: { icon: '/icons/floor.
|
|
14
|
-
ceiling: { icon: '/icons/ceiling.
|
|
15
|
-
column: { icon: '/icons/column.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
item: { icon: '/icons/item.webp', label: 'Item' },
|
|
10
|
+
wall: { icon: '/icons/wall.webp', label: 'Wall' },
|
|
11
|
+
door: { icon: '/icons/door.webp', label: 'Door' },
|
|
12
|
+
window: { icon: '/icons/window.webp', label: 'Window' },
|
|
13
|
+
slab: { icon: '/icons/floor.webp', label: 'Slab' },
|
|
14
|
+
ceiling: { icon: '/icons/ceiling.webp', label: 'Ceiling' },
|
|
15
|
+
column: { icon: '/icons/column.webp', label: 'Column' },
|
|
16
|
+
elevator: { icon: '/icons/elevator.webp', label: 'Elevator' },
|
|
17
|
+
fence: { icon: '/icons/fence.webp', label: 'Fence' },
|
|
18
|
+
roof: { icon: '/icons/roof.webp', label: 'Roof' },
|
|
19
|
+
'roof-segment': { icon: '/icons/roof.webp', label: 'Roof segment' },
|
|
20
|
+
stair: { icon: '/icons/stairs.webp', label: 'Stair' },
|
|
21
|
+
'stair-segment': { icon: '/icons/stairs.webp', label: 'Stair segment' },
|
|
22
|
+
scan: { icon: '/icons/mesh.webp', label: '3D Scan' },
|
|
23
|
+
guide: { icon: '/icons/floorplan.webp', label: 'Guide image' },
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function getNodeDisplay(node: AnyNode | null | undefined): NodeDisplay {
|
|
26
|
-
if (!node) return { icon: '/icons/select.
|
|
27
|
-
const fallback = TYPE_DEFAULTS[node.type] ?? { icon: '/icons/select.
|
|
27
|
+
if (!node) return { icon: '/icons/select.webp', label: 'Selection' }
|
|
28
|
+
const fallback = TYPE_DEFAULTS[node.type] ?? { icon: '/icons/select.webp', label: node.type }
|
|
28
29
|
// Item nodes carry an asset with its own thumbnail/name
|
|
29
30
|
if (node.type === 'item') {
|
|
30
31
|
return {
|
|
@@ -5,8 +5,11 @@ import {
|
|
|
5
5
|
type AnyNodeId,
|
|
6
6
|
type BuildingNode,
|
|
7
7
|
type CeilingNode,
|
|
8
|
+
type ChimneyNode,
|
|
8
9
|
type ColumnNode,
|
|
9
10
|
type DoorNode,
|
|
11
|
+
type DormerNode,
|
|
12
|
+
type ElevatorNode,
|
|
10
13
|
type FenceNode,
|
|
11
14
|
type ItemNode,
|
|
12
15
|
type RoofNode,
|
|
@@ -23,31 +26,22 @@ import { useCallback, useEffect, useState } from 'react'
|
|
|
23
26
|
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
24
27
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
25
28
|
import useEditor from '../../../store/use-editor'
|
|
26
|
-
import { CeilingPanel } from './ceiling-panel'
|
|
27
|
-
import { ColumnPanel } from './column-panel'
|
|
28
|
-
import { DoorPanel } from './door-panel'
|
|
29
|
-
import { FencePanel } from './fence-panel'
|
|
30
|
-
import { ItemPanel } from './item-panel'
|
|
31
29
|
import { MobilePanelSheet } from './mobile-panel-sheet'
|
|
32
30
|
import { MobileSelectionBar } from './mobile-selection-bar'
|
|
33
31
|
import { getNodeDisplay } from './node-display'
|
|
34
|
-
import {
|
|
32
|
+
import { resetDesktopInspectorCollapsed } from './panel-wrapper'
|
|
33
|
+
import { ParametricInspector } from './parametric-inspector'
|
|
35
34
|
import { ReferencePanel } from './reference-panel'
|
|
36
|
-
import { RoofPanel } from './roof-panel'
|
|
37
|
-
import { RoofSegmentPanel } from './roof-segment-panel'
|
|
38
|
-
import { SlabPanel } from './slab-panel'
|
|
39
|
-
import { SpawnPanel } from './spawn-panel'
|
|
40
|
-
import { StairPanel } from './stair-panel'
|
|
41
|
-
import { StairSegmentPanel } from './stair-segment-panel'
|
|
42
|
-
import { WallPanel } from './wall-panel'
|
|
43
|
-
import { WindowPanel } from './window-panel'
|
|
44
35
|
|
|
45
36
|
type MovableNode =
|
|
46
37
|
| ItemNode
|
|
47
38
|
| WindowNode
|
|
48
39
|
| DoorNode
|
|
40
|
+
| ElevatorNode
|
|
49
41
|
| CeilingNode
|
|
42
|
+
| ChimneyNode
|
|
50
43
|
| ColumnNode
|
|
44
|
+
| DormerNode
|
|
51
45
|
| SlabNode
|
|
52
46
|
| WallNode
|
|
53
47
|
| FenceNode
|
|
@@ -61,8 +55,11 @@ const MOVABLE_TYPES = new Set<string>([
|
|
|
61
55
|
'item',
|
|
62
56
|
'window',
|
|
63
57
|
'door',
|
|
58
|
+
'elevator',
|
|
64
59
|
'ceiling',
|
|
60
|
+
'chimney',
|
|
65
61
|
'column',
|
|
62
|
+
'dormer',
|
|
66
63
|
'slab',
|
|
67
64
|
'wall',
|
|
68
65
|
'fence',
|
|
@@ -77,38 +74,17 @@ function isMovableNode(node: AnyNode | null): node is MovableNode {
|
|
|
77
74
|
return !!node && MOVABLE_TYPES.has(node.type)
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
function panelForType(type: string | null) {
|
|
77
|
+
function panelForType(type: string | null, footer?: React.ReactNode) {
|
|
81
78
|
if (!type) return null
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
case 'stair-segment':
|
|
92
|
-
return <StairSegmentPanel />
|
|
93
|
-
case 'slab':
|
|
94
|
-
return <SlabPanel />
|
|
95
|
-
case 'spawn':
|
|
96
|
-
return <SpawnPanel />
|
|
97
|
-
case 'ceiling':
|
|
98
|
-
return <CeilingPanel />
|
|
99
|
-
case 'column':
|
|
100
|
-
return <ColumnPanel />
|
|
101
|
-
case 'wall':
|
|
102
|
-
return <WallPanel />
|
|
103
|
-
case 'fence':
|
|
104
|
-
return <FencePanel />
|
|
105
|
-
case 'door':
|
|
106
|
-
return <DoorPanel />
|
|
107
|
-
case 'window':
|
|
108
|
-
return <WindowPanel />
|
|
109
|
-
default:
|
|
110
|
-
return null
|
|
111
|
-
}
|
|
79
|
+
// Every kind now renders through `<ParametricInspector>`, which either
|
|
80
|
+
// composes auto-derived editors from `parametrics.groups` or lazy-
|
|
81
|
+
// loads the kind-owned panel via `parametrics.customPanel`. The
|
|
82
|
+
// hardcoded switch is gone — all per-kind panel layout lives in
|
|
83
|
+
// `nodes/src/<kind>/panel.tsx`. The `type` arg is preserved for
|
|
84
|
+
// future cases where we might want a non-registry fallback (e.g.
|
|
85
|
+
// reference scale, paint mode); leave the function shape intact.
|
|
86
|
+
void type
|
|
87
|
+
return <ParametricInspector footer={footer} />
|
|
112
88
|
}
|
|
113
89
|
|
|
114
90
|
function MobilePanelLayer({
|
|
@@ -192,13 +168,12 @@ function MobilePanelLayer({
|
|
|
192
168
|
)
|
|
193
169
|
}
|
|
194
170
|
|
|
195
|
-
export function PanelManager() {
|
|
171
|
+
export function PanelManager({ inspectorFooter }: { inspectorFooter?: React.ReactNode }) {
|
|
196
172
|
const isMobile = useIsMobile()
|
|
197
173
|
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
174
|
+
const selectedZoneId = useViewer((s) => s.selection.zoneId)
|
|
175
|
+
const setSelection = useViewer((s) => s.setSelection)
|
|
198
176
|
const selectedReferenceId = useEditor((s) => s.selectedReferenceId)
|
|
199
|
-
const isPaintPanelOpen = useEditor((s) => s.isPaintPanelOpen)
|
|
200
|
-
const mode = useEditor((s) => s.mode)
|
|
201
|
-
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
202
177
|
// Only subscribe to the *type* of the single-selected node — string primitive
|
|
203
178
|
// so we don't re-render on unrelated scene mutations.
|
|
204
179
|
const selectedNodeType = useScene((s) => {
|
|
@@ -212,6 +187,27 @@ export function PanelManager() {
|
|
|
212
187
|
return id ? (s.nodes[id as AnyNodeId] ?? null) : null
|
|
213
188
|
})
|
|
214
189
|
|
|
190
|
+
// Node and reference selection are mutually exclusive: selecting a guide
|
|
191
|
+
// clears the node selection (handleGuideSelect), but node selection never
|
|
192
|
+
// cleared a lingering reference — so clicking a wall with a floorplan
|
|
193
|
+
// selected kept showing the reference panel. Clear the stale reference the
|
|
194
|
+
// moment a scene selection appears.
|
|
195
|
+
const setSelectedReferenceId = useEditor((s) => s.setSelectedReferenceId)
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (selectedIds.length > 0 || selectedZoneId) {
|
|
198
|
+
setSelectedReferenceId(null)
|
|
199
|
+
}
|
|
200
|
+
}, [selectedIds, selectedZoneId, setSelectedReferenceId])
|
|
201
|
+
|
|
202
|
+
// The inspector's expanded state is shared across panel swaps, but a fresh
|
|
203
|
+
// selection after everything was deselected should open collapsed again.
|
|
204
|
+
const hasAnySelection = selectedIds.length > 0 || Boolean(selectedZoneId) || Boolean(selectedReferenceId)
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (!hasAnySelection) {
|
|
207
|
+
resetDesktopInspectorCollapsed()
|
|
208
|
+
}
|
|
209
|
+
}, [hasAnySelection])
|
|
210
|
+
|
|
215
211
|
if (isMobile) {
|
|
216
212
|
if (selectedReferenceId) {
|
|
217
213
|
return <MobilePanelLayer isReference={true} node={null} panel={<ReferencePanel />} />
|
|
@@ -230,15 +226,16 @@ export function PanelManager() {
|
|
|
230
226
|
return <ReferencePanel />
|
|
231
227
|
}
|
|
232
228
|
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
229
|
+
if (selectedZoneId && selectedIds.length === 0) {
|
|
230
|
+
return (
|
|
231
|
+
<ParametricInspector
|
|
232
|
+
footer={inspectorFooter}
|
|
233
|
+
key={selectedZoneId}
|
|
234
|
+
nodeId={selectedZoneId as AnyNodeId}
|
|
235
|
+
onClose={() => setSelection({ zoneId: null })}
|
|
236
|
+
/>
|
|
237
|
+
)
|
|
240
238
|
}
|
|
241
239
|
|
|
242
|
-
|
|
243
|
-
return panelForType(selectedNodeType)
|
|
240
|
+
return panelForType(selectedNodeType, inspectorFooter)
|
|
244
241
|
}
|
|
@@ -1,17 +1,76 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { ChevronLeft, RotateCcw, X } from 'lucide-react'
|
|
3
|
+
import { ChevronDown, ChevronLeft, GripHorizontal, RotateCcw, X } from 'lucide-react'
|
|
4
4
|
import Image from 'next/image'
|
|
5
|
+
import {
|
|
6
|
+
createContext,
|
|
7
|
+
useCallback,
|
|
8
|
+
useContext,
|
|
9
|
+
useLayoutEffect,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from 'react'
|
|
5
13
|
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
6
14
|
import { cn } from '../../../lib/utils'
|
|
7
15
|
|
|
16
|
+
const DRAG_MARGIN = 8
|
|
17
|
+
// Pointer travel (px) below which a header press is treated as a click
|
|
18
|
+
// (toggles collapse) rather than a drag.
|
|
19
|
+
const CLICK_SLOP = 4
|
|
20
|
+
let desktopInspectorCollapsed = true
|
|
21
|
+
|
|
22
|
+
/** Forget the shared expanded state. Called when the last selection clears so
|
|
23
|
+
* a fresh selection opens the inspector collapsed — the sharing is only meant
|
|
24
|
+
* to survive swaps between panels (roof ↔ segment), not a close/reopen. */
|
|
25
|
+
export function resetDesktopInspectorCollapsed() {
|
|
26
|
+
desktopInspectorCollapsed = true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function clamp(value: number, min: number, max: number): number {
|
|
30
|
+
return Math.min(Math.max(value, min), Math.max(min, max))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Bounds the panel is allowed to occupy — the viewer column (tagged with
|
|
35
|
+
* `data-viewer-bounds`) so it can't slide under the sidebar or top bar.
|
|
36
|
+
* Falls back to the viewport when the marker isn't found.
|
|
37
|
+
*/
|
|
38
|
+
function getDragBounds(el: HTMLElement | null): {
|
|
39
|
+
left: number
|
|
40
|
+
top: number
|
|
41
|
+
right: number
|
|
42
|
+
bottom: number
|
|
43
|
+
} {
|
|
44
|
+
const region = el?.closest('[data-viewer-bounds]')
|
|
45
|
+
const rect = region?.getBoundingClientRect()
|
|
46
|
+
if (!rect) {
|
|
47
|
+
return { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight }
|
|
48
|
+
}
|
|
49
|
+
return { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Host-supplied inspector footer (e.g. community's "Save as preset"). The
|
|
54
|
+
* `PanelManager` provides it so every panel — including kind-owned
|
|
55
|
+
* `customPanel`s that render their own `<PanelWrapper>` without threading a
|
|
56
|
+
* `footer` prop — picks it up without per-kind wiring. An explicit `footer`
|
|
57
|
+
* prop still wins over the context.
|
|
58
|
+
*/
|
|
59
|
+
export const InspectorFooterContext = createContext<React.ReactNode>(null)
|
|
60
|
+
|
|
8
61
|
interface PanelWrapperProps {
|
|
9
62
|
title: string
|
|
10
|
-
|
|
63
|
+
/** Either a URL path (legacy panels pass `/icons/floor.webp` etc.,
|
|
64
|
+
* rendered via next/image) OR a React node (registry-driven
|
|
65
|
+
* inspector renders `<Icon icon="lucide:fence" />` from
|
|
66
|
+
* `def.presentation.icon`). */
|
|
67
|
+
icon?: string | React.ReactNode
|
|
11
68
|
onClose?: () => void
|
|
12
69
|
onReset?: () => void
|
|
13
70
|
onBack?: () => void
|
|
14
71
|
children: React.ReactNode
|
|
72
|
+
/** Pinned below the scrollable body, inside the panel card. */
|
|
73
|
+
footer?: React.ReactNode
|
|
15
74
|
className?: string
|
|
16
75
|
width?: number | string
|
|
17
76
|
}
|
|
@@ -23,25 +82,163 @@ export function PanelWrapper({
|
|
|
23
82
|
onReset,
|
|
24
83
|
onBack,
|
|
25
84
|
children,
|
|
85
|
+
footer,
|
|
26
86
|
className,
|
|
27
87
|
width = 320, // default width
|
|
28
88
|
}: PanelWrapperProps) {
|
|
29
89
|
const isMobile = useIsMobile()
|
|
90
|
+
const contextFooter = useContext(InspectorFooterContext)
|
|
91
|
+
const resolvedFooter = footer ?? contextFooter
|
|
92
|
+
|
|
93
|
+
const panelRef = useRef<HTMLDivElement>(null)
|
|
94
|
+
|
|
95
|
+
// The whole panel is collapsed to just its header by default; the chevron
|
|
96
|
+
// expands it to reveal the inspector body. Keep the desktop value shared
|
|
97
|
+
// across inspector swaps (roof ↔ segment, etc.) so navigating between
|
|
98
|
+
// related panels preserves whether the user left the inspector open.
|
|
99
|
+
const [collapsed, setCollapsedState] = useState(desktopInspectorCollapsed)
|
|
100
|
+
|
|
101
|
+
const setCollapsed = useCallback(
|
|
102
|
+
(next: boolean | ((previous: boolean) => boolean)) => {
|
|
103
|
+
setCollapsedState((previous) => {
|
|
104
|
+
const resolved = typeof next === 'function' ? next(previous) : next
|
|
105
|
+
desktopInspectorCollapsed = resolved
|
|
106
|
+
return resolved
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
[],
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// Drag-to-reposition from the header. `offset` is a translation applied on
|
|
113
|
+
// top of the default `top-20 right-4` anchor; null until first dragged.
|
|
114
|
+
// Dragging is clamped so no edge of the panel leaves the viewport.
|
|
115
|
+
const [offset, setOffset] = useState<{ x: number; y: number } | null>(null)
|
|
116
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
117
|
+
const dragRef = useRef<{
|
|
118
|
+
startX: number
|
|
119
|
+
startY: number
|
|
120
|
+
baseX: number
|
|
121
|
+
baseY: number
|
|
122
|
+
rectLeft: number
|
|
123
|
+
rectTop: number
|
|
124
|
+
width: number
|
|
125
|
+
height: number
|
|
126
|
+
minLeft: number
|
|
127
|
+
maxLeft: number
|
|
128
|
+
minTop: number
|
|
129
|
+
maxTop: number
|
|
130
|
+
moved: boolean
|
|
131
|
+
} | null>(null)
|
|
132
|
+
|
|
133
|
+
const handleHeaderPointerDown = useCallback(
|
|
134
|
+
(e: React.PointerEvent<HTMLDivElement>) => {
|
|
135
|
+
// Buttons (close / reset / collapse) handle their own clicks.
|
|
136
|
+
if ((e.target as HTMLElement).closest('button')) return
|
|
137
|
+
const rect = panelRef.current?.getBoundingClientRect()
|
|
138
|
+
if (!rect) return
|
|
139
|
+
const bounds = getDragBounds(panelRef.current)
|
|
140
|
+
const base = offset ?? { x: 0, y: 0 }
|
|
141
|
+
dragRef.current = {
|
|
142
|
+
startX: e.clientX,
|
|
143
|
+
startY: e.clientY,
|
|
144
|
+
baseX: base.x,
|
|
145
|
+
baseY: base.y,
|
|
146
|
+
rectLeft: rect.left,
|
|
147
|
+
rectTop: rect.top,
|
|
148
|
+
width: rect.width,
|
|
149
|
+
height: rect.height,
|
|
150
|
+
minLeft: bounds.left + DRAG_MARGIN,
|
|
151
|
+
maxLeft: bounds.right - rect.width - DRAG_MARGIN,
|
|
152
|
+
minTop: bounds.top + DRAG_MARGIN,
|
|
153
|
+
maxTop: bounds.bottom - rect.height - DRAG_MARGIN,
|
|
154
|
+
moved: false,
|
|
155
|
+
}
|
|
156
|
+
setIsDragging(true)
|
|
157
|
+
e.currentTarget.setPointerCapture(e.pointerId)
|
|
158
|
+
},
|
|
159
|
+
[offset],
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const handleHeaderPointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
163
|
+
const drag = dragRef.current
|
|
164
|
+
if (!drag) return
|
|
165
|
+
const dx = e.clientX - drag.startX
|
|
166
|
+
const dy = e.clientY - drag.startY
|
|
167
|
+
// Hold position until the press clearly becomes a drag, so a click can
|
|
168
|
+
// still toggle collapse.
|
|
169
|
+
if (!drag.moved && Math.hypot(dx, dy) <= CLICK_SLOP) return
|
|
170
|
+
drag.moved = true
|
|
171
|
+
const left = clamp(drag.rectLeft + dx, drag.minLeft, drag.maxLeft)
|
|
172
|
+
const top = clamp(drag.rectTop + dy, drag.minTop, drag.maxTop)
|
|
173
|
+
setOffset({ x: drag.baseX + (left - drag.rectLeft), y: drag.baseY + (top - drag.rectTop) })
|
|
174
|
+
}, [])
|
|
175
|
+
|
|
176
|
+
const handleHeaderPointerUp = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
177
|
+
const drag = dragRef.current
|
|
178
|
+
if (!drag) return
|
|
179
|
+
dragRef.current = null
|
|
180
|
+
setIsDragging(false)
|
|
181
|
+
e.currentTarget.releasePointerCapture(e.pointerId)
|
|
182
|
+
// A press that never turned into a drag is a click → toggle collapse.
|
|
183
|
+
if (!drag.moved) setCollapsed((c) => !c)
|
|
184
|
+
}, [])
|
|
185
|
+
|
|
186
|
+
// Expanding can grow the panel past an edge if it was dragged there while
|
|
187
|
+
// collapsed — nudge it back inside the viewer bounds.
|
|
188
|
+
useLayoutEffect(() => {
|
|
189
|
+
if (isMobile || collapsed) return
|
|
190
|
+
const el = panelRef.current
|
|
191
|
+
if (!el) return
|
|
192
|
+
const rect = el.getBoundingClientRect()
|
|
193
|
+
const bounds = getDragBounds(el)
|
|
194
|
+
const left = clamp(rect.left, bounds.left + DRAG_MARGIN, bounds.right - rect.width - DRAG_MARGIN)
|
|
195
|
+
const top = clamp(rect.top, bounds.top + DRAG_MARGIN, bounds.bottom - rect.height - DRAG_MARGIN)
|
|
196
|
+
const dx = left - rect.left
|
|
197
|
+
const dy = top - rect.top
|
|
198
|
+
if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) {
|
|
199
|
+
setOffset((prev) => ({ x: (prev?.x ?? 0) + dx, y: (prev?.y ?? 0) + dy }))
|
|
200
|
+
}
|
|
201
|
+
}, [collapsed, isMobile])
|
|
30
202
|
|
|
31
203
|
return (
|
|
32
204
|
<div
|
|
33
205
|
className={cn(
|
|
34
206
|
isMobile
|
|
35
207
|
? 'flex h-full w-full flex-col overflow-hidden bg-transparent dark:text-foreground'
|
|
36
|
-
|
|
208
|
+
// Cap height at `100dvh - 154px` so a tall panel's bottom edge
|
|
209
|
+
// aligns flush with the top of the floating bottom action bar.
|
|
210
|
+
// Combined with `top-20` (80px), the panel's bottom sits at
|
|
211
|
+
// `100dvh - 74px` — just clearing the bar without leaving a
|
|
212
|
+
// visible gap. The inner `flex-1 overflow-y-auto` content area
|
|
213
|
+
// (below) handles vertical scrolling when content exceeds the
|
|
214
|
+
// cap.
|
|
215
|
+
: 'pointer-events-auto fixed top-20 right-4 z-50 flex max-h-[calc(100dvh-154px)] flex-col overflow-hidden rounded-xl border border-border/50 bg-sidebar/95 shadow-2xl backdrop-blur-xl dark:text-foreground',
|
|
37
216
|
className,
|
|
38
217
|
)}
|
|
39
|
-
|
|
218
|
+
ref={panelRef}
|
|
219
|
+
style={
|
|
220
|
+
isMobile
|
|
221
|
+
? undefined
|
|
222
|
+
: {
|
|
223
|
+
width,
|
|
224
|
+
transform: offset ? `translate(${offset.x}px, ${offset.y}px)` : undefined,
|
|
225
|
+
}
|
|
226
|
+
}
|
|
40
227
|
>
|
|
41
|
-
{/* Header — desktop only; mobile sheet provides its own header
|
|
228
|
+
{/* Header — desktop only; mobile sheet provides its own header. Doubles
|
|
229
|
+
as the drag handle (grip in the middle) for repositioning the panel. */}
|
|
42
230
|
{!isMobile && (
|
|
43
|
-
<div
|
|
44
|
-
|
|
231
|
+
<div
|
|
232
|
+
className={cn(
|
|
233
|
+
'relative flex select-none items-center justify-between px-3 py-3',
|
|
234
|
+
!collapsed && 'border-border/50 border-b',
|
|
235
|
+
isDragging ? 'cursor-grabbing' : 'cursor-grab',
|
|
236
|
+
)}
|
|
237
|
+
onPointerDown={handleHeaderPointerDown}
|
|
238
|
+
onPointerMove={handleHeaderPointerMove}
|
|
239
|
+
onPointerUp={handleHeaderPointerUp}
|
|
240
|
+
>
|
|
241
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
45
242
|
{onBack && (
|
|
46
243
|
<button
|
|
47
244
|
className="mr-1 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
|
|
@@ -51,14 +248,26 @@ export function PanelWrapper({
|
|
|
51
248
|
<ChevronLeft className="h-4 w-4" />
|
|
52
249
|
</button>
|
|
53
250
|
)}
|
|
54
|
-
{icon &&
|
|
55
|
-
|
|
56
|
-
|
|
251
|
+
{icon &&
|
|
252
|
+
(typeof icon === 'string' ? (
|
|
253
|
+
<Image
|
|
254
|
+
alt=""
|
|
255
|
+
className="shrink-0 object-contain"
|
|
256
|
+
height={16}
|
|
257
|
+
src={icon}
|
|
258
|
+
width={16}
|
|
259
|
+
/>
|
|
260
|
+
) : (
|
|
261
|
+
<span className="flex shrink-0 items-center justify-center">{icon}</span>
|
|
262
|
+
))}
|
|
57
263
|
<h2 className="truncate font-semibold text-foreground text-sm tracking-tight">
|
|
58
264
|
{title}
|
|
59
265
|
</h2>
|
|
60
266
|
</div>
|
|
61
267
|
|
|
268
|
+
{/* Centered grip — purely a visual drag affordance. */}
|
|
269
|
+
<GripHorizontal className="-translate-x-1/2 pointer-events-none absolute left-1/2 h-4 w-4 text-muted-foreground/40" />
|
|
270
|
+
|
|
62
271
|
<div className="flex items-center gap-1">
|
|
63
272
|
{onReset && (
|
|
64
273
|
<button
|
|
@@ -69,6 +278,17 @@ export function PanelWrapper({
|
|
|
69
278
|
<RotateCcw className="h-4 w-4" />
|
|
70
279
|
</button>
|
|
71
280
|
)}
|
|
281
|
+
<button
|
|
282
|
+
aria-expanded={!collapsed}
|
|
283
|
+
aria-label={collapsed ? 'Expand panel' : 'Collapse panel'}
|
|
284
|
+
className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
|
|
285
|
+
onClick={() => setCollapsed((c) => !c)}
|
|
286
|
+
type="button"
|
|
287
|
+
>
|
|
288
|
+
<ChevronDown
|
|
289
|
+
className={cn('h-4 w-4 transition-transform', collapsed ? '' : 'rotate-180')}
|
|
290
|
+
/>
|
|
291
|
+
</button>
|
|
72
292
|
{onClose && (
|
|
73
293
|
<button
|
|
74
294
|
className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
|
|
@@ -82,8 +302,14 @@ export function PanelWrapper({
|
|
|
82
302
|
</div>
|
|
83
303
|
)}
|
|
84
304
|
|
|
85
|
-
{/* Content */}
|
|
86
|
-
|
|
305
|
+
{/* Content — hidden while the panel is collapsed (desktop). */}
|
|
306
|
+
{!(collapsed && !isMobile) && (
|
|
307
|
+
<div className="no-scrollbar flex min-h-0 flex-1 flex-col overflow-y-auto">{children}</div>
|
|
308
|
+
)}
|
|
309
|
+
|
|
310
|
+
{resolvedFooter && !(collapsed && !isMobile) && (
|
|
311
|
+
<div className="shrink-0 border-border/50 border-t p-3">{resolvedFooter}</div>
|
|
312
|
+
)}
|
|
87
313
|
</div>
|
|
88
314
|
)
|
|
89
315
|
}
|