@pascal-app/editor 1.0.7 → 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.
Files changed (136) hide show
  1. package/package.json +7 -7
  2. package/src/components/editor/custom-camera-controls.tsx +90 -34
  3. package/src/components/editor/duplicate-node.ts +2 -1
  4. package/src/components/editor/first-person-controls.tsx +5 -7
  5. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  6. package/src/components/editor/fit-scene-framing.ts +142 -0
  7. package/src/components/editor/floating-action-menu.tsx +4 -2
  8. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  9. package/src/components/editor/floor-region-controls.tsx +2 -2
  10. package/src/components/editor/floorplan-panel.tsx +173 -8
  11. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  12. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  13. package/src/components/editor/footprint-height-handle.tsx +1 -1
  14. package/src/components/editor/group-transform-shared.ts +3 -1
  15. package/src/components/editor/index.tsx +20 -0
  16. package/src/components/editor/measurement-pill.tsx +1 -1
  17. package/src/components/editor/node-arrow-handles.tsx +1 -1
  18. package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -2
  19. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  20. package/src/components/editor/quick-measurement-card.tsx +1 -1
  21. package/src/components/editor/room-controls.tsx +3 -2
  22. package/src/components/editor/room-handles-2d.tsx +2 -1
  23. package/src/components/editor/room-handles.tsx +3 -3
  24. package/src/components/editor/room-labels.tsx +4 -2
  25. package/src/components/editor/site-edge-labels.tsx +6 -2
  26. package/src/components/editor/wall-measurement-label.tsx +30 -14
  27. package/src/components/editor/wall-move-side-handles.tsx +10 -6
  28. package/src/components/editor/wall-region-handles.tsx +2 -2
  29. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  30. package/src/components/editor-2d/floorplan-group-move.tsx +1 -1
  31. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +12 -12
  32. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +1 -2
  33. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  34. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +22 -4
  35. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  36. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
  37. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  38. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  39. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +22 -0
  40. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +14 -2
  41. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  42. package/src/components/systems/roof/roof-edit-system.tsx +30 -19
  43. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  44. package/src/components/tools/distribution/distribution-run-tool.tsx +150 -32
  45. package/src/components/tools/item/move-tool.tsx +1 -6
  46. package/src/components/tools/item/use-placement-coordinator.tsx +1 -1
  47. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
  48. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  49. package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
  50. package/src/components/tools/shared/placement-box.tsx +1 -5
  51. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  52. package/src/components/tools/shared/segment-angle.ts +0 -4
  53. package/src/components/tools/tool-manager.tsx +1 -24
  54. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  55. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  56. package/src/components/ui/controls/slider-control.tsx +18 -6
  57. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  58. package/src/components/ui/helpers/helper-manager.test.tsx +53 -0
  59. package/src/components/ui/helpers/helper-manager.tsx +54 -0
  60. package/src/components/ui/panels/floor-foundation-panel.tsx +1 -1
  61. package/src/components/ui/panels/floor-opening-panel.tsx +1 -1
  62. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  63. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  64. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  65. package/src/components/ui/panels/panel-manager.tsx +4 -2
  66. package/src/components/ui/panels/panel-wrapper.tsx +41 -18
  67. package/src/components/ui/panels/room-construction-rows.tsx +1 -1
  68. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  69. package/src/components/ui/panels/room-panel.tsx +1 -1
  70. package/src/components/ui/right-stack.test.ts +13 -6
  71. package/src/components/ui/right-stack.tsx +21 -10
  72. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
  73. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  74. package/src/components/ui/sidebar/panels/site-panel/index.tsx +1 -8
  75. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  76. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  77. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +1 -1
  78. package/src/components/viewer/viewer-rooms.tsx +1 -1
  79. package/src/i18n/locales/en/messages.po +856 -124
  80. package/src/i18n/locales/en/messages.ts +1 -1
  81. package/src/i18n/locales/en-XA/messages.ts +1 -1
  82. package/src/i18n/locales/es/messages.po +2087 -257
  83. package/src/i18n/locales/fr/messages.po +2087 -257
  84. package/src/i18n/locales/ja/messages.po +2087 -257
  85. package/src/i18n/locales/zh-CN/messages.po +2087 -257
  86. package/src/index.tsx +74 -47
  87. package/src/lib/contextual-help.ts +6 -0
  88. package/src/lib/elevation-guides.ts +21 -6
  89. package/src/lib/elevator-support.ts +1 -72
  90. package/src/lib/floor-footprints.test.ts +1 -1
  91. package/src/lib/floor-footprints.ts +1 -9
  92. package/src/lib/floor-foundation-support.test.ts +8 -3
  93. package/src/lib/floor-opening-draft.ts +1 -1
  94. package/src/lib/floor-region-session.ts +1 -1
  95. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  96. package/src/lib/gesture-lifecycle.ts +3 -1
  97. package/src/lib/interaction/scope.ts +1 -0
  98. package/src/lib/layer-thickness.ts +1 -1
  99. package/src/lib/level-selection.test.ts +18 -2
  100. package/src/lib/level-selection.ts +31 -8
  101. package/src/lib/linear-display.ts +1 -1
  102. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  103. package/src/lib/measurement-label.test.ts +1 -1
  104. package/src/lib/measurements.test.ts +8 -7
  105. package/src/lib/measurements.ts +1 -218
  106. package/src/lib/node-action-movement.test.tsx +115 -0
  107. package/src/lib/node-action-movement.ts +2 -0
  108. package/src/lib/placement-metadata.ts +4 -0
  109. package/src/lib/point-ask/describe-target.ts +6 -8
  110. package/src/lib/point-ask/region.ts +1 -1
  111. package/src/lib/print-shell-compiler.ts +12 -1
  112. package/src/lib/print-wall-solids.test.ts +11 -0
  113. package/src/lib/print-wall-solids.ts +9 -0
  114. package/src/lib/room-assembly-overlay.ts +1 -1
  115. package/src/lib/room-built-on.test.ts +2 -5
  116. package/src/lib/room-built-on.ts +1 -15
  117. package/src/lib/room-construction-commands.ts +3 -2
  118. package/src/lib/room-controls-phase6.test.ts +1 -1
  119. package/src/lib/room-handle-drag.ts +6 -25
  120. package/src/lib/room-push-dimensions.test.ts +1 -1
  121. package/src/lib/scene-bounds.test.ts +144 -0
  122. package/src/lib/scene-bounds.ts +79 -15
  123. package/src/lib/scene-clipboard.ts +2 -1
  124. package/src/lib/snapping-mode.test.ts +23 -1
  125. package/src/lib/viewer-selection.ts +1 -1
  126. package/src/lib/wall-region-list.ts +3 -1
  127. package/src/lib/window-interaction.test.ts +4 -2
  128. package/src/store/use-editor.tsx +5 -0
  129. package/src/store/use-placement-typing.test.ts +165 -0
  130. package/src/store/use-placement-typing.ts +186 -0
  131. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  132. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  133. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  134. package/src/lib/door-interaction.ts +0 -102
  135. package/src/lib/measurement-label.ts +0 -68
  136. package/src/lib/window-interaction.ts +0 -142
@@ -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
+ }
@@ -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