@pascal-app/editor 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +18 -15
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +110 -42
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +77 -68
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +31 -11
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +2 -2
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +458 -140
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/footprint-height-handle.tsx +2 -2
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +23 -1
- package/src/components/editor/index.tsx +150 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +2 -3
- package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +4 -3
- package/src/components/editor/room-handles-2d.tsx +158 -7
- package/src/components/editor/room-handles.tsx +244 -54
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +33 -17
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +7 -3
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +31 -15
- package/src/components/editor/wall-move-side-handles.tsx +15 -7
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +30 -19
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +2 -6
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +47 -19
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
- package/src/components/ui/helpers/helper-manager.tsx +136 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
- package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +11 -10
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +9 -5
- package/src/components/ui/panels/panel-wrapper.tsx +63 -127
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +465 -66
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +22 -13
- package/src/components/ui/panels/room-panel.tsx +207 -100
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +21 -10
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +62 -5
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15849 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +39331 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +39331 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +39331 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +39331 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +224 -45
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +101 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +3 -2
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +15 -6
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +24 -6
- package/src/lib/measurements.ts +1 -203
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-assembly-overlay.ts +1 -1
- package/src/lib/room-built-on.test.ts +28 -17
- package/src/lib/room-built-on.ts +21 -17
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +141 -1
- package/src/lib/room-handle-drag.ts +554 -58
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +38 -1
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-bounds.test.ts +144 -0
- package/src/lib/scene-bounds.ts +79 -15
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +11 -5
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +21 -6
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +17 -6
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +44 -0
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +53 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/store/use-wall-snap-indicator.ts +15 -2
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/window-interaction.ts +0 -100
|
@@ -9,49 +9,74 @@ import {
|
|
|
9
9
|
normalizeFloorplanAnnotationVisibility,
|
|
10
10
|
} from '../lib/floorplan/annotation-visibility'
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
type
|
|
14
|
-
|
|
12
|
+
DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES,
|
|
13
|
+
type FloorplanWallDimensionFaces,
|
|
14
|
+
normalizeFloorplanWallDimensionFaces,
|
|
15
15
|
} from '../lib/floorplan/floorplan-extension'
|
|
16
16
|
|
|
17
|
-
type
|
|
17
|
+
type PersistedFloorplanAnnotationState = {
|
|
18
18
|
visibility: FloorplanAnnotationVisibility
|
|
19
|
-
|
|
19
|
+
showDimensions: boolean
|
|
20
|
+
wallDimensionFaces: FloorplanWallDimensionFaces
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type FloorplanAnnotationVisibilityState = PersistedFloorplanAnnotationState & {
|
|
20
24
|
setCategory: (category: FloorplanAnnotationCategory, visible: boolean) => void
|
|
21
|
-
|
|
25
|
+
setShowDimensions: (shown: boolean) => void
|
|
26
|
+
setWallDimensionFaces: (faces: FloorplanWallDimensionFaces) => void
|
|
22
27
|
reset: () => void
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
const defaultState = (): PersistedFloorplanAnnotationState => ({
|
|
31
|
+
visibility: { ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY },
|
|
32
|
+
showDimensions: true,
|
|
33
|
+
wallDimensionFaces: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The persisted part of the store, read back from storage. Anything stale or
|
|
38
|
+
* unknown (an older shape, a retired `wallDimensionReference`) falls back to
|
|
39
|
+
* the default.
|
|
40
|
+
*/
|
|
41
|
+
function normalizeFloorplanAnnotationState(persisted: unknown): PersistedFloorplanAnnotationState {
|
|
42
|
+
const record = (persisted && typeof persisted === 'object' ? persisted : {}) as Record<
|
|
43
|
+
string,
|
|
44
|
+
unknown
|
|
45
|
+
>
|
|
46
|
+
return {
|
|
47
|
+
visibility: normalizeFloorplanAnnotationVisibility(record.visibility),
|
|
48
|
+
showDimensions: record.showDimensions !== false,
|
|
49
|
+
wallDimensionFaces: normalizeFloorplanWallDimensionFaces(record.wallDimensionFaces),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Display preferences for dimensions and annotations, kept per browser:
|
|
55
|
+
* Display → Dimensions (`showDimensions`) and Measure walls
|
|
56
|
+
* (`wallDimensionFaces`) apply to the plan and the 3D view; `visibility` is
|
|
57
|
+
* the Expert plan's annotation categories.
|
|
58
|
+
*/
|
|
25
59
|
const useFloorplanAnnotationVisibility = create<FloorplanAnnotationVisibilityState>()(
|
|
26
60
|
persist(
|
|
27
61
|
(set) => ({
|
|
28
|
-
|
|
29
|
-
wallDimensionReference: DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE,
|
|
62
|
+
...defaultState(),
|
|
30
63
|
setCategory: (category, visible) =>
|
|
31
64
|
set((state) => ({ visibility: { ...state.visibility, [category]: visible } })),
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
visibility: { ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY },
|
|
36
|
-
wallDimensionReference: DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE,
|
|
37
|
-
}),
|
|
65
|
+
setShowDimensions: (showDimensions) => set({ showDimensions }),
|
|
66
|
+
setWallDimensionFaces: (wallDimensionFaces) => set({ wallDimensionFaces }),
|
|
67
|
+
reset: () => set(defaultState()),
|
|
38
68
|
}),
|
|
39
69
|
{
|
|
40
70
|
name: 'pascal-floorplan-annotation-visibility',
|
|
41
71
|
merge: (persistedState, currentState) => ({
|
|
42
72
|
...currentState,
|
|
43
|
-
|
|
44
|
-
(persistedState as { visibility?: unknown } | undefined)?.visibility,
|
|
45
|
-
),
|
|
46
|
-
wallDimensionReference: normalizeFloorplanWallDimensionReference(
|
|
47
|
-
(persistedState as { wallDimensionReference?: unknown } | undefined)
|
|
48
|
-
?.wallDimensionReference,
|
|
49
|
-
),
|
|
73
|
+
...normalizeFloorplanAnnotationState(persistedState),
|
|
50
74
|
}),
|
|
51
75
|
partialize: (state) =>
|
|
52
76
|
({
|
|
53
77
|
visibility: state.visibility,
|
|
54
|
-
|
|
78
|
+
showDimensions: state.showDimensions,
|
|
79
|
+
wallDimensionFaces: state.wallDimensionFaces,
|
|
55
80
|
}) as FloorplanAnnotationVisibilityState,
|
|
56
81
|
},
|
|
57
82
|
),
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
isPlacementTypingKey,
|
|
4
|
+
PLACEMENT_TYPING_DISTANCE_OFFSET,
|
|
5
|
+
PLACEMENT_TYPING_LENGTH_ANGLE,
|
|
6
|
+
routePlacementTypingKey,
|
|
7
|
+
usePlacementTyping,
|
|
8
|
+
} from './use-placement-typing'
|
|
9
|
+
|
|
10
|
+
const key = (
|
|
11
|
+
k: string,
|
|
12
|
+
mods: Partial<Pick<KeyboardEvent, 'metaKey' | 'ctrlKey' | 'altKey'>> = {},
|
|
13
|
+
) => ({
|
|
14
|
+
key: k,
|
|
15
|
+
metaKey: false,
|
|
16
|
+
ctrlKey: false,
|
|
17
|
+
altKey: false,
|
|
18
|
+
...mods,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const beginTwoFields = (activeField: 0 | 1) => {
|
|
22
|
+
usePlacementTyping.getState().begin({ labels: PLACEMENT_TYPING_DISTANCE_OFFSET, activeField })
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('usePlacementTyping', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
usePlacementTyping.getState().clear()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('keeps two independent field buffers and toggles the active field', () => {
|
|
32
|
+
const typing = usePlacementTyping.getState()
|
|
33
|
+
typing.begin({ labels: PLACEMENT_TYPING_DISTANCE_OFFSET, fieldDefaults: ['1.2', '0'] })
|
|
34
|
+
typing.append('2')
|
|
35
|
+
typing.toggleField()
|
|
36
|
+
typing.append('-')
|
|
37
|
+
typing.append('4')
|
|
38
|
+
|
|
39
|
+
expect(usePlacementTyping.getState().fields).toEqual(['2', '-4'])
|
|
40
|
+
expect(usePlacementTyping.getState().fieldDefaults).toEqual(['1.2', '0'])
|
|
41
|
+
expect(usePlacementTyping.getState().activeField).toBe(1)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('backspace only changes the active field', () => {
|
|
45
|
+
const typing = usePlacementTyping.getState()
|
|
46
|
+
typing.begin({ labels: PLACEMENT_TYPING_DISTANCE_OFFSET })
|
|
47
|
+
typing.append('25')
|
|
48
|
+
typing.toggleField()
|
|
49
|
+
typing.append('3')
|
|
50
|
+
typing.backspace()
|
|
51
|
+
|
|
52
|
+
expect(usePlacementTyping.getState().fields).toEqual(['25', ''])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('publishes projection and an enter request without clearing input', () => {
|
|
56
|
+
const typing = usePlacementTyping.getState()
|
|
57
|
+
typing.begin({ labels: PLACEMENT_TYPING_DISTANCE_OFFSET })
|
|
58
|
+
typing.append('2')
|
|
59
|
+
typing.setProjectedPosition([2, 0, 1])
|
|
60
|
+
typing.requestCommit()
|
|
61
|
+
|
|
62
|
+
expect(usePlacementTyping.getState().projectedPosition).toEqual([2, 0, 1])
|
|
63
|
+
expect(usePlacementTyping.getState().submitRevision).toBe(1)
|
|
64
|
+
expect(usePlacementTyping.getState().isActive).toBe(true)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('clear ends the session and forgets its labels', () => {
|
|
68
|
+
usePlacementTyping.getState().begin({ labels: PLACEMENT_TYPING_LENGTH_ANGLE })
|
|
69
|
+
usePlacementTyping.getState().append('3')
|
|
70
|
+
usePlacementTyping.getState().clear()
|
|
71
|
+
const state = usePlacementTyping.getState()
|
|
72
|
+
expect(state.isActive).toBe(false)
|
|
73
|
+
expect(state.fields).toEqual(['', ''])
|
|
74
|
+
expect(state.labels).toEqual([])
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('routePlacementTypingKey', () => {
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
usePlacementTyping.getState().clear()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('a digit or a leading dot opens a session on the first field', () => {
|
|
84
|
+
expect(routePlacementTypingKey(key('5'), { begin: beginTwoFields })).toBe('edit')
|
|
85
|
+
expect(usePlacementTyping.getState().fields).toEqual(['5', ''])
|
|
86
|
+
usePlacementTyping.getState().clear()
|
|
87
|
+
expect(routePlacementTypingKey(key('.'), { begin: beginTwoFields })).toBe('edit')
|
|
88
|
+
expect(usePlacementTyping.getState().fields).toEqual(['.', ''])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('letters, signs and separators leave tool shortcuts alone until a session opens', () => {
|
|
92
|
+
for (const k of ['c', 'm', 't', 'f', '-', ' ', ',']) {
|
|
93
|
+
expect(routePlacementTypingKey(key(k), { begin: beginTwoFields })).toBeNull()
|
|
94
|
+
}
|
|
95
|
+
expect(usePlacementTyping.getState().isActive).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('no session opens when the tool refuses or offers no begin', () => {
|
|
99
|
+
expect(routePlacementTypingKey(key('5'), { begin: () => false })).toBeNull()
|
|
100
|
+
expect(routePlacementTypingKey(key('5'))).toBeNull()
|
|
101
|
+
expect(usePlacementTyping.getState().isActive).toBe(false)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('Tab opens a session on the second field only when the tool asks for it', () => {
|
|
105
|
+
expect(routePlacementTypingKey(key('Tab'), { begin: beginTwoFields })).toBeNull()
|
|
106
|
+
expect(routePlacementTypingKey(key('Tab'), { begin: beginTwoFields, tabBegins: true })).toBe(
|
|
107
|
+
'edit',
|
|
108
|
+
)
|
|
109
|
+
expect(usePlacementTyping.getState().activeField).toBe(1)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('inside a session: unit letters extend, Tab and comma switch, Enter commits', () => {
|
|
113
|
+
routePlacementTypingKey(key('1'), { begin: beginTwoFields })
|
|
114
|
+
for (const k of ['8', '0', 'c', 'm']) routePlacementTypingKey(key(k))
|
|
115
|
+
expect(routePlacementTypingKey(key('Tab'))).toBe('edit')
|
|
116
|
+
routePlacementTypingKey(key('-'))
|
|
117
|
+
routePlacementTypingKey(key('2'))
|
|
118
|
+
expect(routePlacementTypingKey(key(','))).toBe('edit')
|
|
119
|
+
expect(usePlacementTyping.getState().fields).toEqual(['180cm', '-2'])
|
|
120
|
+
expect(usePlacementTyping.getState().activeField).toBe(0)
|
|
121
|
+
expect(routePlacementTypingKey(key('Enter'))).toBe('commit')
|
|
122
|
+
expect(usePlacementTyping.getState().isActive).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('Backspace edits, Delete empties the active field', () => {
|
|
126
|
+
routePlacementTypingKey(key('4'), { begin: beginTwoFields })
|
|
127
|
+
routePlacementTypingKey(key('2'))
|
|
128
|
+
routePlacementTypingKey(key('Backspace'))
|
|
129
|
+
expect(usePlacementTyping.getState().fields[0]).toBe('4')
|
|
130
|
+
routePlacementTypingKey(key('Delete'))
|
|
131
|
+
expect(usePlacementTyping.getState().fields[0]).toBe('')
|
|
132
|
+
expect(usePlacementTyping.getState().isActive).toBe(true)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('the first Escape only ends the session', () => {
|
|
136
|
+
routePlacementTypingKey(key('3'), { begin: beginTwoFields })
|
|
137
|
+
expect(routePlacementTypingKey(key('Escape'))).toBe('clear')
|
|
138
|
+
expect(usePlacementTyping.getState().isActive).toBe(false)
|
|
139
|
+
expect(routePlacementTypingKey(key('Escape'))).toBeNull()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('chords are never consumed', () => {
|
|
143
|
+
expect(
|
|
144
|
+
routePlacementTypingKey(key('5', { metaKey: true }), { begin: beginTwoFields }),
|
|
145
|
+
).toBeNull()
|
|
146
|
+
routePlacementTypingKey(key('5'), { begin: beginTwoFields })
|
|
147
|
+
expect(routePlacementTypingKey(key('z', { ctrlKey: true }))).toBeNull()
|
|
148
|
+
expect(routePlacementTypingKey(key('Enter', { altKey: true }))).toBeNull()
|
|
149
|
+
expect(usePlacementTyping.getState().fields[0]).toBe('5')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('isPlacementTypingKey', () => {
|
|
154
|
+
test('accepts the measurement grammar', () => {
|
|
155
|
+
for (const k of ['0', '9', 'm', 'c', '.', "'", '"', '-', '+', ' ', '/']) {
|
|
156
|
+
expect(isPlacementTypingKey(k)).toBe(true)
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('rejects control keys and the field separator', () => {
|
|
161
|
+
for (const k of ['Enter', 'Escape', 'Tab', 'Shift', '', 'F1', ',']) {
|
|
162
|
+
expect(isPlacementTypingKey(k)).toBe(false)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
})
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { MessageDescriptor } from '@pascal-app/core'
|
|
2
|
+
import { create } from 'zustand'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Typed coordinate entry shared by the placement tools: a drafted wall's
|
|
6
|
+
* length/angle and a cabinet's distance/offset along its wall. One session at
|
|
7
|
+
* a time; the active tool owns what the two fields mean and reads them on
|
|
8
|
+
* every change, the HUD (`PlacementCoordinateInput`) only displays them.
|
|
9
|
+
*/
|
|
10
|
+
export type PlacementTypingField = 0 | 1
|
|
11
|
+
export type PlacementTypingValues = [string, string]
|
|
12
|
+
export type PlacementTypingProjection = [number, number, number]
|
|
13
|
+
|
|
14
|
+
export const PLACEMENT_TYPING_DISTANCE_OFFSET: readonly MessageDescriptor[] = [
|
|
15
|
+
/* i18n */ { id: 'editor.placementTyping.distance', message: 'Distance' },
|
|
16
|
+
/* i18n */ { id: 'editor.placementTyping.offset', message: 'Offset' },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
export const PLACEMENT_TYPING_LENGTH_ANGLE: readonly MessageDescriptor[] = [
|
|
20
|
+
/* i18n */ { id: 'editor.placementTyping.length', message: 'Length' },
|
|
21
|
+
/* i18n */ { id: 'editor.placementTyping.angle', message: 'Angle' },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
export type PlacementTypingSession = {
|
|
25
|
+
/** What each field means, in field order; the HUD shows one box per label. */
|
|
26
|
+
labels: readonly MessageDescriptor[]
|
|
27
|
+
fieldDefaults?: PlacementTypingValues
|
|
28
|
+
activeField?: PlacementTypingField
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type PlacementTypingState = {
|
|
32
|
+
isActive: boolean
|
|
33
|
+
labels: readonly MessageDescriptor[]
|
|
34
|
+
fields: PlacementTypingValues
|
|
35
|
+
/** What an empty field stands for, shown as its placeholder. */
|
|
36
|
+
fieldDefaults: PlacementTypingValues
|
|
37
|
+
activeField: PlacementTypingField
|
|
38
|
+
projectedPosition: PlacementTypingProjection | null
|
|
39
|
+
submitRevision: number
|
|
40
|
+
begin: (session: PlacementTypingSession) => void
|
|
41
|
+
append: (value: string) => void
|
|
42
|
+
backspace: () => void
|
|
43
|
+
setField: (field: PlacementTypingField, value: string) => void
|
|
44
|
+
setActiveField: (field: PlacementTypingField) => void
|
|
45
|
+
toggleField: () => void
|
|
46
|
+
setProjectedPosition: (position: PlacementTypingProjection | null) => void
|
|
47
|
+
requestCommit: () => void
|
|
48
|
+
clear: () => void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const EMPTY_VALUES: PlacementTypingValues = ['', '']
|
|
52
|
+
|
|
53
|
+
export const usePlacementTyping = create<PlacementTypingState>((set) => ({
|
|
54
|
+
isActive: false,
|
|
55
|
+
labels: [],
|
|
56
|
+
fields: [...EMPTY_VALUES],
|
|
57
|
+
fieldDefaults: [...EMPTY_VALUES],
|
|
58
|
+
activeField: 0,
|
|
59
|
+
projectedPosition: null,
|
|
60
|
+
submitRevision: 0,
|
|
61
|
+
begin: ({ labels, fieldDefaults = EMPTY_VALUES, activeField = 0 }) =>
|
|
62
|
+
set({
|
|
63
|
+
isActive: true,
|
|
64
|
+
labels,
|
|
65
|
+
fields: [...EMPTY_VALUES],
|
|
66
|
+
fieldDefaults: [...fieldDefaults],
|
|
67
|
+
activeField,
|
|
68
|
+
projectedPosition: null,
|
|
69
|
+
}),
|
|
70
|
+
append: (value) =>
|
|
71
|
+
set((state) => {
|
|
72
|
+
const fields: PlacementTypingValues = [...state.fields]
|
|
73
|
+
fields[state.activeField] += value
|
|
74
|
+
return { isActive: true, fields }
|
|
75
|
+
}),
|
|
76
|
+
backspace: () =>
|
|
77
|
+
set((state) => {
|
|
78
|
+
const fields: PlacementTypingValues = [...state.fields]
|
|
79
|
+
fields[state.activeField] = fields[state.activeField].slice(0, -1)
|
|
80
|
+
return { fields }
|
|
81
|
+
}),
|
|
82
|
+
setField: (field, value) =>
|
|
83
|
+
set((state) => {
|
|
84
|
+
const fields: PlacementTypingValues = [...state.fields]
|
|
85
|
+
fields[field] = value
|
|
86
|
+
return { isActive: true, fields }
|
|
87
|
+
}),
|
|
88
|
+
setActiveField: (activeField) => set({ activeField }),
|
|
89
|
+
toggleField: () => set((state) => ({ activeField: state.activeField === 0 ? 1 : 0 })),
|
|
90
|
+
setProjectedPosition: (projectedPosition) => set({ projectedPosition }),
|
|
91
|
+
requestCommit: () =>
|
|
92
|
+
set((state: PlacementTypingState) => ({ submitRevision: state.submitRevision + 1 })),
|
|
93
|
+
clear: () =>
|
|
94
|
+
set({
|
|
95
|
+
isActive: false,
|
|
96
|
+
labels: [],
|
|
97
|
+
fields: [...EMPTY_VALUES],
|
|
98
|
+
fieldDefaults: [...EMPTY_VALUES],
|
|
99
|
+
activeField: 0,
|
|
100
|
+
projectedPosition: null,
|
|
101
|
+
}),
|
|
102
|
+
}))
|
|
103
|
+
|
|
104
|
+
/** Keys that extend a field: digits, unit letters, separators, signs, fractions. */
|
|
105
|
+
export function isPlacementTypingKey(key: string): boolean {
|
|
106
|
+
return key.length === 1 && /^[0-9a-zA-Z.'"+\-/ ]$/.test(key)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Keys that may open a session. Letters only ever continue a field, so a
|
|
111
|
+
* tool's single-letter shortcuts keep working until a number is typed.
|
|
112
|
+
*/
|
|
113
|
+
export function isPlacementTypingStartKey(key: string): boolean {
|
|
114
|
+
return key.length === 1 && /^[0-9.]$/.test(key)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type PlacementTypingKeyOutcome = 'edit' | 'commit' | 'clear'
|
|
118
|
+
|
|
119
|
+
export type PlacementTypingKeyOptions = {
|
|
120
|
+
/**
|
|
121
|
+
* Opens a session for a start key (or Tab, with `tabBegins`) when none is
|
|
122
|
+
* active. Returns false when the tool cannot take typed entry right now.
|
|
123
|
+
*/
|
|
124
|
+
begin?: (activeField: PlacementTypingField) => boolean
|
|
125
|
+
/** Tab with no session opens one on the second field. */
|
|
126
|
+
tabBegins?: boolean
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The one keyboard convention for typed entry. Tab or comma switches field,
|
|
131
|
+
* Enter asks the tool to commit, Escape clears the session (the tool's next
|
|
132
|
+
* Escape cancels its draft), Backspace edits and Delete empties the active
|
|
133
|
+
* field. Chords with Cmd/Ctrl/Alt are never consumed. Returns null when the
|
|
134
|
+
* key is not part of typed entry; the caller prevents default and stops
|
|
135
|
+
* propagation for every other outcome.
|
|
136
|
+
*/
|
|
137
|
+
export function routePlacementTypingKey(
|
|
138
|
+
event: Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey' | 'altKey'>,
|
|
139
|
+
options: PlacementTypingKeyOptions = {},
|
|
140
|
+
): PlacementTypingKeyOutcome | null {
|
|
141
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return null
|
|
142
|
+
const typing = usePlacementTyping.getState()
|
|
143
|
+
const { key } = event
|
|
144
|
+
if (!typing.isActive) {
|
|
145
|
+
if (isPlacementTypingStartKey(key)) {
|
|
146
|
+
if (!options.begin?.(0)) return null
|
|
147
|
+
usePlacementTyping.getState().append(key)
|
|
148
|
+
return 'edit'
|
|
149
|
+
}
|
|
150
|
+
if (key === 'Tab' && options.tabBegins && options.begin?.(1)) return 'edit'
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
if (key === 'Tab' || key === ',') {
|
|
154
|
+
typing.toggleField()
|
|
155
|
+
return 'edit'
|
|
156
|
+
}
|
|
157
|
+
if (key === 'Enter') return 'commit'
|
|
158
|
+
if (key === 'Escape') {
|
|
159
|
+
typing.clear()
|
|
160
|
+
return 'clear'
|
|
161
|
+
}
|
|
162
|
+
if (key === 'Backspace') {
|
|
163
|
+
typing.backspace()
|
|
164
|
+
return 'edit'
|
|
165
|
+
}
|
|
166
|
+
if (key === 'Delete') {
|
|
167
|
+
typing.setField(typing.activeField, '')
|
|
168
|
+
return 'edit'
|
|
169
|
+
}
|
|
170
|
+
if (isPlacementTypingKey(key)) {
|
|
171
|
+
typing.append(key)
|
|
172
|
+
return 'edit'
|
|
173
|
+
}
|
|
174
|
+
return null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Parse options for a typed length: a bare number is read in the display unit. */
|
|
178
|
+
export function placementTypingLengthOptions(
|
|
179
|
+
unit: 'metric' | 'imperial',
|
|
180
|
+
metricNotation: 'meters' | 'millimeters',
|
|
181
|
+
) {
|
|
182
|
+
return {
|
|
183
|
+
bareUnit: unit === 'imperial' ? 'in' : metricNotation === 'millimeters' ? 'mm' : 'm',
|
|
184
|
+
system: unit === 'imperial' ? ('imperial' as const) : ('metric' as const),
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -7,8 +7,19 @@
|
|
|
7
7
|
|
|
8
8
|
import { create } from 'zustand'
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Which kind of geometry the draft point snapped to. A pulled wall also lands
|
|
12
|
+
* square on a wall (`perpendicular`), on a projection guide (`alignment`), or
|
|
13
|
+
* at a grid length (`grid`).
|
|
14
|
+
*/
|
|
15
|
+
export type WallSnapKind =
|
|
16
|
+
| 'endpoint'
|
|
17
|
+
| 'midpoint'
|
|
18
|
+
| 'intersection'
|
|
19
|
+
| 'wall'
|
|
20
|
+
| 'perpendicular'
|
|
21
|
+
| 'alignment'
|
|
22
|
+
| 'grid'
|
|
12
23
|
|
|
13
24
|
export type WallSnapPoint = {
|
|
14
25
|
/** Building-local plan coordinates (XZ meters). */
|
|
@@ -17,6 +28,8 @@ export type WallSnapPoint = {
|
|
|
17
28
|
kind: WallSnapKind
|
|
18
29
|
/** Optional wall ids whose geometry produced this snap. */
|
|
19
30
|
wallIds?: string[]
|
|
31
|
+
/** Words the marker shows beside its glyph ("Endpoint", "On wall"); none when absent. */
|
|
32
|
+
label?: string
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
type WallSnapIndicatorState = {
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_ELEVATOR_WIDTH = 1.84
|
|
2
|
-
export const DEFAULT_ELEVATOR_DEPTH = 1.84
|
|
3
|
-
export const DEFAULT_ELEVATOR_CAB_HEIGHT = 2.35
|
|
4
|
-
export const DEFAULT_ELEVATOR_DOOR_WIDTH = 0.95
|
|
5
|
-
export const DEFAULT_ELEVATOR_DOOR_HEIGHT = 2.1
|
|
6
|
-
export const DEFAULT_ELEVATOR_SPEED = 2.2
|
|
7
|
-
export const DEFAULT_ELEVATOR_DOOR_DURATION_MS = 900
|
|
8
|
-
export const DEFAULT_ELEVATOR_DWELL_MS = 1400
|