@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useScene } from '@pascal-app/core'
|
|
3
|
+
import { formatNumber, parseLocalizedNumber, useLocale, useScene } from '@pascal-app/core'
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
5
5
|
import { lingoUnitSpec, measurementHint, parseMeasurement } from '../../../lib/measurement-parser'
|
|
6
6
|
import { useLinearDisplay } from '../../../lib/use-linear-display'
|
|
@@ -20,6 +20,45 @@ interface MetricControlProps {
|
|
|
20
20
|
restoreOnCommit?: boolean
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export function parseMetricInput(
|
|
24
|
+
text: string,
|
|
25
|
+
unit: string,
|
|
26
|
+
options: { isImperial: boolean; locale: string; parseUnit?: string },
|
|
27
|
+
): number | null {
|
|
28
|
+
const spec = lingoUnitSpec(unit)
|
|
29
|
+
return spec
|
|
30
|
+
? parseMeasurement(text, spec, {
|
|
31
|
+
bareUnit: options.parseUnit ?? spec.unitId,
|
|
32
|
+
locale: options.locale,
|
|
33
|
+
system: options.isImperial ? 'us' : 'metric',
|
|
34
|
+
})
|
|
35
|
+
: parseLocalizedNumber(text, options.locale)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatMetricInputValue(
|
|
39
|
+
value: number,
|
|
40
|
+
precision: number,
|
|
41
|
+
locale: string,
|
|
42
|
+
): string {
|
|
43
|
+
return formatNumber(roundMetricValueForDisplay(value, precision), {
|
|
44
|
+
locale,
|
|
45
|
+
min: precision,
|
|
46
|
+
max: precision,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function roundMetricValueForDisplay(value: number, precision: number): number {
|
|
51
|
+
const rounded = Number(
|
|
52
|
+
formatNumber(value, {
|
|
53
|
+
locale: 'en',
|
|
54
|
+
min: precision,
|
|
55
|
+
max: precision,
|
|
56
|
+
useGrouping: false,
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
59
|
+
return Number.isFinite(rounded) ? rounded : value
|
|
60
|
+
}
|
|
61
|
+
|
|
23
62
|
export function MetricControl({
|
|
24
63
|
label,
|
|
25
64
|
value,
|
|
@@ -33,6 +72,7 @@ export function MetricControl({
|
|
|
33
72
|
unit = '',
|
|
34
73
|
restoreOnCommit = true,
|
|
35
74
|
}: MetricControlProps) {
|
|
75
|
+
const locale = useLocale()
|
|
36
76
|
const {
|
|
37
77
|
isImperial,
|
|
38
78
|
displayUnit,
|
|
@@ -51,16 +91,20 @@ export function MetricControl({
|
|
|
51
91
|
)
|
|
52
92
|
const roundStoredValueForDisplayPrecision = useCallback(
|
|
53
93
|
(storedValue: number) =>
|
|
54
|
-
clamp(toStoredValue(
|
|
94
|
+
clamp(toStoredValue(roundMetricValueForDisplay(toDisplayValue(storedValue), precision))),
|
|
55
95
|
[clamp, precision, toDisplayValue, toStoredValue],
|
|
56
96
|
)
|
|
57
97
|
|
|
58
98
|
const displayValue = toDisplayValue(value)
|
|
99
|
+
const formatDisplayValue = useCallback(
|
|
100
|
+
(nextValue: number) => formatMetricInputValue(nextValue, precision, locale),
|
|
101
|
+
[locale, precision],
|
|
102
|
+
)
|
|
59
103
|
|
|
60
104
|
const [isEditing, setIsEditing] = useState(false)
|
|
61
105
|
const [isDragging, setIsDragging] = useState(false)
|
|
62
106
|
const [isHovered, setIsHovered] = useState(false)
|
|
63
|
-
const [inputValue, setInputValue] = useState(
|
|
107
|
+
const [inputValue, setInputValue] = useState(() => formatDisplayValue(displayValue))
|
|
64
108
|
const startXRef = useRef(0)
|
|
65
109
|
const startValueRef = useRef(0)
|
|
66
110
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
@@ -81,9 +125,9 @@ export function MetricControl({
|
|
|
81
125
|
|
|
82
126
|
useEffect(() => {
|
|
83
127
|
if (!isEditing) {
|
|
84
|
-
setInputValue(displayValue
|
|
128
|
+
setInputValue(formatDisplayValue(displayValue))
|
|
85
129
|
}
|
|
86
|
-
}, [displayValue,
|
|
130
|
+
}, [displayValue, formatDisplayValue, isEditing])
|
|
87
131
|
|
|
88
132
|
useEffect(() => {
|
|
89
133
|
const container = containerRef.current
|
|
@@ -221,8 +265,8 @@ export function MetricControl({
|
|
|
221
265
|
|
|
222
266
|
const handleValueClick = useCallback(() => {
|
|
223
267
|
setIsEditing(true)
|
|
224
|
-
setInputValue(toDisplayValue(value)
|
|
225
|
-
}, [value, toDisplayValue
|
|
268
|
+
setInputValue(formatDisplayValue(toDisplayValue(value)))
|
|
269
|
+
}, [formatDisplayValue, value, toDisplayValue])
|
|
226
270
|
|
|
227
271
|
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
228
272
|
setInputValue(e.target.value)
|
|
@@ -230,18 +274,10 @@ export function MetricControl({
|
|
|
230
274
|
|
|
231
275
|
const submitValue = useCallback(() => {
|
|
232
276
|
const spec = lingoUnitSpec(unit)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
bareUnit: parseUnit ?? spec.unitId,
|
|
236
|
-
system: isImperial ? 'us' : 'metric',
|
|
237
|
-
})
|
|
238
|
-
: null
|
|
239
|
-
if (stored === null) {
|
|
240
|
-
const numValue = Number.parseFloat(inputValue)
|
|
241
|
-
stored = Number.isFinite(numValue) ? toStoredValue(numValue) : null
|
|
242
|
-
}
|
|
277
|
+
const parsed = parseMetricInput(inputValue, unit, { isImperial, locale, parseUnit })
|
|
278
|
+
const stored = parsed === null ? null : spec ? parsed : toStoredValue(parsed)
|
|
243
279
|
if (stored === null) {
|
|
244
|
-
setInputValue(toDisplayValue(value)
|
|
280
|
+
setInputValue(formatDisplayValue(toDisplayValue(value)))
|
|
245
281
|
} else {
|
|
246
282
|
applyCommittedValue(clamp(stored))
|
|
247
283
|
}
|
|
@@ -249,13 +285,14 @@ export function MetricControl({
|
|
|
249
285
|
}, [
|
|
250
286
|
inputValue,
|
|
251
287
|
unit,
|
|
288
|
+
locale,
|
|
252
289
|
isImperial,
|
|
253
290
|
parseUnit,
|
|
254
291
|
applyCommittedValue,
|
|
255
292
|
clamp,
|
|
256
293
|
toStoredValue,
|
|
257
294
|
value,
|
|
258
|
-
|
|
295
|
+
formatDisplayValue,
|
|
259
296
|
toDisplayValue,
|
|
260
297
|
])
|
|
261
298
|
|
|
@@ -265,6 +302,7 @@ export function MetricControl({
|
|
|
265
302
|
? measurementHint(inputValue, spec, {
|
|
266
303
|
bareUnit: parseUnit ?? spec.unitId,
|
|
267
304
|
system: isImperial ? 'us' : 'metric',
|
|
305
|
+
locale,
|
|
268
306
|
displayUnit: parseUnit ?? spec.unitId,
|
|
269
307
|
precision,
|
|
270
308
|
clamp,
|
|
@@ -280,25 +318,25 @@ export function MetricControl({
|
|
|
280
318
|
if (e.key === 'Enter') {
|
|
281
319
|
submitValue()
|
|
282
320
|
} else if (e.key === 'Escape') {
|
|
283
|
-
setInputValue(toDisplayValue(value)
|
|
321
|
+
setInputValue(formatDisplayValue(toDisplayValue(value)))
|
|
284
322
|
setIsEditing(false)
|
|
285
323
|
} else if (e.key === 'ArrowUp') {
|
|
286
324
|
e.preventDefault()
|
|
287
325
|
const newV = clamp(value + toStoredValue(step))
|
|
288
326
|
applyCommittedValue(newV)
|
|
289
|
-
setInputValue(toDisplayValue(newV)
|
|
327
|
+
setInputValue(formatDisplayValue(toDisplayValue(newV)))
|
|
290
328
|
} else if (e.key === 'ArrowDown') {
|
|
291
329
|
e.preventDefault()
|
|
292
330
|
const newV = clamp(value - toStoredValue(step))
|
|
293
331
|
applyCommittedValue(newV)
|
|
294
|
-
setInputValue(toDisplayValue(newV)
|
|
332
|
+
setInputValue(formatDisplayValue(toDisplayValue(newV)))
|
|
295
333
|
}
|
|
296
334
|
},
|
|
297
335
|
[
|
|
298
336
|
submitValue,
|
|
299
337
|
value,
|
|
300
338
|
toDisplayValue,
|
|
301
|
-
|
|
339
|
+
formatDisplayValue,
|
|
302
340
|
step,
|
|
303
341
|
clamp,
|
|
304
342
|
applyCommittedValue,
|
|
@@ -363,7 +401,7 @@ export function MetricControl({
|
|
|
363
401
|
onClick={handleValueClick}
|
|
364
402
|
>
|
|
365
403
|
<span className="font-mono tabular-nums tracking-tight">
|
|
366
|
-
{
|
|
404
|
+
{formatDisplayValue(displayValue)}
|
|
367
405
|
</span>
|
|
368
406
|
{displayUnit && <span className="ml-[1px] text-muted-foreground">{displayUnit}</span>}
|
|
369
407
|
</div>
|
|
@@ -39,7 +39,10 @@ export function SegmentedControl<T extends string>({
|
|
|
39
39
|
aria-disabled={option.disabled || undefined}
|
|
40
40
|
aria-pressed={isSelected}
|
|
41
41
|
className={cn(
|
|
42
|
-
|
|
42
|
+
// Every chip stretches to the row's height (the row is `items-stretch`), so a label that wraps
|
|
43
|
+
// to two lines grows the whole row evenly instead of one tall chip. The tighter line-height
|
|
44
|
+
// keeps a wrapped label reading as one unit and leaves single-line rows at min-h-7.
|
|
45
|
+
'relative flex min-h-7 min-w-0 flex-1 items-center justify-center rounded-md px-1.5 py-1 font-medium text-xs leading-3.5 transition-colors duration-200',
|
|
43
46
|
isSelected
|
|
44
47
|
? 'bg-white dark:bg-[#3e3e3e] text-foreground shadow-sm ring-1 ring-border/50'
|
|
45
48
|
: 'text-muted-foreground hover:bg-black/4 dark:hover:bg-white/5 hover:text-foreground',
|
|
@@ -54,7 +57,7 @@ export function SegmentedControl<T extends string>({
|
|
|
54
57
|
}}
|
|
55
58
|
type="button"
|
|
56
59
|
>
|
|
57
|
-
<span className="relative z-10 flex min-w-0 items-center justify-center gap-1.5 whitespace-normal text-center [&>svg]:shrink-0">
|
|
60
|
+
<span className="relative z-10 flex min-w-0 items-center justify-center gap-1.5 whitespace-normal text-balance text-center [overflow-wrap:anywhere] [&>svg]:shrink-0">
|
|
58
61
|
{option.label}
|
|
59
62
|
</span>
|
|
60
63
|
</button>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { createI18n, getLocale, PascalI18nProvider, setLocale } from '@pascal-app/core'
|
|
3
|
+
import { renderToString } from 'react-dom/server'
|
|
4
|
+
import { parseSliderBareValue, SliderControl } from './slider-control'
|
|
5
|
+
|
|
6
|
+
test('slider numbers use the scoped locale for display and parsing while the global locale is English', () => {
|
|
7
|
+
setLocale('en')
|
|
8
|
+
const i18n = createI18n({ locale: 'fr' })
|
|
9
|
+
try {
|
|
10
|
+
const html = renderToString(
|
|
11
|
+
<PascalI18nProvider i18n={i18n}>
|
|
12
|
+
<SliderControl label="Length" onChange={() => {}} precision={2} value={2.75} />
|
|
13
|
+
</PascalI18nProvider>,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
expect(getLocale()).toBe('en')
|
|
17
|
+
expect(html).toContain('2,75')
|
|
18
|
+
expect(parseSliderBareValue('2,75', i18n.getLocale())).toBe(2.75)
|
|
19
|
+
expect(parseSliderBareValue('1e2', i18n.getLocale())).toBe(100)
|
|
20
|
+
} finally {
|
|
21
|
+
i18n.destroy()
|
|
22
|
+
}
|
|
23
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useScene } from '@pascal-app/core'
|
|
3
|
+
import { formatNumber, parseLocalizedNumber, useLocale, useScene, usePascalI18n } from '@pascal-app/core'
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
5
5
|
import { lingoUnitSpec, measurementHint, parseMeasurement } from '../../../lib/measurement-parser'
|
|
6
6
|
import { useLinearDisplay } from '../../../lib/use-linear-display'
|
|
@@ -23,6 +23,10 @@ interface SliderControlProps {
|
|
|
23
23
|
mixed?: boolean
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export function parseSliderBareValue(text: string, locale: string): number | null {
|
|
27
|
+
return parseLocalizedNumber(text, locale)
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
function stepPrecision(s: number): number {
|
|
27
31
|
if (s <= 0) return 0
|
|
28
32
|
return Math.max(0, Math.ceil(-Math.log10(s)))
|
|
@@ -67,14 +71,25 @@ export function SliderControl({
|
|
|
67
71
|
restoreOnCommit = true,
|
|
68
72
|
mixed = false,
|
|
69
73
|
}: SliderControlProps) {
|
|
74
|
+
const i18n = usePascalI18n()
|
|
75
|
+
const locale = useLocale()
|
|
70
76
|
// Values and bounds stay in meters; gestures and input use the displayed unit.
|
|
71
77
|
const { isImperial, displayUnit, parseUnit, precision, step, toDisplay, toStored } =
|
|
72
78
|
useLinearDisplay(unit, storedPrecision, storedStep)
|
|
79
|
+
const formatDisplay = useCallback(
|
|
80
|
+
(displayValue: number) =>
|
|
81
|
+
formatNumber(displayValue, {
|
|
82
|
+
locale,
|
|
83
|
+
min: precision,
|
|
84
|
+
max: precision,
|
|
85
|
+
}),
|
|
86
|
+
[locale, precision],
|
|
87
|
+
)
|
|
73
88
|
|
|
74
89
|
const [isEditing, setIsEditing] = useState(false)
|
|
75
90
|
const [isDragging, setIsDragging] = useState(false)
|
|
76
91
|
const [isHovered, setIsHovered] = useState(false)
|
|
77
|
-
const [inputValue, setInputValue] = useState(toDisplay(value)
|
|
92
|
+
const [inputValue, setInputValue] = useState(() => formatDisplay(toDisplay(value)))
|
|
78
93
|
// Live readout while dragging. Multi-edit previews write live overrides
|
|
79
94
|
// instead of the scene, so `value` would otherwise stay frozen even though
|
|
80
95
|
// the meshes are moving.
|
|
@@ -116,9 +131,9 @@ export function SliderControl({
|
|
|
116
131
|
|
|
117
132
|
useEffect(() => {
|
|
118
133
|
if (!isEditing) {
|
|
119
|
-
setInputValue(toDisplay(value)
|
|
134
|
+
setInputValue(formatDisplay(toDisplay(value)))
|
|
120
135
|
}
|
|
121
|
-
}, [value,
|
|
136
|
+
}, [value, isEditing, toDisplay, formatDisplay])
|
|
122
137
|
|
|
123
138
|
// Wheel support on the label
|
|
124
139
|
useEffect(() => {
|
|
@@ -246,6 +261,12 @@ export function SliderControl({
|
|
|
246
261
|
[],
|
|
247
262
|
)
|
|
248
263
|
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
const cancelOnBlur = () => { if (dragRef.current || editingRef.current) cancelEdit() }
|
|
266
|
+
window.addEventListener('blur', cancelOnBlur)
|
|
267
|
+
return () => window.removeEventListener('blur', cancelOnBlur)
|
|
268
|
+
}, [cancelEdit])
|
|
269
|
+
|
|
249
270
|
useEffect(() => {
|
|
250
271
|
if (!isDragging) return
|
|
251
272
|
const escape = (event: KeyboardEvent) => {
|
|
@@ -258,8 +279,8 @@ export function SliderControl({
|
|
|
258
279
|
const handleValueClick = useCallback(() => {
|
|
259
280
|
editingRef.current = true
|
|
260
281
|
setIsEditing(true)
|
|
261
|
-
setInputValue(toDisplay(value)
|
|
262
|
-
}, [value,
|
|
282
|
+
setInputValue(formatDisplay(toDisplay(value)))
|
|
283
|
+
}, [value, toDisplay, formatDisplay])
|
|
263
284
|
|
|
264
285
|
const parseInputValue = useCallback(
|
|
265
286
|
(text: string) => {
|
|
@@ -267,16 +288,17 @@ export function SliderControl({
|
|
|
267
288
|
const parsed = spec
|
|
268
289
|
? parseMeasurement(text, spec, {
|
|
269
290
|
bareUnit: parseUnit ?? spec.unitId,
|
|
291
|
+
locale,
|
|
270
292
|
system: isImperial ? 'us' : 'metric',
|
|
271
293
|
})
|
|
272
294
|
: null
|
|
273
|
-
const bare =
|
|
274
|
-
const stored = parsed ?? (
|
|
295
|
+
const bare = parseSliderBareValue(text, locale)
|
|
296
|
+
const stored = parsed ?? (bare === null ? null : toStored(bare))
|
|
275
297
|
return stored === null
|
|
276
298
|
? null
|
|
277
299
|
: clamp(toStored(Number.parseFloat(toDisplay(stored).toFixed(precision))))
|
|
278
300
|
},
|
|
279
|
-
[unit, parseUnit, isImperial, toStored, clamp, toDisplay, precision],
|
|
301
|
+
[unit, parseUnit, locale, isImperial, toStored, clamp, toDisplay, precision],
|
|
280
302
|
)
|
|
281
303
|
|
|
282
304
|
const submitValue = useCallback(() => {
|
|
@@ -285,13 +307,13 @@ export function SliderControl({
|
|
|
285
307
|
const nextValue = parseInputValue(inputValue)
|
|
286
308
|
if (nextValue === null) {
|
|
287
309
|
onCancel?.()
|
|
288
|
-
setInputValue(toDisplay(value)
|
|
310
|
+
setInputValue(formatDisplay(toDisplay(value)))
|
|
289
311
|
} else {
|
|
290
312
|
onChange(nextValue)
|
|
291
313
|
onCommit?.(nextValue)
|
|
292
314
|
}
|
|
293
315
|
setIsEditing(false)
|
|
294
|
-
}, [inputValue, parseInputValue, onCancel, onChange, onCommit, toDisplay, value,
|
|
316
|
+
}, [inputValue, parseInputValue, onCancel, onChange, onCommit, toDisplay, value, formatDisplay])
|
|
295
317
|
|
|
296
318
|
const spec = lingoUnitSpec(unit)
|
|
297
319
|
const hint =
|
|
@@ -299,6 +321,7 @@ export function SliderControl({
|
|
|
299
321
|
? measurementHint(inputValue, spec, {
|
|
300
322
|
bareUnit: parseUnit ?? spec.unitId,
|
|
301
323
|
system: isImperial ? 'us' : 'metric',
|
|
324
|
+
locale,
|
|
302
325
|
displayUnit: parseUnit ?? spec.unitId,
|
|
303
326
|
precision,
|
|
304
327
|
clamp,
|
|
@@ -317,10 +340,10 @@ export function SliderControl({
|
|
|
317
340
|
const adjustedStep = getAdjustedStep(step, e)
|
|
318
341
|
const newV = applyDisplayDelta(value, direction * adjustedStep, adjustedStep)
|
|
319
342
|
onChange(newV)
|
|
320
|
-
setInputValue(toDisplay(newV)
|
|
343
|
+
setInputValue(formatDisplay(toDisplay(newV)))
|
|
321
344
|
}
|
|
322
345
|
},
|
|
323
|
-
[submitValue, cancelEdit, value,
|
|
346
|
+
[submitValue, cancelEdit, value, step, applyDisplayDelta, onChange, toDisplay, formatDisplay],
|
|
324
347
|
)
|
|
325
348
|
|
|
326
349
|
const displayValue = toDisplay(shown)
|
|
@@ -376,6 +399,7 @@ export function SliderControl({
|
|
|
376
399
|
</span>
|
|
377
400
|
)}
|
|
378
401
|
<input
|
|
402
|
+
aria-label={typeof label === 'string' ? label : undefined}
|
|
379
403
|
autoFocus
|
|
380
404
|
className="w-14 bg-transparent p-0 text-right font-mono text-foreground outline-none selection:bg-primary/30"
|
|
381
405
|
onBlur={submitValue}
|
|
@@ -402,22 +426,26 @@ export function SliderControl({
|
|
|
402
426
|
)}
|
|
403
427
|
</>
|
|
404
428
|
) : mixed && !isDragging ? (
|
|
405
|
-
<
|
|
429
|
+
<button
|
|
430
|
+
type="button"
|
|
431
|
+
aria-label={typeof label === 'string' ? i18n._({ id: 'editor.controls.sliderControl.editLabel', message: 'Edit {label}', values: { label } }) : undefined}
|
|
406
432
|
className="flex cursor-text items-center text-muted-foreground transition-colors hover:text-foreground"
|
|
407
433
|
onClick={handleValueClick}
|
|
408
434
|
>
|
|
409
|
-
<span className="font-mono tracking-tight">Mixed</span>
|
|
410
|
-
</
|
|
435
|
+
<span className="font-mono tracking-tight">{i18n._({ id: 'editor.controls.sliderControl.mixed', message: 'Mixed' })}</span>
|
|
436
|
+
</button>
|
|
411
437
|
) : (
|
|
412
|
-
<
|
|
438
|
+
<button
|
|
439
|
+
type="button"
|
|
440
|
+
aria-label={typeof label === 'string' ? i18n._({ id: 'editor.controls.sliderControl.editLabel', message: 'Edit {label}', values: { label } }) : undefined}
|
|
413
441
|
className="flex cursor-text items-center text-foreground/60 transition-colors hover:text-foreground"
|
|
414
442
|
onClick={handleValueClick}
|
|
415
443
|
>
|
|
416
444
|
<span className="font-mono tabular-nums tracking-tight" suppressHydrationWarning>
|
|
417
|
-
{
|
|
445
|
+
{formatDisplay(displayValue)}
|
|
418
446
|
</span>
|
|
419
447
|
{displayUnit && <span className="ml-[1px] text-muted-foreground">{displayUnit}</span>}
|
|
420
|
-
</
|
|
448
|
+
</button>
|
|
421
449
|
)}
|
|
422
450
|
</div>
|
|
423
451
|
</div>
|
|
@@ -34,50 +34,80 @@ function ToolOptionRow({
|
|
|
34
34
|
return (
|
|
35
35
|
<div className="flex flex-col gap-2">
|
|
36
36
|
<div className="px-0.5 font-medium text-muted-foreground text-xs">{option.label}</div>
|
|
37
|
-
|
|
38
|
-
className="
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
37
|
+
{option.input ? (
|
|
38
|
+
<label className="flex items-center gap-2 rounded-lg border border-border px-2 py-1.5">
|
|
39
|
+
<input
|
|
40
|
+
key={value}
|
|
41
|
+
aria-label={option.label}
|
|
42
|
+
className="min-w-0 flex-1 bg-transparent text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
43
|
+
type="number"
|
|
44
|
+
defaultValue={value}
|
|
45
|
+
min={option.input.min}
|
|
46
|
+
max={option.input.max}
|
|
47
|
+
step={option.input.step ?? 'any'}
|
|
48
|
+
onBlur={(event) => {
|
|
49
|
+
const next = event.target.valueAsNumber
|
|
50
|
+
if (
|
|
51
|
+
Number.isFinite(next) &&
|
|
52
|
+
!event.target.validity.rangeUnderflow &&
|
|
53
|
+
!event.target.validity.rangeOverflow
|
|
54
|
+
) {
|
|
55
|
+
option.set(String(next))
|
|
56
|
+
onSelect?.(option, String(next))
|
|
57
|
+
} else event.target.value = value
|
|
58
|
+
}}
|
|
59
|
+
onKeyDown={(event) => {
|
|
60
|
+
if (event.key === 'Enter') event.currentTarget.blur()
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
<span className="text-xs text-muted-foreground">{option.input.unit}</span>
|
|
64
|
+
</label>
|
|
65
|
+
) : (
|
|
66
|
+
<div
|
|
67
|
+
className="grid gap-1.5"
|
|
68
|
+
style={{
|
|
69
|
+
gridTemplateColumns: `repeat(${Math.min(option.choices.length, 3)}, minmax(0, 1fr))`,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{option.choices.map((choice) => {
|
|
73
|
+
const selected = active && choice.value === value
|
|
74
|
+
const thumbnail = getChoiceThumbnail?.(option, choice.value)
|
|
75
|
+
return (
|
|
76
|
+
<button
|
|
77
|
+
aria-pressed={selected}
|
|
78
|
+
className={cn(
|
|
79
|
+
'rounded-lg px-2 py-2 text-center font-medium text-xs transition-colors',
|
|
80
|
+
selected
|
|
81
|
+
? 'bg-primary/10 text-primary ring-1 ring-primary/50'
|
|
82
|
+
: 'bg-black/4 text-muted-foreground hover:bg-black/8 hover:text-foreground dark:bg-muted/40 dark:hover:bg-muted',
|
|
83
|
+
)}
|
|
84
|
+
key={choice.value}
|
|
85
|
+
onClick={() => {
|
|
86
|
+
triggerSFX('sfx:menu-click')
|
|
87
|
+
option.set(choice.value)
|
|
88
|
+
onSelect?.(option, choice.value)
|
|
89
|
+
}}
|
|
90
|
+
onMouseEnter={() => triggerSFX('sfx:menu-hover')}
|
|
91
|
+
type="button"
|
|
92
|
+
>
|
|
93
|
+
{thumbnail && (
|
|
94
|
+
<Image
|
|
95
|
+
alt=""
|
|
96
|
+
className={cn(
|
|
97
|
+
'mx-auto mb-1 size-14 object-contain',
|
|
98
|
+
!selected && 'opacity-70 grayscale',
|
|
99
|
+
)}
|
|
100
|
+
height={56}
|
|
101
|
+
src={thumbnail}
|
|
102
|
+
width={56}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
{choice.label}
|
|
106
|
+
</button>
|
|
107
|
+
)
|
|
108
|
+
})}
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
81
111
|
{activeChoice && activeChoice.description ? (
|
|
82
112
|
<p className="px-0.5 text-[11px] text-muted-foreground leading-relaxed">
|
|
83
113
|
{activeChoice.description}
|