@ahrowe/ui 0.25.2 → 0.26.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 (74) hide show
  1. package/dist/esm/common/floorPlan/floorPlan.faces.mjs +2 -0
  2. package/dist/esm/common/floorPlan/floorPlan.faces.mjs.map +1 -0
  3. package/dist/esm/common/floorPlan/floorPlan.geometry.mjs +2 -0
  4. package/dist/esm/common/floorPlan/floorPlan.geometry.mjs.map +1 -0
  5. package/dist/esm/common/floorPlan/floorPlan.graph.mjs +2 -0
  6. package/dist/esm/common/floorPlan/floorPlan.graph.mjs.map +1 -0
  7. package/dist/esm/common/floorPlan/floorPlan.hit.mjs +2 -0
  8. package/dist/esm/common/floorPlan/floorPlan.hit.mjs.map +1 -0
  9. package/dist/esm/common/floorPlan/floorPlan.json.mjs +2 -0
  10. package/dist/esm/common/floorPlan/floorPlan.json.mjs.map +1 -0
  11. package/dist/esm/common/floorPlan/floorPlan.path.mjs +2 -0
  12. package/dist/esm/common/floorPlan/floorPlan.path.mjs.map +1 -0
  13. package/dist/esm/common/floorPlan/floorPlan.plan.mjs +2 -0
  14. package/dist/esm/common/floorPlan/floorPlan.plan.mjs.map +1 -0
  15. package/dist/esm/common/floorPlan/floorPlan.rooms.mjs +2 -0
  16. package/dist/esm/common/floorPlan/floorPlan.rooms.mjs.map +1 -0
  17. package/dist/esm/common/floorPlan/floorPlan.snap.mjs +2 -0
  18. package/dist/esm/common/floorPlan/floorPlan.snap.mjs.map +1 -0
  19. package/dist/esm/common/floorPlan/floorPlan.types.mjs +2 -0
  20. package/dist/esm/common/floorPlan/floorPlan.types.mjs.map +1 -0
  21. package/dist/esm/common/floorPlan/legacySvg.mjs +2 -0
  22. package/dist/esm/common/floorPlan/legacySvg.mjs.map +1 -0
  23. package/dist/esm/common/planCanvas/planCanvas.mjs +2 -0
  24. package/dist/esm/common/planCanvas/planCanvas.mjs.map +1 -0
  25. package/dist/esm/common/planCanvas/planCanvas.module.mjs +2 -0
  26. package/dist/esm/common/planCanvas/planCanvas.module.mjs.map +1 -0
  27. package/dist/esm/common/roomDrawer/roomDrawer.mjs +1 -1
  28. package/dist/esm/common/roomDrawer/roomDrawer.mjs.map +1 -1
  29. package/dist/esm/common/roomDrawer/roomDrawer.module.mjs +1 -1
  30. package/dist/esm/common/roomDrawer/roomDrawer.module.mjs.map +1 -1
  31. package/dist/esm/common/roomDrawer/usePlanHistory.mjs +2 -0
  32. package/dist/esm/common/roomDrawer/usePlanHistory.mjs.map +1 -0
  33. package/dist/esm/common/roomViewer/roomViewer.mjs +1 -1
  34. package/dist/esm/common/roomViewer/roomViewer.mjs.map +1 -1
  35. package/dist/esm/common/roomViewer/roomViewer.module.mjs +1 -1
  36. package/dist/esm/common/roomViewer/roomViewer.module.mjs.map +1 -1
  37. package/dist/esm/index.mjs +1 -1
  38. package/dist/index.cjs +3 -7
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/style.css +1 -1
  41. package/dist/types/package/common/configProvider/configProvider.types.d.ts +4 -0
  42. package/dist/types/package/common/floorPlan/floorPlan.faces.d.ts +33 -0
  43. package/dist/types/package/common/floorPlan/floorPlan.fixtures.d.ts +35 -0
  44. package/dist/types/package/common/floorPlan/floorPlan.geometry.d.ts +111 -0
  45. package/dist/types/package/common/floorPlan/floorPlan.graph.d.ts +139 -0
  46. package/dist/types/package/common/floorPlan/floorPlan.hit.d.ts +26 -0
  47. package/dist/types/package/common/floorPlan/floorPlan.json.d.ts +23 -0
  48. package/dist/types/package/common/floorPlan/floorPlan.path.d.ts +59 -0
  49. package/dist/types/package/common/floorPlan/floorPlan.plan.d.ts +98 -0
  50. package/dist/types/package/common/floorPlan/floorPlan.rooms.d.ts +47 -0
  51. package/dist/types/package/common/floorPlan/floorPlan.snap.d.ts +101 -0
  52. package/dist/types/package/common/floorPlan/floorPlan.types.d.ts +276 -0
  53. package/dist/types/package/common/floorPlan/index.d.ts +28 -0
  54. package/dist/types/package/common/floorPlan/legacySvg.d.ts +27 -0
  55. package/dist/types/package/common/planCanvas/index.d.ts +2 -0
  56. package/dist/types/package/common/planCanvas/planCanvas.d.ts +7 -0
  57. package/dist/types/package/common/planCanvas/planCanvas.types.d.ts +62 -0
  58. package/dist/types/package/common/roomDrawer/roomDrawer.d.ts +3 -3
  59. package/dist/types/package/common/roomDrawer/roomDrawer.types.d.ts +295 -15
  60. package/dist/types/package/common/roomDrawer/usePlanHistory.d.ts +24 -0
  61. package/dist/types/package/common/roomViewer/index.d.ts +1 -0
  62. package/dist/types/package/common/roomViewer/roomViewer.d.ts +9 -2
  63. package/dist/types/package/common/roomViewer/roomViewer.types.d.ts +88 -9
  64. package/dist/types/package/index.d.ts +1 -0
  65. package/docs/CLAUDE.md +1 -0
  66. package/docs/ConfigProvider.md +1 -0
  67. package/docs/FloorPlan.md +214 -0
  68. package/docs/RoomDrawer.md +330 -30
  69. package/docs/RoomViewer.md +80 -27
  70. package/package.json +1 -1
  71. package/dist/esm/common/roomDrawer/roomDrawer.utils.mjs +0 -6
  72. package/dist/esm/common/roomDrawer/roomDrawer.utils.mjs.map +0 -1
  73. package/dist/types/package/common/roomDrawer/roomDrawer.utils.d.ts +0 -32
  74. package/docs/room-drawing-analysis.md +0 -337
@@ -0,0 +1,101 @@
1
+ import { PlanFloor, PlanPoint } from './floorPlan.types';
2
+ export declare enum SnapKind {
3
+ None = "none",
4
+ Node = "node",
5
+ /** A point projected onto a wall's centreline. */
6
+ Wall = "wall",
7
+ /** The crossing of two walls, or of a wall with the active angle ray. */
8
+ Intersection = "intersection",
9
+ /** On the infinite line continuing an existing wall. */
10
+ Extension = "extension",
11
+ /** Shares an x or y coordinate with an existing node. */
12
+ Alignment = "alignment",
13
+ /** On a ray at a multiple of `angleStepDeg` from `origin`. */
14
+ Angle = "angle",
15
+ Grid = "grid"
16
+ }
17
+ export interface SnapGuide {
18
+ kind: SnapKind;
19
+ a: PlanPoint;
20
+ b: PlanPoint;
21
+ /** Render as an unbounded construction line rather than a segment. */
22
+ infinite?: boolean;
23
+ }
24
+ export interface SnapOptions {
25
+ /** Which snaps are active. Pass `[]` (Alt held) to disable snapping entirely. */
26
+ enabled: SnapKind[];
27
+ /** Millimetres. 0 disables grid snap. */
28
+ gridSize: number;
29
+ /** Degrees. 45 gives 0/45/90. 0 disables angle lock. */
30
+ angleStepDeg: number;
31
+ toleranceScreenPx: number;
32
+ /** Nodes are the most valuable snap, so they get a little more room. */
33
+ nodeToleranceScreenPx?: number;
34
+ }
35
+ export declare const DEFAULT_SNAP_OPTIONS: SnapOptions;
36
+ export interface SnapContext {
37
+ floor: PlanFloor;
38
+ /** World units per screen pixel at the current zoom. See `screenScale`. */
39
+ pxToWorld: number;
40
+ /** The anchored point. Required for angle lock; also enables the length readout. */
41
+ origin?: PlanPoint;
42
+ /**
43
+ * A direction in radians that angle lock measures from, in ADDITION to the world axes.
44
+ *
45
+ * Pass the direction of the wall being continued and the lock offers perpendicular and
46
+ * parallel to that wall, not just to the world. Without it, angle lock is only useful
47
+ * on a building that happens to be aligned to the page.
48
+ */
49
+ referenceAngle?: number;
50
+ excludeWallIds?: ReadonlySet<string>;
51
+ excludeNodeIds?: ReadonlySet<string>;
52
+ options?: Partial<SnapOptions>;
53
+ }
54
+ export interface SnapResult {
55
+ point: PlanPoint;
56
+ kind: SnapKind;
57
+ nodeId?: string;
58
+ wallId?: string;
59
+ /** Parameter along `wallId`, for `SnapKind.Wall`. */
60
+ t?: number;
61
+ /** Everything an overlay needs to draw to explain the snap. */
62
+ guides: SnapGuide[];
63
+ /** Radians from `origin` to `point`, when an origin was given. */
64
+ angle?: number;
65
+ /** Distance from `origin` to `point`, when an origin was given. */
66
+ distance?: number;
67
+ /**
68
+ * Set when angle lock produced the result, saying what it locked against: the world
69
+ * axes, or the reference direction that was passed in.
70
+ */
71
+ angleRelativeTo?: 'world' | 'reference';
72
+ }
73
+ /** World units per screen pixel, from the element's current transform. */
74
+ export declare function screenScale(svg: SVGSVGElement): number;
75
+ /**
76
+ * Client (screen) coordinates to world coordinates. Pure conversion, no snapping.
77
+ *
78
+ * Uses `getBoundingClientRect` arithmetic against an explicit viewport rather than
79
+ * `getScreenCTM`, which returns `null` in jsdom and so makes anything built on it
80
+ * untestable.
81
+ */
82
+ export declare function clientToWorld(clientX: number, clientY: number, rect: {
83
+ left: number;
84
+ top: number;
85
+ }, view: {
86
+ x: number;
87
+ y: number;
88
+ zoom: number;
89
+ }): PlanPoint;
90
+ /**
91
+ * Resolves a raw pointer position to the point the user meant.
92
+ *
93
+ * Two stages, because half of these snaps are constraints rather than points. A hard
94
+ * snap (node, intersection, wall) fixes both degrees of freedom, so the first match
95
+ * wins. A constraint (angle lock, extension, alignment) removes only one, so a flat
96
+ * "first match wins" list would kick the point off the angle ray the moment an
97
+ * alignment also matched. Two constraints intersect; one constraint is projected onto
98
+ * and then slid ALONG to the nearest grid position, which is what lets angle lock and
99
+ * grid coexist instead of fighting.
100
+ */
101
+ export declare function resolveSnap(raw: PlanPoint, ctx: SnapContext): SnapResult;
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Floor plan model: a planar wall graph whose rooms are derived, not stored.
3
+ *
4
+ * Nodes and walls are first-class and shared, so the wall between two rooms is one
5
+ * wall. An opening therefore cuts it exactly once, which is what the previous
6
+ * room-polygon model could not express.
7
+ *
8
+ * GEOMETRY INVARIANT: every coordinate, length, thickness, offset and height in this
9
+ * module is in MILLIMETRES. There is deliberately no per-plan scale factor: one would
10
+ * have to be threaded through every geometry function, and a single missed call site
11
+ * is a silent 1000x error that no type can catch. `unitSystem` below drives display
12
+ * formatting only and is never read by geometry.
13
+ */
14
+ /** A point in plan space, in millimetres. y increases downwards, as in SVG. */
15
+ export interface PlanPoint {
16
+ x: number;
17
+ y: number;
18
+ }
19
+ export declare enum PlanUnitSystem {
20
+ /** Display as m / cm / mm. */
21
+ Metric = "metric",
22
+ /** Display as feet and inches. Storage stays millimetres. */
23
+ Imperial = "imperial"
24
+ }
25
+ export declare enum WallType {
26
+ /** Load-bearing envelope wall. */
27
+ Exterior = "exterior",
28
+ /** Load-bearing interior wall. */
29
+ Interior = "interior",
30
+ /** Non-load-bearing partition. */
31
+ Partition = "partition",
32
+ /**
33
+ * A room boundary with no physical wall, for dividing open-plan space. It still
34
+ * closes a face, so it produces two rooms, but renders as a thin dashed line and
35
+ * extrudes to nothing in 3D.
36
+ */
37
+ Virtual = "virtual"
38
+ }
39
+ export declare enum OpeningKind {
40
+ /** A real door leaf, with a swing and a threshold. */
41
+ Door = "door",
42
+ /** Glazed opening with a sill above floor level. */
43
+ Window = "window",
44
+ /** Structural opening with a head but no leaf. */
45
+ Archway = "archway",
46
+ /**
47
+ * A plain hole in the wall with no jamb detailing. Distinct from "no wall at all":
48
+ * a Gap is a marked span of a wall that exists, which is what lets a gap-style
49
+ * rendering stay unambiguous.
50
+ */
51
+ Gap = "gap"
52
+ }
53
+ export declare enum DoorSwing {
54
+ None = "none",
55
+ /** Hinged at the opening's start, opening towards the wall's left normal. */
56
+ StartLeft = "startLeft",
57
+ StartRight = "startRight",
58
+ EndLeft = "endLeft",
59
+ EndRight = "endRight",
60
+ /** Two leaves, hinged at both ends. */
61
+ Double = "double",
62
+ Sliding = "sliding"
63
+ }
64
+ /** A graph vertex. Structurally assignable to `PlanPoint`. */
65
+ export interface PlanNode {
66
+ id: string;
67
+ x: number;
68
+ y: number;
69
+ }
70
+ /**
71
+ * An undirected edge between two nodes. `a` and `b` define the wall's own direction,
72
+ * which is what opening offsets and door swings are measured against.
73
+ *
74
+ * The wall is centred on the segment a..b, extending `thickness / 2` to each side.
75
+ * There is no eccentric offset in this version.
76
+ */
77
+ export interface PlanWall {
78
+ id: string;
79
+ a: string;
80
+ b: string;
81
+ /** Millimetres. Must be greater than 0. */
82
+ thickness: number;
83
+ /** Millimetres above this wall's base. Omit to inherit `PlanFloor.height`. */
84
+ height?: number;
85
+ /** Millimetres above the floor's finished level. Defaults to 0. */
86
+ baseHeight?: number;
87
+ type: WallType;
88
+ }
89
+ /**
90
+ * An opening cut into exactly one wall. Because a shared wall is one wall, an opening
91
+ * between two rooms exists exactly once and needs no de-duplication anywhere.
92
+ */
93
+ export interface PlanOpening {
94
+ id: string;
95
+ wallId: string;
96
+ kind: OpeningKind;
97
+ /**
98
+ * Distance in millimetres from the wall's node `a` to the opening's CENTRE, measured
99
+ * along the centreline.
100
+ *
101
+ * Deliberately absolute rather than a 0..1 parameter: with a parameter, stretching a
102
+ * wall by dragging one of its nodes would rescale every door on it. Absolute offsets
103
+ * are clamped instead when a wall shortens.
104
+ */
105
+ offset: number;
106
+ /** Millimetres, along the wall. */
107
+ width: number;
108
+ /**
109
+ * Height of the opening's BOTTOM above the floor, in millimetres: 0 for a door, around
110
+ * 900 for a window. Nothing in the 2D plan reads it, since a plan is a horizontal cut
111
+ * and every opening looks the same from above. It is here so the same plan can drive a
112
+ * 3D view later without the heights having to be invented or asked for again.
113
+ */
114
+ sill: number;
115
+ /** Height of the opening's TOP above the floor, in millimetres. Typically 2100. */
116
+ head: number;
117
+ /** Only meaningful for `OpeningKind.Door`. */
118
+ swing?: DoorSwing;
119
+ }
120
+ /**
121
+ * Room METADATA. This is not a geometric object: the polygon comes from face
122
+ * extraction. The trailing `wallIds` / `area` fields are a match cache maintained by
123
+ * `derivePlan`, and are the reason a room's name survives edits.
124
+ */
125
+ export interface PlanRoom {
126
+ id: string;
127
+ name: string;
128
+ /** Any CSS colour. Omit to use the theme default. */
129
+ colour?: string;
130
+ /** Free-form usage tag ("kitchen", "bath"), for later fixture and 3D defaults. */
131
+ usage?: string;
132
+ /**
133
+ * A point that lay inside the room's polygon when it was last written. Used as the
134
+ * label position and as the secondary identity signal during matching.
135
+ */
136
+ anchor: PlanPoint;
137
+ /** Set once the user drags the label; `derivePlan` then stops moving it. */
138
+ anchorPinned: boolean;
139
+ /** Monotonic creation counter, used as a deterministic tie-break when matching. */
140
+ seq: number;
141
+ /** Match cache: the wall ids on this room's boundary at the last successful match. */
142
+ wallIds: string[];
143
+ /** Match cache: area in mm² at the last successful match. */
144
+ area: number;
145
+ }
146
+ export interface PlanFloor {
147
+ id: string;
148
+ label: string;
149
+ /** Finished floor level of this storey, millimetres above datum. */
150
+ elevation: number;
151
+ /** Default storey height in millimetres, used when a wall omits its own `height`. */
152
+ height: number;
153
+ /**
154
+ * Keyed by id for O(1) lookup. Ids must not be numeric strings ('n_12', never '12'),
155
+ * so that JavaScript preserves insertion order and iteration stays reproducible.
156
+ */
157
+ nodes: Record<string, PlanNode>;
158
+ walls: Record<string, PlanWall>;
159
+ openings: Record<string, PlanOpening>;
160
+ rooms: Record<string, PlanRoom>;
161
+ }
162
+ export interface FloorPlan {
163
+ id: string;
164
+ name?: string;
165
+ /** Schema version, for future migrations. */
166
+ version: 1;
167
+ /** Display formatting only. Never read by geometry. */
168
+ unitSystem: PlanUnitSystem;
169
+ floors: PlanFloor[];
170
+ /**
171
+ * Bumped by every mutating operation. Use it as the memo key for `derivePlan`, so a
172
+ * gesture that does not actually change the graph does not trigger a re-derive.
173
+ */
174
+ revision: number;
175
+ }
176
+ /**
177
+ * A raw face from the planar walk, before room metadata is attached.
178
+ *
179
+ * WINDING: `outer` is positively wound under the shoelace formula, which in SVG's
180
+ * y-down space means it reads as visually CLOCKWISE. Anything downstream that assumes
181
+ * counter-clockwise (extruded slab normals, most triangulators) must flip it.
182
+ */
183
+ export interface PlanFace {
184
+ /**
185
+ * Derived from the boundary itself, so it is stable across derives for as long as
186
+ * the geometry is unchanged. Safe to use as a React key.
187
+ */
188
+ id: string;
189
+ /** Outer ring, closed implicitly: the first vertex is not repeated at the end. */
190
+ outer: PlanPoint[];
191
+ /** Inner rings from nested components, wound opposite to `outer`. */
192
+ holes: PlanPoint[][];
193
+ /** mm², always positive, with holes already subtracted. */
194
+ area: number;
195
+ /** mm, outer ring only. */
196
+ perimeter: number;
197
+ /**
198
+ * Wall ids on the boundary, in traversal order. A dangling wall inside this face
199
+ * appears TWICE, once per direction, so consumers needing unique ids must dedupe.
200
+ */
201
+ wallIds: string[];
202
+ nodeIds: string[];
203
+ }
204
+ /** A face with its matched metadata. This is what the components render. */
205
+ export interface DerivedRoom extends PlanFace {
206
+ /** `null` when no `PlanRoom` matched this face during this derive. */
207
+ roomId: string | null;
208
+ meta: PlanRoom | null;
209
+ /** Guaranteed inside `outer` and outside every hole. The label position. */
210
+ anchor: PlanPoint;
211
+ }
212
+ export declare enum PlanIssueKind {
213
+ ZeroLengthWall = "zeroLengthWall",
214
+ SelfLoopWall = "selfLoopWall",
215
+ DuplicateWall = "duplicateWall",
216
+ CoincidentNodes = "coincidentNodes",
217
+ /** Two walls cross with no node at the intersection. Corrupts face extraction. */
218
+ UnsplitCrossing = "unsplitCrossing",
219
+ /** A node sits on a wall's interior but that wall was never split there. */
220
+ UnsplitNodeOnWall = "unsplitNodeOnWall",
221
+ /** Two walls leave a node in the same direction, tying the angular sort. */
222
+ OverlappingWalls = "overlappingWalls",
223
+ OrphanNode = "orphanNode",
224
+ DanglingOpening = "danglingOpening",
225
+ /** A `PlanRoom` matched no face this derive. It is kept, never deleted. */
226
+ UnmatchedRoom = "unmatchedRoom",
227
+ /** An opening was moved or dropped because its wall no longer has room for it. */
228
+ OpeningDropped = "openingDropped"
229
+ }
230
+ export interface PlanIssue {
231
+ kind: PlanIssueKind;
232
+ message: string;
233
+ nodeIds?: string[];
234
+ wallIds?: string[];
235
+ roomIds?: string[];
236
+ openingIds?: string[];
237
+ }
238
+ export interface PlanDerivation {
239
+ /** The floor with room match caches and anchors refreshed. Pure: a new object. */
240
+ floor: PlanFloor;
241
+ rooms: DerivedRoom[];
242
+ issues: PlanIssue[];
243
+ }
244
+ /** The uniform result of every graph mutation. All of them are pure. */
245
+ export interface PlanEdit<T = void> {
246
+ floor: PlanFloor;
247
+ result: T;
248
+ issues: PlanIssue[];
249
+ }
250
+ /**
251
+ * The viewport: which world point sits at the canvas's top-left corner, and how many
252
+ * screen pixels one world millimetre occupies.
253
+ *
254
+ * A 6m plan filling a 600px canvas is `zoom: 0.1`. Note this is a transform on a group,
255
+ * not an SVG `viewBox`: handles, labels and hit tolerances are then sized in screen
256
+ * pixels and stay constant as you zoom, instead of scaling with the drawing.
257
+ */
258
+ export interface PlanView {
259
+ x: number;
260
+ y: number;
261
+ zoom: number;
262
+ }
263
+ /** What is currently selected or hovered. */
264
+ export type PlanSelection = {
265
+ type: 'room';
266
+ id: string;
267
+ } | {
268
+ type: 'wall';
269
+ id: string;
270
+ } | {
271
+ type: 'opening';
272
+ id: string;
273
+ } | {
274
+ type: 'node';
275
+ id: string;
276
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Public surface of the floor plan model.
3
+ *
4
+ * Explicit named exports, NOT `export *`: `scripts/gen-barrel.ts` forwards this folder
5
+ * straight into `package/index.ts`, so anything listed here becomes part of the
6
+ * published `@ahrowe/ui` API. Internal primitives (`cross`, `lerp`, the dart graph)
7
+ * stay unexported and are imported by path within this module.
8
+ */
9
+ export type { DerivedRoom, FloorPlan, PlanDerivation, PlanEdit, PlanFace, PlanFloor, PlanIssue, PlanNode, PlanOpening, PlanPoint, PlanRoom, PlanSelection, PlanView, PlanWall, } from './floorPlan.types';
10
+ export { DoorSwing, OpeningKind, PlanIssueKind, PlanUnitSystem, WallType } from './floorPlan.types';
11
+ export { addFloor, duplicateFloor, emptyFloor, emptyFloorPlan, floorBelow, floorBounds, floorIndex, getFloor, MIN_FLOOR_HEIGHT, moveFloor, removeFloor, renameFloor, setFloor, setFloorHeight, updateFloor, } from './floorPlan.plan';
12
+ export type { AddFloorOptions, EmptyFloorPlanOptions, PlanBounds } from './floorPlan.plan';
13
+ export { MIN_OPENING_WIDTH, MIN_WALL_LENGTH } from './floorPlan.geometry';
14
+ export { deriveFaces, nodeDegrees } from './floorPlan.faces';
15
+ export { commitDerivation, derivePlan, pruneUnmatchedRooms, roomAtPoint } from './floorPlan.rooms';
16
+ export type { CommitDerivationOptions } from './floorPlan.rooms';
17
+ export { HIT_PX, HIT_PX_COARSE, hitTest, selectionsEqual, worldTolerances } from './floorPlan.hit';
18
+ export type { HitTestOptions, HitTolerances } from './floorPlan.hit';
19
+ export { DEFAULT_WALL_ATTRS, addOpening, addRect, addWall, cleanupPlan, dissolveNode, mergeNodes, moveNode, removeOpening, removeWall, scalePlan, splitWall, transformPlan, updateOpening, validatePlan, wallLength, } from './floorPlan.graph';
20
+ export type { DissolveNodeOptions, ResolveNodeResult, SplitWallResult, WallAttrs, } from './floorPlan.graph';
21
+ export { DEFAULT_SNAP_OPTIONS, SnapKind, clientToWorld, resolveSnap, screenScale } from './floorPlan.snap';
22
+ export type { SnapContext, SnapGuide, SnapOptions, SnapResult } from './floorPlan.snap';
23
+ export { buildWallPaths, openingGeometry, wallCentrelineSpans } from './floorPlan.path';
24
+ export type { BuildWallPathsOptions, WallCapLine, WallCapReason, WallPathGroup, WallPathResult, } from './floorPlan.path';
25
+ export { PLAN_FORMAT_VERSION, planFromJson, planToJson } from './floorPlan.json';
26
+ export type { PlanParseResult } from './floorPlan.json';
27
+ export { planFromLegacySvg } from './legacySvg';
28
+ export type { LegacyImportOptions } from './legacySvg';
@@ -0,0 +1,27 @@
1
+ import { scalePlan } from './floorPlan.graph';
2
+ import { FloorPlan } from './floorPlan.types';
3
+ export interface LegacyImportOptions {
4
+ /**
5
+ * Multiplies every imported coordinate, converting the old unitless canvas into the
6
+ * module's millimetre invariant.
7
+ *
8
+ * There is no correct automatic value: the old format records no scale at all. The
9
+ * default of 1 keeps the numbers as they were, which makes a typical 400x300 export
10
+ * a 40cm plan. A 400-unit-wide export meant to be a 12m house wants `scale: 30`.
11
+ */
12
+ scale?: number;
13
+ /** Wall thickness for imported walls, in millimetres after scaling. */
14
+ thickness?: number;
15
+ /** Tolerance for welding coincident endpoints, in millimetres after scaling. */
16
+ weldTolerance?: number;
17
+ label?: string;
18
+ }
19
+ /**
20
+ * Parses a legacy exported SVG into a single-floor `FloorPlan`.
21
+ *
22
+ * Rooms keep their names. Doors are recovered where the old swing-arc path is
23
+ * recognisable, as `OpeningKind.Door`. Windows and archways did not exist in that
24
+ * format, so nothing is lost by not looking for them.
25
+ */
26
+ export declare function planFromLegacySvg(svg: string, options?: LegacyImportOptions): FloorPlan;
27
+ export { scalePlan };
@@ -0,0 +1,2 @@
1
+ export { default } from './planCanvas';
2
+ export * from './planCanvas.types';
@@ -0,0 +1,7 @@
1
+ import { PlanCanvasProps } from './planCanvas.types';
2
+ /**
3
+ * The ref lands on the `<svg>` itself, which is what a consumer wants for measuring or
4
+ * screenshotting. Both the drawer and the viewer expose it as `getSvgElement()`.
5
+ */
6
+ declare const PlanCanvas: import('react').ForwardRefExoticComponent<PlanCanvasProps & import('react').RefAttributes<SVGSVGElement>>;
7
+ export default PlanCanvas;
@@ -0,0 +1,62 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+ import { DerivedRoom, PlanFloor, PlanRoom, PlanSelection, PlanView } from '../floorPlan';
3
+ import { SlotClassNames, SlotStyles } from '../types/slots.types';
4
+ export type PlanCanvasSlots = 'canvas' | 'grid' | 'underlay' | 'rooms' | 'roomFill' | 'roomLabel' | 'walls' | 'wallBody' | 'openings' | 'opening' | 'overlay';
5
+ export interface PlanCanvasProps {
6
+ floor: PlanFloor;
7
+ /**
8
+ * A floor traced faintly behind this one, normally the storey below, so walls can be
9
+ * lined up between storeys. Drawn as wall centrelines only: it is a reference, not a
10
+ * second plan, and it takes no pointer events.
11
+ */
12
+ underlay?: PlanFloor | null;
13
+ /**
14
+ * Derived rooms, passed in rather than computed here so the parent controls
15
+ * memoisation and both components share one derivation per render.
16
+ */
17
+ rooms: DerivedRoom[];
18
+ view: PlanView;
19
+ /** Everything currently selected. A single selection is a one-element array. */
20
+ selection?: readonly PlanSelection[];
21
+ hovered?: PlanSelection | null;
22
+ /** Makes room fills focusable, clickable and screen-reader labelled. */
23
+ interactiveRooms?: boolean;
24
+ interactiveOpenings?: boolean;
25
+ showGrid?: boolean;
26
+ /** Grid spacing in millimetres. */
27
+ gridSize?: number;
28
+ showRoomLabels?: boolean;
29
+ /** Door leaves, swing arcs and window panes. `false` leaves plain gaps. */
30
+ showOpeningSymbols?: boolean;
31
+ getRoomColor?: (room: PlanRoom, area: number) => string | undefined;
32
+ renderRoomLabel?: (room: PlanRoom | null, context: {
33
+ area: number;
34
+ faceId: string;
35
+ }) => ReactNode;
36
+ /** Accessible name for a room. */
37
+ getRoomLabel?: (room: PlanRoom | null, context: {
38
+ area: number;
39
+ }) => string;
40
+ formatArea?: (areaMm2: number) => string;
41
+ onRoomActivate?: (roomId: string) => void;
42
+ onOpeningActivate?: (openingId: string) => void;
43
+ onBackgroundActivate?: () => void;
44
+ onHoverChange?: (target: PlanSelection | null) => void;
45
+ /**
46
+ * Extra SVG drawn above everything, in SCREEN space: handles, previews, badges.
47
+ * The drawer supplies its editing chrome through this.
48
+ */
49
+ overlay?: ReactNode;
50
+ /** Extra SVG drawn in WORLD space, below the screen-space overlay. */
51
+ worldOverlay?: ReactNode;
52
+ /** Width and height of the canvas in CSS pixels, measured by the parent. */
53
+ size: {
54
+ width: number;
55
+ height: number;
56
+ };
57
+ 'aria-label'?: string;
58
+ className?: string;
59
+ style?: CSSProperties;
60
+ classNames?: SlotClassNames<PlanCanvasSlots>;
61
+ styles?: SlotStyles<PlanCanvasSlots>;
62
+ }
@@ -1,3 +1,3 @@
1
- import { ReactElement } from 'react';
2
- import { RoomDrawerProps } from './roomDrawer.types';
3
- export default function RoomDrawer({ className }: RoomDrawerProps): ReactElement;
1
+ import { RoomDrawerHandle, RoomDrawerProps } from './roomDrawer.types';
2
+ declare const RoomDrawer: import('react').ForwardRefExoticComponent<RoomDrawerProps & import('react').RefAttributes<RoomDrawerHandle>>;
3
+ export default RoomDrawer;