@osamash/floor-planner 1.2.2 → 1.2.4

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 (74) hide show
  1. package/dist/floor-planner.css +1 -1
  2. package/dist/floor-planner.es.js +43937 -37765
  3. package/dist/floor-planner.umd.js +59 -49
  4. package/dist/types/index.d.ts +3 -1
  5. package/dist/types/react-planner/ReactPlanner.d.ts +3 -1
  6. package/dist/types/react-planner/ai-chat/AIChatBubble.d.ts +6 -0
  7. package/dist/types/react-planner/ai-chat/AIChatPanel.d.ts +8 -0
  8. package/dist/types/react-planner/ai-chat/SelectionChips.d.ts +2 -0
  9. package/dist/types/react-planner/ai-chat/ThinkingIndicator.d.ts +5 -0
  10. package/dist/types/react-planner/ai-chat/aiService.d.ts +327 -0
  11. package/dist/types/react-planner/ai-chat/imageUpload.d.ts +6 -0
  12. package/dist/types/react-planner/ai-chat/plannerActions.d.ts +3 -0
  13. package/dist/types/react-planner/ai-chat/streamingClient.d.ts +32 -0
  14. package/dist/types/react-planner/ai-chat/useTypewriter.d.ts +19 -0
  15. package/dist/types/react-planner/catalog/catalog.d.ts +4 -0
  16. package/dist/types/react-planner/catalog/factories/AreaFactory.d.ts +1 -24
  17. package/dist/types/react-planner/catalog/factories/WallFactory.d.ts +1 -26
  18. package/dist/types/react-planner/catalog/factories/area-factory-3d.d.ts +2 -2
  19. package/dist/types/react-planner/catalog/factories/area-patterns.d.ts +42 -0
  20. package/dist/types/react-planner/catalog/factories/room-types.d.ts +22 -0
  21. package/dist/types/react-planner/catalog/factories/wall-factory-3d.d.ts +1 -1
  22. package/dist/types/react-planner/class/area.d.ts +23 -0
  23. package/dist/types/react-planner/class/hole.d.ts +13 -0
  24. package/dist/types/react-planner/class/index.d.ts +3 -1
  25. package/dist/types/react-planner/class/item.d.ts +22 -0
  26. package/dist/types/react-planner/class/line.d.ts +12 -3
  27. package/dist/types/react-planner/class/room.d.ts +18 -0
  28. package/dist/types/react-planner/components/ErrorBoundary.d.ts +19 -0
  29. package/dist/types/react-planner/components/SearchInput.d.ts +2 -1
  30. package/dist/types/react-planner/constants.d.ts +3 -0
  31. package/dist/types/react-planner/footer-bar/Footerbar.d.ts +1 -1
  32. package/dist/types/react-planner/models.d.ts +7 -0
  33. package/dist/types/react-planner/plugins/autosave.d.ts +1 -1
  34. package/dist/types/react-planner/plugins/keyboard.d.ts +1 -1
  35. package/dist/types/react-planner/sidebar/Panel.d.ts +2 -1
  36. package/dist/types/react-planner/sidebar/panel-element-editor/AreaAttributesEditor.d.ts +8 -0
  37. package/dist/types/react-planner/sidebar/panel-element-editor/AttributesEditor.d.ts +4 -1
  38. package/dist/types/react-planner/sidebar/panel-element-editor/HoleAttributesEditor.d.ts +3 -4
  39. package/dist/types/react-planner/sidebar/panel-element-editor/ItemAttributesEditor.d.ts +3 -4
  40. package/dist/types/react-planner/sidebar/panel-element-editor/SimilarElements.d.ts +15 -0
  41. package/dist/types/react-planner/sidebar/panel-element-editor/WallAttributesEditor.d.ts +8 -0
  42. package/dist/types/react-planner/sidebar/panel-element-editor/editor-ui.d.ts +42 -0
  43. package/dist/types/react-planner/toolbar/CatalogPicker.d.ts +8 -0
  44. package/dist/types/react-planner/toolbar/NumberStepper.d.ts +11 -0
  45. package/dist/types/react-planner/toolbar/ProjectMiniMap.d.ts +11 -0
  46. package/dist/types/react-planner/toolbar/ProjectPanel.d.ts +2 -0
  47. package/dist/types/react-planner/toolbar/ToolbarRail.d.ts +15 -0
  48. package/dist/types/react-planner/toolbar/ToolbarSubPanel.d.ts +23 -0
  49. package/dist/types/react-planner/toolbar/ZoomControls.d.ts +5 -0
  50. package/dist/types/react-planner/utils/angle.d.ts +9 -0
  51. package/dist/types/react-planner/utils/geometry.d.ts +15 -0
  52. package/dist/types/react-planner/utils/image-trim.d.ts +25 -0
  53. package/dist/types/react-planner/utils/item-footprint.d.ts +23 -0
  54. package/dist/types/react-planner/utils/item-snap.d.ts +56 -0
  55. package/dist/types/react-planner/utils/item-transform.d.ts +9 -0
  56. package/dist/types/react-planner/utils/name-generator.d.ts +1 -1
  57. package/dist/types/react-planner/utils/three-cache.d.ts +17 -0
  58. package/dist/types/react-planner/utils/three-materials.d.ts +33 -0
  59. package/dist/types/react-planner/utils/threeCSG.es6.d.ts +1 -1
  60. package/dist/types/react-planner/utils/wall-miter.d.ts +53 -0
  61. package/dist/types/react-planner/utils/zoom.d.ts +2 -2
  62. package/dist/types/react-planner/viewer2d/Grids.d.ts +1 -1
  63. package/dist/types/react-planner/viewer2d/Ruler.d.ts +6 -1
  64. package/dist/types/react-planner/viewer2d/SelectionToolbar.d.ts +12 -0
  65. package/dist/types/react-planner/viewer2d/WallHoverHandles.d.ts +13 -0
  66. package/dist/types/react-planner/viewer3d/selection-gizmo.d.ts +57 -0
  67. package/dist/types/react-planner/viewer3d/viewer3d.d.ts +61 -0
  68. package/dist/types/shared-style.d.ts +6 -0
  69. package/dist/types/store/ReactPlannerStore.d.ts +102 -1
  70. package/package.json +1 -1
  71. package/dist/types/react-planner/components/PropertyString.d.ts +0 -10
  72. package/dist/types/react-planner/toolbar/Popup.d.ts +0 -11
  73. package/dist/types/react-planner/viewer2d/GridHorizontalStreak.d.ts +0 -7
  74. package/dist/types/react-planner/viewer2d/GridVerticalStreak.d.ts +0 -7
@@ -2,5 +2,7 @@ import ReactPlanner from "./react-planner/ReactPlanner";
2
2
  import ReactPlannerLoader from "./react-planner/ReactPlannerLoader";
3
3
  import convertUnits, { type Unit } from "./react-planner/utils/convert-units";
4
4
  import Catalog, { AreaFactory, ItemFactory, WallFactory, SurfaceFactory } from "./react-planner/catalog";
5
- export { Catalog, AreaFactory, ItemFactory, WallFactory, SurfaceFactory, convertUnits, type Unit, ReactPlannerLoader, };
5
+ import type { AIRequestHandler, AIResponse, AIStreamContext, ChatMessage, PlanSummary, PlannerAction } from "./react-planner/ai-chat/aiService";
6
+ import { createStreamingAIHandler, type StreamingAIHandlerOptions } from "./react-planner/ai-chat/streamingClient";
7
+ export { Catalog, AreaFactory, ItemFactory, WallFactory, SurfaceFactory, convertUnits, type Unit, ReactPlannerLoader, createStreamingAIHandler, type StreamingAIHandlerOptions, type AIRequestHandler, type AIResponse, type AIStreamContext, type ChatMessage, type PlanSummary, type PlannerAction, };
6
8
  export default ReactPlanner;
@@ -1,11 +1,13 @@
1
1
  import { type Plan as PlanType } from "../store/ReactPlannerStore";
2
2
  import "./react-planner.css";
3
+ import type { AIRequestHandler } from "./ai-chat/aiService";
3
4
  interface ReactPlannerProps {
4
5
  catalog: any;
5
6
  plan: PlanType;
6
7
  has3dView?: boolean;
7
8
  onBack?: () => void;
8
9
  onSave?: (data: any) => void;
10
+ onAIMessage?: AIRequestHandler;
9
11
  }
10
- declare const ReactPlanner: ({ plan, catalog, onSave, onBack, has3dView, }: ReactPlannerProps) => import("react/jsx-runtime").JSX.Element;
12
+ declare const ReactPlanner: ({ plan, catalog, onSave, onBack, has3dView, onAIMessage, }: ReactPlannerProps) => import("react/jsx-runtime").JSX.Element;
11
13
  export default ReactPlanner;
@@ -0,0 +1,6 @@
1
+ import type { AIRequestHandler } from "./aiService";
2
+ interface AIChatBubbleProps {
3
+ onAIMessage?: AIRequestHandler;
4
+ }
5
+ declare const AIChatBubble: ({ onAIMessage }: AIChatBubbleProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default AIChatBubble;
@@ -0,0 +1,8 @@
1
+ import { type AIRequestHandler } from "./aiService";
2
+ interface AIChatPanelProps {
3
+ open: boolean;
4
+ onClose: () => void;
5
+ onAIMessage?: AIRequestHandler;
6
+ }
7
+ declare const AIChatPanel: ({ open, onClose, onAIMessage }: AIChatPanelProps) => import("react/jsx-runtime").JSX.Element;
8
+ export default AIChatPanel;
@@ -0,0 +1,2 @@
1
+ declare const SelectionChips: () => import("react/jsx-runtime").JSX.Element | null;
2
+ export default SelectionChips;
@@ -0,0 +1,5 @@
1
+ interface ThinkingIndicatorProps {
2
+ label?: string;
3
+ }
4
+ declare const ThinkingIndicator: ({ label }: ThinkingIndicatorProps) => import("react/jsx-runtime").JSX.Element;
5
+ export default ThinkingIndicator;
@@ -0,0 +1,327 @@
1
+ export interface ChatMessage {
2
+ role: "user" | "assistant";
3
+ content: string;
4
+ /**
5
+ * Photos attached to a user message — e.g. a scan/photo of a paper floor
6
+ * plan the AI should interpret and turn into `add-room`/`add-wall`/etc.
7
+ * actions. `data` is raw base64 (no `data:...;base64,` prefix). Assistant
8
+ * messages never carry this.
9
+ */
10
+ images?: {
11
+ mediaType: string;
12
+ data: string;
13
+ }[];
14
+ /**
15
+ * True for the library's own "keep going" turn inserted between
16
+ * `done: false` continuation calls — never something the user typed.
17
+ * Several current models (Sonnet 5 and the whole Opus 4.6+/Fable 5
18
+ * family) reject a request whose message list ends on an `assistant`
19
+ * turn ("assistant message prefill" is no longer supported), so a
20
+ * continuation can't just resend the conversation as-is after appending
21
+ * the assistant's own reply — it needs a trailing `user` turn too. This
22
+ * flag exists so the chat UI can skip rendering that turn as if the user
23
+ * had sent it, while it's still included in what's sent to the backend.
24
+ */
25
+ synthetic?: boolean;
26
+ }
27
+ export interface WallInfo {
28
+ id: string;
29
+ v0: string;
30
+ v1: string;
31
+ x0: number;
32
+ y0: number;
33
+ x1: number;
34
+ y1: number;
35
+ length: number;
36
+ orientation: "horizontal" | "vertical" | "diagonal";
37
+ holeCount: number;
38
+ }
39
+ export interface HoleInfo {
40
+ id: string;
41
+ type: string;
42
+ lineID: string;
43
+ offset: number;
44
+ /** Mirrored ALONG its wall — for a door, which end the hinge is at. */
45
+ flipAlongWall: boolean;
46
+ /** Mirrored ACROSS its wall — for a door, which side it opens into. */
47
+ flipAcrossWall: boolean;
48
+ }
49
+ export interface AreaInfo {
50
+ id: string;
51
+ /** "room" = enclosed by walls, "surface" = a free-standing polygon (grass,
52
+ * patio, driveway…). Only surfaces can be transformed with
53
+ * `set-surface-transform` / `duplicate-surface`. */
54
+ kind: "room" | "surface" | "area";
55
+ vertices: {
56
+ id: string;
57
+ x: number;
58
+ y: number;
59
+ }[];
60
+ color: string | null;
61
+ customName: string | null;
62
+ roomCategory: string | null;
63
+ roomType: string | null;
64
+ showName: boolean;
65
+ showSurfaceArea: boolean;
66
+ }
67
+ export interface RoomTypeOption {
68
+ value: string;
69
+ label: string;
70
+ color?: string;
71
+ }
72
+ export interface RoomCategoryOption {
73
+ value: string;
74
+ label: string;
75
+ types: RoomTypeOption[];
76
+ }
77
+ export interface PlanSelection {
78
+ walls: string[];
79
+ holes: string[];
80
+ items: string[];
81
+ areas: string[];
82
+ vertices: string[];
83
+ }
84
+ export interface ItemInfo {
85
+ id: string;
86
+ name: string;
87
+ x: number;
88
+ y: number;
89
+ /** Degrees. Positive reads counter-clockwise in the 2D view. */
90
+ rotation: number;
91
+ /** Uniform size multiplier on the catalog element's own dimensions. */
92
+ scale: number;
93
+ flipHorizontal: boolean;
94
+ flipVertical: boolean;
95
+ }
96
+ export interface PlanSummary {
97
+ layerName: string;
98
+ wallCount: number;
99
+ itemCount: number;
100
+ holeCount: number;
101
+ areaCount: number;
102
+ walls: WallInfo[];
103
+ holes: HoleInfo[];
104
+ areas: AreaInfo[];
105
+ items: ItemInfo[];
106
+ mode: string;
107
+ is3D: boolean;
108
+ showDimensions: boolean;
109
+ sceneWidth: number;
110
+ sceneHeight: number;
111
+ unit: string;
112
+ catalogItemTypes: string[];
113
+ catalogHoleTypes: string[];
114
+ catalogSurfaceTypes: string[];
115
+ /** Fill patterns (hatch styles) available for areas — pass in
116
+ * `set-area-properties.pattern`. Also "none" is always accepted. */
117
+ catalogAreaPatterns: string[];
118
+ /** Textures registered on the current area catalog element — pass in
119
+ * `set-area-properties.texture`. Also "none" is always accepted. */
120
+ catalogAreaTextures: string[];
121
+ /** Textures registered on the current wall catalog element — pass in
122
+ * `set-wall-properties.textureA` / `textureB`. Also "none" is always
123
+ * accepted. */
124
+ catalogWallTextures: string[];
125
+ roomTypes: RoomCategoryOption[];
126
+ selection: PlanSelection;
127
+ }
128
+ export type PlannerAction = {
129
+ type: "add-wall";
130
+ x0: number;
131
+ y0: number;
132
+ x1: number;
133
+ y1: number;
134
+ id?: string;
135
+ } | {
136
+ type: "add-room";
137
+ x: number;
138
+ y: number;
139
+ width: number;
140
+ height: number;
141
+ id?: string;
142
+ } | {
143
+ type: "add-surface";
144
+ points: {
145
+ x: number;
146
+ y: number;
147
+ }[];
148
+ surfaceType?: string;
149
+ id?: string;
150
+ } | {
151
+ type: "add-item";
152
+ itemType: string;
153
+ x: number;
154
+ y: number;
155
+ width?: number;
156
+ height?: number;
157
+ rotation?: number;
158
+ id?: string;
159
+ } | {
160
+ type: "add-hole";
161
+ /** Real line ID, or a "$tag" / "$tag.side" reference — see note above. */
162
+ lineID: string;
163
+ holeType: string;
164
+ offset: number;
165
+ id?: string;
166
+ } | {
167
+ type: "set-area-color";
168
+ areaID: string;
169
+ color: string;
170
+ } | {
171
+ type: "set-area-properties";
172
+ areaID: string;
173
+ customName?: string;
174
+ roomCategory?: string;
175
+ roomType?: string;
176
+ patternColor?: string;
177
+ /** One of PlanSummary.catalogAreaPatterns keys (or "none"). */
178
+ pattern?: string;
179
+ /** One of PlanSummary.catalogAreaTextures keys (or "none"). */
180
+ texture?: string;
181
+ showName?: boolean;
182
+ showSurfaceArea?: boolean;
183
+ } | {
184
+ type: "set-wall-properties";
185
+ wallID: string;
186
+ colorA?: string;
187
+ colorB?: string;
188
+ /** One of PlanSummary.catalogWallTextures keys (or "none"). */
189
+ textureA?: string;
190
+ textureB?: string;
191
+ /** cm */
192
+ thickness?: number;
193
+ /** cm */
194
+ height?: number;
195
+ customName?: string;
196
+ showName?: boolean;
197
+ } | {
198
+ /** Edit an existing item in place — the same set of changes the
199
+ * selection toolbar offers in the 2D view. Omitted fields are left
200
+ * untouched. */
201
+ type: "set-item-properties";
202
+ itemID: string;
203
+ x?: number;
204
+ y?: number;
205
+ /** Degrees, absolute (not a delta). Positive = counter-clockwise. */
206
+ rotation?: number;
207
+ /** Uniform size multiplier, clamped to 0.2–8. 1 = catalog size. */
208
+ scale?: number;
209
+ flipHorizontal?: boolean;
210
+ flipVertical?: boolean;
211
+ } | {
212
+ /** Edit an existing door / window in place. */
213
+ type: "set-hole-properties";
214
+ holeID: string;
215
+ /** 0..1 along its wall. */
216
+ offset?: number;
217
+ /** Mirror along the wall — moves a door's hinge to the other end. */
218
+ flipAlongWall?: boolean;
219
+ /** Mirror across the wall — flips which side a door opens into. */
220
+ flipAcrossWall?: boolean;
221
+ } | {
222
+ /** Rotate / mirror a surface polygon about its own center. Rooms are
223
+ * rejected: their vertices are shared with the walls around them. */
224
+ type: "set-surface-transform";
225
+ areaID: string;
226
+ /** Degrees, relative to where the surface is now. Positive =
227
+ * counter-clockwise on screen, as for `set-item-properties.rotation`. */
228
+ rotate?: number;
229
+ /** "x" flips it left-right, "y" top-bottom. */
230
+ mirror?: "x" | "y";
231
+ } | {
232
+ /** Copy a surface polygon, keeping its look, set down clear of the
233
+ * original. The copy ends up selected. */
234
+ type: "duplicate-surface";
235
+ areaID: string;
236
+ } | {
237
+ /** Copy an item, keeping its rotation / scale / flips, offset slightly
238
+ * from the original. The copy ends up selected. */
239
+ type: "duplicate-item";
240
+ itemID: string;
241
+ } | {
242
+ /** Copy a door / window along the same wall. */
243
+ type: "duplicate-hole";
244
+ holeID: string;
245
+ } | {
246
+ type: "move-vertex";
247
+ vertexID: string;
248
+ x: number;
249
+ y: number;
250
+ } | {
251
+ type: "delete-wall";
252
+ wallID: string;
253
+ } | {
254
+ type: "delete-item";
255
+ itemID: string;
256
+ } | {
257
+ type: "delete-hole";
258
+ holeID: string;
259
+ } | {
260
+ type: "remove-room";
261
+ areaID: string;
262
+ } | {
263
+ type: "clear";
264
+ } | {
265
+ type: "switch-view";
266
+ view: "2d" | "3d";
267
+ } | {
268
+ type: "toggle-dimensions";
269
+ show: boolean;
270
+ } | {
271
+ type: "undo";
272
+ };
273
+ export interface AIResponse {
274
+ reply: string;
275
+ actions: PlannerAction[];
276
+ /**
277
+ * Multi-step continuation. Building something big (a whole multi-room
278
+ * floor plan) can need more actions than fit in one well-grounded
279
+ * response — e.g. you want to see a room's real wall IDs (from a fresh
280
+ * `PlanSummary`) before placing doors on it, rather than guessing with a
281
+ * `$tag` (see the note on `PlannerAction` above; `$tag`s work within one
282
+ * response, this is for spanning several).
283
+ *
284
+ * Set `done: false` to have the library apply this response's `actions`,
285
+ * take a fresh `summarizePlan()` of the result, and call your handler
286
+ * again — same `messages` array with this response's own `reply` appended
287
+ * as an assistant turn, no synthetic "continue" message injected — so you
288
+ * see your own last reply plus the updated plan and pick up from there.
289
+ * Each intermediate `reply` is shown in the chat as its own message, so
290
+ * narrate what that step did (e.g. "Built the room shells, adding doors
291
+ * next…").
292
+ *
293
+ * Omit, or set `true`, once you're finished — this is also the default
294
+ * for backends that don't implement this field, so single-response
295
+ * behavior is unchanged. The library caps continuations at 8 steps
296
+ * regardless, in case of a backend bug that never stops; don't rely on
297
+ * more than that for one user request.
298
+ */
299
+ done?: boolean;
300
+ }
301
+ /**
302
+ * Extras the library hands a handler on every call. Optional on purpose: a
303
+ * handler written against the original two-argument signature keeps working
304
+ * untouched — it just never streams and never gets cancelled.
305
+ */
306
+ export interface AIStreamContext {
307
+ /**
308
+ * Aborted when the planner unmounts mid-request. Pass it to `fetch` so a
309
+ * closed tab stops the backend generating (and billing) into a dead socket.
310
+ */
311
+ signal?: AbortSignal;
312
+ /**
313
+ * Call with each incremental chunk of reply text as it arrives, in order.
314
+ * The chat paints the accumulated text live while the request is still in
315
+ * flight.
316
+ *
317
+ * The deltas are a *preview*, not the result: when the promise resolves,
318
+ * the chat throws the accumulated text away and renders `AIResponse.reply`
319
+ * instead. The two legitimately differ (a backend that re-parses its own
320
+ * output can correct or replace what it streamed), so a handler must still
321
+ * resolve with the complete, authoritative reply — never with `""` on the
322
+ * grounds that it already streamed the text.
323
+ */
324
+ onReplyDelta?: (delta: string) => void;
325
+ }
326
+ export type AIRequestHandler = (messages: ChatMessage[], plan: PlanSummary, ctx?: AIStreamContext) => Promise<AIResponse>;
327
+ export declare function getAIResponse(messages: ChatMessage[], _plan: PlanSummary): Promise<AIResponse>;
@@ -0,0 +1,6 @@
1
+ export interface PendingImage {
2
+ mediaType: string;
3
+ data: string;
4
+ previewUrl: string;
5
+ }
6
+ export declare function fileToChatImage(file: File): Promise<PendingImage>;
@@ -0,0 +1,3 @@
1
+ import type { PlanSummary, PlannerAction } from "./aiService";
2
+ export declare function summarizePlan(planner: any): PlanSummary;
3
+ export declare function applyPlannerActions(actions: PlannerAction[]): void;
@@ -0,0 +1,32 @@
1
+ import type { AIRequestHandler } from "./aiService";
2
+ /**
3
+ * Client for a backend that speaks the streaming variant of the chat contract
4
+ * (`floor-planner-ai-backend`'s `POST /ai/chat/stream`). Lives in the library
5
+ * so every consumer doesn't hand-roll an SSE parser; the URL and any auth
6
+ * headers still belong to the consuming app — the library never holds a key.
7
+ *
8
+ * Wire format (Server-Sent Events, POST — so NOT `EventSource`-compatible):
9
+ *
10
+ * event: meta data: { provider, model } once, before any output
11
+ * event: reply data: { delta } zero or more, in order
12
+ * event: final data: { reply, actions, done, … } authoritative, terminal
13
+ * event: error data: { error, type } terminal
14
+ *
15
+ * Only the reply text streams. Actions arrive whole in `final` because a
16
+ * half-parsed action isn't something the planner can apply.
17
+ *
18
+ * Failures *before* the first token (bad key, rate limit, an image sent to a
19
+ * text-only model) come back as an ordinary JSON error with a real status
20
+ * code. Once the stream has started the status line is spent, so anything
21
+ * after that arrives as an `error` frame — hence both paths below.
22
+ */
23
+ export interface StreamingAIHandlerOptions {
24
+ /** Full URL of the streaming endpoint, e.g. `http://host:3001/ai/chat/stream`. */
25
+ url: string;
26
+ /**
27
+ * Extra request headers (auth, tenant id…). A function is re-invoked per
28
+ * request, so a token that rotates doesn't go stale.
29
+ */
30
+ headers?: Record<string, string> | (() => Record<string, string>);
31
+ }
32
+ export declare const createStreamingAIHandler: ({ url, headers, }: StreamingAIHandlerOptions) => AIRequestHandler;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Paces streamed text into the UI at frame rate instead of at network rate.
3
+ *
4
+ * Deltas off an SSE stream don't arrive one token at a time — TCP and any
5
+ * proxy in between coalesce them, so what lands is a few characters, then 80,
6
+ * then nothing for 300ms. Rendering each delta the moment it arrives makes the
7
+ * text jump in uneven blocks. Buffering here and draining a slice per animation
8
+ * frame turns the same bytes into steady output.
9
+ *
10
+ * The drain is proportional (a fixed chars-per-frame rate would fall further
11
+ * and further behind a fast model), so a big burst catches up in a handful of
12
+ * frames rather than trickling out for seconds after the response is done.
13
+ */
14
+ export declare const useTypewriter: () => {
15
+ text: string;
16
+ push: (delta: string) => void;
17
+ reset: () => void;
18
+ getFullText: () => string;
19
+ };
@@ -50,6 +50,10 @@ export default class Catalog {
50
50
  unit: Unit;
51
51
  constructor(unit?: Unit);
52
52
  getElement(type: string): CatalogElement;
53
+ /** Every registered element of a prototype ("items", "holes", "lines"),
54
+ * regardless of the category it was filed under. Backs the sidebar's
55
+ * "similar items" list, which matches on tags rather than category. */
56
+ getElementsOfPrototype(prototype: string): CatalogElement[];
53
57
  getCategory(categoryName: string): CatalogCategory;
54
58
  getPropertyType(type: string): any;
55
59
  registerElement(json: any): void;
@@ -1,24 +1 @@
1
- export default function AreaFactory(name: string, info: any, textures: any): {
2
- name: string;
3
- prototype: string;
4
- info: any;
5
- properties: {
6
- patternColor: {
7
- label: string;
8
- type: string;
9
- defaultValue: string;
10
- };
11
- thickness: {
12
- label: string;
13
- type: string;
14
- defaultValue: {
15
- length: number;
16
- _length: number;
17
- _unit: string;
18
- };
19
- };
20
- };
21
- render2D: (element: any, layer: any, scene: any) => import("react/jsx-runtime").JSX.Element;
22
- render3D: (element: any, layer: any, scene: any) => Promise<import("three").Mesh<import("three").ShapeGeometry, import("three").MeshPhongMaterial, import("three").Object3DEventMap>>;
23
- updateRender3D: (element: any, layer: any, scene: any, mesh: any, oldElement: any, differences: any, selfDestroy: any, selfBuild: any) => Promise<import("three").Mesh<import("three").BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>, import("three").Material | import("three").Material[], import("three").Object3DEventMap>>;
24
- };
1
+ export default function AreaFactory(name: string, info: any, textures: any): any;
@@ -1,26 +1 @@
1
- export default function WallFactory(name: string, info: any, textures: any): {
2
- name: string;
3
- prototype: string;
4
- info: any;
5
- properties: {
6
- height: {
7
- label: string;
8
- type: string;
9
- defaultValue: {
10
- length: number;
11
- };
12
- };
13
- thickness: {
14
- label: string;
15
- type: string;
16
- defaultValue: {
17
- length: number;
18
- _length: number;
19
- _unit: string;
20
- };
21
- };
22
- };
23
- render2D: (element: any, layer: any, scene: any, mode: string) => import("react/jsx-runtime").JSX.Element;
24
- render3D: (element: any, layer: any, scene: any) => Promise<import("three").Group<import("three").Object3DEventMap>>;
25
- updateRender3D: (element: any, layer: any, scene: any, mesh: any, oldElement: any, differences: any, selfDestroy: any, selfBuild: any) => any;
26
- };
1
+ export default function WallFactory(name: string, info: any, textures: any): any;
@@ -1,4 +1,4 @@
1
- import { MeshPhongMaterial, ShapeGeometry, Mesh } from "three";
1
+ import { MeshStandardMaterial, ShapeGeometry, Mesh } from "three";
2
2
  import * as THREE from "three";
3
3
  import type { Layer, Scene } from "../../models";
4
4
  type TextureData = {
@@ -13,6 +13,6 @@ type TextureData = {
13
13
  heightRepeatScale: number;
14
14
  };
15
15
  };
16
- export declare function createArea(element: any, layer: Layer, scene: Scene, textures: Record<string, TextureData>): Promise<Mesh<ShapeGeometry, MeshPhongMaterial, THREE.Object3DEventMap>>;
16
+ export declare function createArea(element: any, layer: Layer, scene: Scene, textures: Record<string, TextureData>): Promise<Mesh<ShapeGeometry, MeshStandardMaterial, THREE.Object3DEventMap>>;
17
17
  export declare function updatedArea(element: any, layer: Layer, scene: Scene, textures: Record<string, TextureData>, mesh: Mesh, oldElement: any, differences: string[], selfDestroy: () => void, selfBuild: () => Promise<Mesh>): Promise<Mesh>;
18
18
  export {};
@@ -0,0 +1,42 @@
1
+ export interface AreaPattern {
2
+ key: string;
3
+ name: string;
4
+ size: number;
5
+ /**
6
+ * Degrees the whole tiling lattice is turned by, SVG-style (positive =
7
+ * clockwise on screen). Only 45° is periodic under square tiling — see
8
+ * `patternTileDataURI` — so stick to that or leave it out.
9
+ */
10
+ rotation?: number;
11
+ /**
12
+ * The tile's contents as raw SVG markup rather than JSX, because the same
13
+ * string has to serve two very different consumers: React, for the 2D
14
+ * viewer, and a standalone `<svg>` document rasterized into a Three.js
15
+ * texture for the 3D viewer. Anything expressible only as React elements
16
+ * would have to be written twice and would drift.
17
+ */
18
+ marks: (color: string) => string;
19
+ }
20
+ export declare function darkenHex(hex: string, factor?: number): string;
21
+ export declare const AREA_PATTERNS: AreaPattern[];
22
+ export declare function getAreaPattern(key: string | undefined): AreaPattern | undefined;
23
+ /** `patternTransform` for the SVG `<pattern>`, or undefined when unrotated. */
24
+ export declare function patternTransform(pattern: AreaPattern): string | undefined;
25
+ /** The marks, for any React consumer that is building an SVG `<pattern>`. */
26
+ export declare function PatternMarks({ pattern, color, }: {
27
+ pattern: AreaPattern;
28
+ color: string;
29
+ }): import("react/jsx-runtime").JSX.Element;
30
+ /**
31
+ * One tile of `pattern` as an `<svg>` data URI, background included.
32
+ *
33
+ * The 3D floor can't hatch a mesh the way SVG hatches a path, so it gets a
34
+ * texture instead — and the texture is produced by handing the browser the
35
+ * *same* `<pattern>` element the 2D viewer uses. Rotation, stroke widths and
36
+ * spacing therefore come out of one renderer, not two approximations of each
37
+ * other, and the mesh only has to tile the result.
38
+ */
39
+ export declare function patternTileDataURI(pattern: AreaPattern, background: string, markColor: string): {
40
+ uri: string;
41
+ tileSize: number;
42
+ };
@@ -0,0 +1,22 @@
1
+ export interface RoomType {
2
+ value: string;
3
+ label: string;
4
+ color: string;
5
+ }
6
+ export interface RoomCategory {
7
+ value: string;
8
+ label: string;
9
+ types: RoomType[];
10
+ }
11
+ export declare const ROOM_CATEGORIES: RoomCategory[];
12
+ export declare function getCategory(value: string | undefined): RoomCategory | undefined;
13
+ export declare function getRoomType(categoryValue: string | undefined, typeValue: string | undefined): RoomType | undefined;
14
+ export declare function getRoomTypeLabel(categoryValue: string | undefined, typeValue: string | undefined): string;
15
+ export declare function getRoomTypeColor(categoryValue: string | undefined, typeValue: string | undefined): string | undefined;
16
+ export declare const DEFAULT_ROOM_CATEGORY = "";
17
+ export declare const DEFAULT_ROOM_TYPE = "";
18
+ export type AreaKind = "Room" | "Surface" | "Area";
19
+ export declare function getAreaKind(area: any, layer: any): AreaKind;
20
+ export declare function deriveAreaLabel(area: any, layer: any, opts?: {
21
+ defaultLabel?: string;
22
+ }): string;
@@ -13,5 +13,5 @@ type TextureInfo = {
13
13
  };
14
14
  };
15
15
  export declare function buildWall(element: any, layer: Layer, scene: Scene, textures: Record<string, TextureInfo>): Promise<Group>;
16
- export declare function updatedWall(element: any, layer: any, scene: any, textures: any, mesh: any, oldElement: any, differences: any, selfDestroy: any, selfBuild: any): any;
16
+ export declare function updatedWall(element: any, layer: any, scene: any, textures: any, mesh: any, _oldElement: any, differences: any, selfDestroy: any, selfBuild: any): any;
17
17
  export {};
@@ -1,6 +1,29 @@
1
1
  import { Set } from "immutable";
2
2
  declare class Area {
3
3
  static getSurfaceLines(state: any, layerID: string, areaID: string): Set<string>;
4
+ static center(state: any, layerID: string, areaID: string): {
5
+ x: number;
6
+ y: number;
7
+ } | null;
8
+ static transform(state: any, layerID: string, areaID: string, transform: (point: {
9
+ x: number;
10
+ y: number;
11
+ }) => {
12
+ x: number;
13
+ y: number;
14
+ }): {
15
+ updatedState: any;
16
+ };
17
+ /** Rotate about the area's center. Maths convention: in these y-up scene
18
+ * coords a positive angle turns counter-clockwise, which reads *clockwise*
19
+ * in the y-flipped 2D view. */
20
+ static rotate(state: any, layerID: string, areaID: string, degrees: number): {
21
+ updatedState: any;
22
+ };
23
+ /** Mirror about the area's center — "x" flips it left-right, "y" top-bottom. */
24
+ static mirror(state: any, layerID: string, areaID: string, axis: "x" | "y"): {
25
+ updatedState: any;
26
+ };
4
27
  static add(state: any, layerID: any, type: any, verticesCoords: any, catalog: any): {
5
28
  updatedState: any;
6
29
  area: undefined;
@@ -28,6 +28,16 @@ declare class Hole {
28
28
  static endDraggingHole(state: any, x: any, y: any): {
29
29
  updatedState: any;
30
30
  };
31
+ static flip(state: any, layerID: any, holeID: any, axis?: "x" | "y"): {
32
+ updatedState: any;
33
+ };
34
+ static duplicate(state: any, layerID: any, holeID: any): {
35
+ updatedState: any;
36
+ hole?: undefined;
37
+ } | {
38
+ updatedState: any;
39
+ hole: any;
40
+ };
31
41
  static setProperties(state: any, layerID: any, holeID: any, properties: any): {
32
42
  updatedState: any;
33
43
  };
@@ -40,6 +50,9 @@ declare class Hole {
40
50
  static updateJsProperties(state: any, layerID: any, holeID: any, properties: any): {
41
51
  updatedState: any;
42
52
  };
53
+ static mergeAttributes(state: any, layerID: any, holeID: any, attributes: any): {
54
+ updatedState: any;
55
+ };
43
56
  static setAttributes(state: any, layerID: any, holeID: any, holesAttributes: any): {
44
57
  updatedState: any;
45
58
  };