@elytracms/studio 0.0.92 → 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 (599) hide show
  1. package/convex/analytics-cost.test.ts +817 -0
  2. package/convex/asset-alt-migration.test.ts +127 -0
  3. package/convex/asset-replace.test.ts +168 -0
  4. package/convex/asset-revalidate.test.ts +6 -6
  5. package/convex/assets.ts +41 -5
  6. package/convex/auth.ts +5 -2
  7. package/convex/authority.ts +35 -11
  8. package/convex/cms.ts +25 -11
  9. package/convex/contract-parity.test.ts +25 -3
  10. package/convex/delivery.ts +12 -9
  11. package/convex/document-revalidate.test.ts +10 -12
  12. package/convex/guard.ts +9 -7
  13. package/convex/http-routes.test.ts +18 -8
  14. package/convex/http.ts +23 -17
  15. package/convex/ingest.test.ts +326 -0
  16. package/convex/ingest.ts +281 -0
  17. package/convex/invites.ts +13 -5
  18. package/convex/members.ts +10 -4
  19. package/convex/orphan-hydration.test.ts +116 -0
  20. package/convex/rail-write-parity.test.ts +11 -8
  21. package/convex/record-functions.ts +325 -0
  22. package/convex/records.test.ts +156 -0
  23. package/convex/redirect-migration.test.ts +205 -0
  24. package/convex/redirect-revalidate.test.ts +5 -1
  25. package/convex/redirects.ts +120 -79
  26. package/convex/role-policy.test.ts +315 -0
  27. package/convex/roles.ts +83 -0
  28. package/convex/schema.ts +36 -0
  29. package/convex/triggers.ts +21 -1
  30. package/dist/addon.d.ts +71 -0
  31. package/dist/addon.js +67 -0
  32. package/dist/addon.js.map +1 -0
  33. package/dist/components/account/account-settings.js +2 -2
  34. package/dist/components/account/account-settings.js.map +1 -1
  35. package/dist/components/account/credential-panel.js +2 -2
  36. package/dist/components/account/credential-panel.js.map +1 -1
  37. package/dist/components/account/self-account-schema.d.ts +6 -15
  38. package/dist/components/account/self-account-schema.js +52 -34
  39. package/dist/components/account/self-account-schema.js.map +1 -1
  40. package/dist/components/account/self-account-view-source.d.ts +1 -1
  41. package/dist/components/account/self-account-view-source.js +6 -4
  42. package/dist/components/account/self-account-view-source.js.map +1 -1
  43. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  44. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  45. package/dist/components/ai/ai-chat-panel.js +6 -6
  46. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  47. package/dist/components/ai/ai-cms-panel.js +2 -2
  48. package/dist/components/ai/ai-cms-panel.js.map +1 -1
  49. package/dist/components/ai/ai-panel-toggle.js +4 -4
  50. package/dist/components/ai/ai-panel-toggle.js.map +1 -1
  51. package/dist/components/ai/chat-ui.js +2 -2
  52. package/dist/components/ai/chat-ui.js.map +1 -1
  53. package/dist/components/ai/proposal-card.d.ts +2 -2
  54. package/dist/components/ai/proposal-card.js +4 -3
  55. package/dist/components/ai/proposal-card.js.map +1 -1
  56. package/dist/components/ai/proposal-issues.js +2 -2
  57. package/dist/components/ai/proposal-issues.js.map +1 -1
  58. package/dist/components/ai/translate-document-action.js +2 -2
  59. package/dist/components/ai/translate-document-action.js.map +1 -1
  60. package/dist/components/ai/translate-document-dialog.js +10 -8
  61. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  62. package/dist/components/ai/translate-skipped.d.ts +19 -0
  63. package/dist/components/ai/translate-skipped.js +46 -0
  64. package/dist/components/ai/translate-skipped.js.map +1 -0
  65. package/dist/components/app-views/default-modules.d.ts +50 -2
  66. package/dist/components/app-views/default-modules.js +65 -20
  67. package/dist/components/app-views/default-modules.js.map +1 -1
  68. package/dist/components/app-views/module.d.ts +53 -3
  69. package/dist/components/app-views/module.js +14 -7
  70. package/dist/components/app-views/module.js.map +1 -1
  71. package/dist/components/app-views/sidebar.js +1 -1
  72. package/dist/components/app-views/sidebar.js.map +1 -1
  73. package/dist/components/app-views/studio-config.d.ts +11 -2
  74. package/dist/components/app-views/studio-config.js.map +1 -1
  75. package/dist/components/assets/asset-detail.js +20 -10
  76. package/dist/components/assets/asset-detail.js.map +1 -1
  77. package/dist/components/assets/asset-grid.js +2 -2
  78. package/dist/components/assets/asset-grid.js.map +1 -1
  79. package/dist/components/assets/asset-metadata.d.ts +15 -4
  80. package/dist/components/assets/asset-metadata.js +15 -2
  81. package/dist/components/assets/asset-metadata.js.map +1 -1
  82. package/dist/components/assets/asset-search.js +4 -1
  83. package/dist/components/assets/asset-search.js.map +1 -1
  84. package/dist/components/assets/asset-selection.d.ts +0 -8
  85. package/dist/components/assets/asset-selection.js +4 -23
  86. package/dist/components/assets/asset-selection.js.map +1 -1
  87. package/dist/components/assets/asset-thumb.js +6 -5
  88. package/dist/components/assets/asset-thumb.js.map +1 -1
  89. package/dist/components/assets/assets-collection-view-source.d.ts +5 -5
  90. package/dist/components/assets/assets-collection-view-source.js +9 -5
  91. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  92. package/dist/components/assets/assets-module.js +1 -1
  93. package/dist/components/assets/assets-module.js.map +1 -1
  94. package/dist/components/assets/assets-store.d.ts +16 -2
  95. package/dist/components/assets/assets-store.js +28 -6
  96. package/dist/components/assets/assets-store.js.map +1 -1
  97. package/dist/components/assets/assets-view-preference.js +1 -1
  98. package/dist/components/assets/assets-view-preference.js.map +1 -1
  99. package/dist/components/assets/replace-file.d.ts +22 -0
  100. package/dist/components/assets/replace-file.js +59 -0
  101. package/dist/components/assets/replace-file.js.map +1 -0
  102. package/dist/components/auth-context.d.ts +1 -1
  103. package/dist/components/auth-context.js +6 -6
  104. package/dist/components/auth-context.js.map +1 -1
  105. package/dist/components/builder/blocks-model.d.ts +15 -27
  106. package/dist/components/builder/blocks-model.js +38 -44
  107. package/dist/components/builder/blocks-model.js.map +1 -1
  108. package/dist/components/builder/blocks-panel.d.ts +8 -4
  109. package/dist/components/builder/blocks-panel.js +10 -9
  110. package/dist/components/builder/blocks-panel.js.map +1 -1
  111. package/dist/components/builder/builder-store.d.ts +104 -15
  112. package/dist/components/builder/builder-store.js +157 -32
  113. package/dist/components/builder/builder-store.js.map +1 -1
  114. package/dist/components/builder/canvas-body.d.ts +30 -0
  115. package/dist/components/builder/canvas-body.js +38 -0
  116. package/dist/components/builder/canvas-body.js.map +1 -0
  117. package/dist/components/builder/canvas-breakpoints.js +1 -1
  118. package/dist/components/builder/canvas-breakpoints.js.map +1 -1
  119. package/dist/components/builder/canvas-chrome-css.js +7 -0
  120. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  121. package/dist/components/builder/canvas-dnd.d.ts +9 -9
  122. package/dist/components/builder/canvas-dnd.js +6 -6
  123. package/dist/components/builder/canvas-dnd.js.map +1 -1
  124. package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
  125. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  126. package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
  127. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
  128. package/dist/components/builder/canvas-render-context.d.ts +28 -20
  129. package/dist/components/builder/canvas-render-context.js +46 -47
  130. package/dist/components/builder/canvas-render-context.js.map +1 -1
  131. package/dist/components/builder/canvas-runs.d.ts +45 -0
  132. package/dist/components/builder/canvas-runs.js +51 -0
  133. package/dist/components/builder/canvas-runs.js.map +1 -0
  134. package/dist/components/builder/canvas-theme-notice.js +2 -2
  135. package/dist/components/builder/canvas-theme-notice.js.map +1 -1
  136. package/dist/components/builder/canvas.js +35 -8
  137. package/dist/components/builder/canvas.js.map +1 -1
  138. package/dist/components/builder/clipboard-actions.d.ts +16 -1
  139. package/dist/components/builder/clipboard-actions.js +48 -11
  140. package/dist/components/builder/clipboard-actions.js.map +1 -1
  141. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  142. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  143. package/dist/components/builder/composition-field-editor.d.ts +4 -16
  144. package/dist/components/builder/composition-field-editor.js +32 -43
  145. package/dist/components/builder/composition-field-editor.js.map +1 -1
  146. package/dist/components/builder/delete-key.d.ts +5 -0
  147. package/dist/components/builder/delete-key.js +4 -0
  148. package/dist/components/builder/delete-key.js.map +1 -1
  149. package/dist/components/builder/drag-session.d.ts +1 -1
  150. package/dist/components/builder/drag-session.js.map +1 -1
  151. package/dist/components/builder/fixture-thumbnail.js +1 -1
  152. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  153. package/dist/components/builder/insert-context.d.ts +18 -26
  154. package/dist/components/builder/insert-context.js +47 -94
  155. package/dist/components/builder/insert-context.js.map +1 -1
  156. package/dist/components/builder/layout-inspector.js +1 -1
  157. package/dist/components/builder/layout-inspector.js.map +1 -1
  158. package/dist/components/builder/node-title.js +1 -1
  159. package/dist/components/builder/node-title.js.map +1 -1
  160. package/dist/components/builder/page-tree.d.ts +4 -4
  161. package/dist/components/builder/page-tree.js +8 -8
  162. package/dist/components/builder/page-tree.js.map +1 -1
  163. package/dist/components/builder/placement-refusal.d.ts +18 -0
  164. package/dist/components/builder/placement-refusal.js +46 -0
  165. package/dist/components/builder/placement-refusal.js.map +1 -0
  166. package/dist/components/builder/property-inspector.js +8 -4
  167. package/dist/components/builder/property-inspector.js.map +1 -1
  168. package/dist/components/builder/reuse-context.js +68 -30
  169. package/dist/components/builder/reuse-context.js.map +1 -1
  170. package/dist/components/builder/reuse.d.ts +79 -1
  171. package/dist/components/builder/reuse.js +70 -3
  172. package/dist/components/builder/reuse.js.map +1 -1
  173. package/dist/components/builder/rich-text-toolbar.js +1 -1
  174. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  175. package/dist/components/builder/run-inspector.d.ts +11 -0
  176. package/dist/components/builder/run-inspector.js +33 -0
  177. package/dist/components/builder/run-inspector.js.map +1 -0
  178. package/dist/components/builder/selection-overlay.js +3 -3
  179. package/dist/components/builder/selection-overlay.js.map +1 -1
  180. package/dist/components/builder/spacing-handles.d.ts +2 -2
  181. package/dist/components/builder/spacing-handles.js +3 -3
  182. package/dist/components/builder/spacing-handles.js.map +1 -1
  183. package/dist/components/cms/asset-field.d.ts +38 -1
  184. package/dist/components/cms/asset-field.js +184 -60
  185. package/dist/components/cms/asset-field.js.map +1 -1
  186. package/dist/components/cms/asset-picker-core.d.ts +16 -0
  187. package/dist/components/cms/asset-picker-core.js +20 -0
  188. package/dist/components/cms/asset-picker-core.js.map +1 -1
  189. package/dist/components/cms/asset-picker.js +14 -20
  190. package/dist/components/cms/asset-picker.js.map +1 -1
  191. package/dist/components/cms/bulk-actions.d.ts +4 -20
  192. package/dist/components/cms/bulk-actions.js +5 -26
  193. package/dist/components/cms/bulk-actions.js.map +1 -1
  194. package/dist/components/cms/bulk-bar.d.ts +2 -2
  195. package/dist/components/cms/bulk-bar.js +2 -2
  196. package/dist/components/cms/bulk-bar.js.map +1 -1
  197. package/dist/components/cms/cell-edit.d.ts +1 -1
  198. package/dist/components/cms/cell-edit.js +1 -1
  199. package/dist/components/cms/cell-edit.js.map +1 -1
  200. package/dist/components/cms/cell.d.ts +1 -1
  201. package/dist/components/cms/cell.js +3 -3
  202. package/dist/components/cms/cell.js.map +1 -1
  203. package/dist/components/cms/cms-index.js +2 -2
  204. package/dist/components/cms/cms-index.js.map +1 -1
  205. package/dist/components/cms/cms-labels.js +2 -2
  206. package/dist/components/cms/cms-labels.js.map +1 -1
  207. package/dist/components/cms/cms-store.d.ts +7 -6
  208. package/dist/components/cms/cms-store.js +23 -43
  209. package/dist/components/cms/cms-store.js.map +1 -1
  210. package/dist/components/cms/collection-table.js +7 -7
  211. package/dist/components/cms/collection-table.js.map +1 -1
  212. package/dist/components/cms/detail-editor.d.ts +5 -2
  213. package/dist/components/cms/detail-editor.js +20 -15
  214. package/dist/components/cms/detail-editor.js.map +1 -1
  215. package/dist/components/cms/details-pane.js +1 -1
  216. package/dist/components/cms/details-pane.js.map +1 -1
  217. package/dist/components/cms/doc-state.js +1 -1
  218. package/dist/components/cms/doc-state.js.map +1 -1
  219. package/dist/components/cms/document-diff-view.d.ts +3 -1
  220. package/dist/components/cms/document-diff-view.js +17 -12
  221. package/dist/components/cms/document-diff-view.js.map +1 -1
  222. package/dist/components/cms/document-editor-context.js +1 -1
  223. package/dist/components/cms/document-editor-context.js.map +1 -1
  224. package/dist/components/cms/document-staging.js +1 -1
  225. package/dist/components/cms/document-staging.js.map +1 -1
  226. package/dist/components/cms/document-url-path.js +2 -2
  227. package/dist/components/cms/document-url-path.js.map +1 -1
  228. package/dist/components/cms/duplicate-document.d.ts +1 -1
  229. package/dist/components/cms/duplicate-document.js.map +1 -1
  230. package/dist/components/cms/editable-cell.js +3 -3
  231. package/dist/components/cms/editable-cell.js.map +1 -1
  232. package/dist/components/cms/field-anchors.d.ts +7 -4
  233. package/dist/components/cms/field-anchors.js +9 -7
  234. package/dist/components/cms/field-anchors.js.map +1 -1
  235. package/dist/components/cms/field-collaboration.d.ts +1 -1
  236. package/dist/components/cms/field-collaboration.js +1 -1
  237. package/dist/components/cms/field-collaboration.js.map +1 -1
  238. package/dist/components/cms/field-connection.d.ts +1 -1
  239. package/dist/components/cms/field-connection.js +2 -2
  240. package/dist/components/cms/field-connection.js.map +1 -1
  241. package/dist/components/cms/field-control-lib.d.ts +15 -1
  242. package/dist/components/cms/field-control-lib.js +23 -4
  243. package/dist/components/cms/field-control-lib.js.map +1 -1
  244. package/dist/components/cms/field-control.js +4 -4
  245. package/dist/components/cms/field-control.js.map +1 -1
  246. package/dist/components/cms/field-renderer.d.ts +1 -1
  247. package/dist/components/cms/field-renderer.js +2 -2
  248. package/dist/components/cms/field-renderer.js.map +1 -1
  249. package/dist/components/cms/filter-builder.d.ts +1 -1
  250. package/dist/components/cms/filter-builder.js +3 -3
  251. package/dist/components/cms/filter-builder.js.map +1 -1
  252. package/dist/components/cms/gate-validation.d.ts +21 -30
  253. package/dist/components/cms/gate-validation.js +11 -32
  254. package/dist/components/cms/gate-validation.js.map +1 -1
  255. package/dist/components/cms/import-export.js +2 -2
  256. package/dist/components/cms/import-export.js.map +1 -1
  257. package/dist/components/cms/issue-copy.js +1 -1
  258. package/dist/components/cms/issue-copy.js.map +1 -1
  259. package/dist/components/cms/issues-pane.js +3 -3
  260. package/dist/components/cms/issues-pane.js.map +1 -1
  261. package/dist/components/cms/object-array-field.js +6 -6
  262. package/dist/components/cms/object-array-field.js.map +1 -1
  263. package/dist/components/cms/prop-fields.d.ts +7 -2
  264. package/dist/components/cms/prop-fields.js +14 -7
  265. package/dist/components/cms/prop-fields.js.map +1 -1
  266. package/dist/components/cms/reference-confirm.d.ts +1 -1
  267. package/dist/components/cms/reference-confirm.js +2 -2
  268. package/dist/components/cms/reference-confirm.js.map +1 -1
  269. package/dist/components/cms/relation-picker.js +2 -2
  270. package/dist/components/cms/relation-picker.js.map +1 -1
  271. package/dist/components/cms/rich-text/block-style.js +1 -1
  272. package/dist/components/cms/rich-text/block-style.js.map +1 -1
  273. package/dist/components/cms/rich-text/component-embed.js +4 -4
  274. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  275. package/dist/components/cms/rich-text/editor.js +3 -3
  276. package/dist/components/cms/rich-text/editor.js.map +1 -1
  277. package/dist/components/cms/rich-text/embed-picker.d.ts +1 -1
  278. package/dist/components/cms/rich-text/embed-picker.js +3 -3
  279. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  280. package/dist/components/cms/rich-text/embed-props.js +3 -3
  281. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  282. package/dist/components/cms/rich-text/extensions.js +1 -1
  283. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  284. package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
  285. package/dist/components/cms/rich-text/font-size-control.js +2 -2
  286. package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
  287. package/dist/components/cms/rich-text/insert.d.ts +20 -9
  288. package/dist/components/cms/rich-text/insert.js +25 -17
  289. package/dist/components/cms/rich-text/insert.js.map +1 -1
  290. package/dist/components/cms/rich-text/lazy.js +1 -1
  291. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  292. package/dist/components/cms/rich-text/link-control.js +2 -2
  293. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  294. package/dist/components/cms/rich-text/slash-command.js +3 -3
  295. package/dist/components/cms/rich-text/slash-command.js.map +1 -1
  296. package/dist/components/cms/rich-text/toolbar.js +1 -1
  297. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  298. package/dist/components/cms/rich-text/unknown-node.js +2 -2
  299. package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
  300. package/dist/components/cms/rich-text-preview.js +2 -2
  301. package/dist/components/cms/rich-text-preview.js.map +1 -1
  302. package/dist/components/cms/row-actions.d.ts +1 -1
  303. package/dist/components/cms/row-actions.js.map +1 -1
  304. package/dist/components/cms/scalar-value-editor.d.ts +1 -1
  305. package/dist/components/cms/scalar-value-editor.js +2 -2
  306. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  307. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  308. package/dist/components/cms/use-discard-controller.js +1 -1
  309. package/dist/components/cms/use-discard-controller.js.map +1 -1
  310. package/dist/components/cms/use-document-draft.d.ts +7 -1
  311. package/dist/components/cms/use-document-draft.js +16 -2
  312. package/dist/components/cms/use-document-draft.js.map +1 -1
  313. package/dist/components/cms/use-document-versions.js +1 -1
  314. package/dist/components/cms/use-document-versions.js.map +1 -1
  315. package/dist/components/cms/use-field-focus.d.ts +1 -1
  316. package/dist/components/cms/use-field-focus.js +2 -2
  317. package/dist/components/cms/use-field-focus.js.map +1 -1
  318. package/dist/components/cms/use-live-url.d.ts +1 -1
  319. package/dist/components/cms/use-live-url.js +3 -3
  320. package/dist/components/cms/use-live-url.js.map +1 -1
  321. package/dist/components/cms/use-publish-controller.d.ts +2 -3
  322. package/dist/components/cms/use-publish-controller.js +8 -20
  323. package/dist/components/cms/use-publish-controller.js.map +1 -1
  324. package/dist/components/cms/use-published-perspective.d.ts +1 -1
  325. package/dist/components/cms/use-published-perspective.js.map +1 -1
  326. package/dist/components/cms/use-restore-version.d.ts +1 -1
  327. package/dist/components/cms/use-restore-version.js +1 -1
  328. package/dist/components/cms/use-restore-version.js.map +1 -1
  329. package/dist/components/cms/version-compare-lazy.js +1 -1
  330. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  331. package/dist/components/cms/version-compare.js +1 -1
  332. package/dist/components/cms/version-compare.js.map +1 -1
  333. package/dist/components/cms/versions-pane.js +11 -4
  334. package/dist/components/cms/versions-pane.js.map +1 -1
  335. package/dist/components/cms/view-bar.js +3 -3
  336. package/dist/components/cms/view-bar.js.map +1 -1
  337. package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
  338. package/dist/components/collection-view/cms-collection-view-source.js +5 -3
  339. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  340. package/dist/components/collection-view/collection-view-context.d.ts +1 -1
  341. package/dist/components/collection-view/collection-view-context.js +1 -1
  342. package/dist/components/collection-view/collection-view-context.js.map +1 -1
  343. package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
  344. package/dist/components/collection-view/collection-view-surface.js +18 -0
  345. package/dist/components/collection-view/collection-view-surface.js.map +1 -0
  346. package/dist/components/components/component-detail.js +10 -10
  347. package/dist/components/components/component-detail.js.map +1 -1
  348. package/dist/components/components/component-preview.d.ts +2 -2
  349. package/dist/components/components/component-preview.js +5 -5
  350. package/dist/components/components/component-preview.js.map +1 -1
  351. package/dist/components/components/component-status.d.ts +1 -1
  352. package/dist/components/components/component-status.js +1 -1
  353. package/dist/components/components/component-status.js.map +1 -1
  354. package/dist/components/components/components-index.js +2 -2
  355. package/dist/components/components/components-index.js.map +1 -1
  356. package/dist/components/components/field-inspector.js +1 -1
  357. package/dist/components/components/field-inspector.js.map +1 -1
  358. package/dist/components/components/registry-browser.js +3 -3
  359. package/dist/components/components/registry-browser.js.map +1 -1
  360. package/dist/components/components/taxonomy.d.ts +1 -1
  361. package/dist/components/components/taxonomy.js.map +1 -1
  362. package/dist/components/components/transfer-dialog.js +4 -3
  363. package/dist/components/components/transfer-dialog.js.map +1 -1
  364. package/dist/components/components/transfer.d.ts +10 -10
  365. package/dist/components/components/transfer.js +13 -12
  366. package/dist/components/components/transfer.js.map +1 -1
  367. package/dist/components/current-user-menu.js +1 -1
  368. package/dist/components/current-user-menu.js.map +1 -1
  369. package/dist/components/delivery-health-banner.js +2 -2
  370. package/dist/components/delivery-health-banner.js.map +1 -1
  371. package/dist/components/deployment-setup-gate.js +3 -3
  372. package/dist/components/deployment-setup-gate.js.map +1 -1
  373. package/dist/components/dev-login-badge.js +2 -2
  374. package/dist/components/dev-login-badge.js.map +1 -1
  375. package/dist/components/guards.js +1 -1
  376. package/dist/components/guards.js.map +1 -1
  377. package/dist/components/inspector/document-panes.d.ts +54 -0
  378. package/dist/components/inspector/document-panes.js +41 -0
  379. package/dist/components/inspector/document-panes.js.map +1 -0
  380. package/dist/components/inspector/inspector-sidebar.js +1 -1
  381. package/dist/components/inspector/inspector-sidebar.js.map +1 -1
  382. package/dist/components/nav-issues-registry.d.ts +53 -0
  383. package/dist/components/nav-issues-registry.js +69 -0
  384. package/dist/components/nav-issues-registry.js.map +1 -0
  385. package/dist/components/nav-issues.js +4 -4
  386. package/dist/components/nav-issues.js.map +1 -1
  387. package/dist/components/operation-error.d.ts +1 -1
  388. package/dist/components/operation-error.js +4 -4
  389. package/dist/components/operation-error.js.map +1 -1
  390. package/dist/components/playground-badge.js +2 -2
  391. package/dist/components/playground-badge.js.map +1 -1
  392. package/dist/components/project-list.js +3 -3
  393. package/dist/components/project-list.js.map +1 -1
  394. package/dist/components/project-session.d.ts +2 -2
  395. package/dist/components/project-session.js +5 -5
  396. package/dist/components/project-session.js.map +1 -1
  397. package/dist/components/project-switcher.js +2 -2
  398. package/dist/components/project-switcher.js.map +1 -1
  399. package/dist/components/rail-store-status.js +1 -1
  400. package/dist/components/rail-store-status.js.map +1 -1
  401. package/dist/components/rail-view-source.d.ts +6 -5
  402. package/dist/components/rail-view-source.js +4 -3
  403. package/dist/components/rail-view-source.js.map +1 -1
  404. package/dist/components/records/record-lens.d.ts +51 -0
  405. package/dist/components/records/record-lens.js +156 -0
  406. package/dist/components/records/record-lens.js.map +1 -0
  407. package/dist/components/records/record-rail.d.ts +86 -0
  408. package/dist/components/records/record-rail.js +241 -0
  409. package/dist/components/records/record-rail.js.map +1 -0
  410. package/dist/components/redirects/redirects-schema.js +1 -1
  411. package/dist/components/redirects/redirects-schema.js.map +1 -1
  412. package/dist/components/references/references-panel.js +2 -2
  413. package/dist/components/references/references-panel.js.map +1 -1
  414. package/dist/components/section-nav.js +27 -11
  415. package/dist/components/section-nav.js.map +1 -1
  416. package/dist/components/section-state.js +2 -2
  417. package/dist/components/section-state.js.map +1 -1
  418. package/dist/components/section-surface.js +2 -2
  419. package/dist/components/section-surface.js.map +1 -1
  420. package/dist/components/session-expired-overlay.js +3 -3
  421. package/dist/components/session-expired-overlay.js.map +1 -1
  422. package/dist/components/settings-form.js +1 -1
  423. package/dist/components/settings-form.js.map +1 -1
  424. package/dist/components/sign-in-needed-badge.js +1 -1
  425. package/dist/components/sign-in-needed-badge.js.map +1 -1
  426. package/dist/components/sign-in-view.d.ts +2 -2
  427. package/dist/components/sign-in-view.js +5 -5
  428. package/dist/components/sign-in-view.js.map +1 -1
  429. package/dist/components/studio-context.js +3 -3
  430. package/dist/components/studio-context.js.map +1 -1
  431. package/dist/components/studio-project-screen.js +30 -3
  432. package/dist/components/studio-project-screen.js.map +1 -1
  433. package/dist/components/studio-shell.js +6 -5
  434. package/dist/components/studio-shell.js.map +1 -1
  435. package/dist/components/theme-context.d.ts +1 -1
  436. package/dist/components/theme-context.js +2 -2
  437. package/dist/components/theme-context.js.map +1 -1
  438. package/dist/components/use-hydration-phase.d.ts +1 -1
  439. package/dist/components/use-hydration-phase.js +1 -1
  440. package/dist/components/use-hydration-phase.js.map +1 -1
  441. package/dist/components/users/invite-user-bar.d.ts +27 -0
  442. package/dist/components/users/invite-user-bar.js +25 -9
  443. package/dist/components/users/invite-user-bar.js.map +1 -1
  444. package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
  445. package/dist/components/users/role-policy-drift-issues.js +71 -0
  446. package/dist/components/users/role-policy-drift-issues.js.map +1 -0
  447. package/dist/components/users/users-collection-view-source.d.ts +1 -1
  448. package/dist/components/users/users-collection-view-source.js +4 -2
  449. package/dist/components/users/users-collection-view-source.js.map +1 -1
  450. package/dist/components/users/users-module.js +1 -1
  451. package/dist/components/users/users-module.js.map +1 -1
  452. package/dist/components/users/users-schema.d.ts +10 -1
  453. package/dist/components/users/users-schema.js +45 -25
  454. package/dist/components/users/users-schema.js.map +1 -1
  455. package/dist/components/users/users-store.js +3 -3
  456. package/dist/components/users/users-store.js.map +1 -1
  457. package/dist/components/workspace-projects.d.ts +1 -1
  458. package/dist/components/workspace-projects.js +1 -1
  459. package/dist/components/workspace-projects.js.map +1 -1
  460. package/dist/lib/ai/apply-content.js +56 -116
  461. package/dist/lib/ai/apply-content.js.map +1 -1
  462. package/dist/lib/ai/chat-context.d.ts +8 -0
  463. package/dist/lib/ai/chat-context.js +37 -6
  464. package/dist/lib/ai/chat-context.js.map +1 -1
  465. package/dist/lib/ai/chat-state.d.ts +6 -0
  466. package/dist/lib/ai/chat-state.js.map +1 -1
  467. package/dist/lib/ai/constraints.d.ts +20 -5
  468. package/dist/lib/ai/constraints.js +125 -33
  469. package/dist/lib/ai/constraints.js.map +1 -1
  470. package/dist/lib/ai/fake-provider.d.ts +16 -3
  471. package/dist/lib/ai/fake-provider.js +22 -6
  472. package/dist/lib/ai/fake-provider.js.map +1 -1
  473. package/dist/lib/ai/index.d.ts +3 -1
  474. package/dist/lib/ai/index.js +3 -1
  475. package/dist/lib/ai/index.js.map +1 -1
  476. package/dist/lib/ai/leash.d.ts +34 -0
  477. package/dist/lib/ai/leash.js +28 -0
  478. package/dist/lib/ai/leash.js.map +1 -0
  479. package/dist/lib/ai/node-handler.d.ts +1 -1
  480. package/dist/lib/ai/node-handler.js.map +1 -1
  481. package/dist/lib/ai/rich-text-input.d.ts +8 -8
  482. package/dist/lib/ai/rich-text-input.js +2 -1
  483. package/dist/lib/ai/rich-text-input.js.map +1 -1
  484. package/dist/lib/ai/rich-text-translate.d.ts +27 -4
  485. package/dist/lib/ai/rich-text-translate.js +28 -10
  486. package/dist/lib/ai/rich-text-translate.js.map +1 -1
  487. package/dist/lib/ai/shape.d.ts +13 -13
  488. package/dist/lib/ai/shape.js +69 -93
  489. package/dist/lib/ai/shape.js.map +1 -1
  490. package/dist/lib/ai/tools.d.ts +36 -41
  491. package/dist/lib/ai/tools.js +83 -87
  492. package/dist/lib/ai/tools.js.map +1 -1
  493. package/dist/lib/ai/translate-document.d.ts +32 -4
  494. package/dist/lib/ai/translate-document.js +145 -47
  495. package/dist/lib/ai/translate-document.js.map +1 -1
  496. package/dist/lib/ai/types.d.ts +34 -23
  497. package/dist/lib/ai/types.js.map +1 -1
  498. package/dist/lib/ai/update-fields.d.ts +15 -12
  499. package/dist/lib/ai/update-fields.js +112 -188
  500. package/dist/lib/ai/update-fields.js.map +1 -1
  501. package/dist/lib/ai/writer-form.d.ts +116 -0
  502. package/dist/lib/ai/writer-form.js +429 -0
  503. package/dist/lib/ai/writer-form.js.map +1 -0
  504. package/dist/lib/assets/upload.d.ts +31 -2
  505. package/dist/lib/assets/upload.js +61 -6
  506. package/dist/lib/assets/upload.js.map +1 -1
  507. package/dist/lib/clipboard/os-clipboard.d.ts +10 -2
  508. package/dist/lib/clipboard/os-clipboard.js +8 -4
  509. package/dist/lib/clipboard/os-clipboard.js.map +1 -1
  510. package/dist/lib/clipboard/paste-issues.js +1 -1
  511. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  512. package/dist/lib/clipboard/paste-target.d.ts +18 -9
  513. package/dist/lib/clipboard/paste-target.js +10 -9
  514. package/dist/lib/clipboard/paste-target.js.map +1 -1
  515. package/dist/lib/cms/field-patch.d.ts +0 -10
  516. package/dist/lib/cms/field-patch.js +4 -30
  517. package/dist/lib/cms/field-patch.js.map +1 -1
  518. package/dist/lib/cms/published-documents.js +1 -1
  519. package/dist/lib/cms/published-documents.js.map +1 -1
  520. package/dist/lib/cms-views/view-model.d.ts +9 -9
  521. package/dist/lib/cms-views/view-model.js +2 -2
  522. package/dist/lib/cms-views/view-model.js.map +1 -1
  523. package/dist/lib/collection-view/collection-view-source.d.ts +14 -5
  524. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  525. package/dist/lib/data/collection-content.js +3 -2
  526. package/dist/lib/data/collection-content.js.map +1 -1
  527. package/dist/lib/data/content-session-refresh.d.ts +1 -1
  528. package/dist/lib/data/content-session-refresh.js.map +1 -1
  529. package/dist/lib/data/playground-unload-guard.d.ts +1 -1
  530. package/dist/lib/data/playground-unload-guard.js +1 -1
  531. package/dist/lib/data/playground-unload-guard.js.map +1 -1
  532. package/dist/lib/dnd/drop-rules.d.ts +25 -31
  533. package/dist/lib/dnd/drop-rules.js +53 -59
  534. package/dist/lib/dnd/drop-rules.js.map +1 -1
  535. package/dist/lib/engine-version.d.ts +1 -1
  536. package/dist/lib/engine-version.js +1 -1
  537. package/dist/lib/engine-version.js.map +1 -1
  538. package/dist/lib/i18n/i18n.js +1 -1
  539. package/dist/lib/i18n/i18n.js.map +1 -1
  540. package/dist/lib/i18n/messages.de.js +44 -3
  541. package/dist/lib/i18n/messages.de.js.map +1 -1
  542. package/dist/lib/i18n/messages.en.d.ts +38 -2
  543. package/dist/lib/i18n/messages.en.js +51 -3
  544. package/dist/lib/i18n/messages.en.js.map +1 -1
  545. package/dist/lib/operations/project-runtime-config.d.ts +34 -6
  546. package/dist/lib/operations/project-runtime-config.js +50 -10
  547. package/dist/lib/operations/project-runtime-config.js.map +1 -1
  548. package/dist/lib/operations/publish-with-revision.d.ts +33 -1
  549. package/dist/lib/operations/publish-with-revision.js +32 -0
  550. package/dist/lib/operations/publish-with-revision.js.map +1 -1
  551. package/dist/lib/operations/runtime-validator.d.ts +19 -68
  552. package/dist/lib/operations/runtime-validator.js +12 -138
  553. package/dist/lib/operations/runtime-validator.js.map +1 -1
  554. package/dist/lib/operations/studio-operations.d.ts +17 -2
  555. package/dist/lib/operations/studio-operations.js +18 -11
  556. package/dist/lib/operations/studio-operations.js.map +1 -1
  557. package/dist/lib/persistence/convex-adapter.d.ts +16 -3
  558. package/dist/lib/persistence/convex-adapter.js +83 -47
  559. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  560. package/dist/lib/persistence/index.js +14 -1
  561. package/dist/lib/persistence/index.js.map +1 -1
  562. package/dist/lib/persona/labels.js +4 -2
  563. package/dist/lib/persona/labels.js.map +1 -1
  564. package/dist/lib/query/operations-query.d.ts +1 -1
  565. package/dist/lib/query/operations-query.js +1 -1
  566. package/dist/lib/query/operations-query.js.map +1 -1
  567. package/dist/lib/references/reference-model.js +1 -1
  568. package/dist/lib/references/reference-model.js.map +1 -1
  569. package/dist/lib/references/references-store.js +1 -1
  570. package/dist/lib/references/references-store.js.map +1 -1
  571. package/dist/lib/workspace/index.d.ts +14 -11
  572. package/dist/lib/workspace/index.js +20 -24
  573. package/dist/lib/workspace/index.js.map +1 -1
  574. package/dist/mount.d.ts +28 -0
  575. package/dist/mount.js +27 -0
  576. package/dist/mount.js.map +1 -0
  577. package/dist/router/root.js +10 -10
  578. package/dist/router/root.js.map +1 -1
  579. package/dist/ui/badge.d.ts +1 -1
  580. package/dist/ui/button.d.ts +2 -2
  581. package/package.json +15 -11
  582. package/dist/components/cms/cms-composition-validators.d.ts +0 -16
  583. package/dist/components/cms/cms-composition-validators.js +0 -133
  584. package/dist/components/cms/cms-composition-validators.js.map +0 -1
  585. package/dist/components/cms/publish-clean.d.ts +0 -28
  586. package/dist/components/cms/publish-clean.js +0 -38
  587. package/dist/components/cms/publish-clean.js.map +0 -1
  588. package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
  589. package/dist/components/redirects/redirects-collection-view-source.js +0 -146
  590. package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
  591. package/dist/components/redirects/redirects-module.d.ts +0 -21
  592. package/dist/components/redirects/redirects-module.js +0 -40
  593. package/dist/components/redirects/redirects-module.js.map +0 -1
  594. package/dist/components/redirects/redirects-store.d.ts +0 -68
  595. package/dist/components/redirects/redirects-store.js +0 -132
  596. package/dist/components/redirects/redirects-store.js.map +0 -1
  597. package/dist/lib/assets/asset-placements.d.ts +0 -28
  598. package/dist/lib/assets/asset-placements.js +0 -83
  599. package/dist/lib/assets/asset-placements.js.map +0 -1
@@ -0,0 +1,30 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ComponentNode } from '@elytracms/core/project-graph';
3
+ import type { RenderContext } from '@elytracms/core/runtime-renderer';
4
+ import type { PreservedItem } from './canvas-runs.js';
5
+ /**
6
+ * What the canvas iframe renders (EC-592 + EC-549, owner-ratified 4d) — pure, so the
7
+ * markup is pinned by `renderToStaticMarkup`.
8
+ *
9
+ * The canvas renders the FIELD VALUE — the store's component roots merged with its
10
+ * preserved typography (`mergeBlockRoots`, the same merge the write path stores) —
11
+ * through delivery's ONE entry point (`renderDocumentBodyItems(documentBodyItems(…))`),
12
+ * with the context core's `createRenderContext` built. So the canvas and the live page
13
+ * cannot diverge: typography runs paint in their true positions through the same
14
+ * rich-text mapping, nested values keep their prose, and blocks render as bare
15
+ * siblings exactly as on delivery.
16
+ *
17
+ * The editor's synthetic root is DECORATED, never rendered as a component: it is the
18
+ * selectable, droppable frame around the items (`data-ec-node-id` of the root, the
19
+ * `ec-editable` wrapper every canvas gesture resolves against) — no Stack styling the
20
+ * stored value does not carry. Each top-level typography run is decorated by its PATH
21
+ * (`data-ec-run` = the run's key, `data-ec-run-start`/`-end` = its index range in the
22
+ * field value), so it can be selected and deleted like a block (EC-549).
23
+ */
24
+ export declare function renderCanvasBody(args: {
25
+ root: ComponentNode;
26
+ preserved: readonly PreservedItem[];
27
+ ctx: RenderContext;
28
+ selectedId: string | null;
29
+ selectedRun: number | null;
30
+ }): ReactNode;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { DEFAULT_SLOT, mergeBlockRoots } from '@elytracms/core/project-graph';
3
+ import { documentBodyItems, renderDocumentBodyItems } from '@elytracms/core/runtime-renderer';
4
+ import { canvasRuns } from './canvas-runs.js';
5
+ /**
6
+ * What the canvas iframe renders (EC-592 + EC-549, owner-ratified 4d) — pure, so the
7
+ * markup is pinned by `renderToStaticMarkup`.
8
+ *
9
+ * The canvas renders the FIELD VALUE — the store's component roots merged with its
10
+ * preserved typography (`mergeBlockRoots`, the same merge the write path stores) —
11
+ * through delivery's ONE entry point (`renderDocumentBodyItems(documentBodyItems(…))`),
12
+ * with the context core's `createRenderContext` built. So the canvas and the live page
13
+ * cannot diverge: typography runs paint in their true positions through the same
14
+ * rich-text mapping, nested values keep their prose, and blocks render as bare
15
+ * siblings exactly as on delivery.
16
+ *
17
+ * The editor's synthetic root is DECORATED, never rendered as a component: it is the
18
+ * selectable, droppable frame around the items (`data-ec-node-id` of the root, the
19
+ * `ec-editable` wrapper every canvas gesture resolves against) — no Stack styling the
20
+ * stored value does not carry. Each top-level typography run is decorated by its PATH
21
+ * (`data-ec-run` = the run's key, `data-ec-run-start`/`-end` = its index range in the
22
+ * field value), so it can be selected and deleted like a block (EC-549).
23
+ */
24
+ export function renderCanvasBody(args) {
25
+ const { root, preserved, ctx } = args;
26
+ const roots = root.slots?.[DEFAULT_SLOT] ?? [];
27
+ const value = mergeBlockRoots(roots, preserved);
28
+ const runs = canvasRuns(roots.length, preserved);
29
+ return (_jsx("div", { "data-ec-node-id": root.id, "data-ec-canvas-root": "", className: "ec-editable", "data-selected": root.id === args.selectedId || undefined, children: renderDocumentBodyItems(documentBodyItems(value), ctx, {
30
+ decorateRegion: (region, rendered) => {
31
+ const run = region.range ? runs.find((candidate) => candidate.range.start === region.range.start) : undefined;
32
+ if (!run)
33
+ return rendered;
34
+ return (_jsx("div", { "data-ec-run": run.key, "data-ec-run-start": run.range.start, "data-ec-run-end": run.range.end, className: "ec-run", "data-selected": run.key === args.selectedRun || undefined, children: rendered }));
35
+ },
36
+ }) }));
37
+ }
38
+ //# sourceMappingURL=canvas-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-body.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-body.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAE7E,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAA;AAE7F,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAG1C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAMhC;IACC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAA;IAC9C,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IAC/C,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAChD,OAAO,CACL,iCACmB,IAAI,CAAC,EAAE,yBACJ,EAAE,EACtB,SAAS,EAAC,aAAa,mBACR,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,UAAU,IAAI,SAAS,YAEtD,uBAAuB,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE;YACtD,cAAc,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;gBACnC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC,KAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;gBAC9G,IAAI,CAAC,GAAG;oBAAE,OAAO,QAAQ,CAAA;gBACzB,OAAO,CACL,6BACe,GAAG,CAAC,GAAG,uBACD,GAAG,CAAC,KAAK,CAAC,KAAK,qBACjB,GAAG,CAAC,KAAK,CAAC,GAAG,EAC9B,SAAS,EAAC,QAAQ,mBACH,GAAG,CAAC,GAAG,KAAK,IAAI,CAAC,WAAW,IAAI,SAAS,YAEvD,QAAQ,GACL,CACP,CAAA;YACH,CAAC;SACF,CAAC,GACE,CACP,CAAA;AACH,CAAC","sourcesContent":["import type { ReactNode } from 'react'\nimport { DEFAULT_SLOT, mergeBlockRoots } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { documentBodyItems, renderDocumentBodyItems } from '@elytracms/core/runtime-renderer'\nimport type { RenderContext } from '@elytracms/core/runtime-renderer'\nimport { canvasRuns } from './canvas-runs'\nimport type { PreservedItem } from './canvas-runs'\n\n/**\n * What the canvas iframe renders (EC-592 + EC-549, owner-ratified 4d) — pure, so the\n * markup is pinned by `renderToStaticMarkup`.\n *\n * The canvas renders the FIELD VALUE — the store's component roots merged with its\n * preserved typography (`mergeBlockRoots`, the same merge the write path stores) —\n * through delivery's ONE entry point (`renderDocumentBodyItems(documentBodyItems(…))`),\n * with the context core's `createRenderContext` built. So the canvas and the live page\n * cannot diverge: typography runs paint in their true positions through the same\n * rich-text mapping, nested values keep their prose, and blocks render as bare\n * siblings exactly as on delivery.\n *\n * The editor's synthetic root is DECORATED, never rendered as a component: it is the\n * selectable, droppable frame around the items (`data-ec-node-id` of the root, the\n * `ec-editable` wrapper every canvas gesture resolves against) — no Stack styling the\n * stored value does not carry. Each top-level typography run is decorated by its PATH\n * (`data-ec-run` = the run's key, `data-ec-run-start`/`-end` = its index range in the\n * field value), so it can be selected and deleted like a block (EC-549).\n */\nexport function renderCanvasBody(args: {\n root: ComponentNode\n preserved: readonly PreservedItem[]\n ctx: RenderContext\n selectedId: string | null\n selectedRun: number | null\n}): ReactNode {\n const { root, preserved, ctx } = args\n const roots = root.slots?.[DEFAULT_SLOT] ?? []\n const value = mergeBlockRoots(roots, preserved)\n const runs = canvasRuns(roots.length, preserved)\n return (\n <div\n data-ec-node-id={root.id}\n data-ec-canvas-root=\"\"\n className=\"ec-editable\"\n data-selected={root.id === args.selectedId || undefined}\n >\n {renderDocumentBodyItems(documentBodyItems(value), ctx, {\n decorateRegion: (region, rendered) => {\n const run = region.range ? runs.find((candidate) => candidate.range.start === region.range!.start) : undefined\n if (!run) return rendered\n return (\n <div\n data-ec-run={run.key}\n data-ec-run-start={run.range.start}\n data-ec-run-end={run.range.end}\n className=\"ec-run\"\n data-selected={run.key === args.selectedRun || undefined}\n >\n {rendered}\n </div>\n )\n },\n })}\n </div>\n )\n}\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"]}
@@ -88,6 +88,13 @@ const CHROME_UI_CSS = `
88
88
  it); its !important (contract item 3) keeps a site theme's element rules
89
89
  from hiding those states. */
90
90
  .ec-editable { outline-offset: -1px !important; cursor: default; }
91
+ /* EC-549: a typography RUN of the field value (loose prose between blocks) —
92
+ selectable and deletable by its path. Prose flows in the page, so the selected
93
+ run carries its own outline (a run has no frame bar: it is edited and deleted
94
+ in the inspector, or with the Delete key). */
95
+ .ec-run { cursor: default; }
96
+ .ec-run:hover { outline: 1px dashed #93b4ff !important; outline-offset: 2px !important; }
97
+ .ec-run[data-selected] { outline: 2px solid #5b8cff !important; outline-offset: 2px !important; }
91
98
 
92
99
  /* ── Selection/hover overlay (EC-404) ───────────────────────────────────
93
100
  The topmost editing layer, portaled into the iframe body as a sibling of
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-chrome-css.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-chrome-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAyC9D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,4BAA4B,CAAA;AAE3E;;;;;;;;GAQG;AACH,MAAM,sBAAsB,GAAG;;;;;CAK9B,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,mBAAmB,GAAG;;CAE3B,CAAA;AAED;;;;;GAKG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCA4IkB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCzD,CAAA;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,aAAa,CAAA;AAC5F,CAAC","sourcesContent":["import { PRESERVE_NODE_ATTR } from '@elytracms/core/rich-text'\n\n/**\n * The canvas chrome STYLESHEET (EC-414 / AD-B) — the studio's complete in-frame\n * styling, hand-written. This is deliberately NOT a second Tailwind world: the\n * chrome inside the canvas iframe styles itself with `ec-*` classes, `data-ec-*`\n * attributes, and inline styles only, so the consumer's theme is the ONLY design\n * system in the canvas document and the canvas resolves exactly what delivery\n * resolves (AD-A).\n *\n * The chrome contract (binding on ALL in-frame studio UI — EC-404/405/408 build\n * on this):\n *\n * 1. Only `ec-*` class names / `data-ec-*` attributes / inline styles — never\n * studio utility classes, never studio React UI primitives.\n * 2. Chrome roots carry the containment recipe (`.ec-chrome`): `all: initial`\n * plus explicit font/color/line-height/color-scheme re-establishment, so\n * consumer element rules and inherited typography cannot restyle chrome.\n * NOTE (verified, MDN): `all: initial` does NOT stop custom-property\n * inheritance — the real protection is that chrome never CONSUMES a var()\n * name a consumer could define (this file uses no var() at all; the\n * contract test enforces it).\n * 3. Where a consumer ELEMENT selector could still match chrome descendants\n * (`div`, `span`, `button`, `input` rules in a site theme), chrome rules\n * use `!important` deliberately — a small, documented surface: the\n * selection/DnD indicators, the rich-text toolbar, and the locale chip.\n * The `.ec-chrome` recipe itself stays normal-strength on purpose: inline\n * styles are the top styling layer for chrome-owned looks (placeholders,\n * spacing handles) and stylesheet-`!important` would beat them.\n * 4. Escape hatch (documented only, NOT built): if chrome ever outgrows\n * hand-written CSS, the verified prefixed-bundle recipe is the upgrade\n * path — granular Tailwind imports `theme.css layer(theme) prefix(ec)` +\n * `utilities.css layer(utilities) prefix(ec) important source(none)`\n * compiled into an engine-provided entry (compiles clean on 4.3.2).\n */\n\n/** The two canvas stylesheet worlds (AD-C): `legacy` = the pre-EC-414 merged\n * canvas.css (studio design system + consumer theme in one build); `new` = the\n * consumer's standalone full-Tailwind theme entry, studio-free. */\nexport type CanvasWorld = 'legacy' | 'new'\n\n/**\n * Injected FIRST into the canvas head (before the `ec-app-css` link): a bare\n * layer STATEMENT that pins `ec-canvas-backdrop` to the very bottom of the\n * document's layer order (layer order is first-occurrence order). The new-world\n * backdrop rules live in this layer, so they lose to EVERYTHING the consumer\n * ships — `@layer base` body rules, unlayered rules, later layers — and only\n * paint when the consumer paints nothing. (An unlayered backdrop would ALWAYS\n * beat the consumer's layered `@layer base body { background }` — the exact\n * NK-004 mechanism this task retires.) Inert in the legacy world: the legacy\n * chrome CSS puts nothing in the layer.\n */\nexport const CANVAS_BACKDROP_LAYER_STATEMENT = '@layer ec-canvas-backdrop;'\n\n/**\n * NEW-world backdrop (AD-B item 4): the studio-utility body-class default is\n * retired; these neutral hand-written rules give the canvas a light, site-like\n * page when the consumer theme paints nothing, in the weakest possible cascade\n * position (see {@link CANVAS_BACKDROP_LAYER_STATEMENT}). Background sits on\n * BODY (not html) so a consumer `body { background }` keeps its normal\n * canvas-propagation behavior — short pages paint edge-to-edge exactly as the\n * delivery build does.\n */\nconst NEW_WORLD_BACKDROP_CSS = `\n @layer ec-canvas-backdrop {\n html { color-scheme: light; }\n body { background-color: #ffffff; }\n }\n`\n\n/**\n * LEGACY-world backdrop — today's rule, verbatim and unlayered (EC-389-era\n * behavior applies EXACTLY in the legacy world, per AD-C): the canvas backdrop\n * is deliberately always light/site-like, so the native form-control/scrollbar\n * theme is forced to match and a page with real inputs never shows dark native\n * chrome floating on a light page. The light page itself comes from the studio\n * body-class default (`DEFAULT_CANVAS_BODY_CLASS`) resolving against the merged\n * stylesheet's studio tokens.\n */\nconst LEGACY_BACKDROP_CSS = `\n html { color-scheme: light; }\n`\n\n/**\n * The chrome UI rules — identical in both worlds. Sections:\n * page frame, containment recipe, editable wrappers, selection/hover overlay\n * (EC-404), inline editing, rich-text toolbar, locale-fallback chips, drag &\n * drop indicators.\n */\nconst CHROME_UI_CSS = `\n /* ── Page frame ─────────────────────────────────────────────────────────\n The canvas gutter — unlayered on purpose (identical in both worlds, and\n it must beat a consumer body margin: the 16px frame is editor ergonomics,\n not page styling). */\n body { margin: 0; padding: 16px; }\n\n /* ── Containment recipe (chrome contract item 2) ────────────────────────\n Every chrome ROOT (toolbar, spacing handles, in-canvas placeholders)\n carries .ec-chrome: reset everything, then re-establish the chrome's own\n typography and color-scheme explicitly. Inherited custom properties pierce\n 'all: initial' (and even shadow DOM) — that is why chrome never consumes\n var() names (contract-tested), not something this recipe could fix.\n The :where(div, span, p) guard re-links descendant text to the chrome\n root's typography with class-level specificity, so a consumer's bare\n 'div, span { color: … }' element rules cannot restyle chrome text; chrome\n elements styled inline stay inline-styled (inline always wins here). */\n .ec-chrome {\n all: initial;\n display: block;\n box-sizing: border-box;\n font-family: ui-sans-serif, system-ui, sans-serif;\n font-size: 13px;\n line-height: 1.5;\n color: rgb(30, 35, 45);\n color-scheme: light;\n }\n .ec-chrome *,\n .ec-chrome *::before,\n .ec-chrome *::after { box-sizing: border-box; }\n .ec-chrome :where(div, span, p) { color: inherit; font: inherit; }\n\n /* ── Editable wrappers ──────────────────────────────────────────────────\n Scoped to the per-node editable wrappers. The wrappers WRAP consumer\n content, so they are deliberately NOT contained (no .ec-chrome — they must\n stay layout-transparent). EC-404 RETIRED the in-document selection/hover\n outlines (data-selected / data-hovered): an outline paints in the\n element's own stacking order and any full-bleed background or higher\n z-index sibling paints over it — a structural occlusion, not a z-index bug.\n Selection + hover are now drawn by the topmost .ec-overlay layer below.\n The outline-offset stays for the editing outline (and DnD 'into' overrides\n it); its !important (contract item 3) keeps a site theme's element rules\n from hiding those states. */\n .ec-editable { outline-offset: -1px !important; cursor: default; }\n\n /* ── Selection/hover overlay (EC-404) ───────────────────────────────────\n The topmost editing layer, portaled into the iframe body as a sibling of\n the page composition and pinned to the maximum stacking level, so NOTHING\n the page paints can occlude the selection frame. The root carries the\n .ec-chrome containment recipe; geometry (left/top/width/height) is inline,\n everything visual lives here. Element-matching declarations carry\n !important (contract item 3): the frame boxes are <div>s and the controls\n are <button>s a site theme's element rules would otherwise restyle. Frames\n are click-through (pointer-events:none); only the bar re-enables events. */\n .ec-overlay {\n position: absolute !important; top: 0 !important; left: 0 !important;\n z-index: 2147483647 !important; pointer-events: none !important;\n width: 0 !important; height: 0 !important;\n }\n .ec-overlay-frame {\n position: absolute !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 2px !important;\n }\n /* Hover frame (EC-435 AD-3): accent-leaning and heavier than the old 1px\n grey-blue dash (easy to overlook over imagery; live-tuned on light AND\n busy sections — 1.5px collapses to 1px at 1x DPR, so 2px is the smallest\n deterministic bump). Still clearly secondary to the SELECTED frame: the\n same weight but dashed and translucent vs. solid opaque. */\n .ec-overlay-frame[data-ec-hover] { z-index: 1 !important; border: 2px dashed rgba(91,140,255,0.8) !important; }\n .ec-overlay-frame[data-ec-selected] { z-index: 2 !important; border: 2px solid #5b8cff !important; }\n /* ── Prop highlight (EC-408 reverse: field focus → sub-part) ─────────────\n A quiet FILLED tint (no hard border) on the focused prop's element,\n deliberately distinct from the selection frame (solid border) and the\n hover frame (dashed border). Sits below both frames so their borders stay\n crisp; click-through like the frames. */\n .ec-overlay-prop {\n position: absolute !important; z-index: 1 !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 3px !important;\n background: rgba(91,140,255,0.16) !important;\n box-shadow: inset 0 0 0 1px rgba(91,140,255,0.45) !important;\n }\n .ec-overlay-bar {\n position: absolute !important; z-index: 3 !important; pointer-events: auto !important;\n display: flex !important; align-items: center !important; gap: 1px !important;\n height: 22px !important; max-width: 320px !important; padding: 0 2px 0 6px !important;\n border-radius: 6px !important; background: #5b8cff !important;\n box-shadow: 0 2px 8px rgba(8,12,24,0.35) !important;\n font: 600 11px/1 ui-sans-serif, system-ui, sans-serif !important;\n color: #ffffff !important; white-space: nowrap !important;\n }\n .ec-overlay-label {\n overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important;\n max-width: 180px !important; padding-right: 2px !important;\n font: inherit !important; color: #ffffff !important;\n }\n .ec-overlay-sep {\n width: 1px !important; height: 12px !important; margin: 0 3px !important;\n background: rgba(255,255,255,0.4) !important;\n }\n .ec-overlay-btn {\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 18px !important; padding: 0 !important; margin: 0 !important;\n border: 0 !important; border-radius: 4px !important; background: transparent !important;\n color: #ffffff !important; cursor: pointer !important; -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-btn:hover { background: rgba(255,255,255,0.22) !important; }\n .ec-overlay-btn:disabled { opacity: 0.4 !important; cursor: default !important; }\n\n /* ── Insertion affordances (EC-405) ─────────────────────────────────────\n A \"+\" button centered ON a block boundary (before/after edge, or the center\n of an empty container) — the geometry is inline (the anchor point), the\n translate here centers the button on it. Appears only on hover/selection\n (never during a drag or inline edit); pointer-events re-enabled so it is\n clickable while the overlay root stays click-through. Element-matching\n declarations !important (contract item 3): it is a <button> a site theme's\n element rules would otherwise restyle. */\n .ec-overlay-add {\n position: absolute !important; z-index: 4 !important; pointer-events: auto !important;\n transform: translate(-50%, -50%) !important;\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 20px !important; padding: 0 !important; margin: 0 !important;\n border: 1.5px solid #ffffff !important; border-radius: 999px !important;\n background: #5b8cff !important; color: #ffffff !important; cursor: pointer !important;\n box-shadow: 0 1px 5px rgba(8,12,24,0.4) !important;\n -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-add:hover { background: #3f74f5 !important; }\n\n /* ── Inline editing (EC-158 text, EC-159 rich text) ─────────────────────\n The rendered element itself becomes the editor — no overlay, no\n replacement input — so typography, alignment, and layout are\n pixel-identical between edit and render states. The wrapper keeps the\n selection outline; the host only gets a caret. pre-wrap applies to PLAIN\n text hosts only — rich-text markup must keep its rendered whitespace\n handling (no layout shift). */\n .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff !important; }\n .ec-editable [data-ec-edit] { outline: none; cursor: text; }\n .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }\n /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,\n images, embeds): visible and rendered, but not text-editable. */\n .ec-editable [data-ec-edit='rich'] [${PRESERVE_NODE_ATTR}] { user-select: none; }\n\n /* ── Floating mark toolbar (EC-159), portaled into the iframe ───────────\n All visual declarations !important (contract item 3): the toolbar is\n stylesheet-styled chrome whose button/input elements a site theme's\n element rules would otherwise match. */\n .ec-rt-toolbar { color-scheme: dark !important; }\n .ec-rt-toolbar-row { display: flex !important; align-items: center !important; gap: 2px !important;\n padding: 3px !important; margin: 0 !important;\n border: 1px solid rgba(130,150,190,0.4) !important; border-radius: 8px !important;\n background: rgb(24,26,32) !important; box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; }\n .ec-rt-toolbar-btn { min-width: 26px !important; height: 24px !important; padding: 0 7px !important;\n border: 0 !important; border-radius: 5px !important;\n background: transparent !important; color: rgb(225,228,236) !important;\n font: 600 12px/1 ui-sans-serif, system-ui, sans-serif !important;\n cursor: pointer !important; }\n .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25) !important; }\n .ec-rt-toolbar-btn[data-active] { background: #5b8cff !important; color: white !important; }\n .ec-rt-toolbar-input { width: 200px !important; height: 24px !important; padding: 0 7px !important;\n border-radius: 5px !important;\n border: 1px solid rgba(130,150,190,0.4) !important; background: rgb(16,18,22) !important;\n color: rgb(225,228,236) !important;\n font: 12px/1 ui-sans-serif, system-ui, sans-serif !important; outline: none !important; }\n\n /* ── Drag & drop states (EC-160/EC-162) ─────────────────────────────────\n Eligibility comes from the shared pure rules (lib/dnd/drop-rules); these\n classes only *show* the resolution. Rejection is explicit and visible\n during the drag — never a silent failed drop. Before/after bars follow the\n parent's flow axis (data-ec-drop-axis); left/right are the\n wrap-into-columns gesture (violet, paired highlight). Indicator\n declarations !important (contract item 3). */\n .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399 !important; outline-offset: -2px !important; background-color: rgba(52, 211, 153, 0.08) !important; }\n .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444 !important; outline-offset: -2px !important; background-color: rgba(239, 68, 68, 0.08) !important; cursor: not-allowed !important; }\n`\n\n/**\n * The complete chrome stylesheet for a world — the `ec-outline-css` element's\n * content (id kept from EC-389; the cascade position is pinned by test).\n */\nexport function canvasChromeCss(world: CanvasWorld): string {\n return (world === 'legacy' ? LEGACY_BACKDROP_CSS : NEW_WORLD_BACKDROP_CSS) + CHROME_UI_CSS\n}\n"]}
1
+ {"version":3,"file":"canvas-chrome-css.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-chrome-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAyC9D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,4BAA4B,CAAA;AAE3E;;;;;;;;GAQG;AACH,MAAM,sBAAsB,GAAG;;;;;CAK9B,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,mBAAmB,GAAG;;CAE3B,CAAA;AAED;;;;;GAKG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAmJkB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCzD,CAAA;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,aAAa,CAAA;AAC5F,CAAC","sourcesContent":["import { PRESERVE_NODE_ATTR } from '@elytracms/core/rich-text'\n\n/**\n * The canvas chrome STYLESHEET (EC-414 / AD-B) — the studio's complete in-frame\n * styling, hand-written. This is deliberately NOT a second Tailwind world: the\n * chrome inside the canvas iframe styles itself with `ec-*` classes, `data-ec-*`\n * attributes, and inline styles only, so the consumer's theme is the ONLY design\n * system in the canvas document and the canvas resolves exactly what delivery\n * resolves (AD-A).\n *\n * The chrome contract (binding on ALL in-frame studio UI — EC-404/405/408 build\n * on this):\n *\n * 1. Only `ec-*` class names / `data-ec-*` attributes / inline styles — never\n * studio utility classes, never studio React UI primitives.\n * 2. Chrome roots carry the containment recipe (`.ec-chrome`): `all: initial`\n * plus explicit font/color/line-height/color-scheme re-establishment, so\n * consumer element rules and inherited typography cannot restyle chrome.\n * NOTE (verified, MDN): `all: initial` does NOT stop custom-property\n * inheritance — the real protection is that chrome never CONSUMES a var()\n * name a consumer could define (this file uses no var() at all; the\n * contract test enforces it).\n * 3. Where a consumer ELEMENT selector could still match chrome descendants\n * (`div`, `span`, `button`, `input` rules in a site theme), chrome rules\n * use `!important` deliberately — a small, documented surface: the\n * selection/DnD indicators, the rich-text toolbar, and the locale chip.\n * The `.ec-chrome` recipe itself stays normal-strength on purpose: inline\n * styles are the top styling layer for chrome-owned looks (placeholders,\n * spacing handles) and stylesheet-`!important` would beat them.\n * 4. Escape hatch (documented only, NOT built): if chrome ever outgrows\n * hand-written CSS, the verified prefixed-bundle recipe is the upgrade\n * path — granular Tailwind imports `theme.css layer(theme) prefix(ec)` +\n * `utilities.css layer(utilities) prefix(ec) important source(none)`\n * compiled into an engine-provided entry (compiles clean on 4.3.2).\n */\n\n/** The two canvas stylesheet worlds (AD-C): `legacy` = the pre-EC-414 merged\n * canvas.css (studio design system + consumer theme in one build); `new` = the\n * consumer's standalone full-Tailwind theme entry, studio-free. */\nexport type CanvasWorld = 'legacy' | 'new'\n\n/**\n * Injected FIRST into the canvas head (before the `ec-app-css` link): a bare\n * layer STATEMENT that pins `ec-canvas-backdrop` to the very bottom of the\n * document's layer order (layer order is first-occurrence order). The new-world\n * backdrop rules live in this layer, so they lose to EVERYTHING the consumer\n * ships — `@layer base` body rules, unlayered rules, later layers — and only\n * paint when the consumer paints nothing. (An unlayered backdrop would ALWAYS\n * beat the consumer's layered `@layer base body { background }` — the exact\n * NK-004 mechanism this task retires.) Inert in the legacy world: the legacy\n * chrome CSS puts nothing in the layer.\n */\nexport const CANVAS_BACKDROP_LAYER_STATEMENT = '@layer ec-canvas-backdrop;'\n\n/**\n * NEW-world backdrop (AD-B item 4): the studio-utility body-class default is\n * retired; these neutral hand-written rules give the canvas a light, site-like\n * page when the consumer theme paints nothing, in the weakest possible cascade\n * position (see {@link CANVAS_BACKDROP_LAYER_STATEMENT}). Background sits on\n * BODY (not html) so a consumer `body { background }` keeps its normal\n * canvas-propagation behavior — short pages paint edge-to-edge exactly as the\n * delivery build does.\n */\nconst NEW_WORLD_BACKDROP_CSS = `\n @layer ec-canvas-backdrop {\n html { color-scheme: light; }\n body { background-color: #ffffff; }\n }\n`\n\n/**\n * LEGACY-world backdrop — today's rule, verbatim and unlayered (EC-389-era\n * behavior applies EXACTLY in the legacy world, per AD-C): the canvas backdrop\n * is deliberately always light/site-like, so the native form-control/scrollbar\n * theme is forced to match and a page with real inputs never shows dark native\n * chrome floating on a light page. The light page itself comes from the studio\n * body-class default (`DEFAULT_CANVAS_BODY_CLASS`) resolving against the merged\n * stylesheet's studio tokens.\n */\nconst LEGACY_BACKDROP_CSS = `\n html { color-scheme: light; }\n`\n\n/**\n * The chrome UI rules — identical in both worlds. Sections:\n * page frame, containment recipe, editable wrappers, selection/hover overlay\n * (EC-404), inline editing, rich-text toolbar, locale-fallback chips, drag &\n * drop indicators.\n */\nconst CHROME_UI_CSS = `\n /* ── Page frame ─────────────────────────────────────────────────────────\n The canvas gutter — unlayered on purpose (identical in both worlds, and\n it must beat a consumer body margin: the 16px frame is editor ergonomics,\n not page styling). */\n body { margin: 0; padding: 16px; }\n\n /* ── Containment recipe (chrome contract item 2) ────────────────────────\n Every chrome ROOT (toolbar, spacing handles, in-canvas placeholders)\n carries .ec-chrome: reset everything, then re-establish the chrome's own\n typography and color-scheme explicitly. Inherited custom properties pierce\n 'all: initial' (and even shadow DOM) — that is why chrome never consumes\n var() names (contract-tested), not something this recipe could fix.\n The :where(div, span, p) guard re-links descendant text to the chrome\n root's typography with class-level specificity, so a consumer's bare\n 'div, span { color: … }' element rules cannot restyle chrome text; chrome\n elements styled inline stay inline-styled (inline always wins here). */\n .ec-chrome {\n all: initial;\n display: block;\n box-sizing: border-box;\n font-family: ui-sans-serif, system-ui, sans-serif;\n font-size: 13px;\n line-height: 1.5;\n color: rgb(30, 35, 45);\n color-scheme: light;\n }\n .ec-chrome *,\n .ec-chrome *::before,\n .ec-chrome *::after { box-sizing: border-box; }\n .ec-chrome :where(div, span, p) { color: inherit; font: inherit; }\n\n /* ── Editable wrappers ──────────────────────────────────────────────────\n Scoped to the per-node editable wrappers. The wrappers WRAP consumer\n content, so they are deliberately NOT contained (no .ec-chrome — they must\n stay layout-transparent). EC-404 RETIRED the in-document selection/hover\n outlines (data-selected / data-hovered): an outline paints in the\n element's own stacking order and any full-bleed background or higher\n z-index sibling paints over it — a structural occlusion, not a z-index bug.\n Selection + hover are now drawn by the topmost .ec-overlay layer below.\n The outline-offset stays for the editing outline (and DnD 'into' overrides\n it); its !important (contract item 3) keeps a site theme's element rules\n from hiding those states. */\n .ec-editable { outline-offset: -1px !important; cursor: default; }\n /* EC-549: a typography RUN of the field value (loose prose between blocks) —\n selectable and deletable by its path. Prose flows in the page, so the selected\n run carries its own outline (a run has no frame bar: it is edited and deleted\n in the inspector, or with the Delete key). */\n .ec-run { cursor: default; }\n .ec-run:hover { outline: 1px dashed #93b4ff !important; outline-offset: 2px !important; }\n .ec-run[data-selected] { outline: 2px solid #5b8cff !important; outline-offset: 2px !important; }\n\n /* ── Selection/hover overlay (EC-404) ───────────────────────────────────\n The topmost editing layer, portaled into the iframe body as a sibling of\n the page composition and pinned to the maximum stacking level, so NOTHING\n the page paints can occlude the selection frame. The root carries the\n .ec-chrome containment recipe; geometry (left/top/width/height) is inline,\n everything visual lives here. Element-matching declarations carry\n !important (contract item 3): the frame boxes are <div>s and the controls\n are <button>s a site theme's element rules would otherwise restyle. Frames\n are click-through (pointer-events:none); only the bar re-enables events. */\n .ec-overlay {\n position: absolute !important; top: 0 !important; left: 0 !important;\n z-index: 2147483647 !important; pointer-events: none !important;\n width: 0 !important; height: 0 !important;\n }\n .ec-overlay-frame {\n position: absolute !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 2px !important;\n }\n /* Hover frame (EC-435 AD-3): accent-leaning and heavier than the old 1px\n grey-blue dash (easy to overlook over imagery; live-tuned on light AND\n busy sections — 1.5px collapses to 1px at 1x DPR, so 2px is the smallest\n deterministic bump). Still clearly secondary to the SELECTED frame: the\n same weight but dashed and translucent vs. solid opaque. */\n .ec-overlay-frame[data-ec-hover] { z-index: 1 !important; border: 2px dashed rgba(91,140,255,0.8) !important; }\n .ec-overlay-frame[data-ec-selected] { z-index: 2 !important; border: 2px solid #5b8cff !important; }\n /* ── Prop highlight (EC-408 reverse: field focus → sub-part) ─────────────\n A quiet FILLED tint (no hard border) on the focused prop's element,\n deliberately distinct from the selection frame (solid border) and the\n hover frame (dashed border). Sits below both frames so their borders stay\n crisp; click-through like the frames. */\n .ec-overlay-prop {\n position: absolute !important; z-index: 1 !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 3px !important;\n background: rgba(91,140,255,0.16) !important;\n box-shadow: inset 0 0 0 1px rgba(91,140,255,0.45) !important;\n }\n .ec-overlay-bar {\n position: absolute !important; z-index: 3 !important; pointer-events: auto !important;\n display: flex !important; align-items: center !important; gap: 1px !important;\n height: 22px !important; max-width: 320px !important; padding: 0 2px 0 6px !important;\n border-radius: 6px !important; background: #5b8cff !important;\n box-shadow: 0 2px 8px rgba(8,12,24,0.35) !important;\n font: 600 11px/1 ui-sans-serif, system-ui, sans-serif !important;\n color: #ffffff !important; white-space: nowrap !important;\n }\n .ec-overlay-label {\n overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important;\n max-width: 180px !important; padding-right: 2px !important;\n font: inherit !important; color: #ffffff !important;\n }\n .ec-overlay-sep {\n width: 1px !important; height: 12px !important; margin: 0 3px !important;\n background: rgba(255,255,255,0.4) !important;\n }\n .ec-overlay-btn {\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 18px !important; padding: 0 !important; margin: 0 !important;\n border: 0 !important; border-radius: 4px !important; background: transparent !important;\n color: #ffffff !important; cursor: pointer !important; -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-btn:hover { background: rgba(255,255,255,0.22) !important; }\n .ec-overlay-btn:disabled { opacity: 0.4 !important; cursor: default !important; }\n\n /* ── Insertion affordances (EC-405) ─────────────────────────────────────\n A \"+\" button centered ON a block boundary (before/after edge, or the center\n of an empty container) — the geometry is inline (the anchor point), the\n translate here centers the button on it. Appears only on hover/selection\n (never during a drag or inline edit); pointer-events re-enabled so it is\n clickable while the overlay root stays click-through. Element-matching\n declarations !important (contract item 3): it is a <button> a site theme's\n element rules would otherwise restyle. */\n .ec-overlay-add {\n position: absolute !important; z-index: 4 !important; pointer-events: auto !important;\n transform: translate(-50%, -50%) !important;\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 20px !important; padding: 0 !important; margin: 0 !important;\n border: 1.5px solid #ffffff !important; border-radius: 999px !important;\n background: #5b8cff !important; color: #ffffff !important; cursor: pointer !important;\n box-shadow: 0 1px 5px rgba(8,12,24,0.4) !important;\n -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-add:hover { background: #3f74f5 !important; }\n\n /* ── Inline editing (EC-158 text, EC-159 rich text) ─────────────────────\n The rendered element itself becomes the editor — no overlay, no\n replacement input — so typography, alignment, and layout are\n pixel-identical between edit and render states. The wrapper keeps the\n selection outline; the host only gets a caret. pre-wrap applies to PLAIN\n text hosts only — rich-text markup must keep its rendered whitespace\n handling (no layout shift). */\n .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff !important; }\n .ec-editable [data-ec-edit] { outline: none; cursor: text; }\n .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }\n /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,\n images, embeds): visible and rendered, but not text-editable. */\n .ec-editable [data-ec-edit='rich'] [${PRESERVE_NODE_ATTR}] { user-select: none; }\n\n /* ── Floating mark toolbar (EC-159), portaled into the iframe ───────────\n All visual declarations !important (contract item 3): the toolbar is\n stylesheet-styled chrome whose button/input elements a site theme's\n element rules would otherwise match. */\n .ec-rt-toolbar { color-scheme: dark !important; }\n .ec-rt-toolbar-row { display: flex !important; align-items: center !important; gap: 2px !important;\n padding: 3px !important; margin: 0 !important;\n border: 1px solid rgba(130,150,190,0.4) !important; border-radius: 8px !important;\n background: rgb(24,26,32) !important; box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; }\n .ec-rt-toolbar-btn { min-width: 26px !important; height: 24px !important; padding: 0 7px !important;\n border: 0 !important; border-radius: 5px !important;\n background: transparent !important; color: rgb(225,228,236) !important;\n font: 600 12px/1 ui-sans-serif, system-ui, sans-serif !important;\n cursor: pointer !important; }\n .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25) !important; }\n .ec-rt-toolbar-btn[data-active] { background: #5b8cff !important; color: white !important; }\n .ec-rt-toolbar-input { width: 200px !important; height: 24px !important; padding: 0 7px !important;\n border-radius: 5px !important;\n border: 1px solid rgba(130,150,190,0.4) !important; background: rgb(16,18,22) !important;\n color: rgb(225,228,236) !important;\n font: 12px/1 ui-sans-serif, system-ui, sans-serif !important; outline: none !important; }\n\n /* ── Drag & drop states (EC-160/EC-162) ─────────────────────────────────\n Eligibility comes from the shared pure rules (lib/dnd/drop-rules); these\n classes only *show* the resolution. Rejection is explicit and visible\n during the drag — never a silent failed drop. Before/after bars follow the\n parent's flow axis (data-ec-drop-axis); left/right are the\n wrap-into-columns gesture (violet, paired highlight). Indicator\n declarations !important (contract item 3). */\n .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399 !important; outline-offset: -2px !important; background-color: rgba(52, 211, 153, 0.08) !important; }\n .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444 !important; outline-offset: -2px !important; background-color: rgba(239, 68, 68, 0.08) !important; cursor: not-allowed !important; }\n`\n\n/**\n * The complete chrome stylesheet for a world — the `ec-outline-css` element's\n * content (id kept from EC-389; the cascade position is pinned by test).\n */\nexport function canvasChromeCss(world: CanvasWorld): string {\n return (world === 'legacy' ? LEGACY_BACKDROP_CSS : NEW_WORLD_BACKDROP_CSS) + CHROME_UI_CSS\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { RefObject } from 'react';
2
- import type { ComponentNode, LayoutContainerIds } 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';
2
+ import type { ComponentNode, LayoutContainerIds, PlacementScope } from '@elytracms/core/project-graph';
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
  /**
@@ -32,11 +32,11 @@ export interface CanvasDndDeps {
32
32
  /** Manifest lookup feeding the pure drop rules. */
33
33
  getManifest(componentId: string): ManifestRules | undefined;
34
34
  /**
35
- * The composition field's top-level `allow` (EC-554; `undefined` = the field
36
- * declares none) — judged by `resolveDrop` for drops whose parent is the root.
37
- * A required key: the wiring must state it.
35
+ * EC-584: the placement scope `resolveDrop` judges by — the manifest lookup and
36
+ * the composition field's effective top-level vocabulary (`builder.placement`,
37
+ * the scope the reducer enforces). A required key: the wiring must state it.
38
38
  */
39
- fieldAllow: readonly string[] | undefined;
39
+ placement: PlacementScope;
40
40
  /**
41
41
  * The layout-role component ids (EC-591, `ComponentRegistry.layout`): the flow
42
42
  * axis of a Stack/Grid parent, and the Grid the wrap gesture builds — whose side
@@ -68,6 +68,6 @@ export declare function useCanvasDnd(doc: Document | null, deps: {
68
68
  studio: StudioRegistry;
69
69
  /** The live inline-edit session (owned by `useCanvasInlineEditing`). */
70
70
  editingRef: RefObject<ActiveInlineEdit | null>;
71
- /** The composition field's top-level `allow` (EC-554) — `builder.field.allow`. */
72
- fieldAllow: readonly string[] | undefined;
71
+ /** EC-584: the store's placement scope (`builder.placement`). */
72
+ placement: PlacementScope;
73
73
  }): void;
@@ -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
  /**
@@ -8,7 +8,7 @@ import { wrapperAt } from './canvas-dom.js';
8
8
  * React's synthetic events don't cross frames). Returns the detach function.
9
9
  */
10
10
  export function attachCanvasDndListeners(doc, deps) {
11
- const { currentRoot, getManifest, fieldAllow, layout, dispatch } = deps;
11
+ const { currentRoot, getManifest, placement, layout, dispatch } = deps;
12
12
  let dropWrapper = null;
13
13
  let dropPlan = null;
14
14
  const clearIndicator = () => {
@@ -108,7 +108,7 @@ export function attachCanvasDndListeners(doc, deps) {
108
108
  root,
109
109
  getManifest,
110
110
  layout,
111
- fieldAllow,
111
+ placement,
112
112
  subject: drag.subject,
113
113
  location: { nodeId: refId, position },
114
114
  });
@@ -195,7 +195,7 @@ export function attachCanvasDndListeners(doc, deps) {
195
195
  * session without re-subscribing on every edit.
196
196
  */
197
197
  export function useCanvasDnd(doc, deps) {
198
- const { presentRef, dispatch, studio, editingRef, fieldAllow } = deps;
198
+ const { presentRef, dispatch, studio, editingRef, placement } = deps;
199
199
  useEffect(() => {
200
200
  if (!doc)
201
201
  return;
@@ -204,11 +204,11 @@ export function useCanvasDnd(doc, deps) {
204
204
  // EC-386: adapt through `manifestRules` so blocks-prop regions (e.g. a
205
205
  // Section's `body`) receive drops with their `allow` leash, like raw slots.
206
206
  getManifest: (id) => manifestRules(studio.registry.getManifest(id)),
207
- fieldAllow,
207
+ placement,
208
208
  layout: studio.registry.layout,
209
209
  dispatch,
210
210
  isEditingTarget: (target) => editingRef.current?.host.contains(target) ?? false,
211
211
  });
212
- }, [doc, dispatch, studio, presentRef, editingRef, fieldAllow]);
212
+ }, [doc, dispatch, studio, presentRef, editingRef, placement]);
213
213
  }
214
214
  //# sourceMappingURL=canvas-dnd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-dnd.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-dnd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAEtE,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,WAAW,GACZ,MAAM,sCAAsC,CAAA;AAI7C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjF,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAmDxC;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,GAAsB,EAAE,IAAmB;IAClF,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAA;IAEvE,IAAI,WAAW,GAAuB,IAAI,CAAA;IAC1C,IAAI,QAAQ,GAAoB,IAAI,CAAA;IACpC,MAAM,cAAc,GAAG,GAAG,EAAE;QAC1B,WAAW,EAAE,eAAe,CAAC,cAAc,CAAC,CAAA;QAC5C,WAAW,EAAE,eAAe,CAAC,mBAAmB,CAAC,CAAA;QACjD,WAAW,GAAG,IAAI,CAAA;QAClB,QAAQ,GAAG,IAAI,CAAA;IACjB,CAAC,CAAA;IACD,MAAM,aAAa,GAAG,CAAC,OAA2B,EAAE,KAAoB,EAAE,IAAc,EAAE,EAAE;QAC1F,IAAI,WAAW,IAAI,WAAW,KAAK,OAAO,EAAE,CAAC;YAC3C,WAAW,CAAC,eAAe,CAAC,cAAc,CAAC,CAAA;YAC3C,WAAW,CAAC,eAAe,CAAC,mBAAmB,CAAC,CAAA;QAClD,CAAC;QACD,WAAW,GAAG,OAAO,CAAA;QACrB,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,CAAC,YAAY,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;YAC3C,kEAAkE;YAClE,OAAO,CAAC,YAAY,CAAC,mBAAmB,EAAE,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QAC9E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,eAAe,CAAC,cAAc,CAAC,CAAA;YACvC,OAAO,CAAC,eAAe,CAAC,mBAAmB,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC,CAAA;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,qEAAqE;IACrE,2CAA2C;IAC3C,MAAM,WAAW,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC/B,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QACnC,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAA;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAA;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAC/C,IAAI,IAAI,IAAI,MAAM,IAAI,MAAM,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtD,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;QAC3C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,eAAe,CAAC,WAAW,CAAC,CAAA;QACtC,CAAC;IACH,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC/B,MAAM,KAAK,GAAG,CAAc,CAAA;QAC5B,gFAAgF;QAChF,IAAI,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC;YAAE,OAAM;QAC9C,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACvC,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAA;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAE,CAAA;QACvD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACrD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC;YACzC,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,OAAM;QACR,CAAC;QACD,SAAS,CACP,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,EAC/G,KAAK,CAAC,YAAY,CACnB,CAAA;IACH,CAAC,CAAA;IAED,MAAM,UAAU,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAG,CAAc,CAAA;QAC5B,MAAM,IAAI,GAAG,UAAU,EAAE,CAAA;QACzB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAA;QAC1B,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACvC,sEAAsE;QACtE,MAAM,KAAK,GAAG,OAAO,EAAE,YAAY,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,EAAE,CAAA;QACjE,MAAM,UAAU,GACd,OAAO,IAAK,GAAG,CAAC,aAAa,CAAC,qBAAqB,IAAI,CAAC,EAAE,IAAI,CAAwB,CAAA;QACxF,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QACpC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,cAAc,EAAE,CAAA;YAChB,OAAM;QACR,CAAC;QACD,IAAI,QAAQ,GAAiB,MAAM,CAAA;QACnC,IAAI,IAAI,GAAa,UAAU,CAAA;QAC/B,IAAI,OAAO,IAAI,KAAK,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAA;YAC5C,mEAAmE;YACnE,iEAAiE;YACjE,IAAI,GAAG,iBAAiB,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;YACpE,QAAQ,GAAG,oBAAoB,CAAC;gBAC9B,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG;gBAClE,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG;gBACnE,IAAI;gBACJ,QAAQ,EAAE,WAAW,CAAC,WAAW,EAAE,OAAO,CAAC,WAAW,CAAC;gBACvD,KAAK,EAAE,IAAI;gBACX,6EAA6E;gBAC7E,SAAS,EAAE,MAAM,CAAC,IAAI,KAAK,SAAS;aACrC,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,WAAW,CAAC;YAC7B,IAAI;YACJ,WAAW;YACX,MAAM;YACN,UAAU;YACV,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE;SACtC,CAAC,CAAA;QACF,aAAa,CAAC,UAAU,EAAE,gBAAgB,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAA;QACvE,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;YACxB,QAAQ,GAAG,UAAU,CAAA;YACrB,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;gBACvB,KAAK,CAAC,YAAY,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;YAChF,CAAC;QACH,CAAC;aAAM,CAAC;YACN,QAAQ,GAAG,IAAI,CAAA;QACjB,CAAC;IACH,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC1B,MAAM,KAAK,GAAG,CAAc,CAAA;QAC5B,MAAM,IAAI,GAAG,UAAU,EAAE,CAAA;QACzB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,IAAI,GAAG,QAAQ,CAAA;QACrB,cAAc,EAAE,CAAA;QAChB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,wEAAwE;QACxE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAClE,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAChC,gEAAgE;YAChE,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,KAAK,SAAS;gBACvB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;gBAChD,CAAC,CAAC,IAAI,CAAC,SAAS;oBACd,CAAC,CAAC,EAAE,IAAI,EAAE,WAAoB,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;oBACxD,CAAC,CAAC,IAAI,CAAA;YACZ,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,WAAW,CAAC,CAAA;gBACpB,OAAM;YACR,CAAC;YACD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;QACrF,CAAC;aAAM,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC1B,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QAC3E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,WAAW,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QACD,OAAO,CAAC,SAAS,CAAC,CAAA;IACpB,CAAC,CAAA;IAED,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,cAAc,EAAE,CAAA;QAChB,OAAO,CAAC,WAAW,CAAC,CAAA;IACtB,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC/B,gEAAgE;QAChE,IAAI,CAAE,CAAe,CAAC,aAAa;YAAE,cAAc,EAAE,CAAA;IACvD,CAAC,CAAA;IAED,uEAAuE;IACvE,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,CAAC,CAAA;IAE5D,GAAG,CAAC,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,CAAA;IACpD,GAAG,CAAC,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAC9C,GAAG,CAAC,gBAAgB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;IAC5C,GAAG,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,GAAG,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;IAC1C,GAAG,CAAC,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAC9C,OAAO,GAAG,EAAE;QACV,WAAW,EAAE,CAAA;QACb,cAAc,EAAE,CAAA;QAChB,GAAG,CAAC,mBAAmB,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,CAAA;QACvD,GAAG,CAAC,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;QACjD,GAAG,CAAC,mBAAmB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;QAC/C,GAAG,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACvC,GAAG,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QAC7C,GAAG,CAAC,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IACnD,CAAC,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAoB,EACpB,IASC;IAED,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI,CAAA;IACrE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,OAAO,wBAAwB,CAAC,GAAG,EAAE;YACnC,WAAW,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO;YACrC,uEAAuE;YACvE,4EAA4E;YAC5E,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YACnE,UAAU;YACV,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;YAC9B,QAAQ;YACR,eAAe,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAc,CAAC,IAAI,KAAK;SACxF,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAA;AACjE,CAAC","sourcesContent":["import { useEffect } from 'react'\nimport type { RefObject } from 'react'\nimport { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode, LayoutContainerIds } 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 * The composition field's top-level `allow` (EC-554; `undefined` = the field\n * declares none) — judged by `resolveDrop` for drops whose parent is the root.\n * A required key: the wiring must state it.\n */\n fieldAllow: readonly string[] | undefined\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, fieldAllow, 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 fieldAllow,\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 /** The composition field's top-level `allow` (EC-554) — `builder.field.allow`. */\n fieldAllow: readonly string[] | undefined\n },\n): void {\n const { presentRef, dispatch, studio, editingRef, fieldAllow } = 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 fieldAllow,\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, fieldAllow])\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":"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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 '@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":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAM7D;;;;;GAKG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAUlC;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAU,EAAE,IAA2C;IACnF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,SAAS,CAAA;IACvC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAA;IACpF,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;AAChE,CAAC;AAUD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAmB,EAAE,MAAc;IAClE,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAA;IACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IACrD,OAAO;QACL,MAAM,EAAE,KAAK;QACb,SAAS,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC;QACzB,WAAW,EAAE,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC;KAC9C,CAAA;AACH,CAAC;AAKD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAsB,EAAE,MAAc;IACvE,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ;YACX,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAA;QACnD,KAAK,UAAU;YACb,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAA;QAClD,KAAK,WAAW;YACd,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,CAAA;QAC1C,KAAK,QAAQ;YACX,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAA;IACzC,CAAC;AACH,CAAC;AAYD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,IAAU,EAAE,QAAwB,EAAE,IAAc;IAClF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;IACvC,IAAI,QAAQ,KAAK,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IACzD,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IACxF,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;AACvF,CAAC","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"]}