@pascal-app/editor 1.0.5 → 1.0.7
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 +20 -17
- 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 +20 -8
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +72 -61
- package/src/components/editor/floating-action-menu.tsx +27 -9
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +285 -132
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- 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 +20 -0
- package/src/components/editor/index.tsx +130 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +1 -2
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.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 +1 -1
- package/src/components/editor/room-handles-2d.tsx +156 -6
- package/src/components/editor/room-handles.tsx +241 -51
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +29 -15
- 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 +1 -1
- 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 +1 -1
- package/src/components/editor/wall-move-side-handles.tsx +5 -1
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
- 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 +31 -3
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
- 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-registry-layer.test.ts +12 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
- 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/tools/distribution/distribution-run-tool.tsx +50 -26
- 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 +30 -34
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- 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 +1 -1
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- 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/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 +30 -14
- 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 +53 -3
- package/src/components/ui/helpers/helper-manager.tsx +82 -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 +113 -38
- package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
- 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 +8 -6
- 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 +8 -7
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +22 -109
- 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 +464 -65
- 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 +21 -12
- package/src/components/ui/panels/room-panel.tsx +206 -99
- 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/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/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
- 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 +117 -64
- 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 +8 -3
- 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 +18 -8
- 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 +61 -4
- 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 +15117 -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 +37501 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +37501 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +37501 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +37501 -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 +156 -4
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +95 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/floor-footprints.test.ts +2 -1
- package/src/lib/floor-foundation-support.test.ts +7 -3
- 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/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/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/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/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-parser.ts +7 -3
- package/src/lib/measurements.test.ts +17 -0
- package/src/lib/measurements.ts +20 -5
- package/src/lib/mezzanine-selection.test.ts +3 -1
- 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/reference-scale.ts +83 -0
- package/src/lib/room-built-on.test.ts +26 -12
- package/src/lib/room-built-on.ts +20 -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 +140 -0
- package/src/lib/room-handle-drag.ts +557 -42
- 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 +37 -0
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +9 -4
- 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/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 +20 -5
- 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 +14 -5
- 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 +42 -0
- package/src/lib/window-interaction.ts +47 -5
- 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 +48 -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-wall-snap-indicator.ts +15 -2
|
@@ -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 } 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,24 @@ export function SliderControl({
|
|
|
67
71
|
restoreOnCommit = true,
|
|
68
72
|
mixed = false,
|
|
69
73
|
}: SliderControlProps) {
|
|
74
|
+
const locale = useLocale()
|
|
70
75
|
// Values and bounds stay in meters; gestures and input use the displayed unit.
|
|
71
76
|
const { isImperial, displayUnit, parseUnit, precision, step, toDisplay, toStored } =
|
|
72
77
|
useLinearDisplay(unit, storedPrecision, storedStep)
|
|
78
|
+
const formatDisplay = useCallback(
|
|
79
|
+
(displayValue: number) =>
|
|
80
|
+
formatNumber(displayValue, {
|
|
81
|
+
locale,
|
|
82
|
+
min: precision,
|
|
83
|
+
max: precision,
|
|
84
|
+
}),
|
|
85
|
+
[locale, precision],
|
|
86
|
+
)
|
|
73
87
|
|
|
74
88
|
const [isEditing, setIsEditing] = useState(false)
|
|
75
89
|
const [isDragging, setIsDragging] = useState(false)
|
|
76
90
|
const [isHovered, setIsHovered] = useState(false)
|
|
77
|
-
const [inputValue, setInputValue] = useState(toDisplay(value)
|
|
91
|
+
const [inputValue, setInputValue] = useState(() => formatDisplay(toDisplay(value)))
|
|
78
92
|
// Live readout while dragging. Multi-edit previews write live overrides
|
|
79
93
|
// instead of the scene, so `value` would otherwise stay frozen even though
|
|
80
94
|
// the meshes are moving.
|
|
@@ -116,9 +130,9 @@ export function SliderControl({
|
|
|
116
130
|
|
|
117
131
|
useEffect(() => {
|
|
118
132
|
if (!isEditing) {
|
|
119
|
-
setInputValue(toDisplay(value)
|
|
133
|
+
setInputValue(formatDisplay(toDisplay(value)))
|
|
120
134
|
}
|
|
121
|
-
}, [value,
|
|
135
|
+
}, [value, isEditing, toDisplay, formatDisplay])
|
|
122
136
|
|
|
123
137
|
// Wheel support on the label
|
|
124
138
|
useEffect(() => {
|
|
@@ -258,8 +272,8 @@ export function SliderControl({
|
|
|
258
272
|
const handleValueClick = useCallback(() => {
|
|
259
273
|
editingRef.current = true
|
|
260
274
|
setIsEditing(true)
|
|
261
|
-
setInputValue(toDisplay(value)
|
|
262
|
-
}, [value,
|
|
275
|
+
setInputValue(formatDisplay(toDisplay(value)))
|
|
276
|
+
}, [value, toDisplay, formatDisplay])
|
|
263
277
|
|
|
264
278
|
const parseInputValue = useCallback(
|
|
265
279
|
(text: string) => {
|
|
@@ -267,16 +281,17 @@ export function SliderControl({
|
|
|
267
281
|
const parsed = spec
|
|
268
282
|
? parseMeasurement(text, spec, {
|
|
269
283
|
bareUnit: parseUnit ?? spec.unitId,
|
|
284
|
+
locale,
|
|
270
285
|
system: isImperial ? 'us' : 'metric',
|
|
271
286
|
})
|
|
272
287
|
: null
|
|
273
|
-
const bare =
|
|
274
|
-
const stored = parsed ?? (
|
|
288
|
+
const bare = parseSliderBareValue(text, locale)
|
|
289
|
+
const stored = parsed ?? (bare === null ? null : toStored(bare))
|
|
275
290
|
return stored === null
|
|
276
291
|
? null
|
|
277
292
|
: clamp(toStored(Number.parseFloat(toDisplay(stored).toFixed(precision))))
|
|
278
293
|
},
|
|
279
|
-
[unit, parseUnit, isImperial, toStored, clamp, toDisplay, precision],
|
|
294
|
+
[unit, parseUnit, locale, isImperial, toStored, clamp, toDisplay, precision],
|
|
280
295
|
)
|
|
281
296
|
|
|
282
297
|
const submitValue = useCallback(() => {
|
|
@@ -285,13 +300,13 @@ export function SliderControl({
|
|
|
285
300
|
const nextValue = parseInputValue(inputValue)
|
|
286
301
|
if (nextValue === null) {
|
|
287
302
|
onCancel?.()
|
|
288
|
-
setInputValue(toDisplay(value)
|
|
303
|
+
setInputValue(formatDisplay(toDisplay(value)))
|
|
289
304
|
} else {
|
|
290
305
|
onChange(nextValue)
|
|
291
306
|
onCommit?.(nextValue)
|
|
292
307
|
}
|
|
293
308
|
setIsEditing(false)
|
|
294
|
-
}, [inputValue, parseInputValue, onCancel, onChange, onCommit, toDisplay, value,
|
|
309
|
+
}, [inputValue, parseInputValue, onCancel, onChange, onCommit, toDisplay, value, formatDisplay])
|
|
295
310
|
|
|
296
311
|
const spec = lingoUnitSpec(unit)
|
|
297
312
|
const hint =
|
|
@@ -299,6 +314,7 @@ export function SliderControl({
|
|
|
299
314
|
? measurementHint(inputValue, spec, {
|
|
300
315
|
bareUnit: parseUnit ?? spec.unitId,
|
|
301
316
|
system: isImperial ? 'us' : 'metric',
|
|
317
|
+
locale,
|
|
302
318
|
displayUnit: parseUnit ?? spec.unitId,
|
|
303
319
|
precision,
|
|
304
320
|
clamp,
|
|
@@ -317,10 +333,10 @@ export function SliderControl({
|
|
|
317
333
|
const adjustedStep = getAdjustedStep(step, e)
|
|
318
334
|
const newV = applyDisplayDelta(value, direction * adjustedStep, adjustedStep)
|
|
319
335
|
onChange(newV)
|
|
320
|
-
setInputValue(toDisplay(newV)
|
|
336
|
+
setInputValue(formatDisplay(toDisplay(newV)))
|
|
321
337
|
}
|
|
322
338
|
},
|
|
323
|
-
[submitValue, cancelEdit, value,
|
|
339
|
+
[submitValue, cancelEdit, value, step, applyDisplayDelta, onChange, toDisplay, formatDisplay],
|
|
324
340
|
)
|
|
325
341
|
|
|
326
342
|
const displayValue = toDisplay(shown)
|
|
@@ -414,7 +430,7 @@ export function SliderControl({
|
|
|
414
430
|
onClick={handleValueClick}
|
|
415
431
|
>
|
|
416
432
|
<span className="font-mono tabular-nums tracking-tight" suppressHydrationWarning>
|
|
417
|
-
{
|
|
433
|
+
{formatDisplay(displayValue)}
|
|
418
434
|
</span>
|
|
419
435
|
{displayUnit && <span className="ml-[1px] text-muted-foreground">{displayUnit}</span>}
|
|
420
436
|
</div>
|