@reekon-tools/boldr-cad 0.1.0 → 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 (555) hide show
  1. package/README.md +99 -16
  2. package/dist/app/edit.d.ts +12 -1
  3. package/dist/app/edit.js +96 -11
  4. package/dist/app/edit.js.map +1 -1
  5. package/dist/app/state.d.ts +31 -4
  6. package/dist/app/state.js +31 -2
  7. package/dist/app/state.js.map +1 -1
  8. package/dist/data/memory.d.ts +9 -1
  9. package/dist/data/memory.js +13 -8
  10. package/dist/data/memory.js.map +1 -1
  11. package/dist/data/provider.d.ts +30 -10
  12. package/dist/data/sync.d.ts +14 -3
  13. package/dist/data/sync.js +133 -66
  14. package/dist/data/sync.js.map +1 -1
  15. package/dist/editor/actions.d.ts +1 -2
  16. package/dist/editor/actions.js +391 -113
  17. package/dist/editor/actions.js.map +1 -1
  18. package/dist/editor/confirm.d.ts +23 -0
  19. package/dist/editor/confirm.js +35 -0
  20. package/dist/editor/confirm.js.map +1 -0
  21. package/dist/editor/elevation.d.ts +191 -90
  22. package/dist/editor/elevation.js +812 -616
  23. package/dist/editor/elevation.js.map +1 -1
  24. package/dist/editor/face.d.ts +73 -0
  25. package/dist/editor/face.js +269 -0
  26. package/dist/editor/face.js.map +1 -0
  27. package/dist/editor/files.d.ts +6 -0
  28. package/dist/editor/files.js +50 -4
  29. package/dist/editor/files.js.map +1 -1
  30. package/dist/editor/gestures.d.ts +18 -3
  31. package/dist/editor/gestures.js +145 -30
  32. package/dist/editor/gestures.js.map +1 -1
  33. package/dist/editor/keypad.d.ts +20 -5
  34. package/dist/editor/keypad.js +62 -15
  35. package/dist/editor/keypad.js.map +1 -1
  36. package/dist/editor/keys.js +36 -4
  37. package/dist/editor/keys.js.map +1 -1
  38. package/dist/editor/laser.d.ts +20 -4
  39. package/dist/editor/laser.js +104 -21
  40. package/dist/editor/laser.js.map +1 -1
  41. package/dist/editor/linkActions.d.ts +49 -0
  42. package/dist/editor/linkActions.js +210 -0
  43. package/dist/editor/linkActions.js.map +1 -0
  44. package/dist/editor/linkFollow.d.ts +2 -0
  45. package/dist/editor/linkFollow.js +53 -0
  46. package/dist/editor/linkFollow.js.map +1 -0
  47. package/dist/editor/linkWrite.d.ts +13 -0
  48. package/dist/editor/linkWrite.js +84 -0
  49. package/dist/editor/linkWrite.js.map +1 -0
  50. package/dist/editor/links.d.ts +84 -0
  51. package/dist/editor/links.js +130 -0
  52. package/dist/editor/links.js.map +1 -0
  53. package/dist/editor/markup.d.ts +29 -3
  54. package/dist/editor/markup.js +95 -10
  55. package/dist/editor/markup.js.map +1 -1
  56. package/dist/editor/mode.d.ts +40 -2
  57. package/dist/editor/mode.js +71 -4
  58. package/dist/editor/mode.js.map +1 -1
  59. package/dist/editor/ports.d.ts +38 -6
  60. package/dist/editor/ports.js +10 -1
  61. package/dist/editor/ports.js.map +1 -1
  62. package/dist/editor/project.d.ts +38 -6
  63. package/dist/editor/project.js +170 -27
  64. package/dist/editor/project.js.map +1 -1
  65. package/dist/editor/selection.d.ts +10 -0
  66. package/dist/editor/selection.js +62 -8
  67. package/dist/editor/selection.js.map +1 -1
  68. package/dist/editor/status.js +3 -0
  69. package/dist/editor/status.js.map +1 -1
  70. package/dist/editor/tooltips.d.ts +5 -0
  71. package/dist/editor/tooltips.js +48 -0
  72. package/dist/editor/tooltips.js.map +1 -0
  73. package/dist/editor/view3d.d.ts +17 -0
  74. package/dist/editor/view3d.js +135 -0
  75. package/dist/editor/view3d.js.map +1 -0
  76. package/dist/editor/view3dState.d.ts +39 -0
  77. package/dist/editor/view3dState.js +44 -0
  78. package/dist/editor/view3dState.js.map +1 -0
  79. package/dist/export/dxf.js +1 -1
  80. package/dist/export/dxf.js.map +1 -1
  81. package/dist/export/pdf.js +17 -12
  82. package/dist/export/pdf.js.map +1 -1
  83. package/dist/export/plan.d.ts +23 -6
  84. package/dist/export/plan.js +127 -44
  85. package/dist/export/plan.js.map +1 -1
  86. package/dist/index.d.ts +7 -1
  87. package/dist/index.js +2 -0
  88. package/dist/index.js.map +1 -1
  89. package/dist/model/constraints.js +15 -4
  90. package/dist/model/constraints.js.map +1 -1
  91. package/dist/model/demo.js +3 -3
  92. package/dist/model/demo.js.map +1 -1
  93. package/dist/model/elevation.d.ts +6 -9
  94. package/dist/model/elevation.js +5 -13
  95. package/dist/model/elevation.js.map +1 -1
  96. package/dist/model/faces.d.ts +58 -0
  97. package/dist/model/faces.js +197 -0
  98. package/dist/model/faces.js.map +1 -0
  99. package/dist/model/facesolve.d.ts +72 -0
  100. package/dist/model/facesolve.js +369 -0
  101. package/dist/model/facesolve.js.map +1 -0
  102. package/dist/model/history.d.ts +3 -2
  103. package/dist/model/history.js +17 -11
  104. package/dist/model/history.js.map +1 -1
  105. package/dist/model/images.d.ts +18 -0
  106. package/dist/model/images.js +26 -0
  107. package/dist/model/images.js.map +1 -0
  108. package/dist/model/links.d.ts +46 -0
  109. package/dist/model/links.js +73 -0
  110. package/dist/model/links.js.map +1 -0
  111. package/dist/model/markup.d.ts +46 -0
  112. package/dist/model/markup.js +71 -0
  113. package/dist/model/markup.js.map +1 -0
  114. package/dist/model/measurement.d.ts +11 -0
  115. package/dist/model/measurement.js +24 -0
  116. package/dist/model/measurement.js.map +1 -0
  117. package/dist/model/openings.d.ts +4 -0
  118. package/dist/model/openings.js +4 -0
  119. package/dist/model/openings.js.map +1 -1
  120. package/dist/model/planGeom.d.ts +72 -2
  121. package/dist/model/planGeom.js +184 -10
  122. package/dist/model/planGeom.js.map +1 -1
  123. package/dist/model/project.js +3 -2
  124. package/dist/model/project.js.map +1 -1
  125. package/dist/model/projectfile.js +2 -1
  126. package/dist/model/projectfile.js.map +1 -1
  127. package/dist/model/rooms.js +2 -2
  128. package/dist/model/rooms.js.map +1 -1
  129. package/dist/model/settings.d.ts +6 -0
  130. package/dist/model/settings.js +2 -2
  131. package/dist/model/settings.js.map +1 -1
  132. package/dist/model/sketch.d.ts +14 -0
  133. package/dist/model/sketch.js +143 -24
  134. package/dist/model/sketch.js.map +1 -1
  135. package/dist/model/smooth.d.ts +6 -0
  136. package/dist/model/smooth.js +52 -0
  137. package/dist/model/smooth.js.map +1 -0
  138. package/dist/model/store.d.ts +12 -0
  139. package/dist/model/store.js +35 -3
  140. package/dist/model/store.js.map +1 -1
  141. package/dist/model/types.d.ts +173 -8
  142. package/dist/model/wallfaces.d.ts +32 -0
  143. package/dist/model/wallfaces.js +133 -20
  144. package/dist/model/wallfaces.js.map +1 -1
  145. package/dist/model/wallshape.d.ts +15 -0
  146. package/dist/model/wallshape.js +89 -0
  147. package/dist/model/wallshape.js.map +1 -0
  148. package/dist/react/BottomBar.d.ts +9 -5
  149. package/dist/react/BottomBar.js +213 -24
  150. package/dist/react/BottomBar.js.map +1 -1
  151. package/dist/react/Buttons.d.ts +13 -2
  152. package/dist/react/Buttons.js +37 -5
  153. package/dist/react/Buttons.js.map +1 -1
  154. package/dist/react/CadEditor.d.ts +54 -4
  155. package/dist/react/CadEditor.js +79 -21
  156. package/dist/react/CadEditor.js.map +1 -1
  157. package/dist/react/ConfirmSheet.d.ts +4 -0
  158. package/dist/react/ConfirmSheet.js +31 -0
  159. package/dist/react/ConfirmSheet.js.map +1 -0
  160. package/dist/react/ConflictSheet.d.ts +2 -1
  161. package/dist/react/ConflictSheet.js +16 -8
  162. package/dist/react/ConflictSheet.js.map +1 -1
  163. package/dist/react/Drawer.d.ts +8 -2
  164. package/dist/react/Drawer.js +167 -38
  165. package/dist/react/Drawer.js.map +1 -1
  166. package/dist/react/Duck.d.ts +13 -0
  167. package/dist/react/Duck.js +45 -0
  168. package/dist/react/Duck.js.map +1 -0
  169. package/dist/react/Empty3D.d.ts +2 -0
  170. package/dist/react/Empty3D.js +39 -0
  171. package/dist/react/Empty3D.js.map +1 -0
  172. package/dist/react/FitScroll.d.ts +6 -0
  173. package/dist/react/FitScroll.js +14 -0
  174. package/dist/react/FitScroll.js.map +1 -0
  175. package/dist/react/Icon.native.d.ts +6 -0
  176. package/dist/react/Icon.native.js +4 -0
  177. package/dist/react/Icon.native.js.map +1 -1
  178. package/dist/react/Icon.web.d.ts +6 -0
  179. package/dist/react/Icon.web.js +4 -0
  180. package/dist/react/Icon.web.js.map +1 -1
  181. package/dist/react/KeypadSheet.d.ts +9 -2
  182. package/dist/react/KeypadSheet.js +174 -42
  183. package/dist/react/KeypadSheet.js.map +1 -1
  184. package/dist/react/LaserTray.js +8 -5
  185. package/dist/react/LaserTray.js.map +1 -1
  186. package/dist/react/MarkupPanel.d.ts +8 -0
  187. package/dist/react/MarkupPanel.js +67 -0
  188. package/dist/react/MarkupPanel.js.map +1 -0
  189. package/dist/react/PageThumb.native.d.ts +7 -2
  190. package/dist/react/PageThumb.native.js +19 -7
  191. package/dist/react/PageThumb.native.js.map +1 -1
  192. package/dist/react/PageThumb.web.d.ts +3 -2
  193. package/dist/react/PageThumb.web.js +4 -4
  194. package/dist/react/PageThumb.web.js.map +1 -1
  195. package/dist/react/PlanCanvas.native.d.ts +1 -1
  196. package/dist/react/PlanCanvas.native.js +134 -36
  197. package/dist/react/PlanCanvas.native.js.map +1 -1
  198. package/dist/react/PlanCanvas.web.js +16 -5
  199. package/dist/react/PlanCanvas.web.js.map +1 -1
  200. package/dist/react/Popovers.d.ts +12 -6
  201. package/dist/react/Popovers.js +53 -26
  202. package/dist/react/Popovers.js.map +1 -1
  203. package/dist/react/Rail.d.ts +24 -0
  204. package/dist/react/Rail.js +324 -34
  205. package/dist/react/Rail.js.map +1 -1
  206. package/dist/react/ShapeMenu.d.ts +7 -0
  207. package/dist/react/ShapeMenu.js +58 -0
  208. package/dist/react/ShapeMenu.js.map +1 -0
  209. package/dist/react/Sheet.d.ts +14 -4
  210. package/dist/react/Sheet.js +54 -9
  211. package/dist/react/Sheet.js.map +1 -1
  212. package/dist/react/Slider.d.ts +18 -0
  213. package/dist/react/Slider.js +27 -0
  214. package/dist/react/Slider.js.map +1 -0
  215. package/dist/react/Toast.js +14 -6
  216. package/dist/react/Toast.js.map +1 -1
  217. package/dist/react/ToolTip.d.ts +7 -0
  218. package/dist/react/ToolTip.js +43 -0
  219. package/dist/react/ToolTip.js.map +1 -0
  220. package/dist/react/TopBar.d.ts +6 -3
  221. package/dist/react/TopBar.js +48 -17
  222. package/dist/react/TopBar.js.map +1 -1
  223. package/dist/react/Tween.d.ts +7 -0
  224. package/dist/react/Tween.js +26 -0
  225. package/dist/react/Tween.js.map +1 -0
  226. package/dist/react/Tween.native.d.ts +6 -0
  227. package/dist/react/Tween.native.js +43 -0
  228. package/dist/react/Tween.native.js.map +1 -0
  229. package/dist/react/View3D.native.d.ts +4 -0
  230. package/dist/react/View3D.native.js +103 -0
  231. package/dist/react/View3D.native.js.map +1 -0
  232. package/dist/react/View3D.web.d.ts +4 -0
  233. package/dist/react/View3D.web.js +151 -0
  234. package/dist/react/View3D.web.js.map +1 -0
  235. package/dist/react/View3DHost.d.ts +143 -0
  236. package/dist/react/View3DHost.js +377 -0
  237. package/dist/react/View3DHost.js.map +1 -0
  238. package/dist/react/corners.d.ts +7 -0
  239. package/dist/react/corners.js +51 -0
  240. package/dist/react/corners.js.map +1 -0
  241. package/dist/react/easing.d.ts +15 -0
  242. package/dist/react/easing.js +10 -0
  243. package/dist/react/easing.js.map +1 -0
  244. package/dist/react/host.d.ts +7 -2
  245. package/dist/react/host.js +8 -3
  246. package/dist/react/host.js.map +1 -1
  247. package/dist/react/hostKeypad.d.ts +56 -0
  248. package/dist/react/hostKeypad.js +5 -0
  249. package/dist/react/hostKeypad.js.map +1 -0
  250. package/dist/react/motion.d.ts +77 -0
  251. package/dist/react/motion.js +173 -0
  252. package/dist/react/motion.js.map +1 -0
  253. package/dist/react/ports.js +90 -8
  254. package/dist/react/ports.js.map +1 -1
  255. package/dist/react/saving.d.ts +15 -0
  256. package/dist/react/saving.js +76 -0
  257. package/dist/react/saving.js.map +1 -0
  258. package/dist/react/store.d.ts +38 -1
  259. package/dist/react/store.js +31 -1
  260. package/dist/react/store.js.map +1 -1
  261. package/dist/react/theme.d.ts +10 -0
  262. package/dist/react/theme.js +8 -0
  263. package/dist/react/theme.js.map +1 -1
  264. package/dist/react/topRow.d.ts +22 -0
  265. package/dist/react/topRow.js +61 -0
  266. package/dist/react/topRow.js.map +1 -0
  267. package/dist/react/touch.d.ts +6 -0
  268. package/dist/react/touch.js +9 -0
  269. package/dist/react/touch.js.map +1 -0
  270. package/dist/render/ctx.d.ts +8 -0
  271. package/dist/render/draw.d.ts +33 -1
  272. package/dist/render/draw.js +593 -215
  273. package/dist/render/draw.js.map +1 -1
  274. package/dist/render/elevation.d.ts +65 -43
  275. package/dist/render/elevation.js +414 -140
  276. package/dist/render/elevation.js.map +1 -1
  277. package/dist/render/figmaIcons.d.ts +12 -0
  278. package/dist/render/figmaIcons.js +21 -0
  279. package/dist/render/figmaIcons.js.map +1 -0
  280. package/dist/render/hit.d.ts +22 -2
  281. package/dist/render/hit.js +168 -25
  282. package/dist/render/hit.js.map +1 -1
  283. package/dist/render/icons.js +69 -10
  284. package/dist/render/icons.js.map +1 -1
  285. package/dist/render/linkTile.d.ts +15 -0
  286. package/dist/render/linkTile.js +369 -0
  287. package/dist/render/linkTile.js.map +1 -0
  288. package/dist/render/markup.d.ts +11 -0
  289. package/dist/render/markup.js +112 -0
  290. package/dist/render/markup.js.map +1 -0
  291. package/dist/render/navigate.d.ts +88 -0
  292. package/dist/render/navigate.js +100 -0
  293. package/dist/render/navigate.js.map +1 -0
  294. package/dist/render/snap.d.ts +20 -1
  295. package/dist/render/snap.js +118 -25
  296. package/dist/render/snap.js.map +1 -1
  297. package/dist/render/squircle.d.ts +8 -0
  298. package/dist/render/squircle.js +71 -0
  299. package/dist/render/squircle.js.map +1 -0
  300. package/dist/render/view.d.ts +115 -2
  301. package/dist/render/view.js +244 -19
  302. package/dist/render/view.js.map +1 -1
  303. package/dist/skia/SkiaDrawCtx.d.ts +30 -2
  304. package/dist/skia/SkiaDrawCtx.js +198 -43
  305. package/dist/skia/SkiaDrawCtx.js.map +1 -1
  306. package/dist/skia/host.d.ts +18 -2
  307. package/dist/skia/host.js +46 -1
  308. package/dist/skia/host.js.map +1 -1
  309. package/dist/solver/equations.js +37 -2
  310. package/dist/solver/equations.js.map +1 -1
  311. package/dist/solver/gauss-newton.d.ts +17 -0
  312. package/dist/solver/gauss-newton.js +144 -36
  313. package/dist/solver/gauss-newton.js.map +1 -1
  314. package/dist/solver/linalg.d.ts +45 -2
  315. package/dist/solver/linalg.js +164 -10
  316. package/dist/solver/linalg.js.map +1 -1
  317. package/dist/solver/types.d.ts +5 -0
  318. package/dist/tools/annotate.d.ts +57 -1
  319. package/dist/tools/annotate.js +64 -43
  320. package/dist/tools/annotate.js.map +1 -1
  321. package/dist/tools/chamfer.d.ts +12 -0
  322. package/dist/tools/chamfer.js +114 -0
  323. package/dist/tools/chamfer.js.map +1 -0
  324. package/dist/tools/corner.d.ts +38 -0
  325. package/dist/tools/corner.js +99 -0
  326. package/dist/tools/corner.js.map +1 -0
  327. package/dist/tools/dim.d.ts +86 -9
  328. package/dist/tools/dim.js +582 -97
  329. package/dist/tools/dim.js.map +1 -1
  330. package/dist/tools/elevation.js +5 -5
  331. package/dist/tools/elevation.js.map +1 -1
  332. package/dist/tools/fillet.d.ts +9 -0
  333. package/dist/tools/fillet.js +86 -0
  334. package/dist/tools/fillet.js.map +1 -0
  335. package/dist/tools/freehand.d.ts +3 -2
  336. package/dist/tools/freehand.js +19 -12
  337. package/dist/tools/freehand.js.map +1 -1
  338. package/dist/tools/index.js +14 -4
  339. package/dist/tools/index.js.map +1 -1
  340. package/dist/tools/line.d.ts +8 -3
  341. package/dist/tools/line.js +66 -30
  342. package/dist/tools/line.js.map +1 -1
  343. package/dist/tools/modify.d.ts +13 -0
  344. package/dist/tools/modify.js +105 -25
  345. package/dist/tools/modify.js.map +1 -1
  346. package/dist/tools/openings.js +25 -18
  347. package/dist/tools/openings.js.map +1 -1
  348. package/dist/tools/registry.d.ts +8 -0
  349. package/dist/tools/registry.js +42 -2
  350. package/dist/tools/registry.js.map +1 -1
  351. package/dist/tools/select.d.ts +77 -3
  352. package/dist/tools/select.js +560 -74
  353. package/dist/tools/select.js.map +1 -1
  354. package/dist/tools/shapes.d.ts +14 -0
  355. package/dist/tools/shapes.js +154 -75
  356. package/dist/tools/shapes.js.map +1 -1
  357. package/dist/units/format.d.ts +11 -3
  358. package/dist/units/format.js +27 -6
  359. package/dist/units/format.js.map +1 -1
  360. package/dist/units/parse.d.ts +2 -1
  361. package/dist/units/parse.js +5 -4
  362. package/dist/units/parse.js.map +1 -1
  363. package/dist/units/prefs.d.ts +9 -1
  364. package/dist/units/prefs.js +6 -0
  365. package/dist/units/prefs.js.map +1 -1
  366. package/dist/view3d/build.d.ts +35 -0
  367. package/dist/view3d/build.js +630 -0
  368. package/dist/view3d/build.js.map +1 -0
  369. package/dist/view3d/camera.d.ts +60 -0
  370. package/dist/view3d/camera.js +84 -0
  371. package/dist/view3d/camera.js.map +1 -0
  372. package/dist/view3d/fatLines.d.ts +11 -0
  373. package/dist/view3d/fatLines.js +83 -0
  374. package/dist/view3d/fatLines.js.map +1 -0
  375. package/dist/view3d/lines.d.ts +17 -0
  376. package/dist/view3d/lines.js +271 -0
  377. package/dist/view3d/lines.js.map +1 -0
  378. package/dist/view3d/mesh.d.ts +51 -0
  379. package/dist/view3d/mesh.js +101 -0
  380. package/dist/view3d/mesh.js.map +1 -0
  381. package/dist/view3d/poly.d.ts +16 -0
  382. package/dist/view3d/poly.js +109 -0
  383. package/dist/view3d/poly.js.map +1 -0
  384. package/dist/view3d/viewer.d.ts +86 -0
  385. package/dist/view3d/viewer.js +350 -0
  386. package/dist/view3d/viewer.js.map +1 -0
  387. package/package.json +15 -2
  388. package/src/app/edit.ts +83 -12
  389. package/src/app/main.ts +11 -5
  390. package/src/app/state.ts +38 -5
  391. package/src/data/memory.ts +15 -10
  392. package/src/data/provider.ts +25 -10
  393. package/src/data/sync.ts +107 -53
  394. package/src/editor/actions.ts +292 -83
  395. package/src/editor/confirm.ts +58 -0
  396. package/src/editor/elevation.ts +698 -478
  397. package/src/editor/face.ts +227 -0
  398. package/src/editor/files.ts +40 -4
  399. package/src/editor/gestures.ts +147 -31
  400. package/src/editor/keypad.ts +67 -18
  401. package/src/editor/keys.ts +25 -5
  402. package/src/editor/laser.ts +98 -20
  403. package/src/editor/linkActions.ts +194 -0
  404. package/src/editor/linkFollow.ts +43 -0
  405. package/src/editor/linkWrite.ts +77 -0
  406. package/src/editor/links.ts +181 -0
  407. package/src/editor/markup.ts +92 -8
  408. package/src/editor/mode.ts +83 -7
  409. package/src/editor/ports.ts +39 -6
  410. package/src/editor/project.ts +157 -26
  411. package/src/editor/selection.ts +54 -8
  412. package/src/editor/status.ts +2 -0
  413. package/src/editor/tooltips.ts +47 -0
  414. package/src/editor/view3d.ts +119 -0
  415. package/src/editor/view3dState.ts +61 -0
  416. package/src/export/dxf.ts +1 -1
  417. package/src/export/pdf.ts +15 -7
  418. package/src/export/plan.ts +119 -35
  419. package/src/index.ts +7 -1
  420. package/src/input/keyboard.ts +2 -4
  421. package/src/model/constraints.ts +13 -4
  422. package/src/model/demo.ts +3 -3
  423. package/src/model/elevation.ts +6 -16
  424. package/src/model/faces.ts +153 -0
  425. package/src/model/facesolve.ts +322 -0
  426. package/src/model/history.ts +19 -11
  427. package/src/model/images.ts +35 -0
  428. package/src/model/links.ts +77 -0
  429. package/src/model/markup.ts +80 -0
  430. package/src/model/measurement.ts +26 -0
  431. package/src/model/openings.ts +6 -0
  432. package/src/model/planGeom.ts +174 -8
  433. package/src/model/project.ts +3 -2
  434. package/src/model/projectfile.ts +2 -1
  435. package/src/model/rooms.ts +1 -1
  436. package/src/model/settings.ts +8 -2
  437. package/src/model/sketch.ts +101 -14
  438. package/src/model/smooth.ts +51 -0
  439. package/src/model/store.ts +35 -4
  440. package/src/model/types.ts +170 -8
  441. package/src/model/wallfaces.ts +126 -17
  442. package/src/model/wallshape.ts +97 -0
  443. package/src/react/BottomBar.tsx +263 -46
  444. package/src/react/Buttons.tsx +50 -9
  445. package/src/react/CadEditor.tsx +142 -34
  446. package/src/react/ConfirmSheet.tsx +38 -0
  447. package/src/react/ConflictSheet.tsx +19 -12
  448. package/src/react/Drawer.tsx +213 -110
  449. package/src/react/Duck.tsx +50 -0
  450. package/src/react/Empty3D.tsx +32 -0
  451. package/src/react/FitScroll.tsx +19 -0
  452. package/src/react/Icon.native.tsx +5 -0
  453. package/src/react/Icon.web.tsx +5 -0
  454. package/src/react/KeypadSheet.tsx +253 -60
  455. package/src/react/LaserTray.tsx +12 -8
  456. package/src/react/MarkupPanel.tsx +150 -0
  457. package/src/react/PageThumb.native.tsx +18 -7
  458. package/src/react/PageThumb.web.tsx +4 -4
  459. package/src/react/PlanCanvas.native.tsx +151 -34
  460. package/src/react/PlanCanvas.web.tsx +15 -5
  461. package/src/react/Popovers.tsx +116 -51
  462. package/src/react/Rail.tsx +360 -27
  463. package/src/react/ShapeMenu.tsx +66 -0
  464. package/src/react/Sheet.tsx +56 -11
  465. package/src/react/Slider.tsx +48 -0
  466. package/src/react/Toast.tsx +16 -9
  467. package/src/react/ToolTip.tsx +49 -0
  468. package/src/react/TopBar.tsx +97 -39
  469. package/src/react/Tween.native.tsx +36 -0
  470. package/src/react/Tween.tsx +24 -0
  471. package/src/react/View3D.native.tsx +131 -0
  472. package/src/react/View3D.web.tsx +131 -0
  473. package/src/react/View3DHost.ts +367 -0
  474. package/src/react/corners.ts +52 -0
  475. package/src/react/easing.ts +14 -0
  476. package/src/react/host.ts +8 -3
  477. package/src/react/hostKeypad.ts +66 -0
  478. package/src/react/motion.tsx +239 -0
  479. package/src/react/ports.ts +78 -9
  480. package/src/react/saving.ts +80 -0
  481. package/src/react/store.ts +35 -3
  482. package/src/react/theme.ts +8 -0
  483. package/src/react/topRow.ts +71 -0
  484. package/src/react/touch.ts +10 -0
  485. package/src/render/ctx.ts +8 -0
  486. package/src/render/draw.ts +437 -175
  487. package/src/render/elevation.ts +349 -143
  488. package/src/render/figmaIcons.ts +22 -0
  489. package/src/render/hit.ts +140 -21
  490. package/src/render/icons.ts +69 -10
  491. package/src/render/linkTile.ts +297 -0
  492. package/src/render/markup.ts +91 -0
  493. package/src/render/navigate.ts +114 -0
  494. package/src/render/snap.ts +105 -22
  495. package/src/render/squircle.ts +73 -0
  496. package/src/render/view.ts +255 -19
  497. package/src/skia/SkiaDrawCtx.ts +163 -41
  498. package/src/skia/host.ts +46 -1
  499. package/src/solver/equations.ts +37 -2
  500. package/src/solver/gauss-newton.ts +125 -28
  501. package/src/solver/linalg.ts +141 -5
  502. package/src/solver/types.ts +5 -0
  503. package/src/styles.css +91 -42
  504. package/src/tools/annotate.ts +58 -29
  505. package/src/tools/chamfer.ts +103 -0
  506. package/src/tools/corner.ts +99 -0
  507. package/src/tools/dim.ts +525 -93
  508. package/src/tools/elevation.ts +5 -5
  509. package/src/tools/fillet.ts +82 -0
  510. package/src/tools/freehand.ts +20 -16
  511. package/src/tools/index.ts +17 -4
  512. package/src/tools/line.ts +59 -23
  513. package/src/tools/modify.ts +86 -24
  514. package/src/tools/openings.ts +11 -16
  515. package/src/tools/registry.ts +27 -2
  516. package/src/tools/select.ts +424 -75
  517. package/src/tools/shapes.ts +119 -58
  518. package/src/ui/ctx.ts +2 -1
  519. package/src/ui/domPorts.ts +23 -4
  520. package/src/ui/drawer.ts +6 -26
  521. package/src/ui/elevation.ts +10 -101
  522. package/src/ui/keypad.ts +8 -14
  523. package/src/ui/laser.ts +2 -2
  524. package/src/ui/menu.ts +63 -17
  525. package/src/ui/noteinput.ts +3 -3
  526. package/src/ui/opacity.ts +108 -0
  527. package/src/ui/palette.ts +67 -12
  528. package/src/ui/rail.ts +21 -7
  529. package/src/ui/topbar.ts +4 -14
  530. package/src/units/format.ts +24 -6
  531. package/src/units/parse.ts +4 -3
  532. package/src/units/prefs.ts +13 -1
  533. package/src/view3d/build.ts +607 -0
  534. package/src/view3d/camera.ts +113 -0
  535. package/src/view3d/fatLines.ts +83 -0
  536. package/src/view3d/lines.ts +258 -0
  537. package/src/view3d/mesh.ts +111 -0
  538. package/src/view3d/poly.ts +104 -0
  539. package/src/view3d/viewer.ts +348 -0
  540. package/dist/react/ElevCanvas.native.d.ts +0 -3
  541. package/dist/react/ElevCanvas.native.js +0 -58
  542. package/dist/react/ElevCanvas.native.js.map +0 -1
  543. package/dist/react/ElevCanvas.web.d.ts +0 -4
  544. package/dist/react/ElevCanvas.web.js +0 -44
  545. package/dist/react/ElevCanvas.web.js.map +0 -1
  546. package/dist/react/ElevationSheet.d.ts +0 -2
  547. package/dist/react/ElevationSheet.js +0 -38
  548. package/dist/react/ElevationSheet.js.map +0 -1
  549. package/dist/render/elevationView.d.ts +0 -3
  550. package/dist/render/elevationView.js +0 -100
  551. package/dist/render/elevationView.js.map +0 -1
  552. package/src/react/ElevCanvas.native.tsx +0 -68
  553. package/src/react/ElevCanvas.web.tsx +0 -42
  554. package/src/react/ElevationSheet.tsx +0 -88
  555. package/src/render/elevationView.ts +0 -73
@@ -1,51 +1,24 @@
1
- import { ed } from '../app/state';
2
- import { elevLines, elevLineUZ, elevOpenings, toU } from '../model/elevation';
3
- import { edgeS, openingSpan, pickBetween, refS, sameRef } from '../model/openings';
1
+ import { ed, isSel } from '../app/state';
2
+ import { measure } from '../model/constraints';
3
+ import { elevOpenings, toU } from '../model/elevation';
4
+ import { cabPoints, cornerAnchor, emptyFace, openingAnchor } from '../model/faces';
5
+ import { edgeS, openingSpan, pickBetween, refS, sameRef, widthEntered } from '../model/openings';
4
6
  import { fmtLen } from '../units';
5
- import { chip } from './draw';
7
+ import { chip, linkBlue, linkMark } from './draw';
6
8
  import { FONT, TH } from './theme';
7
9
  import { S } from './view';
8
- export const elevT = (v, W, H) => ({ s: v.s, ox: W / 2 - v.cu * v.s, oy: H / 2 + v.cz * v.s });
9
- /** Screen room taken by dimensions around the wall, pixels: below the floor, right of the face. */
10
- const DIM_BELOW = 92, DIM_RIGHT = 70, PAD = 36;
11
- /**
12
- * Fit the wall to a W × H view, leaving pixel margins for the dimensions and for anything overlaid
13
- * (`keep`: space taken at the top or the right, e.g. the key plan).
14
- */
15
- export function elevFit(f, W, H, keep = {}) {
16
- const u0 = Math.min(f.face[0], ...f.strips.map(s => s[0])), u1 = Math.max(f.face[1], ...f.strips.map(s => s[1]));
17
- const w = u1 - u0, h = f.H, top = keep.top || 0, right = Math.max(DIM_RIGHT, keep.right || 0), left = keep.left || 0, below = DIM_BELOW + (keep.bottom || 0);
18
- const s = Math.max(0.05, Math.min((W - 2 * PAD - right - left) / (w || 1), (H - 2 * PAD - below - top) / (h || 1)));
19
- // centre the drawing in the space left after the margins
20
- return { s, cu: (u0 + u1) / 2 + (right - left) / 2 / s, cz: h / 2 - (below - top) / 2 / s };
21
- }
22
10
  const LOOK = (look) => look === 'drive' ? { bg: TH.blue, fg: '#fff', line: TH.blue } : look === 'bad' ? { bg: TH.red, fg: '#fff', line: TH.red }
23
11
  : look === 'linked' ? { bg: TH.bg, fg: linkBlue(), line: linkBlue() }
24
12
  : look === 'ref' ? { bg: TH.ref, fg: TH.reftext, line: TH.chipline } : { bg: TH.chip, fg: TH.ink, line: TH.ink2 };
25
- /** Blue for outlined (linked) chips: the lighter blue on a dark canvas, so the text stays readable. */
26
- function linkBlue() {
27
- const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(TH.bg);
28
- const dark = !!m && (parseInt(m[1], 16) * 299 + parseInt(m[2], 16) * 587 + parseInt(m[3], 16) * 114) / 1000 < 128;
29
- return dark ? TH.lblue : TH.blue;
30
- }
31
- /** A small chain-link mark centred on (x, y). */
32
- function linkMark(c, x, y, color) {
33
- c.save();
34
- c.translate(x, y);
35
- c.rotate(-Math.PI / 4);
36
- c.strokeStyle = color;
37
- c.lineWidth = 1.4;
38
- c.beginPath();
39
- c.roundRect(-6.5, -2.25, 7.5, 4.5, 2.25);
40
- c.roundRect(-1, -2.25, 7.5, 4.5, 2.25);
41
- c.stroke();
42
- c.restore();
43
- }
44
13
  /**
45
14
  * Linear dimension between screen points p and q, its line offset by (nx, ny) pixels: ticks, extension lines and
46
15
  * the value chip. Returns the chip's box (for taps). `force` draws the chip even for a zero-length dimension.
47
16
  */
48
- function dim(c, p, q, nx, ny, label, look = 'free', force = false, selected = false) {
17
+ /**
18
+ * A dimension: extension lines, the dimension line with 45° ticks, the chip. `dashed`: a number that holds nothing (only
19
+ * measured, or a reference) — its dimension line dashed and its value in parentheses, as in the plan.
20
+ */
21
+ function dim(c, p, q, nx, ny, label, look = 'free', force = false, selected = false, dashed = false) {
49
22
  const len = Math.hypot(q[0] - p[0], q[1] - p[1]);
50
23
  if (len < 2 && !force)
51
24
  return null;
@@ -59,8 +32,20 @@ function dim(c, p, q, nx, ny, label, look = 'free', force = false, selected = fa
59
32
  c.lineTo(e[0] + (nx / L) * 5, e[1] + (ny / L) * 5);
60
33
  }
61
34
  if (len >= 2) {
62
- c.moveTo(a[0], a[1]);
63
- c.lineTo(b[0], b[1]);
35
+ if (dashed) {
36
+ c.stroke();
37
+ c.beginPath();
38
+ c.setLineDash([3, 3]);
39
+ c.moveTo(a[0], a[1]);
40
+ c.lineTo(b[0], b[1]);
41
+ c.stroke();
42
+ c.beginPath();
43
+ c.setLineDash([]);
44
+ }
45
+ else {
46
+ c.moveTo(a[0], a[1]);
47
+ c.lineTo(b[0], b[1]);
48
+ }
64
49
  // 45° architectural ticks
65
50
  const ux = (b[0] - a[0]) / len, uy = (b[1] - a[1]) / len;
66
51
  const tx = (ux + nx / L) * 4, ty = (uy + ny / L) * 4;
@@ -72,7 +57,8 @@ function dim(c, p, q, nx, ny, label, look = 'free', force = false, selected = fa
72
57
  c.stroke();
73
58
  c.restore();
74
59
  const cx = (a[0] + b[0]) / 2 + (len < 2 ? (nx / L) * 4 : 0), cy = (a[1] + b[1]) / 2 - (len < 2 ? 12 : 0), h = 20;
75
- const linked = look === 'linked', text = look === 'ref' ? '(' + label + ')' : linked ? '\u2007\u2007' + label : label; // (figure spaces: room for the link mark)
60
+ // a number that holds nothing (dashed) reads in parentheses, as in the plan
61
+ const linked = look === 'linked', text = dashed ? '(' + label + ')' : linked ? '  ' + label : label; // (figure spaces: room for the link mark)
76
62
  const w = chip(c, text, cx, cy, { bg: st.bg, fg: st.fg, font: '600 11.5px', h, pad: 6, border: selected ? [TH.yellow, 2] : linked ? [st.line, 1.5] : null });
77
63
  if (linked)
78
64
  linkMark(c, cx - w / 2 + 11, cy, st.fg);
@@ -131,50 +117,96 @@ function drawOpening(c, T, o) {
131
117
  c.fillText(o.type === 'door' ? 'Door' : 'Window', (x0 + x1) / 2, o.type === 'door' ? yTop + h * 0.3 : (yTop + yBot) / 2);
132
118
  c.restore();
133
119
  }
134
- /** Draw the elevation of frame `f` into a W × H (CSS pixel) canvas context. */
135
- /** `selected`: the selected dimension's action (JSON), drawn with the yellow selection border. */
136
- export function drawElevation(c, W, H, v, sk, f, selected = null) {
137
- const T = elevT(v, W, H);
120
+ /** A cabinet's tint and panel lines: neutral grey (no yellow — that's the selection's), against a window's blue glass. */
121
+ const WOOD = 'rgba(128,128,128,.14)', WOOD_LINE = 'rgba(128,128,128,.9)';
122
+ /**
123
+ * A cabinet, as cabinetry is drawn in elevation: a wood-tinted box, a drawer across its top with a pull, and a door
124
+ * below it with a handle — so it reads as a cabinet at a glance, not a window. Its name and size sit on the door.
125
+ */
126
+ function drawCabinet(c, T, x, y, w, h, name) {
127
+ const pad = Math.min(1.5 * T.s, w / 10, h / 10), drawer = Math.min(6 * T.s, h * 0.24);
128
+ c.save();
138
129
  c.fillStyle = TH.bg;
139
- c.fillRect(0, 0, W, H);
130
+ c.fillRect(x, y, w, h);
131
+ c.fillStyle = WOOD;
132
+ c.fillRect(x, y, w, h);
133
+ c.strokeStyle = TH.ink;
134
+ c.lineWidth = 1.5;
135
+ c.strokeRect(x, y, w, h);
136
+ // the drawer front and the door, inset in the box
137
+ const ix = x + pad, iw = w - 2 * pad, dy = y + pad, dh = drawer - pad, oy = y + drawer + pad * 0.5, oh = h - drawer - pad * 1.5;
138
+ c.strokeStyle = WOOD_LINE;
139
+ c.lineWidth = 1;
140
+ if (dh > 3)
141
+ c.strokeRect(ix, dy, iw, dh);
142
+ if (oh > 6)
143
+ c.strokeRect(ix, oy, iw, oh);
144
+ // a pull on the drawer, a handle at the top of the door on its right
145
+ c.strokeStyle = TH.ink;
146
+ c.lineWidth = 2;
147
+ c.lineCap = 'round';
148
+ c.beginPath();
149
+ if (dh > 6 && iw > 16) {
150
+ const pw = Math.min(iw * 0.3, 4 * T.s);
151
+ c.moveTo(ix + iw / 2 - pw / 2, dy + dh / 2);
152
+ c.lineTo(ix + iw / 2 + pw / 2, dy + dh / 2);
153
+ }
154
+ if (oh > 24 && iw > 16) {
155
+ const hx = ix + iw - Math.max(5, pad * 2), hl = Math.min(oh * 0.18, 4 * T.s);
156
+ c.moveTo(hx, oy + Math.max(6, pad * 1.5));
157
+ c.lineTo(hx, oy + Math.max(6, pad * 1.5) + hl);
158
+ }
159
+ c.stroke();
160
+ // its name, on the door (its sizes are in the bar when it's picked: on the drawing they'd be noise)
161
+ c.fillStyle = TH.ink2;
162
+ c.textAlign = 'center';
163
+ c.textBaseline = 'middle';
164
+ const cx = x + w / 2, cy = oy + oh / 2;
165
+ if (w > 44 && oh > 22) {
166
+ c.font = '600 11px ' + FONT;
167
+ c.fillText(name, cx, cy);
168
+ }
169
+ c.restore();
170
+ }
171
+ /** A cabinet's extent on the face. */
172
+ export function cabBox(face, id) {
173
+ const cb = face.cabs?.[id], ps = cb ? cabPoints(cb).map(p => face.points[p]) : [];
174
+ if (!cb || ps.some(p => !p))
175
+ return null;
176
+ return { u0: Math.min(...ps.map(p => p.x)), u1: Math.max(...ps.map(p => p.x)), z0: Math.min(...ps.map(p => p.y)), z1: Math.max(...ps.map(p => p.y)) };
177
+ }
178
+ /** A cabinet's left and right bottom corners as seen. */
179
+ const cabSides = (face, id) => {
180
+ const cb = face.cabs[id];
181
+ return face.points[cb.bl].x <= face.points[cb.br].x ? [cb.bl, cb.br] : [cb.br, cb.bl];
182
+ };
183
+ /**
184
+ * Under what's drawn on the face: the walls cut at its corners, the face, its doors and windows, its cabinets (a door
185
+ * panel and their name and size; their edges are the face's lines, drawn over this), the face's outline and the floor.
186
+ * (The selected door, window or cabinet's outline: drawWallSelection, over all of it.)
187
+ */
188
+ export function drawWallBackdrop(c, T, sk, f, face) {
140
189
  const [fx0, fyTop] = S(T, f.face[0], f.H), [fx1, fyBot] = S(T, f.face[1], 0);
141
- // walls cut at the corners, then the face
142
190
  for (const [a, b] of f.strips) {
143
191
  const [x0] = S(T, a, 0), [x1] = S(T, b, 0);
144
192
  hatch(c, x0, fyTop, x1 - x0, fyBot - fyTop);
145
193
  }
146
194
  c.fillStyle = TH.room;
147
195
  c.fillRect(fx0, fyTop, fx1 - fx0, fyBot - fyTop);
148
- const ops = elevOpenings(sk, f);
149
- const hits = [];
150
- // the face's end edges, as tap targets for the corners (first, so openings and chips on top win)
151
- for (const [u, end] of [[f.face[0], f.leftEnd], [f.face[1], f.leftEnd === 'a' ? 'b' : 'a']]) {
152
- const [x, yTop] = S(T, u, f.H), [, yBot] = S(T, u, 0);
153
- hits.push({ x: x - 10, y: yTop, w: 20, h: yBot - yTop, act: { k: 'corner', end } });
154
- }
155
- for (const o of ops) {
196
+ for (const o of elevOpenings(sk, f))
156
197
  drawOpening(c, T, o);
157
- const [x0, y0] = S(T, o.u0, o.head), [x1, y1] = S(T, o.u1, o.sill);
158
- hits.push({ x: x0, y: y0, w: x1 - x0, h: y1 - y0, act: { k: 'body', op: o.id } }); // first, so chips on top win taps
198
+ for (const [id, cb] of Object.entries(face.cabs ?? {})) {
199
+ const b = cabBox(face, id);
200
+ if (!b)
201
+ continue;
202
+ const [x0, y0] = S(T, b.u0, b.z1), [x1, y1] = S(T, b.u1, b.z0);
203
+ drawCabinet(c, T, x0, y0, x1 - x0, y1 - y0, cb.label || 'Cabinet');
159
204
  }
160
- // lines drawn on this face: solid, or dashed guides
161
205
  c.save();
162
- c.lineCap = 'round';
163
- for (const ln of elevLines(sk, f)) {
164
- const [a, b] = elevLineUZ(f, ln), p = S(T, a[0], a[1]), q = S(T, b[0], b[1]);
165
- c.strokeStyle = ln.guide ? TH.constr : TH.ink;
166
- c.lineWidth = ln.guide ? 1.5 : 2;
167
- c.setLineDash(ln.guide ? [6, 5] : []);
168
- c.beginPath();
169
- c.moveTo(p[0], p[1]);
170
- c.lineTo(q[0], q[1]);
171
- c.stroke();
172
- }
173
- c.restore();
174
206
  c.strokeStyle = TH.ink;
175
207
  c.lineWidth = 2;
176
208
  c.strokeRect(fx0, fyTop, fx1 - fx0, fyBot - fyTop);
177
- // floor line across everything
209
+ // the floor line, across the cut walls too
178
210
  const u0 = Math.min(f.face[0], ...f.strips.map(s => s[0])), u1 = Math.max(f.face[1], ...f.strips.map(s => s[1]));
179
211
  const [gx0] = S(T, u0, 0), [gx1] = S(T, u1, 0);
180
212
  c.lineWidth = 3;
@@ -182,80 +214,324 @@ export function drawElevation(c, W, H, v, sk, f, selected = null) {
182
214
  c.moveTo(gx0 - 14, fyBot);
183
215
  c.lineTo(gx1 + 14, fyBot);
184
216
  c.stroke();
185
- // dimensions (all tappable but the overall length): a chain along the floor (corner → openings → corner),
186
- // the overall face, the wall height, and each opening's sill and height
187
- /** a dimension chip with its tap action; the selected one gets the plan's yellow border */
188
- const tag = (p, q, nx, ny, label, look, act, force = false) => {
189
- const box = dim(c, p, q, nx, ny, label, look, force, selected === JSON.stringify(act));
190
- if (box)
191
- hits.push({ ...box, act, look });
217
+ c.restore();
218
+ }
219
+ /**
220
+ * The selected door, window or cabinet (and the one under the pointer, fainter): a yellow outline right on its edges,
221
+ * drawn over everything else on the wall — its own edges and its neighbours' — so the whole of it shows, even where
222
+ * it shares a side with another cabinet, or lines up with a window.
223
+ */
224
+ export function drawWallSelection(c, T, sk, f, face) {
225
+ const lit = (type, id) => isSel(type, id) || (ed.hover?.type === type && ed.hover.id === id);
226
+ const ring = (u0, z1, u1, z0, selected) => {
227
+ const [x0, y0] = S(T, u0, z1), [x1, y1] = S(T, u1, z0);
228
+ c.save();
229
+ c.strokeStyle = TH.yellow;
230
+ c.globalAlpha = selected ? 1 : 0.5;
231
+ c.lineWidth = 3;
232
+ c.lineJoin = 'miter';
233
+ c.strokeRect(x0, y0, x1 - x0, y1 - y0);
234
+ c.restore();
192
235
  };
193
- const { segs } = chain(sk, f), lookOf = (d) => (!d ? 'free' : ed.badOpeningDims.has(d.id) ? 'bad' : d.driving ? 'drive' : 'ref');
194
- for (const g of segs) {
195
- const p = S(T, g.u0, 0), q = S(T, g.u1, 0), text = fmtLen(g.u1 - g.u0);
196
- if (g.act.k === 'w')
197
- tag(p, q, 0, ROW0, text, 'drive', g.act);
198
- else
199
- tag(p, q, 0, ROW0, text, lookOf(g.dim), g.act);
200
- }
201
- // saved dimensions that aren't one step of the string (e.g. window → door across another window, or from a
202
- // plan wall point): each on its own row below it, so it can be seen, selected and deleted
203
- const extra = extraDims(sk, f);
204
- for (const x of extra)
205
- tag(S(T, x.u0, 0), S(T, x.u1, 0), 0, ROW0 + ROW * (x.row + 1), fmtLen(x.u1 - x.u0), lookOf(x.d), x.act);
206
- const rows = extra.reduce((m, x) => Math.max(m, x.row + 1), 0);
207
- // the wall's length comes from the floor plan: linked (outlined blue) when a dimension locks it there, grey when free
208
- const planLocked = Object.values(sk.cons).some(cn => cn.type === 'dist' && cn.line === f.lid && cn.driving !== false && cn.layer !== 'print');
209
- tag(S(T, f.face[0], 0), S(T, f.face[1], 0), 0, segs.length || rows ? ROW0 + ROW * (rows + 1) : ROW0, fmtLen(f.face[1] - f.face[0]), planLocked ? 'linked' : 'free', { k: 'len' });
210
- // heights: blue once entered (the sill: locked), grey while they're defaults / free to drag
211
- tag(S(T, f.face[1], 0), S(T, f.face[1], f.H), 34, 0, fmtLen(f.H), sk.lines[f.lid].height != null ? 'drive' : 'free', { k: 'wallH' });
212
- for (const o of ops) {
213
- const off = 24;
214
- const op = sk.openings[o.id];
215
- if (o.sill > 0.01 || o.type === 'window')
216
- tag(S(T, o.u1, 0), S(T, o.u1, o.sill), off, 0, fmtLen(o.sill), op.sillLocked ? 'drive' : 'free', { k: 'sill', op: o.id }, true);
217
- tag(S(T, o.u1, o.sill), S(T, o.u1, o.head), off, 0, fmtLen(o.head - o.sill), op.h != null ? 'drive' : 'free', { k: 'h', op: o.id });
236
+ for (const o of elevOpenings(sk, f))
237
+ if (lit('opening', o.id))
238
+ ring(o.u0, o.head, o.u1, o.sill, isSel('opening', o.id));
239
+ for (const id of Object.keys(face.cabs ?? {})) {
240
+ if (!lit('cab', id))
241
+ continue;
242
+ const b = cabBox(face, id);
243
+ if (b)
244
+ ring(b.u0, b.z1, b.u1, b.z0, isSel('cab', id));
218
245
  }
219
- return hits;
220
246
  }
221
- /** Rows of dimensions below the floor, pixels: the string, then extra rows, then the overall length. */
247
+ /** Rows of dimensions below the floor, pixels: the strings, then extra rows, then the overall length. */
222
248
  const ROW0 = 30, ROW = 34;
223
- /** The dimension string along the floor: corner → each opening's edges → corner, left to right. */
224
- function chain(sk, f) {
225
- const ops = elevOpenings(sk, f), other = f.leftEnd === 'a' ? 'b' : 'a';
226
- const nodes = [{ u: f.face[0], end: f.leftEnd }];
227
- for (const o of ops) {
228
- if (o.u0 < nodes[nodes.length - 1].u - 0.01 || o.u1 > f.face[1] + 0.01)
229
- continue; // overlapping or past the corner: left out of the string
230
- nodes.push({ u: o.u0, op: o, edge: o.e0 }, { u: o.u1, op: o, edge: o.e1 });
249
+ /** One rule for every chip: blue once someone entered it; grey while it's a standard size (a new cabinet's) or only measured. */
250
+ const faceLook = (c, id) => (ed.highlight.has(id) || ed.ana.bad.has(id) || ed.badFaceCons.has(id) ? 'bad' : c.driving === false ? 'ref' : c.auto ? 'free' : 'drive');
251
+ /** The face dimension across the wall between two points, if there is one. */
252
+ function dimAcross(face, a, b) {
253
+ for (const [id, c] of Object.entries(face.cons))
254
+ if (c.type === 'dist' && c.kind === 'h' && ((c.a === a && c.b === b) || (c.a === b && c.b === a)))
255
+ return id;
256
+ return null;
257
+ }
258
+ /** The face dimension of a cabinet's height above the floor, if there is one. */
259
+ export function cabLiftDim(face, id) {
260
+ const cb = face.cabs[id];
261
+ for (const [cid, k] of Object.entries(face.cons)) {
262
+ const ends = [k.a, k.b];
263
+ if (k.type === 'dist' && k.kind === 'v' && ends.some(v => !!v && !!face.points[v]?.anchor && face.points[v].y === 0) && ends.some(v => v === cb.bl || v === cb.br))
264
+ return cid;
231
265
  }
232
- nodes.push({ u: f.face[1], end: other });
266
+ return null;
267
+ }
268
+ /**
269
+ * The strings along the floor, corner to corner, left to right: the doors and windows and the cabinets that fit
270
+ * between them in the first; cabinets that overlap those (hung above a counter, say) in the next ones.
271
+ */
272
+ function strings(sk, f, face) {
273
+ const ops = elevOpenings(sk, f).map(o => ({ u0: o.u0, u1: o.u1, op: o }));
274
+ const cabs = Object.keys(face.cabs ?? {}).flatMap(id => {
275
+ const b = cabBox(face, id);
276
+ if (!b)
277
+ return [];
278
+ const [l, r] = cabSides(face, id);
279
+ return [{ u0: b.u0, u1: b.u1, cab: { id, l, r } }];
280
+ });
281
+ const rows = [];
282
+ const inside = (it) => it.u0 >= f.face[0] - 0.01 && it.u1 <= f.face[1] + 0.01;
283
+ const fits = (row, it) => inside(it) && !row.some(x => it.u0 < x.u1 - 0.01 && it.u1 > x.u0 + 0.01);
284
+ // doors and windows first, then cabinets: each in the first string it fits, overlapping ones in strings of their own
285
+ for (const it of [...ops.sort((p, q) => p.u0 - q.u0), ...cabs.sort((p, q) => p.u0 - q.u0)]) {
286
+ if (!inside(it))
287
+ continue;
288
+ const row = rows.find(r => fits(r, it));
289
+ if (row)
290
+ row.push(it);
291
+ else
292
+ rows.push([it]);
293
+ }
294
+ return rows.map(r => r.sort((p, q) => p.u0 - q.u0));
295
+ }
296
+ /** One string's dimensions: each gap between corners, doors, windows and cabinets, and each one's width. */
297
+ function stringSegs(sk, f, face, row) {
298
+ const other = f.leftEnd === 'a' ? 'b' : 'a';
299
+ const nodes = [{ u: f.face[0], pt: cornerAnchor(f.leftEnd), end: f.leftEnd }];
300
+ for (const it of row) {
301
+ if (it.op)
302
+ nodes.push({ u: it.u0, pt: openingAnchor(it.op.id, it.op.e0), it, edge: it.op.e0 }, { u: it.u1, pt: openingAnchor(it.op.id, it.op.e1), it, edge: it.op.e1 });
303
+ else
304
+ nodes.push({ u: it.u0, pt: it.cab.l, it }, { u: it.u1, pt: it.cab.r, it });
305
+ }
306
+ nodes.push({ u: f.face[1], pt: cornerAnchor(other), end: other });
233
307
  if (nodes.length <= 2)
234
- return { segs: [] };
235
- const face = (end) => ({ face: end, line: f.lid, side: f.side });
308
+ return [];
309
+ const faceRef = (end) => ({ face: end, line: f.lid, side: f.side });
236
310
  const segs = [];
237
311
  for (let i = 1; i < nodes.length; i++) {
238
312
  const n1 = nodes[i - 1], n2 = nodes[i];
239
- if (n1.op && n2.op && n1.op === n2.op) {
240
- segs.push({ u0: n1.u, u1: n2.u, act: { k: 'w', op: n1.op.id } });
313
+ if (n1.it && n1.it === n2.it) {
314
+ // a door's or window's width (its own), or a cabinet's (a face dimension)
315
+ if (n1.it.op)
316
+ segs.push({ u0: n1.u, u1: n2.u, act: { k: 'w', op: n1.it.op.id }, look: widthEntered(sk.openings[n1.it.op.id]) ? 'drive' : 'free', width: true });
317
+ else {
318
+ const id = face.cabs[n1.it.cab.id].w;
319
+ segs.push(id && face.cons[id]
320
+ ? { u0: n1.u, u1: n2.u, act: { k: 'fdim', id }, look: faceLook(face.cons[id], id), width: true }
321
+ : { u0: n1.u, u1: n2.u, act: { k: 'gap', a: n1.pt, b: n2.pt, kind: 'h', title: 'Cabinet width' }, look: 'free', width: true });
322
+ }
241
323
  continue;
242
324
  }
243
- let act;
244
- if (!n1.op)
245
- act = { k: 'pos', op: n2.op.id, edge: n2.edge, ref: face(n1.end), title: 'Distance from the corner' };
246
- else if (!n2.op)
247
- act = { k: 'pos', op: n1.op.id, edge: n1.edge, ref: face(n2.end), title: 'Distance from the corner' };
248
- // between two openings: one of them is locked to the other (pickBetween says which)
249
- else
250
- act = { k: 'pos', ...pickBetween(sk, n1.op.id, n1.edge, n2.op.id, n2.edge), title: 'Space between' };
251
- const dim = (sk.openings[act.op].dims || []).find(d => d.edge === act.edge && sameRef(d.ref, act.ref));
252
- segs.push({ u0: n1.u, u1: n2.u, act, dim });
325
+ if (!n1.it?.cab && !n2.it?.cab) {
326
+ if (!n1.it && !n2.it)
327
+ continue;
328
+ // between corners, doors and windows: the plan's door/window dimensions
329
+ let act;
330
+ if (!n1.it)
331
+ act = { k: 'pos', op: n2.it.op.id, edge: n2.edge, ref: faceRef(n1.end), title: 'Distance from the corner' };
332
+ else if (!n2.it)
333
+ act = { k: 'pos', op: n1.it.op.id, edge: n1.edge, ref: faceRef(n2.end), title: 'Distance from the corner' };
334
+ else
335
+ act = { k: 'pos', ...pickBetween(sk, n1.it.op.id, n1.edge, n2.it.op.id, n2.edge), title: 'Space between' };
336
+ const d = (sk.openings[act.op].dims || []).find(x => x.edge === act.edge && sameRef(x.ref, act.ref));
337
+ segs.push({ u0: n1.u, u1: n2.u, act, look: !d ? 'free' : ed.badOpeningDims.has(d.id) ? 'bad' : d.driving ? 'drive' : 'ref' });
338
+ continue;
339
+ }
340
+ // to or from a cabinet: a face dimension once entered
341
+ const id = dimAcross(face, n1.pt, n2.pt);
342
+ const title = n1.end || n2.end ? 'Distance from the corner' : 'Space between';
343
+ segs.push(id ? { u0: n1.u, u1: n2.u, act: { k: 'fdim', id }, look: faceLook(face.cons[id], id) } : { u0: n1.u, u1: n2.u, act: { k: 'gap', a: n1.pt, b: n2.pt, kind: 'h', title }, look: 'free' });
344
+ }
345
+ return segs;
346
+ }
347
+ /** The doors, windows and cabinets a wall-view dimension measures from or to (by the points it runs between). */
348
+ function owners(face, act) {
349
+ const out = [];
350
+ const of = (pid) => {
351
+ if (!pid)
352
+ return;
353
+ const m = /^@o:([^:]+):/.exec(pid);
354
+ if (m) {
355
+ out.push(m[1]);
356
+ return;
357
+ }
358
+ for (const [id, cb] of Object.entries(face.cabs ?? {}))
359
+ if ([cb.bl, cb.br, cb.tr, cb.tl].includes(pid))
360
+ out.push(id);
361
+ };
362
+ if ('op' in act)
363
+ out.push(act.op);
364
+ if (act.k === 'pos' && 'op' in act.ref)
365
+ out.push(act.ref.op);
366
+ if (act.k === 'depth')
367
+ out.push(act.cab);
368
+ if (act.k === 'gap') {
369
+ of(act.a);
370
+ of(act.b);
371
+ }
372
+ if (act.k === 'fdim') {
373
+ const c = face.cons[act.id];
374
+ of(c?.a);
375
+ of(c?.b);
376
+ }
377
+ // the wall's own length and height: the wall's
378
+ if (act.k === 'len' || act.k === 'wallH')
379
+ out.push(WALL);
380
+ return out;
381
+ }
382
+ /**
383
+ * The doors, windows and cabinets whose reference dimensions show (as the plan shows a door's or window's distances
384
+ * only while it's selected): the selected and hovered ones, and the one a selected dimension belongs to.
385
+ */
386
+ /** The wall itself, among what a wall view's dimensions belong to: live while one of its edges or corners is picked. */
387
+ const WALL = '@wall';
388
+ /** One of the wall's own edges or corners (the face's anchors that aren't a door's or window's, nor a shape's). */
389
+ const wallPart = (id) => id.startsWith('@') && !id.startsWith('@o:') && !id.startsWith('@k:');
390
+ function liveObjects(face) {
391
+ const live = new Set();
392
+ const add = (type, id) => {
393
+ if (type === 'opening' || type === 'cab')
394
+ live.add(id);
395
+ else if ((type === 'line' || type === 'point') && wallPart(id))
396
+ live.add(WALL);
397
+ else if (type === 'elev') {
398
+ try {
399
+ for (const o of owners(face, JSON.parse(id)))
400
+ live.add(o);
401
+ }
402
+ catch { /* not a chip */ }
403
+ }
404
+ else if (type === 'dim' && face.cons[id])
405
+ for (const o of owners(face, { k: 'fdim', id }))
406
+ live.add(o);
407
+ };
408
+ for (const s of ed.sel)
409
+ add(s.type, s.id);
410
+ if (ed.hover)
411
+ add(ed.hover.type, ed.hover.id);
412
+ return live;
413
+ }
414
+ /**
415
+ * The wall view's dimensions, over the face: the strings along the floor, door/window dimensions that aren't a step of
416
+ * one, face dimensions entered from a chip that no longer are, the wall's length (set in the plan) and height, and
417
+ * beside each door, window and cabinet its height off the floor and its own height. `on`: whether a chip is selected.
418
+ * As in the plan, what's been entered (blue) always shows; the rest (grey: just measured, a standard size or a
419
+ * reference) only while what it belongs to — a door, window or cabinet, or the wall itself (an edge or corner of it) —
420
+ * is selected or hovered. Returns the chips, to tap.
421
+ */
422
+ export function drawWallChips(c, T, sk, f, face, on) {
423
+ const hits = [], shown = new Set(), live = liveObjects(face);
424
+ /** entered by someone, so it shows in blue (or red, when it can't be met) — not just measured, a standard size or a reference */
425
+ const entered = (act, look) => (look === 'drive' || look === 'bad' || look === 'linked') && !(act.k === 'fdim' && face.cons[act.id]?.auto);
426
+ /** an object's own size (a door's, window's or cabinet's width or height; the wall's length or height), not a distance */
427
+ const ownSize = (act) => act.k === 'w' || act.k === 'h' || act.k === 'len' || act.k === 'wallH'
428
+ || (act.k === 'gap' && (act.title === 'Cabinet width' || act.title === 'Cabinet height'))
429
+ || (act.k === 'fdim' && Object.values(face.cabs ?? {}).some(cb => cb.w === act.id || cb.h === act.id));
430
+ // a dimension shows once it's entered, and otherwise only while what it belongs to is selected or hovered
431
+ const tag = (p, q, nx, ny, label, look, act, force = false) => {
432
+ if (!entered(act, look) && !on(act) && !owners(face, act).some(o => live.has(o)))
433
+ return;
434
+ // as in the plan, a number that holds nothing is dashed and in parentheses: a reference, or one only measured —
435
+ // a distance, or the wall's length when the plan doesn't lock it (a standard size, which holds, stays solid)
436
+ const dashed = look === 'ref' || (look === 'free' && !(ownSize(act) && act.k !== 'len'));
437
+ const box = dim(c, p, q, nx, ny, label, look, force, on(act), dashed);
438
+ if (box)
439
+ hits.push({ ...box, act, look });
440
+ if (act.k === 'fdim')
441
+ shown.add(act.id);
442
+ };
443
+ /** the chip's value: a driving face dimension's own, otherwise what's measured */
444
+ const val = (act, measured) => (act.k === 'fdim' && face.cons[act.id]?.driving !== false ? face.cons[act.id].value : measured);
445
+ const rows = strings(sk, f, face);
446
+ rows.forEach((row, r) => {
447
+ for (const g of stringSegs(sk, f, face, row))
448
+ tag(S(T, g.u0, 0), S(T, g.u1, 0), 0, ROW0 + ROW * r, fmtLen(val(g.act, g.u1 - g.u0)), g.look, g.act);
449
+ });
450
+ let next = Math.max(rows.length, 1);
451
+ // door/window dimensions that aren't a step of a string, packed into rows of their own
452
+ const extra = extraDims(sk, f, rows);
453
+ for (const x of extra)
454
+ tag(S(T, x.u0, 0), S(T, x.u1, 0), 0, ROW0 + ROW * (next + x.row), fmtLen(x.u1 - x.u0), ed.badOpeningDims.has(x.d.id) ? 'bad' : x.d.driving ? 'drive' : 'ref', x.act);
455
+ next += extra.reduce((m, x) => Math.max(m, x.row + 1), 0);
456
+ // heights beside doors, windows and cabinets
457
+ for (const o of elevOpenings(sk, f)) {
458
+ const op = sk.openings[o.id];
459
+ if (o.sill > 0.01 || o.type === 'window')
460
+ tag(S(T, o.u1, 0), S(T, o.u1, o.sill), 24, 0, fmtLen(o.sill), op.sillLocked ? 'drive' : 'free', { k: 'sill', op: o.id }, true);
461
+ tag(S(T, o.u1, o.sill), S(T, o.u1, o.head), 24, 0, fmtLen(o.head - o.sill), op.h != null ? 'drive' : 'free', { k: 'h', op: o.id });
253
462
  }
254
- return { segs };
463
+ // beside a cabinet: on its right like a window's, unless a cabinet or opening is right there (a run of cabinets),
464
+ // then on its left, else just inside it
465
+ const boxes = [...Object.keys(face.cabs ?? {}).map(id => ({ id, b: cabBox(face, id) })).filter(x => x.b).map(x => ({ id: x.id, ...x.b })),
466
+ ...elevOpenings(sk, f).map(o => ({ id: o.id, u0: o.u0, u1: o.u1, z0: o.sill, z1: o.head }))];
467
+ const room = 64 / T.s;
468
+ const blocked = (id, b, lo, hi) => boxes.some(o => o.id !== id && o.u1 > lo + 0.01 && o.u0 < hi - 0.01 && o.z1 > b.z0 - 0.01 && o.z0 < b.z1 + 0.01);
469
+ for (const [id, cb] of Object.entries(face.cabs ?? {})) {
470
+ const b = cabBox(face, id);
471
+ if (!b)
472
+ continue;
473
+ const [l, r] = cabSides(face, id), zid = cabLiftDim(face, id);
474
+ const right = !blocked(id, b, b.u1, b.u1 + room), left = !right && !blocked(id, b, b.u0 - room, b.u0);
475
+ const u = right ? b.u1 : b.u0, off = right ? 24 : left ? -24 : 24, at = right || left ? u : b.u1 - 48 / T.s;
476
+ // its height off the floor: when it's off the floor, set, or selected (a cabinet on the floor needs no 0")
477
+ if (b.z0 > 0.01 || zid || live.has(id)) {
478
+ const zAct = zid ? { k: 'fdim', id: zid } : { k: 'gap', a: cornerAnchor(f.leftEnd), b: l, kind: 'v', title: 'Cabinet off the floor' };
479
+ tag(S(T, at, 0), S(T, at, b.z0), off, 0, fmtLen(val(zAct, b.z0)), zid ? faceLook(face.cons[zid], zid) : 'free', zAct, true);
480
+ }
481
+ // its depth, on its door near the bottom: a chip like the others (grey until it's entered, and then only while
482
+ // the cabinet is selected or hovered), on the cabinet itself
483
+ if (cb.depthSet || live.has(id) || on({ k: 'depth', cab: id })) {
484
+ const dAct = { k: 'depth', cab: id }, look = cb.depthSet ? 'drive' : 'free', st = LOOK(look);
485
+ const [cx] = S(T, (b.u0 + b.u1) / 2, 0), [, by] = S(T, 0, b.z0), [, ty] = S(T, 0, b.z1);
486
+ if (by - ty > 40) {
487
+ const y = by - Math.min(16, (by - ty) * 0.15), text = 'D ' + fmtLen(cb.depth);
488
+ const w = chip(c, text, cx, y, { bg: st.bg, fg: st.fg, font: '600 11.5px', h: 20, pad: 6, border: on(dAct) ? [TH.yellow, 2] : null });
489
+ hits.push({ x: cx - w / 2 - 4, y: y - 14, w: w + 8, h: 28, act: dAct, look });
490
+ }
491
+ }
492
+ const top = r === cb.br ? cb.tr : cb.tl;
493
+ const hAct = cb.h && face.cons[cb.h] ? { k: 'fdim', id: cb.h } : { k: 'gap', a: r, b: top, kind: 'v', title: 'Cabinet height' };
494
+ tag(S(T, at, b.z0), S(T, at, b.z1), off, 0, fmtLen(val(hAct, b.z1 - b.z0)), hAct.k === 'fdim' ? faceLook(face.cons[hAct.id], hAct.id) : 'free', hAct);
495
+ }
496
+ // face dimensions entered from a chip that isn't drawn now (what they measure has moved): across in rows of their own, up beside their point
497
+ const lostRows = [];
498
+ for (const [id, k] of Object.entries(face.cons)) {
499
+ if (!k.elev || k.anchor || k.type !== 'dist' || shown.has(id) || !k.a || !k.b || !face.points[k.a] || !face.points[k.b])
500
+ continue;
501
+ if (Object.values(face.cabs ?? {}).some(cb => cb.w === id || cb.h === id))
502
+ continue;
503
+ const A = face.points[k.a], B = face.points[k.b], act = { k: 'fdim', id }, text = fmtLen(k.driving === false ? measure(face, k) : k.value);
504
+ if (k.kind === 'v') {
505
+ tag(S(T, B.x, A.y), S(T, B.x, B.y), -24, 0, text, faceLook(k, id), act, true);
506
+ continue;
507
+ }
508
+ const u0 = Math.min(A.x, B.x), u1 = Math.max(A.x, B.x);
509
+ let row = 0;
510
+ while ((lostRows[row] || []).some(([x, y]) => u0 < y + 6 && u1 > x - 6))
511
+ row++;
512
+ (lostRows[row] = lostRows[row] || []).push([u0, u1]);
513
+ tag(S(T, A.x, 0), S(T, B.x, 0), 0, ROW0 + ROW * (next + row), text, faceLook(k, id), act);
514
+ }
515
+ next += lostRows.length;
516
+ // the wall's length comes from the floor plan: linked (outlined blue) when a dimension locks it there, grey when free
517
+ const planLocked = Object.values(sk.cons).some(cn => cn.type === 'dist' && cn.line === f.lid && cn.driving !== false && cn.layer !== 'print');
518
+ const anyRow = rows.some(r => r.length) || next > 1;
519
+ tag(S(T, f.face[0], 0), S(T, f.face[1], 0), 0, anyRow ? ROW0 + ROW * next : ROW0, fmtLen(f.face[1] - f.face[0]), planLocked ? 'linked' : 'free', { k: 'len' });
520
+ // the wall's height: blue once entered, grey while it's the default
521
+ tag(S(T, f.face[1], 0), S(T, f.face[1], f.H), 34, 0, fmtLen(f.H), sk.lines[f.lid].height != null ? 'drive' : 'free', { k: 'wallH' });
522
+ return hits;
255
523
  }
256
- /** Saved door/window dimensions on this wall that the string doesn't show, packed into rows (u intervals don't overlap). */
257
- export function extraDims(sk, f) {
258
- const shown = new Set(chain(sk, f).segs.map(g => g.dim?.id).filter(Boolean));
524
+ /** Saved door/window dimensions on this wall that the strings (`rows`) don't show, packed into rows. */
525
+ export function extraDims(sk, f, strs) {
526
+ const shown = new Set();
527
+ for (const row of strs ?? strings(sk, f, emptyFace()))
528
+ for (const g of stringSegs(sk, f, emptyFace(), row.filter(it => it.op))) {
529
+ if (g.act.k !== 'pos')
530
+ continue;
531
+ const a = g.act, d = (sk.openings[a.op].dims || []).find(x => x.edge === a.edge && sameRef(x.ref, a.ref));
532
+ if (d)
533
+ shown.add(d.id);
534
+ }
259
535
  const out = [], rows = [];
260
536
  for (const o of elevOpenings(sk, f)) {
261
537
  const op = sk.openings[o.id], g = openingSpan(sk, op);
@@ -268,19 +544,17 @@ export function extraDims(sk, f) {
268
544
  if (r == null)
269
545
  continue;
270
546
  const a = toU(f, r), b = toU(f, edgeS(op, g.w, d.edge)), u0 = Math.min(a, b), u1 = Math.max(a, b);
271
- let row = 0;
272
- while ((rows[row] || []).some(([x, y]) => u0 < y + 6 && u1 > x - 6))
273
- row++;
274
- (rows[row] = rows[row] || []).push([u0, u1]);
547
+ let k = 0;
548
+ while ((rows[k] || []).some(([x, y]) => u0 < y + 6 && u1 > x - 6))
549
+ k++;
550
+ (rows[k] = rows[k] || []).push([u0, u1]);
275
551
  const title = 'op' in d.ref ? 'Space between' : 'face' in d.ref ? 'Distance from the corner' : 'Distance from the wall end';
276
- out.push({ u0, u1, d, row, act: { k: 'pos', op: o.id, edge: d.edge, ref: d.ref, title } });
552
+ out.push({ u0, u1, d, row: k, act: { k: 'pos', op: o.id, edge: d.edge, ref: d.ref, title } });
277
553
  }
278
554
  }
279
555
  return out;
280
556
  }
281
- /** Extra pixels below the floor that the extra dimension rows take (for fitting the view). */
282
- export const extraRowsPx = (sk, f) => ROW * extraDims(sk, f).reduce((m, x) => Math.max(m, x.row + 1), 0);
283
- /** A chip for previews (line length while drawing). */
557
+ /** A chip for previews (a ghost's size, a height while placing). */
284
558
  export const previewChip = (c, text, x, y) => chip(c, text, x, y, { bg: TH.chip, fg: TH.ink, font: '600 11.5px', h: 20, pad: 6, border: [TH.purple, 1.5] });
285
559
  /** A small plan of the page in the corner: the wall in yellow and an arrow showing where you're looking from. */
286
560
  export function drawKeyPlan(c, x, y, size, sk, f) {