@elytracms/studio 0.0.93 → 0.0.94

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 (481) hide show
  1. package/convex/analytics-cost.test.ts +817 -0
  2. package/convex/auth.ts +5 -2
  3. package/convex/authority.ts +35 -11
  4. package/convex/contract-parity.test.ts +7 -0
  5. package/convex/delivery.ts +12 -9
  6. package/convex/guard.ts +9 -7
  7. package/convex/http-routes.test.ts +18 -8
  8. package/convex/http.ts +23 -17
  9. package/convex/ingest.test.ts +326 -0
  10. package/convex/ingest.ts +281 -0
  11. package/convex/invites.ts +13 -5
  12. package/convex/members.ts +10 -4
  13. package/convex/record-functions.ts +325 -0
  14. package/convex/records.test.ts +156 -0
  15. package/convex/redirect-migration.test.ts +205 -0
  16. package/convex/redirect-revalidate.test.ts +5 -1
  17. package/convex/redirects.ts +120 -79
  18. package/convex/role-policy.test.ts +315 -0
  19. package/convex/roles.ts +83 -0
  20. package/convex/schema.ts +30 -0
  21. package/convex/triggers.ts +21 -1
  22. package/dist/addon.d.ts +71 -0
  23. package/dist/addon.js +67 -0
  24. package/dist/addon.js.map +1 -0
  25. package/dist/components/account/account-settings.js +2 -2
  26. package/dist/components/account/account-settings.js.map +1 -1
  27. package/dist/components/account/credential-panel.js +2 -2
  28. package/dist/components/account/credential-panel.js.map +1 -1
  29. package/dist/components/account/self-account-schema.d.ts +6 -15
  30. package/dist/components/account/self-account-schema.js +52 -34
  31. package/dist/components/account/self-account-schema.js.map +1 -1
  32. package/dist/components/account/self-account-view-source.d.ts +1 -1
  33. package/dist/components/account/self-account-view-source.js +6 -4
  34. package/dist/components/account/self-account-view-source.js.map +1 -1
  35. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  36. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  37. package/dist/components/ai/ai-chat-panel.js +6 -6
  38. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  39. package/dist/components/ai/ai-cms-panel.js +2 -2
  40. package/dist/components/ai/ai-cms-panel.js.map +1 -1
  41. package/dist/components/ai/ai-panel-toggle.js +4 -4
  42. package/dist/components/ai/ai-panel-toggle.js.map +1 -1
  43. package/dist/components/ai/chat-ui.js +2 -2
  44. package/dist/components/ai/chat-ui.js.map +1 -1
  45. package/dist/components/ai/proposal-card.d.ts +2 -2
  46. package/dist/components/ai/proposal-card.js +2 -2
  47. package/dist/components/ai/proposal-card.js.map +1 -1
  48. package/dist/components/ai/proposal-issues.js +2 -2
  49. package/dist/components/ai/proposal-issues.js.map +1 -1
  50. package/dist/components/ai/translate-document-action.js +2 -2
  51. package/dist/components/ai/translate-document-action.js.map +1 -1
  52. package/dist/components/ai/translate-document-dialog.js +6 -6
  53. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  54. package/dist/components/ai/translate-skipped.d.ts +1 -1
  55. package/dist/components/ai/translate-skipped.js +2 -2
  56. package/dist/components/ai/translate-skipped.js.map +1 -1
  57. package/dist/components/app-views/default-modules.d.ts +50 -2
  58. package/dist/components/app-views/default-modules.js +65 -20
  59. package/dist/components/app-views/default-modules.js.map +1 -1
  60. package/dist/components/app-views/module.d.ts +53 -3
  61. package/dist/components/app-views/module.js +14 -7
  62. package/dist/components/app-views/module.js.map +1 -1
  63. package/dist/components/app-views/sidebar.js +1 -1
  64. package/dist/components/app-views/sidebar.js.map +1 -1
  65. package/dist/components/app-views/studio-config.d.ts +11 -2
  66. package/dist/components/app-views/studio-config.js.map +1 -1
  67. package/dist/components/assets/asset-detail.js +2 -2
  68. package/dist/components/assets/asset-detail.js.map +1 -1
  69. package/dist/components/assets/asset-grid.js +2 -2
  70. package/dist/components/assets/asset-grid.js.map +1 -1
  71. package/dist/components/assets/asset-thumb.js +3 -3
  72. package/dist/components/assets/asset-thumb.js.map +1 -1
  73. package/dist/components/assets/assets-collection-view-source.d.ts +2 -2
  74. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  75. package/dist/components/assets/assets-module.js +1 -1
  76. package/dist/components/assets/assets-module.js.map +1 -1
  77. package/dist/components/assets/assets-store.d.ts +2 -2
  78. package/dist/components/assets/assets-store.js +6 -6
  79. package/dist/components/assets/assets-store.js.map +1 -1
  80. package/dist/components/assets/assets-view-preference.js +1 -1
  81. package/dist/components/assets/assets-view-preference.js.map +1 -1
  82. package/dist/components/assets/replace-file.js +1 -1
  83. package/dist/components/assets/replace-file.js.map +1 -1
  84. package/dist/components/auth-context.d.ts +1 -1
  85. package/dist/components/auth-context.js +6 -6
  86. package/dist/components/auth-context.js.map +1 -1
  87. package/dist/components/builder/blocks-model.d.ts +1 -1
  88. package/dist/components/builder/blocks-model.js +3 -3
  89. package/dist/components/builder/blocks-model.js.map +1 -1
  90. package/dist/components/builder/blocks-panel.d.ts +1 -1
  91. package/dist/components/builder/blocks-panel.js +4 -4
  92. package/dist/components/builder/blocks-panel.js.map +1 -1
  93. package/dist/components/builder/builder-store.js +2 -2
  94. package/dist/components/builder/builder-store.js.map +1 -1
  95. package/dist/components/builder/canvas-breakpoints.js +1 -1
  96. package/dist/components/builder/canvas-breakpoints.js.map +1 -1
  97. package/dist/components/builder/canvas-dnd.d.ts +2 -2
  98. package/dist/components/builder/canvas-dnd.js +1 -1
  99. package/dist/components/builder/canvas-dnd.js.map +1 -1
  100. package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
  101. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  102. package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
  103. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
  104. package/dist/components/builder/canvas-render-context.d.ts +1 -1
  105. package/dist/components/builder/canvas-render-context.js +4 -4
  106. package/dist/components/builder/canvas-render-context.js.map +1 -1
  107. package/dist/components/builder/canvas-theme-notice.js +2 -2
  108. package/dist/components/builder/canvas-theme-notice.js.map +1 -1
  109. package/dist/components/builder/canvas.js +3 -3
  110. package/dist/components/builder/canvas.js.map +1 -1
  111. package/dist/components/builder/clipboard-actions.d.ts +2 -2
  112. package/dist/components/builder/clipboard-actions.js +4 -4
  113. package/dist/components/builder/clipboard-actions.js.map +1 -1
  114. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  115. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  116. package/dist/components/builder/composition-field-editor.d.ts +1 -1
  117. package/dist/components/builder/composition-field-editor.js +3 -3
  118. package/dist/components/builder/composition-field-editor.js.map +1 -1
  119. package/dist/components/builder/drag-session.d.ts +1 -1
  120. package/dist/components/builder/drag-session.js.map +1 -1
  121. package/dist/components/builder/fixture-thumbnail.js +1 -1
  122. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  123. package/dist/components/builder/insert-context.d.ts +1 -1
  124. package/dist/components/builder/insert-context.js +3 -3
  125. package/dist/components/builder/insert-context.js.map +1 -1
  126. package/dist/components/builder/layout-inspector.js +1 -1
  127. package/dist/components/builder/layout-inspector.js.map +1 -1
  128. package/dist/components/builder/node-title.js +1 -1
  129. package/dist/components/builder/node-title.js.map +1 -1
  130. package/dist/components/builder/page-tree.d.ts +1 -1
  131. package/dist/components/builder/page-tree.js +5 -5
  132. package/dist/components/builder/page-tree.js.map +1 -1
  133. package/dist/components/builder/placement-refusal.js +3 -3
  134. package/dist/components/builder/placement-refusal.js.map +1 -1
  135. package/dist/components/builder/property-inspector.js +4 -4
  136. package/dist/components/builder/property-inspector.js.map +1 -1
  137. package/dist/components/builder/reuse-context.js +4 -4
  138. package/dist/components/builder/reuse-context.js.map +1 -1
  139. package/dist/components/builder/reuse.js +1 -1
  140. package/dist/components/builder/reuse.js.map +1 -1
  141. package/dist/components/builder/rich-text-toolbar.js +1 -1
  142. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  143. package/dist/components/builder/run-inspector.js +1 -1
  144. package/dist/components/builder/run-inspector.js.map +1 -1
  145. package/dist/components/builder/selection-overlay.js +3 -3
  146. package/dist/components/builder/selection-overlay.js.map +1 -1
  147. package/dist/components/builder/spacing-handles.d.ts +2 -2
  148. package/dist/components/builder/spacing-handles.js +3 -3
  149. package/dist/components/builder/spacing-handles.js.map +1 -1
  150. package/dist/components/cms/asset-field.js +2 -2
  151. package/dist/components/cms/asset-field.js.map +1 -1
  152. package/dist/components/cms/asset-picker.js +2 -2
  153. package/dist/components/cms/asset-picker.js.map +1 -1
  154. package/dist/components/cms/bulk-actions.d.ts +1 -1
  155. package/dist/components/cms/bulk-actions.js +2 -2
  156. package/dist/components/cms/bulk-actions.js.map +1 -1
  157. package/dist/components/cms/bulk-bar.d.ts +2 -2
  158. package/dist/components/cms/bulk-bar.js +2 -2
  159. package/dist/components/cms/bulk-bar.js.map +1 -1
  160. package/dist/components/cms/cell-edit.d.ts +1 -1
  161. package/dist/components/cms/cell-edit.js +1 -1
  162. package/dist/components/cms/cell-edit.js.map +1 -1
  163. package/dist/components/cms/cell.d.ts +1 -1
  164. package/dist/components/cms/cell.js +3 -3
  165. package/dist/components/cms/cell.js.map +1 -1
  166. package/dist/components/cms/cms-index.js +2 -2
  167. package/dist/components/cms/cms-index.js.map +1 -1
  168. package/dist/components/cms/cms-labels.js +2 -2
  169. package/dist/components/cms/cms-labels.js.map +1 -1
  170. package/dist/components/cms/cms-store.d.ts +2 -2
  171. package/dist/components/cms/cms-store.js +10 -10
  172. package/dist/components/cms/cms-store.js.map +1 -1
  173. package/dist/components/cms/collection-table.js +7 -7
  174. package/dist/components/cms/collection-table.js.map +1 -1
  175. package/dist/components/cms/detail-editor.d.ts +5 -2
  176. package/dist/components/cms/detail-editor.js +20 -14
  177. package/dist/components/cms/detail-editor.js.map +1 -1
  178. package/dist/components/cms/details-pane.js +1 -1
  179. package/dist/components/cms/details-pane.js.map +1 -1
  180. package/dist/components/cms/doc-state.js +1 -1
  181. package/dist/components/cms/doc-state.js.map +1 -1
  182. package/dist/components/cms/document-diff-view.js +5 -5
  183. package/dist/components/cms/document-diff-view.js.map +1 -1
  184. package/dist/components/cms/document-editor-context.js +1 -1
  185. package/dist/components/cms/document-editor-context.js.map +1 -1
  186. package/dist/components/cms/document-staging.js +1 -1
  187. package/dist/components/cms/document-staging.js.map +1 -1
  188. package/dist/components/cms/document-url-path.js +2 -2
  189. package/dist/components/cms/document-url-path.js.map +1 -1
  190. package/dist/components/cms/duplicate-document.d.ts +1 -1
  191. package/dist/components/cms/duplicate-document.js.map +1 -1
  192. package/dist/components/cms/editable-cell.js +3 -3
  193. package/dist/components/cms/editable-cell.js.map +1 -1
  194. package/dist/components/cms/field-collaboration.d.ts +1 -1
  195. package/dist/components/cms/field-collaboration.js +1 -1
  196. package/dist/components/cms/field-collaboration.js.map +1 -1
  197. package/dist/components/cms/field-connection.d.ts +1 -1
  198. package/dist/components/cms/field-connection.js +2 -2
  199. package/dist/components/cms/field-connection.js.map +1 -1
  200. package/dist/components/cms/field-control-lib.js +1 -1
  201. package/dist/components/cms/field-control-lib.js.map +1 -1
  202. package/dist/components/cms/field-control.js +4 -4
  203. package/dist/components/cms/field-control.js.map +1 -1
  204. package/dist/components/cms/field-renderer.d.ts +1 -1
  205. package/dist/components/cms/field-renderer.js +2 -2
  206. package/dist/components/cms/field-renderer.js.map +1 -1
  207. package/dist/components/cms/filter-builder.d.ts +1 -1
  208. package/dist/components/cms/filter-builder.js +3 -3
  209. package/dist/components/cms/filter-builder.js.map +1 -1
  210. package/dist/components/cms/gate-validation.js +1 -1
  211. package/dist/components/cms/gate-validation.js.map +1 -1
  212. package/dist/components/cms/import-export.js +2 -2
  213. package/dist/components/cms/import-export.js.map +1 -1
  214. package/dist/components/cms/issue-copy.js +1 -1
  215. package/dist/components/cms/issue-copy.js.map +1 -1
  216. package/dist/components/cms/issues-pane.js +3 -3
  217. package/dist/components/cms/issues-pane.js.map +1 -1
  218. package/dist/components/cms/object-array-field.js +2 -2
  219. package/dist/components/cms/object-array-field.js.map +1 -1
  220. package/dist/components/cms/prop-fields.js +2 -2
  221. package/dist/components/cms/prop-fields.js.map +1 -1
  222. package/dist/components/cms/reference-confirm.d.ts +1 -1
  223. package/dist/components/cms/reference-confirm.js +2 -2
  224. package/dist/components/cms/reference-confirm.js.map +1 -1
  225. package/dist/components/cms/relation-picker.js +2 -2
  226. package/dist/components/cms/relation-picker.js.map +1 -1
  227. package/dist/components/cms/rich-text/block-style.js +1 -1
  228. package/dist/components/cms/rich-text/block-style.js.map +1 -1
  229. package/dist/components/cms/rich-text/component-embed.js +4 -4
  230. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  231. package/dist/components/cms/rich-text/editor.js +3 -3
  232. package/dist/components/cms/rich-text/editor.js.map +1 -1
  233. package/dist/components/cms/rich-text/embed-picker.js +3 -3
  234. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  235. package/dist/components/cms/rich-text/embed-props.js +3 -3
  236. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  237. package/dist/components/cms/rich-text/extensions.js +1 -1
  238. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  239. package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
  240. package/dist/components/cms/rich-text/font-size-control.js +2 -2
  241. package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
  242. package/dist/components/cms/rich-text/lazy.js +1 -1
  243. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  244. package/dist/components/cms/rich-text/link-control.js +2 -2
  245. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  246. package/dist/components/cms/rich-text/slash-command.js +3 -3
  247. package/dist/components/cms/rich-text/slash-command.js.map +1 -1
  248. package/dist/components/cms/rich-text/toolbar.js +1 -1
  249. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  250. package/dist/components/cms/rich-text/unknown-node.js +2 -2
  251. package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
  252. package/dist/components/cms/rich-text-preview.js +2 -2
  253. package/dist/components/cms/rich-text-preview.js.map +1 -1
  254. package/dist/components/cms/row-actions.d.ts +1 -1
  255. package/dist/components/cms/row-actions.js.map +1 -1
  256. package/dist/components/cms/scalar-value-editor.d.ts +1 -1
  257. package/dist/components/cms/scalar-value-editor.js +2 -2
  258. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  259. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  260. package/dist/components/cms/use-discard-controller.js +1 -1
  261. package/dist/components/cms/use-discard-controller.js.map +1 -1
  262. package/dist/components/cms/use-document-draft.d.ts +7 -1
  263. package/dist/components/cms/use-document-draft.js +16 -2
  264. package/dist/components/cms/use-document-draft.js.map +1 -1
  265. package/dist/components/cms/use-document-versions.js +1 -1
  266. package/dist/components/cms/use-document-versions.js.map +1 -1
  267. package/dist/components/cms/use-field-focus.d.ts +1 -1
  268. package/dist/components/cms/use-field-focus.js +2 -2
  269. package/dist/components/cms/use-field-focus.js.map +1 -1
  270. package/dist/components/cms/use-live-url.d.ts +1 -1
  271. package/dist/components/cms/use-live-url.js +3 -3
  272. package/dist/components/cms/use-live-url.js.map +1 -1
  273. package/dist/components/cms/use-publish-controller.d.ts +1 -1
  274. package/dist/components/cms/use-publish-controller.js +1 -1
  275. package/dist/components/cms/use-publish-controller.js.map +1 -1
  276. package/dist/components/cms/use-published-perspective.d.ts +1 -1
  277. package/dist/components/cms/use-published-perspective.js.map +1 -1
  278. package/dist/components/cms/use-restore-version.d.ts +1 -1
  279. package/dist/components/cms/use-restore-version.js +1 -1
  280. package/dist/components/cms/use-restore-version.js.map +1 -1
  281. package/dist/components/cms/version-compare-lazy.js +1 -1
  282. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  283. package/dist/components/cms/version-compare.js +1 -1
  284. package/dist/components/cms/version-compare.js.map +1 -1
  285. package/dist/components/cms/versions-pane.js +3 -3
  286. package/dist/components/cms/versions-pane.js.map +1 -1
  287. package/dist/components/cms/view-bar.js +3 -3
  288. package/dist/components/cms/view-bar.js.map +1 -1
  289. package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
  290. package/dist/components/collection-view/cms-collection-view-source.js +2 -2
  291. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  292. package/dist/components/collection-view/collection-view-context.d.ts +1 -1
  293. package/dist/components/collection-view/collection-view-context.js +1 -1
  294. package/dist/components/collection-view/collection-view-context.js.map +1 -1
  295. package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
  296. package/dist/components/collection-view/collection-view-surface.js +18 -0
  297. package/dist/components/collection-view/collection-view-surface.js.map +1 -0
  298. package/dist/components/components/component-detail.js +10 -10
  299. package/dist/components/components/component-detail.js.map +1 -1
  300. package/dist/components/components/component-preview.d.ts +2 -2
  301. package/dist/components/components/component-preview.js +5 -5
  302. package/dist/components/components/component-preview.js.map +1 -1
  303. package/dist/components/components/component-status.d.ts +1 -1
  304. package/dist/components/components/component-status.js +1 -1
  305. package/dist/components/components/component-status.js.map +1 -1
  306. package/dist/components/components/components-index.js +2 -2
  307. package/dist/components/components/components-index.js.map +1 -1
  308. package/dist/components/components/field-inspector.js +1 -1
  309. package/dist/components/components/field-inspector.js.map +1 -1
  310. package/dist/components/components/registry-browser.js +3 -3
  311. package/dist/components/components/registry-browser.js.map +1 -1
  312. package/dist/components/components/taxonomy.d.ts +1 -1
  313. package/dist/components/components/taxonomy.js.map +1 -1
  314. package/dist/components/components/transfer-dialog.js +3 -3
  315. package/dist/components/components/transfer-dialog.js.map +1 -1
  316. package/dist/components/current-user-menu.js +1 -1
  317. package/dist/components/current-user-menu.js.map +1 -1
  318. package/dist/components/delivery-health-banner.js +2 -2
  319. package/dist/components/delivery-health-banner.js.map +1 -1
  320. package/dist/components/deployment-setup-gate.js +3 -3
  321. package/dist/components/deployment-setup-gate.js.map +1 -1
  322. package/dist/components/dev-login-badge.js +2 -2
  323. package/dist/components/dev-login-badge.js.map +1 -1
  324. package/dist/components/guards.js +1 -1
  325. package/dist/components/guards.js.map +1 -1
  326. package/dist/components/inspector/document-panes.d.ts +54 -0
  327. package/dist/components/inspector/document-panes.js +41 -0
  328. package/dist/components/inspector/document-panes.js.map +1 -0
  329. package/dist/components/inspector/inspector-sidebar.js +1 -1
  330. package/dist/components/inspector/inspector-sidebar.js.map +1 -1
  331. package/dist/components/nav-issues-registry.d.ts +53 -0
  332. package/dist/components/nav-issues-registry.js +69 -0
  333. package/dist/components/nav-issues-registry.js.map +1 -0
  334. package/dist/components/nav-issues.js +4 -4
  335. package/dist/components/nav-issues.js.map +1 -1
  336. package/dist/components/operation-error.d.ts +1 -1
  337. package/dist/components/operation-error.js +4 -4
  338. package/dist/components/operation-error.js.map +1 -1
  339. package/dist/components/playground-badge.js +2 -2
  340. package/dist/components/playground-badge.js.map +1 -1
  341. package/dist/components/project-list.js +3 -3
  342. package/dist/components/project-list.js.map +1 -1
  343. package/dist/components/project-session.d.ts +2 -2
  344. package/dist/components/project-session.js +5 -5
  345. package/dist/components/project-session.js.map +1 -1
  346. package/dist/components/project-switcher.js +2 -2
  347. package/dist/components/project-switcher.js.map +1 -1
  348. package/dist/components/rail-store-status.js +1 -1
  349. package/dist/components/rail-store-status.js.map +1 -1
  350. package/dist/components/rail-view-source.d.ts +6 -5
  351. package/dist/components/rail-view-source.js +4 -3
  352. package/dist/components/rail-view-source.js.map +1 -1
  353. package/dist/components/records/record-lens.d.ts +51 -0
  354. package/dist/components/records/record-lens.js +156 -0
  355. package/dist/components/records/record-lens.js.map +1 -0
  356. package/dist/components/records/record-rail.d.ts +86 -0
  357. package/dist/components/records/record-rail.js +241 -0
  358. package/dist/components/records/record-rail.js.map +1 -0
  359. package/dist/components/redirects/redirects-schema.js +1 -1
  360. package/dist/components/redirects/redirects-schema.js.map +1 -1
  361. package/dist/components/references/references-panel.js +2 -2
  362. package/dist/components/references/references-panel.js.map +1 -1
  363. package/dist/components/section-nav.js +27 -11
  364. package/dist/components/section-nav.js.map +1 -1
  365. package/dist/components/section-state.js +2 -2
  366. package/dist/components/section-state.js.map +1 -1
  367. package/dist/components/section-surface.js +2 -2
  368. package/dist/components/section-surface.js.map +1 -1
  369. package/dist/components/session-expired-overlay.js +3 -3
  370. package/dist/components/session-expired-overlay.js.map +1 -1
  371. package/dist/components/settings-form.js +1 -1
  372. package/dist/components/settings-form.js.map +1 -1
  373. package/dist/components/sign-in-needed-badge.js +1 -1
  374. package/dist/components/sign-in-needed-badge.js.map +1 -1
  375. package/dist/components/sign-in-view.d.ts +2 -2
  376. package/dist/components/sign-in-view.js +5 -5
  377. package/dist/components/sign-in-view.js.map +1 -1
  378. package/dist/components/studio-context.js +3 -3
  379. package/dist/components/studio-context.js.map +1 -1
  380. package/dist/components/studio-project-screen.js +30 -3
  381. package/dist/components/studio-project-screen.js.map +1 -1
  382. package/dist/components/studio-shell.js +6 -5
  383. package/dist/components/studio-shell.js.map +1 -1
  384. package/dist/components/theme-context.d.ts +1 -1
  385. package/dist/components/theme-context.js +2 -2
  386. package/dist/components/theme-context.js.map +1 -1
  387. package/dist/components/use-hydration-phase.d.ts +1 -1
  388. package/dist/components/use-hydration-phase.js +1 -1
  389. package/dist/components/use-hydration-phase.js.map +1 -1
  390. package/dist/components/users/invite-user-bar.d.ts +27 -0
  391. package/dist/components/users/invite-user-bar.js +25 -9
  392. package/dist/components/users/invite-user-bar.js.map +1 -1
  393. package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
  394. package/dist/components/users/role-policy-drift-issues.js +71 -0
  395. package/dist/components/users/role-policy-drift-issues.js.map +1 -0
  396. package/dist/components/users/users-collection-view-source.d.ts +1 -1
  397. package/dist/components/users/users-collection-view-source.js +4 -2
  398. package/dist/components/users/users-collection-view-source.js.map +1 -1
  399. package/dist/components/users/users-module.js +1 -1
  400. package/dist/components/users/users-module.js.map +1 -1
  401. package/dist/components/users/users-schema.d.ts +10 -1
  402. package/dist/components/users/users-schema.js +45 -25
  403. package/dist/components/users/users-schema.js.map +1 -1
  404. package/dist/components/users/users-store.js +3 -3
  405. package/dist/components/users/users-store.js.map +1 -1
  406. package/dist/components/workspace-projects.d.ts +1 -1
  407. package/dist/components/workspace-projects.js +1 -1
  408. package/dist/components/workspace-projects.js.map +1 -1
  409. package/dist/lib/ai/index.d.ts +1 -1
  410. package/dist/lib/ai/index.js +1 -1
  411. package/dist/lib/ai/index.js.map +1 -1
  412. package/dist/lib/ai/node-handler.d.ts +1 -1
  413. package/dist/lib/ai/node-handler.js.map +1 -1
  414. package/dist/lib/ai/translate-document.js +1 -1
  415. package/dist/lib/ai/translate-document.js.map +1 -1
  416. package/dist/lib/clipboard/paste-issues.js +1 -1
  417. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  418. package/dist/lib/clipboard/paste-target.js +2 -2
  419. package/dist/lib/clipboard/paste-target.js.map +1 -1
  420. package/dist/lib/cms/published-documents.js +1 -1
  421. package/dist/lib/cms/published-documents.js.map +1 -1
  422. package/dist/lib/cms-views/view-model.d.ts +12 -12
  423. package/dist/lib/cms-views/view-model.js +2 -2
  424. package/dist/lib/cms-views/view-model.js.map +1 -1
  425. package/dist/lib/collection-view/collection-view-source.d.ts +7 -2
  426. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  427. package/dist/lib/data/content-session-refresh.d.ts +1 -1
  428. package/dist/lib/data/content-session-refresh.js.map +1 -1
  429. package/dist/lib/data/playground-unload-guard.d.ts +1 -1
  430. package/dist/lib/data/playground-unload-guard.js +1 -1
  431. package/dist/lib/data/playground-unload-guard.js.map +1 -1
  432. package/dist/lib/engine-version.d.ts +1 -1
  433. package/dist/lib/engine-version.js +1 -1
  434. package/dist/lib/engine-version.js.map +1 -1
  435. package/dist/lib/i18n/i18n.js +1 -1
  436. package/dist/lib/i18n/i18n.js.map +1 -1
  437. package/dist/lib/i18n/messages.de.js +4 -0
  438. package/dist/lib/i18n/messages.de.js.map +1 -1
  439. package/dist/lib/i18n/messages.en.d.ts +4 -0
  440. package/dist/lib/i18n/messages.en.js +8 -0
  441. package/dist/lib/i18n/messages.en.js.map +1 -1
  442. package/dist/lib/operations/project-runtime-config.d.ts +9 -0
  443. package/dist/lib/operations/project-runtime-config.js +13 -0
  444. package/dist/lib/operations/project-runtime-config.js.map +1 -1
  445. package/dist/lib/operations/studio-operations.d.ts +9 -1
  446. package/dist/lib/operations/studio-operations.js +6 -1
  447. package/dist/lib/operations/studio-operations.js.map +1 -1
  448. package/dist/lib/persistence/convex-adapter.d.ts +16 -3
  449. package/dist/lib/persistence/convex-adapter.js +78 -47
  450. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  451. package/dist/lib/persistence/index.js +14 -1
  452. package/dist/lib/persistence/index.js.map +1 -1
  453. package/dist/lib/persona/labels.js +1 -1
  454. package/dist/lib/persona/labels.js.map +1 -1
  455. package/dist/lib/query/operations-query.d.ts +1 -1
  456. package/dist/lib/query/operations-query.js +1 -1
  457. package/dist/lib/query/operations-query.js.map +1 -1
  458. package/dist/lib/references/reference-model.js +1 -1
  459. package/dist/lib/references/reference-model.js.map +1 -1
  460. package/dist/lib/references/references-store.js +1 -1
  461. package/dist/lib/references/references-store.js.map +1 -1
  462. package/dist/lib/workspace/index.d.ts +6 -0
  463. package/dist/lib/workspace/index.js +8 -0
  464. package/dist/lib/workspace/index.js.map +1 -1
  465. package/dist/mount.d.ts +28 -0
  466. package/dist/mount.js +27 -0
  467. package/dist/mount.js.map +1 -0
  468. package/dist/router/root.js +10 -10
  469. package/dist/router/root.js.map +1 -1
  470. package/dist/ui/badge.d.ts +1 -1
  471. package/dist/ui/button.d.ts +2 -2
  472. package/package.json +15 -11
  473. package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
  474. package/dist/components/redirects/redirects-collection-view-source.js +0 -146
  475. package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
  476. package/dist/components/redirects/redirects-module.d.ts +0 -21
  477. package/dist/components/redirects/redirects-module.js +0 -40
  478. package/dist/components/redirects/redirects-module.js.map +0 -1
  479. package/dist/components/redirects/redirects-store.d.ts +0 -68
  480. package/dist/components/redirects/redirects-store.js +0 -132
  481. package/dist/components/redirects/redirects-store.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"builder-store.js","sourceRoot":"","sources":["../../../src/components/builder/builder-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useReducer, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n DEFAULT_SLOT,\n cloneWithNewIds,\n duplicateNode,\n getNode,\n getParentInfo,\n gridContainerNode,\n insertNode,\n moveNode,\n normalizeContainers,\n removeNode,\n removeNodeProp,\n reorderNode,\n replaceNode,\n replacePlacementRefusal,\n setNodeProp,\n subtreePlacementRefusal,\n} from '@elytracms/core/project-graph'\nimport type {\n AllowList,\n ComponentLookup,\n ComponentNode,\n InsertTarget,\n LayoutContainerIds,\n PlacementScope,\n PlacementVerdict,\n PropValue,\n} from '@elytracms/core/project-graph'\nimport { createUuidIdFactory } from '@elytracms/core/persistence'\nimport type { IdFactory } from '@elytracms/core/persistence'\nimport { WRAP_GRID_OPTIONS, applyWrap } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { replaceRun, runByKey } from './canvas-runs'\nimport type { PreservedItem } from './canvas-runs'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\nconst HISTORY_LIMIT = 100\n\n/**\n * A canvas preview-width key (EC-250): `'full'` (unconstrained) or a configured\n * breakpoint name (`'sm'`, `'md'`, …). Dynamic because the set is sourced from\n * the consuming site's breakpoints, so it is a string, not a fixed union — the\n * switcher resolves the available options via {@link resolveCanvasViewports}.\n */\nexport type Viewport = string\n\n/**\n * The builder store's action union. EC-287 retired the DB-resident project graph\n * and its persistence mirror, so these actions are pure in-memory editor state —\n * no action maps to a persisted `graph.*` command anymore. The block-field editor\n * lifts committed edits into the CMS document via `onChange`\n * (`composition-field-editor`); nothing persists a graph.\n */\nexport type BuilderAction =\n | { type: 'select'; id: string | null }\n | { type: 'hover'; id: string | null }\n | { type: 'viewport'; viewport: Viewport }\n | { type: 'undo' }\n | { type: 'redo' }\n /**\n * Insert a node (its subtree re-id'd from the store's id factory). `minted` — the\n * node ALREADY carries fresh ids from the same factory (the paste path mints first\n * so its issue deep links name exactly the ids that land); the reducer then inserts\n * it as given.\n */\n | { type: 'insert'; target: InsertTarget; node: ComponentNode; select?: boolean; minted?: true }\n | { type: 'remove'; id: string }\n | { type: 'duplicate'; id: string }\n | { type: 'reorder'; id: string; delta: -1 | 1 }\n | { type: 'move'; id: string; target: InsertTarget }\n /**\n * EC-282 reuse gesture: replace a node with zero or more nodes at its\n * position (clone-with-fresh-ids, like `insert`). Promote → 1 embed node;\n * detach → the referenced element's inline blocks. One atomic structural\n * edit, one undo step.\n */\n | { type: 'replace'; id: string; nodes: ComponentNode[]; select?: boolean }\n /**\n * EC-162 layout gesture: wrap the reference node and the dragged subject in\n * a two-column Grid container as ONE atomic structural edit (one undo step).\n */\n | {\n type: 'wrap'\n referenceId: string\n side: 'left' | 'right'\n subject: { kind: 'node'; nodeId: string } | { kind: 'component'; node: ComponentNode }\n }\n | { type: 'setProp'; id: string; name: string; value: PropValue; coalesceKey?: string }\n | { type: 'removeProp'; id: string; name: string }\n /** EC-584: the editor dismissed the visible refusal ({@link BuilderState.refusal}). */\n | { type: 'clearRefusal' }\n /**\n * EC-549 — select a typography RUN by its key (`canvas-runs.ts`: the index of its\n * first preserved item); `null` clears it. A run and a node are never both selected.\n */\n | { type: 'selectRun'; run: number | null }\n /** EC-549 — delete a typography run: a real, undoable edit of the preserved list. */\n | { type: 'removeRun'; run: number }\n /**\n * EC-549 — replace a run's typography with `content` (the inspector's rich-text\n * editor on that run; consecutive same-key edits coalesce into one undo step). Empty\n * `content` deletes the run.\n */\n | { type: 'setRun'; run: number; content: readonly unknown[]; coalesceKey?: string }\n\n/** The structural actions the placement judge rules on (EC-584): every node that enters or moves. */\nexport type PlacingAction = Extract<BuilderAction, { type: 'insert' | 'replace' | 'move' | 'wrap' | 'duplicate' }>\n\n/** A refused placement (EC-584): the judge's verdict and the action it refused. */\nexport interface PlacementRefusal {\n action: PlacingAction['type']\n verdict: PlacementVerdict\n}\n\n/**\n * One history entry (EC-549): the component tree AND the preserved typography — so a\n * run's deletion or edit is undone exactly like a block's.\n */\nexport interface CanvasSnapshot {\n root: ComponentNode\n preserved: readonly PreservedItem[]\n}\n\nexport interface BuilderState {\n /**\n * The edited composition: one root-bearing component tree (EC-287). The canvas\n * wraps a page document's `body` blocks into a synthetic root and edits it\n * directly — no page/layout wrapper. Undo/redo snapshot it with the preserved\n * typography ({@link CanvasSnapshot}).\n */\n past: CanvasSnapshot[]\n present: ComponentNode\n future: CanvasSnapshot[]\n /**\n * EC-549 — the field value's typography items (no ids, EC-500 D1), each anchored by\n * how many component roots preceded it (`componentPrefix`); the field value is\n * `mergeBlockRoots(present's roots, preserved)`. In the store, so selecting,\n * deleting and editing a run is a real action with truthful undo.\n */\n preserved: readonly PreservedItem[]\n selectedId: string | null\n /** EC-549 — the selected typography run's key, or `null` (exclusive with {@link selectedId}). */\n selectedRun: number | null\n hoveredId: string | null\n viewport: Viewport\n /**\n * EC-578 / EC-595 A2 — THE node-id factory (injected; UUIDs in the studio, a\n * sequential factory in fixtures). It replaced the per-session `node-<n>` counter,\n * which two sessions editing one page advanced identically — both minted the same\n * next id, a `duplicate-node-id` on the second save. Stored ids are untouched.\n */\n newId: IdFactory\n /** Coalescing key of the last edit (consecutive same-key edits don't stack history). */\n lastCoalesceKey: string | null\n /**\n * The components filling the layout roles (EC-591, `ComponentRegistry.layout`),\n * fixed for the store's lifetime: which nodes container normalization treats as\n * Stack/Grid, and the Grid the wrap gesture builds. No hardcoded engine id.\n */\n layout: LayoutContainerIds\n /**\n * EC-584 — the last REFUSED placement, with its reason: the reducer judged an\n * `insert`/`replace`/`move`/`wrap`/`duplicate` against the field and slot\n * vocabularies and canvas eligibility (the core placement judge) and left the\n * composition unchanged. Transient — the next edit, selection, undo or redo clears\n * it — and never silent: the canvas shows it (`PlacementRefusalNotice`). A refused\n * action changes no tree, so it adds no history entry and emits no canvas patch.\n */\n refusal: PlacementRefusal | null\n}\n\ntype Action = BuilderAction\n\n/**\n * Push a new graph onto history, coalescing consecutive edits with the same key.\n *\n * Every commit normalizes layout containers (EC-162 ledger fix): the\n * operations layer normalizes every persisted `graph.*` write (EC-161), so\n * the optimistic working graph must apply the same `normalizeContainers` pass\n * or the UI state diverges from the persisted revision after\n * container-emptying moves/removes. The mirrored command stays un-normalized\n * (`graphInvocationFor` sees the raw action); both sides normalize\n * identically, so they land on the same graph.\n */\nfunction commit(\n state: BuilderState,\n root: ComponentNode,\n opts: {\n coalesceKey?: string\n selectedId?: string | null\n preserved?: readonly PreservedItem[]\n selectedRun?: number | null\n } = {},\n): BuilderState {\n const normalized = normalizeContainers(root, state.layout)\n const present = normalized\n const preserved = opts.preserved ?? state.preserved\n const coalesce = opts.coalesceKey != null && opts.coalesceKey === state.lastCoalesceKey\n // Normalization can dissolve the node a caller wanted selected (e.g. the\n // parent container of a removed child) — selection never points at a ghost.\n const desired = opts.selectedId !== undefined ? opts.selectedId : state.selectedId\n const selectedId = desired !== null && getNode(normalized, desired) ? desired : null\n const desiredRun = opts.selectedRun !== undefined ? opts.selectedRun : state.selectedRun\n return {\n ...state,\n past: coalesce ? state.past : [...state.past, snapshotOf(state)].slice(-HISTORY_LIMIT),\n present,\n preserved,\n future: [],\n lastCoalesceKey: opts.coalesceKey ?? null,\n selectedId,\n selectedRun: liveRun(present, preserved, desiredRun),\n refusal: null,\n }\n}\n\nfunction snapshotOf(state: BuilderState): CanvasSnapshot {\n return { root: state.present, preserved: state.preserved }\n}\n\n/** The run key if that run still exists in this state, else `null` (never a ghost selection). */\nfunction liveRun(root: ComponentNode, preserved: readonly PreservedItem[], run: number | null): number | null {\n if (run === null) return null\n return runByKey(rootCount(root), preserved, run) ? run : null\n}\n\n/** How many component roots the composition has (the synthetic root's children). */\nfunction rootCount(root: ComponentNode): number {\n return root.slots?.[DEFAULT_SLOT]?.length ?? 0\n}\n\n/** Restore a history entry (undo/redo), keeping a selection only where it still exists. */\nfunction restore(state: BuilderState, to: CanvasSnapshot, past: CanvasSnapshot[], future: CanvasSnapshot[]): BuilderState {\n return {\n ...state,\n past,\n present: to.root,\n preserved: to.preserved,\n future,\n lastCoalesceKey: null,\n selectedId: getNode(to.root, state.selectedId ?? '') ? state.selectedId : null,\n selectedRun: liveRun(to.root, to.preserved, state.selectedRun),\n refusal: null,\n }\n}\n\n/** Leave the composition untouched and say why (EC-584 — never a silent no-op). */\nfunction refuse(state: BuilderState, action: PlacingAction, verdict: PlacementVerdict): BuilderState {\n return { ...state, refusal: { action: action.type, verdict } }\n}\n\n/**\n * The builder's pure state transition, CONSTRUCTED with the placement scope it judges\n * by (EC-584, owner-ratified 4a): the field's effective top-level vocabulary and the\n * manifest lookup. The scope is static per store and deliberately not reducer STATE\n * (it is the declaration the tree is judged against, not part of the tree) — the\n * reducer RECEIVES it, so there is no `(state, action)` to call without it.\n *\n * Every structural action that places a node — `insert`, `replace`, `move`, `wrap`,\n * `duplicate` — runs the ONE core judge (`subtreePlacementRefusal`) over every node\n * that enters or moves, BEFORE committing. A refusal returns the composition\n * unchanged, with the verdict in {@link BuilderState.refusal}: so no gesture — a\n * forgotten pre-check, promote, detach, a fixture's `slotChildren`, a server-only\n * paste — can place what the field, a slot or canvas eligibility forbids, and the\n * publish gate (which calls the same judge) cannot disagree with the canvas.\n *\n * Exported (with `initialBuilderState`) for unit tests — gesture determinism and\n * normalization lockstep are reducer-level properties, not render behavior.\n */\nexport function createBuilderReducer(\n placement: PlacementScope,\n): (state: BuilderState, action: Action) => BuilderState {\n return (state, action) => transition(state, action, placement)\n}\n\nfunction transition(state: BuilderState, action: Action, placement: PlacementScope): BuilderState {\n const root = state.present\n switch (action.type) {\n case 'select':\n return { ...state, selectedId: action.id, selectedRun: action.id === null ? state.selectedRun : null, refusal: null }\n case 'selectRun':\n return {\n ...state,\n selectedRun: liveRun(root, state.preserved, action.run),\n selectedId: action.run === null ? state.selectedId : null,\n refusal: null,\n }\n case 'removeRun': {\n const run = runByKey(rootCount(root), state.preserved, action.run)\n if (!run) return state\n return commit(state, root, { preserved: replaceRun(state.preserved, run, []), selectedRun: null })\n }\n case 'setRun': {\n const run = runByKey(rootCount(root), state.preserved, action.run)\n if (!run) return state\n return commit(state, root, {\n preserved: replaceRun(state.preserved, run, action.content),\n coalesceKey: action.coalesceKey,\n selectedRun: action.content.length > 0 ? action.run : null,\n })\n }\n case 'hover':\n return state.hoveredId === action.id ? state : { ...state, hoveredId: action.id }\n case 'viewport':\n return { ...state, viewport: action.viewport }\n case 'clearRefusal':\n return state.refusal === null ? state : { ...state, refusal: null }\n\n case 'undo': {\n const prev = state.past.at(-1)\n if (!prev) return state\n return restore(state, prev, state.past.slice(0, -1), [snapshotOf(state), ...state.future])\n }\n case 'redo': {\n const next = state.future[0]\n if (!next) return state\n return restore(state, next, [...state.past, snapshotOf(state)], state.future.slice(1))\n }\n\n case 'insert': {\n const node = action.minted ? action.node : cloneWithNewIds(action.node, state.newId)\n const verdict = subtreePlacementRefusal(root, node, landingOf(action.target), placement)\n if (verdict) return refuse(state, action, verdict)\n const next = insertNode(root, action.target, node)\n return commit(state, next, {\n selectedId: action.select === false ? state.selectedId : node.id,\n })\n }\n case 'remove': {\n const parent = getParentInfo(root, action.id)\n const next = removeNode(root, action.id)\n const selectedId =\n state.selectedId === action.id ? (parent?.parent.id ?? null) : state.selectedId\n return commit(state, next, { selectedId })\n }\n case 'duplicate': {\n // The copy (and every node of its subtree) ENTERS beside the original.\n const info = getParentInfo(root, action.id)\n const source = getNode(root, action.id)\n if (info && source) {\n const verdict = subtreePlacementRefusal(root, source, { parentId: info.parent.id, slot: info.slot }, placement)\n if (verdict) return refuse(state, action, verdict)\n }\n const { root: next, newId } = duplicateNode(root, action.id, state.newId)\n return commit(state, next, { selectedId: newId ?? state.selectedId })\n }\n case 'reorder':\n // Same parent, same slot: the placement does not change — nothing to judge.\n return commit(state, reorderNode(root, action.id, action.delta))\n case 'move': {\n // A node that changes parent or slot lands somewhere new (its descendants keep\n // their own slots, so only it is judged); a move within its own slot is a\n // reorder and leaves the placement as it was.\n const info = getParentInfo(root, action.id)\n const node = getNode(root, action.id)\n const relocates = !info || info.parent.id !== action.target.parentId || info.slot !== action.target.slot\n if (node && relocates) {\n const verdict = subtreePlacementRefusal(root, withoutSlots(node), landingOf(action.target), placement)\n if (verdict) return refuse(state, action, verdict)\n }\n return commit(state, moveNode(root, action.id, action.target))\n }\n case 'replace': {\n // Mint fresh ids for the incoming nodes (page-local namespace) so a detached\n // copy never collides with the page, and a promoted block's source subtree\n // (kept for the new reusable document) stays untouched.\n const cloned = action.nodes.map((n) => cloneWithNewIds(n, state.newId))\n // EC-560: promote (one embed) and detach (the reusable's content) land where the\n // replaced node was — judged there like any insert (the reuse preflight asks the\n // same `replacePlacementRefusal` before any side effect).\n const verdict = replacePlacementRefusal(root, action.id, cloned, placement)\n if (verdict) return refuse(state, action, verdict)\n const next = replaceNode(root, action.id, cloned)\n if (next === root) return state // root or absent id — no-op\n return commit(state, next, {\n selectedId: action.select === false ? state.selectedId : (cloned[0]?.id ?? state.selectedId),\n })\n }\n case 'wrap': {\n // Layout gesture (EC-162): the reference and the dragged subject become\n // the two columns of a new Grid container — ONE structural edit, one\n // history entry, one mirrored command, so a single undo dissolves the\n // whole gesture.\n const partner =\n action.subject.kind === 'node'\n ? { kind: 'node' as const, nodeId: action.subject.nodeId }\n : { kind: 'subtree' as const, node: cloneWithNewIds(action.subject.node, state.newId) }\n // EC-591: the gesture builds the `layout.grid` component; with none, it is off\n // (the drop rules refuse it before dispatch — this guard keeps the reducer total).\n const gridComponentId = state.layout.grid\n if (gridComponentId === undefined) return state\n // EC-584: the Grid lands where the reference is; the reference and the partner\n // land in the Grid's column slot. A moved partner keeps its own subtree (only it\n // relocates); a new one (a blocks-panel tile) enters whole.\n const reference = getNode(root, action.referenceId)\n const referenceInfo = getParentInfo(root, action.referenceId)\n const partnerNode = partner.kind === 'node' ? getNode(root, partner.nodeId) : partner.node\n if (reference && referenceInfo && partnerNode) {\n const columns = [withoutSlots(reference), partner.kind === 'node' ? withoutSlots(partnerNode) : partnerNode]\n const probe = gridContainerNode(gridComponentId, 'wrap', WRAP_GRID_OPTIONS, columns)\n const verdict = subtreePlacementRefusal(\n root,\n probe,\n { parentId: referenceInfo.parent.id, slot: referenceInfo.slot },\n placement,\n )\n if (verdict) return refuse(state, action, verdict)\n }\n const containerId = state.newId()\n const wrapped = applyWrap({\n root,\n gridComponentId,\n referenceId: action.referenceId,\n side: action.side,\n partner,\n containerId,\n })\n if (!wrapped) return state\n return commit(state, wrapped.root, { selectedId: containerId })\n }\n case 'setProp':\n return commit(state, setNodeProp(root, action.id, action.name, action.value), {\n coalesceKey: action.coalesceKey,\n })\n case 'removeProp':\n return commit(state, removeNodeProp(root, action.id, action.name))\n default:\n return state\n }\n}\n\nfunction landingOf(target: InsertTarget): { parentId: string; slot?: string } {\n return { parentId: target.parentId, slot: target.slot }\n}\n\n/** The node alone — a move relocates it; its descendants keep their own slots. */\nfunction withoutSlots(node: ComponentNode): ComponentNode {\n const { slots: _slots, ...rest } = node\n return rest\n}\n\n/**\n * The composition FIELD whose value the store edits (EC-554). Static for the\n * store's lifetime and deliberately NOT reducer state: it is the declaration the\n * edited tree is judged against, not part of the tree. It travels WITH the store\n * so every placement surface under the provider — the canvas and page-tree\n * drag-and-drop, paste (canvas, tree, keyboard), and the insert offer — reads the\n * same value from the one context it already requires; none can be mounted\n * without it, and `BuilderProvider` cannot be mounted without declaring it.\n */\nexport interface CompositionFieldScope {\n /** The field's display label — what a refusal notice names (e.g. \"Canvas\"). */\n label: string\n /**\n * The field's EFFECTIVE top-level vocabulary (EC-188; EC-584 — `fieldAllowList(field)`:\n * its `allow`, else the `blocks` type's default). The judge applies it only to\n * direct children of the composition root (`allowAt`, core `placement.ts`).\n */\n allow: AllowList\n}\n\nexport interface BuilderStore extends BuilderState {\n canUndo: boolean\n canRedo: boolean\n selectedNode: ComponentNode | null\n /** The composition field being edited (EC-554) — see {@link CompositionFieldScope}. */\n field: CompositionFieldScope\n /**\n * EC-584 — what the reducer judges every placement by: the manifest lookup and the\n * field's vocabulary. The gestures' own pre-checks (drag-and-drop, paste, the insert\n * offer, the reuse preflight) read the SAME scope, so an indicator and the sink agree.\n */\n placement: PlacementScope\n dispatch: (action: Action) => void\n}\n\nconst { Context: BuilderContext, useRequired: useBuilder } =\n createRequiredContext<BuilderStore>('Builder')\n\nexport function initialBuilderState(\n composition: ComponentNode,\n layout: LayoutContainerIds,\n newId: IdFactory,\n /** EC-549 — the field value's typography items with their anchors (`splitBlockRoots`). */\n preserved: readonly PreservedItem[] = [],\n): BuilderState {\n // Start container-normal (idempotent on healthy trees) so the session and the\n // lifted baseline agree from the first edit.\n const present = normalizeContainers(composition, layout)\n return {\n past: [],\n present,\n future: [],\n preserved,\n selectedId: present.id,\n selectedRun: null,\n hoveredId: null,\n viewport: 'full',\n // EC-578: fresh ids come from the injected factory — never a counter seeded past\n // the tree's `node-<n>` ids (EC-203), which two sessions advanced identically.\n newId,\n lastCoalesceKey: null,\n layout,\n refusal: null,\n }\n}\n\n/**\n * Undo/redo design (EC-141): the history stack is *UI state* — an in-memory\n * stack of composition-root snapshots. EC-287 retired the DB-resident project\n * graph and its append-only revision log, so the store no longer mirrors edits onto a\n * persistence layer: it is a pure in-memory editor. The block-field editor lifts\n * its committed composition into the open CMS document via `onChange`\n * (`composition-field-editor`); the document's own Save persists it as content.\n */\nexport function BuilderProvider({\n composition,\n preserved,\n field,\n components,\n layout,\n idFactory,\n children,\n}: {\n /** The composition root to edit (the composition-field editor's synthetic root). */\n composition: ComponentNode\n /** EC-549 — the field value's typography items (`splitBlockRoots(value).preserved`). */\n preserved?: readonly PreservedItem[]\n /** The field the composition is the value of (EC-554) — required, never defaulted. */\n field: CompositionFieldScope\n /** The manifest lookup the placement judge reads (EC-584, `registry.lookup`) — required. */\n components: ComponentLookup\n /** The layout-role component ids (EC-591, `ComponentRegistry.layout`) — required. */\n layout: LayoutContainerIds\n /**\n * EC-578 — the node-id factory. Defaults to UUIDs (`createUuidIdFactory`, one per\n * store); fixtures inject a deterministic factory.\n */\n idFactory?: IdFactory\n children: ReactNode\n}) {\n const [newId] = useState<IdFactory>(() => idFactory ?? createUuidIdFactory())\n // EC-584: the reducer is constructed ONCE with the scope it judges by — the field\n // and the lookup are static for the store's lifetime (a remount re-reads them).\n const [placement] = useState<PlacementScope>(() => ({ components, fieldAllow: field.allow }))\n const [reducer] = useState(() => createBuilderReducer(placement))\n // EC-287: nothing persists a graph — committed edits are lifted into the CMS\n // document by the composition-field editor's `onChange`. `useReducer` drives the\n // pure reducer (consecutive dispatches in one gesture apply in sequence); the\n // store below decorates the state with the derived undo/redo + selection flags.\n const [state, dispatch] = useReducer(reducer, { composition, layout, newId, preserved }, (init) =>\n initialBuilderState(init.composition, init.layout, init.newId, init.preserved),\n )\n\n const store = useMemo<BuilderStore>(() => {\n const selectedNode = state.selectedId\n ? (getNode(state.present, state.selectedId) ?? null)\n : null\n return {\n ...state,\n canUndo: state.past.length > 0,\n canRedo: state.future.length > 0,\n selectedNode,\n field,\n placement,\n dispatch,\n }\n }, [state, field, placement, dispatch])\n\n return <BuilderContext.Provider value={store}>{children}</BuilderContext.Provider>\n}\n\nexport { useBuilder }\n"]}
1
+ {"version":3,"file":"builder-store.js","sourceRoot":"","sources":["../../../src/components/builder/builder-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useReducer, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n DEFAULT_SLOT,\n cloneWithNewIds,\n duplicateNode,\n getNode,\n getParentInfo,\n gridContainerNode,\n insertNode,\n moveNode,\n normalizeContainers,\n removeNode,\n removeNodeProp,\n reorderNode,\n replaceNode,\n replacePlacementRefusal,\n setNodeProp,\n subtreePlacementRefusal,\n} from '@elytracms/core/project-graph'\nimport type {\n AllowList,\n ComponentLookup,\n ComponentNode,\n InsertTarget,\n LayoutContainerIds,\n PlacementScope,\n PlacementVerdict,\n PropValue,\n} from '@elytracms/core/project-graph'\nimport { createUuidIdFactory } from '@elytracms/core/persistence'\nimport type { IdFactory } from '@elytracms/core/persistence'\nimport { WRAP_GRID_OPTIONS, applyWrap } from '../../lib/dnd/drop-rules'\nimport { replaceRun, runByKey } from './canvas-runs'\nimport type { PreservedItem } from './canvas-runs'\nimport { createRequiredContext } from '../../lib/required-context'\n\nconst HISTORY_LIMIT = 100\n\n/**\n * A canvas preview-width key (EC-250): `'full'` (unconstrained) or a configured\n * breakpoint name (`'sm'`, `'md'`, …). Dynamic because the set is sourced from\n * the consuming site's breakpoints, so it is a string, not a fixed union — the\n * switcher resolves the available options via {@link resolveCanvasViewports}.\n */\nexport type Viewport = string\n\n/**\n * The builder store's action union. EC-287 retired the DB-resident project graph\n * and its persistence mirror, so these actions are pure in-memory editor state —\n * no action maps to a persisted `graph.*` command anymore. The block-field editor\n * lifts committed edits into the CMS document via `onChange`\n * (`composition-field-editor`); nothing persists a graph.\n */\nexport type BuilderAction =\n | { type: 'select'; id: string | null }\n | { type: 'hover'; id: string | null }\n | { type: 'viewport'; viewport: Viewport }\n | { type: 'undo' }\n | { type: 'redo' }\n /**\n * Insert a node (its subtree re-id'd from the store's id factory). `minted` — the\n * node ALREADY carries fresh ids from the same factory (the paste path mints first\n * so its issue deep links name exactly the ids that land); the reducer then inserts\n * it as given.\n */\n | { type: 'insert'; target: InsertTarget; node: ComponentNode; select?: boolean; minted?: true }\n | { type: 'remove'; id: string }\n | { type: 'duplicate'; id: string }\n | { type: 'reorder'; id: string; delta: -1 | 1 }\n | { type: 'move'; id: string; target: InsertTarget }\n /**\n * EC-282 reuse gesture: replace a node with zero or more nodes at its\n * position (clone-with-fresh-ids, like `insert`). Promote → 1 embed node;\n * detach → the referenced element's inline blocks. One atomic structural\n * edit, one undo step.\n */\n | { type: 'replace'; id: string; nodes: ComponentNode[]; select?: boolean }\n /**\n * EC-162 layout gesture: wrap the reference node and the dragged subject in\n * a two-column Grid container as ONE atomic structural edit (one undo step).\n */\n | {\n type: 'wrap'\n referenceId: string\n side: 'left' | 'right'\n subject: { kind: 'node'; nodeId: string } | { kind: 'component'; node: ComponentNode }\n }\n | { type: 'setProp'; id: string; name: string; value: PropValue; coalesceKey?: string }\n | { type: 'removeProp'; id: string; name: string }\n /** EC-584: the editor dismissed the visible refusal ({@link BuilderState.refusal}). */\n | { type: 'clearRefusal' }\n /**\n * EC-549 — select a typography RUN by its key (`canvas-runs.ts`: the index of its\n * first preserved item); `null` clears it. A run and a node are never both selected.\n */\n | { type: 'selectRun'; run: number | null }\n /** EC-549 — delete a typography run: a real, undoable edit of the preserved list. */\n | { type: 'removeRun'; run: number }\n /**\n * EC-549 — replace a run's typography with `content` (the inspector's rich-text\n * editor on that run; consecutive same-key edits coalesce into one undo step). Empty\n * `content` deletes the run.\n */\n | { type: 'setRun'; run: number; content: readonly unknown[]; coalesceKey?: string }\n\n/** The structural actions the placement judge rules on (EC-584): every node that enters or moves. */\nexport type PlacingAction = Extract<BuilderAction, { type: 'insert' | 'replace' | 'move' | 'wrap' | 'duplicate' }>\n\n/** A refused placement (EC-584): the judge's verdict and the action it refused. */\nexport interface PlacementRefusal {\n action: PlacingAction['type']\n verdict: PlacementVerdict\n}\n\n/**\n * One history entry (EC-549): the component tree AND the preserved typography — so a\n * run's deletion or edit is undone exactly like a block's.\n */\nexport interface CanvasSnapshot {\n root: ComponentNode\n preserved: readonly PreservedItem[]\n}\n\nexport interface BuilderState {\n /**\n * The edited composition: one root-bearing component tree (EC-287). The canvas\n * wraps a page document's `body` blocks into a synthetic root and edits it\n * directly — no page/layout wrapper. Undo/redo snapshot it with the preserved\n * typography ({@link CanvasSnapshot}).\n */\n past: CanvasSnapshot[]\n present: ComponentNode\n future: CanvasSnapshot[]\n /**\n * EC-549 — the field value's typography items (no ids, EC-500 D1), each anchored by\n * how many component roots preceded it (`componentPrefix`); the field value is\n * `mergeBlockRoots(present's roots, preserved)`. In the store, so selecting,\n * deleting and editing a run is a real action with truthful undo.\n */\n preserved: readonly PreservedItem[]\n selectedId: string | null\n /** EC-549 — the selected typography run's key, or `null` (exclusive with {@link selectedId}). */\n selectedRun: number | null\n hoveredId: string | null\n viewport: Viewport\n /**\n * EC-578 / EC-595 A2 — THE node-id factory (injected; UUIDs in the studio, a\n * sequential factory in fixtures). It replaced the per-session `node-<n>` counter,\n * which two sessions editing one page advanced identically — both minted the same\n * next id, a `duplicate-node-id` on the second save. Stored ids are untouched.\n */\n newId: IdFactory\n /** Coalescing key of the last edit (consecutive same-key edits don't stack history). */\n lastCoalesceKey: string | null\n /**\n * The components filling the layout roles (EC-591, `ComponentRegistry.layout`),\n * fixed for the store's lifetime: which nodes container normalization treats as\n * Stack/Grid, and the Grid the wrap gesture builds. No hardcoded engine id.\n */\n layout: LayoutContainerIds\n /**\n * EC-584 — the last REFUSED placement, with its reason: the reducer judged an\n * `insert`/`replace`/`move`/`wrap`/`duplicate` against the field and slot\n * vocabularies and canvas eligibility (the core placement judge) and left the\n * composition unchanged. Transient — the next edit, selection, undo or redo clears\n * it — and never silent: the canvas shows it (`PlacementRefusalNotice`). A refused\n * action changes no tree, so it adds no history entry and emits no canvas patch.\n */\n refusal: PlacementRefusal | null\n}\n\ntype Action = BuilderAction\n\n/**\n * Push a new graph onto history, coalescing consecutive edits with the same key.\n *\n * Every commit normalizes layout containers (EC-162 ledger fix): the\n * operations layer normalizes every persisted `graph.*` write (EC-161), so\n * the optimistic working graph must apply the same `normalizeContainers` pass\n * or the UI state diverges from the persisted revision after\n * container-emptying moves/removes. The mirrored command stays un-normalized\n * (`graphInvocationFor` sees the raw action); both sides normalize\n * identically, so they land on the same graph.\n */\nfunction commit(\n state: BuilderState,\n root: ComponentNode,\n opts: {\n coalesceKey?: string\n selectedId?: string | null\n preserved?: readonly PreservedItem[]\n selectedRun?: number | null\n } = {},\n): BuilderState {\n const normalized = normalizeContainers(root, state.layout)\n const present = normalized\n const preserved = opts.preserved ?? state.preserved\n const coalesce = opts.coalesceKey != null && opts.coalesceKey === state.lastCoalesceKey\n // Normalization can dissolve the node a caller wanted selected (e.g. the\n // parent container of a removed child) — selection never points at a ghost.\n const desired = opts.selectedId !== undefined ? opts.selectedId : state.selectedId\n const selectedId = desired !== null && getNode(normalized, desired) ? desired : null\n const desiredRun = opts.selectedRun !== undefined ? opts.selectedRun : state.selectedRun\n return {\n ...state,\n past: coalesce ? state.past : [...state.past, snapshotOf(state)].slice(-HISTORY_LIMIT),\n present,\n preserved,\n future: [],\n lastCoalesceKey: opts.coalesceKey ?? null,\n selectedId,\n selectedRun: liveRun(present, preserved, desiredRun),\n refusal: null,\n }\n}\n\nfunction snapshotOf(state: BuilderState): CanvasSnapshot {\n return { root: state.present, preserved: state.preserved }\n}\n\n/** The run key if that run still exists in this state, else `null` (never a ghost selection). */\nfunction liveRun(root: ComponentNode, preserved: readonly PreservedItem[], run: number | null): number | null {\n if (run === null) return null\n return runByKey(rootCount(root), preserved, run) ? run : null\n}\n\n/** How many component roots the composition has (the synthetic root's children). */\nfunction rootCount(root: ComponentNode): number {\n return root.slots?.[DEFAULT_SLOT]?.length ?? 0\n}\n\n/** Restore a history entry (undo/redo), keeping a selection only where it still exists. */\nfunction restore(state: BuilderState, to: CanvasSnapshot, past: CanvasSnapshot[], future: CanvasSnapshot[]): BuilderState {\n return {\n ...state,\n past,\n present: to.root,\n preserved: to.preserved,\n future,\n lastCoalesceKey: null,\n selectedId: getNode(to.root, state.selectedId ?? '') ? state.selectedId : null,\n selectedRun: liveRun(to.root, to.preserved, state.selectedRun),\n refusal: null,\n }\n}\n\n/** Leave the composition untouched and say why (EC-584 — never a silent no-op). */\nfunction refuse(state: BuilderState, action: PlacingAction, verdict: PlacementVerdict): BuilderState {\n return { ...state, refusal: { action: action.type, verdict } }\n}\n\n/**\n * The builder's pure state transition, CONSTRUCTED with the placement scope it judges\n * by (EC-584, owner-ratified 4a): the field's effective top-level vocabulary and the\n * manifest lookup. The scope is static per store and deliberately not reducer STATE\n * (it is the declaration the tree is judged against, not part of the tree) — the\n * reducer RECEIVES it, so there is no `(state, action)` to call without it.\n *\n * Every structural action that places a node — `insert`, `replace`, `move`, `wrap`,\n * `duplicate` — runs the ONE core judge (`subtreePlacementRefusal`) over every node\n * that enters or moves, BEFORE committing. A refusal returns the composition\n * unchanged, with the verdict in {@link BuilderState.refusal}: so no gesture — a\n * forgotten pre-check, promote, detach, a fixture's `slotChildren`, a server-only\n * paste — can place what the field, a slot or canvas eligibility forbids, and the\n * publish gate (which calls the same judge) cannot disagree with the canvas.\n *\n * Exported (with `initialBuilderState`) for unit tests — gesture determinism and\n * normalization lockstep are reducer-level properties, not render behavior.\n */\nexport function createBuilderReducer(\n placement: PlacementScope,\n): (state: BuilderState, action: Action) => BuilderState {\n return (state, action) => transition(state, action, placement)\n}\n\nfunction transition(state: BuilderState, action: Action, placement: PlacementScope): BuilderState {\n const root = state.present\n switch (action.type) {\n case 'select':\n return { ...state, selectedId: action.id, selectedRun: action.id === null ? state.selectedRun : null, refusal: null }\n case 'selectRun':\n return {\n ...state,\n selectedRun: liveRun(root, state.preserved, action.run),\n selectedId: action.run === null ? state.selectedId : null,\n refusal: null,\n }\n case 'removeRun': {\n const run = runByKey(rootCount(root), state.preserved, action.run)\n if (!run) return state\n return commit(state, root, { preserved: replaceRun(state.preserved, run, []), selectedRun: null })\n }\n case 'setRun': {\n const run = runByKey(rootCount(root), state.preserved, action.run)\n if (!run) return state\n return commit(state, root, {\n preserved: replaceRun(state.preserved, run, action.content),\n coalesceKey: action.coalesceKey,\n selectedRun: action.content.length > 0 ? action.run : null,\n })\n }\n case 'hover':\n return state.hoveredId === action.id ? state : { ...state, hoveredId: action.id }\n case 'viewport':\n return { ...state, viewport: action.viewport }\n case 'clearRefusal':\n return state.refusal === null ? state : { ...state, refusal: null }\n\n case 'undo': {\n const prev = state.past.at(-1)\n if (!prev) return state\n return restore(state, prev, state.past.slice(0, -1), [snapshotOf(state), ...state.future])\n }\n case 'redo': {\n const next = state.future[0]\n if (!next) return state\n return restore(state, next, [...state.past, snapshotOf(state)], state.future.slice(1))\n }\n\n case 'insert': {\n const node = action.minted ? action.node : cloneWithNewIds(action.node, state.newId)\n const verdict = subtreePlacementRefusal(root, node, landingOf(action.target), placement)\n if (verdict) return refuse(state, action, verdict)\n const next = insertNode(root, action.target, node)\n return commit(state, next, {\n selectedId: action.select === false ? state.selectedId : node.id,\n })\n }\n case 'remove': {\n const parent = getParentInfo(root, action.id)\n const next = removeNode(root, action.id)\n const selectedId =\n state.selectedId === action.id ? (parent?.parent.id ?? null) : state.selectedId\n return commit(state, next, { selectedId })\n }\n case 'duplicate': {\n // The copy (and every node of its subtree) ENTERS beside the original.\n const info = getParentInfo(root, action.id)\n const source = getNode(root, action.id)\n if (info && source) {\n const verdict = subtreePlacementRefusal(root, source, { parentId: info.parent.id, slot: info.slot }, placement)\n if (verdict) return refuse(state, action, verdict)\n }\n const { root: next, newId } = duplicateNode(root, action.id, state.newId)\n return commit(state, next, { selectedId: newId ?? state.selectedId })\n }\n case 'reorder':\n // Same parent, same slot: the placement does not change — nothing to judge.\n return commit(state, reorderNode(root, action.id, action.delta))\n case 'move': {\n // A node that changes parent or slot lands somewhere new (its descendants keep\n // their own slots, so only it is judged); a move within its own slot is a\n // reorder and leaves the placement as it was.\n const info = getParentInfo(root, action.id)\n const node = getNode(root, action.id)\n const relocates = !info || info.parent.id !== action.target.parentId || info.slot !== action.target.slot\n if (node && relocates) {\n const verdict = subtreePlacementRefusal(root, withoutSlots(node), landingOf(action.target), placement)\n if (verdict) return refuse(state, action, verdict)\n }\n return commit(state, moveNode(root, action.id, action.target))\n }\n case 'replace': {\n // Mint fresh ids for the incoming nodes (page-local namespace) so a detached\n // copy never collides with the page, and a promoted block's source subtree\n // (kept for the new reusable document) stays untouched.\n const cloned = action.nodes.map((n) => cloneWithNewIds(n, state.newId))\n // EC-560: promote (one embed) and detach (the reusable's content) land where the\n // replaced node was — judged there like any insert (the reuse preflight asks the\n // same `replacePlacementRefusal` before any side effect).\n const verdict = replacePlacementRefusal(root, action.id, cloned, placement)\n if (verdict) return refuse(state, action, verdict)\n const next = replaceNode(root, action.id, cloned)\n if (next === root) return state // root or absent id — no-op\n return commit(state, next, {\n selectedId: action.select === false ? state.selectedId : (cloned[0]?.id ?? state.selectedId),\n })\n }\n case 'wrap': {\n // Layout gesture (EC-162): the reference and the dragged subject become\n // the two columns of a new Grid container — ONE structural edit, one\n // history entry, one mirrored command, so a single undo dissolves the\n // whole gesture.\n const partner =\n action.subject.kind === 'node'\n ? { kind: 'node' as const, nodeId: action.subject.nodeId }\n : { kind: 'subtree' as const, node: cloneWithNewIds(action.subject.node, state.newId) }\n // EC-591: the gesture builds the `layout.grid` component; with none, it is off\n // (the drop rules refuse it before dispatch — this guard keeps the reducer total).\n const gridComponentId = state.layout.grid\n if (gridComponentId === undefined) return state\n // EC-584: the Grid lands where the reference is; the reference and the partner\n // land in the Grid's column slot. A moved partner keeps its own subtree (only it\n // relocates); a new one (a blocks-panel tile) enters whole.\n const reference = getNode(root, action.referenceId)\n const referenceInfo = getParentInfo(root, action.referenceId)\n const partnerNode = partner.kind === 'node' ? getNode(root, partner.nodeId) : partner.node\n if (reference && referenceInfo && partnerNode) {\n const columns = [withoutSlots(reference), partner.kind === 'node' ? withoutSlots(partnerNode) : partnerNode]\n const probe = gridContainerNode(gridComponentId, 'wrap', WRAP_GRID_OPTIONS, columns)\n const verdict = subtreePlacementRefusal(\n root,\n probe,\n { parentId: referenceInfo.parent.id, slot: referenceInfo.slot },\n placement,\n )\n if (verdict) return refuse(state, action, verdict)\n }\n const containerId = state.newId()\n const wrapped = applyWrap({\n root,\n gridComponentId,\n referenceId: action.referenceId,\n side: action.side,\n partner,\n containerId,\n })\n if (!wrapped) return state\n return commit(state, wrapped.root, { selectedId: containerId })\n }\n case 'setProp':\n return commit(state, setNodeProp(root, action.id, action.name, action.value), {\n coalesceKey: action.coalesceKey,\n })\n case 'removeProp':\n return commit(state, removeNodeProp(root, action.id, action.name))\n default:\n return state\n }\n}\n\nfunction landingOf(target: InsertTarget): { parentId: string; slot?: string } {\n return { parentId: target.parentId, slot: target.slot }\n}\n\n/** The node alone — a move relocates it; its descendants keep their own slots. */\nfunction withoutSlots(node: ComponentNode): ComponentNode {\n const { slots: _slots, ...rest } = node\n return rest\n}\n\n/**\n * The composition FIELD whose value the store edits (EC-554). Static for the\n * store's lifetime and deliberately NOT reducer state: it is the declaration the\n * edited tree is judged against, not part of the tree. It travels WITH the store\n * so every placement surface under the provider — the canvas and page-tree\n * drag-and-drop, paste (canvas, tree, keyboard), and the insert offer — reads the\n * same value from the one context it already requires; none can be mounted\n * without it, and `BuilderProvider` cannot be mounted without declaring it.\n */\nexport interface CompositionFieldScope {\n /** The field's display label — what a refusal notice names (e.g. \"Canvas\"). */\n label: string\n /**\n * The field's EFFECTIVE top-level vocabulary (EC-188; EC-584 — `fieldAllowList(field)`:\n * its `allow`, else the `blocks` type's default). The judge applies it only to\n * direct children of the composition root (`allowAt`, core `placement.ts`).\n */\n allow: AllowList\n}\n\nexport interface BuilderStore extends BuilderState {\n canUndo: boolean\n canRedo: boolean\n selectedNode: ComponentNode | null\n /** The composition field being edited (EC-554) — see {@link CompositionFieldScope}. */\n field: CompositionFieldScope\n /**\n * EC-584 — what the reducer judges every placement by: the manifest lookup and the\n * field's vocabulary. The gestures' own pre-checks (drag-and-drop, paste, the insert\n * offer, the reuse preflight) read the SAME scope, so an indicator and the sink agree.\n */\n placement: PlacementScope\n dispatch: (action: Action) => void\n}\n\nconst { Context: BuilderContext, useRequired: useBuilder } =\n createRequiredContext<BuilderStore>('Builder')\n\nexport function initialBuilderState(\n composition: ComponentNode,\n layout: LayoutContainerIds,\n newId: IdFactory,\n /** EC-549 — the field value's typography items with their anchors (`splitBlockRoots`). */\n preserved: readonly PreservedItem[] = [],\n): BuilderState {\n // Start container-normal (idempotent on healthy trees) so the session and the\n // lifted baseline agree from the first edit.\n const present = normalizeContainers(composition, layout)\n return {\n past: [],\n present,\n future: [],\n preserved,\n selectedId: present.id,\n selectedRun: null,\n hoveredId: null,\n viewport: 'full',\n // EC-578: fresh ids come from the injected factory — never a counter seeded past\n // the tree's `node-<n>` ids (EC-203), which two sessions advanced identically.\n newId,\n lastCoalesceKey: null,\n layout,\n refusal: null,\n }\n}\n\n/**\n * Undo/redo design (EC-141): the history stack is *UI state* — an in-memory\n * stack of composition-root snapshots. EC-287 retired the DB-resident project\n * graph and its append-only revision log, so the store no longer mirrors edits onto a\n * persistence layer: it is a pure in-memory editor. The block-field editor lifts\n * its committed composition into the open CMS document via `onChange`\n * (`composition-field-editor`); the document's own Save persists it as content.\n */\nexport function BuilderProvider({\n composition,\n preserved,\n field,\n components,\n layout,\n idFactory,\n children,\n}: {\n /** The composition root to edit (the composition-field editor's synthetic root). */\n composition: ComponentNode\n /** EC-549 — the field value's typography items (`splitBlockRoots(value).preserved`). */\n preserved?: readonly PreservedItem[]\n /** The field the composition is the value of (EC-554) — required, never defaulted. */\n field: CompositionFieldScope\n /** The manifest lookup the placement judge reads (EC-584, `registry.lookup`) — required. */\n components: ComponentLookup\n /** The layout-role component ids (EC-591, `ComponentRegistry.layout`) — required. */\n layout: LayoutContainerIds\n /**\n * EC-578 — the node-id factory. Defaults to UUIDs (`createUuidIdFactory`, one per\n * store); fixtures inject a deterministic factory.\n */\n idFactory?: IdFactory\n children: ReactNode\n}) {\n const [newId] = useState<IdFactory>(() => idFactory ?? createUuidIdFactory())\n // EC-584: the reducer is constructed ONCE with the scope it judges by — the field\n // and the lookup are static for the store's lifetime (a remount re-reads them).\n const [placement] = useState<PlacementScope>(() => ({ components, fieldAllow: field.allow }))\n const [reducer] = useState(() => createBuilderReducer(placement))\n // EC-287: nothing persists a graph — committed edits are lifted into the CMS\n // document by the composition-field editor's `onChange`. `useReducer` drives the\n // pure reducer (consecutive dispatches in one gesture apply in sequence); the\n // store below decorates the state with the derived undo/redo + selection flags.\n const [state, dispatch] = useReducer(reducer, { composition, layout, newId, preserved }, (init) =>\n initialBuilderState(init.composition, init.layout, init.newId, init.preserved),\n )\n\n const store = useMemo<BuilderStore>(() => {\n const selectedNode = state.selectedId\n ? (getNode(state.present, state.selectedId) ?? null)\n : null\n return {\n ...state,\n canUndo: state.past.length > 0,\n canRedo: state.future.length > 0,\n selectedNode,\n field,\n placement,\n dispatch,\n }\n }, [state, field, placement, dispatch])\n\n return <BuilderContext.Provider value={store}>{children}</BuilderContext.Provider>\n}\n\nexport { useBuilder }\n"]}
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from 'react';
2
- import { resolveDeploymentBreakpoints } from '@elytracms/studio/lib/workspace';
2
+ import { resolveDeploymentBreakpoints } from '../../lib/workspace/index.js';
3
3
  /**
4
4
  * Tailwind's default `screens` scale. The documented fallback when a project
5
5
  * declares no `breakpoints`: an unconfigured Tailwind site uses exactly these,
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-breakpoints.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-breakpoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAA;AAsB9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAgC;IACnE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;IAC1B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;IAC1B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAC3B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAC3B,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;CAC7B,CAAA;AAED,2EAA2E;AAC3E,MAAM,IAAI,GAAmB,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAa1E;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,iBAA0D;IAE1D,MAAM,MAAM,GAAG,iBAAiB,IAAI,wBAAwB,CAAA;IAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IAC5E,IAAI,iBAAiB,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO;YACL,SAAS,EAAE,CAAC,IAAI,CAAC;YACjB,KAAK,EACH,sIAAsI;SACzI,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAA;IAC5D,OAAO;QACL,SAAS,EAAE;YACT,IAAI;YACJ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;SACtG;KACF,CAAA;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,SAA2B,EAAE,SAAiB;IAC1E,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,IAAI,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,KAAK,CAAA;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,4BAA4B,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;AAClF,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport type { BreakpointConfig } from '@elytracms/core/studio-core'\nimport { resolveDeploymentBreakpoints } from '@elytracms/studio/lib/workspace'\n\n/**\n * EC-250 — canvas breakpoint preview, sourced from the consuming site's real\n * widths instead of a baked-in desktop/tablet/phone triple.\n *\n * A `Viewport` is a preview-width control only (v1, EC-211): switching one\n * resizes the preview frame so the author can verify components' OWN responsive\n * behavior. It is NOT per-breakpoint authoring and writes no graph command.\n */\n\n/** One option the breakpoint switcher offers: an id, a label, and a CSS width. */\nexport interface ViewportOption {\n /** The `builder.viewport` key this option sets ('full' or a breakpoint name). */\n key: string\n label: string\n /** CSS width the canvas frame takes: '100%' for full, else `${width}px`. */\n width: string\n /** Numeric min-width in px (absent for the full-width option). */\n px?: number\n}\n\n/**\n * Tailwind's default `screens` scale. The documented fallback when a project\n * declares no `breakpoints`: an unconfigured Tailwind site uses exactly these,\n * so it is the site's real screens — not the arbitrary legacy triple.\n */\nexport const TAILWIND_DEFAULT_SCREENS: readonly BreakpointConfig[] = [\n { name: 'sm', width: 640 },\n { name: 'md', width: 768 },\n { name: 'lg', width: 1024 },\n { name: 'xl', width: 1280 },\n { name: '2xl', width: 1536 },\n]\n\n/** The always-present unconstrained canvas width (the natural default). */\nconst FULL: ViewportOption = { key: 'full', label: 'Full', width: '100%' }\n\nexport interface ResolvedViewports {\n /** Full first, then each breakpoint ascending by width. Never empty. */\n viewports: ViewportOption[]\n /**\n * A visible validation issue (no silent fallback) when a project DECLARED\n * breakpoints but none are usable — the switcher shows Full only, with this\n * surfaced beside it. Absent in the common (config or Tailwind-default) case.\n */\n issue?: string\n}\n\n/**\n * Resolve the breakpoint switcher's options from a project's configured\n * breakpoints (EC-250):\n * - `undefined` (none declared) → Tailwind's default screens, no issue,\n * - declared + at least one usable → those (sorted ascending),\n * - declared but none usable (empty / non-positive widths) → Full only + a\n * visible issue, never the legacy desktop/tablet/phone triple.\n * The full-width option is always present and first. Deterministic.\n */\nexport function resolveCanvasViewports(\n configBreakpoints: readonly BreakpointConfig[] | undefined,\n): ResolvedViewports {\n const source = configBreakpoints ?? TAILWIND_DEFAULT_SCREENS\n const usable = source.filter((b) => Number.isFinite(b.width) && b.width > 0)\n if (configBreakpoints !== undefined && usable.length === 0) {\n return {\n viewports: [FULL],\n issue:\n 'No usable canvas breakpoints — `breakpoints` is configured but empty or has no positive widths. Each needs a positive `width` in px.',\n }\n }\n const sorted = [...usable].sort((a, b) => a.width - b.width)\n return {\n viewports: [\n FULL,\n ...sorted.map((b) => ({ key: b.name, label: b.label ?? b.name, width: `${b.width}px`, px: b.width })),\n ],\n }\n}\n\n/** The CSS width for the active viewport key, falling back to full. */\nexport function viewportWidth(viewports: ViewportOption[], activeKey: string): string {\n return (viewports.find((v) => v.key === activeKey) ?? viewports[0]!).width\n}\n\n/**\n * The resolved breakpoint switcher for the active config project. The config is\n * static per studio boot, so this memoizes on nothing — both the toolbar switcher\n * and the canvas frame read the SAME resolved set (no drift).\n */\nexport function useCanvasViewports(): ResolvedViewports {\n return useMemo(() => resolveCanvasViewports(resolveDeploymentBreakpoints()), [])\n}\n"]}
1
+ {"version":3,"file":"canvas-breakpoints.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-breakpoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AAsBlE;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAgC;IACnE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;IAC1B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;IAC1B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAC3B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAC3B,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;CAC7B,CAAA;AAED,2EAA2E;AAC3E,MAAM,IAAI,GAAmB,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAa1E;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,iBAA0D;IAE1D,MAAM,MAAM,GAAG,iBAAiB,IAAI,wBAAwB,CAAA;IAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IAC5E,IAAI,iBAAiB,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO;YACL,SAAS,EAAE,CAAC,IAAI,CAAC;YACjB,KAAK,EACH,sIAAsI;SACzI,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAA;IAC5D,OAAO;QACL,SAAS,EAAE;YACT,IAAI;YACJ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;SACtG;KACF,CAAA;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,SAA2B,EAAE,SAAiB;IAC1E,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,IAAI,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,KAAK,CAAA;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,4BAA4B,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;AAClF,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport type { BreakpointConfig } from '@elytracms/core/studio-core'\nimport { resolveDeploymentBreakpoints } from '../../lib/workspace'\n\n/**\n * EC-250 — canvas breakpoint preview, sourced from the consuming site's real\n * widths instead of a baked-in desktop/tablet/phone triple.\n *\n * A `Viewport` is a preview-width control only (v1, EC-211): switching one\n * resizes the preview frame so the author can verify components' OWN responsive\n * behavior. It is NOT per-breakpoint authoring and writes no graph command.\n */\n\n/** One option the breakpoint switcher offers: an id, a label, and a CSS width. */\nexport interface ViewportOption {\n /** The `builder.viewport` key this option sets ('full' or a breakpoint name). */\n key: string\n label: string\n /** CSS width the canvas frame takes: '100%' for full, else `${width}px`. */\n width: string\n /** Numeric min-width in px (absent for the full-width option). */\n px?: number\n}\n\n/**\n * Tailwind's default `screens` scale. The documented fallback when a project\n * declares no `breakpoints`: an unconfigured Tailwind site uses exactly these,\n * so it is the site's real screens — not the arbitrary legacy triple.\n */\nexport const TAILWIND_DEFAULT_SCREENS: readonly BreakpointConfig[] = [\n { name: 'sm', width: 640 },\n { name: 'md', width: 768 },\n { name: 'lg', width: 1024 },\n { name: 'xl', width: 1280 },\n { name: '2xl', width: 1536 },\n]\n\n/** The always-present unconstrained canvas width (the natural default). */\nconst FULL: ViewportOption = { key: 'full', label: 'Full', width: '100%' }\n\nexport interface ResolvedViewports {\n /** Full first, then each breakpoint ascending by width. Never empty. */\n viewports: ViewportOption[]\n /**\n * A visible validation issue (no silent fallback) when a project DECLARED\n * breakpoints but none are usable — the switcher shows Full only, with this\n * surfaced beside it. Absent in the common (config or Tailwind-default) case.\n */\n issue?: string\n}\n\n/**\n * Resolve the breakpoint switcher's options from a project's configured\n * breakpoints (EC-250):\n * - `undefined` (none declared) → Tailwind's default screens, no issue,\n * - declared + at least one usable → those (sorted ascending),\n * - declared but none usable (empty / non-positive widths) → Full only + a\n * visible issue, never the legacy desktop/tablet/phone triple.\n * The full-width option is always present and first. Deterministic.\n */\nexport function resolveCanvasViewports(\n configBreakpoints: readonly BreakpointConfig[] | undefined,\n): ResolvedViewports {\n const source = configBreakpoints ?? TAILWIND_DEFAULT_SCREENS\n const usable = source.filter((b) => Number.isFinite(b.width) && b.width > 0)\n if (configBreakpoints !== undefined && usable.length === 0) {\n return {\n viewports: [FULL],\n issue:\n 'No usable canvas breakpoints — `breakpoints` is configured but empty or has no positive widths. Each needs a positive `width` in px.',\n }\n }\n const sorted = [...usable].sort((a, b) => a.width - b.width)\n return {\n viewports: [\n FULL,\n ...sorted.map((b) => ({ key: b.name, label: b.label ?? b.name, width: `${b.width}px`, px: b.width })),\n ],\n }\n}\n\n/** The CSS width for the active viewport key, falling back to full. */\nexport function viewportWidth(viewports: ViewportOption[], activeKey: string): string {\n return (viewports.find((v) => v.key === activeKey) ?? viewports[0]!).width\n}\n\n/**\n * The resolved breakpoint switcher for the active config project. The config is\n * static per studio boot, so this memoizes on nothing — both the toolbar switcher\n * and the canvas frame read the SAME resolved set (no drift).\n */\nexport function useCanvasViewports(): ResolvedViewports {\n return useMemo(() => resolveCanvasViewports(resolveDeploymentBreakpoints()), [])\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { RefObject } from 'react';
2
2
  import type { ComponentNode, LayoutContainerIds, PlacementScope } from '@elytracms/core/project-graph';
3
- import type { ManifestRules } from '@elytracms/studio/lib/dnd/drop-rules';
4
- import type { StudioRegistry } from '@elytracms/studio/lib/registry';
3
+ import type { ManifestRules } from '../../lib/dnd/drop-rules.js';
4
+ import type { StudioRegistry } from '../../lib/registry/index.js';
5
5
  import type { BuilderAction } from './builder-store.js';
6
6
  import type { ActiveInlineEdit } from './canvas-inline-editing.js';
7
7
  /**
@@ -1,6 +1,6 @@
1
1
  import { useEffect } from 'react';
2
2
  import { getNode, getParentInfo } from '@elytracms/core/project-graph';
3
- import { canNestInto, containerFlowAxis, dropIndicatorFor, dropPositionForPoint, manifestRules, resolveDrop, } from '@elytracms/studio/lib/dnd/drop-rules';
3
+ import { canNestInto, containerFlowAxis, dropIndicatorFor, dropPositionForPoint, manifestRules, resolveDrop, } from '../../lib/dnd/drop-rules.js';
4
4
  import { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session.js';
5
5
  import { wrapperAt } from './canvas-dom.js';
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-dnd.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-dnd.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect } from 'react'\nimport type { RefObject } from 'react'\nimport { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode, LayoutContainerIds, PlacementScope } from '@elytracms/core/project-graph'\nimport {\n canNestInto,\n containerFlowAxis,\n dropIndicatorFor,\n dropPositionForPoint,\n manifestRules,\n resolveDrop,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { DropPlan, DropPosition, FlowAxis, ManifestRules } from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { BuilderAction } from './builder-store'\nimport { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session'\nimport type { ActiveInlineEdit } from './canvas-inline-editing'\nimport { wrapperAt } from './canvas-dom'\n\n/**\n * Drag & drop on the canvas (EC-160), extracted from `BuilderCanvas` (EC-330).\n * Eligibility is the pure shared rule set (`lib/dnd/drop-rules`) — the same\n * functions the page tree consumes — evaluated live during `dragover`; the\n * wrapper under the pointer shows the resolution (`data-ec-drop`:\n * into/before/after, or `blocked` for a visible rejection). A completed drop\n * dispatches exactly one builder action (move or insert) → one operations\n * command → one undo step. Ineligible targets never `preventDefault`, so\n * releasing over one is a native no-op.\n *\n * The listener wiring is framework-free ({@link attachCanvasDndListeners},\n * the `clipboard-actions` pattern) so the whole gesture state machine —\n * arm-on-mousedown, live indicator, plan resolution, one-dispatch drop — is\n * unit-testable without a DOM environment; {@link useCanvasDnd} only owns the\n * React effect lifecycle.\n */\n\n/** The structural slice of the iframe document the DnD wiring needs. */\nexport interface CanvasDndDocument {\n addEventListener(type: string, listener: (e: Event) => void, capture?: boolean): void\n removeEventListener(type: string, listener: (e: Event) => void, capture?: boolean): void\n querySelector(selectors: string): Element | null\n}\n\nexport interface CanvasDndDeps {\n /** The current composition root (read live — listeners outlive renders). */\n currentRoot(): ComponentNode\n /** Manifest lookup feeding the pure drop rules. */\n getManifest(componentId: string): ManifestRules | undefined\n /**\n * EC-584: the placement scope `resolveDrop` judges by — the manifest lookup and\n * the composition field's effective top-level vocabulary (`builder.placement`,\n * the scope the reducer enforces). A required key: the wiring must state it.\n */\n placement: PlacementScope\n /**\n * The layout-role component ids (EC-591, `ComponentRegistry.layout`): the flow\n * axis of a Stack/Grid parent, and the Grid the wrap gesture builds — whose side\n * bands are offered only when a component fills `layout.grid`.\n */\n layout: LayoutContainerIds\n dispatch(action: BuilderAction): void\n /**\n * Whether `target` sits inside the live inline editor (EC-158): a press\n * there must keep native caret clicks/text selection, never start a node drag.\n */\n isEditingTarget(target: EventTarget | null): boolean\n}\n\n/**\n * Wire the canvas drag-and-drop listeners onto the iframe document (native —\n * React's synthetic events don't cross frames). Returns the detach function.\n */\nexport function attachCanvasDndListeners(doc: CanvasDndDocument, deps: CanvasDndDeps): () => void {\n const { currentRoot, getManifest, placement, layout, dispatch } = deps\n\n let dropWrapper: HTMLElement | null = null\n let dropPlan: DropPlan | null = null\n const clearIndicator = () => {\n dropWrapper?.removeAttribute('data-ec-drop')\n dropWrapper?.removeAttribute('data-ec-drop-axis')\n dropWrapper = null\n dropPlan = null\n }\n const showIndicator = (wrapper: HTMLElement | null, state: string | null, axis: FlowAxis) => {\n if (dropWrapper && dropWrapper !== wrapper) {\n dropWrapper.removeAttribute('data-ec-drop')\n dropWrapper.removeAttribute('data-ec-drop-axis')\n }\n dropWrapper = wrapper\n if (!wrapper) return\n if (state) {\n wrapper.setAttribute('data-ec-drop', state)\n // Before/after bars render along the parent's flow axis (EC-162).\n wrapper.setAttribute('data-ec-drop-axis', axis === 'horizontal' ? 'x' : 'y')\n } else {\n wrapper.removeAttribute('data-ec-drop')\n wrapper.removeAttribute('data-ec-drop-axis')\n }\n }\n\n // Wrappers are armed draggable per-gesture on mousedown, never via render\n // state: the active inline editor (EC-158) must keep native caret clicks\n // and text selection, so a press inside the editing host disarms its\n // wrapper instead of starting a node drag.\n const onMouseDown = (e: Event) => {\n const wrapper = wrapperAt(e.target)\n if (!wrapper) return\n const root = currentRoot()\n const nodeId = wrapper.getAttribute('data-ec-node-id')\n const inEditor = deps.isEditingTarget(e.target)\n if (root && nodeId && nodeId !== root.id && !inEditor) {\n wrapper.setAttribute('draggable', 'true')\n } else {\n wrapper.removeAttribute('draggable')\n }\n }\n\n const onDragStart = (e: Event) => {\n const event = e as DragEvent\n // Drags inside the live inline editor are native text drags — leave them alone.\n if (deps.isEditingTarget(event.target)) return\n const wrapper = wrapperAt(event.target)\n if (!wrapper) return\n const root = currentRoot()\n const nodeId = wrapper.getAttribute('data-ec-node-id')!\n const node = root ? getNode(root, nodeId) : undefined\n if (!root || !node || nodeId === root.id) {\n event.preventDefault()\n return\n }\n beginDrag(\n { subject: { kind: 'node', nodeId, componentId: node.componentId }, source: 'canvas', label: node.componentId },\n event.dataTransfer,\n )\n }\n\n const onDragOver = (e: Event) => {\n const event = e as DragEvent\n const drag = activeDrag()\n if (!drag) return\n const root = currentRoot()\n if (!root) return\n const wrapper = wrapperAt(event.target)\n // Empty space around the page subtree targets the root slot (append).\n const refId = wrapper?.getAttribute('data-ec-node-id') ?? root.id\n const refWrapper =\n wrapper ?? (doc.querySelector(`[data-ec-node-id=\"${root.id}\"]`) as HTMLElement | null)\n const refNode = getNode(root, refId)\n if (!refNode) {\n clearIndicator()\n return\n }\n let position: DropPosition = 'into'\n let flow: FlowAxis = 'vertical'\n if (wrapper && refId !== root.id) {\n const rect = wrapper.getBoundingClientRect()\n // Hit-bands follow the parent's flow axis (EC-162); the cross-axis\n // side bands of vertically flowing targets are the wrap gesture.\n flow = containerFlowAxis(getParentInfo(root, refId)?.parent, layout)\n position = dropPositionForPoint({\n x: rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5,\n y: rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5,\n flow,\n nestable: canNestInto(getManifest, refNode.componentId),\n edges: true,\n // EC-591: the wrap gesture exists only when a component fills `layout.grid`.\n wrappable: layout.grid !== undefined,\n })\n }\n const resolution = resolveDrop({\n root,\n getManifest,\n layout,\n placement,\n subject: drag.subject,\n location: { nodeId: refId, position },\n })\n showIndicator(refWrapper, dropIndicatorFor(resolution, position), flow)\n if (resolution.eligible) {\n dropPlan = resolution\n event.preventDefault()\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = drag.subject.kind === 'node' ? 'move' : 'copy'\n }\n } else {\n dropPlan = null\n }\n }\n\n const onDrop = (e: Event) => {\n const event = e as DragEvent\n const drag = activeDrag()\n if (!drag) return\n const plan = dropPlan\n clearIndicator()\n if (!plan) return\n event.preventDefault()\n // Exactly one dispatch per completed drop (one undo step, one command).\n if (plan.kind === 'move') {\n dispatch({ type: 'move', id: plan.nodeId, target: plan.target })\n } else if (plan.kind === 'wrap') {\n // EC-162 gesture: reference + subject become a two-column Grid.\n const subject =\n plan.nodeId !== undefined\n ? { kind: 'node' as const, nodeId: plan.nodeId }\n : drag.buildNode\n ? { kind: 'component' as const, node: drag.buildNode() }\n : null\n if (!subject) {\n endDrag('cancelled')\n return\n }\n dispatch({ type: 'wrap', referenceId: plan.referenceId, side: plan.side, subject })\n } else if (drag.buildNode) {\n dispatch({ type: 'insert', target: plan.target, node: drag.buildNode() })\n } else {\n endDrag('cancelled')\n return\n }\n endDrag('dropped')\n }\n\n const onDragEnd = () => {\n clearIndicator()\n endDrag('cancelled')\n }\n\n const onDragLeave = (e: Event) => {\n // Leaving the iframe document entirely (relatedTarget is null).\n if (!(e as DragEvent).relatedTarget) clearIndicator()\n }\n\n // Cleanup when the drag ends anywhere (e.g. dropped on the page tree).\n const unsubscribe = onDragSessionEnd(() => clearIndicator())\n\n doc.addEventListener('mousedown', onMouseDown, true)\n doc.addEventListener('dragstart', onDragStart)\n doc.addEventListener('dragover', onDragOver)\n doc.addEventListener('drop', onDrop)\n doc.addEventListener('dragend', onDragEnd)\n doc.addEventListener('dragleave', onDragLeave)\n return () => {\n unsubscribe()\n clearIndicator()\n doc.removeEventListener('mousedown', onMouseDown, true)\n doc.removeEventListener('dragstart', onDragStart)\n doc.removeEventListener('dragover', onDragOver)\n doc.removeEventListener('drop', onDrop)\n doc.removeEventListener('dragend', onDragEnd)\n doc.removeEventListener('dragleave', onDragLeave)\n }\n}\n\n/**\n * The React face of the canvas DnD wiring: attach on the iframe document once\n * it exists, detach on doc swap/unmount. State lives in refs on purpose —\n * listeners outlive renders and must read the *current* graph and editing\n * session without re-subscribing on every edit.\n */\nexport function useCanvasDnd(\n doc: Document | null,\n deps: {\n /** The live composition root (`builder.present`, mirrored into a ref). */\n presentRef: RefObject<ComponentNode>\n dispatch: (action: BuilderAction) => void\n studio: StudioRegistry\n /** The live inline-edit session (owned by `useCanvasInlineEditing`). */\n editingRef: RefObject<ActiveInlineEdit | null>\n /** EC-584: the store's placement scope (`builder.placement`). */\n placement: PlacementScope\n },\n): void {\n const { presentRef, dispatch, studio, editingRef, placement } = deps\n useEffect(() => {\n if (!doc) return\n return attachCanvasDndListeners(doc, {\n currentRoot: () => presentRef.current,\n // EC-386: adapt through `manifestRules` so blocks-prop regions (e.g. a\n // Section's `body`) receive drops with their `allow` leash, like raw slots.\n getManifest: (id) => manifestRules(studio.registry.getManifest(id)),\n placement,\n layout: studio.registry.layout,\n dispatch,\n isEditingTarget: (target) => editingRef.current?.host.contains(target as Node) ?? false,\n })\n }, [doc, dispatch, studio, presentRef, editingRef, placement])\n}\n"]}
1
+ {"version":3,"file":"canvas-dnd.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-dnd.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect } from 'react'\nimport type { RefObject } from 'react'\nimport { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode, LayoutContainerIds, PlacementScope } from '@elytracms/core/project-graph'\nimport {\n canNestInto,\n containerFlowAxis,\n dropIndicatorFor,\n dropPositionForPoint,\n manifestRules,\n resolveDrop,\n} from '../../lib/dnd/drop-rules'\nimport type { DropPlan, DropPosition, FlowAxis, ManifestRules } from '../../lib/dnd/drop-rules'\nimport type { StudioRegistry } from '../../lib/registry'\nimport type { BuilderAction } from './builder-store'\nimport { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session'\nimport type { ActiveInlineEdit } from './canvas-inline-editing'\nimport { wrapperAt } from './canvas-dom'\n\n/**\n * Drag & drop on the canvas (EC-160), extracted from `BuilderCanvas` (EC-330).\n * Eligibility is the pure shared rule set (`lib/dnd/drop-rules`) — the same\n * functions the page tree consumes — evaluated live during `dragover`; the\n * wrapper under the pointer shows the resolution (`data-ec-drop`:\n * into/before/after, or `blocked` for a visible rejection). A completed drop\n * dispatches exactly one builder action (move or insert) → one operations\n * command → one undo step. Ineligible targets never `preventDefault`, so\n * releasing over one is a native no-op.\n *\n * The listener wiring is framework-free ({@link attachCanvasDndListeners},\n * the `clipboard-actions` pattern) so the whole gesture state machine —\n * arm-on-mousedown, live indicator, plan resolution, one-dispatch drop — is\n * unit-testable without a DOM environment; {@link useCanvasDnd} only owns the\n * React effect lifecycle.\n */\n\n/** The structural slice of the iframe document the DnD wiring needs. */\nexport interface CanvasDndDocument {\n addEventListener(type: string, listener: (e: Event) => void, capture?: boolean): void\n removeEventListener(type: string, listener: (e: Event) => void, capture?: boolean): void\n querySelector(selectors: string): Element | null\n}\n\nexport interface CanvasDndDeps {\n /** The current composition root (read live — listeners outlive renders). */\n currentRoot(): ComponentNode\n /** Manifest lookup feeding the pure drop rules. */\n getManifest(componentId: string): ManifestRules | undefined\n /**\n * EC-584: the placement scope `resolveDrop` judges by — the manifest lookup and\n * the composition field's effective top-level vocabulary (`builder.placement`,\n * the scope the reducer enforces). A required key: the wiring must state it.\n */\n placement: PlacementScope\n /**\n * The layout-role component ids (EC-591, `ComponentRegistry.layout`): the flow\n * axis of a Stack/Grid parent, and the Grid the wrap gesture builds — whose side\n * bands are offered only when a component fills `layout.grid`.\n */\n layout: LayoutContainerIds\n dispatch(action: BuilderAction): void\n /**\n * Whether `target` sits inside the live inline editor (EC-158): a press\n * there must keep native caret clicks/text selection, never start a node drag.\n */\n isEditingTarget(target: EventTarget | null): boolean\n}\n\n/**\n * Wire the canvas drag-and-drop listeners onto the iframe document (native —\n * React's synthetic events don't cross frames). Returns the detach function.\n */\nexport function attachCanvasDndListeners(doc: CanvasDndDocument, deps: CanvasDndDeps): () => void {\n const { currentRoot, getManifest, placement, layout, dispatch } = deps\n\n let dropWrapper: HTMLElement | null = null\n let dropPlan: DropPlan | null = null\n const clearIndicator = () => {\n dropWrapper?.removeAttribute('data-ec-drop')\n dropWrapper?.removeAttribute('data-ec-drop-axis')\n dropWrapper = null\n dropPlan = null\n }\n const showIndicator = (wrapper: HTMLElement | null, state: string | null, axis: FlowAxis) => {\n if (dropWrapper && dropWrapper !== wrapper) {\n dropWrapper.removeAttribute('data-ec-drop')\n dropWrapper.removeAttribute('data-ec-drop-axis')\n }\n dropWrapper = wrapper\n if (!wrapper) return\n if (state) {\n wrapper.setAttribute('data-ec-drop', state)\n // Before/after bars render along the parent's flow axis (EC-162).\n wrapper.setAttribute('data-ec-drop-axis', axis === 'horizontal' ? 'x' : 'y')\n } else {\n wrapper.removeAttribute('data-ec-drop')\n wrapper.removeAttribute('data-ec-drop-axis')\n }\n }\n\n // Wrappers are armed draggable per-gesture on mousedown, never via render\n // state: the active inline editor (EC-158) must keep native caret clicks\n // and text selection, so a press inside the editing host disarms its\n // wrapper instead of starting a node drag.\n const onMouseDown = (e: Event) => {\n const wrapper = wrapperAt(e.target)\n if (!wrapper) return\n const root = currentRoot()\n const nodeId = wrapper.getAttribute('data-ec-node-id')\n const inEditor = deps.isEditingTarget(e.target)\n if (root && nodeId && nodeId !== root.id && !inEditor) {\n wrapper.setAttribute('draggable', 'true')\n } else {\n wrapper.removeAttribute('draggable')\n }\n }\n\n const onDragStart = (e: Event) => {\n const event = e as DragEvent\n // Drags inside the live inline editor are native text drags — leave them alone.\n if (deps.isEditingTarget(event.target)) return\n const wrapper = wrapperAt(event.target)\n if (!wrapper) return\n const root = currentRoot()\n const nodeId = wrapper.getAttribute('data-ec-node-id')!\n const node = root ? getNode(root, nodeId) : undefined\n if (!root || !node || nodeId === root.id) {\n event.preventDefault()\n return\n }\n beginDrag(\n { subject: { kind: 'node', nodeId, componentId: node.componentId }, source: 'canvas', label: node.componentId },\n event.dataTransfer,\n )\n }\n\n const onDragOver = (e: Event) => {\n const event = e as DragEvent\n const drag = activeDrag()\n if (!drag) return\n const root = currentRoot()\n if (!root) return\n const wrapper = wrapperAt(event.target)\n // Empty space around the page subtree targets the root slot (append).\n const refId = wrapper?.getAttribute('data-ec-node-id') ?? root.id\n const refWrapper =\n wrapper ?? (doc.querySelector(`[data-ec-node-id=\"${root.id}\"]`) as HTMLElement | null)\n const refNode = getNode(root, refId)\n if (!refNode) {\n clearIndicator()\n return\n }\n let position: DropPosition = 'into'\n let flow: FlowAxis = 'vertical'\n if (wrapper && refId !== root.id) {\n const rect = wrapper.getBoundingClientRect()\n // Hit-bands follow the parent's flow axis (EC-162); the cross-axis\n // side bands of vertically flowing targets are the wrap gesture.\n flow = containerFlowAxis(getParentInfo(root, refId)?.parent, layout)\n position = dropPositionForPoint({\n x: rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5,\n y: rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5,\n flow,\n nestable: canNestInto(getManifest, refNode.componentId),\n edges: true,\n // EC-591: the wrap gesture exists only when a component fills `layout.grid`.\n wrappable: layout.grid !== undefined,\n })\n }\n const resolution = resolveDrop({\n root,\n getManifest,\n layout,\n placement,\n subject: drag.subject,\n location: { nodeId: refId, position },\n })\n showIndicator(refWrapper, dropIndicatorFor(resolution, position), flow)\n if (resolution.eligible) {\n dropPlan = resolution\n event.preventDefault()\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = drag.subject.kind === 'node' ? 'move' : 'copy'\n }\n } else {\n dropPlan = null\n }\n }\n\n const onDrop = (e: Event) => {\n const event = e as DragEvent\n const drag = activeDrag()\n if (!drag) return\n const plan = dropPlan\n clearIndicator()\n if (!plan) return\n event.preventDefault()\n // Exactly one dispatch per completed drop (one undo step, one command).\n if (plan.kind === 'move') {\n dispatch({ type: 'move', id: plan.nodeId, target: plan.target })\n } else if (plan.kind === 'wrap') {\n // EC-162 gesture: reference + subject become a two-column Grid.\n const subject =\n plan.nodeId !== undefined\n ? { kind: 'node' as const, nodeId: plan.nodeId }\n : drag.buildNode\n ? { kind: 'component' as const, node: drag.buildNode() }\n : null\n if (!subject) {\n endDrag('cancelled')\n return\n }\n dispatch({ type: 'wrap', referenceId: plan.referenceId, side: plan.side, subject })\n } else if (drag.buildNode) {\n dispatch({ type: 'insert', target: plan.target, node: drag.buildNode() })\n } else {\n endDrag('cancelled')\n return\n }\n endDrag('dropped')\n }\n\n const onDragEnd = () => {\n clearIndicator()\n endDrag('cancelled')\n }\n\n const onDragLeave = (e: Event) => {\n // Leaving the iframe document entirely (relatedTarget is null).\n if (!(e as DragEvent).relatedTarget) clearIndicator()\n }\n\n // Cleanup when the drag ends anywhere (e.g. dropped on the page tree).\n const unsubscribe = onDragSessionEnd(() => clearIndicator())\n\n doc.addEventListener('mousedown', onMouseDown, true)\n doc.addEventListener('dragstart', onDragStart)\n doc.addEventListener('dragover', onDragOver)\n doc.addEventListener('drop', onDrop)\n doc.addEventListener('dragend', onDragEnd)\n doc.addEventListener('dragleave', onDragLeave)\n return () => {\n unsubscribe()\n clearIndicator()\n doc.removeEventListener('mousedown', onMouseDown, true)\n doc.removeEventListener('dragstart', onDragStart)\n doc.removeEventListener('dragover', onDragOver)\n doc.removeEventListener('drop', onDrop)\n doc.removeEventListener('dragend', onDragEnd)\n doc.removeEventListener('dragleave', onDragLeave)\n }\n}\n\n/**\n * The React face of the canvas DnD wiring: attach on the iframe document once\n * it exists, detach on doc swap/unmount. State lives in refs on purpose —\n * listeners outlive renders and must read the *current* graph and editing\n * session without re-subscribing on every edit.\n */\nexport function useCanvasDnd(\n doc: Document | null,\n deps: {\n /** The live composition root (`builder.present`, mirrored into a ref). */\n presentRef: RefObject<ComponentNode>\n dispatch: (action: BuilderAction) => void\n studio: StudioRegistry\n /** The live inline-edit session (owned by `useCanvasInlineEditing`). */\n editingRef: RefObject<ActiveInlineEdit | null>\n /** EC-584: the store's placement scope (`builder.placement`). */\n placement: PlacementScope\n },\n): void {\n const { presentRef, dispatch, studio, editingRef, placement } = deps\n useEffect(() => {\n if (!doc) return\n return attachCanvasDndListeners(doc, {\n currentRoot: () => presentRef.current,\n // EC-386: adapt through `manifestRules` so blocks-prop regions (e.g. a\n // Section's `body`) receive drops with their `allow` leash, like raw slots.\n getManifest: (id) => manifestRules(studio.registry.getManifest(id)),\n placement,\n layout: studio.registry.layout,\n dispatch,\n isEditingTarget: (target) => editingRef.current?.host.contains(target as Node) ?? false,\n })\n }, [doc, dispatch, studio, presentRef, editingRef, placement])\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { RefObject } from 'react';
2
2
  import type { ComponentManifest } from '@elytracms/core/component-registry';
3
3
  import type { ComponentNode } from '@elytracms/core/project-graph';
4
- import type { StudioRegistry } from '@elytracms/studio/lib/registry';
4
+ import type { StudioRegistry } from '../../lib/registry/index.js';
5
5
  import type { BuilderAction } from './builder-store.js';
6
6
  /**
7
7
  * Inline editing on the canvas (EC-158 plain text, EC-159 rich text),
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-inline-editing.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-inline-editing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGnD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAEnE,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,2BAA2B,CAAA;AAIlC,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,kBAAkB,EAClB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AA6DxC;;;GAGG;AACH,SAAS,gBAAgB,CAAC,OAAoB;IAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,YAAY,CAAuB,CAAA;IACtE,OAAO,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAChD,GAAgC,EAChC,IAA6B;IAE7B,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAA;IAEzB,4EAA4E;IAC5E,MAAM,SAAS,GAAG,CAAC,OAAoB,EAAE,IAAiB,EAAE,OAA0B,EAAE,EAAE;QACxF,IAAI,IAAI,GAAG,KAAK,CAAA;QAChB,MAAM,MAAM,GAAG,CAAC,IAAyB,EAAE,EAAE;YAC3C,IAAI,IAAI;gBAAE,OAAM;YAChB,IAAI,GAAG,IAAI,CAAA;YACX,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACrB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAA;YACnC,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;YAC9C,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;YACxC,IAAI,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAA;YACvC,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,CAAA;YACpC,OAAO,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAA;YAC1C,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBACpC,IAAI,OAAO,EAAE,CAAC;oBACZ,QAAQ,CAAC;wBACP,IAAI,EAAE,SAAS;wBACf,EAAE,EAAE,OAAO,CAAC,MAAM;wBAClB,IAAI,EAAE,OAAO,CAAC,QAAQ;wBACtB,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC;qBACjC,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAA;gBAChC,IAAI,OAAO;oBAAE,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,KAAK,CAAA;YAC/C,CAAC;YACD,GAAG,CAAC,YAAY,EAAE,EAAE,eAAe,EAAE,CAAA;QACvC,CAAC,CAAA;QACD,MAAM,SAAS,GAAG,CAAC,CAAgB,EAAE,EAAE;YACrC,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;gBACtB,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,MAAM,CAAC,QAAQ,CAAC,CAAA;YAClB,CAAC;iBAAM,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC9B,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,MAAM,CAAC,QAAQ,CAAC,CAAA;YAClB,CAAC;QACH,CAAC,CAAA;QACD,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QACrC,0EAA0E;QAC1E,IAAI,CAAC,YAAY,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,CAAA;QACtD,IAAI,CAAC,IAAI,CAAC,iBAAiB;YAAE,IAAI,CAAC,YAAY,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAA;QACzE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;QACzC,OAAO,CAAC,YAAY,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAA;QAC3C,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QAC3C,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACrC,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QACzD,IAAI,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QACnC,uEAAuE;QACvE,8CAA8C;QAC9C,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,EAAE,CAAA;QAC9B,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACnE,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;YAC/B,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;YAC9B,GAAG,EAAE,eAAe,EAAE,CAAA;YACtB,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;QACtB,CAAC;IACH,CAAC,CAAA;IAED,4EAA4E;IAC5E,MAAM,aAAa,GAAG,CAAC,OAAoB,EAAE,MAAc,EAAE,MAA0B,EAAE,EAAE;QACzF,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;QACtC,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,iEAAiE;QACjE,qEAAqE;QACrE,sEAAsE;QACtE,mEAAmE;QACnE,kEAAkE;QAClE,yEAAyE;QACzE,MAAM,KAAK,GAAG,qBAAqB,CACjC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAmB,EAAE,EACxE,IAA+B,CAChC,CAAA;QACD,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,OAAO,GAAG,2BAA2B,CAAC;YAC1C,MAAM;YACN,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,KAAK;SACvB,CAAC,CAAA;QACF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAiC,CAAA;YACjD,EAAE,CAAC,YAAY,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;YAC9D,EAAE,CAAC,YAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAA;QAC7C,CAAC;QACD,IAAI,IAAI,GAAG,KAAK,CAAA;QAChB,MAAM,MAAM,GAAG,CAAC,IAAyB,EAAE,EAAE;YAC3C,IAAI,IAAI;gBAAE,OAAM;YAChB,IAAI,GAAG,IAAI,CAAA;YACX,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACrB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;YAC1B,iEAAiE;YACjE,mEAAmE;YACnE,8DAA8D;YAC9D,+DAA+D;YAC/D,4BAA4B;YAC5B,MAAM,MAAM,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAA+B,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;YAC7F,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;YAC9C,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;YACxC,IAAI,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAA;YACvC,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,CAAA;YACpC,OAAO,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAA;YAC1C,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAA;YAC9B,IAAI,MAAM,EAAE,CAAC;gBACX,mEAAmE;gBACnE,8DAA8D;gBAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;gBAC1C,IAAI,OAAO,EAAE,CAAC;oBACZ,QAAQ,CAAC;wBACP,IAAI,EAAE,SAAS;wBACf,EAAE,EAAE,OAAO,CAAC,MAAM;wBAClB,IAAI,EAAE,OAAO,CAAC,QAAQ;wBACtB,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,KAA6B,CAAC;qBACzD,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,EAAE,CAAA;YAClB,CAAC;YACD,GAAG,CAAC,YAAY,EAAE,EAAE,eAAe,EAAE,CAAA;QACvC,CAAC,CAAA;QACD,MAAM,SAAS,GAAG,CAAC,CAAgB,EAAE,EAAE;YACrC,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACvB,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,MAAM,CAAC,QAAQ,CAAC,CAAA;YAClB,CAAC;iBAAM,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzD,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,MAAM,CAAC,QAAQ,CAAC,CAAA;YAClB,CAAC;YACD,qEAAqE;QACvE,CAAC,CAAA;QACD,MAAM,MAAM,GAAG,CAAC,CAAa,EAAE,EAAE;YAC/B,gEAAgE;YAChE,MAAM,IAAI,GAAG,CAAC,CAAC,aAAmC,CAAA;YAClD,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC,sBAAsB,CAAC;gBAAE,OAAM;YACnD,MAAM,CAAC,QAAQ,CAAC,CAAA;QAClB,CAAC,CAAA;QACD,IAAI,CAAC,YAAY,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAA;QAC5C,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;QACzC,OAAO,CAAC,YAAY,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAA;QAC3C,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QAC3C,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACrC,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QACzC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;QAC1B,IAAI,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IACrC,CAAC,CAAA;IAED,MAAM,UAAU,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC9B,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QACnC,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAE,CAAA;QACvD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAA;QACjC,IAAI,OAAO,EAAE,CAAC;YACZ,gEAAgE;YAChE,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;gBAAE,OAAM;YACrC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAAA;QAChD,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACnD,MAAM,MAAM,GAAG,yBAAyB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QACxD,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAuB,CAAA;YAC5E,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,UAAU;gBAAE,OAAM;YACrD,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;YACxC,SAAS,CACP,OAAO,EACP,IAAI,EACJ,uBAAuB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CACvF,CAAA;YACD,OAAM;QACR,CAAC;QACD,MAAM,UAAU,GAAG,yBAAyB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC5D,IAAI,CAAC,UAAU;YAAE,OAAM;QACvB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;QACxC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;IAC5C,CAAC,CAAA;IAED,GAAG,CAAC,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,CAAA;IAClD,OAAO,GAAG,EAAE;QACV,GAAG,CAAC,mBAAmB,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,CAAA;QACrD,iFAAiF;QACjF,IAAI,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;IACrC,CAAC,CAAA;AACH,CAAC;AAuBD;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,GAAoB,EACpB,IAKC;IAED,MAAM,UAAU,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAA;IACxD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAA;IAC1E,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAyB,EAAE,CAAC,CAAA;IAChF,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IAE7C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,OAAO,kCAAkC,CAAC,GAAG,EAAE;YAC7C,WAAW,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO;YACrC,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACpD,QAAQ;YACR,UAAU,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO;YACpC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;gBACnB,UAAU,CAAC,OAAO,GAAG,IAAI,CAAA;gBACzB,UAAU,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;YAC3B,CAAC;YACD,eAAe;YACf,iBAAiB,EAAE,CAAC,MAAM,EAAE,EAAE,CAC5B,iBAAiB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;SAChF,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;IAEvC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,OAAO,EAAE,CAAA;AAC9D,CAAC","sourcesContent":["import { useEffect, useRef, useState } from 'react'\nimport type { RefObject } from 'react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { getNode, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport {\n PRESERVE_NODE_ATTR,\n collectPreservedPairs,\n richTextDocFromDom,\n} from '@elytracms/core/rich-text'\nimport type { RtDomElement, RichTextNode } from '@elytracms/core/rich-text'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { BuilderAction } from './builder-store'\nimport {\n createInlineEditSession,\n createInlineRichTextSession,\n findInlineTextHost,\n resolveInlineEditableProp,\n resolveInlineRichTextProp,\n} from './inline-edit'\nimport type { InlineEditSession, InlineRichTextProp } from './inline-edit'\nimport { wrapperAt } from './canvas-dom'\n\n/**\n * Inline editing on the canvas (EC-158 plain text, EC-159 rich text),\n * extracted from `BuilderCanvas` (EC-330): double-click on a node whose\n * manifest flags an inline-editable prop turns the *rendered element itself*\n * contenteditable — no overlay, no input swap, so typography and layout are\n * identical in edit and render states. A session finishes exactly once, and a\n * commit dispatches exactly one `setProp` action — one undo step. The graph\n * stays the single source of truth.\n *\n * Commit/cancel gestures: plain text commits on Enter or blur; rich text is\n * multiline, so plain Enter inserts a paragraph and the commit gesture is\n * Cmd/Ctrl+Enter or blur (Enter-to-commit would make multi-paragraph content\n * unauthorable in place). Esc always cancels, dispatching nothing.\n *\n * The listener wiring is framework-free\n * ({@link attachCanvasInlineEditingListeners}, the `clipboard-actions`\n * pattern) so the begin/commit/cancel state machine is unit-testable without\n * a DOM environment; {@link useCanvasInlineEditing} owns the React state the\n * canvas renders from (the live rich-text host, the remount epochs) and the\n * session ref other canvas listeners consult.\n */\n\n/** A live inline-editing session bound to its DOM host (EC-158/EC-159). */\nexport interface ActiveInlineEdit {\n nodeId: string\n host: HTMLElement\n /** Finish the session exactly once (later calls are no-ops). */\n finish: (mode: 'commit' | 'cancel') => void\n}\n\n/** The structural slice of the iframe document the editing wiring needs. */\nexport interface CanvasInlineEditingDocument {\n addEventListener(type: string, listener: (e: Event) => void, capture?: boolean): void\n removeEventListener(type: string, listener: (e: Event) => void, capture?: boolean): void\n getSelection(): Selection | null\n createRange(): Range\n}\n\nexport interface CanvasInlineEditingDeps {\n /** The current composition root (read live — listeners outlive renders). */\n currentRoot(): ComponentNode\n getManifest(componentId: string): ComponentManifest | undefined\n dispatch(action: BuilderAction): void\n /** The live session — imperative DOM state, mirrored for other listeners. */\n getEditing(): ActiveInlineEdit | null\n setEditing(edit: ActiveInlineEdit | null): void\n /** Mirror the rich-text host into render state (mounts the mark toolbar). */\n setRichEditHost(host: HTMLElement | null): void\n /**\n * A rich-text session mutates the rendered DOM behind React's back\n * (contenteditable + mark commands), so finishing one must REMOUNT the\n * node's subtree — bumping its epoch changes the decorate key, React\n * discards the edited DOM wholesale and re-renders from the graph (the\n * committed value, or the untouched original on cancel). Patching the old\n * fiber tree against editor-mutated DOM would leave stale artifacts.\n */\n bumpRichEditEpoch(nodeId: string): void\n}\n\n/**\n * The rendered `.rich-text` element inside a node's wrapper — the rich-text\n * editing host (EC-159). Never crosses into a nested node's wrapper.\n */\nfunction findRichTextHost(wrapper: HTMLElement): HTMLElement | null {\n const host = wrapper.querySelector('.rich-text') as HTMLElement | null\n return host && host.closest('[data-ec-node-id]') === wrapper ? host : null\n}\n\n/**\n * Wire the inline-editing dblclick listener onto the iframe document (native —\n * React's synthetic events don't cross frames). Returns the detach function;\n * detaching mid-session cancels it (never dispatch from cleanup).\n */\nexport function attachCanvasInlineEditingListeners(\n doc: CanvasInlineEditingDocument,\n deps: CanvasInlineEditingDeps,\n): () => void {\n const { dispatch } = deps\n\n // ---- plain text (EC-158) ------------------------------------------------\n const beginText = (wrapper: HTMLElement, host: HTMLElement, session: InlineEditSession) => {\n let done = false\n const finish = (mode: 'commit' | 'cancel') => {\n if (done) return\n done = true\n deps.setEditing(null)\n const text = host.textContent ?? ''\n host.removeEventListener('keydown', onKeyDown)\n host.removeEventListener('blur', onBlur)\n host.removeAttribute('contenteditable')\n host.removeAttribute('data-ec-edit')\n wrapper.removeAttribute('data-ec-editing')\n if (mode === 'commit') {\n const outcome = session.commit(text)\n if (outcome) {\n dispatch({\n type: 'setProp',\n id: outcome.nodeId,\n name: outcome.propName,\n value: staticProp(outcome.value),\n })\n }\n } else {\n const outcome = session.cancel()\n if (outcome) host.textContent = outcome.value\n }\n doc.getSelection()?.removeAllRanges()\n }\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n e.stopPropagation()\n finish('commit')\n } else if (e.key === 'Escape') {\n e.preventDefault()\n e.stopPropagation()\n finish('cancel')\n }\n }\n const onBlur = () => finish('commit')\n // `plaintext-only` keeps paste/typing plain; fall back where unsupported.\n host.setAttribute('contenteditable', 'plaintext-only')\n if (!host.isContentEditable) host.setAttribute('contenteditable', 'true')\n host.setAttribute('data-ec-edit', 'text')\n wrapper.setAttribute('data-ec-editing', '')\n host.addEventListener('keydown', onKeyDown)\n host.addEventListener('blur', onBlur)\n deps.setEditing({ nodeId: session.nodeId, host, finish })\n host.focus({ preventScroll: true })\n // Keep the gesture's native caret/word selection when it landed inside\n // the host; otherwise select the whole value.\n const sel = doc.getSelection()\n if (!sel || sel.rangeCount === 0 || !host.contains(sel.anchorNode)) {\n const range = doc.createRange()\n range.selectNodeContents(host)\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n\n // ---- rich text (EC-159) -------------------------------------------------\n const beginRichText = (wrapper: HTMLElement, nodeId: string, target: InlineRichTextProp) => {\n const host = findRichTextHost(wrapper)\n if (!host) return\n // Pair the storage tree against the rendered DOM and stamp every\n // non-invertible node (custom blocks, unknown nodes, images, embeds)\n // with its exact JSON: they stay rendered *inside* the live editor as\n // non-editable atoms and survive the session byte-exact. A pairing\n // mismatch means the DOM diverged from the deterministic render —\n // refuse to start rather than risk lossy parsing (\"polished or absent\").\n const pairs = collectPreservedPairs(\n { type: 'doc', content: (target.value.content ?? []) as RichTextNode[] },\n host as unknown as RtDomElement,\n )\n if (!pairs) return\n const session = createInlineRichTextSession({\n nodeId,\n propName: target.propName,\n original: target.value,\n })\n for (const pair of pairs) {\n const el = pair.element as unknown as HTMLElement\n el.setAttribute(PRESERVE_NODE_ATTR, JSON.stringify(pair.node))\n el.setAttribute('contenteditable', 'false')\n }\n let done = false\n const finish = (mode: 'commit' | 'cancel') => {\n if (done) return\n done = true\n deps.setEditing(null)\n deps.setRichEditHost(null)\n // Parse the edited DOM, detach the session, then bump the node's\n // remount epoch: React discards the editor-mutated DOM and renders\n // fresh from the graph — committed content or, on cancel, the\n // untouched original. Never patch a fiber tree against DOM the\n // editing commands rewrote.\n const parsed = mode === 'commit' ? richTextDocFromDom(host as unknown as RtDomElement) : null\n host.removeEventListener('keydown', onKeyDown)\n host.removeEventListener('blur', onBlur)\n host.removeAttribute('contenteditable')\n host.removeAttribute('data-ec-edit')\n wrapper.removeAttribute('data-ec-editing')\n deps.bumpRichEditEpoch(nodeId)\n if (parsed) {\n // An unchanged document commits nothing: the stored value is never\n // rewritten, so untouched content round-trips byte-identical.\n const outcome = session.commit(parsed.doc)\n if (outcome) {\n dispatch({\n type: 'setProp',\n id: outcome.nodeId,\n name: outcome.propName,\n value: staticProp(outcome.value as unknown as JsonValue),\n })\n }\n } else {\n session.cancel()\n }\n doc.getSelection()?.removeAllRanges()\n }\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault()\n e.stopPropagation()\n finish('cancel')\n } else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n e.stopPropagation()\n finish('commit')\n }\n // Plain Enter falls through: native paragraph insertion (multiline).\n }\n const onBlur = (e: FocusEvent) => {\n // Interacting with the floating toolbar is part of the session.\n const next = e.relatedTarget as HTMLElement | null\n if (next?.closest?.('[data-ec-rt-toolbar]')) return\n finish('commit')\n }\n host.setAttribute('contenteditable', 'true')\n host.setAttribute('data-ec-edit', 'rich')\n wrapper.setAttribute('data-ec-editing', '')\n host.addEventListener('keydown', onKeyDown)\n host.addEventListener('blur', onBlur)\n deps.setEditing({ nodeId, host, finish })\n deps.setRichEditHost(host)\n host.focus({ preventScroll: true })\n }\n\n const onDblClick = (e: Event) => {\n const wrapper = wrapperAt(e.target)\n if (!wrapper) return\n const nodeId = wrapper.getAttribute('data-ec-node-id')!\n const editing = deps.getEditing()\n if (editing) {\n // Double-click inside the live editor is native word selection.\n if (editing.nodeId === nodeId) return\n editing.finish('commit')\n }\n const node = getNode(deps.currentRoot(), nodeId)\n if (!node) return\n const manifest = deps.getManifest(node.componentId)\n const target = resolveInlineEditableProp(manifest, node)\n if (target) {\n const host = findInlineTextHost(wrapper, target.value) as HTMLElement | null\n if (!host || typeof host.focus !== 'function') return\n dispatch({ type: 'select', id: nodeId })\n beginText(\n wrapper,\n host,\n createInlineEditSession({ nodeId, propName: target.propName, original: target.value }),\n )\n return\n }\n const richTarget = resolveInlineRichTextProp(manifest, node)\n if (!richTarget) return\n dispatch({ type: 'select', id: nodeId })\n beginRichText(wrapper, nodeId, richTarget)\n }\n\n doc.addEventListener('dblclick', onDblClick, true)\n return () => {\n doc.removeEventListener('dblclick', onDblClick, true)\n // Teardown mid-session (doc swap/unmount): cancel — never dispatch from cleanup.\n deps.getEditing()?.finish('cancel')\n }\n}\n\n/** What `useCanvasInlineEditing` hands back to the canvas. */\nexport interface CanvasInlineEditing {\n /**\n * The active session — imperative DOM state inside the iframe, not render\n * state. Other canvas listeners (selection clicks, DnD arming, clipboard\n * blocking) consult it live through this ref.\n */\n editingRef: RefObject<ActiveInlineEdit | null>\n /** The live rich-text host, mirrored into render state (mark toolbar mount). */\n richEditHost: HTMLElement | null\n /** Per-node remount epochs — decorate keys change when a rich session ends. */\n richEditEpochs: Record<string, number>\n /**\n * Reactive mirror of `editingRef` for renderers that must react to the\n * edit state (EC-404: the selection overlay suppresses its frame + chrome\n * while a text/rich-text session owns the node, so the `[data-ec-editing]`\n * outline alone marks it).\n */\n editing: boolean\n}\n\n/**\n * The React face of the canvas inline editing (EC-158/EC-159): owns the\n * session ref and the render state (`richEditHost`, `richEditEpochs`), wires\n * the dblclick listener when the iframe document exists, and cancels a live\n * session on teardown.\n */\nexport function useCanvasInlineEditing(\n doc: Document | null,\n deps: {\n /** The live composition root (`builder.present`, mirrored into a ref). */\n presentRef: RefObject<ComponentNode>\n dispatch: (action: BuilderAction) => void\n studio: StudioRegistry\n },\n): CanvasInlineEditing {\n const editingRef = useRef<ActiveInlineEdit | null>(null)\n const [editing, setEditing] = useState(false)\n const [richEditHost, setRichEditHost] = useState<HTMLElement | null>(null)\n const [richEditEpochs, setRichEditEpochs] = useState<Record<string, number>>({})\n const { presentRef, dispatch, studio } = deps\n\n useEffect(() => {\n if (!doc) return\n return attachCanvasInlineEditingListeners(doc, {\n currentRoot: () => presentRef.current,\n getManifest: (id) => studio.registry.getManifest(id),\n dispatch,\n getEditing: () => editingRef.current,\n setEditing: (edit) => {\n editingRef.current = edit\n setEditing(edit !== null)\n },\n setRichEditHost,\n bumpRichEditEpoch: (nodeId) =>\n setRichEditEpochs((prev) => ({ ...prev, [nodeId]: (prev[nodeId] ?? 0) + 1 })),\n })\n }, [doc, dispatch, studio, presentRef])\n\n return { editingRef, richEditHost, richEditEpochs, editing }\n}\n"]}
1
+ {"version":3,"file":"canvas-inline-editing.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-inline-editing.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState } from 'react'\nimport type { RefObject } from 'react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { getNode, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport {\n PRESERVE_NODE_ATTR,\n collectPreservedPairs,\n richTextDocFromDom,\n} from '@elytracms/core/rich-text'\nimport type { RtDomElement, RichTextNode } from '@elytracms/core/rich-text'\nimport type { StudioRegistry } from '../../lib/registry'\nimport type { BuilderAction } from './builder-store'\nimport {\n createInlineEditSession,\n createInlineRichTextSession,\n findInlineTextHost,\n resolveInlineEditableProp,\n resolveInlineRichTextProp,\n} from './inline-edit'\nimport type { InlineEditSession, InlineRichTextProp } from './inline-edit'\nimport { wrapperAt } from './canvas-dom'\n\n/**\n * Inline editing on the canvas (EC-158 plain text, EC-159 rich text),\n * extracted from `BuilderCanvas` (EC-330): double-click on a node whose\n * manifest flags an inline-editable prop turns the *rendered element itself*\n * contenteditable — no overlay, no input swap, so typography and layout are\n * identical in edit and render states. A session finishes exactly once, and a\n * commit dispatches exactly one `setProp` action — one undo step. The graph\n * stays the single source of truth.\n *\n * Commit/cancel gestures: plain text commits on Enter or blur; rich text is\n * multiline, so plain Enter inserts a paragraph and the commit gesture is\n * Cmd/Ctrl+Enter or blur (Enter-to-commit would make multi-paragraph content\n * unauthorable in place). Esc always cancels, dispatching nothing.\n *\n * The listener wiring is framework-free\n * ({@link attachCanvasInlineEditingListeners}, the `clipboard-actions`\n * pattern) so the begin/commit/cancel state machine is unit-testable without\n * a DOM environment; {@link useCanvasInlineEditing} owns the React state the\n * canvas renders from (the live rich-text host, the remount epochs) and the\n * session ref other canvas listeners consult.\n */\n\n/** A live inline-editing session bound to its DOM host (EC-158/EC-159). */\nexport interface ActiveInlineEdit {\n nodeId: string\n host: HTMLElement\n /** Finish the session exactly once (later calls are no-ops). */\n finish: (mode: 'commit' | 'cancel') => void\n}\n\n/** The structural slice of the iframe document the editing wiring needs. */\nexport interface CanvasInlineEditingDocument {\n addEventListener(type: string, listener: (e: Event) => void, capture?: boolean): void\n removeEventListener(type: string, listener: (e: Event) => void, capture?: boolean): void\n getSelection(): Selection | null\n createRange(): Range\n}\n\nexport interface CanvasInlineEditingDeps {\n /** The current composition root (read live — listeners outlive renders). */\n currentRoot(): ComponentNode\n getManifest(componentId: string): ComponentManifest | undefined\n dispatch(action: BuilderAction): void\n /** The live session — imperative DOM state, mirrored for other listeners. */\n getEditing(): ActiveInlineEdit | null\n setEditing(edit: ActiveInlineEdit | null): void\n /** Mirror the rich-text host into render state (mounts the mark toolbar). */\n setRichEditHost(host: HTMLElement | null): void\n /**\n * A rich-text session mutates the rendered DOM behind React's back\n * (contenteditable + mark commands), so finishing one must REMOUNT the\n * node's subtree — bumping its epoch changes the decorate key, React\n * discards the edited DOM wholesale and re-renders from the graph (the\n * committed value, or the untouched original on cancel). Patching the old\n * fiber tree against editor-mutated DOM would leave stale artifacts.\n */\n bumpRichEditEpoch(nodeId: string): void\n}\n\n/**\n * The rendered `.rich-text` element inside a node's wrapper — the rich-text\n * editing host (EC-159). Never crosses into a nested node's wrapper.\n */\nfunction findRichTextHost(wrapper: HTMLElement): HTMLElement | null {\n const host = wrapper.querySelector('.rich-text') as HTMLElement | null\n return host && host.closest('[data-ec-node-id]') === wrapper ? host : null\n}\n\n/**\n * Wire the inline-editing dblclick listener onto the iframe document (native —\n * React's synthetic events don't cross frames). Returns the detach function;\n * detaching mid-session cancels it (never dispatch from cleanup).\n */\nexport function attachCanvasInlineEditingListeners(\n doc: CanvasInlineEditingDocument,\n deps: CanvasInlineEditingDeps,\n): () => void {\n const { dispatch } = deps\n\n // ---- plain text (EC-158) ------------------------------------------------\n const beginText = (wrapper: HTMLElement, host: HTMLElement, session: InlineEditSession) => {\n let done = false\n const finish = (mode: 'commit' | 'cancel') => {\n if (done) return\n done = true\n deps.setEditing(null)\n const text = host.textContent ?? ''\n host.removeEventListener('keydown', onKeyDown)\n host.removeEventListener('blur', onBlur)\n host.removeAttribute('contenteditable')\n host.removeAttribute('data-ec-edit')\n wrapper.removeAttribute('data-ec-editing')\n if (mode === 'commit') {\n const outcome = session.commit(text)\n if (outcome) {\n dispatch({\n type: 'setProp',\n id: outcome.nodeId,\n name: outcome.propName,\n value: staticProp(outcome.value),\n })\n }\n } else {\n const outcome = session.cancel()\n if (outcome) host.textContent = outcome.value\n }\n doc.getSelection()?.removeAllRanges()\n }\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n e.stopPropagation()\n finish('commit')\n } else if (e.key === 'Escape') {\n e.preventDefault()\n e.stopPropagation()\n finish('cancel')\n }\n }\n const onBlur = () => finish('commit')\n // `plaintext-only` keeps paste/typing plain; fall back where unsupported.\n host.setAttribute('contenteditable', 'plaintext-only')\n if (!host.isContentEditable) host.setAttribute('contenteditable', 'true')\n host.setAttribute('data-ec-edit', 'text')\n wrapper.setAttribute('data-ec-editing', '')\n host.addEventListener('keydown', onKeyDown)\n host.addEventListener('blur', onBlur)\n deps.setEditing({ nodeId: session.nodeId, host, finish })\n host.focus({ preventScroll: true })\n // Keep the gesture's native caret/word selection when it landed inside\n // the host; otherwise select the whole value.\n const sel = doc.getSelection()\n if (!sel || sel.rangeCount === 0 || !host.contains(sel.anchorNode)) {\n const range = doc.createRange()\n range.selectNodeContents(host)\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n\n // ---- rich text (EC-159) -------------------------------------------------\n const beginRichText = (wrapper: HTMLElement, nodeId: string, target: InlineRichTextProp) => {\n const host = findRichTextHost(wrapper)\n if (!host) return\n // Pair the storage tree against the rendered DOM and stamp every\n // non-invertible node (custom blocks, unknown nodes, images, embeds)\n // with its exact JSON: they stay rendered *inside* the live editor as\n // non-editable atoms and survive the session byte-exact. A pairing\n // mismatch means the DOM diverged from the deterministic render —\n // refuse to start rather than risk lossy parsing (\"polished or absent\").\n const pairs = collectPreservedPairs(\n { type: 'doc', content: (target.value.content ?? []) as RichTextNode[] },\n host as unknown as RtDomElement,\n )\n if (!pairs) return\n const session = createInlineRichTextSession({\n nodeId,\n propName: target.propName,\n original: target.value,\n })\n for (const pair of pairs) {\n const el = pair.element as unknown as HTMLElement\n el.setAttribute(PRESERVE_NODE_ATTR, JSON.stringify(pair.node))\n el.setAttribute('contenteditable', 'false')\n }\n let done = false\n const finish = (mode: 'commit' | 'cancel') => {\n if (done) return\n done = true\n deps.setEditing(null)\n deps.setRichEditHost(null)\n // Parse the edited DOM, detach the session, then bump the node's\n // remount epoch: React discards the editor-mutated DOM and renders\n // fresh from the graph — committed content or, on cancel, the\n // untouched original. Never patch a fiber tree against DOM the\n // editing commands rewrote.\n const parsed = mode === 'commit' ? richTextDocFromDom(host as unknown as RtDomElement) : null\n host.removeEventListener('keydown', onKeyDown)\n host.removeEventListener('blur', onBlur)\n host.removeAttribute('contenteditable')\n host.removeAttribute('data-ec-edit')\n wrapper.removeAttribute('data-ec-editing')\n deps.bumpRichEditEpoch(nodeId)\n if (parsed) {\n // An unchanged document commits nothing: the stored value is never\n // rewritten, so untouched content round-trips byte-identical.\n const outcome = session.commit(parsed.doc)\n if (outcome) {\n dispatch({\n type: 'setProp',\n id: outcome.nodeId,\n name: outcome.propName,\n value: staticProp(outcome.value as unknown as JsonValue),\n })\n }\n } else {\n session.cancel()\n }\n doc.getSelection()?.removeAllRanges()\n }\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault()\n e.stopPropagation()\n finish('cancel')\n } else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n e.stopPropagation()\n finish('commit')\n }\n // Plain Enter falls through: native paragraph insertion (multiline).\n }\n const onBlur = (e: FocusEvent) => {\n // Interacting with the floating toolbar is part of the session.\n const next = e.relatedTarget as HTMLElement | null\n if (next?.closest?.('[data-ec-rt-toolbar]')) return\n finish('commit')\n }\n host.setAttribute('contenteditable', 'true')\n host.setAttribute('data-ec-edit', 'rich')\n wrapper.setAttribute('data-ec-editing', '')\n host.addEventListener('keydown', onKeyDown)\n host.addEventListener('blur', onBlur)\n deps.setEditing({ nodeId, host, finish })\n deps.setRichEditHost(host)\n host.focus({ preventScroll: true })\n }\n\n const onDblClick = (e: Event) => {\n const wrapper = wrapperAt(e.target)\n if (!wrapper) return\n const nodeId = wrapper.getAttribute('data-ec-node-id')!\n const editing = deps.getEditing()\n if (editing) {\n // Double-click inside the live editor is native word selection.\n if (editing.nodeId === nodeId) return\n editing.finish('commit')\n }\n const node = getNode(deps.currentRoot(), nodeId)\n if (!node) return\n const manifest = deps.getManifest(node.componentId)\n const target = resolveInlineEditableProp(manifest, node)\n if (target) {\n const host = findInlineTextHost(wrapper, target.value) as HTMLElement | null\n if (!host || typeof host.focus !== 'function') return\n dispatch({ type: 'select', id: nodeId })\n beginText(\n wrapper,\n host,\n createInlineEditSession({ nodeId, propName: target.propName, original: target.value }),\n )\n return\n }\n const richTarget = resolveInlineRichTextProp(manifest, node)\n if (!richTarget) return\n dispatch({ type: 'select', id: nodeId })\n beginRichText(wrapper, nodeId, richTarget)\n }\n\n doc.addEventListener('dblclick', onDblClick, true)\n return () => {\n doc.removeEventListener('dblclick', onDblClick, true)\n // Teardown mid-session (doc swap/unmount): cancel — never dispatch from cleanup.\n deps.getEditing()?.finish('cancel')\n }\n}\n\n/** What `useCanvasInlineEditing` hands back to the canvas. */\nexport interface CanvasInlineEditing {\n /**\n * The active session — imperative DOM state inside the iframe, not render\n * state. Other canvas listeners (selection clicks, DnD arming, clipboard\n * blocking) consult it live through this ref.\n */\n editingRef: RefObject<ActiveInlineEdit | null>\n /** The live rich-text host, mirrored into render state (mark toolbar mount). */\n richEditHost: HTMLElement | null\n /** Per-node remount epochs — decorate keys change when a rich session ends. */\n richEditEpochs: Record<string, number>\n /**\n * Reactive mirror of `editingRef` for renderers that must react to the\n * edit state (EC-404: the selection overlay suppresses its frame + chrome\n * while a text/rich-text session owns the node, so the `[data-ec-editing]`\n * outline alone marks it).\n */\n editing: boolean\n}\n\n/**\n * The React face of the canvas inline editing (EC-158/EC-159): owns the\n * session ref and the render state (`richEditHost`, `richEditEpochs`), wires\n * the dblclick listener when the iframe document exists, and cancels a live\n * session on teardown.\n */\nexport function useCanvasInlineEditing(\n doc: Document | null,\n deps: {\n /** The live composition root (`builder.present`, mirrored into a ref). */\n presentRef: RefObject<ComponentNode>\n dispatch: (action: BuilderAction) => void\n studio: StudioRegistry\n },\n): CanvasInlineEditing {\n const editingRef = useRef<ActiveInlineEdit | null>(null)\n const [editing, setEditing] = useState(false)\n const [richEditHost, setRichEditHost] = useState<HTMLElement | null>(null)\n const [richEditEpochs, setRichEditEpochs] = useState<Record<string, number>>({})\n const { presentRef, dispatch, studio } = deps\n\n useEffect(() => {\n if (!doc) return\n return attachCanvasInlineEditingListeners(doc, {\n currentRoot: () => presentRef.current,\n getManifest: (id) => studio.registry.getManifest(id),\n dispatch,\n getEditing: () => editingRef.current,\n setEditing: (edit) => {\n editingRef.current = edit\n setEditing(edit !== null)\n },\n setRichEditHost,\n bumpRichEditEpoch: (nodeId) =>\n setRichEditEpochs((prev) => ({ ...prev, [nodeId]: (prev[nodeId] ?? 0) + 1 })),\n })\n }, [doc, dispatch, studio, presentRef])\n\n return { editingRef, richEditHost, richEditEpochs, editing }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { ComponentNode } from '@elytracms/core/project-graph';
2
- import type { FlowAxis } from '@elytracms/studio/lib/dnd/drop-rules';
2
+ import type { FlowAxis } from '../../lib/dnd/drop-rules.js';
3
3
  import type { BuilderAction } from './builder-store.js';
4
4
  import type { Rect } from './canvas-rect-tracking.js';
5
5
  import type { InsertPosition } from './insert-context.js';
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-overlay-geometry.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-overlay-geometry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getParentInfo } from '@elytracms/core/project-graph'\nimport type { FlowAxis } from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { BuilderAction } from './builder-store'\nimport type { Rect } from './canvas-rect-tracking'\nimport type { InsertPosition } from './insert-context'\n\n/**\n * Pure geometry + action-mapping for the canvas selection overlay (EC-404) —\n * everything the overlay decides that does NOT touch the DOM or React, extracted\n * so it is unit-testable in the node env: where the frame's chrome bar sits, and\n * which tree actions its controls dispatch.\n */\n\n/** Height reserved for the frame's chrome bar (the label + control cluster). */\nexport const FRAME_BAR_HEIGHT = 24\n\n/** Placement of the frame chrome bar in document coordinates. */\nexport interface FrameBarPlacement {\n top: number\n left: number\n /** `above` the frame's top edge, or `inside` its top when there's no room. */\n placement: 'above' | 'inside'\n}\n\n/**\n * Place the frame's chrome bar just above the frame's top edge, flipping to\n * sit INSIDE the top when the edge is within a bar-height of the visible top\n * (`docTop` = the current scroll offset, so the bar never floats above the\n * viewport). Left-aligned to the frame.\n */\nexport function placeFrameBar(rect: Rect, opts: { barHeight: number; docTop: number }): FrameBarPlacement {\n const above = rect.top - opts.barHeight\n if (above >= opts.docTop) return { top: above, left: rect.left, placement: 'above' }\n return { top: rect.top, left: rect.left, placement: 'inside' }\n}\n\n/** Which frame controls are available for a node (root has none; ends can't move further). */\nexport interface FrameControls {\n /** The composition root — no move/duplicate/delete affordances (mirrors the tree). */\n isRoot: boolean\n canMoveUp: boolean\n canMoveDown: boolean\n}\n\n/**\n * Resolve a node's frame controls from the graph: the root gets none; a child's\n * move affordances follow its position among its slot siblings — the identical\n * enablement the page-tree row menu computes from `isFirst`/`isLast`.\n */\nexport function frameControlsFor(root: ComponentNode, nodeId: string): FrameControls {\n const info = getParentInfo(root, nodeId)\n if (!info) return { isRoot: true, canMoveUp: false, canMoveDown: false }\n const siblings = info.parent.slots?.[info.slot] ?? []\n return {\n isRoot: false,\n canMoveUp: info.index > 0,\n canMoveDown: info.index < siblings.length - 1,\n }\n}\n\n/** The four frame controls, in display order. */\nexport type FrameControlKind = 'moveUp' | 'moveDown' | 'duplicate' | 'delete'\n\n/**\n * Map a frame control to the EXISTING builder tree action it dispatches — the\n * same `reorder`/`duplicate`/`remove` the page-tree \"•••\" menu fires (no new\n * mutation path). One control, one dispatch, one undo step.\n */\nexport function frameControlAction(kind: FrameControlKind, nodeId: string): BuilderAction {\n switch (kind) {\n case 'moveUp':\n return { type: 'reorder', id: nodeId, delta: -1 }\n case 'moveDown':\n return { type: 'reorder', id: nodeId, delta: 1 }\n case 'duplicate':\n return { type: 'duplicate', id: nodeId }\n case 'delete':\n return { type: 'remove', id: nodeId }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Insertion affordances (EC-405)\n// ---------------------------------------------------------------------------\n\n/** The anchor point (document coords) a \"+\" affordance button centers on. */\nexport interface AffordanceAnchor {\n left: number\n top: number\n}\n\n/**\n * Place a \"+\" insertion affordance for `position` on a node's measured rect\n * (EC-405). The returned point is the button's CENTER (the chrome CSS centers the\n * button on it with a translate), so it sits ON the boundary:\n *\n * - `before`/`after` ride the node's leading/trailing edge along the parent's\n * flow axis — top/bottom mid-points for a vertical flow, left/right mid-points\n * for a horizontal one (so the \"+\" always straddles the gap a drop `before`/\n * `after` would fill).\n * - `into` sits at the node's center — the affordance for dropping the first\n * child into an empty container.\n *\n * Pure over the rect + flow; no DOM, no clamping (the 16px body gutter keeps\n * edge affordances on-canvas).\n */\nexport function placeAffordance(rect: Rect, position: InsertPosition, flow: FlowAxis): AffordanceAnchor {\n const midX = rect.left + rect.width / 2\n const midY = rect.top + rect.height / 2\n if (position === 'into') return { left: midX, top: midY }\n if (flow === 'horizontal') {\n return { left: position === 'before' ? rect.left : rect.left + rect.width, top: midY }\n }\n return { left: midX, top: position === 'before' ? rect.top : rect.top + rect.height }\n}\n"]}
1
+ {"version":3,"file":"canvas-overlay-geometry.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-overlay-geometry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getParentInfo } from '@elytracms/core/project-graph'\nimport type { FlowAxis } from '../../lib/dnd/drop-rules'\nimport type { BuilderAction } from './builder-store'\nimport type { Rect } from './canvas-rect-tracking'\nimport type { InsertPosition } from './insert-context'\n\n/**\n * Pure geometry + action-mapping for the canvas selection overlay (EC-404) —\n * everything the overlay decides that does NOT touch the DOM or React, extracted\n * so it is unit-testable in the node env: where the frame's chrome bar sits, and\n * which tree actions its controls dispatch.\n */\n\n/** Height reserved for the frame's chrome bar (the label + control cluster). */\nexport const FRAME_BAR_HEIGHT = 24\n\n/** Placement of the frame chrome bar in document coordinates. */\nexport interface FrameBarPlacement {\n top: number\n left: number\n /** `above` the frame's top edge, or `inside` its top when there's no room. */\n placement: 'above' | 'inside'\n}\n\n/**\n * Place the frame's chrome bar just above the frame's top edge, flipping to\n * sit INSIDE the top when the edge is within a bar-height of the visible top\n * (`docTop` = the current scroll offset, so the bar never floats above the\n * viewport). Left-aligned to the frame.\n */\nexport function placeFrameBar(rect: Rect, opts: { barHeight: number; docTop: number }): FrameBarPlacement {\n const above = rect.top - opts.barHeight\n if (above >= opts.docTop) return { top: above, left: rect.left, placement: 'above' }\n return { top: rect.top, left: rect.left, placement: 'inside' }\n}\n\n/** Which frame controls are available for a node (root has none; ends can't move further). */\nexport interface FrameControls {\n /** The composition root — no move/duplicate/delete affordances (mirrors the tree). */\n isRoot: boolean\n canMoveUp: boolean\n canMoveDown: boolean\n}\n\n/**\n * Resolve a node's frame controls from the graph: the root gets none; a child's\n * move affordances follow its position among its slot siblings — the identical\n * enablement the page-tree row menu computes from `isFirst`/`isLast`.\n */\nexport function frameControlsFor(root: ComponentNode, nodeId: string): FrameControls {\n const info = getParentInfo(root, nodeId)\n if (!info) return { isRoot: true, canMoveUp: false, canMoveDown: false }\n const siblings = info.parent.slots?.[info.slot] ?? []\n return {\n isRoot: false,\n canMoveUp: info.index > 0,\n canMoveDown: info.index < siblings.length - 1,\n }\n}\n\n/** The four frame controls, in display order. */\nexport type FrameControlKind = 'moveUp' | 'moveDown' | 'duplicate' | 'delete'\n\n/**\n * Map a frame control to the EXISTING builder tree action it dispatches — the\n * same `reorder`/`duplicate`/`remove` the page-tree \"•••\" menu fires (no new\n * mutation path). One control, one dispatch, one undo step.\n */\nexport function frameControlAction(kind: FrameControlKind, nodeId: string): BuilderAction {\n switch (kind) {\n case 'moveUp':\n return { type: 'reorder', id: nodeId, delta: -1 }\n case 'moveDown':\n return { type: 'reorder', id: nodeId, delta: 1 }\n case 'duplicate':\n return { type: 'duplicate', id: nodeId }\n case 'delete':\n return { type: 'remove', id: nodeId }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Insertion affordances (EC-405)\n// ---------------------------------------------------------------------------\n\n/** The anchor point (document coords) a \"+\" affordance button centers on. */\nexport interface AffordanceAnchor {\n left: number\n top: number\n}\n\n/**\n * Place a \"+\" insertion affordance for `position` on a node's measured rect\n * (EC-405). The returned point is the button's CENTER (the chrome CSS centers the\n * button on it with a translate), so it sits ON the boundary:\n *\n * - `before`/`after` ride the node's leading/trailing edge along the parent's\n * flow axis — top/bottom mid-points for a vertical flow, left/right mid-points\n * for a horizontal one (so the \"+\" always straddles the gap a drop `before`/\n * `after` would fill).\n * - `into` sits at the node's center — the affordance for dropping the first\n * child into an empty container.\n *\n * Pure over the rect + flow; no DOM, no clamping (the 16px body gutter keeps\n * edge affordances on-canvas).\n */\nexport function placeAffordance(rect: Rect, position: InsertPosition, flow: FlowAxis): AffordanceAnchor {\n const midX = rect.left + rect.width / 2\n const midY = rect.top + rect.height / 2\n if (position === 'into') return { left: midX, top: midY }\n if (flow === 'horizontal') {\n return { left: position === 'before' ? rect.left : rect.left + rect.width, top: midY }\n }\n return { left: midX, top: position === 'before' ? rect.top : rect.top + rect.height }\n}\n"]}
@@ -3,7 +3,7 @@ import type { AssetRecord } from '@elytracms/core/persistence';
3
3
  import type { ListingQuery, RenderAsset, RenderContext, ResolveAsset, ResolveLink, ResolveListing, ResolveRelation } from '@elytracms/core/runtime-renderer';
4
4
  import type { ContentQueryError } from '@elytracms/core/content';
5
5
  import type { ComponentNode } from '@elytracms/core/project-graph';
6
- import type { StudioRegistry } from '@elytracms/studio/lib/registry';
6
+ import type { StudioRegistry } from '../../lib/registry/index.js';
7
7
  import type { ReuseCapability } from './reuse.js';
8
8
  /**
9
9
  * The canvas's `RenderContext` (EC-330, extracted from `BuilderCanvas`): the
@@ -3,10 +3,10 @@ import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react';
3
3
  import { DRAFT_PERSPECTIVE } from '@elytracms/core/cms-core';
4
4
  import { resolvedAsset } from '@elytracms/core/content';
5
5
  import { createRenderContext, listingFailureOf } from '@elytracms/core/runtime-renderer';
6
- import { translate } from '@elytracms/studio/lib/i18n';
7
- import { contentQueryErrorMessage } from '@elytracms/studio/lib/persona/labels';
8
- import { acquireContentSessionConsumer, contentSessionEpoch, resolveCanvasLink, resolveCanvasListing, resolveCanvasRelation, subscribeContentSession, validateCanvasListingQuery, } from '@elytracms/studio/lib/data';
9
- import { useStudioLocale } from '@elytracms/studio/lib/locale';
6
+ import { translate } from '../../lib/i18n/index.js';
7
+ import { contentQueryErrorMessage } from '../../lib/persona/labels.js';
8
+ import { acquireContentSessionConsumer, contentSessionEpoch, resolveCanvasLink, resolveCanvasListing, resolveCanvasRelation, subscribeContentSession, validateCanvasListingQuery, } from '../../lib/data/index.js';
9
+ import { useStudioLocale } from '../../lib/locale/index.js';
10
10
  import { useOptionalAssets } from '../assets/assets-store.js';
11
11
  import { useReuse } from './reuse-context.js';
12
12
  import { reuseEmbedRef } from './reuse.js';