@next-buildr/editor 0.0.0-stage → 2.0.1

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 (471) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -2
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/app/builder-editor.d.ts +8 -0
  5. package/dist/app/builder-editor.d.ts.map +1 -0
  6. package/dist/app/builder-editor.js +14 -0
  7. package/dist/app/builder-editor.test.d.ts +2 -0
  8. package/dist/app/builder-editor.test.d.ts.map +1 -0
  9. package/dist/app/builder-editor.test.js +199 -0
  10. package/dist/app/config.d.ts +59 -0
  11. package/dist/app/config.d.ts.map +1 -0
  12. package/dist/app/config.js +18 -0
  13. package/dist/app/content-language.test.d.ts +2 -0
  14. package/dist/app/content-language.test.d.ts.map +1 -0
  15. package/dist/app/content-language.test.js +157 -0
  16. package/dist/app/editor-app.d.ts +17 -0
  17. package/dist/app/editor-app.d.ts.map +1 -0
  18. package/dist/app/editor-app.js +206 -0
  19. package/dist/app/editor-app.test.d.ts +2 -0
  20. package/dist/app/editor-app.test.d.ts.map +1 -0
  21. package/dist/app/editor-app.test.js +135 -0
  22. package/dist/app/layout.d.ts +32 -0
  23. package/dist/app/layout.d.ts.map +1 -0
  24. package/dist/app/layout.js +82 -0
  25. package/dist/app/manifest.d.ts +12 -0
  26. package/dist/app/manifest.d.ts.map +1 -0
  27. package/dist/app/manifest.js +17 -0
  28. package/dist/app/shell-panels.d.ts +15 -0
  29. package/dist/app/shell-panels.d.ts.map +1 -0
  30. package/dist/app/shell-panels.js +5 -0
  31. package/dist/app/small-screen.d.ts +12 -0
  32. package/dist/app/small-screen.d.ts.map +1 -0
  33. package/dist/app/small-screen.js +33 -0
  34. package/dist/app/small-screen.test.d.ts +2 -0
  35. package/dist/app/small-screen.test.d.ts.map +1 -0
  36. package/dist/app/small-screen.test.js +108 -0
  37. package/dist/app/status-info.d.ts +14 -0
  38. package/dist/app/status-info.d.ts.map +1 -0
  39. package/dist/app/status-info.js +34 -0
  40. package/dist/app/use-resizable.d.ts +34 -0
  41. package/dist/app/use-resizable.d.ts.map +1 -0
  42. package/dist/app/use-resizable.js +84 -0
  43. package/dist/canvas-host/canvas-frame.d.ts +53 -0
  44. package/dist/canvas-host/canvas-frame.d.ts.map +1 -0
  45. package/dist/canvas-host/canvas-frame.js +146 -0
  46. package/dist/canvas-host/canvas-frame.test.d.ts +2 -0
  47. package/dist/canvas-host/canvas-frame.test.d.ts.map +1 -0
  48. package/dist/canvas-host/canvas-frame.test.js +91 -0
  49. package/dist/canvas-host/host.d.ts +109 -0
  50. package/dist/canvas-host/host.d.ts.map +1 -0
  51. package/dist/canvas-host/host.js +293 -0
  52. package/dist/canvas-host/host.test.d.ts +2 -0
  53. package/dist/canvas-host/host.test.d.ts.map +1 -0
  54. package/dist/canvas-host/host.test.js +398 -0
  55. package/dist/canvas-host/index.d.ts +5 -0
  56. package/dist/canvas-host/index.d.ts.map +1 -0
  57. package/dist/canvas-host/index.js +2 -0
  58. package/dist/clipboard/clipboard.d.ts +38 -0
  59. package/dist/clipboard/clipboard.d.ts.map +1 -0
  60. package/dist/clipboard/clipboard.js +160 -0
  61. package/dist/clipboard/clipboard.test.d.ts +2 -0
  62. package/dist/clipboard/clipboard.test.d.ts.map +1 -0
  63. package/dist/clipboard/clipboard.test.js +457 -0
  64. package/dist/clipboard/format.d.ts +27 -0
  65. package/dist/clipboard/format.d.ts.map +1 -0
  66. package/dist/clipboard/format.js +37 -0
  67. package/dist/clipboard/index.d.ts +6 -0
  68. package/dist/clipboard/index.d.ts.map +1 -0
  69. package/dist/clipboard/index.js +3 -0
  70. package/dist/clipboard/react.d.ts +20 -0
  71. package/dist/clipboard/react.d.ts.map +1 -0
  72. package/dist/clipboard/react.js +71 -0
  73. package/dist/dialogs/index.d.ts +2 -0
  74. package/dist/dialogs/index.d.ts.map +1 -0
  75. package/dist/dialogs/index.js +1 -0
  76. package/dist/dialogs/media-picker/index.d.ts +7 -0
  77. package/dist/dialogs/media-picker/index.d.ts.map +1 -0
  78. package/dist/dialogs/media-picker/index.js +3 -0
  79. package/dist/dialogs/media-picker/library.d.ts +17 -0
  80. package/dist/dialogs/media-picker/library.d.ts.map +1 -0
  81. package/dist/dialogs/media-picker/library.js +9 -0
  82. package/dist/dialogs/media-picker/media-picker.d.ts +18 -0
  83. package/dist/dialogs/media-picker/media-picker.d.ts.map +1 -0
  84. package/dist/dialogs/media-picker/media-picker.js +99 -0
  85. package/dist/dialogs/media-picker/media-picker.test.d.ts +2 -0
  86. package/dist/dialogs/media-picker/media-picker.test.d.ts.map +1 -0
  87. package/dist/dialogs/media-picker/media-picker.test.js +161 -0
  88. package/dist/dialogs/media-picker/media-ref.d.ts +28 -0
  89. package/dist/dialogs/media-picker/media-ref.d.ts.map +1 -0
  90. package/dist/dialogs/media-picker/media-ref.js +52 -0
  91. package/dist/dnd/check.d.ts +8 -0
  92. package/dist/dnd/check.d.ts.map +1 -0
  93. package/dist/dnd/check.js +12 -0
  94. package/dist/dnd/destinations.d.ts +15 -0
  95. package/dist/dnd/destinations.d.ts.map +1 -0
  96. package/dist/dnd/destinations.js +43 -0
  97. package/dist/dnd/dnd-react.test.d.ts +2 -0
  98. package/dist/dnd/dnd-react.test.d.ts.map +1 -0
  99. package/dist/dnd/dnd-react.test.js +93 -0
  100. package/dist/dnd/dnd.test.d.ts +2 -0
  101. package/dist/dnd/dnd.test.d.ts.map +1 -0
  102. package/dist/dnd/dnd.test.js +327 -0
  103. package/dist/dnd/engine.d.ts +85 -0
  104. package/dist/dnd/engine.d.ts.map +1 -0
  105. package/dist/dnd/engine.js +185 -0
  106. package/dist/dnd/fragment.d.ts +6 -0
  107. package/dist/dnd/fragment.d.ts.map +1 -0
  108. package/dist/dnd/fragment.js +18 -0
  109. package/dist/dnd/geometry.d.ts +30 -0
  110. package/dist/dnd/geometry.d.ts.map +1 -0
  111. package/dist/dnd/geometry.js +31 -0
  112. package/dist/dnd/index.d.ts +14 -0
  113. package/dist/dnd/index.d.ts.map +1 -0
  114. package/dist/dnd/index.js +7 -0
  115. package/dist/dnd/move-to-dialog.d.ts +13 -0
  116. package/dist/dnd/move-to-dialog.d.ts.map +1 -0
  117. package/dist/dnd/move-to-dialog.js +30 -0
  118. package/dist/dnd/react.d.ts +43 -0
  119. package/dist/dnd/react.d.ts.map +1 -0
  120. package/dist/dnd/react.js +185 -0
  121. package/dist/dnd/tree-target.d.ts +29 -0
  122. package/dist/dnd/tree-target.d.ts.map +1 -0
  123. package/dist/dnd/tree-target.js +97 -0
  124. package/dist/fonts/LICENSE-inter.txt +93 -0
  125. package/dist/fonts/inter-latin-400-normal.woff2 +0 -0
  126. package/dist/fonts/inter-latin-500-normal.woff2 +0 -0
  127. package/dist/fonts/inter-latin-600-normal.woff2 +0 -0
  128. package/dist/fonts/inter-latin-ext-400-normal.woff2 +0 -0
  129. package/dist/fonts/inter-latin-ext-500-normal.woff2 +0 -0
  130. package/dist/fonts/inter-latin-ext-600-normal.woff2 +0 -0
  131. package/dist/index.d.ts +22 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +21 -0
  134. package/dist/messages/en.d.ts +335 -0
  135. package/dist/messages/en.d.ts.map +1 -0
  136. package/dist/messages/en.js +332 -0
  137. package/dist/messages/index.d.ts +16 -0
  138. package/dist/messages/index.d.ts.map +1 -0
  139. package/dist/messages/index.js +22 -0
  140. package/dist/messages/pl.d.ts +3 -0
  141. package/dist/messages/pl.d.ts.map +1 -0
  142. package/dist/messages/pl.js +327 -0
  143. package/dist/panels/breadcrumbs/breadcrumbs.d.ts +6 -0
  144. package/dist/panels/breadcrumbs/breadcrumbs.d.ts.map +1 -0
  145. package/dist/panels/breadcrumbs/breadcrumbs.js +24 -0
  146. package/dist/panels/breadcrumbs/breadcrumbs.test.d.ts +2 -0
  147. package/dist/panels/breadcrumbs/breadcrumbs.test.d.ts.map +1 -0
  148. package/dist/panels/breadcrumbs/breadcrumbs.test.js +56 -0
  149. package/dist/panels/index.d.ts +9 -0
  150. package/dist/panels/index.d.ts.map +1 -0
  151. package/dist/panels/index.js +6 -0
  152. package/dist/panels/insert/catalog.d.ts +30 -0
  153. package/dist/panels/insert/catalog.d.ts.map +1 -0
  154. package/dist/panels/insert/catalog.js +76 -0
  155. package/dist/panels/insert/catalog.order.test.d.ts +2 -0
  156. package/dist/panels/insert/catalog.order.test.d.ts.map +1 -0
  157. package/dist/panels/insert/catalog.order.test.js +17 -0
  158. package/dist/panels/insert/category-name.d.ts +4 -0
  159. package/dist/panels/insert/category-name.d.ts.map +1 -0
  160. package/dist/panels/insert/category-name.js +8 -0
  161. package/dist/panels/insert/index.d.ts +3 -0
  162. package/dist/panels/insert/index.d.ts.map +1 -0
  163. package/dist/panels/insert/index.js +2 -0
  164. package/dist/panels/insert/insert-panel.d.ts +7 -0
  165. package/dist/panels/insert/insert-panel.d.ts.map +1 -0
  166. package/dist/panels/insert/insert-panel.js +155 -0
  167. package/dist/panels/insert/insert-panel.test.d.ts +2 -0
  168. package/dist/panels/insert/insert-panel.test.d.ts.map +1 -0
  169. package/dist/panels/insert/insert-panel.test.js +323 -0
  170. package/dist/panels/insert/target.d.ts +17 -0
  171. package/dist/panels/insert/target.d.ts.map +1 -0
  172. package/dist/panels/insert/target.js +48 -0
  173. package/dist/panels/inspector/controls/boolean.d.ts +4 -0
  174. package/dist/panels/inspector/controls/boolean.d.ts.map +1 -0
  175. package/dist/panels/inspector/controls/boolean.js +4 -0
  176. package/dist/panels/inspector/controls/compound.d.ts +15 -0
  177. package/dist/panels/inspector/controls/compound.d.ts.map +1 -0
  178. package/dist/panels/inspector/controls/compound.js +99 -0
  179. package/dist/panels/inspector/controls/compound.test.d.ts +2 -0
  180. package/dist/panels/inspector/controls/compound.test.d.ts.map +1 -0
  181. package/dist/panels/inspector/controls/compound.test.js +195 -0
  182. package/dist/panels/inspector/controls/icon.d.ts +5 -0
  183. package/dist/panels/inspector/controls/icon.d.ts.map +1 -0
  184. package/dist/panels/inspector/controls/icon.js +6 -0
  185. package/dist/panels/inspector/controls/index.d.ts +13 -0
  186. package/dist/panels/inspector/controls/index.d.ts.map +1 -0
  187. package/dist/panels/inspector/controls/index.js +11 -0
  188. package/dist/panels/inspector/controls/label.d.ts +4 -0
  189. package/dist/panels/inspector/controls/label.d.ts.map +1 -0
  190. package/dist/panels/inspector/controls/label.js +8 -0
  191. package/dist/panels/inspector/controls/link.d.ts +5 -0
  192. package/dist/panels/inspector/controls/link.d.ts.map +1 -0
  193. package/dist/panels/inspector/controls/link.js +6 -0
  194. package/dist/panels/inspector/controls/media.d.ts +9 -0
  195. package/dist/panels/inspector/controls/media.d.ts.map +1 -0
  196. package/dist/panels/inspector/controls/media.js +21 -0
  197. package/dist/panels/inspector/controls/number.d.ts +10 -0
  198. package/dist/panels/inspector/controls/number.d.ts.map +1 -0
  199. package/dist/panels/inspector/controls/number.js +33 -0
  200. package/dist/panels/inspector/controls/rich-text-model.d.ts +20 -0
  201. package/dist/panels/inspector/controls/rich-text-model.d.ts.map +1 -0
  202. package/dist/panels/inspector/controls/rich-text-model.js +91 -0
  203. package/dist/panels/inspector/controls/rich-text.d.ts +10 -0
  204. package/dist/panels/inspector/controls/rich-text.d.ts.map +1 -0
  205. package/dist/panels/inspector/controls/rich-text.js +159 -0
  206. package/dist/panels/inspector/controls/select.d.ts +5 -0
  207. package/dist/panels/inspector/controls/select.d.ts.map +1 -0
  208. package/dist/panels/inspector/controls/select.js +16 -0
  209. package/dist/panels/inspector/controls/select.test.d.ts +2 -0
  210. package/dist/panels/inspector/controls/select.test.d.ts.map +1 -0
  211. package/dist/panels/inspector/controls/select.test.js +43 -0
  212. package/dist/panels/inspector/controls/text.d.ts +4 -0
  213. package/dist/panels/inspector/controls/text.d.ts.map +1 -0
  214. package/dist/panels/inspector/controls/text.js +5 -0
  215. package/dist/panels/inspector/controls/textarea.d.ts +4 -0
  216. package/dist/panels/inspector/controls/textarea.d.ts.map +1 -0
  217. package/dist/panels/inspector/controls/textarea.js +4 -0
  218. package/dist/panels/inspector/controls/types.d.ts +16 -0
  219. package/dist/panels/inspector/controls/types.d.ts.map +1 -0
  220. package/dist/panels/inspector/controls/types.js +1 -0
  221. package/dist/panels/inspector/index.d.ts +17 -0
  222. package/dist/panels/inspector/index.d.ts.map +1 -0
  223. package/dist/panels/inspector/index.js +9 -0
  224. package/dist/panels/inspector/inspector.d.ts +16 -0
  225. package/dist/panels/inspector/inspector.d.ts.map +1 -0
  226. package/dist/panels/inspector/inspector.js +116 -0
  227. package/dist/panels/inspector/inspector.test.d.ts +2 -0
  228. package/dist/panels/inspector/inspector.test.d.ts.map +1 -0
  229. package/dist/panels/inspector/inspector.test.js +328 -0
  230. package/dist/panels/inspector/props-panel.d.ts +26 -0
  231. package/dist/panels/inspector/props-panel.d.ts.map +1 -0
  232. package/dist/panels/inspector/props-panel.js +117 -0
  233. package/dist/panels/inspector/styles/box-model.d.ts +20 -0
  234. package/dist/panels/inspector/styles/box-model.d.ts.map +1 -0
  235. package/dist/panels/inspector/styles/box-model.js +31 -0
  236. package/dist/panels/inspector/styles/index.d.ts +5 -0
  237. package/dist/panels/inspector/styles/index.d.ts.map +1 -0
  238. package/dist/panels/inspector/styles/index.js +2 -0
  239. package/dist/panels/inspector/styles/model.d.ts +55 -0
  240. package/dist/panels/inspector/styles/model.d.ts.map +1 -0
  241. package/dist/panels/inspector/styles/model.js +111 -0
  242. package/dist/panels/inspector/styles/segments.d.ts +18 -0
  243. package/dist/panels/inspector/styles/segments.d.ts.map +1 -0
  244. package/dist/panels/inspector/styles/segments.js +75 -0
  245. package/dist/panels/inspector/styles/sides-editor.d.ts +34 -0
  246. package/dist/panels/inspector/styles/sides-editor.d.ts.map +1 -0
  247. package/dist/panels/inspector/styles/sides-editor.js +86 -0
  248. package/dist/panels/inspector/styles/style-inspector.d.ts +15 -0
  249. package/dist/panels/inspector/styles/style-inspector.d.ts.map +1 -0
  250. package/dist/panels/inspector/styles/style-inspector.js +219 -0
  251. package/dist/panels/inspector/styles/style-inspector.test.d.ts +2 -0
  252. package/dist/panels/inspector/styles/style-inspector.test.d.ts.map +1 -0
  253. package/dist/panels/inspector/styles/style-inspector.test.js +265 -0
  254. package/dist/panels/inspector/styles/token-picker.d.ts +15 -0
  255. package/dist/panels/inspector/styles/token-picker.d.ts.map +1 -0
  256. package/dist/panels/inspector/styles/token-picker.js +23 -0
  257. package/dist/panels/inspector/styles/use-style-value.d.ts +28 -0
  258. package/dist/panels/inspector/styles/use-style-value.d.ts.map +1 -0
  259. package/dist/panels/inspector/styles/use-style-value.js +51 -0
  260. package/dist/panels/inspector/value.d.ts +20 -0
  261. package/dist/panels/inspector/value.d.ts.map +1 -0
  262. package/dist/panels/inspector/value.js +32 -0
  263. package/dist/panels/inspector/values/analyze.d.ts +49 -0
  264. package/dist/panels/inspector/values/analyze.d.ts.map +1 -0
  265. package/dist/panels/inspector/values/analyze.js +115 -0
  266. package/dist/panels/inspector/values/data.d.ts +18 -0
  267. package/dist/panels/inspector/values/data.d.ts.map +1 -0
  268. package/dist/panels/inspector/values/data.js +10 -0
  269. package/dist/panels/inspector/values/translation.d.ts +20 -0
  270. package/dist/panels/inspector/values/translation.d.ts.map +1 -0
  271. package/dist/panels/inspector/values/translation.js +25 -0
  272. package/dist/panels/inspector/values/value-editor.d.ts +30 -0
  273. package/dist/panels/inspector/values/value-editor.d.ts.map +1 -0
  274. package/dist/panels/inspector/values/value-editor.js +191 -0
  275. package/dist/panels/inspector/values/values.test.d.ts +2 -0
  276. package/dist/panels/inspector/values/values.test.d.ts.map +1 -0
  277. package/dist/panels/inspector/values/values.test.js +173 -0
  278. package/dist/panels/issues/collect.d.ts +70 -0
  279. package/dist/panels/issues/collect.d.ts.map +1 -0
  280. package/dist/panels/issues/collect.js +96 -0
  281. package/dist/panels/issues/index.d.ts +5 -0
  282. package/dist/panels/issues/index.d.ts.map +1 -0
  283. package/dist/panels/issues/index.js +2 -0
  284. package/dist/panels/issues/issues-panel.d.ts +17 -0
  285. package/dist/panels/issues/issues-panel.d.ts.map +1 -0
  286. package/dist/panels/issues/issues-panel.js +77 -0
  287. package/dist/panels/layers/flatten.d.ts +28 -0
  288. package/dist/panels/layers/flatten.d.ts.map +1 -0
  289. package/dist/panels/layers/flatten.js +61 -0
  290. package/dist/panels/layers/layers-panel.d.ts +14 -0
  291. package/dist/panels/layers/layers-panel.d.ts.map +1 -0
  292. package/dist/panels/layers/layers-panel.js +428 -0
  293. package/dist/panels/layers/layers-panel.test.d.ts +2 -0
  294. package/dist/panels/layers/layers-panel.test.d.ts.map +1 -0
  295. package/dist/panels/layers/layers-panel.test.js +315 -0
  296. package/dist/persistence/controller.d.ts +55 -0
  297. package/dist/persistence/controller.d.ts.map +1 -0
  298. package/dist/persistence/controller.js +341 -0
  299. package/dist/persistence/index.d.ts +6 -0
  300. package/dist/persistence/index.d.ts.map +1 -0
  301. package/dist/persistence/index.js +3 -0
  302. package/dist/persistence/load.d.ts +35 -0
  303. package/dist/persistence/load.d.ts.map +1 -0
  304. package/dist/persistence/load.js +67 -0
  305. package/dist/persistence/persistence.test.d.ts +2 -0
  306. package/dist/persistence/persistence.test.d.ts.map +1 -0
  307. package/dist/persistence/persistence.test.js +483 -0
  308. package/dist/persistence/react.d.ts +18 -0
  309. package/dist/persistence/react.d.ts.map +1 -0
  310. package/dist/persistence/react.js +105 -0
  311. package/dist/persistence/types.d.ts +182 -0
  312. package/dist/persistence/types.d.ts.map +1 -0
  313. package/dist/persistence/types.js +1 -0
  314. package/dist/preview/index.d.ts +3 -0
  315. package/dist/preview/index.d.ts.map +1 -0
  316. package/dist/preview/index.js +2 -0
  317. package/dist/preview/prepare.d.ts +16 -0
  318. package/dist/preview/prepare.d.ts.map +1 -0
  319. package/dist/preview/prepare.js +21 -0
  320. package/dist/preview/preview.d.ts +27 -0
  321. package/dist/preview/preview.d.ts.map +1 -0
  322. package/dist/preview/preview.js +51 -0
  323. package/dist/preview/preview.test.d.ts +2 -0
  324. package/dist/preview/preview.test.d.ts.map +1 -0
  325. package/dist/preview/preview.test.js +129 -0
  326. package/dist/shortcuts/actions.d.ts +17 -0
  327. package/dist/shortcuts/actions.d.ts.map +1 -0
  328. package/dist/shortcuts/actions.js +85 -0
  329. package/dist/shortcuts/index.d.ts +11 -0
  330. package/dist/shortcuts/index.d.ts.map +1 -0
  331. package/dist/shortcuts/index.js +5 -0
  332. package/dist/shortcuts/keys.d.ts +29 -0
  333. package/dist/shortcuts/keys.d.ts.map +1 -0
  334. package/dist/shortcuts/keys.js +100 -0
  335. package/dist/shortcuts/map.d.ts +17 -0
  336. package/dist/shortcuts/map.d.ts.map +1 -0
  337. package/dist/shortcuts/map.js +31 -0
  338. package/dist/shortcuts/registry.d.ts +32 -0
  339. package/dist/shortcuts/registry.d.ts.map +1 -0
  340. package/dist/shortcuts/registry.js +53 -0
  341. package/dist/shortcuts/shortcuts.d.ts +32 -0
  342. package/dist/shortcuts/shortcuts.d.ts.map +1 -0
  343. package/dist/shortcuts/shortcuts.js +95 -0
  344. package/dist/shortcuts/shortcuts.test.d.ts +2 -0
  345. package/dist/shortcuts/shortcuts.test.d.ts.map +1 -0
  346. package/dist/shortcuts/shortcuts.test.js +263 -0
  347. package/dist/store/create-store.d.ts +13 -0
  348. package/dist/store/create-store.d.ts.map +1 -0
  349. package/dist/store/create-store.js +248 -0
  350. package/dist/store/index.d.ts +9 -0
  351. package/dist/store/index.d.ts.map +1 -0
  352. package/dist/store/index.js +5 -0
  353. package/dist/store/locale.d.ts +23 -0
  354. package/dist/store/locale.d.ts.map +1 -0
  355. package/dist/store/locale.js +31 -0
  356. package/dist/store/react.d.ts +17 -0
  357. package/dist/store/react.d.ts.map +1 -0
  358. package/dist/store/react.js +32 -0
  359. package/dist/store/selection.d.ts +32 -0
  360. package/dist/store/selection.d.ts.map +1 -0
  361. package/dist/store/selection.js +84 -0
  362. package/dist/store/selection.test.d.ts +2 -0
  363. package/dist/store/selection.test.d.ts.map +1 -0
  364. package/dist/store/selection.test.js +95 -0
  365. package/dist/store/selectors.d.ts +18 -0
  366. package/dist/store/selectors.d.ts.map +1 -0
  367. package/dist/store/selectors.js +39 -0
  368. package/dist/store/store.test.d.ts +2 -0
  369. package/dist/store/store.test.d.ts.map +1 -0
  370. package/dist/store/store.test.js +331 -0
  371. package/dist/store/types.d.ts +114 -0
  372. package/dist/store/types.d.ts.map +1 -0
  373. package/dist/store/types.js +1 -0
  374. package/dist/styles/tokens.test.d.ts +2 -0
  375. package/dist/styles/tokens.test.d.ts.map +1 -0
  376. package/dist/styles/tokens.test.js +199 -0
  377. package/dist/styles.css +2885 -0
  378. package/dist/toolbar/brand.d.ts +7 -0
  379. package/dist/toolbar/brand.d.ts.map +1 -0
  380. package/dist/toolbar/brand.js +11 -0
  381. package/dist/toolbar/breakpoint-control.d.ts +9 -0
  382. package/dist/toolbar/breakpoint-control.d.ts.map +1 -0
  383. package/dist/toolbar/breakpoint-control.js +23 -0
  384. package/dist/toolbar/editable-title.d.ts +14 -0
  385. package/dist/toolbar/editable-title.d.ts.map +1 -0
  386. package/dist/toolbar/editable-title.js +46 -0
  387. package/dist/toolbar/editable-title.test.d.ts +2 -0
  388. package/dist/toolbar/editable-title.test.d.ts.map +1 -0
  389. package/dist/toolbar/editable-title.test.js +53 -0
  390. package/dist/toolbar/index.d.ts +12 -0
  391. package/dist/toolbar/index.d.ts.map +1 -0
  392. package/dist/toolbar/index.js +5 -0
  393. package/dist/toolbar/locale-switcher.d.ts +10 -0
  394. package/dist/toolbar/locale-switcher.d.ts.map +1 -0
  395. package/dist/toolbar/locale-switcher.js +20 -0
  396. package/dist/toolbar/more-menu.d.ts +12 -0
  397. package/dist/toolbar/more-menu.d.ts.map +1 -0
  398. package/dist/toolbar/more-menu.js +47 -0
  399. package/dist/toolbar/panel-toggles.d.ts +9 -0
  400. package/dist/toolbar/panel-toggles.d.ts.map +1 -0
  401. package/dist/toolbar/panel-toggles.js +19 -0
  402. package/dist/toolbar/publish-dialog.d.ts +18 -0
  403. package/dist/toolbar/publish-dialog.d.ts.map +1 -0
  404. package/dist/toolbar/publish-dialog.js +49 -0
  405. package/dist/toolbar/publish.test.d.ts +2 -0
  406. package/dist/toolbar/publish.test.d.ts.map +1 -0
  407. package/dist/toolbar/publish.test.js +258 -0
  408. package/dist/toolbar/sample-picker.d.ts +18 -0
  409. package/dist/toolbar/sample-picker.d.ts.map +1 -0
  410. package/dist/toolbar/sample-picker.js +72 -0
  411. package/dist/toolbar/sample-picker.test.d.ts +2 -0
  412. package/dist/toolbar/sample-picker.test.d.ts.map +1 -0
  413. package/dist/toolbar/sample-picker.test.js +78 -0
  414. package/dist/toolbar/save-indicator.d.ts +8 -0
  415. package/dist/toolbar/save-indicator.d.ts.map +1 -0
  416. package/dist/toolbar/save-indicator.js +30 -0
  417. package/dist/toolbar/theme.d.ts +22 -0
  418. package/dist/toolbar/theme.d.ts.map +1 -0
  419. package/dist/toolbar/theme.js +46 -0
  420. package/dist/toolbar/theme.test.d.ts +2 -0
  421. package/dist/toolbar/theme.test.d.ts.map +1 -0
  422. package/dist/toolbar/theme.test.js +49 -0
  423. package/dist/toolbar/toolbar-centre.d.ts +13 -0
  424. package/dist/toolbar/toolbar-centre.d.ts.map +1 -0
  425. package/dist/toolbar/toolbar-centre.js +7 -0
  426. package/dist/toolbar/toolbar-left.d.ts +12 -0
  427. package/dist/toolbar/toolbar-left.d.ts.map +1 -0
  428. package/dist/toolbar/toolbar-left.js +16 -0
  429. package/dist/toolbar/toolbar-right.d.ts +19 -0
  430. package/dist/toolbar/toolbar-right.d.ts.map +1 -0
  431. package/dist/toolbar/toolbar-right.js +21 -0
  432. package/dist/toolbar/toolbar.d.ts +38 -0
  433. package/dist/toolbar/toolbar.d.ts.map +1 -0
  434. package/dist/toolbar/toolbar.js +27 -0
  435. package/dist/toolbar/toolbar.test.d.ts +2 -0
  436. package/dist/toolbar/toolbar.test.d.ts.map +1 -0
  437. package/dist/toolbar/toolbar.test.js +289 -0
  438. package/dist/toolbar/use-overflow-level.d.ts +9 -0
  439. package/dist/toolbar/use-overflow-level.d.ts.map +1 -0
  440. package/dist/toolbar/use-overflow-level.js +40 -0
  441. package/dist/toolbar/zoom-menu.d.ts +10 -0
  442. package/dist/toolbar/zoom-menu.d.ts.map +1 -0
  443. package/dist/toolbar/zoom-menu.js +17 -0
  444. package/dist/ui/icon.d.ts +102 -0
  445. package/dist/ui/icon.d.ts.map +1 -0
  446. package/dist/ui/icon.js +182 -0
  447. package/dist/ui/icon.test.d.ts +2 -0
  448. package/dist/ui/icon.test.d.ts.map +1 -0
  449. package/dist/ui/icon.test.js +115 -0
  450. package/dist/ui/index.d.ts +11 -0
  451. package/dist/ui/index.d.ts.map +1 -0
  452. package/dist/ui/index.js +5 -0
  453. package/dist/ui/panel.d.ts +18 -0
  454. package/dist/ui/panel.d.ts.map +1 -0
  455. package/dist/ui/panel.js +21 -0
  456. package/dist/ui/portal.d.ts +14 -0
  457. package/dist/ui/portal.d.ts.map +1 -0
  458. package/dist/ui/portal.js +14 -0
  459. package/dist/ui/primitives.d.ts +190 -0
  460. package/dist/ui/primitives.d.ts.map +1 -0
  461. package/dist/ui/primitives.js +164 -0
  462. package/dist/ui/primitives.test.d.ts +2 -0
  463. package/dist/ui/primitives.test.d.ts.map +1 -0
  464. package/dist/ui/primitives.test.js +215 -0
  465. package/dist/ui/toast.d.ts +39 -0
  466. package/dist/ui/toast.d.ts.map +1 -0
  467. package/dist/ui/toast.js +76 -0
  468. package/dist/ui/toast.test.d.ts +2 -0
  469. package/dist/ui/toast.test.d.ts.map +1 -0
  470. package/dist/ui/toast.test.js +95 -0
  471. package/package.json +67 -3
@@ -0,0 +1,483 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @vitest-environment jsdom
3
+ import { createRegistryMeta, createSeededIdGenerator, } from '@next-buildr/core';
4
+ import { act } from 'react';
5
+ import { createRoot } from 'react-dom/client';
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ import { MessagesProvider } from '../messages/index.js';
8
+ import { createEditorStore, EditorStoreProvider } from '../store/index.js';
9
+ import { ToastProvider } from '../ui/index.js';
10
+ import { createPersistence, EXTERNAL_CHECK_INTERVAL_MS, RETRY_DELAYS_MS, } from './controller.js';
11
+ import { LoadError, loadDocument } from './load.js';
12
+ import { PersistenceProvider, SaveStatus, useSaveAction } from './react.js';
13
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
14
+ const item = {
15
+ type: 'buildr/item',
16
+ version: 1,
17
+ label: 'Item',
18
+ category: 'content',
19
+ props: {},
20
+ contentCategories: ['flow'],
21
+ styles: { groups: [] },
22
+ runtime: 'shared',
23
+ slots: { default: {} },
24
+ };
25
+ const page = {
26
+ ...item,
27
+ type: 'buildr/page',
28
+ label: 'Page',
29
+ capabilities: { root: true },
30
+ };
31
+ const registryMeta = createRegistryMeta({ components: [page, item] });
32
+ const fixture = () => ({
33
+ schemaVersion: 1,
34
+ root: 'root',
35
+ nodes: {
36
+ root: { id: 'root', type: 'buildr/page', slots: { default: ['itemAAAAA1', 'itemBBBBB1'] } },
37
+ itemAAAAA1: { id: 'itemAAAAA1', type: 'buildr/item' },
38
+ itemBBBBB1: { id: 'itemBBBBB1', type: 'buildr/item' },
39
+ },
40
+ components: {},
41
+ });
42
+ const ref = { collection: 'pages', id: '1' };
43
+ function fakeAdapter() {
44
+ const saves = [];
45
+ let revision = 1;
46
+ const fake = {
47
+ saves,
48
+ next: () => ({ ok: true, revision: ++revision, updatedAt: `t${revision}` }),
49
+ loaded: {
50
+ title: 'Home',
51
+ status: 'draft',
52
+ updatedAt: 't1',
53
+ revision: 5,
54
+ document: fixture(),
55
+ },
56
+ canEdit: true,
57
+ remote: { revision: 1 },
58
+ revisionCalls: 0,
59
+ adapter: {
60
+ getRevision: async () => {
61
+ fake.revisionCalls += 1;
62
+ return { updatedAt: 't', ...fake.remote };
63
+ },
64
+ getSession: async () => ({ canEdit: fake.canEdit, canPublish: false }),
65
+ load: async () => fake.loaded,
66
+ save: async (_ref, request) => {
67
+ saves.push(request);
68
+ return fake.next(request);
69
+ },
70
+ publish: async () => ({ ok: true, revision: 1, updatedAt: 't' }),
71
+ getDataSchema: async () => ({ scopes: {}, entities: {} }),
72
+ getContext: async () => {
73
+ throw new Error('unused');
74
+ },
75
+ media: { search: async () => ({ items: [] }) },
76
+ previewUrl: () => '/preview',
77
+ },
78
+ };
79
+ return fake;
80
+ }
81
+ function setup(fake = fakeAdapter(), revision = 1, extra = {}) {
82
+ const store = createEditorStore({
83
+ doc: fixture(),
84
+ registry: registryMeta,
85
+ generateId: createSeededIdGenerator(9),
86
+ validationDelayMs: null,
87
+ });
88
+ const controller = createPersistence({
89
+ store,
90
+ adapter: fake.adapter,
91
+ ref,
92
+ revision,
93
+ debounceMs: 2000,
94
+ maxWaitMs: 20_000,
95
+ ...extra,
96
+ });
97
+ const stop = controller.start();
98
+ return { fake, store, controller, stop };
99
+ }
100
+ let counter = 0;
101
+ const edit = (store, id = 'itemAAAAA1') => act(() => {
102
+ counter += 1;
103
+ store.dispatch({
104
+ type: 'node.setAttr',
105
+ payload: { id, key: 'name', value: `v${counter}` },
106
+ });
107
+ });
108
+ const advance = (ms) => act(async () => vi.advanceTimersByTimeAsync(ms));
109
+ const status = (c) => c.state.getState().status;
110
+ beforeEach(() => vi.useFakeTimers());
111
+ afterEach(() => vi.useRealTimers());
112
+ describe('autosave', () => {
113
+ it('is clean until something changes, then saves after the debounce', async () => {
114
+ const { fake, store, controller } = setup();
115
+ expect(status(controller)).toBe('clean');
116
+ edit(store);
117
+ expect(status(controller)).toBe('dirty');
118
+ await advance(1999);
119
+ expect(fake.saves).toHaveLength(0);
120
+ await advance(1);
121
+ expect(fake.saves).toHaveLength(1);
122
+ expect(fake.saves[0]).toMatchObject({ baseRevision: 1, autosave: true });
123
+ expect(status(controller)).toBe('clean');
124
+ expect(controller.state.getState().revision).toBe(2);
125
+ expect(store.getState().savedCursorId).toBe(store.getState().cursorId);
126
+ });
127
+ it('restarts the debounce with every change but saves at the max wait', async () => {
128
+ const { fake, store } = setup();
129
+ for (let i = 0; i < 12; i++) {
130
+ edit(store);
131
+ await advance(1900);
132
+ }
133
+ // 12 x 1.9 s of typing without a 2 s pause: the max wait (20 s) forced one save.
134
+ expect(fake.saves).toHaveLength(1);
135
+ });
136
+ it('is clean again after undoing back to the saved state', async () => {
137
+ const { fake, store, controller } = setup();
138
+ edit(store);
139
+ expect(status(controller)).toBe('dirty');
140
+ act(() => {
141
+ store.undo();
142
+ });
143
+ expect(status(controller)).toBe('clean');
144
+ await advance(30_000);
145
+ expect(fake.saves).toHaveLength(0);
146
+ });
147
+ it('runs one save at a time and saves what changed meanwhile afterwards', async () => {
148
+ const fake = fakeAdapter();
149
+ let release = () => undefined;
150
+ fake.next = () => new Promise((resolve) => (release = resolve));
151
+ const { store, controller } = setup(fake);
152
+ edit(store);
153
+ await advance(2000);
154
+ expect(status(controller)).toBe('saving');
155
+ edit(store, 'itemBBBBB1');
156
+ await advance(30_000);
157
+ expect(fake.saves).toHaveLength(1); // nothing overlaps
158
+ fake.next = () => ({ ok: true, revision: 3, updatedAt: 't3' });
159
+ await act(async () => release({ ok: true, revision: 2, updatedAt: 't2' }));
160
+ expect(status(controller)).toBe('dirty');
161
+ await advance(2000);
162
+ expect(fake.saves).toHaveLength(2);
163
+ expect(fake.saves[1]?.baseRevision).toBe(2);
164
+ expect(status(controller)).toBe('clean');
165
+ });
166
+ it('saves at once on request, without autosave', async () => {
167
+ const { fake, store, controller } = setup();
168
+ edit(store);
169
+ await act(async () => controller.saveNow());
170
+ expect(fake.saves).toHaveLength(1);
171
+ expect(fake.saves[0]?.autosave).toBe(false);
172
+ await act(async () => controller.saveNow()); // nothing to save
173
+ expect(fake.saves).toHaveLength(1);
174
+ });
175
+ it('does not save a read-only document', async () => {
176
+ const { fake, store, controller } = setup();
177
+ edit(store);
178
+ store.setReadOnly(true);
179
+ await act(async () => controller.saveNow());
180
+ await advance(30_000);
181
+ expect(fake.saves).toHaveLength(0);
182
+ });
183
+ it('stops for good when stopped', async () => {
184
+ const { fake, store, stop } = setup();
185
+ edit(store);
186
+ stop();
187
+ await advance(60_000);
188
+ expect(fake.saves).toHaveLength(0);
189
+ });
190
+ });
191
+ describe('failures', () => {
192
+ it('retries after a network error with growing delays and stays dirty', async () => {
193
+ const fake = fakeAdapter();
194
+ fake.next = () => Promise.reject(new Error('offline'));
195
+ const { store, controller } = setup(fake);
196
+ edit(store);
197
+ await advance(2000);
198
+ expect(status(controller)).toBe('error');
199
+ expect(controller.state.getState().error).toMatchObject({ kind: 'network', attempt: 1 });
200
+ expect(controller.hasUnsavedWork()).toBe(true);
201
+ await advance((RETRY_DELAYS_MS[0] ?? 0) - 1);
202
+ expect(fake.saves).toHaveLength(1);
203
+ await advance(1);
204
+ expect(fake.saves).toHaveLength(2);
205
+ await advance(RETRY_DELAYS_MS[1] ?? 0);
206
+ expect(fake.saves).toHaveLength(3);
207
+ await advance(RETRY_DELAYS_MS[2] ?? 0);
208
+ expect(fake.saves).toHaveLength(4);
209
+ fake.next = () => ({ ok: true, revision: 2, updatedAt: 't2' });
210
+ await advance(RETRY_DELAYS_MS[2] ?? 0);
211
+ expect(status(controller)).toBe('clean');
212
+ expect(controller.state.getState().error).toBeUndefined();
213
+ });
214
+ it('treats a malformed reply as a failed save', async () => {
215
+ const fake = fakeAdapter();
216
+ fake.next = () => ({ nope: true });
217
+ const { store, controller } = setup(fake);
218
+ edit(store);
219
+ await advance(2000);
220
+ expect(status(controller)).toBe('error');
221
+ expect(store.getState().savedCursorId).not.toBe(store.getState().cursorId);
222
+ });
223
+ it('does not retry a rejected document, and a change tries it again', async () => {
224
+ const fake = fakeAdapter();
225
+ fake.next = () => ({
226
+ ok: false,
227
+ kind: 'invalid',
228
+ diagnostics: [{ code: 'x', message: 'bad', severity: 'error' }],
229
+ });
230
+ const { store, controller } = setup(fake);
231
+ edit(store);
232
+ await advance(2000);
233
+ expect(controller.state.getState().error?.kind).toBe('invalid');
234
+ await advance(60_000);
235
+ expect(fake.saves).toHaveLength(1);
236
+ edit(store);
237
+ expect(status(controller)).toBe('dirty');
238
+ await advance(2000);
239
+ expect(fake.saves).toHaveLength(2);
240
+ });
241
+ });
242
+ describe('conflicts', () => {
243
+ const conflict = () => ({ ok: false, kind: 'conflict', currentRevision: 9 });
244
+ it('stops saving and keeps the local work', async () => {
245
+ const fake = fakeAdapter();
246
+ fake.next = conflict;
247
+ const { store, controller } = setup(fake);
248
+ edit(store);
249
+ await advance(2000);
250
+ expect(status(controller)).toBe('conflict');
251
+ expect(controller.state.getState().conflictRevision).toBe(9);
252
+ edit(store);
253
+ await advance(60_000);
254
+ expect(fake.saves).toHaveLength(1);
255
+ expect(controller.hasUnsavedWork()).toBe(true);
256
+ });
257
+ it('overwrites on the revision the backend has', async () => {
258
+ const fake = fakeAdapter();
259
+ fake.next = conflict;
260
+ const { store, controller } = setup(fake);
261
+ edit(store);
262
+ await advance(2000);
263
+ fake.next = () => ({ ok: true, revision: 10, updatedAt: 't10' });
264
+ await act(async () => controller.overwrite());
265
+ expect(fake.saves[1]?.baseRevision).toBe(9);
266
+ expect(status(controller)).toBe('clean');
267
+ expect(controller.state.getState().revision).toBe(10);
268
+ });
269
+ it('reloads the backend document and drops the local changes', async () => {
270
+ const fake = fakeAdapter();
271
+ fake.next = conflict;
272
+ const { store, controller } = setup(fake);
273
+ edit(store);
274
+ await advance(2000);
275
+ const theirs = fixture();
276
+ const nodes = theirs.nodes;
277
+ delete nodes['itemBBBBB1'];
278
+ nodes['root'] = { id: 'root', type: 'buildr/page', slots: { default: ['itemAAAAA1'] } };
279
+ fake.loaded = { ...fake.loaded, revision: 9, document: theirs };
280
+ await act(async () => controller.reload());
281
+ expect(status(controller)).toBe('clean');
282
+ expect(controller.state.getState().revision).toBe(9);
283
+ expect(Object.keys(store.getState().doc.nodes).sort()).toEqual(['itemAAAAA1', 'root']);
284
+ expect(controller.hasUnsavedWork()).toBe(false);
285
+ });
286
+ });
287
+ describe('external changes', () => {
288
+ it('offers a reload when somebody saved and the document is unchanged', async () => {
289
+ const { fake, controller } = setup();
290
+ await advance(EXTERNAL_CHECK_INTERVAL_MS - 1);
291
+ expect(fake.revisionCalls).toBe(0);
292
+ await advance(1);
293
+ expect(fake.revisionCalls).toBe(1);
294
+ expect(controller.state.getState().external).toBeUndefined();
295
+ fake.remote = { revision: 2, updatedBy: 'agent@example.com' };
296
+ await advance(EXTERNAL_CHECK_INTERVAL_MS);
297
+ expect(controller.state.getState().external).toEqual({
298
+ revision: 2,
299
+ updatedBy: 'agent@example.com',
300
+ });
301
+ expect(status(controller)).toBe('clean');
302
+ fake.loaded = { ...fake.loaded, revision: 2 };
303
+ await act(async () => controller.reload());
304
+ expect(controller.state.getState()).toMatchObject({ revision: 2, external: undefined });
305
+ });
306
+ it('turns local edits after a seen external save into an immediate conflict', async () => {
307
+ const { fake, store, controller } = setup();
308
+ fake.remote = { revision: 4 };
309
+ await advance(EXTERNAL_CHECK_INTERVAL_MS);
310
+ expect(controller.state.getState().external?.revision).toBe(4);
311
+ edit(store);
312
+ expect(status(controller)).toBe('conflict');
313
+ expect(controller.state.getState().conflictRevision).toBe(4);
314
+ expect(fake.saves).toHaveLength(0);
315
+ // Overwrite builds on the revision that was seen, so it needs no second round trip.
316
+ await act(async () => controller.overwrite());
317
+ expect(fake.saves[0]).toMatchObject({ baseRevision: 4 });
318
+ expect(status(controller)).toBe('clean');
319
+ });
320
+ it('finds a save made elsewhere while dirty before the autosave is refused', async () => {
321
+ const { fake, store, controller } = setup(fakeAdapter(), 1, {
322
+ debounceMs: 60_000,
323
+ maxWaitMs: 90_000,
324
+ });
325
+ edit(store);
326
+ fake.remote = { revision: 5 };
327
+ await advance(EXTERNAL_CHECK_INTERVAL_MS);
328
+ expect(status(controller)).toBe('conflict');
329
+ expect(controller.state.getState().conflictRevision).toBe(5);
330
+ expect(fake.saves).toHaveLength(0);
331
+ });
332
+ it('does not poll while the tab is hidden, and checks on demand when it is shown again', async () => {
333
+ let visible = false;
334
+ const { fake, controller } = setup(fakeAdapter(), 1, { isVisible: () => visible });
335
+ await advance(EXTERNAL_CHECK_INTERVAL_MS * 3);
336
+ expect(fake.revisionCalls).toBe(0);
337
+ visible = true;
338
+ fake.remote = { revision: 2 };
339
+ await act(async () => controller.checkExternal());
340
+ expect(fake.revisionCalls).toBe(1);
341
+ expect(controller.state.getState().external?.revision).toBe(2);
342
+ });
343
+ it('stops polling when stopped, ignores failures, and needs getRevision', async () => {
344
+ const fake = fakeAdapter();
345
+ const { controller, stop } = setup(fake);
346
+ fake.adapter.getRevision = async () => {
347
+ fake.revisionCalls += 1;
348
+ throw new Error('offline');
349
+ };
350
+ await advance(EXTERNAL_CHECK_INTERVAL_MS);
351
+ expect(fake.revisionCalls).toBe(1);
352
+ expect(status(controller)).toBe('clean');
353
+ expect(controller.state.getState().external).toBeUndefined();
354
+ stop();
355
+ await advance(EXTERNAL_CHECK_INTERVAL_MS * 3);
356
+ expect(fake.revisionCalls).toBe(1);
357
+ const bare = fakeAdapter();
358
+ delete bare.adapter.getRevision;
359
+ const other = setup(bare);
360
+ await advance(EXTERNAL_CHECK_INTERVAL_MS * 2);
361
+ await other.controller.checkExternal();
362
+ expect(bare.revisionCalls).toBe(0);
363
+ });
364
+ it('ignores an answer that a save overtook', async () => {
365
+ const fake = fakeAdapter();
366
+ const { store, controller } = setup(fake);
367
+ let release = () => undefined;
368
+ fake.adapter.getRevision = () => new Promise((resolve) => {
369
+ release = () => resolve({ revision: 2, updatedAt: 't' });
370
+ });
371
+ const pending = controller.checkExternal();
372
+ edit(store);
373
+ await advance(2000);
374
+ expect(controller.state.getState().revision).toBe(2);
375
+ release();
376
+ await act(async () => pending);
377
+ expect(controller.state.getState().external).toBeUndefined();
378
+ expect(status(controller)).toBe('clean');
379
+ });
380
+ });
381
+ describe('loadDocument', () => {
382
+ it('returns the validated document', async () => {
383
+ const fake = fakeAdapter();
384
+ const loaded = await loadDocument(fake.adapter, ref);
385
+ expect(loaded.revision).toBe(5);
386
+ expect(loaded.readOnly).toBe(false);
387
+ });
388
+ it('keeps the context the canvas binds against by default', async () => {
389
+ const fake = fakeAdapter();
390
+ fake.loaded = { ...fake.loaded, contextRef: 'pages:7' };
391
+ expect((await loadDocument(fake.adapter, ref)).contextRef).toBe('pages:7');
392
+ });
393
+ it('opens a document read-only when the session cannot edit or the backend says so', async () => {
394
+ const fake = fakeAdapter();
395
+ fake.canEdit = false;
396
+ expect((await loadDocument(fake.adapter, ref)).readOnly).toBe(true);
397
+ fake.canEdit = true;
398
+ fake.loaded = { ...fake.loaded, readOnly: true };
399
+ expect((await loadDocument(fake.adapter, ref)).readOnly).toBe(true);
400
+ });
401
+ it('refuses malformed replies and invalid documents', async () => {
402
+ const fake = fakeAdapter();
403
+ fake.loaded = { title: 1 };
404
+ await expect(loadDocument(fake.adapter, ref)).rejects.toBeInstanceOf(LoadError);
405
+ fake.loaded = { ...fakeAdapter().loaded, document: { nope: true } };
406
+ await expect(loadDocument(fake.adapter, ref)).rejects.toBeInstanceOf(LoadError);
407
+ });
408
+ });
409
+ describe('PersistenceProvider', () => {
410
+ let container;
411
+ let root;
412
+ let saveAction;
413
+ function Probe() {
414
+ saveAction = useSaveAction();
415
+ return _jsx(SaveStatus, {});
416
+ }
417
+ async function mount(fake) {
418
+ const { store, controller } = setup(fake);
419
+ await act(async () => root.render(_jsx(MessagesProvider, { locale: "en", children: _jsx(ToastProvider, { children: _jsx(EditorStoreProvider, { store: store, children: _jsx(PersistenceProvider, { controller: controller, children: _jsx(Probe, {}) }) }) }) })));
420
+ return { store, controller };
421
+ }
422
+ beforeEach(() => {
423
+ container = document.createElement('div');
424
+ document.body.append(container);
425
+ root = createRoot(container);
426
+ });
427
+ afterEach(() => {
428
+ act(() => root.unmount());
429
+ document.body.innerHTML = '';
430
+ });
431
+ it('shows the status and saves on the save action', async () => {
432
+ const fake = fakeAdapter();
433
+ const { store } = await mount(fake);
434
+ expect(container.textContent).toContain('All changes saved');
435
+ edit(store);
436
+ expect(container.textContent).toContain('Unsaved changes');
437
+ await act(async () => {
438
+ expect(saveAction()).toBe(true);
439
+ });
440
+ expect(fake.saves).toHaveLength(1);
441
+ expect(container.textContent).toContain('All changes saved');
442
+ });
443
+ it('warns before the tab closes while there is unsaved work', async () => {
444
+ const fake = fakeAdapter();
445
+ const { store } = await mount(fake);
446
+ const clean = new Event('beforeunload', { cancelable: true });
447
+ window.dispatchEvent(clean);
448
+ expect(clean.defaultPrevented).toBe(false);
449
+ edit(store);
450
+ const dirty = new Event('beforeunload', { cancelable: true });
451
+ window.dispatchEvent(dirty);
452
+ expect(dirty.defaultPrevented).toBe(true);
453
+ });
454
+ it('announces who saved in the toast region, and reloads from it', async () => {
455
+ const fake = fakeAdapter();
456
+ const { controller } = await mount(fake);
457
+ const banner = () => container.ownerDocument.querySelector('.bd-toast-region [role=status]');
458
+ expect(banner()?.textContent).toBe('');
459
+ fake.remote = { revision: 2, updatedBy: 'Claude agent' };
460
+ fake.loaded = { ...fake.loaded, revision: 2 };
461
+ await act(async () => window.dispatchEvent(new Event('focus')));
462
+ expect(banner()?.textContent).toContain('Claude agent saved a newer version');
463
+ const reload = banner()?.querySelector('button');
464
+ expect(reload?.textContent).toBe('Reload the latest version');
465
+ await act(async () => reload?.click());
466
+ expect(banner()?.textContent).toBe('');
467
+ expect(controller.state.getState().revision).toBe(2);
468
+ });
469
+ it('asks the author to choose in a conflict', async () => {
470
+ const fake = fakeAdapter();
471
+ fake.next = () => ({ ok: false, kind: 'conflict', currentRevision: 9 });
472
+ const { store, controller } = await mount(fake);
473
+ edit(store);
474
+ await advance(2000);
475
+ const dialog = document.querySelector('[role=dialog]');
476
+ expect(dialog?.textContent).toContain('This page was changed elsewhere');
477
+ fake.next = () => ({ ok: true, revision: 10, updatedAt: 't10' });
478
+ const overwrite = [...(dialog?.querySelectorAll('button') ?? [])].find((b) => b.textContent?.includes('Overwrite'));
479
+ await act(async () => overwrite?.click());
480
+ expect(status(controller)).toBe('clean');
481
+ expect(document.querySelector('[role=dialog]')).toBeNull();
482
+ });
483
+ });
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { PersistenceController } from './controller.ts';
3
+ import type { PersistenceState } from './types.ts';
4
+ export declare function usePersistence(): PersistenceController;
5
+ export declare function usePersistenceState<T>(selector: (state: PersistenceState) => T): T;
6
+ /**
7
+ * Runs the autosave of `controller`, warns before the tab is closed while work would be lost, and
8
+ * shows the conflict dialog. `useSaveAction` gives the handler for the Ctrl+S shortcut.
9
+ */
10
+ export declare function PersistenceProvider(props: {
11
+ readonly controller: PersistenceController;
12
+ readonly children: ReactNode;
13
+ }): import("react").JSX.Element;
14
+ /** For `<ShortcutProvider actions={{ save }}>`: Ctrl+S saves now and takes the key from the browser. */
15
+ export declare function useSaveAction(): () => boolean;
16
+ /** A short status line for the toolbar (PB-083): saved, unsaved, saving, failed, conflict. */
17
+ export declare function SaveStatus(): import("react").JSX.Element;
18
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/persistence/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAyB,MAAM,OAAO,CAAC;AAK7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD,wBAAgB,cAAc,IAAI,qBAAqB,CAMtD;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,CAAC,GAAG,CAAC,CAElF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B,+BA8BA;AAED,wGAAwG;AACxG,wBAAgB,aAAa,IAAI,MAAM,OAAO,CAM7C;AAwDD,8FAA8F;AAC9F,wBAAgB,UAAU,gCAqBzB"}
@@ -0,0 +1,105 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect } from 'react';
3
+ import { useStore } from 'zustand';
4
+ import { useT } from '../messages/index.js';
5
+ import { useEditorState } from '../store/index.js';
6
+ import { Button, Dialog, useToast } from '../ui/index.js';
7
+ const PersistenceContext = createContext(undefined);
8
+ export function usePersistence() {
9
+ const controller = useContext(PersistenceContext);
10
+ if (controller === undefined) {
11
+ throw new Error('usePersistence must be used inside <PersistenceProvider>');
12
+ }
13
+ return controller;
14
+ }
15
+ export function usePersistenceState(selector) {
16
+ return useStore(usePersistence().state, selector);
17
+ }
18
+ /**
19
+ * Runs the autosave of `controller`, warns before the tab is closed while work would be lost, and
20
+ * shows the conflict dialog. `useSaveAction` gives the handler for the Ctrl+S shortcut.
21
+ */
22
+ export function PersistenceProvider(props) {
23
+ const { controller, children } = props;
24
+ useEffect(() => controller.start(), [controller]);
25
+ useEffect(() => {
26
+ const warn = (event) => {
27
+ if (!controller.hasUnsavedWork())
28
+ return;
29
+ event.preventDefault();
30
+ // Some browsers still want a return value to show their prompt.
31
+ event.returnValue = '';
32
+ };
33
+ window.addEventListener('beforeunload', warn);
34
+ return () => window.removeEventListener('beforeunload', warn);
35
+ }, [controller]);
36
+ useEffect(() => {
37
+ // Coming back to the tab or window is when somebody else's save is most likely to matter.
38
+ const check = () => void controller.checkExternal();
39
+ window.addEventListener('focus', check);
40
+ document.addEventListener('visibilitychange', check);
41
+ return () => {
42
+ window.removeEventListener('focus', check);
43
+ document.removeEventListener('visibilitychange', check);
44
+ };
45
+ }, [controller]);
46
+ return (_jsxs(PersistenceContext.Provider, { value: controller, children: [children, _jsx(ExternalChangeBanner, {}), _jsx(ConflictDialog, {})] }));
47
+ }
48
+ /** For `<ShortcutProvider actions={{ save }}>`: Ctrl+S saves now and takes the key from the browser. */
49
+ export function useSaveAction() {
50
+ const controller = usePersistence();
51
+ return () => {
52
+ void controller.saveNow();
53
+ return true;
54
+ };
55
+ }
56
+ /**
57
+ * A non-blocking notice, shown as a persistent toast, that somebody else saved while this document
58
+ * is unchanged: one click reloads. It goes away when the external change is gone.
59
+ */
60
+ function ExternalChangeBanner() {
61
+ const t = useT();
62
+ const toast = useToast();
63
+ const controller = usePersistence();
64
+ const external = usePersistenceState((state) => state.external);
65
+ const updatedBy = external?.updatedBy;
66
+ const present = external !== undefined;
67
+ useEffect(() => {
68
+ if (!present) {
69
+ toast.dismiss('external-changes');
70
+ return;
71
+ }
72
+ toast.show({
73
+ id: 'external-changes',
74
+ variant: 'info',
75
+ duration: null,
76
+ message: updatedBy === undefined
77
+ ? t('external.banner')
78
+ : t('external.banner.by').replace('{name}', updatedBy),
79
+ action: { label: t('external.reload'), onSelect: () => void controller.reload() },
80
+ });
81
+ }, [present, updatedBy, toast, t, controller]);
82
+ return null;
83
+ }
84
+ function ConflictDialog() {
85
+ const t = useT();
86
+ const controller = usePersistence();
87
+ const status = usePersistenceState((state) => state.status);
88
+ return (_jsx(Dialog, { open: status === 'conflict', hideClose: true,
89
+ // The choice is the only way out: closing without one would leave the editor stuck in conflict.
90
+ onOpenChange: () => undefined, title: t('save.conflict.title'), description: t('save.conflict.description'), footer: _jsxs(_Fragment, { children: [_jsx(Button, { onClick: () => void controller.reload(), children: t('save.conflict.reload') }), _jsx(Button, { variant: "danger", onClick: () => void controller.overwrite(), children: t('save.conflict.overwrite') })] }) }));
91
+ }
92
+ /** A short status line for the toolbar (PB-083): saved, unsaved, saving, failed, conflict. */
93
+ export function SaveStatus() {
94
+ const t = useT();
95
+ const controller = usePersistence();
96
+ const status = usePersistenceState((state) => state.status);
97
+ const error = usePersistenceState((state) => state.error);
98
+ const readOnly = useEditorState((state) => state.readOnly);
99
+ const text = readOnly
100
+ ? t('save.readOnly')
101
+ : status === 'error' && error?.kind === 'invalid'
102
+ ? t('save.invalid')
103
+ : t(`save.${status}`);
104
+ return (_jsxs("span", { className: "bd-save-status", "data-status": readOnly ? 'readOnly' : status, role: "status", children: [text, !readOnly && (status === 'dirty' || status === 'error') && (_jsx(Button, { variant: "ghost", onClick: () => void controller.saveNow(), children: t('save.now') }))] }));
105
+ }