@osamash/floor-planner 1.2.4 → 1.2.5

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.
@@ -1,2 +1,21 @@
1
- declare const ReactPlannerLoader: () => import("react/jsx-runtime").JSX.Element;
1
+ import "./react-planner.css";
2
+ interface ReactPlannerLoaderProps {
3
+ /** Defaults to the theme the planner itself is about to open in, so the
4
+ * hand-off from skeleton to real UI doesn't flash. */
5
+ theme?: "light" | "dark";
6
+ }
7
+ /** Skeleton of the planner's chrome, shown while the plan and catalog load.
8
+ *
9
+ * It reuses the real layout classes — `.floor_planner-headerbar`,
10
+ * `.fp-toolbar-rail`, `.fp-toolbar-subpanel`, `.fp-toolbar-utility`,
11
+ * `.fp-zoom-controls` — instead of restating their geometry, so the
12
+ * placeholder keeps lining up with the real UI when that chrome moves. Only
13
+ * the grey blocks and the few `.fp-loader__*` rules are its own.
14
+ *
15
+ * What it mirrors is the planner's *opening* state: header, the three-item
16
+ * tool rail, the Build sub-panel the toolbar starts on, zoom controls
17
+ * shifted clear of that panel, the utility bar and the AI bubble. No
18
+ * properties sidebar — that only appears once something is selected — and no
19
+ * footer, which renders nothing today. */
20
+ declare const ReactPlannerLoader: ({ theme }: ReactPlannerLoaderProps) => import("react/jsx-runtime").JSX.Element;
2
21
  export default ReactPlannerLoader;
@@ -35,6 +35,12 @@ export interface WallInfo {
35
35
  length: number;
36
36
  orientation: "horizontal" | "vertical" | "diagonal";
37
37
  holeCount: number;
38
+ /** cm. */
39
+ thickness: number | null;
40
+ height: number | null;
41
+ /** The wall's two faces. Which one looks into a given room is geometric. */
42
+ textureA: string | null;
43
+ textureB: string | null;
38
44
  }
39
45
  export interface HoleInfo {
40
46
  id: string;
@@ -49,8 +55,8 @@ export interface HoleInfo {
49
55
  export interface AreaInfo {
50
56
  id: string;
51
57
  /** "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`. */
58
+ * patio, driveway…). A room's corners are shared with the walls around it,
59
+ * so the two are not interchangeable targets for anything that reshapes. */
54
60
  kind: "room" | "surface" | "area";
55
61
  vertices: {
56
62
  id: string;
@@ -58,6 +64,9 @@ export interface AreaInfo {
58
64
  y: number;
59
65
  }[];
60
66
  color: string | null;
67
+ /** Floor fill/texture. Mutually exclusive; "none" means unset. */
68
+ pattern: string | null;
69
+ texture: string | null;
61
70
  customName: string | null;
62
71
  roomCategory: string | null;
63
72
  roomType: string | null;
@@ -125,150 +134,145 @@ export interface PlanSummary {
125
134
  roomTypes: RoomCategoryOption[];
126
135
  selection: PlanSelection;
127
136
  }
128
- export type PlannerAction = {
129
- type: "add-wall";
130
- x0: number;
131
- y0: number;
132
- x1: number;
133
- y1: number;
134
- id?: string;
135
- } | {
137
+ /**
138
+ * Which side of an existing room a new one goes on. Screen-relative, matching
139
+ * how a user says it; note the scene's +y is UP, so "below" is smaller y.
140
+ */
141
+ export type RoomSide = "left" | "right" | "above" | "below";
142
+ /**
143
+ * An action the agent may emit for the planner to apply.
144
+ *
145
+ * **No action carries coordinates, and none ever should.** The agent says what
146
+ * to build and which existing room it relates to; every number describing a
147
+ * POSITION is computed by `applyPlannerActions`. Rooms live at 5-digit scene
148
+ * coordinates, and a room whose edge is a few centimetres off its neighbour
149
+ * draws as an unreachable void in 2D and a hole in the floor in 3D — so that
150
+ * arithmetic belongs where it can be exact.
151
+ *
152
+ * The set is being rebuilt one action at a time; the previous 23-variant
153
+ * union is on the `pre-rewrite-backup` branch. Each new variant needs three
154
+ * things moving together: this type (what may arrive), the backend's
155
+ * `ACTION_SPECS` (what the model may say), and a branch in
156
+ * `applyPlannerActions` (what actually happens).
157
+ */
158
+ export type PlannerAction = AddRoomAction | SetRoomPropertiesAction | AddHoleAction | AddItemAction | SetWallPropertiesAction | SetItemPropertiesAction | AddSurfaceAction | DeleteAction | SetSurfacePropertiesAction;
159
+ export type AddRoomAction = {
136
160
  type: "add-room";
137
- x: number;
138
- y: number;
161
+ /**
162
+ * Shown on the plan, and how a later `of` refers to this room — so it has to
163
+ * be unique within the scene ("Bedroom 1", "Bedroom 2").
164
+ */
165
+ name: string;
166
+ /** cm. Defaults are applied backend-side, so these arrive populated. */
139
167
  width: number;
140
168
  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;
169
+ /**
170
+ * The NAME of the room this one sits against — either already in the scene
171
+ * or created earlier in this same batch. Omitted together with `side` for
172
+ * the first room of a plan, which is centered in the scene instead.
173
+ */
174
+ of?: string;
175
+ side?: RoomSide;
176
+ /** Catalog room type; also sets the floor color to that type's own color. */
174
177
  roomCategory?: string;
175
178
  roomType?: string;
176
- patternColor?: string;
177
- /** One of PlanSummary.catalogAreaPatterns keys (or "none"). */
179
+ /** Explicit floor color. Wins over the room type's default when both are set. */
180
+ color?: string;
181
+ };
182
+ export type SetRoomPropertiesAction = {
183
+ type: "set-room-properties";
184
+ /** A room name, or the literal "selected" for the room highlighted in the canvas. */
185
+ room: string;
186
+ /** Renames the room. */
187
+ name?: string;
188
+ roomCategory?: string;
189
+ roomType?: string;
190
+ color?: string;
178
191
  pattern?: string;
179
- /** One of PlanSummary.catalogAreaTextures keys (or "none"). */
180
192
  texture?: string;
181
- showName?: boolean;
182
- showSurfaceArea?: boolean;
183
- } | {
193
+ };
194
+ export type AddHoleAction = {
195
+ type: "add-hole";
196
+ /** Verbatim from PlanSummary.catalogHoleTypes. */
197
+ holeType: string;
198
+ /**
199
+ * The two rooms this opening connects; the shared wall is found here. One
200
+ * opening has one representation, so it cannot be described from both sides
201
+ * and land as two doors. Mutually exclusive with room/side.
202
+ */
203
+ between?: [string, string];
204
+ /** A particular wall — the only way to reach an exterior one. */
205
+ room?: string;
206
+ side?: RoomSide;
207
+ };
208
+ export type AddItemAction = {
209
+ type: "add-item";
210
+ /** Verbatim from PlanSummary.catalogItemTypes. */
211
+ itemType: string;
212
+ /** A room name, or "selected". */
213
+ room: string;
214
+ /** 1-8. col 1 is the left eighth of the room, row 1 the top. */
215
+ col: number;
216
+ row: number;
217
+ /** 0, 90, 180 or 270. */
218
+ rotation?: number;
219
+ };
220
+ export type SetWallPropertiesAction = {
184
221
  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 */
222
+ /** A room name, or "selected". All four of its walls unless `side` is given. */
223
+ room: string;
224
+ side?: RoomSide;
225
+ /** Applied to the wall face looking into `room`. */
226
+ color?: string;
227
+ texture?: string;
228
+ /** cm. Whole-wall, so a shared wall changes for both rooms. */
192
229
  thickness?: number;
193
- /** cm */
194
230
  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. */
231
+ };
232
+ export type SetItemPropertiesAction = {
201
233
  type: "set-item-properties";
202
- itemID: string;
203
- x?: number;
204
- y?: number;
205
- /** Degrees, absolute (not a delta). Positive = counter-clockwise. */
234
+ /** A room name, or "selected". */
235
+ room: string;
236
+ /** The furniture's catalog name, e.g. "macheroni sofa". */
237
+ item: string;
238
+ /** Move it to this cell of the room's 8x8 grid. Both or neither. */
239
+ col?: number;
240
+ row?: number;
241
+ /** The new angle, not a turn to add. */
206
242
  rotation?: number;
207
- /** Uniform size multiplier, clamped to 0.2–8. 1 = catalog size. */
243
+ /** Multiplier on the catalog size; the only sizing an item has. */
208
244
  scale?: number;
209
245
  flipHorizontal?: boolean;
210
246
  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";
247
+ };
248
+ export type AddSurfaceAction = {
249
+ type: "add-surface";
250
+ /** The room whose wall it lies against, and which side. Must face outdoors. */
251
+ of: string;
252
+ side: RoomSide;
253
+ name?: string;
254
+ /** cm away from the wall; it runs that wall's full width. */
255
+ depth?: number;
256
+ texture?: string;
257
+ color?: string;
258
+ };
259
+ export type DeleteAction = {
260
+ type: "delete";
261
+ /** A room name, a surface name, or "selected". */
262
+ room: string;
263
+ /** Present to delete one piece of furniture inside it instead of the room. */
264
+ item?: string;
265
+ };
266
+ export type SetSurfacePropertiesAction = {
267
+ type: "set-surface-properties";
268
+ /** Its name from the Outdoor surfaces list. */
269
+ surface: string;
270
+ name?: string;
271
+ /** cm out from the wall it lies against; same wall, same width. */
272
+ depth?: number;
273
+ texture?: string;
274
+ pattern?: string;
275
+ color?: string;
272
276
  };
273
277
  export interface AIResponse {
274
278
  reply: string;
@@ -277,9 +281,9 @@ export interface AIResponse {
277
281
  * Multi-step continuation. Building something big (a whole multi-room
278
282
  * floor plan) can need more actions than fit in one well-grounded
279
283
  * 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).
284
+ * `PlanSummary`) before placing doors on it. Real IDs are random and only
285
+ * exist once the library has created the element, so anything that needs one
286
+ * has to wait for the next call.
283
287
  *
284
288
  * Set `done: false` to have the library apply this response's `actions`,
285
289
  * take a fresh `summarizePlan()` of the result, and call your handler
@@ -297,6 +301,15 @@ export interface AIResponse {
297
301
  * more than that for one user request.
298
302
  */
299
303
  done?: boolean;
304
+ /**
305
+ * Actions the backend rejected, with the reason. Fed back to the agent on the
306
+ * next continuation so it can correct itself instead of repeating the
307
+ * mistake — see the "Continue." turn in AIChatPanel.
308
+ */
309
+ dropped?: {
310
+ reason: string;
311
+ action: unknown;
312
+ }[];
300
313
  }
301
314
  /**
302
315
  * Extras the library hands a handler on every call. Optional on purpose: a
@@ -1,3 +1,20 @@
1
1
  import type { PlanSummary, PlannerAction } from "./aiService";
2
2
  export declare function summarizePlan(planner: any): PlanSummary;
3
- export declare function applyPlannerActions(actions: PlannerAction[]): void;
3
+ /**
4
+ * Applies a batch of actions from the agent to the planner.
5
+ *
6
+ * The agent never sends coordinates. It says how big a room is, what to call
7
+ * it, and which existing room it sits next to — every number describing a
8
+ * POSITION is worked out here, where it can be exact. A room with no `of` /
9
+ * `side` is centered in the scene, which is only ever right for the first room.
10
+ *
11
+ * Rooms created earlier in this same batch are valid anchors: each one is
12
+ * looked up by name from live state after it is created, so a five-room plan
13
+ * arrives as five actions that chain off each other.
14
+ *
15
+ * @returns `skipped` — human-readable reasons anything was not applied, shown
16
+ * to the user by the caller.
17
+ */
18
+ export declare function applyPlannerActions(actions: PlannerAction[]): {
19
+ skipped: string[];
20
+ };
@@ -7,7 +7,6 @@ import type { AIRequestHandler } from "./aiService";
7
7
  *
8
8
  * Wire format (Server-Sent Events, POST — so NOT `EventSource`-compatible):
9
9
  *
10
- * event: meta data: { provider, model } once, before any output
11
10
  * event: reply data: { delta } zero or more, in order
12
11
  * event: final data: { reply, actions, done, … } authoritative, terminal
13
12
  * event: error data: { error, type } terminal
@@ -47,8 +47,28 @@ export default class Catalog {
47
47
  private elements;
48
48
  categories: Record<string, CatalogCategory>;
49
49
  private propertyTypes;
50
+ /**
51
+ * Placeholders handed out for types the plan references and this catalog
52
+ * doesn't have. Cached so a type resolves to the same object every render,
53
+ * and so the warning about it is printed once.
54
+ */
55
+ private missingElements;
50
56
  unit: Unit;
51
57
  constructor(unit?: Unit);
58
+ /**
59
+ * The element `type` names, or a placeholder if the catalog has no such
60
+ * element.
61
+ *
62
+ * Deliberately total. Every renderer and editor reaches an element through
63
+ * here, and a plan can legitimately outlive the catalog it was drawn with —
64
+ * an item renamed, dropped, or opened by an app carrying a different subset.
65
+ * Throwing meant one stale `type` in a saved plan took down the render of
66
+ * everything around it; the placeholder keeps the rest of the plan on screen
67
+ * and keeps the offending element selectable, movable and — because nothing
68
+ * rewrites its `type` — able to come back intact against the right catalog.
69
+ * Callers that need to know whether something is really registered ask
70
+ * `hasElement`.
71
+ */
52
72
  getElement(type: string): CatalogElement;
53
73
  /** Every registered element of a prototype ("items", "holes", "lines"),
54
74
  * regardless of the category it was filed under. Backs the sidebar's
@@ -12,6 +12,8 @@ export default function (json: any): {
12
12
  tag: any;
13
13
  description: any;
14
14
  image: any;
15
+ width: any;
16
+ depth: any;
15
17
  };
16
18
  properties: any;
17
19
  render2D: (element: any, layer: any, scene: any) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import type { CatalogElement } from "./catalog";
2
+ /**
3
+ * Build the stand-in for `type`.
4
+ *
5
+ * `prototype` is left empty on purpose: the element's own record already
6
+ * carries the right one, and claiming one here would file the placeholder among
7
+ * real elements — `getElementsOfPrototype` backs the "similar elements" list,
8
+ * which should never offer a missing type as something to place.
9
+ */
10
+ export default function unknownElement(type: string): CatalogElement;
@@ -1,4 +1,5 @@
1
1
  import { Set } from "immutable";
2
+ import { type LineFamily } from "../utils/line-family";
2
3
  declare class Area {
3
4
  static getSurfaceLines(state: any, layerID: string, areaID: string): Set<string>;
4
5
  static center(state: any, layerID: string, areaID: string): {
@@ -24,7 +25,10 @@ declare class Area {
24
25
  static mirror(state: any, layerID: string, areaID: string, axis: "x" | "y"): {
25
26
  updatedState: any;
26
27
  };
27
- static add(state: any, layerID: any, type: any, verticesCoords: any, catalog: any): {
28
+ /** `family` says which graph the boundary belongs to. Walls and surfaces can
29
+ * now have distinct vertices at identical coordinates, so without it the
30
+ * lookup below could bind a surface polygon to a wall's corner. */
31
+ static add(state: any, layerID: any, type: any, verticesCoords: any, catalog: any, family?: LineFamily): {
28
32
  updatedState: any;
29
33
  area: undefined;
30
34
  };
@@ -1,7 +1,27 @@
1
+ /** Free-standing polygons — patios, lawns, decking. Drawn corner by corner
2
+ * with the same lines/vertices as walls, but kept out of the wall graph by
3
+ * `utils/line-family`, so laying one over a room neither welds it to the
4
+ * walls it crosses nor takes the room's floor over.
5
+ *
6
+ * Snapping is the deliberate exception: a surface *should* be able to land
7
+ * exactly on a wall corner or run flush along a wall — it just must not
8
+ * become attached to it. So the whole scene is offered as snap targets, the
9
+ * same set the wall tool uses.
10
+ *
11
+ * Drawing: click each corner. Click back on the first corner to close the
12
+ * polygon, or leave the tool (Esc, double-click) once three corners are down
13
+ * and `finishDrawingSurface` closes it for you.
14
+ */
1
15
  declare class Surface {
2
16
  static selectToolDrawingSurface(state: any, sceneComponentType: string): {
3
17
  updatedState: any;
4
18
  };
19
+ /** Snap targets for the whole scene, plus the horizontal/vertical guides
20
+ * through `(x, y)` — exactly what `Line.beginDrawingLine` builds. This was
21
+ * simply missing before: `snapElements` stayed empty for the surface tool,
22
+ * so every `nearestSnap` downstream had nothing to find and the tool
23
+ * behaved as if snapping were off. */
24
+ private static anchorSnaps;
5
25
  static beginDrawingSurface(state: any, layerID: string, x: number, y: number): {
6
26
  updatedState: any;
7
27
  };
@@ -11,5 +31,19 @@ declare class Surface {
11
31
  static endDrawingSurface(state: any, x: number, y: number): {
12
32
  updatedState: any;
13
33
  };
34
+ /** Leave the tool keeping what has been drawn, by committing the edge from
35
+ * the last clicked corner back to the first.
36
+ *
37
+ * This is what Esc and the double-click go through. The outline on screen
38
+ * is already drawn closed while the tool is live, so throwing it away is
39
+ * never what leaving the tool means — but only clicked corners count, not
40
+ * wherever the cursor happens to be resting.
41
+ *
42
+ * Under three corners there is no polygon to keep; `finished: false` says
43
+ * so, and the caller falls back to a real rollback. */
44
+ static finishDrawingSurface(state: any): {
45
+ updatedState: any;
46
+ finished: boolean;
47
+ };
14
48
  }
15
49
  export default Surface;
@@ -1,5 +1,10 @@
1
+ import { type LineFamily } from "../utils/line-family";
1
2
  declare class Vertex {
2
- static add(state: any, layerID: any, x: any, y: any, relatedPrototype: any, relatedID: any): {
3
+ /** `family` is what the caller is about to attach here. A coincident vertex
4
+ * is only reused when it belongs to the same family, so a surface corner
5
+ * dropped on a wall corner gets its own vertex and the two graphs stay
6
+ * separate. Omit it to reuse any coincident vertex (the old behaviour). */
7
+ static add(state: any, layerID: any, x: any, y: any, relatedPrototype: any, relatedID: any, family?: LineFamily): {
3
8
  updatedState: any;
4
9
  vertex: any;
5
10
  };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Keeps a plan thumbnail up to date, handing each new one to `onReady`.
3
+ *
4
+ * Capture is deliberately *ahead* of unload, not during it. `beforeunload`
5
+ * can't await anything, and a multi-hundred-KB data URI is far past what
6
+ * sendBeacon or fetch({ keepalive: true }) will carry (~64KB), so there is no
7
+ * way to both build and ship an image once the page is going away. Instead we
8
+ * capture on an idle timer and again when the page is hidden — by the time the
9
+ * tab actually closes, the server already has a current image.
10
+ */
11
+ export declare function usePlanThumbnail(onReady?: (img: string) => void): {
12
+ ensureScreenshot: (force?: boolean) => Promise<void>;
13
+ };
@@ -173,6 +173,18 @@ declare const Catalog_base: Record.Factory<{
173
173
  }>;
174
174
  export declare class Catalog extends Catalog_base {
175
175
  constructor(json?: any);
176
+ /**
177
+ * Build a record of `type` with its catalog defaults filled in.
178
+ *
179
+ * A type the catalog doesn't have is normally a caller's mistake, but not
180
+ * always: duplicating something already in the plan goes through here, and a
181
+ * plan can outlive the catalog it was drawn with (see
182
+ * `catalog/unknown-element.tsx`). So when `options.prototype` says which kind
183
+ * of record to build, one gets built — carrying whatever properties came in,
184
+ * since there are no defaults to merge — rather than taking the planner down
185
+ * over a copy of something already on screen. Without that hint there is
186
+ * nothing to construct, and it still throws.
187
+ */
176
188
  factoryElement(type: string, options: any, initialProperties?: Map<string, any>): Line | Hole | Area | Item;
177
189
  }
178
190
  declare const HistoryStructure_base: Record.Factory<{
@@ -34,6 +34,57 @@ export interface EditorAction {
34
34
  export declare const ActionRow: ({ actions }: {
35
35
  actions: EditorAction[];
36
36
  }) => import("react/jsx-runtime").JSX.Element;
37
+ /**
38
+ * One entry of a catalog element's `textures` map, as far as the picker cares.
39
+ * Structural on purpose — the repeat scales matter to the renderers, not here.
40
+ */
41
+ export interface MaterialTexture {
42
+ name: string;
43
+ uri: string;
44
+ tag?: string[];
45
+ }
46
+ interface MaterialPickerProps {
47
+ textures: Record<string, MaterialTexture>;
48
+ /** Currently applied key, or `"none"`. */
49
+ value: string;
50
+ onChange: (key: string) => void;
51
+ /** Heading, rendered only alongside the picker so it can't be left stranded
52
+ * above nothing when the element has no materials. */
53
+ label?: string;
54
+ }
55
+ /**
56
+ * Browse a catalog element's materials.
57
+ *
58
+ * Shared by the wall and area editors because they browse the same library —
59
+ * the catalog hands floors and walls one `textures` map, so whatever makes it
60
+ * navigable for one has to be there for the other. Categories come from the
61
+ * materials' own tags in the order the catalog lists them, so no vocabulary is
62
+ * baked in here.
63
+ */
64
+ export declare const MaterialPicker: ({ textures, value, onChange, label, }: MaterialPickerProps) => import("react/jsx-runtime").JSX.Element | null;
65
+ interface FinishPickerProps {
66
+ /** Paint colour, as `#RRGGBB`. */
67
+ color: string;
68
+ onColorChange: (color: string) => void;
69
+ textures: Record<string, MaterialTexture>;
70
+ /** Currently applied texture key, or `"none"`. */
71
+ texture: string;
72
+ onTextureChange: (key: string) => void;
73
+ /**
74
+ * Rendered under the palette on the Colors tab. Areas put their hatch
75
+ * patterns here; walls have none, and pass nothing.
76
+ */
77
+ patterns?: React.ReactNode;
78
+ }
79
+ /**
80
+ * How a surface's finish is chosen: paint on one tab, materials on the other.
81
+ *
82
+ * Walls and areas both answer the same question here — what is this surface
83
+ * covered in — so they get the same two tabs over the same palette and the same
84
+ * material library. What differs is what else lives under the palette, which
85
+ * comes in as `patterns`.
86
+ */
87
+ export declare const FinishPicker: ({ color, onColorChange, textures, texture, onTextureChange, patterns, }: FinishPickerProps) => import("react/jsx-runtime").JSX.Element;
37
88
  /** Catalog thumbnail, falling back to a neutral tile when there is no image. */
38
89
  export declare const ElementPreview: ({ imageUri, alt, }: {
39
90
  imageUri?: string;
@@ -0,0 +1,6 @@
1
+ interface PaintPaletteProps {
2
+ value: string;
3
+ onChange: (color: string) => void;
4
+ }
5
+ export declare const PaintPalette: ({ value, onChange }: PaintPaletteProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default PaintPalette;
@@ -5,9 +5,10 @@ import * as SnapUtils from "./snap";
5
5
  import * as SnapSceneUtils from "./snap-scene";
6
6
  import * as history from "./history";
7
7
  import * as ObjectUtils from "./objects-utils";
8
+ import * as LineFamilyUtils from "./line-family";
8
9
  import IDBroker from "./id-broker";
9
10
  import NameGenerator from "./name-generator";
10
- export { GeometryUtils, GraphInnerCycles, MathUtils, SnapUtils, SnapSceneUtils, history, IDBroker, NameGenerator, ObjectUtils, };
11
+ export { GeometryUtils, GraphInnerCycles, MathUtils, SnapUtils, SnapSceneUtils, history, IDBroker, NameGenerator, ObjectUtils, LineFamilyUtils, };
11
12
  declare const _default: {
12
13
  GeometryUtils: typeof GeometryUtils;
13
14
  GraphInnerCycles: typeof GraphInnerCycles;
@@ -18,5 +19,6 @@ declare const _default: {
18
19
  IDBroker: typeof IDBroker;
19
20
  NameGenerator: typeof NameGenerator;
20
21
  ObjectUtils: typeof ObjectUtils;
22
+ LineFamilyUtils: typeof LineFamilyUtils;
21
23
  };
22
24
  export default _default;