@reekon-tools/boldr-cad 0.1.0 → 1.0.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 (547) hide show
  1. package/README.md +81 -9
  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 +5 -0
  9. package/dist/data/memory.js +2 -2
  10. package/dist/data/memory.js.map +1 -1
  11. package/dist/data/provider.d.ts +19 -7
  12. package/dist/data/sync.d.ts +7 -2
  13. package/dist/data/sync.js +115 -64
  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 +19 -4
  34. package/dist/editor/keypad.js +39 -9
  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 +36 -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 +167 -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 +5 -0
  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 +46 -3
  155. package/dist/react/CadEditor.js +77 -20
  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 +581 -96
  329. package/dist/tools/dim.js.map +1 -1
  330. package/dist/tools/elevation.js +1 -1
  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 +22 -15
  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 +10 -2
  358. package/dist/units/format.js +18 -3
  359. package/dist/units/format.js.map +1 -1
  360. package/dist/view3d/build.d.ts +35 -0
  361. package/dist/view3d/build.js +630 -0
  362. package/dist/view3d/build.js.map +1 -0
  363. package/dist/view3d/camera.d.ts +60 -0
  364. package/dist/view3d/camera.js +84 -0
  365. package/dist/view3d/camera.js.map +1 -0
  366. package/dist/view3d/fatLines.d.ts +11 -0
  367. package/dist/view3d/fatLines.js +83 -0
  368. package/dist/view3d/fatLines.js.map +1 -0
  369. package/dist/view3d/lines.d.ts +17 -0
  370. package/dist/view3d/lines.js +271 -0
  371. package/dist/view3d/lines.js.map +1 -0
  372. package/dist/view3d/mesh.d.ts +51 -0
  373. package/dist/view3d/mesh.js +101 -0
  374. package/dist/view3d/mesh.js.map +1 -0
  375. package/dist/view3d/poly.d.ts +16 -0
  376. package/dist/view3d/poly.js +109 -0
  377. package/dist/view3d/poly.js.map +1 -0
  378. package/dist/view3d/viewer.d.ts +86 -0
  379. package/dist/view3d/viewer.js +350 -0
  380. package/dist/view3d/viewer.js.map +1 -0
  381. package/package.json +15 -2
  382. package/src/app/edit.ts +83 -12
  383. package/src/app/main.ts +11 -5
  384. package/src/app/state.ts +38 -5
  385. package/src/data/memory.ts +2 -2
  386. package/src/data/provider.ts +15 -7
  387. package/src/data/sync.ts +87 -50
  388. package/src/editor/actions.ts +292 -83
  389. package/src/editor/confirm.ts +58 -0
  390. package/src/editor/elevation.ts +698 -478
  391. package/src/editor/face.ts +227 -0
  392. package/src/editor/files.ts +40 -4
  393. package/src/editor/gestures.ts +147 -31
  394. package/src/editor/keypad.ts +43 -11
  395. package/src/editor/keys.ts +25 -5
  396. package/src/editor/laser.ts +98 -20
  397. package/src/editor/linkActions.ts +194 -0
  398. package/src/editor/linkFollow.ts +43 -0
  399. package/src/editor/linkWrite.ts +77 -0
  400. package/src/editor/links.ts +181 -0
  401. package/src/editor/markup.ts +92 -8
  402. package/src/editor/mode.ts +83 -7
  403. package/src/editor/ports.ts +37 -6
  404. package/src/editor/project.ts +155 -26
  405. package/src/editor/selection.ts +54 -8
  406. package/src/editor/status.ts +2 -0
  407. package/src/editor/tooltips.ts +47 -0
  408. package/src/editor/view3d.ts +119 -0
  409. package/src/editor/view3dState.ts +61 -0
  410. package/src/export/dxf.ts +1 -1
  411. package/src/export/pdf.ts +15 -7
  412. package/src/export/plan.ts +119 -35
  413. package/src/index.ts +5 -0
  414. package/src/input/keyboard.ts +2 -4
  415. package/src/model/constraints.ts +13 -4
  416. package/src/model/demo.ts +3 -3
  417. package/src/model/elevation.ts +6 -16
  418. package/src/model/faces.ts +153 -0
  419. package/src/model/facesolve.ts +322 -0
  420. package/src/model/history.ts +19 -11
  421. package/src/model/images.ts +35 -0
  422. package/src/model/links.ts +77 -0
  423. package/src/model/markup.ts +80 -0
  424. package/src/model/measurement.ts +26 -0
  425. package/src/model/openings.ts +6 -0
  426. package/src/model/planGeom.ts +174 -8
  427. package/src/model/project.ts +3 -2
  428. package/src/model/projectfile.ts +2 -1
  429. package/src/model/rooms.ts +1 -1
  430. package/src/model/settings.ts +8 -2
  431. package/src/model/sketch.ts +101 -14
  432. package/src/model/smooth.ts +51 -0
  433. package/src/model/store.ts +35 -4
  434. package/src/model/types.ts +170 -8
  435. package/src/model/wallfaces.ts +126 -17
  436. package/src/model/wallshape.ts +97 -0
  437. package/src/react/BottomBar.tsx +263 -46
  438. package/src/react/Buttons.tsx +50 -9
  439. package/src/react/CadEditor.tsx +133 -30
  440. package/src/react/ConfirmSheet.tsx +38 -0
  441. package/src/react/ConflictSheet.tsx +19 -12
  442. package/src/react/Drawer.tsx +213 -110
  443. package/src/react/Duck.tsx +50 -0
  444. package/src/react/Empty3D.tsx +32 -0
  445. package/src/react/FitScroll.tsx +19 -0
  446. package/src/react/Icon.native.tsx +5 -0
  447. package/src/react/Icon.web.tsx +5 -0
  448. package/src/react/KeypadSheet.tsx +253 -60
  449. package/src/react/LaserTray.tsx +12 -8
  450. package/src/react/MarkupPanel.tsx +150 -0
  451. package/src/react/PageThumb.native.tsx +18 -7
  452. package/src/react/PageThumb.web.tsx +4 -4
  453. package/src/react/PlanCanvas.native.tsx +151 -34
  454. package/src/react/PlanCanvas.web.tsx +15 -5
  455. package/src/react/Popovers.tsx +116 -51
  456. package/src/react/Rail.tsx +360 -27
  457. package/src/react/ShapeMenu.tsx +66 -0
  458. package/src/react/Sheet.tsx +56 -11
  459. package/src/react/Slider.tsx +48 -0
  460. package/src/react/Toast.tsx +16 -9
  461. package/src/react/ToolTip.tsx +49 -0
  462. package/src/react/TopBar.tsx +97 -39
  463. package/src/react/Tween.native.tsx +36 -0
  464. package/src/react/Tween.tsx +24 -0
  465. package/src/react/View3D.native.tsx +131 -0
  466. package/src/react/View3D.web.tsx +131 -0
  467. package/src/react/View3DHost.ts +367 -0
  468. package/src/react/corners.ts +52 -0
  469. package/src/react/easing.ts +14 -0
  470. package/src/react/host.ts +8 -3
  471. package/src/react/hostKeypad.ts +66 -0
  472. package/src/react/motion.tsx +239 -0
  473. package/src/react/ports.ts +78 -9
  474. package/src/react/saving.ts +80 -0
  475. package/src/react/store.ts +35 -3
  476. package/src/react/theme.ts +8 -0
  477. package/src/react/topRow.ts +71 -0
  478. package/src/react/touch.ts +10 -0
  479. package/src/render/ctx.ts +8 -0
  480. package/src/render/draw.ts +437 -175
  481. package/src/render/elevation.ts +349 -143
  482. package/src/render/figmaIcons.ts +22 -0
  483. package/src/render/hit.ts +140 -21
  484. package/src/render/icons.ts +69 -10
  485. package/src/render/linkTile.ts +297 -0
  486. package/src/render/markup.ts +91 -0
  487. package/src/render/navigate.ts +114 -0
  488. package/src/render/snap.ts +105 -22
  489. package/src/render/squircle.ts +73 -0
  490. package/src/render/view.ts +255 -19
  491. package/src/skia/SkiaDrawCtx.ts +163 -41
  492. package/src/skia/host.ts +46 -1
  493. package/src/solver/equations.ts +37 -2
  494. package/src/solver/gauss-newton.ts +125 -28
  495. package/src/solver/linalg.ts +141 -5
  496. package/src/solver/types.ts +5 -0
  497. package/src/styles.css +91 -42
  498. package/src/tools/annotate.ts +58 -29
  499. package/src/tools/chamfer.ts +103 -0
  500. package/src/tools/corner.ts +99 -0
  501. package/src/tools/dim.ts +524 -92
  502. package/src/tools/elevation.ts +1 -1
  503. package/src/tools/fillet.ts +82 -0
  504. package/src/tools/freehand.ts +20 -16
  505. package/src/tools/index.ts +17 -4
  506. package/src/tools/line.ts +59 -23
  507. package/src/tools/modify.ts +86 -24
  508. package/src/tools/openings.ts +8 -13
  509. package/src/tools/registry.ts +27 -2
  510. package/src/tools/select.ts +424 -75
  511. package/src/tools/shapes.ts +119 -58
  512. package/src/ui/ctx.ts +2 -1
  513. package/src/ui/domPorts.ts +23 -4
  514. package/src/ui/drawer.ts +6 -26
  515. package/src/ui/elevation.ts +10 -101
  516. package/src/ui/keypad.ts +8 -14
  517. package/src/ui/laser.ts +2 -2
  518. package/src/ui/menu.ts +63 -17
  519. package/src/ui/noteinput.ts +3 -3
  520. package/src/ui/opacity.ts +108 -0
  521. package/src/ui/palette.ts +67 -12
  522. package/src/ui/rail.ts +21 -7
  523. package/src/ui/topbar.ts +4 -14
  524. package/src/units/format.ts +18 -3
  525. package/src/view3d/build.ts +607 -0
  526. package/src/view3d/camera.ts +113 -0
  527. package/src/view3d/fatLines.ts +83 -0
  528. package/src/view3d/lines.ts +258 -0
  529. package/src/view3d/mesh.ts +111 -0
  530. package/src/view3d/poly.ts +104 -0
  531. package/src/view3d/viewer.ts +348 -0
  532. package/dist/react/ElevCanvas.native.d.ts +0 -3
  533. package/dist/react/ElevCanvas.native.js +0 -58
  534. package/dist/react/ElevCanvas.native.js.map +0 -1
  535. package/dist/react/ElevCanvas.web.d.ts +0 -4
  536. package/dist/react/ElevCanvas.web.js +0 -44
  537. package/dist/react/ElevCanvas.web.js.map +0 -1
  538. package/dist/react/ElevationSheet.d.ts +0 -2
  539. package/dist/react/ElevationSheet.js +0 -38
  540. package/dist/react/ElevationSheet.js.map +0 -1
  541. package/dist/render/elevationView.d.ts +0 -3
  542. package/dist/render/elevationView.js +0 -100
  543. package/dist/render/elevationView.js.map +0 -1
  544. package/src/react/ElevCanvas.native.tsx +0 -68
  545. package/src/react/ElevCanvas.web.tsx +0 -42
  546. package/src/react/ElevationSheet.tsx +0 -88
  547. package/src/render/elevationView.ts +0 -73
@@ -1,84 +1,301 @@
1
- import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
1
+ import { memo, useRef, useState } from 'react';
2
+ import { Animated, ScrollView, Text, View } from 'react-native';
2
3
  import { ed } from '../app/state';
3
4
  import { applyCtxAction, barItems, deleteSelection, plusAction, plusLabel, plusVisible } from '../editor/actions';
4
- import { statusPill, currentHint } from '../editor/status';
5
+ import { addLinks, LINK_PLUS_LABEL, linkPlusVisible, linkSizeVisible, pageLinkSize, setLinkSize, SIZE_STEPS, sizeToStep, stepToSize } from '../editor/linkActions';
6
+ import { afterEdit } from '../app/edit';
7
+ import { LINK_SIDE_IN } from '../model/links';
8
+ import { fmtLen } from '../units';
9
+ import { bar3d, run3d } from '../editor/view3d';
10
+ import { is3d } from '../editor/view3dState';
11
+ import { OPACITY_MIN, OPACITY_STEP, ROTATE_MAX, ROTATE_STEP, setUnderlayOpacity, setUnderlayRotation } from '../editor/project';
12
+ import { pushHistory } from '../model/history';
13
+ import { imageById } from '../model/images';
14
+ import { Slider } from './Slider';
15
+ import { notify, ui, useEditorKey } from './store';
16
+ import { currentHint } from '../editor/status';
5
17
  import { hovered } from './Buttons';
6
18
  import { Icon } from './Icon';
7
19
  import { RichText } from './RichText';
8
20
  import { C, FONT, shadow } from './theme';
21
+ import { Tap, usePopup } from './motion';
22
+ import { createSheet } from './corners';
9
23
 
10
- /** The contextual bar: what fits the tool and selection, the yellow + to measure, and the trash on its own. */
24
+ /**
25
+ * The contextual bar: what fits the tool and selection, the yellow + to measure, and the trash on its own. It's as
26
+ * long as what's in it, centred on the screen — on a phone, unless that would put it under the trash, when it moves
27
+ * just clear of it (and scrolls only if even that leaves too little room). In Annotate on a phone (or a window too
28
+ * short for it under the rail) the + that adds the job's files and measurements has the trash's corner while nothing's
29
+ * selected: selecting anything puts the trash there instead, and letting go brings the + back.
30
+ */
11
31
  export function BottomBar({ phone }: { phone: boolean }) {
12
- const items = barItems(), plus = plusVisible();
32
+ // what's in the bar, and the selected image's opacity and turn (its sliders)
33
+ useEditorKey(() => {
34
+ const u = ed.sel.length === 1 && ed.sel[0].type === 'underlay' ? imageById(ed.sel[0].id) : undefined;
35
+ return [buttonsKey(), plusVisible(), ed.sel.length, u ? u.opacity ?? 0.6 : '', u ? (u.rot ?? 0) + (u.locked ? 'L' : '') : '', linkPlusVisible(), ui.linkFab, linkSizeVisible(), pageLinkSize() ?? ''].join('|');
36
+ });
37
+ // (on a phone only buttons show: a bar that would hold just a label isn't shown at all. The 3D tab measures and
38
+ // deletes nothing: what's tapped there shows its sizes, and what can be done with it from there)
39
+ const items = shown(phone), plus = !is3d() && plusVisible(), trash = !is3d() && ed.sel.length > 0;
40
+ const fab = !trash && (phone || ui.linkFab) && linkPlusVisible(), corner = trash || fab;
41
+ // it scrolls only when its buttons can't all fit (a narrow phone): otherwise a drag on the bar would move it
42
+ const [room, setRoom] = useState(0), [content, setContent] = useState(0), over = content > room + 1;
13
43
  return (
14
44
  <>
15
45
  {(items.length > 0 || plus) && (
16
- <View style={[s.bottom, phone ? { left: 12, right: 76 } : s.centered]}>
17
- <View style={[s.ctx, phone && { flex: 1 }]}>
18
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={s.items} style={{ flexShrink: 1 }}>
19
- {items.map((it, i) => {
20
- if (it === 'sep') return <View key={'s' + i} style={s.sep} />;
21
- if ('lbl' in it) return <Text key={'l' + i} style={s.lbl}>{it.lbl}</Text>;
22
- const fg = it.on ? C.dark : it.locked ? C.red : '#fff';
23
- return (
24
- <Pressable key={it.act + i} accessibilityRole="button" accessibilityLabel={it.label}
25
- accessibilityState={it.on != null || it.locked != null ? { selected: !!(it.on || it.locked) } : undefined}
26
- onPress={e => applyCtxAction(it.act, e.currentTarget)}
27
- style={st => [s.cb, it.on ? { backgroundColor: '#fff' } : hovered(st) && { backgroundColor: 'rgba(255,255,255,0.08)' }]}>
28
- {it.ic === 'color' ? <View style={[s.colorDot, { backgroundColor: ed.noteColor }]} /> : <Icon name={it.ic} color={fg} />}
29
- <Text style={[s.cbText, { color: fg }]}>{it.label}</Text>
30
- </Pressable>
31
- );
32
- })}
46
+ <View style={[s.bottom, phone ? s.phoneRow : s.centered]}>
47
+ <ImagePopover />
48
+ <LinkSizePopover />
49
+ {/* a phone's bar sits between two springs that start as wide as the trash takes and share what's left
50
+ equally, so it's centred; only the left one gives way (first, before the bar), so a long bar slides left
51
+ to clear the trash. (A minimum width on the right one alone did that in a browser, but a device's layout
52
+ shares the room out differently and left a short bar off-centre.) The springs let touches through. */}
53
+ {phone && <View pointerEvents="none" style={[s.springL, { flexBasis: corner ? TRASH_ROOM : 0 }]} />}
54
+ <View testID="ctxBar" style={s.ctx}>
55
+ {/* (a native ScrollView grows to fill its row by default, which stretched the bar across a tablet) */}
56
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} scrollEnabled={over} bounces={false} contentContainerStyle={s.items}
57
+ style={{ flexGrow: 0, flexShrink: 1 }} onLayout={e => setRoom(e.nativeEvent.layout.width)} onContentSizeChange={w => setContent(w)}>
58
+ <BarButtons k={buttonsKey()} phone={phone} />
33
59
  </ScrollView>
34
60
  {plus && (
35
- <Pressable accessibilityRole="button" accessibilityLabel={plusLabel()} testID="addbtn" onPress={plusAction} style={s.add}>
61
+ <Tap accessibilityRole="button" accessibilityLabel={plusLabel()} testID="addbtn" onPress={plusAction} style={s.add}>
36
62
  <Icon name="plus" color={C.dark} />
37
- </Pressable>
63
+ </Tap>
38
64
  )}
39
65
  </View>
66
+ {phone && <View pointerEvents="none" style={[s.springR, { flexBasis: corner ? TRASH_ROOM : 0 }]} />}
40
67
  </View>
41
68
  )}
42
- {ed.sel.length > 0 && (
43
- <Pressable accessibilityRole="button" accessibilityLabel="Delete selection" testID="btnTrash" onPress={deleteSelection}
44
- style={({ pressed }) => [s.trash, phone && { width: 52, height: 52, right: 12 }, { transform: [{ scale: pressed ? 0.93 : 1 }] }]}>
45
- <Icon name="trash" />
46
- </Pressable>
69
+ {trash && (
70
+ <Tap accessibilityRole="button" accessibilityLabel="Delete selection" testID="btnTrash" onPress={deleteSelection}
71
+ style={s.trash}>
72
+ <Icon name="trash" size={24} color="#fff" />
73
+ </Tap>
74
+ )}
75
+ {fab && (
76
+ <Tap accessibilityRole="button" accessibilityLabel={LINK_PLUS_LABEL} testID="linkFab" onPress={() => { void addLinks(); }} style={[s.trash, s.fab]}>
77
+ <Icon name="plus" size={30} color={C.dark} />
78
+ </Tap>
47
79
  )}
48
80
  </>
49
81
  );
50
82
  }
51
83
 
52
- /** The definition-state pill (top left), and the hint for screen readers. */
53
- export function StatusPill({ phone }: { phone: boolean }) {
54
- const st = statusPill();
84
+ /** What's in the bar: for the selection, or for what's tapped in the 3D tab. */
85
+ const entries = () => (is3d() ? bar3d() : barItems());
86
+
87
+ /**
88
+ * What the bar shows: on a phone, its buttons alone (see buttonsOnly) — except in 3D, where the sizes are the point of
89
+ * tapping something, and nothing else shows them.
90
+ */
91
+ const shown = (phone: boolean) => (phone && !is3d() ? buttonsOnly(entries()) : entries());
92
+
93
+ /** What the bar's buttons show: its items (with their states), and which slider is open above it. */
94
+ function buttonsKey(): string {
95
+ const its = entries();
96
+ return JSON.stringify(its) + '|' + ui.imgPop;
97
+ }
98
+
99
+ /**
100
+ * A phone's bar: the buttons alone — the line saying what's selected and its sizes ("Cabinet 24" L × 34 1/2" H…") takes
101
+ * room a phone hasn't got, and the drawing shows the sizes anyway — with no divider left at an end or doubled up.
102
+ */
103
+ function buttonsOnly(items: ReturnType<typeof barItems>): ReturnType<typeof barItems> {
104
+ const out: ReturnType<typeof barItems> = [];
105
+ for (const it of items) {
106
+ if (it !== 'sep' && 'lbl' in it) continue;
107
+ if (it === 'sep' && (!out.length || out[out.length - 1] === 'sep')) continue;
108
+ out.push(it);
109
+ }
110
+ while (out[out.length - 1] === 'sep') out.pop();
111
+ return out;
112
+ }
113
+
114
+ /**
115
+ * The bar's buttons, drawn again only when what they show changes (`k`, see buttonsKey) — not when the bar itself
116
+ * is, as it measures whether they fit.
117
+ */
118
+ const BarButtons = memo(function BarButtons({ phone }: { k: string; phone: boolean }) {
55
119
  return (
56
120
  <>
57
- <View style={[s.dof, phone && { top: 120 }, st.state === 'bad' && { backgroundColor: C.wred }]} testID="dof">
58
- <View style={[s.dofDot, { backgroundColor: st.state === 'full' ? C.green : st.state === 'bad' ? '#fff' : C.lblue }]} />
59
- <Text style={[s.dofText, st.state === 'bad' && { color: '#fff' }]}>{st.text}</Text>
60
- </View>
61
- <View style={s.srOnly} accessibilityLiveRegion="polite" nativeID="hint">
62
- <RichText html={currentHint()} />
63
- </View>
121
+ {shown(phone).map((it, i) => {
122
+ if (it === 'sep') return <View key={'s' + i} style={s.sep} />;
123
+ if ('lbl' in it) return <Text key={'l' + i} style={s.lbl}>{it.lbl}</Text>;
124
+ const fg = it.on ? C.dark : it.locked ? C.red : '#fff';
125
+ return (
126
+ <Tap key={it.act + i} accessibilityRole="button" accessibilityLabel={it.label}
127
+ accessibilityState={it.on != null || it.locked != null ? { selected: !!(it.on || it.locked) } : undefined}
128
+ onPress={e => { if (!(is3d() && run3d(it.act))) applyCtxAction(it.act, e.currentTarget); }}
129
+ style={st => [s.cb, it.on || (it.act === 'imgOpacity' && ui.imgPop === 'opacity') || (it.act === 'imgRotate' && ui.imgPop === 'rotate') || (it.act === 'linkSize' && ui.imgPop === 'tilesize') ? { backgroundColor: 'rgba(255,255,255,0.14)' } : null,
130
+ it.on ? { backgroundColor: '#fff' } : hovered(st) && { backgroundColor: 'rgba(255,255,255,0.08)' }]}>
131
+ <Icon name={it.ic} color={fg} />
132
+ <Text style={[s.cbText, { color: fg }]}>{it.label}</Text>
133
+ </Tap>
134
+ );
135
+ })}
64
136
  </>
65
137
  );
138
+ });
139
+
140
+ /** Degrees, as the rotation slider shows them: whole, or to the half degree. */
141
+ const fmtDeg = (v: number): string => (Math.round(v * 2) / 2).toString().replace('-', '−') + '°';
142
+
143
+ /**
144
+ * The selected image's slider, just above the bar: its opacity, or how far it's turned (Opacity or Rotate in the bar
145
+ * opens and closes it), with Reset to stand a turned one upright again. A locked image can't be turned: its slider
146
+ * goes with the lock.
147
+ *
148
+ * Kept quick on a device: as it's dragged the thumb and the number follow the finger from here (`live`), the image is
149
+ * changed at most once a frame, and only the photos are drawn again (requestImageDraw) — the rest of the editor
150
+ * hears of it once, as it's let go.
151
+ */
152
+ function ImagePopover() {
153
+ const [live, setLive] = useState<number | null>(null);
154
+ const pending = useRef<{ v: number; frame: number } | null>(null);
155
+ const now = ed.sel.length === 1 && ed.sel[0].type === 'underlay' ? imageById(ed.sel[0].id) : undefined;
156
+ const kind = ui.imgPop === 'opacity' || ui.imgPop === 'rotate' ? ui.imgPop : null;
157
+ const open = !!kind && !!now && !(kind === 'rotate' && now.locked);
158
+ // (it rises from the bar; as it goes it keeps showing the image it was for)
159
+ const last = useRef<{ kind: 'opacity' | 'rotate'; sel: NonNullable<typeof now> } | null>(null), pop = usePopup(open, { y: 8 }, 'bottom center');
160
+ if (open) last.current = { kind: kind!, sel: now! };
161
+ if (!pop.present || !last.current) return null;
162
+ const { sel } = last.current, rotate = last.current.kind === 'rotate', id = sel.id;
163
+ const apply = (v: number) => (rotate ? setUnderlayRotation(id, v) : setUnderlayOpacity(id, v / 100));
164
+ const value = live ?? (rotate ? sel.rot ?? 0 : Math.round((sel.opacity ?? 0.6) * 100));
165
+ const change = (v: number) => {
166
+ setLive(v);
167
+ const p = pending.current;
168
+ if (p) { p.v = v; return; }
169
+ pending.current = { v, frame: requestAnimationFrame(() => { const q = pending.current; pending.current = null; if (q) apply(q.v); }) };
170
+ };
171
+ const end = () => {
172
+ const p = pending.current;
173
+ if (p) { cancelAnimationFrame(p.frame); pending.current = null; apply(p.v); }
174
+ setLive(null);
175
+ notify();
176
+ };
177
+ const upright = Math.abs(sel.rot ?? 0) < 1e-9;
178
+ return (
179
+ <View style={s.popWrap} pointerEvents="box-none">
180
+ <Animated.View style={[s.pop, rotate && s.popReset, pop.style]} pointerEvents={open ? 'auto' : 'none'} testID={open ? (rotate ? 'rotatePop' : 'opacityPop') : undefined}>
181
+ <Text style={s.popLabel}>{rotate ? 'Rotate' : 'Opacity'}</Text>
182
+ <View style={{ flex: 1 }}>
183
+ {rotate
184
+ ? <Slider label="Image rotation" testID="rotateSlider" value={value} min={-ROTATE_MAX} max={ROTATE_MAX} step={ROTATE_STEP} format={fmtDeg}
185
+ onStart={pushHistory} onChange={change} onEnd={end} />
186
+ : <Slider label="Image opacity" testID="opacitySlider" value={value} min={OPACITY_MIN} max={100} step={OPACITY_STEP}
187
+ onStart={pushHistory} onChange={change} onEnd={end} />}
188
+ </View>
189
+ <Text style={s.popValue}>{rotate ? fmtDeg(value) : value + '%'}</Text>
190
+ {rotate && (
191
+ <Tap feel="dim" accessibilityRole="button" accessibilityLabel="Reset rotation" accessibilityState={{ disabled: upright }} disabled={upright}
192
+ testID="rotateReset" onPress={() => { pushHistory(); setUnderlayRotation(id, 0); notify(); }} style={[s.reset, upright && { opacity: 0.4 }]}>
193
+ <Text style={s.resetText}>Reset</Text>
194
+ </Tap>
195
+ )}
196
+ </Animated.View>
197
+ </View>
198
+ );
66
199
  }
67
200
 
68
- const s = StyleSheet.create({
201
+ /** A tile size from the slider, as it's kept: to the quarter inch. */
202
+ const tileSize = (step: number): number => Math.round(stepToSize(step) * 4) / 4;
203
+
204
+ /**
205
+ * The tiles' size, just above the bar (Size in it opens and closes it): one size for every tile on the page, on the drawing
206
+ * (they zoom with the plan and print at it). Kept quick on a device, as ImagePopover is: the thumb and the number follow
207
+ * the finger from here, the tiles are sized at most once a frame, and the rest of the editor hears of it as it's let go.
208
+ */
209
+ function LinkSizePopover() {
210
+ const [live, setLive] = useState<number | null>(null);
211
+ // the number's width as it was laid out, held while a drag goes on (the track ends at it: as long as the number is,
212
+ // no longer — but never narrower mid-drag, which would move the track under the finger)
213
+ const [numW, setNumW] = useState(0);
214
+ const pending = useRef<{ v: number; frame: number } | null>(null);
215
+ // (opened from the bar's Size, as an image's sliders are; a press on the drawing closes it)
216
+ const size = ui.imgPop === 'tilesize' && linkSizeVisible() ? pageLinkSize() : null, open = size != null;
217
+ // (as it goes it keeps showing the size it had)
218
+ const last = useRef(size ?? LINK_SIDE_IN), pop = usePopup(open, { y: 8 }, 'bottom center');
219
+ if (size != null) last.current = size;
220
+ if (!pop.present) return null;
221
+ const value = live ?? sizeToStep(last.current);
222
+ const change = (v: number) => {
223
+ setLive(v);
224
+ const p = pending.current;
225
+ if (p) { p.v = v; return; }
226
+ pending.current = { v, frame: requestAnimationFrame(() => { const q = pending.current; pending.current = null; if (q) setLinkSize(tileSize(q.v)); }) };
227
+ };
228
+ const end = () => {
229
+ const p = pending.current;
230
+ if (p) { cancelAnimationFrame(p.frame); pending.current = null; setLinkSize(tileSize(p.v)); }
231
+ setLive(null);
232
+ afterEdit();
233
+ notify();
234
+ };
235
+ return (
236
+ <View style={s.popWrap} pointerEvents="box-none">
237
+ <Animated.View style={[s.pop, pop.style]} pointerEvents={open ? 'auto' : 'none'} testID={open ? 'tileSizePop' : undefined}>
238
+ <Text style={s.popLabel}>Tile size</Text>
239
+ <View style={{ flex: 1 }}>
240
+ <Slider label="Tile size" testID="tileSizeSlider" value={value} min={0} max={SIZE_STEPS} step={1} format={v => fmtLen(tileSize(v))}
241
+ onStart={pushHistory} onChange={change} onEnd={end} />
242
+ </View>
243
+ <Text onLayout={e => { if (live == null) setNumW(e.nativeEvent.layout.width); }}
244
+ style={[s.popValue, s.sizeValue, live != null && numW ? { minWidth: numW } : null]}>{fmtLen(live != null ? tileSize(live) : last.current)}</Text>
245
+ </Animated.View>
246
+ </View>
247
+ );
248
+ }
249
+
250
+ /** The hint for screen readers: what to do next with the tool or what's picked, read out as it changes. */
251
+ export function SpokenHint() {
252
+ useEditorKey(currentHint);
253
+ return (
254
+ <View style={s.srOnly} accessibilityLiveRegion="polite" nativeID="hint">
255
+ <RichText html={currentHint()} />
256
+ </View>
257
+ );
258
+ }
259
+
260
+ /** What the trash (or Annotate's +, in its place) takes at the right of a phone's bar row: its 60 pt, and the 12 between them. */
261
+ const TRASH_ROOM = 72;
262
+
263
+ const s = createSheet({
69
264
  bottom: { pointerEvents: 'box-none', position: 'absolute', bottom: 14, zIndex: 24, flexDirection: 'row', alignItems: 'center' },
70
265
  centered: { left: 90, right: 90, justifyContent: 'center' },
71
- ctx: { flexDirection: 'row', alignItems: 'center', backgroundColor: C.g2, borderRadius: 34, paddingVertical: 6, paddingHorizontal: 8, maxWidth: '100%', ...shadow },
266
+ phoneRow: { left: 12, right: 12 },
267
+ springL: { flexGrow: 1, flexShrink: 1000 },
268
+ springR: { flexGrow: 1, flexShrink: 0 },
269
+ // the same 6 all round, and corners of the buttons' radius plus 6: the end buttons sit concentric in the bar
270
+ // (it gives way when its buttons don't fit — React Native doesn't shrink a view unless told — and then scrolls)
271
+ ctx: { flexDirection: 'row', alignItems: 'center', backgroundColor: C.g2, borderRadius: 27 + 6, minHeight: 54 + 12, padding: 6, maxWidth: '100%', flexShrink: 1, ...shadow },
72
272
  items: { flexDirection: 'row', alignItems: 'center', gap: 2 },
73
273
  cb: { minWidth: 58, height: 54, borderRadius: 27, alignItems: 'center', justifyContent: 'center', gap: 3, paddingHorizontal: 8 },
74
274
  cbText: { fontFamily: FONT, fontSize: 10.5, fontWeight: '500' },
75
275
  sep: { width: 1, height: 32, backgroundColor: C.g3, marginHorizontal: 4 },
76
276
  lbl: { fontFamily: FONT, fontSize: 12, color: C.lg1, paddingHorizontal: 10 },
77
- colorDot: { width: 16, height: 16, borderRadius: 8, borderWidth: 2, borderColor: '#fff' },
78
- add: { width: 48, height: 48, borderRadius: 24, backgroundColor: C.yellow, alignItems: 'center', justifyContent: 'center', marginLeft: 4 },
79
- trash: { position: 'absolute', right: 16, bottom: 16, zIndex: 24, width: 56, height: 56, borderRadius: 28, backgroundColor: C.red, alignItems: 'center', justifyContent: 'center', ...shadow },
80
- dof: { position: 'absolute', top: 70, left: 12, zIndex: 20, height: 32, borderRadius: 16, backgroundColor: C.g1, paddingLeft: 10, paddingRight: 12, flexDirection: 'row', alignItems: 'center', gap: 8, ...shadow },
81
- dofDot: { width: 10, height: 10, borderRadius: 5 },
82
- dofText: { fontFamily: FONT, fontSize: 13, fontWeight: '600', color: C.lg2 },
277
+ // the slider sits just above the bar, centred on it
278
+ popWrap: { position: 'absolute', left: 0, right: 0, bottom: 76, alignItems: 'center' },
279
+ pop: { width: 400, maxWidth: '100%', flexDirection: 'row', alignItems: 'center', gap: 10, backgroundColor: C.g2, borderRadius: 999, paddingHorizontal: 16, paddingVertical: 4, ...shadow },
280
+ popLabel: { fontFamily: FONT, fontSize: 12.5, fontWeight: '600', color: C.lg2 },
281
+ popValue: { fontFamily: FONT, fontSize: 12.5, fontWeight: '600', color: '#fff', width: 48, textAlign: 'right' },
282
+ // (a length: as long as it is, the track running up to it)
283
+ sizeValue: { width: 'auto', minWidth: 0 },
284
+ // back to upright: a pill at the end of the rotation slider, concentric with it — the same 4 above, below and beside
285
+ // it, and the slider's radius (22, half its 44) less that 4 for its own
286
+ popReset: { paddingRight: 4 },
287
+ reset: { height: 36, paddingHorizontal: 16, borderRadius: 18, backgroundColor: C.g3, justifyContent: 'center' },
288
+ resetText: { fontFamily: FONT, fontSize: 12.5, fontWeight: '600', color: '#fff' },
289
+ // (smaller than a button, so 3 in from the bar's end as well as from its top and bottom: concentric too)
290
+ add: { width: 48, height: 48, borderRadius: 24, backgroundColor: C.yellow, alignItems: 'center', justifyContent: 'center', marginLeft: 4, marginRight: 3 },
291
+ // BOLDR's delete FAB (the annotation editors' DeleteFab): a 60 pt solid red circle 12 from the right and 14 from the
292
+ // bottom, level with the bar beside it, on every screen size
293
+ trash: {
294
+ position: 'absolute', right: 12, bottom: 14, zIndex: 24, width: 60, height: 60, borderRadius: 30,
295
+ backgroundColor: '#FD2525', alignItems: 'center', justifyContent: 'center',
296
+ boxShadow: '0 2.5px 2.5px rgba(0,0,0,0.15)', elevation: 4,
297
+ },
83
298
  srOnly: { position: 'absolute', width: 1, height: 1, overflow: 'hidden', opacity: 0 },
299
+ // BOLDR's add FAB, in the trash's place: yellow, its + dark (white on the yellow is too faint)
300
+ fab: { backgroundColor: C.yellow },
84
301
  });
@@ -1,42 +1,83 @@
1
- import type { ReactNode } from 'react';
2
- import { Pressable, StyleSheet, Text, View, type PressableStateCallbackType, type StyleProp, type ViewStyle } from 'react-native';
1
+ import { useMemo, type ReactNode } from 'react';
2
+ import { Animated, Pressable, Text, View, type PressableStateCallbackType, type StyleProp, type ViewStyle } from 'react-native';
3
3
  import { Icon } from './Icon';
4
+ import { Tap, TOGGLE_EASE, TOGGLE_MS, TOGGLE_PRESS_MS, usePress, useToggleEased } from './motion';
4
5
  import { C, FONT, shadow } from './theme';
6
+ import { createSheet } from './corners';
5
7
 
6
8
  /** A round icon button (the top bar's circles). */
7
9
  export function CircleButton({ icon, label, onPress, color = C.g3, iconColor = '#fff', size = 46, testID }: {
8
10
  icon: string; label: string; onPress: () => void; color?: string; iconColor?: string; size?: number; testID?: string;
9
11
  }) {
10
12
  return (
11
- <Pressable accessibilityRole="button" accessibilityLabel={label} testID={testID} onPress={onPress}
12
- style={({ pressed }) => [s.circle, { width: size, height: size, backgroundColor: color, transform: [{ scale: pressed ? 0.93 : 1 }] }]}>
13
+ <Tap accessibilityRole="button" accessibilityLabel={label} testID={testID} onPress={onPress}
14
+ style={[s.circle, { width: size, height: size, backgroundColor: color }]}>
13
15
  <Icon name={icon} color={iconColor} />
14
- </Pressable>
16
+ </Tap>
15
17
  );
16
18
  }
17
19
 
18
20
  /** A rounded pill button: yellow for the main action, grey otherwise. */
19
- export function Pill({ label, onPress, children, kind = 'yellow', style, testID, accessibilityLabel }: {
21
+ export function Pill({ label, onPress, children, kind = 'yellow', style, testID, accessibilityLabel, disabled }: {
20
22
  label?: string; onPress: () => void; children?: ReactNode; kind?: 'yellow' | 'grey' | 'dark'; style?: StyleProp<ViewStyle>; testID?: string; accessibilityLabel?: string;
23
+ disabled?: boolean;
21
24
  }) {
22
25
  const bg = kind === 'yellow' ? C.yellow : kind === 'grey' ? C.g2 : C.g1, fg = kind === 'yellow' ? C.dark : '#fff';
23
26
  return (
24
- <Pressable accessibilityRole="button" accessibilityLabel={accessibilityLabel ?? label} testID={testID} onPress={onPress}
25
- style={({ pressed }) => [s.pill, { backgroundColor: bg, transform: [{ scale: pressed ? 0.96 : 1 }] }, style]}>
27
+ <Tap accessibilityRole="button" accessibilityLabel={accessibilityLabel ?? label} accessibilityState={{ disabled: !!disabled }}
28
+ testID={testID} onPress={onPress} disabled={disabled}
29
+ style={[s.pill, { backgroundColor: bg }, disabled && { opacity: 0.7 }, style]}>
26
30
  {children}
27
31
  {label ? <Text style={[s.pillText, { color: fg }]}>{label}</Text> : null}
32
+ </Tap>
33
+ );
34
+ }
35
+
36
+ /**
37
+ * BOLDR's on/off toggle: a grey track with its knob on the left when off, green with the knob on the right when on.
38
+ * As on Apple's switches, pressing it in stretches the knob toward the side it's about to go (to the right when off,
39
+ * to the left when on); letting go, the knob glides across, rounding up again, as the track fades (react/motion.tsx).
40
+ */
41
+ export function Toggle({ label, value, onChange }: { label: string; value: boolean; onChange: () => void }) {
42
+ // pressed in it stretches (unhurried, so a quick tap still shows it); let go, it rounds up again over the whole glide
43
+ const { v: t, go } = useToggleEased(value), press = usePress(TOGGLE_PRESS_MS, TOGGLE_MS, TOGGLE_EASE, true);
44
+ // the knob sets off at once; the setting changes a frame later, once the glide is with the native driver (what it
45
+ // sets off — the menu drawn again, the drawing for a layer or a snap — then can't hold it up)
46
+ const flip = () => go(!value, onChange);
47
+ const travel = TOGGLE.w - TOGGLE.k - 2 * TOGGLE.pad;
48
+ // the stretch grows from the knob's outer edge: rightward from the left, leftward from the right. Drawn as a scale
49
+ // and a slide (what the native driver can play): the knob, k wide, scaled about its middle to k + stretch, its left
50
+ // edge at t × (travel − stretch) — so moved there plus half the stretch
51
+ const { fill, x, sx } = useMemo(() => {
52
+ const stretch = Animated.multiply(press.v, TOGGLE.stretch);
53
+ return {
54
+ fill: t.interpolate({ inputRange: [0, 1], outputRange: [C.g4, C.on] }),
55
+ x: Animated.add(Animated.subtract(Animated.multiply(t, travel), Animated.multiply(t, stretch)), Animated.multiply(stretch, 0.5)),
56
+ sx: Animated.add(1, Animated.multiply(press.v, TOGGLE.stretch / TOGGLE.k)),
57
+ };
58
+ }, [t, press.v, travel]);
59
+ return (
60
+ <Pressable accessibilityRole="switch" accessibilityLabel={label} accessibilityState={{ checked: value }} aria-checked={value} hitSlop={6}
61
+ onPressIn={press.onPressIn} onPressOut={press.onPressOut} onPress={flip}>
62
+ <Animated.View style={[s.track, { backgroundColor: fill }]}>
63
+ <Animated.View style={[s.knob, { transform: [{ translateX: x }, { scaleX: sx }] }]} />
64
+ </Animated.View>
28
65
  </Pressable>
29
66
  );
30
67
  }
68
+ /** The Figma's 69 × 36 toggle (30 knob), scaled to the menus */
69
+ const TOGGLE = { w: 48, h: 25, k: 21, pad: 2, stretch: 6 };
31
70
 
32
71
  export function Row({ children, gap = 8, style }: { children: ReactNode; gap?: number; style?: StyleProp<ViewStyle> }) {
33
72
  return <View style={[{ flexDirection: 'row', alignItems: 'center', gap }, style]}>{children}</View>;
34
73
  }
35
74
 
36
- const s = StyleSheet.create({
75
+ const s = createSheet({
37
76
  circle: { borderRadius: 999, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(255,255,255,0.16)', ...shadow },
38
77
  pill: { height: 46, minWidth: 64, paddingHorizontal: 18, borderRadius: 24, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, ...shadow },
39
78
  pillText: { fontFamily: FONT, fontWeight: '600', fontSize: 15 },
79
+ track: { width: TOGGLE.w, height: TOGGLE.h, borderRadius: 100, padding: TOGGLE.pad, justifyContent: 'center', alignItems: 'flex-start', flexShrink: 0 },
80
+ knob: { width: TOGGLE.k, height: TOGGLE.k, borderRadius: 100, backgroundColor: '#fff', boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.2)' },
40
81
  });
41
82
 
42
83
  /** Whether a Pressable is under the mouse (react-native-web adds `hovered`; devices without a mouse never are). */