@templatical/editor 0.43.3 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/{AiChatSidebar-BAAypLEP.js → AiChatSidebar-BUR5Jshu.js} +8 -8
  2. package/dist/{AiFeatureMenu-CQcrDR-J.js → AiFeatureMenu-CgfLFHJw.js} +4 -4
  3. package/dist/{BlockIssueBadge-DYI7ic1j.js → BlockIssueBadge-CWZEwMXX.js} +2 -2
  4. package/dist/{BlockPreviewCanvas-x5W-Sdhg.js → BlockPreviewCanvas-2vVu55yN.js} +3 -3
  5. package/dist/{CloudHeaderExtras-DnLaX4ea.js → CloudHeaderExtras-BZ62q1aS.js} +2 -2
  6. package/dist/{CloudPanels-BHPG3d1o.js → CloudPanels-sv4aCKKE.js} +4 -4
  7. package/dist/{CollaboratorBar-DG6KroMt.js → CollaboratorBar-CSEFL7uC.js} +2 -2
  8. package/dist/{CommentsPanel-Bzk6VhJg.js → CommentsPanel-CYMHfFcn.js} +1 -1
  9. package/dist/{CommentsSidebar-CaRC3c1b.js → CommentsSidebar-uvQyCMUe.js} +10 -10
  10. package/dist/{CountdownToolbar-B-pAigUh.js → CountdownToolbar-C052mx2P.js} +3 -3
  11. package/dist/{DesignReferenceSidebar-DfuJyDBz.js → DesignReferenceSidebar-ZKJ7y7-G.js} +6 -6
  12. package/dist/{IssuesPanel-vWhcDIvb.js → IssuesPanel-DAlNiJX_.js} +5 -5
  13. package/dist/{LogicTagInsertButton-D2Fjo444.js → LogicTagInsertButton-DMUfVsPr.js} +6 -6
  14. package/dist/{MediaEditModal-BuP8SJrv.js → MediaEditModal-Cmd9tQXZ.js} +1 -1
  15. package/dist/{MediaPanels-BkcF0r_r.js → MediaPanels-DoBhG5gg.js} +1 -1
  16. package/dist/{MergeTagInput-fEOOY7OU.js → MergeTagInput-DoQWMSW4.js} +1 -1
  17. package/dist/{MergeTagModeToggle-BOeEL52u.js → MergeTagModeToggle-DOHixe4G.js} +4 -4
  18. package/dist/{ParagraphEditor-BlJGeBRY.js → ParagraphEditor-C_p6yKLK.js} +10 -10
  19. package/dist/{RestoreVersionDialog-DYaoFk5G.js → RestoreVersionDialog-CKHeCPG2.js} +3 -3
  20. package/dist/{RichTextEditorContent-GacK8qwu.js → RichTextEditorContent-BedBVHXS.js} +4 -4
  21. package/dist/{SaveBlockDialog-DR1UCtQY.js → SaveBlockDialog-q3_fjb9t.js} +8 -8
  22. package/dist/{SavedBlocksBrowserModal-q_-qYW9a.js → SavedBlocksBrowserModal-C4J-_cW8.js} +9 -9
  23. package/dist/{SavedBlocksPanels-nTH8NTAT.js → SavedBlocksPanels-DBoqC6df.js} +1 -1
  24. package/dist/{SavedBlocksPickBar-C7vHmc5c.js → SavedBlocksPickBar-Pq2bjpB_.js} +2 -2
  25. package/dist/{TemplateScoringPanel-D9jeQCYl.js → TemplateScoringPanel-DXsyp0_A.js} +11 -11
  26. package/dist/{TemplateSettings-ClfHwxUa.js → TemplateSettings-Bh10NFIN.js} +6 -6
  27. package/dist/{TestEmailModal-DEnvrv38.js → TestEmailModal-BYCKpH7g.js} +7 -7
  28. package/dist/{TestEmailPanel-Ca9t6KG7.js → TestEmailPanel-lUXAFsT5.js} +1 -1
  29. package/dist/{TitleEditor-D7mNs3s5.js → TitleEditor-CWPxXUp2.js} +6 -6
  30. package/dist/{ToggleSwitch-Z-rqEcig.js → ToggleSwitch-D7GLaT8s.js} +1 -1
  31. package/dist/{Toolbar-DUgyDBkz.js → Toolbar-CWxqqBHE.js} +21 -21
  32. package/dist/{TplModal-CYyFAScB.js → TplModal-D6lqrKls.js} +1 -1
  33. package/dist/{VersionHistoryMenu-D42tGKM6.js → VersionHistoryMenu-fLrx-Lvi.js} +5 -5
  34. package/dist/{VersionHistoryPanels-o0HGvQJX.js → VersionHistoryPanels-C3XhpQO_.js} +1 -1
  35. package/dist/{VersionPreviewBanner-D5RZtvUf.js → VersionPreviewBanner-CWuxq_bb.js} +1 -1
  36. package/dist/{WrapperBlock-B0_vSfSD.js → WrapperBlock-ox8TMS_p.js} +1 -1
  37. package/dist/{blockTypeIcons-CEk_GHmU.js → blockTypeIcons-DEvNVvBj.js} +2 -2
  38. package/dist/bundle-stats.json +5 -5
  39. package/dist/cdn/chunks/{AiChatSidebar-oFwacME4.js → AiChatSidebar-Cvp9beXR.js} +3 -3
  40. package/dist/cdn/chunks/{AiChatSidebar-oFwacME4.js.map → AiChatSidebar-Cvp9beXR.js.map} +1 -1
  41. package/dist/cdn/chunks/{AiFeatureMenu-BiHFJf9i.js → AiFeatureMenu-vOJtwdmT.js} +3 -3
  42. package/dist/cdn/chunks/{AiFeatureMenu-BiHFJf9i.js.map → AiFeatureMenu-vOJtwdmT.js.map} +1 -1
  43. package/dist/cdn/chunks/{BlockIssueBadge-CWlcDBFm.js → BlockIssueBadge-CKIrYROg.js} +2 -2
  44. package/dist/cdn/chunks/{BlockIssueBadge-CWlcDBFm.js.map → BlockIssueBadge-CKIrYROg.js.map} +1 -1
  45. package/dist/cdn/chunks/{BlockPreviewCanvas-CIwIA3zq.js → BlockPreviewCanvas-CB27Joyn.js} +4 -4
  46. package/dist/cdn/chunks/{BlockPreviewCanvas-CIwIA3zq.js.map → BlockPreviewCanvas-CB27Joyn.js.map} +1 -1
  47. package/dist/cdn/chunks/{CloudHeaderExtras-BgsjwWUQ.js → CloudHeaderExtras-9tKrhPm6.js} +3 -3
  48. package/dist/cdn/chunks/{CloudHeaderExtras-BgsjwWUQ.js.map → CloudHeaderExtras-9tKrhPm6.js.map} +1 -1
  49. package/dist/cdn/chunks/{CloudPanels-DFCwq8_J.js → CloudPanels-DTQ7zbEN.js} +4 -4
  50. package/dist/cdn/chunks/{CloudPanels-DFCwq8_J.js.map → CloudPanels-DTQ7zbEN.js.map} +1 -1
  51. package/dist/cdn/chunks/{CollaboratorBar-CiMZfMwN.js → CollaboratorBar-BKTkVMbz.js} +3 -3
  52. package/dist/cdn/chunks/{CollaboratorBar-CiMZfMwN.js.map → CollaboratorBar-BKTkVMbz.js.map} +1 -1
  53. package/dist/cdn/chunks/{CommentsPanel-CL4grbMp.js → CommentsPanel-BjHRj_Yw.js} +2 -2
  54. package/dist/cdn/chunks/{CommentsPanel-CL4grbMp.js.map → CommentsPanel-BjHRj_Yw.js.map} +1 -1
  55. package/dist/cdn/chunks/{CommentsSidebar-tWYYKv1s.js → CommentsSidebar-CpCfqSq4.js} +2 -2
  56. package/dist/cdn/chunks/{CommentsSidebar-tWYYKv1s.js.map → CommentsSidebar-CpCfqSq4.js.map} +1 -1
  57. package/dist/cdn/chunks/{CountdownToolbar-DfyqIppF.js → CountdownToolbar-C9QpHQcv.js} +4 -4
  58. package/dist/cdn/chunks/{CountdownToolbar-DfyqIppF.js.map → CountdownToolbar-C9QpHQcv.js.map} +1 -1
  59. package/dist/cdn/chunks/{DesignReferenceSidebar-C6O12hvt.js → DesignReferenceSidebar-BO0xz1wC.js} +3 -3
  60. package/dist/cdn/chunks/{DesignReferenceSidebar-C6O12hvt.js.map → DesignReferenceSidebar-BO0xz1wC.js.map} +1 -1
  61. package/dist/cdn/chunks/{IssuesPanel-DYJXIwnw.js → IssuesPanel--62qud8w.js} +2 -2
  62. package/dist/cdn/chunks/{IssuesPanel-DYJXIwnw.js.map → IssuesPanel--62qud8w.js.map} +1 -1
  63. package/dist/cdn/chunks/{LogicTagInsertButton-FNMVxm_V.js → LogicTagInsertButton-gc8ICURV.js} +5 -5
  64. package/dist/cdn/chunks/{LogicTagInsertButton-FNMVxm_V.js.map → LogicTagInsertButton-gc8ICURV.js.map} +1 -1
  65. package/dist/cdn/chunks/{MediaEditModal-C9yZ0AJv.js → MediaEditModal-BuDXKwx4.js} +2 -2
  66. package/dist/cdn/chunks/{MediaEditModal-C9yZ0AJv.js.map → MediaEditModal-BuDXKwx4.js.map} +1 -1
  67. package/dist/cdn/chunks/{MediaPanels-DiKU1r9P.js → MediaPanels-xFpaxFRH.js} +2 -2
  68. package/dist/cdn/chunks/{MediaPanels-DiKU1r9P.js.map → MediaPanels-xFpaxFRH.js.map} +1 -1
  69. package/dist/cdn/chunks/{MergeTagInput-CSC64xNL.js → MergeTagInput-DsPKWbWr.js} +2 -2
  70. package/dist/cdn/chunks/{MergeTagInput-CSC64xNL.js.map → MergeTagInput-DsPKWbWr.js.map} +1 -1
  71. package/dist/cdn/chunks/{MergeTagModeToggle-027RMXDP.js → MergeTagModeToggle-DU0WqgVH.js} +3 -3
  72. package/dist/cdn/chunks/{MergeTagModeToggle-027RMXDP.js.map → MergeTagModeToggle-DU0WqgVH.js.map} +1 -1
  73. package/dist/cdn/chunks/{ParagraphEditor-0Z26-N5u.js → ParagraphEditor-BSDGOsja.js} +10 -10
  74. package/dist/cdn/chunks/{ParagraphEditor-0Z26-N5u.js.map → ParagraphEditor-BSDGOsja.js.map} +1 -1
  75. package/dist/cdn/chunks/{RestoreVersionDialog-Dd1v1KLK.js → RestoreVersionDialog-DLe0Co57.js} +3 -3
  76. package/dist/cdn/chunks/{RestoreVersionDialog-Dd1v1KLK.js.map → RestoreVersionDialog-DLe0Co57.js.map} +1 -1
  77. package/dist/cdn/chunks/{RichTextEditorContent-BOZ_5I76.js → RichTextEditorContent-Dkcts8Vh.js} +4 -4
  78. package/dist/cdn/chunks/{RichTextEditorContent-BOZ_5I76.js.map → RichTextEditorContent-Dkcts8Vh.js.map} +1 -1
  79. package/dist/cdn/chunks/{SaveBlockDialog-B6WQwNJD.js → SaveBlockDialog-DQzaED8U.js} +7 -7
  80. package/dist/cdn/chunks/{SaveBlockDialog-B6WQwNJD.js.map → SaveBlockDialog-DQzaED8U.js.map} +1 -1
  81. package/dist/cdn/chunks/{SavedBlocksBrowserModal-C015fVMV.js → SavedBlocksBrowserModal-D3JF-Gx-.js} +6 -6
  82. package/dist/cdn/chunks/{SavedBlocksBrowserModal-C015fVMV.js.map → SavedBlocksBrowserModal-D3JF-Gx-.js.map} +1 -1
  83. package/dist/cdn/chunks/{SavedBlocksPanels-DZmfXW4x.js → SavedBlocksPanels-Cxbgby24.js} +2 -2
  84. package/dist/cdn/chunks/{SavedBlocksPanels-DZmfXW4x.js.map → SavedBlocksPanels-Cxbgby24.js.map} +1 -1
  85. package/dist/cdn/chunks/{SavedBlocksPickBar-DtCQFCCW.js → SavedBlocksPickBar-DEL0r3Bv.js} +3 -3
  86. package/dist/cdn/chunks/{SavedBlocksPickBar-DtCQFCCW.js.map → SavedBlocksPickBar-DEL0r3Bv.js.map} +1 -1
  87. package/dist/cdn/chunks/{TemplateScoringPanel-CeCVyNV1.js → TemplateScoringPanel-BBRHCNcR.js} +2 -2
  88. package/dist/cdn/chunks/{TemplateScoringPanel-CeCVyNV1.js.map → TemplateScoringPanel-BBRHCNcR.js.map} +1 -1
  89. package/dist/cdn/chunks/{TemplateSettings-CuXwthI6.js → TemplateSettings-vsFOU0oA.js} +5 -5
  90. package/dist/cdn/chunks/{TemplateSettings-CuXwthI6.js.map → TemplateSettings-vsFOU0oA.js.map} +1 -1
  91. package/dist/cdn/chunks/{TestEmailModal-DV1XKWRh.js → TestEmailModal-B7Dr2eFa.js} +7 -7
  92. package/dist/cdn/chunks/{TestEmailModal-DV1XKWRh.js.map → TestEmailModal-B7Dr2eFa.js.map} +1 -1
  93. package/dist/cdn/chunks/{TestEmailPanel-CnVW00RI.js → TestEmailPanel-C3PTYDbT.js} +2 -2
  94. package/dist/cdn/chunks/{TestEmailPanel-CnVW00RI.js.map → TestEmailPanel-C3PTYDbT.js.map} +1 -1
  95. package/dist/cdn/chunks/{TitleEditor-B9sKbRd4.js → TitleEditor-gHTla_4I.js} +5 -5
  96. package/dist/cdn/chunks/{TitleEditor-B9sKbRd4.js.map → TitleEditor-gHTla_4I.js.map} +1 -1
  97. package/dist/cdn/chunks/{ToggleSwitch-eHkFBx4o.js → ToggleSwitch-DoUNcLPP.js} +2 -2
  98. package/dist/cdn/chunks/{ToggleSwitch-eHkFBx4o.js.map → ToggleSwitch-DoUNcLPP.js.map} +1 -1
  99. package/dist/cdn/chunks/{Toolbar-DeKbYejp.js → Toolbar-CnfNUkOY.js} +9 -9
  100. package/dist/cdn/chunks/{Toolbar-DeKbYejp.js.map → Toolbar-CnfNUkOY.js.map} +1 -1
  101. package/dist/cdn/chunks/{TplModal-Dqq-Ermd.js → TplModal-BVUBOEQL.js} +2 -2
  102. package/dist/cdn/chunks/{TplModal-Dqq-Ermd.js.map → TplModal-BVUBOEQL.js.map} +1 -1
  103. package/dist/cdn/chunks/{VersionHistoryMenu-Kf4QFm7m.js → VersionHistoryMenu-Cp3EHO_P.js} +2 -2
  104. package/dist/cdn/chunks/{VersionHistoryMenu-Kf4QFm7m.js.map → VersionHistoryMenu-Cp3EHO_P.js.map} +1 -1
  105. package/dist/cdn/chunks/{VersionHistoryPanels-Cz8rde-s.js → VersionHistoryPanels-Dcdy7ySA.js} +2 -2
  106. package/dist/cdn/chunks/{VersionHistoryPanels-Cz8rde-s.js.map → VersionHistoryPanels-Dcdy7ySA.js.map} +1 -1
  107. package/dist/cdn/chunks/{VersionPreviewBanner-DNYHqbj9.js → VersionPreviewBanner-BgkroXnG.js} +2 -2
  108. package/dist/cdn/chunks/{VersionPreviewBanner-DNYHqbj9.js.map → VersionPreviewBanner-BgkroXnG.js.map} +1 -1
  109. package/dist/cdn/chunks/{WrapperBlock-DIZHUwFn.js → WrapperBlock-23nAzweL.js} +2 -2
  110. package/dist/cdn/chunks/{WrapperBlock-DIZHUwFn.js.map → WrapperBlock-23nAzweL.js.map} +1 -1
  111. package/dist/cdn/chunks/{blockTypeIcons-hgzxMFWX.js → blockTypeIcons-BMZi7sdX.js} +2 -2
  112. package/dist/cdn/chunks/{blockTypeIcons-hgzxMFWX.js.map → blockTypeIcons-BMZi7sdX.js.map} +1 -1
  113. package/dist/cdn/chunks/{cloud-De-eh1u6.js → cloud-DLQU5l0Z.js} +2 -2
  114. package/dist/cdn/chunks/{cloud-De-eh1u6.js.map → cloud-DLQU5l0Z.js.map} +1 -1
  115. package/dist/cdn/chunks/{createCloudRuntime-DsaUaWvr.js → createCloudRuntime-Mnx8kV2W.js} +3 -3
  116. package/dist/cdn/chunks/{createCloudRuntime-DsaUaWvr.js.map → createCloudRuntime-Mnx8kV2W.js.map} +1 -1
  117. package/dist/cdn/chunks/{editor-modal-cKeJ4wC2.js → editor-modal-B7NNAFI5.js} +4 -4
  118. package/dist/cdn/chunks/{editor-modal-cKeJ4wC2.js.map → editor-modal-B7NNAFI5.js.map} +1 -1
  119. package/dist/cdn/chunks/en-C3n0qWte.js +2 -0
  120. package/dist/cdn/chunks/{extensions-rPP7qRRm.js → extensions-ClmkNA8R.js} +4 -4
  121. package/dist/cdn/chunks/{extensions-rPP7qRRm.js.map → extensions-ClmkNA8R.js.map} +1 -1
  122. package/dist/cdn/chunks/{icons-BF0mJX0V.js → icons-BjvraR-Y.js} +25 -25
  123. package/dist/cdn/chunks/{icons-BF0mJX0V.js.map → icons-BjvraR-Y.js.map} +1 -1
  124. package/dist/cdn/chunks/{preRenderCustomBlocks-h4ZFBKbD.js → preRenderCustomBlocks-Bg3rIgGJ.js} +2 -2
  125. package/dist/cdn/chunks/{preRenderCustomBlocks-h4ZFBKbD.js.map → preRenderCustomBlocks-Bg3rIgGJ.js.map} +1 -1
  126. package/dist/cdn/chunks/{socialIcons-ZOCwCCyn.js → socialIcons-CnUJBZTO.js} +2 -2
  127. package/dist/cdn/chunks/socialIcons-CnUJBZTO.js.map +1 -0
  128. package/dist/cdn/chunks/{src-fZSLjhjy.js → src-B5-VKcRQ.js} +2 -2
  129. package/dist/cdn/chunks/{src-fZSLjhjy.js.map → src-B5-VKcRQ.js.map} +1 -1
  130. package/dist/cdn/chunks/{src-V9_ODH_1.js → src-BmKDV_BK.js} +3 -3
  131. package/dist/cdn/chunks/{src-V9_ODH_1.js.map → src-BmKDV_BK.js.map} +1 -1
  132. package/dist/cdn/chunks/{src-BR3I3gOW.js → src-QIGNPFkl.js} +6 -8
  133. package/dist/cdn/chunks/src-QIGNPFkl.js.map +1 -0
  134. package/dist/cdn/chunks/{useEditorCore-DWvAia8X.js → useEditorCore-DjD7fJpq.js} +10 -10
  135. package/dist/cdn/chunks/{useEditorCore-DWvAia8X.js.map → useEditorCore-DjD7fJpq.js.map} +1 -1
  136. package/dist/cdn/chunks/{useLogicTag-J_i_hvZg.js → useLogicTag-D6lWY0bD.js} +2 -2
  137. package/dist/cdn/chunks/{useLogicTag-J_i_hvZg.js.map → useLogicTag-D6lWY0bD.js.map} +1 -1
  138. package/dist/cdn/chunks/{useMergeTag-C5MyHYxJ.js → useMergeTag-2iW-BKWW.js} +2 -2
  139. package/dist/cdn/chunks/{useMergeTag-C5MyHYxJ.js.map → useMergeTag-2iW-BKWW.js.map} +1 -1
  140. package/dist/cdn/editor.js +239 -234
  141. package/dist/cdn/editor.js.map +1 -1
  142. package/dist/{check-DEjsanPt.js → check-DNyj-E0y.js} +1 -1
  143. package/dist/{chevron-down-Cv11TeKQ.js → chevron-down-BYnhkMsA.js} +1 -1
  144. package/dist/{chevron-right-R8dOpzBZ.js → chevron-right-D9HGBqJo.js} +1 -1
  145. package/dist/{chevron-up-DsDEytX1.js → chevron-up-BnIR1-ma.js} +1 -1
  146. package/dist/{circle-alert-B_NqWETg.js → circle-alert-B6xl1B_M.js} +1 -1
  147. package/dist/{clock-CDGKPx_X.js → clock-Cs5Wd3dW.js} +1 -1
  148. package/dist/{cloud-AO_X8T9g.js → cloud-BNEL7bxL.js} +1 -1
  149. package/dist/{copy-Dv2VltLv.js → copy-6uKc-WBk.js} +1 -1
  150. package/dist/{createCloudRuntime-D641nhri.js → createCloudRuntime-CaZapQZ6.js} +2 -2
  151. package/dist/{createLucideIcon-MrFrvtkF.js → createLucideIcon-BVcoovvP.js} +5 -5
  152. package/dist/{dist-iLUVPysa.js → dist-HjILVdTA.js} +5 -7
  153. package/dist/{editor-modal-DmgxKgII.js → editor-modal-Co-rZGaf.js} +16 -16
  154. package/dist/en-B-xoKkWi.js +2 -0
  155. package/dist/{extensions-CkwL1oWn.js → extensions-DXSI8Dyj.js} +3 -3
  156. package/dist/{eye-BVxcDaZZ.js → eye-Nbtw_q05.js} +1 -1
  157. package/dist/{image-up-CnC4n-8q.js → image-up-C1jC7NzQ.js} +1 -1
  158. package/dist/index.d.ts +2427 -373
  159. package/dist/{info-q7gVjMBC.js → info-ClH66pKN.js} +1 -1
  160. package/dist/{link-B1-UqOP6.js → link-y47GNW0o.js} +1 -1
  161. package/dist/{list-BvlNrd6M.js → list-DiLMlMaE.js} +1 -1
  162. package/dist/{loader-circle-CJ8klGfl.js → loader-circle-BRdCPW5h.js} +1 -1
  163. package/dist/{message-circle-Bgepipq_.js → message-circle-CNgIpVJv.js} +1 -1
  164. package/dist/{package-BYIdon2Q.js → package-B9OWk-Kp.js} +1 -1
  165. package/dist/{pencil-Bhs9MNTT.js → pencil-BDnjo24u.js} +1 -1
  166. package/dist/{plus-C0eGj8vT.js → plus-BMgjN7lI.js} +1 -1
  167. package/dist/{preRenderCustomBlocks-CTstI3Q-.js → preRenderCustomBlocks-C_PIxBp7.js} +1 -1
  168. package/dist/{refresh-cw-BQTOIZKD.js → refresh-cw-CR5lenxl.js} +1 -1
  169. package/dist/{search-CeT7yRBa.js → search-CxRUiOSF.js} +1 -1
  170. package/dist/{send-D7ck2xEq.js → send-Bu2RysGL.js} +1 -1
  171. package/dist/{shield-check-CF_VbIbH.js → shield-check-Cg8f4LB0.js} +1 -1
  172. package/dist/{smartphone-CkVOj7Qn.js → smartphone-CHf-8lHp.js} +1 -1
  173. package/dist/{socialIcons-QYtdurnO.js → socialIcons-D1gkHc_i.js} +2 -2
  174. package/dist/{sparkles-Cfb-Kkgv.js → sparkles-_DQGv4ub.js} +1 -1
  175. package/dist/templatical-editor.js +252 -247
  176. package/dist/{text-align-start-CkFs2Zig.js → text-align-start-PHo-Ep5i.js} +1 -1
  177. package/dist/{trash-C2LPw5pa.js → trash-BxIzHAri.js} +1 -1
  178. package/dist/{triangle-alert-gaUz4rum.js → triangle-alert-fcb2KoA-.js} +1 -1
  179. package/dist/{upload-C_DYZD_p.js → upload-BxBtMPMv.js} +1 -1
  180. package/dist/{useEditorCore-Cmv5ll-D.js → useEditorCore-iuKC8yr5.js} +14 -14
  181. package/dist/{useLogicTag-BaH8gey0.js → useLogicTag-wqfdwYYR.js} +1 -1
  182. package/dist/{useMergeTag-Cyad1yBE.js → useMergeTag-D3XDE_58.js} +1 -1
  183. package/dist/{useScrollToBlock-BsPYYJ8h.js → useScrollToBlock-DA_aIJXS.js} +1 -1
  184. package/dist/{x-JhKma5YO.js → x-DrZO5tt0.js} +1 -1
  185. package/package.json +13 -9
  186. package/dist/cdn/chunks/en-CoNe1psX.js +0 -2
  187. package/dist/cdn/chunks/socialIcons-ZOCwCCyn.js.map +0 -1
  188. package/dist/cdn/chunks/src-BR3I3gOW.js.map +0 -1
  189. package/dist/en-DXYNty6E.js +0 -2
package/dist/index.d.ts CHANGED
@@ -1,279 +1,704 @@
1
- import type { AiConfig } from '@templatical/types';
2
- import { applyLayout } from '@templatical/types';
3
- import { BlockDefaults } from '@templatical/types';
4
- import type { CollaborationConfig } from '@templatical/types';
5
- import { ColorsConfig } from '@templatical/types';
6
- import { CommentEventMeta } from '@templatical/types';
7
- import { CommentsOptions } from '@templatical/types';
8
- import type { CommentsProvider } from '@templatical/types';
9
- import { ComputedRef } from 'vue';
10
- import { createDefaultTemplateContent } from '@templatical/types';
11
- import { createLocalStorageMediaProvider } from '@templatical/core';
12
- import { createLocalStorageSavedBlocksProvider } from '@templatical/core';
13
- import { createParagraphBlock } from '@templatical/types';
14
- import { createSlotBlock } from '@templatical/types';
15
- import { createWrapperBlock } from '@templatical/types';
16
- import type { CustomBlock } from '@templatical/types';
17
- import { CustomBlockDefinition } from '@templatical/types';
18
- import { CustomFont } from '@templatical/types';
19
- import { DisplayConditionsConfig } from '@templatical/types';
20
- import type { EditorUser } from '@templatical/types';
21
- import { FontsConfig } from '@templatical/types';
22
- import { isSlot } from '@templatical/types';
23
- import { isWrapper } from '@templatical/types';
24
- import { layoutWrapsSlot } from '@templatical/types';
25
- import { LintOptions } from '@templatical/quality';
26
- import { LocalStorageMediaProviderOptions } from '@templatical/core';
27
- import { LocalStorageSavedBlocksOptions } from '@templatical/core';
28
- import { LogicPair } from '@templatical/types';
29
- import { LogicTag } from '@templatical/types';
30
- import { LogicTagsConfig } from '@templatical/types';
31
- import type { McpConfig } from '@templatical/types';
32
- import { MediaAsset } from '@templatical/types';
33
- import { MediaOptions } from '@templatical/types';
34
- import { MediaProvider as MediaProvider_2 } from '@templatical/types';
35
- import { MediaRequestContext } from '@templatical/types';
36
- import type { MediaResult } from '@templatical/types';
37
- import type { MergeTag } from '@templatical/types';
38
- import { MergeTagsConfig } from '@templatical/types';
39
- import { Ref } from 'vue';
40
- import { RenderPayload } from '@templatical/types';
41
- import { RenderProvider } from '@templatical/types';
42
- import type { ResolvePreview } from '@templatical/types';
43
- import { SavedBlock } from '@templatical/types';
44
- import { SavedBlocksListParams } from '@templatical/types';
45
- import { SavedBlocksOptions } from '@templatical/types';
46
- import { SavedBlocksProvider } from '@templatical/types';
47
- import { Template } from '@templatical/types';
48
- import { TemplateContent } from '@templatical/types';
49
- import { TemplateDefaults } from '@templatical/types';
50
- import { TemplatePatch } from '@templatical/types';
51
- import { TemplateSaveTrigger } from '@templatical/types';
52
- import { TemplateSettingsConfig } from '@templatical/types';
53
- import { TemplatesOptions } from '@templatical/types';
54
- import { TemplatesProvider } from '@templatical/types';
55
- import { TestEmailOptions } from '@templatical/types';
56
- import { TestEmailPayload } from '@templatical/types';
57
- import { TestEmailProvider } from '@templatical/types';
58
- import { ThemeOverrides } from '@templatical/types';
59
- import { UiTheme } from '@templatical/types';
60
- import { validateLayout } from '@templatical/types';
61
- import { VersionHistoryOptions } from '@templatical/types';
62
- import type { VersionHistoryProvider } from '@templatical/types';
63
- import { ViewportSize } from '@templatical/types';
64
-
65
- export { applyLayout }
66
-
67
- export { BlockDefaults }
68
-
69
- export { ColorsConfig }
70
-
71
- export { CommentEventMeta }
72
-
73
- export { CommentsOptions }
74
-
75
- export { createDefaultTemplateContent }
76
-
77
- export { createLocalStorageMediaProvider }
78
-
79
- export { createLocalStorageSavedBlocksProvider }
80
-
81
- export { createParagraphBlock }
82
-
83
- export { createSlotBlock }
84
-
85
- export { createWrapperBlock }
86
-
87
- export { CustomBlockDefinition }
88
-
89
- export { CustomFont }
90
-
91
- export { DisplayConditionsConfig }
92
-
93
- export declare interface EditorCapabilities {
94
- plan?: {
95
- hasFeature(feature: string): boolean;
96
- };
97
- ai?: {
98
- isFeatureEnabled(feature: string): boolean;
1
+ /** Options consumed only by the accessibility linter. */
2
+ declare interface AccessibilityLintOptions {
3
+ rules?: RuleOverrides;
4
+ thresholds?: Partial<LintThresholds>;
5
+ }
6
+
7
+ declare interface AiConfig {
8
+ chat?: boolean;
9
+ scoring?: boolean;
10
+ designToTemplate?: boolean;
11
+ rewrite?: boolean;
12
+ }
13
+
14
+ /**
15
+ * Overlay `layout` around `content`. Clones both; remints layout ids.
16
+ * Layout wins only `settings.backgroundColor`.
17
+ */
18
+ export declare function applyLayout(layout: TemplateContent, content: TemplateContent): TemplateContent;
19
+
20
+ declare interface BaseBlock {
21
+ id: string;
22
+ type: string;
23
+ styles: BlockStyles;
24
+ visibility?: BlockVisibility;
25
+ displayCondition?: {
26
+ label: string;
27
+ before: string;
28
+ after: string;
29
+ group?: string;
30
+ description?: string;
99
31
  };
32
+ }
33
+
34
+ declare type Block = SectionBlock | TitleBlock | ParagraphBlock | ImageBlock | ButtonBlock | DividerBlock | VideoBlock | SocialIconsBlock | SpacerBlock | HtmlBlock | MenuBlock | TableBlock | CountdownBlock | CustomBlock | SlotBlock | WrapperBlock;
35
+
36
+ export declare interface BlockDefaults {
37
+ title?: BlockDefaultsFor<TitleBlock>;
38
+ paragraph?: BlockDefaultsFor<ParagraphBlock>;
39
+ image?: BlockDefaultsFor<ImageBlock>;
40
+ button?: BlockDefaultsFor<ButtonBlock>;
41
+ divider?: BlockDefaultsFor<DividerBlock>;
42
+ section?: BlockDefaultsFor<SectionBlock>;
43
+ video?: BlockDefaultsFor<VideoBlock>;
44
+ social?: BlockDefaultsFor<SocialIconsBlock>;
45
+ spacer?: BlockDefaultsFor<SpacerBlock>;
46
+ html?: BlockDefaultsFor<HtmlBlock>;
47
+ menu?: BlockDefaultsFor<MenuBlock>;
48
+ table?: BlockDefaultsFor<TableBlock>;
49
+ countdown?: BlockDefaultsFor<CountdownBlock>;
50
+ }
51
+
52
+ declare type BlockDefaultsFor<T> = Partial<Omit<T, "id" | "type">>;
53
+
54
+ declare interface BlockStyles {
55
+ padding: SpacingValue;
56
+ backgroundColor?: string;
57
+ }
58
+
59
+ declare interface BlockVisibility {
60
+ desktop: boolean;
61
+ mobile: boolean;
62
+ }
63
+
64
+ declare type BorderCorner = "topLeft" | "topRight" | "bottomRight" | "bottomLeft";
65
+
66
+ /**
67
+ * A corner radius in px: one number for all four corners, or a radius per
68
+ * corner.
69
+ */
70
+ declare type BorderRadiusValue = number | CornerRadius;
71
+
72
+ /** One side of a border. A width of `0` leaves that side undrawn. */
73
+ declare interface BorderSideValue {
74
+ /** Width in px. `0` means no border on this side. */
75
+ width: number;
76
+ style: BorderStyle;
77
+ color: string;
78
+ }
79
+
80
+ declare type BorderStyle = "solid" | "dashed" | "dotted";
81
+
82
+ /**
83
+ * A border described per side, like `SpacingValue`. Only elements MJML can
84
+ * border natively carry one: sections, images and buttons.
85
+ */
86
+ declare interface BorderValue {
87
+ top: BorderSideValue;
88
+ right: BorderSideValue;
89
+ bottom: BorderSideValue;
90
+ left: BorderSideValue;
91
+ }
92
+
93
+ declare interface ButtonBlock extends BaseBlock {
94
+ type: "button";
95
+ text: string;
96
+ url: string;
97
+ openInNewTab?: boolean;
98
+ backgroundColor: string;
99
+ textColor: string;
100
+ /** Corner radius in px — one number, or a radius per corner. */
101
+ borderRadius: BorderRadiusValue;
100
102
  /**
101
- * Present only when a `CommentsProvider` **and** a `user` are configured — via
102
- * `init({ comments, user })`, or in Cloud whenever the plan grants `commenting`
103
- * (its `user` comes from the JWT).
104
- */
105
- comments?: {
106
- getBlockCount(blockId: string): number;
107
- openForBlock(blockId: string): void;
108
- /**
109
- * Whether the block exists in the **stored** template. A comment is anchored
110
- * server-side, so filtering to a block that only exists on the canvas has
111
- * nothing to show — the panel says so rather than rendering an empty list.
112
- *
113
- * Always `true` for a store that accepts any anchor; Cloud is what supplies a
114
- * real answer.
115
- */
116
- isBlockSaved(blockId: string): boolean;
117
- /**
118
- * Whether the feature is usable right now. Reactive for the same reason as
119
- * `savedBlocks.isAvailable`, plus one of its own: with no `user` there is
120
- * nobody to attribute a comment to, so the feature reports itself unavailable
121
- * rather than writing an anonymous one.
122
- */
123
- isAvailable: ComputedRef<boolean>;
124
- /** Open threads — the count badge on the header trigger. */
125
- unresolvedCount: ComputedRef<number>;
126
- /**
127
- * Which mutations the provider supplied — `false` instead of a function
128
- * withholds one. Shared UI hides the corresponding affordance: with all four
129
- * false the review is read-only (threads readable, jump-to-block working, no
130
- * way to add, edit, delete or resolve).
131
- */
132
- canCreate: ComputedRef<boolean>;
133
- canUpdate: ComputedRef<boolean>;
134
- canDelete: ComputedRef<boolean>;
135
- canResolve: ComputedRef<boolean>;
136
- };
103
+ * Border around the button itself, per side. Omitted = no border; a side
104
+ * with width 0 is not drawn. For an outline ("ghost") button, set
105
+ * `backgroundColor` to the keyword `"transparent"` (the background control
106
+ * stores it) and set `textColor` too — a new button is `#333333` with white
107
+ * text. An empty fill exports as `transparent`, because MJML paints
108
+ * `#414141` when the attribute is omitted.
109
+ */
110
+ border?: BorderValue;
111
+ fontSize: number;
112
+ buttonPadding: SpacingValue;
113
+ fontFamily?: string;
114
+ width?: number | "full";
137
115
  /**
138
- * Present only when a `SavedBlocksProvider` is configured — in OSS via
139
- * `init({ savedBlocks })`, in Cloud whenever cloud mode is active.
140
- */
141
- savedBlocks?: {
142
- /**
143
- * Begin a canvas pick session seeded with this block. Blocks are then
144
- * chosen by plain clicks until the floating bar confirms or cancels —
145
- * `EditorState.selectedBlockId` is untouched throughout.
146
- */
147
- startPicking(blockId: string): void;
148
- togglePick(blockId: string): void;
149
- isPicked(blockId: string): boolean;
150
- /** True while a pick session is running; block chrome swaps behaviour. */
151
- isPicking: ComputedRef<boolean>;
152
- /** Exposed so the shared keyboard handler can drive Enter/Escape. */
153
- confirmPicking(): void;
154
- cancelPicking(): void;
155
- openBrowser(): void;
156
- /**
157
- * How many entries are loaded. Informational only — the sidebar rail is
158
- * gated on {@link isAvailable} alone, never on this, so a slow or empty
159
- * `list()` can't make the entry appear late or shift the rail.
160
- */
161
- count: ComputedRef<number>;
162
- /**
163
- * Whether the feature is usable right now. Reactive because Cloud only
164
- * learns its plan entitlement after an async config fetch, which happens
165
- * *after* capabilities are provided — so presence alone can't encode it.
166
- * UI must gate on this, or it will render controls that do nothing.
167
- */
168
- isAvailable: ComputedRef<boolean>;
169
- /**
170
- * Which mutations the provider supplied — a provider may pass `false`
171
- * instead of a function to withhold one. Shared UI hides the corresponding
172
- * affordance: with `canCreate` false there is no bookmark action and so no
173
- * pick session, leaving a browse-and-insert-only library.
174
- *
175
- * Separate from {@link isAvailable}, which answers whether the feature
176
- * exists at all.
177
- */
178
- canCreate: ComputedRef<boolean>;
179
- canUpdate: ComputedRef<boolean>;
180
- canDelete: ComputedRef<boolean>;
181
- };
116
+ * Placement of the button within its column. No visible effect when `width`
117
+ * is `"full"`, since the button then spans the column.
118
+ */
119
+ align: "left" | "center" | "right";
120
+ }
121
+
122
+ declare interface CollaborationConfig {
123
+ enabled: boolean;
124
+ onCollaboratorJoined?: (collaborator: Collaborator) => void;
125
+ onCollaboratorLeft?: (collaborator: Collaborator) => void;
126
+ onBlockLocked?: (event: {
127
+ blockId: string;
128
+ collaborator: Collaborator;
129
+ }) => void;
130
+ onBlockUnlocked?: (event: {
131
+ blockId: string;
132
+ collaborator: Collaborator;
133
+ }) => void;
134
+ }
135
+
136
+ declare interface Collaborator {
137
+ id: string;
138
+ name: string;
139
+ color: string;
140
+ selectedBlockId: string | null;
141
+ }
142
+
143
+ export declare interface ColorsConfig {
182
144
  /**
183
- * Present only when a `TemplatesProvider` is configured — in OSS via
184
- * `init({ templates })`.
185
- *
186
- * Everything the header needs to render the template's identity and its save
187
- * state, so the same chrome works over any storage backend.
188
- */
189
- templates?: {
190
- /**
191
- * Persist now. Fire-and-forget: the outcome lands in {@link status}. A no-op
192
- * while a save is running, without a loaded template, or when the provider
193
- * withheld `save`.
194
- */
195
- save(): void;
196
- /**
197
- * Commit an inline rename and persist it. Empty names and no-change commits
198
- * are ignored.
199
- */
200
- rename(name: string): void;
201
- /** The loaded template's name, `undefined` when unnamed or not loaded. */
202
- name: ComputedRef<string | undefined>;
203
- /**
204
- * When the stored template was last written, for the header's relative
205
- * label. `updatedAt` when the store supplies it, otherwise `createdAt`;
206
- * `null` when it supplies neither, which renders no line at all. `kind` is
207
- * what picks the "Updated"/"Created" wording.
208
- */
209
- timestamp: ComputedRef<{
210
- iso: string;
211
- kind: "updatedAt" | "createdAt";
212
- } | null>;
213
- /** Whether `create()` or `load()` has resolved — nothing to save until then. */
214
- hasTemplate: ComputedRef<boolean>;
215
- isSaving: ComputedRef<boolean>;
216
- /** Drives the three-state status indicator. */
217
- status: Ref<"idle" | "saved" | "error">;
218
- errorMessage: Ref<string>;
219
- /**
220
- * Which mutations the provider supplied — `false` instead of a function
221
- * withholds one. With `canSave` false the save button *and* the status
222
- * indicator disappear, and the name becomes read-only: there is nowhere for
223
- * a change to go.
224
- */
225
- canCreate: ComputedRef<boolean>;
226
- canSave: ComputedRef<boolean>;
227
- /**
228
- * Whether the feature is usable right now. Reactive for the same reason as
229
- * `savedBlocks.isAvailable`.
230
- */
231
- isAvailable: ComputedRef<boolean>;
232
- };
145
+ * Preset colors rendered as a clickable grid inside every color picker
146
+ * popover. Each entry is a hex string (`'#0b5cff'`); clicking one applies it,
147
+ * and the preset matching the current value is marked selected (matched
148
+ * case-insensitively). The grid supplements the wheel and hex input unless
149
+ * `allowCustom` is `false`.
150
+ */
151
+ presets?: string[];
233
152
  /**
234
- * Present when a `VersionHistoryProvider` is configured via
235
- * `init({ versionHistory })`. Always present in Cloud — Cloud injects its
236
- * own adapter unconditionally, so `initCloud({ versionHistory })` (events
237
- * only, no storage) attaches handlers to it rather than gating whether
238
- * this capability exists.
239
- */
240
- versionHistory?: {
241
- /** Re-read the list from the provider. A no-op before a template exists. */
242
- refresh(): void;
243
- /** True while a past version is on the canvas instead of the user's work. */
244
- isPreviewing: ComputedRef<boolean>;
245
- /** Nothing has a history until `create()` or `load()` has resolved. */
246
- hasTemplate: ComputedRef<boolean>;
247
- /**
248
- * Whether the feature is usable right now. Reactive for the same reason as
249
- * `savedBlocks.isAvailable`.
250
- */
251
- isAvailable: ComputedRef<boolean>;
252
- /**
253
- * Which mutations the provider supplied — `false` instead of a function
254
- * withholds one. With `canRestore` false the history is browsable and
255
- * previewable but the Restore action does not render.
256
- */
257
- canCreate: ComputedRef<boolean>;
258
- canRestore: ComputedRef<boolean>;
259
- };
153
+ * Allow free-form color entry — the wheel and hex input — alongside the
154
+ * presets. Defaults to `true`.
155
+ *
156
+ * Set to `false` together with `presets` to lock authors to the preset
157
+ * palette: the popover then offers only the preset grid, with no wheel or hex
158
+ * field (a white-label / brand-kit constraint). Ignored with a warning when
159
+ * no `presets` are configured — hiding the only inputs would leave the picker
160
+ * with no way to choose a color.
161
+ */
162
+ allowCustom?: boolean;
163
+ }
164
+
165
+ declare type ColumnLayout = "1" | "2" | "3" | "2-1" | "1-2";
166
+
167
+ /**
168
+ * One comment — a thread root, or a reply to one.
169
+ *
170
+ * Threads are **one level deep**: a root carries {@link replies}, and a reply
171
+ * never does. That is what the editor renders, and flattening a deeper tree is
172
+ * your store's problem rather than the editor's.
173
+ */
174
+ declare interface Comment_2 {
175
+ /** Store-assigned. The editor never generates one. */
176
+ id: string;
177
+ body: string;
178
+ author: CommentAuthor;
179
+ /** ISO 8601. */
180
+ createdAt: string;
260
181
  /**
261
- * Present only when a `TestEmailProvider` is configured — in OSS via
262
- * `init({ testEmail })`, in Cloud whenever cloud mode is active (or when a
263
- * consumer supplied their own sender to `initCloud()`).
264
- */
265
- testEmail?: {
266
- /** Open the send dialog. A no-op while {@link isAvailable} is false. */
267
- open(): void;
268
- /**
269
- * Whether the feature is usable right now. Reactive for the same reason as
270
- * `savedBlocks.isAvailable`: Cloud resolves its plan entitlement and its
271
- * allowed-recipient list *after* capabilities are provided, and an
272
- * explicitly empty allowlist makes the feature unusable. UI must gate on
273
- * this or it renders a button that does nothing.
274
- */
275
- isAvailable: ComputedRef<boolean>;
276
- };
182
+ * ISO 8601, present only when the body has been edited since. Drives the
183
+ * "(edited)" marker, so a store that stamps it on creation makes every comment
184
+ * look edited.
185
+ */
186
+ updatedAt?: string;
187
+ /**
188
+ * The block this comment is anchored to, or `null` for a comment about the
189
+ * template as a whole.
190
+ */
191
+ blockId?: string | null;
192
+ /** The thread root this is a reply to, or `null` for a root. */
193
+ parentId?: string | null;
194
+ /** ISO 8601 when resolved; `null` or absent while the thread is open. */
195
+ resolvedAt?: string | null;
196
+ /** Who resolved it. Absent when unresolved, or when your store doesn't track it. */
197
+ resolvedBy?: CommentAuthor | null;
198
+ /** Replies to this root, oldest first. Absent or empty on a reply. */
199
+ replies?: Comment_2[];
200
+ }
201
+
202
+ /**
203
+ * Who wrote a comment, or resolved one.
204
+ *
205
+ * The same shape as {@link EditorUser}, and deliberately so — a comment's author
206
+ * is whoever was editing. It is a separate name only because a store returns
207
+ * authors it did not get from this session.
208
+ */
209
+ declare interface CommentAuthor {
210
+ id: string;
211
+ name: string;
212
+ }
213
+
214
+ /**
215
+ * One remote change, as reported by {@link CommentsProvider.subscribe}.
216
+ *
217
+ * A discriminated union rather than a `{ type, comment }` pair, because a delete
218
+ * has no comment to carry — only the id, plus the parent so the editor knows
219
+ * which list to remove it from without looking it up first.
220
+ */
221
+ declare type CommentChange = {
222
+ type: "created";
223
+ comment: Comment_2;
224
+ } | {
225
+ type: "updated";
226
+ comment: Comment_2;
227
+ } | {
228
+ type: "deleted";
229
+ commentId: string;
230
+ parentId?: string | null;
231
+ };
232
+
233
+ /**
234
+ * Where a comment change came from.
235
+ *
236
+ * `local` — the mutation ran through your own `create` / `update` / `delete` /
237
+ * `setResolved`, called by this editor. `remote` — it arrived through
238
+ * {@link CommentsProvider.subscribe} and this editor never called a mutation
239
+ * method: someone else, in another browser.
240
+ *
241
+ * A "new comments" badge counts `remote` only; counting `local` too makes a
242
+ * user's own comment increment their own unread count.
243
+ */
244
+ export declare interface CommentEventMeta {
245
+ origin: "local" | "remote";
246
+ }
247
+
248
+ /** What {@link CommentsProvider.create} is asked to store. */
249
+ declare interface CommentInput {
250
+ body: string;
251
+ /** Omitted for a template-level comment. */
252
+ blockId?: string;
253
+ /** Omitted for a thread root. */
254
+ parentId?: string;
255
+ }
256
+
257
+ /**
258
+ * Partial patch for {@link CommentsProvider.update}. A patch rather than a bare
259
+ * body, matching `TemplatePatch` and `SavedBlockPatch` — retrofitting one later
260
+ * would break every implementation, and only the shape is being paid for now.
261
+ */
262
+ declare type CommentPatch = Partial<{
263
+ body: string;
264
+ }>;
265
+
266
+ /**
267
+ * Reserved filter object for {@link CommentsProvider.list}.
268
+ *
269
+ * Empty today. The editor always calls `list` bare and narrows in memory
270
+ * (unresolved / all / this block), so the provider decides *what is visible*
271
+ * and the editor decides how it is filtered within that.
272
+ *
273
+ * **Not a pagination hook, and comments deliberately has none.** `useComments`
274
+ * derives `unresolvedCount` (the header badge) and `commentCountByBlock` (the
275
+ * per-block canvas indicators) over the *whole* loaded list. A partial page
276
+ * would make both under-report silently — wrong rather than slow. Correct
277
+ * paging would mean moving counts and filtering server-side, which is a
278
+ * redesign, not a `loadMore()`. A long-lived template caps its own growth by
279
+ * having `list()` stop returning resolved threads past some age; the panel
280
+ * hides those by default anyway. Contrast {@link VersionHistoryListParams},
281
+ * which does page: its list is a flat menu with nothing aggregating over it.
282
+ */
283
+ declare interface CommentsListParams {}
284
+
285
+ /**
286
+ * Outward notifications about the review conversation. Every member is optional.
287
+ *
288
+ * Separate from the storage methods so `initCloud()` can accept this half alone:
289
+ * a comment is keyed to a template id Cloud issued and its author is signed by
290
+ * the auth token, so Cloud owns the conversation. {@link CommentsProvider}
291
+ * extends this, so one object satisfies both entry points.
292
+ *
293
+ * Each fires once per change the editor applied, after the list reflects it. A
294
+ * handler that throws is reported through `onError` and never fails the write.
295
+ */
296
+ export declare interface CommentsOptions {
297
+ onCreated?: (comment: Comment_2, meta: CommentEventMeta) => void;
298
+ onUpdated?: (comment: Comment_2, meta: CommentEventMeta) => void;
299
+ onDeleted?: (comment: Comment_2, meta: CommentEventMeta) => void;
300
+ /**
301
+ * Which of `onResolved` / `onUnresolved` fires is decided by the **stored**
302
+ * result's `resolvedAt`, never by the state that was requested — a store may
303
+ * refuse to reopen, and the handler should hear what happened.
304
+ */
305
+ onResolved?: (comment: Comment_2, meta: CommentEventMeta) => void;
306
+ onUnresolved?: (comment: Comment_2, meta: CommentEventMeta) => void;
307
+ }
308
+
309
+ /**
310
+ * Storage contract for **comments** — the review conversation on a template.
311
+ *
312
+ * Pass an implementation as `comments` to `init()`. With no provider the panel,
313
+ * its trigger and the per-block indicators do not render and none of that UI is
314
+ * downloaded.
315
+ *
316
+ * Comments also need to know **who is commenting**, which is not part of this
317
+ * contract: it is the top-level `user` config key, because collaboration presence
318
+ * will want the same answer and a provider-local copy would be the first thing to
319
+ * drift. With no `user` the feature reports itself unavailable — an
320
+ * unattributable comment is worse than no comment feature.
321
+ *
322
+ * **Each mutation can be turned off by passing `false` instead of a function**,
323
+ * mirroring `SavedBlocksProvider`, `TemplatesProvider` and
324
+ * `VersionHistoryProvider`. They are required rather than optional precisely so
325
+ * that disabling is a decision you state, never something you get by forgetting a
326
+ * method. All four `false` yields a read-only review: existing threads are
327
+ * browsable and jump-to-block still works, with no way to add, edit, delete or
328
+ * resolve.
329
+ *
330
+ * ```ts
331
+ * const provider: CommentsProvider = {
332
+ * list: (templateId) =>
333
+ * fetch(`/api/templates/${templateId}/comments`).then((r) => r.json()),
334
+ * create: (templateId, input) =>
335
+ * fetch(`/api/templates/${templateId}/comments`, {
336
+ * method: "POST",
337
+ * headers: { "Content-Type": "application/json" },
338
+ * body: JSON.stringify(input),
339
+ * }).then((r) => r.json()),
340
+ * update: (templateId, commentId, patch) =>
341
+ * fetch(`/api/templates/${templateId}/comments/${commentId}`, {
342
+ * method: "PATCH",
343
+ * headers: { "Content-Type": "application/json" },
344
+ * body: JSON.stringify(patch),
345
+ * }).then((r) => r.json()),
346
+ * delete: async (templateId, commentId) => {
347
+ * await fetch(`/api/templates/${templateId}/comments/${commentId}`, {
348
+ * method: "DELETE",
349
+ * });
350
+ * },
351
+ * setResolved: (templateId, commentId, resolved) =>
352
+ * fetch(`/api/templates/${templateId}/comments/${commentId}/resolve`, {
353
+ * method: "POST",
354
+ * headers: { "Content-Type": "application/json" },
355
+ * body: JSON.stringify({ resolved }),
356
+ * }).then((r) => r.json()),
357
+ * };
358
+ * ```
359
+ */
360
+ declare interface CommentsProvider extends CommentsOptions {
361
+ /**
362
+ * The thread roots to show, each with its `replies`. The editor renders this
363
+ * order verbatim and never re-sorts — ordering is your store's call.
364
+ *
365
+ * The one method that cannot be disabled: without it there is nothing to show.
366
+ */
367
+ list(templateId: string, params?: CommentsListParams): Promise<Comment_2[]>;
368
+ /**
369
+ * Store a new comment or reply and return it with its store-assigned `id`, or
370
+ * `false` to make the review read-only.
371
+ */
372
+ create: false | ((templateId: string, input: CommentInput) => Promise<Comment_2>);
373
+ /**
374
+ * Apply a partial update and return the stored result, or `false` to disable
375
+ * editing — the pencil action then does not render on any comment.
376
+ */
377
+ update: false | ((templateId: string, commentId: string, patch: CommentPatch) => Promise<Comment_2>);
378
+ /**
379
+ * Remove a comment (and, for a root, its replies), or `false` to disable
380
+ * deletion. Resolves to nothing: there is no post-delete state to report.
381
+ */
382
+ delete: false | ((templateId: string, commentId: string) => Promise<void>);
383
+ /**
384
+ * Mark a thread resolved or reopened and return the stored result, or `false`
385
+ * to disable it.
386
+ *
387
+ * Takes the **target state** rather than toggling, so the call is idempotent
388
+ * and a store that receives two clicks in flight cannot end up inverted.
389
+ */
390
+ setResolved: false | ((templateId: string, commentId: string, resolved: boolean) => Promise<Comment_2>);
391
+ /**
392
+ * **Optional.** Push remote changes into the open panel, so a colleague's
393
+ * comment appears without a reload.
394
+ *
395
+ * Realtime is separable from comments rather than a prerequisite for them:
396
+ * without this the feature works exactly as it does with it, you just don't see
397
+ * someone else's comment until the list is next read. Returns an unsubscribe
398
+ * function; the editor calls it when the template changes and on teardown.
399
+ *
400
+ * ```ts
401
+ * subscribe: (templateId, onChange) => {
402
+ * const source = new EventSource(`/api/templates/${templateId}/comments/stream`);
403
+ * source.onmessage = (e) => onChange(JSON.parse(e.data) as CommentChange);
404
+ * return () => source.close();
405
+ * }
406
+ * ```
407
+ *
408
+ * Your own writes may echo back through here. Neither the list nor
409
+ * {@link CommentsOptions} needs de-duplication on your side: a `created` or
410
+ * `updated` for a comment already in the list replaces the existing entry by
411
+ * id, and the matching handler fires only when that replacement actually
412
+ * changes the stored comment — a same-content echo replaces silently, with
413
+ * nothing to notify. The comparison is structural equality over the stored
414
+ * shape (`sameComment` decides both), so an echo whose transport frame
415
+ * serializes identical content in a different key order is not recognized
416
+ * as the same comment.
417
+ */
418
+ subscribe?: (templateId: string, onChange: (change: CommentChange) => void) => () => void;
419
+ }
420
+
421
+ /**
422
+ * Writing direction of the delivered email — the canvas `dir` and the
423
+ * rendered `<mjml dir>`. Independent of the editor chrome language
424
+ * (`init({ locale })`).
425
+ */
426
+ declare type ContentDirection = "ltr" | "rtl";
427
+
428
+ /** A radius per corner, in px. */
429
+ declare type CornerRadius = Record<BorderCorner, number>;
430
+
431
+ declare interface CountdownBlock extends BaseBlock {
432
+ type: "countdown";
433
+ targetDate: string;
434
+ timezone: string;
435
+ showDays: boolean;
436
+ showHours: boolean;
437
+ showMinutes: boolean;
438
+ showSeconds: boolean;
439
+ separator: ":" | "-" | " ";
440
+ digitFontSize: number;
441
+ digitColor: string;
442
+ labelColor: string;
443
+ labelFontSize: number;
444
+ backgroundColor: string;
445
+ fontFamily?: string;
446
+ labelDays: string;
447
+ labelHours: string;
448
+ labelMinutes: string;
449
+ labelSeconds: string;
450
+ expiredMessage: string;
451
+ expiredImageUrl: string;
452
+ hideOnExpiry: boolean;
453
+ }
454
+
455
+ export declare function createDefaultTemplateContent(defaultFontFamily?: string, templateDefaults?: TemplateDefaults): TemplateContent;
456
+
457
+ /**
458
+ * Browser-local {@link MediaProvider} backed by `localStorage`.
459
+ *
460
+ * Opt-in: pass it explicitly as `init({ media: createLocalStorageMediaProvider() })`.
461
+ * The editor never falls back to it, so consumers without a provider keep
462
+ * image fields URL-only.
463
+ *
464
+ * `create` stores the file as a data URL. That is a demo-sized persistence
465
+ * model — `localStorage` quotas are typically ~5 MB, and a few large
466
+ * images will fill it. Back a real gallery with your own API.
467
+ *
468
+ * Folders, replace, import, usage, frequently-used and quota are `false`:
469
+ * this adapter is a flat list of assets.
470
+ */
471
+ export declare function createLocalStorageMediaProvider(options?: LocalStorageMediaProviderOptions): MediaProvider_2;
472
+
473
+ /**
474
+ * Browser-local {@link SavedBlocksProvider} backed by `localStorage`.
475
+ *
476
+ * Opt-in: pass it explicitly as `init({ savedBlocks: createLocalStorageSavedBlocksProvider() })`.
477
+ * The editor never falls back to it, so consumers without a provider keep the
478
+ * feature — and its UI — entirely off.
479
+ *
480
+ * Intended for demos, prototypes, and single-device use. Entries live in one
481
+ * browser profile only: they don't sync across devices or users, and clearing
482
+ * site data removes them. Back saved blocks with your own API for anything
483
+ * that needs to outlive a browser profile.
484
+ */
485
+ export declare function createLocalStorageSavedBlocksProvider(options?: LocalStorageSavedBlocksOptions): SavedBlocksProvider;
486
+
487
+ export declare function createParagraphBlock(partial?: Partial<ParagraphBlock>): ParagraphBlock;
488
+
489
+ /** Layout-only. Use this to build a layout; createBlock("slot") throws. */
490
+ export declare function createSlotBlock(): SlotBlock;
491
+
492
+ /** Layout-only. Use this to build a layout; createBlock("wrapper") throws. */
493
+ export declare function createWrapperBlock(partial?: Partial<WrapperBlock>): WrapperBlock;
494
+
495
+ declare interface CustomBlock extends BaseBlock {
496
+ type: "custom";
497
+ customType: string;
498
+ fieldValues: Record<string, unknown>;
499
+ renderedHtml?: string;
500
+ dataSourceFetched?: boolean;
501
+ }
502
+
503
+ declare interface CustomBlockBooleanField extends CustomBlockFieldBase {
504
+ type: "boolean";
505
+ default?: boolean;
506
+ }
507
+
508
+ /**
509
+ * A color-picker field. Beyond the shared field props it accepts the same flat
510
+ * `presets` / `allowCustom` pair as the editor-wide `colors` config (their
511
+ * shapes are derived from `ColorsConfig`, so the two can't drift), scoping this
512
+ * one field to a color *role* — e.g. a button-color field offering only the
513
+ * brand's accent/ink pair while other color fields inherit the global palette.
514
+ *
515
+ * **Narrowing only — of the author's freedom, not the palette.** A field can
516
+ * restrict what authors may do (lock this field, or give it its own `presets`),
517
+ * but never unlock what the editor locked. Its `presets` *replace* the
518
+ * editor-wide grid rather than intersecting it, so a locked field may offer
519
+ * colors outside `colors.presets`:
520
+ *
521
+ * - `presets` — entries are validated exactly like editor-level presets
522
+ * (`#rgb` / `#rrggbb` hex; invalid ones are skipped with a console warning
523
+ * naming the block and field). A non-empty valid list replaces the editor's
524
+ * palette for this field; leaving it unset, passing `[]`, or passing only
525
+ * invalid entries all inherit the editor's palette.
526
+ * - `allowCustom` — `false` locks this field to its palette even while the rest
527
+ * of the editor allows free-form entry. `true` cannot unlock a field when the
528
+ * editor-wide `colors.allowCustom` is `false`; it is ignored with a warning.
529
+ *
530
+ * @see https://docs.templatical.com/guide/custom-blocks
531
+ */
532
+ declare interface CustomBlockColorField extends CustomBlockFieldBase, Pick<ColorsConfig, "presets" | "allowCustom"> {
533
+ type: "color";
534
+ default?: string;
535
+ }
536
+
537
+ export declare interface CustomBlockDefinition {
538
+ type: string;
539
+ name: string;
540
+ icon?: string;
541
+ description?: string;
542
+ fields: CustomBlockField[];
543
+ template: string;
544
+ dataSource?: DataSourceConfig;
545
+ /**
546
+ * Default block styles applied when a new instance of this custom block is
547
+ * created. Deep-merged over the built-in defaults — only specify the fields
548
+ * you want to override. Controls both the editor canvas wrapper and the
549
+ * rendered MJML/email output.
550
+ *
551
+ * @example
552
+ * defaultStyles: {
553
+ * padding: { top: 0, right: 0, bottom: 0, left: 0 },
554
+ * }
555
+ */
556
+ defaultStyles?: Partial<BlockStyles>;
557
+ /**
558
+ * Optional CSS rules attached to this custom block definition. Emitted once
559
+ * (deduped across instances) into `<mj-head><mj-style>…</mj-style></mj-head>`
560
+ * in the rendered MJML, and adopted into the editor canvas (shadow root or
561
+ * light-DOM mount) so authored responsive/hover/font behavior previews
562
+ * inside the editor.
563
+ *
564
+ * Use this for media queries, hover states, or any CSS that should apply
565
+ * once per definition rather than per block instance. Class names are not
566
+ * scoped by the SDK — namespace them yourself (e.g. `.tplc-<type>-<el>`) to
567
+ * avoid collisions with other definitions or built-in editor styles.
568
+ *
569
+ * @example
570
+ * stylesheet: `
571
+ * @media (max-width: 480px) {
572
+ * .tplc-image-text-cell { display: block !important; width: 100% !important; }
573
+ * }
574
+ * `
575
+ */
576
+ stylesheet?: string;
577
+ }
578
+
579
+ declare type CustomBlockField = CustomBlockTextField | CustomBlockTextareaField | CustomBlockImageField | CustomBlockColorField | CustomBlockNumberField | CustomBlockSelectField | CustomBlockBooleanField | CustomBlockRepeatableField;
580
+
581
+ declare interface CustomBlockFieldBase {
582
+ key: string;
583
+ label: string;
584
+ required?: boolean;
585
+ placeholder?: string;
586
+ readOnly?: boolean;
587
+ }
588
+
589
+ declare interface CustomBlockImageField extends CustomBlockFieldBase {
590
+ type: "image";
591
+ default?: string;
592
+ }
593
+
594
+ declare interface CustomBlockNumberField extends CustomBlockFieldBase {
595
+ type: "number";
596
+ default?: number;
597
+ min?: number;
598
+ max?: number;
599
+ step?: number;
600
+ }
601
+
602
+ declare interface CustomBlockRepeatableField extends CustomBlockFieldBase {
603
+ type: "repeatable";
604
+ fields: Exclude<CustomBlockField, CustomBlockRepeatableField>[];
605
+ default?: Record<string, unknown>[];
606
+ minItems?: number;
607
+ maxItems?: number;
608
+ }
609
+
610
+ declare interface CustomBlockSelectField extends CustomBlockFieldBase {
611
+ type: "select";
612
+ options: SelectOption[];
613
+ default?: string;
614
+ }
615
+
616
+ declare interface CustomBlockTextareaField extends CustomBlockFieldBase {
617
+ type: "textarea";
618
+ default?: string;
619
+ }
620
+
621
+ declare interface CustomBlockTextField extends CustomBlockFieldBase {
622
+ type: "text";
623
+ default?: string;
624
+ }
625
+
626
+ export declare interface CustomFont {
627
+ name: string;
628
+ url: string;
629
+ fallback?: string;
630
+ }
631
+
632
+ declare interface DataSourceConfig {
633
+ label: string;
634
+ onFetch: (context: DataSourceFetchContext) => Promise<Record<string, unknown> | null>;
635
+ }
636
+
637
+ declare interface DataSourceFetchContext {
638
+ fieldValues: Record<string, unknown>;
639
+ blockId: string;
640
+ }
641
+
642
+ declare interface DisplayCondition {
643
+ label: string;
644
+ before: string;
645
+ after: string;
646
+ group?: string;
647
+ description?: string;
648
+ }
649
+
650
+ export declare interface DisplayConditionsConfig {
651
+ conditions: DisplayCondition[];
652
+ allowCustom?: boolean;
653
+ }
654
+
655
+ declare interface DividerBlock extends BaseBlock {
656
+ type: "divider";
657
+ lineStyle: "solid" | "dashed" | "dotted";
658
+ color: string;
659
+ thickness: number;
660
+ /**
661
+ * `"full"` spans the column and a number is pixels. A percentage is a share
662
+ * of the column: MJML renders it natively, and it shrinks with the column on
663
+ * a phone where a pixel width would overflow.
664
+ */
665
+ width: number | "full" | DividerPercentWidth;
666
+ }
667
+
668
+ /**
669
+ * A share of the column, from `"0%"` to `"100%"`.
670
+ * @pattern ^(?:100(?:\.0+)?|\d{1,2}(?:\.\d+)?)%$
671
+ */
672
+ declare type DividerPercentWidth = `${number}%`;
673
+
674
+ /**
675
+ * Who is using the editor right now — `init({ user })`.
676
+ *
677
+ * A **top-level config key, not part of any provider.** Comments are the first
678
+ * feature to need it (the panel decides which comments may be edited or deleted
679
+ * by comparing `id` against each author, and a new comment is attributed to
680
+ * `name`), but collaboration presence will want exactly the same answer. Putting
681
+ * it on the comments provider would guarantee a second, drifting copy the moment
682
+ * the second feature landed.
683
+ *
684
+ * Features that need an identity **report themselves unavailable without one**
685
+ * rather than falling back to an anonymous author. An unattributable comment is
686
+ * worse than no comment feature — the same reasoning that makes an explicitly
687
+ * empty `TestEmailProvider.allowedRecipients` disable the feature instead of
688
+ * degrading to free text.
689
+ *
690
+ * Not a security boundary: this identifies the user to the editor's UI, in the
691
+ * user's own browser. Whatever your provider writes must be attributed
692
+ * server-side, from the session your backend already trusts.
693
+ */
694
+ declare interface EditorUser {
695
+ /**
696
+ * Stable identifier, compared against `Comment.author.id`. Whatever your
697
+ * backend calls a user — a primary key, a UUID, an email.
698
+ */
699
+ id: string;
700
+ /** Display name, shown on the comments this session writes. */
701
+ name: string;
277
702
  }
278
703
 
279
704
  export declare interface FontOption {
@@ -282,7 +707,29 @@ export declare interface FontOption {
282
707
  isCustom?: boolean;
283
708
  }
284
709
 
285
- export { FontsConfig }
710
+ export declare interface FontsConfig {
711
+ defaultFallback?: string;
712
+ defaultFont?: string;
713
+ customFonts?: CustomFont[];
714
+ /**
715
+ * Restrict which of the seven built-in fonts the font picker offers.
716
+ *
717
+ * - `true` / omitted — every built-in is offered (the default).
718
+ * - `false` — no built-ins; the picker lists only `customFonts`. Use this to
719
+ * keep authors on an embedded brand kit's typefaces.
720
+ * - `string[]` — an allowlist of built-in names to keep, matched
721
+ * case-insensitively (`['Georgia', 'arial']`). A name that isn't a built-in
722
+ * is logged with a warning and skipped — consistent with how
723
+ * `paletteBlocks` treats an unknown entry — so a typo narrows the list
724
+ * rather than silently offering all seven.
725
+ *
726
+ * Independent of `customFonts`: excluding the built-ins never removes a custom
727
+ * font, and a custom font can still be the `defaultFont` when every built-in
728
+ * is excluded. Filtering the picker never affects rendering — content already
729
+ * using an excluded built-in still resolves to its proper fallback stack.
730
+ */
731
+ builtIns?: boolean | string[];
732
+ }
286
733
 
287
734
  /**
288
735
  * Get the base language code from a locale string.
@@ -296,6 +743,13 @@ export declare function getSupportedCloudLocales(): string[];
296
743
  /** List of OSS-supported locales. */
297
744
  export declare function getSupportedLocales(): string[];
298
745
 
746
+ declare type HeadingLevel = 1 | 2 | 3 | 4;
747
+
748
+ declare interface HtmlBlock extends BaseBlock {
749
+ type: "html";
750
+ content: string;
751
+ }
752
+
299
753
  /**
300
754
  * Consumer-facing shape of the `htmlBlockPreview` editor config option.
301
755
  *
@@ -309,129 +763,1442 @@ export declare type HtmlBlockPreviewConfig = boolean | {
309
763
  enabled: boolean;
310
764
  };
311
765
 
766
+ declare interface ImageBlock extends BaseBlock {
767
+ type: "image";
768
+ src: string;
769
+ alt: string;
770
+ width: number | "full";
771
+ /**
772
+ * Height in pixels. Absent means the height is derived from the width, so the
773
+ * image keeps its aspect ratio — setting both stretches it, since email
774
+ * clients don't support `object-fit`.
775
+ */
776
+ height?: number;
777
+ align: "left" | "center" | "right";
778
+ /**
779
+ * Corner radius in px. Omitted/0 = square corners. A radius of at least half
780
+ * the rendered size rounds a square image to a circle, which is how avatar
781
+ * and portrait layouts are built. A radius per corner is also accepted.
782
+ */
783
+ borderRadius?: BorderRadiusValue;
784
+ /**
785
+ * Border around the image itself, per side. Omitted = no border; a side with
786
+ * width 0 is not drawn.
787
+ */
788
+ border?: BorderValue;
789
+ linkUrl?: string;
790
+ linkOpenInNewTab?: boolean;
791
+ placeholderUrl?: string;
792
+ decorative?: boolean;
793
+ }
794
+
312
795
  export declare function init(config: TemplaticalEditorConfig): Promise<TemplaticalEditor>;
313
796
 
314
797
  /**
315
- * Mount the editor against Templatical Cloud.
316
- *
317
- * **This is `init()` with Cloud's adapters filled in**, and deliberately nothing
318
- * more. Templates, rendering and version history are providers, so Cloud's entry
319
- * point is adapter wiring rather than a second editor:
798
+ * Mount the editor against Templatical Cloud.
799
+ *
800
+ * **This is `init()` with Cloud's adapters filled in**, and deliberately nothing
801
+ * more. Templates, rendering and version history are providers, so Cloud's entry
802
+ * point is adapter wiring rather than a second editor:
803
+ *
804
+ * 1. Build the auth manager and complete the handshake.
805
+ * 2. Health-check the API, and fetch the plan config.
806
+ * 3. Build Cloud's adapters over that auth manager.
807
+ * 4. Delegate to `init()`.
808
+ *
809
+ * A failure in steps 1–2 **rejects**, rather than mounting an editor that shows
810
+ * an error overlay. That is the one place the wrapper is genuinely not `init()`:
811
+ * `init()` cannot fail after it mounts, and OSS should not grow the ability. A
812
+ * session that dies *later* — an auth refresh that cannot renew the token — does
813
+ * still surface as an overlay, because by then there is an editor to cover.
814
+ *
815
+ * `templates`, `comments` and `versionHistory` are all keyed to a template
816
+ * id Cloud issued, which also anchors collaboration, AI rewrite, scoring and
817
+ * the server-side export — a store Cloud never issued ids for would degrade
818
+ * all of them silently. Cloud therefore keeps each key's storage, but each
819
+ * key is still accepted, for its configuration and events:
820
+ * `templates.load`/`create`/`save`,
821
+ * `comments.list`/`create`/`update`/`delete`/`setResolved`, and
822
+ * `versionHistory.list`/`get`/`create`/`restore` are ignored with a warning
823
+ * naming them, while `templates`' `autoSave`, `unsavedChangesGuard`,
824
+ * `nameField`, `onSaved`, `onCreated`, `onLoaded`, `comments`' `onCreated`,
825
+ * `onUpdated`, `onDeleted`, `onResolved`, `onUnresolved`, and
826
+ * `versionHistory`'s `onCreated`, `onRestored` are all honoured.
827
+ *
828
+ * `render` is not a key here at all, unlike `init()` — Cloud renders
829
+ * server-side for test email, sends and exports, so a supplied renderer
830
+ * would change only what you preview and export, never what Cloud delivers.
831
+ * `resolvePreview` is the same key with the same type on both entry points,
832
+ * so upgrading an OSS integration is a deletion. `savedBlocks`, `testEmail`
833
+ * and `media` are the same key on both entry points too, but Cloud widens
834
+ * each type to also accept an events-only shape — `boolean |
835
+ * SavedBlocksOptions | SavedBlocksProvider`, `Pick<TestEmailOptions,
836
+ * "onSent" | "defaultRecipient"> | TestEmailProvider`, and
837
+ * `false | MediaOptions | MediaProvider` — so upgrading is still a
838
+ * deletion: drop the key to adopt Cloud's store or sender, or leave it
839
+ * exactly as it is to keep your own.
840
+ *
841
+ * `user` is not a key either: Cloud signs comment writes against the auth token's
842
+ * `user` claim, so it fills `init({ user })` from there rather than letting a
843
+ * browser name someone else.
844
+ */
845
+ export declare function initCloud(config: TemplaticalCloudEditorConfig): Promise<TemplaticalCloudEditor>;
846
+
847
+ /** Check if a locale has cloud translations (matched by exact locale, then base). */
848
+ export declare function isCloudLocaleSupported(locale: string): boolean;
849
+
850
+ /** Check if a locale has OSS translations (matched by exact locale, then base). */
851
+ export declare function isLocaleSupported(locale: string): boolean;
852
+
853
+ export declare function isSlot(block: Block): block is SlotBlock;
854
+
855
+ export declare function isWrapper(block: Block): block is WrapperBlock;
856
+
857
+ /** True iff the one slot is a direct child of a wrapper (the card). */
858
+ export declare function layoutWrapsSlot(layout: TemplateContent): boolean;
859
+
860
+ /** Options consumed only by the links linter. */
861
+ declare interface LinksLintOptions {
862
+ rules?: RuleOverrides;
863
+ /**
864
+ * Host patterns that should flag as "staging / non-production".
865
+ * Each entry is a glob-style pattern matched against the URL host.
866
+ * `*` matches any run of characters (including `.`), so `*.staging.*`
867
+ * matches `app.staging.example.com`.
868
+ *
869
+ * Default: ['localhost', '127.0.0.1', '0.0.0.0', '*.local',
870
+ * '*.staging.*', '*.dev.*']
871
+ */
872
+ nonProductionHosts?: string[];
873
+ }
874
+
875
+ declare interface LintOptions {
876
+ /**
877
+ * Fully disable linting. When true, the editor skips lazy-loading the
878
+ * package, hides the sidebar tab, and suppresses inline badges.
879
+ */
880
+ disabled?: boolean;
881
+ /** Locale for vague-text dictionaries and message text. Falls back to `en`. */
882
+ locale?: string;
883
+ /**
884
+ * Accessibility linter config. Set to `false` to disable the whole
885
+ * `lintAccessibility` linter without enumerating its rules.
886
+ */
887
+ accessibility?: false | AccessibilityLintOptions;
888
+ /**
889
+ * Structure linter config. Set to `false` to disable the whole
890
+ * `lintStructure` linter without enumerating its rules.
891
+ */
892
+ structure?: false | StructureLintOptions;
893
+ /**
894
+ * Links linter config. Set to `false` to disable the whole `lintLinks`
895
+ * linter without enumerating its rules.
896
+ */
897
+ links?: false | LinksLintOptions;
898
+ }
899
+
900
+ declare interface LintThresholds {
901
+ altMaxLength: number;
902
+ minFontSize: number;
903
+ allCapsMinLength: number;
904
+ minTouchTargetPx: number;
905
+ }
906
+
907
+ export declare interface LocalStorageMediaProviderOptions {
908
+ /**
909
+ * `localStorage` key holding the serialized array.
910
+ *
911
+ * @default "templatical:media"
912
+ */
913
+ key?: string;
914
+ }
915
+
916
+ export declare interface LocalStorageSavedBlocksOptions {
917
+ /**
918
+ * `localStorage` key holding the serialized array.
919
+ *
920
+ * @default "templatical:saved-blocks"
921
+ */
922
+ key?: string;
923
+ }
924
+
925
+ /**
926
+ * A paired logic construct — an opening tag (`before`) and its matching closer
927
+ * (`after`), e.g. `{% if vip %}` … `{% endif %}`. Selecting one inserts both
928
+ * pills at once, wrapping the selection (or with the caret placed between
929
+ * them). Shares its `before`/`after` shape with `DisplayCondition`.
930
+ */
931
+ export declare interface LogicPair {
932
+ label: string;
933
+ /** Opening logic tag, including delimiters (e.g. `{% if vip %}`). */
934
+ before: string;
935
+ /** Closing logic tag, including delimiters (e.g. `{% endif %}`). */
936
+ after: string;
937
+ /** Optional grouping label used to section the logic picker. */
938
+ group?: string;
939
+ /** Optional helper text shown beneath the pair in the picker. */
940
+ description?: string;
941
+ }
942
+
943
+ /**
944
+ * A standalone logic tag — a control-flow token (e.g. `{% else %}`) inserted
945
+ * at the cursor. Distinct from data merge tags; rendered as a keyword badge.
946
+ */
947
+ export declare interface LogicTag {
948
+ label: string;
949
+ value: string;
950
+ /** Optional grouping label used to section the logic picker. */
951
+ group?: string;
952
+ /** Optional helper text shown beneath the tag in the picker. */
953
+ description?: string;
954
+ }
955
+
956
+ /**
957
+ * Standalone logic-tag configuration, independent of `mergeTags`. `tags` are
958
+ * single control-flow tokens inserted at the cursor; `pairs` are open/close
959
+ * constructs inserted around the selection. Both appear in the built-in logic
960
+ * picker, grouped together by `group`. Typed/pasted logic highlighting is
961
+ * always on regardless of this.
962
+ */
963
+ export declare interface LogicTagsConfig {
964
+ tags?: LogicTag[];
965
+ pairs?: LogicPair[];
966
+ /**
967
+ * Consumer-owned picker. When set, the "Insert logic" affordance calls this
968
+ * instead of the built-in logic picker — return the chosen tag/pair (or
969
+ * `null` to cancel). Mirrors `MergeTagsConfig.onRequest`; lets consumers use
970
+ * their own UI and custom syntax. Precedence: `onRequest` → built-in picker.
971
+ */
972
+ onRequest?: () => Promise<LogicTag | LogicPair | null>;
973
+ }
974
+
975
+ declare interface McpConfig {
976
+ enabled: boolean;
977
+ onOperation?: (payload: TemplateOperationPayload) => void;
978
+ }
979
+
980
+ /**
981
+ * One file in a media library — the row `list` returns and `create` persists.
982
+ *
983
+ * Distinct from `MediaResult` in `./config` (`{ url, alt? }`): that is what
984
+ * an image field stores. A picked asset is reduced to a result at confirm;
985
+ * the library itself talks in assets.
986
+ *
987
+ * The store owns `id` — it is returned by {@link MediaProvider.create} and
988
+ * never generated by the editor, so identity stays a property of your storage.
989
+ *
990
+ * There is no conversion set (`small` / `medium` / `large`) on this type. The
991
+ * grid uses {@link thumbnailUrl} falling back to {@link url}; confirm always
992
+ * inserts `url`. If a derivative belongs in the email, put that URL on `url`.
993
+ */
994
+ export declare interface MediaAsset {
995
+ /** Store-assigned. The editor never generates one. */
996
+ id: string;
997
+ /** The URL that lands on the block when the asset is confirmed. */
998
+ url: string;
999
+ alt?: string;
1000
+ filename?: string;
1001
+ mimeType?: string;
1002
+ width?: number;
1003
+ height?: number;
1004
+ /** Size in bytes. */
1005
+ size?: number;
1006
+ /**
1007
+ * Grid thumbnail. Falls back to {@link url} when omitted, so a store that
1008
+ * only has one URL still renders.
1009
+ */
1010
+ thumbnailUrl?: string;
1011
+ /** Folder this asset sits in, or `null` / omitted for the root. */
1012
+ folderId?: string | null;
1013
+ /**
1014
+ * Store-assigned timestamps, ISO 8601, used for display only. They do not
1015
+ * affect ordering — the editor renders whatever order `list()` returns.
1016
+ */
1017
+ createdAt?: string;
1018
+ updatedAt?: string;
1019
+ /**
1020
+ * Per-entry permission carve-outs. **Absent means allowed** — the provider's
1021
+ * `update` / `delete` already say whether the capability exists at all, so
1022
+ * these exist only to forbid it on *particular* entries.
1023
+ *
1024
+ * They compose one-way: `canUpdate: true` cannot re-enable an `update: false`.
1025
+ */
1026
+ canUpdate?: boolean;
1027
+ canDelete?: boolean;
1028
+ }
1029
+
1030
+ /**
1031
+ * Partial patch for {@link MediaProvider.update}. Only the keys present are
1032
+ * being changed.
1033
+ */
1034
+ declare interface MediaAssetPatch {
1035
+ alt?: string;
1036
+ filename?: string;
1037
+ }
1038
+
1039
+ /**
1040
+ * Which kind of file a listing or picker is constrained to.
1041
+ *
1042
+ * Image fields and the video thumbnail pass `["images"]`. Custom blocks may
1043
+ * pass other categories. The modal forces `list({ category })` to that set.
1044
+ */
1045
+ declare type MediaCategory = "images" | "documents" | "videos" | "audio";
1046
+
1047
+ /** Payload for {@link MediaProvider.create}. */
1048
+ declare interface MediaCreateInput {
1049
+ file: File;
1050
+ folderId?: string | null;
1051
+ alt?: string;
1052
+ filename?: string;
1053
+ templateId?: string;
1054
+ }
1055
+
1056
+ /**
1057
+ * One folder in the library. {@link MediaFoldersProvider.list} returns these
1058
+ * **flat**; the UI trees them via {@link parentId}.
1059
+ */
1060
+ declare interface MediaFolder {
1061
+ id: string;
1062
+ name: string;
1063
+ /** `null` / omitted = root. */
1064
+ parentId?: string | null;
1065
+ }
1066
+
1067
+ /** Payload for {@link MediaFoldersProvider.create}. */
1068
+ declare interface MediaFolderInput {
1069
+ name: string;
1070
+ parentId?: string | null;
1071
+ }
1072
+
1073
+ /**
1074
+ * Nested folder + move contract, so an OSS gallery writes `folders: false`
1075
+ * once rather than stubbing five methods.
1076
+ *
1077
+ * `list` returns a **flat** array; the UI trees it via {@link MediaFolder.parentId}.
1078
+ * A store whose HTTP API returns a tree flattens it here.
1079
+ */
1080
+ declare interface MediaFoldersProvider {
1081
+ /** Flat listing. The one method that cannot be `false` when folders exist. */
1082
+ list(): Promise<MediaFolder[]>;
1083
+ create: false | ((input: MediaFolderInput) => Promise<MediaFolder>);
1084
+ update: false | ((id: string, patch: {
1085
+ name: string;
1086
+ }) => Promise<MediaFolder>);
1087
+ delete: false | ((id: string) => Promise<void>);
1088
+ /**
1089
+ * Move assets into a folder (`folderId: null` = root) and return the stored
1090
+ * results. Bulk — the grid is multi-select.
1091
+ */
1092
+ move: false | ((ids: string[], folderId: string | null) => Promise<MediaAsset[]>);
1093
+ }
1094
+
1095
+ /**
1096
+ * One page of {@link MediaProvider.list}.
1097
+ *
1098
+ * An envelope rather than a bare array so pagination never needs a breaking
1099
+ * change: a cursor has somewhere to live from day one. A provider that returns
1100
+ * everything at once omits {@link nextCursor}.
1101
+ */
1102
+ declare interface MediaListPage {
1103
+ items: MediaAsset[];
1104
+ nextCursor?: string;
1105
+ }
1106
+
1107
+ /**
1108
+ * Parameters for {@link MediaProvider.list}.
1109
+ *
1110
+ * Unlike saved-blocks (which the editor calls bare and filters in memory),
1111
+ * these are **real**: galleries outgrow one response, so search, folder,
1112
+ * category and cursor are sent on every listing. A provider that ignores them
1113
+ * cannot page or search.
1114
+ *
1115
+ * `templateId` is opportunistic — present when a template is loaded, omitted
1116
+ * on a blank canvas. Cloud's store ignores it; a CMS that scopes a gallery
1117
+ * per template reads it here.
1118
+ */
1119
+ declare interface MediaListParams {
1120
+ search?: string;
1121
+ cursor?: string;
1122
+ folderId?: string | null;
1123
+ category?: MediaCategory;
1124
+ /** When a template is loaded; Cloud ignores. */
1125
+ templateId?: string;
1126
+ }
1127
+
1128
+ /**
1129
+ * Configuration and outward notifications for the media library. Every member
1130
+ * is optional.
1131
+ *
1132
+ * Separate from the storage methods so `initCloud()` can accept this half
1133
+ * alone: these fire the same way whichever store holds the assets, Cloud's
1134
+ * own or yours. {@link MediaProvider} extends this, so one object satisfies
1135
+ * both entry points.
1136
+ *
1137
+ * `maxFileSize` / `mimeTypes` are a **client pre-check**, not a security
1138
+ * boundary — the backend must enforce too.
1139
+ */
1140
+ export declare interface MediaOptions {
1141
+ /** Bytes; client pre-check only. */
1142
+ maxFileSize?: number;
1143
+ mimeTypes?: Partial<Record<MediaCategory, string[]>>;
1144
+ /** An asset was created and prepended to the listing. */
1145
+ onCreated?: (asset: MediaAsset) => void;
1146
+ /** An asset was updated and its listing entry replaced. */
1147
+ onUpdated?: (asset: MediaAsset) => void;
1148
+ /**
1149
+ * An asset was deleted and filtered out of the listing. `delete` resolves
1150
+ * to nothing, so this carries the entry the editor captured before removing
1151
+ * it — not an id.
1152
+ *
1153
+ * Fires only when the entry was present in the locally loaded list at the
1154
+ * time of the delete. Deleting an id that was never loaded still deletes
1155
+ * successfully; there is just nothing to pass, so this does not fire.
1156
+ */
1157
+ onDeleted?: (asset: MediaAsset) => void;
1158
+ }
1159
+
1160
+ /**
1161
+ * Storage contract for a media library. Implement it to back the editor's
1162
+ * media picker with your own persistence — the editor owns the modal, the
1163
+ * grid, crop (client-side; persisting a crop is `create` or `replace` of the
1164
+ * resulting `File`) and insertion; you own the transport.
1165
+ *
1166
+ * Pass an implementation as `media` to `init()`. When omitted, the feature
1167
+ * stays off entirely and image fields are URL-only. `onRequestMedia` remains
1168
+ * the UI override (a host widget); it is not this store.
1169
+ *
1170
+ * Every method may reject; the editor surfaces the failure through `onError`
1171
+ * and leaves its in-memory list untouched. Provider messages are
1172
+ * user-presentable.
1173
+ *
1174
+ * **Each method other than `list` can be turned off by passing `false`
1175
+ * instead of a function.** The editor then hides the affordance rather than
1176
+ * letting the user try and fail. They are required rather than optional
1177
+ * precisely so that disabling is a decision you state, never something you
1178
+ * get by forgetting a method. Setting every mutation to `false` yields a
1179
+ * **read-only library**: users still browse, search and pick, because picking
1180
+ * only copies a URL onto the canvas.
1181
+ *
1182
+ * `list` cannot be `false`: without it there is nothing to show.
1183
+ *
1184
+ * ```ts
1185
+ * const media: MediaProvider = {
1186
+ * list: ({ search, cursor, templateId }) =>
1187
+ * myCms.page({ search, cursor, templateId }),
1188
+ * create: ({ file }) => myCms.upload(file),
1189
+ * update: false,
1190
+ * delete: false,
1191
+ * folders: false,
1192
+ * replace: false,
1193
+ * importFromUrl: false,
1194
+ * checkUsage: false,
1195
+ * frequentlyUsed: false,
1196
+ * storage: false,
1197
+ * };
1198
+ * ```
1199
+ */
1200
+ declare interface MediaProvider_2 extends MediaOptions {
1201
+ /**
1202
+ * Fetch one page of assets. Search, folder, category and cursor are real
1203
+ * params — this is not an in-memory filter over a full dump.
1204
+ *
1205
+ * The one method that cannot be disabled: without it the feature has nothing
1206
+ * to show.
1207
+ */
1208
+ list(params?: MediaListParams): Promise<MediaListPage>;
1209
+ /**
1210
+ * Persist a new asset from a `File` and return it with its store-assigned
1211
+ * `id`, or `false` to disable upload — the drop zone and upload control then
1212
+ * do not render. There is no progress callback; resolve with the asset.
1213
+ */
1214
+ create: false | ((input: MediaCreateInput) => Promise<MediaAsset>);
1215
+ /**
1216
+ * Apply a partial update and return the stored result, or `false` to disable
1217
+ * editing entirely. Per-entry exceptions go through
1218
+ * {@link MediaAsset.canUpdate}.
1219
+ */
1220
+ update: false | ((id: string, patch: MediaAssetPatch) => Promise<MediaAsset>);
1221
+ /**
1222
+ * Remove assets by id, resolving once the store has applied the delete, or
1223
+ * `false` to disable deletion entirely. **Bulk** — the grid is multi-select.
1224
+ * Per-entry exceptions go through {@link MediaAsset.canDelete}.
1225
+ */
1226
+ delete: false | ((ids: string[]) => Promise<void>);
1227
+ /**
1228
+ * Folder tree + move, or `false` to hide folders entirely. Nested so a
1229
+ * gallery without folders writes `folders: false` once.
1230
+ */
1231
+ folders: false | MediaFoldersProvider;
1232
+ /**
1233
+ * Replace an asset's file in place and return the stored result, or `false`
1234
+ * to disable replace.
1235
+ */
1236
+ replace: false | ((id: string, file: File) => Promise<MediaAsset>);
1237
+ /**
1238
+ * Import a remote URL into the library and return the stored asset, or
1239
+ * `false` to hide import. `folderId` / `templateId` are the same opportunistic
1240
+ * scoping as {@link MediaCreateInput}.
1241
+ */
1242
+ importFromUrl: false | ((url: string, folderId?: string | null, templateId?: string) => Promise<MediaAsset>);
1243
+ /**
1244
+ * Look up how many templates reference each asset, or `false` to skip the
1245
+ * usage warning on delete. **Bulk** — keyed by the same id array `delete`
1246
+ * takes.
1247
+ */
1248
+ checkUsage: false | ((ids: string[]) => Promise<Record<string, MediaUsageInfo>>);
1249
+ /**
1250
+ * Assets the current user reaches for often, or `false` to hide that tab.
1251
+ */
1252
+ frequentlyUsed: false | (() => Promise<MediaAsset[]>);
1253
+ /**
1254
+ * Current quota, or `false` to hide the storage ring. May resolve `null`
1255
+ * when the store does not (yet) know.
1256
+ */
1257
+ storage: false | (() => Promise<MediaStorageInfo | null>);
1258
+ }
1259
+ export { MediaProvider_2 as MediaProvider }
1260
+
1261
+ /**
1262
+ * Why the media picker opened. Passed to `onRequestMedia` and used by the
1263
+ * modal to constrain the listing.
1264
+ *
1265
+ * `files` is the drop path: image files dragged onto an image block. Absent
1266
+ * on click → Browse.
1267
+ */
1268
+ export declare interface MediaRequestContext {
1269
+ accept?: MediaCategory[];
1270
+ /** Drop path; absent on Browse. */
1271
+ files?: File[];
1272
+ }
1273
+
1274
+ declare interface MediaResult {
1275
+ url: string;
1276
+ alt?: string;
1277
+ }
1278
+
1279
+ /**
1280
+ * Quota snapshot for the storage ring. `storage()` may resolve `null` when
1281
+ * the store does not track quota (or, for Cloud, until plan config has loaded).
1282
+ */
1283
+ declare interface MediaStorageInfo {
1284
+ usedBytes: number;
1285
+ limitBytes: number;
1286
+ }
1287
+
1288
+ /**
1289
+ * How many templates reference an asset, for the bulk-delete warning.
1290
+ *
1291
+ * Returned from {@link MediaProvider.checkUsage} keyed by asset id.
1292
+ */
1293
+ declare interface MediaUsageInfo {
1294
+ templateCount: number;
1295
+ templateNames: string[];
1296
+ }
1297
+
1298
+ declare interface MenuBlock extends BaseBlock {
1299
+ type: "menu";
1300
+ items: MenuItemData[];
1301
+ fontSize: number;
1302
+ fontFamily?: string;
1303
+ /** Base text/link color. Unset = inherit the document-level `textColor`. */
1304
+ color?: string;
1305
+ linkColor?: string;
1306
+ textAlign: "left" | "center" | "right";
1307
+ separator: string;
1308
+ separatorColor: string;
1309
+ spacing: number;
1310
+ }
1311
+
1312
+ declare interface MenuItemData {
1313
+ id: string;
1314
+ text: string;
1315
+ url: string;
1316
+ openInNewTab: boolean;
1317
+ bold: boolean;
1318
+ underline: boolean;
1319
+ color?: string;
1320
+ }
1321
+
1322
+ declare interface MergeTag {
1323
+ label: string;
1324
+ value: string;
1325
+ /**
1326
+ * Optional grouping label used by the built-in merge tag picker to
1327
+ * section the list. When no tag in the configured array carries
1328
+ * `group`, the picker renders a plain flat list with no headers.
1329
+ * Ignored by the renderer and by typing-autocomplete.
1330
+ */
1331
+ group?: string;
1332
+ /**
1333
+ * Optional helper text shown beneath the tag in the built-in merge
1334
+ * tag picker. Not rendered anywhere else (toolbar, autocomplete,
1335
+ * MJML output) and not stored on the inserted document node.
1336
+ */
1337
+ description?: string;
1338
+ /**
1339
+ * Example value shown in place of this tag on preview surfaces — e.g.
1340
+ * `"Ada"` for `{{first_name}}` — so a preview reads like a delivered
1341
+ * email instead of a list of field names.
1342
+ *
1343
+ * Display-only, and only in the previews' Sample mode: never written to
1344
+ * the template, never sent, never present in MJML output. Setting it is
1345
+ * the whole opt-in; there is no accompanying flag. A tag without a
1346
+ * sample keeps showing its `label`.
1347
+ */
1348
+ sample?: string;
1349
+ }
1350
+
1351
+ /**
1352
+ * Why the merge-tag chooser opened, handed to {@link MergeTagsConfig.onRequest}.
1353
+ *
1354
+ * `"insert"` is a new tag at the caret. `"edit"` is the user activating a tag
1355
+ * that is already in the content, asking to swap it for another one.
1356
+ */
1357
+ declare interface MergeTagRequestContext {
1358
+ reason: "insert" | "edit";
1359
+ /**
1360
+ * The tag being replaced. Present only when `reason` is `"edit"` AND the
1361
+ * token in the content resolves against `tags` — a token that matches no
1362
+ * configured tag still opens the chooser, with nothing to preselect.
1363
+ */
1364
+ current?: MergeTag;
1365
+ }
1366
+
1367
+ export declare interface MergeTagsConfig {
1368
+ syntax?: SyntaxPresetName | SyntaxPreset;
1369
+ tags?: MergeTag[];
1370
+ /**
1371
+ * Consumer-owned chooser. Called both to insert a tag at the caret and to
1372
+ * replace one already in the content — `context.reason` says which. Return
1373
+ * the chosen tag, or `null` to cancel.
1374
+ *
1375
+ * The parameter is optional, so an existing zero-argument implementation
1376
+ * keeps working unchanged.
1377
+ */
1378
+ onRequest?: (context?: MergeTagRequestContext) => Promise<MergeTag | null>;
1379
+ /**
1380
+ * Whether a tag's tooltip reveals the raw token behind its label. Defaults
1381
+ * to `true`, which suits a readable syntax like `{{first_name}}` — the
1382
+ * tooltip tells an author which field they are looking at.
1383
+ *
1384
+ * Set `false` when `value` is an internal identifier (an opaque id resolved
1385
+ * by your backend) that an author should never see. Display-only: the token
1386
+ * is unchanged in stored content and in the rendered output.
1387
+ */
1388
+ showRawValue?: boolean;
1389
+ /**
1390
+ * Enables typing-based autocomplete in rich text blocks and in every
1391
+ * merge-tag-enabled input/textarea field (toolbars, template settings,
1392
+ * custom-block text fields). When the user types the syntax opener
1393
+ * (e.g. `{{`), a popup lists matching `tags`. Both surfaces share the
1394
+ * same popup, so behavior is identical.
1395
+ *
1396
+ * Defaults to `true`. Effective only when `tags` is non-empty AND
1397
+ * `syntax` matches a built-in preset (custom regex syntaxes cannot be
1398
+ * mapped to a trigger string and silently disable autocomplete).
1399
+ */
1400
+ autocomplete?: boolean;
1401
+ }
1402
+
1403
+ /** Function type for media browser requests, used by both OSS and Cloud editors. */
1404
+ export declare type OnRequestMedia = (context?: MediaRequestContext) => Promise<MediaResult | null>;
1405
+
1406
+ declare interface ParagraphBlock extends BaseBlock {
1407
+ type: "paragraph";
1408
+ content: string;
1409
+ /**
1410
+ * Gap in px between this block's paragraphs — the space below every `<p>`
1411
+ * except the last. Absent means `RICH_TEXT_SPACING.paragraphGap`.
1412
+ *
1413
+ * Only affects a block holding more than one paragraph; a single `<p>` has no
1414
+ * internal gap, and the space around the block is `styles.padding`.
1415
+ *
1416
+ * `0` is a valid choice (paragraphs butted together) and is distinct from the
1417
+ * field being absent, so readers must test for `undefined` rather than
1418
+ * falsiness.
1419
+ */
1420
+ paragraphSpacing?: number;
1421
+ }
1422
+
1423
+ /**
1424
+ * Context handed to `resolvePreview`.
1425
+ *
1426
+ * `recipient` is present only on surfaces that have one — the test-email
1427
+ * dialog — and absent in the editor's own preview mode. Treat its absence as
1428
+ * "no particular recipient", not as an error: a resolver should still return
1429
+ * something renderable, typically its own default or sample data.
1430
+ */
1431
+ declare interface PreviewResolveContext {
1432
+ /**
1433
+ * The template as it currently stands. Already a copy — mutating it has no
1434
+ * effect on the editor, and returning it unchanged is a valid no-op.
1435
+ */
1436
+ content: TemplateContent;
1437
+ /** The address the preview is being shown for, when the surface has one. */
1438
+ recipient?: string;
1439
+ }
1440
+
1441
+ /**
1442
+ * Everything a {@link RenderProvider} needs to render a template, handed over as
1443
+ * one object so the editor's obligations are stated rather than implied.
1444
+ *
1445
+ * The editor guarantees the payload is **render-complete**: whatever a backend
1446
+ * cannot know by itself has already been resolved into it.
1447
+ */
1448
+ export declare interface RenderPayload {
1449
+ /**
1450
+ * The template to render, with every custom block's `renderedHtml` already
1451
+ * filled in.
1452
+ *
1453
+ * This is the part a server genuinely cannot do: a custom block's HTML comes
1454
+ * from the consumer's liquid template plus the block's field values, and the
1455
+ * template registration lives in the browser. A renderer that receives a
1456
+ * custom block with neither a resolver nor `renderedHtml` omits it silently,
1457
+ * so pre-rendering is a contract obligation, not an optimisation.
1458
+ *
1459
+ * A defensive copy — mutating it does not touch what the user is editing.
1460
+ */
1461
+ content: TemplateContent;
1462
+ /**
1463
+ * The fonts the editor is actually rendering with: the custom faces it
1464
+ * resolved, plus the fallback stack to use for anything unmatched.
1465
+ *
1466
+ * Optional on the type so a headless caller can omit it; the editor always
1467
+ * sends it, because a font list assembled from `init({ fonts })` is not
1468
+ * something a backend can reconstruct from `content` alone.
1469
+ */
1470
+ fonts?: {
1471
+ customFonts: CustomFont[];
1472
+ defaultFallback: string;
1473
+ };
1474
+ }
1475
+
1476
+ /**
1477
+ * Rendering backend — how a template becomes MJML, and how MJML becomes the HTML
1478
+ * that gets sent.
1479
+ *
1480
+ * Deliberately separate from `TemplatesProvider`: saving and rendering run
1481
+ * at different frequencies (autosave would compile MJML on every debounce tick),
1482
+ * fail in different ways, and are wanted by different callers. Rendering is never
1483
+ * a field on a save result.
1484
+ *
1485
+ * Pass an implementation as `render` to `init()` or `initCloud()`. **Every method
1486
+ * is independently optional**, and each one the editor resolves on its own:
1487
+ *
1488
+ * | Call | Order |
1489
+ * |---|---|
1490
+ * | `editor.toMjml()` | {@link toMjml} → the local `@templatical/renderer` → throw |
1491
+ * | `editor.toHtml()` | {@link toHtml} → `toMjml()`'s result + {@link compileMjml} → throw |
1492
+ *
1493
+ * **There is no local HTML path, ever.** The SDK does not bundle an MJML
1494
+ * compiler, so `toHtml()` without either `toHtml` or `compileMjml` rejects with
1495
+ * an explanatory error rather than guessing.
1496
+ *
1497
+ * ```ts
1498
+ * // The cheapest useful implementation: one dumb mjml2html endpoint, and the
1499
+ * // editor gains `toHtml()` while still rendering MJML locally.
1500
+ * const editor = await init({
1501
+ * container,
1502
+ * render: {
1503
+ * compileMjml: (mjml) =>
1504
+ * fetch("/api/mjml", { method: "POST", body: mjml }).then((r) => r.text()),
1505
+ * },
1506
+ * });
1507
+ *
1508
+ * const html = await editor.toHtml();
1509
+ * ```
1510
+ */
1511
+ export declare interface RenderProvider {
1512
+ /**
1513
+ * Render the template to MJML source. Wins over the bundled renderer — a
1514
+ * backend can render block types or refinements the browser cannot (a
1515
+ * server-generated countdown GIF, a composited video thumbnail), so its output
1516
+ * is treated as authoritative.
1517
+ */
1518
+ toMjml?(payload: RenderPayload): Promise<string>;
1519
+ /**
1520
+ * Render the template straight to sending-ready HTML, skipping the MJML round
1521
+ * trip. Implement this when your backend already owns the whole pipeline.
1522
+ */
1523
+ toHtml?(payload: RenderPayload): Promise<string>;
1524
+ /**
1525
+ * Compile MJML to HTML. A dumb `mjml2html` endpoint — it needs no knowledge of
1526
+ * the block model, which is exactly the point: MJML compilation is a commodity
1527
+ * (an off-the-shelf service, a container, a CLI shell-out), whereas rendering
1528
+ * Templatical JSON is not.
1529
+ *
1530
+ * This is the cheap tier, and the reason the interface has three methods
1531
+ * instead of two: without it, every non-Node backend that wants HTML would have
1532
+ * to stand up a Node sidecar first.
1533
+ */
1534
+ compileMjml?(mjml: string): Promise<string>;
1535
+ }
1536
+
1537
+ /**
1538
+ * Display-only resolver for image `src` values (`config.resolveImageUrl`,
1539
+ * #415). Maps a canonical src (e.g. a plain file name like `logo.png`) to a
1540
+ * URL the canvas can actually display (e.g. an ephemeral `blob:` URL).
1541
+ * Returning `null` (or the input value) means "use the src as-is". The
1542
+ * resolved value never enters the content model or the MJML export.
1543
+ */
1544
+ export declare type ResolveImageUrl = (src: string) => string | null | Promise<string | null>;
1545
+
1546
+ /**
1547
+ * Resolves a template for display on preview surfaces — typically substituting
1548
+ * merge tags and **evaluating logic tags** against real data.
1549
+ *
1550
+ * Logic tags are the reason this hook exists. `MergeTag.sample` already covers
1551
+ * value tags client-side, but branching (`{% if %}` … `{% endif %}`) cannot be
1552
+ * evaluated in a browser for every supported syntax — mailchimp and ampscript
1553
+ * logic are server-side dialects — so only the consumer's own backend can do it.
1554
+ *
1555
+ * Display-only. The returned content reaches preview surfaces and nothing else:
1556
+ * it is never written to the editor's state, never returned from
1557
+ * `getContent()`, never sent, and never exported. Rejecting is safe — the
1558
+ * preview falls back to the unresolved template and says so.
1559
+ */
1560
+ declare type ResolvePreview = (context: PreviewResolveContext) => Promise<TemplateContent>;
1561
+
1562
+ /**
1563
+ * Per-rule severity override. Set a rule to `'off'` to disable it.
1564
+ * Keys are the full prefixed rule IDs (`a11y.*`, `structure.*`, `link.*`)
1565
+ * so a value copied from `LintIssue.ruleId` pastes straight in.
1566
+ */
1567
+ declare type RuleOverrides = Record<string, Severity>;
1568
+
1569
+ /**
1570
+ * A reusable, user-authored group of blocks — saved from the canvas and
1571
+ * re-insertable into any template.
1572
+ *
1573
+ * Distinct from a *custom block* (`CustomBlockDefinition`), which is a
1574
+ * developer-defined block **type** with its own template and field schema.
1575
+ * A saved block is an instance-level snapshot of ordinary blocks.
1576
+ */
1577
+ export declare interface SavedBlock {
1578
+ /**
1579
+ * Store-assigned identifier. Returned by {@link SavedBlocksProvider.create}
1580
+ * — the editor never generates it, so the store stays the authority on
1581
+ * identity (database primary key, storage slug, etc.).
1582
+ */
1583
+ id: string;
1584
+ name: string;
1585
+ /**
1586
+ * Top-level blocks captured in this saved block. A `section` carries its own
1587
+ * `children`, so a whole section-with-columns round-trips as one entry.
1588
+ *
1589
+ * Blocks are re-identified on insert (via `cloneBlock`), so the IDs stored
1590
+ * here never collide with the blocks already on a canvas.
1591
+ */
1592
+ content: Block[];
1593
+ /**
1594
+ * Optional free-text grouping, surfaced in the browser as a filter.
1595
+ *
1596
+ * Flat and free-text by design — there is no category registry and no
1597
+ * nesting. The editor derives the set of available categories from the
1598
+ * entries it has loaded, so a category exists exactly as long as something
1599
+ * carries it; an entry without one is simply uncategorised.
1600
+ */
1601
+ category?: string;
1602
+ /**
1603
+ * Per-entry permission carve-outs. **Absent means allowed** — the provider's
1604
+ * `update` / `delete` already say whether the capability exists at all, so
1605
+ * these exist only to forbid it on *particular* entries (a shared block a
1606
+ * viewer may insert but not edit, someone else's block, a locked entry).
1607
+ *
1608
+ * Return them from your API alongside the row, where the answer is already
1609
+ * known — the editor never second-guesses them and never computes its own.
1610
+ * When one is `false` the corresponding control is not rendered for that
1611
+ * entry.
1612
+ */
1613
+ canUpdate?: boolean;
1614
+ canDelete?: boolean;
1615
+ /**
1616
+ * Store-assigned timestamps, used for display only: the browser shows a
1617
+ * relative "5m ago" label per entry (preferring `updatedAt`, falling back to
1618
+ * `createdAt`) with the absolute date on hover.
1619
+ *
1620
+ * They do **not** affect ordering — the editor renders whatever order
1621
+ * `list()` returns and never re-sorts. Both are optional; omit them and the
1622
+ * label is simply not shown.
1623
+ */
1624
+ createdAt?: string;
1625
+ updatedAt?: string;
1626
+ }
1627
+
1628
+ /** Payload for {@link SavedBlocksProvider.create}. */
1629
+ declare interface SavedBlockInput {
1630
+ name: string;
1631
+ content: Block[];
1632
+ category?: string;
1633
+ }
1634
+
1635
+ /**
1636
+ * Partial patch for {@link SavedBlocksProvider.update}. Only the keys present
1637
+ * are being changed — `category: ""` clears the category, whereas omitting the
1638
+ * key leaves it alone.
1639
+ */
1640
+ declare type SavedBlockPatch = Partial<{
1641
+ name: string;
1642
+ content: Block[];
1643
+ category: string;
1644
+ }>;
1645
+
1646
+ /**
1647
+ * Parameters for {@link SavedBlocksProvider.list}. An object (rather than
1648
+ * positional arguments) so future filters can be added without breaking
1649
+ * existing provider implementations.
1650
+ *
1651
+ * **These are only sent by headless callers.** The editor's own browser calls
1652
+ * `list()` with no parameters and filters the loaded entries in memory — that
1653
+ * way a provider stays a dumb store and still gets a working search box and
1654
+ * category filter. They arrive only when you drive `useSavedBlocks`
1655
+ * yourself (see the guide's "Headless use"), so implement them if you want
1656
+ * server-side filtering for your own UI and ignore them otherwise.
1657
+ */
1658
+ export declare interface SavedBlocksListParams {
1659
+ /** Free-text filter over the saved block's `name`. */
1660
+ search?: string;
1661
+ /** Exact-match filter over {@link SavedBlock.category}. */
1662
+ category?: string;
1663
+ }
1664
+
1665
+ /**
1666
+ * Outward notifications for saved-block mutations. Every member is optional.
320
1667
  *
321
- * 1. Build the auth manager and complete the handshake.
322
- * 2. Health-check the API, and fetch the plan config.
323
- * 3. Build Cloud's adapters over that auth manager.
324
- * 4. Delegate to `init()`.
1668
+ * Separate from the storage methods so `initCloud()` can accept this half
1669
+ * alone: these fire the same way whichever store holds the entries, Cloud's
1670
+ * own or yours. {@link SavedBlocksProvider} extends this, so one object
1671
+ * satisfies both entry points.
1672
+ */
1673
+ export declare interface SavedBlocksOptions {
1674
+ /** A saved block was created and prepended to the list. */
1675
+ onCreated?: (block: SavedBlock) => void;
1676
+ /** A saved block was updated and its list entry replaced. */
1677
+ onUpdated?: (block: SavedBlock) => void;
1678
+ /**
1679
+ * A saved block was deleted and filtered out of the list. `delete` resolves
1680
+ * to nothing, so this carries the entry the editor captured before removing
1681
+ * it — not an id.
1682
+ *
1683
+ * Fires only when the entry was present in the locally loaded list at the
1684
+ * time of the delete, since that capture is where the entry comes from.
1685
+ * Deleting an id that was never loaded — e.g. a headless caller acting on
1686
+ * an id from elsewhere — still deletes successfully; there is just nothing
1687
+ * to pass, so this does not fire.
1688
+ */
1689
+ onDeleted?: (block: SavedBlock) => void;
1690
+ }
1691
+
1692
+ /**
1693
+ * Storage contract for saved blocks. Implement it to back the editor's saved
1694
+ * blocks UI with your own persistence — the editor owns the save dialog, the
1695
+ * browser, and insertion; you own the transport.
325
1696
  *
326
- * A failure in steps 1–2 **rejects**, rather than mounting an editor that shows
327
- * an error overlay. That is the one place the wrapper is genuinely not `init()`:
328
- * `init()` cannot fail after it mounts, and OSS should not grow the ability. A
329
- * session that dies *later* — an auth refresh that cannot renew the token — does
330
- * still surface as an overlay, because by then there is an editor to cover.
1697
+ * Pass an implementation as `savedBlocks` to `init()`. When omitted, the
1698
+ * feature stays off entirely and none of its UI renders.
331
1699
  *
332
- * `templates`, `comments` and `versionHistory` are all keyed to a template
333
- * id Cloud issued, which also anchors collaboration, AI rewrite, scoring and
334
- * the server-side export — a store Cloud never issued ids for would degrade
335
- * all of them silently. Cloud therefore keeps each key's storage, but each
336
- * key is still accepted, for its configuration and events:
337
- * `templates.load`/`create`/`save`,
338
- * `comments.list`/`create`/`update`/`delete`/`setResolved`, and
339
- * `versionHistory.list`/`get`/`create`/`restore` are ignored with a warning
340
- * naming them, while `templates`' `autoSave`, `unsavedChangesGuard`,
341
- * `nameField`, `onSaved`, `onCreated`, `onLoaded`, `comments`' `onCreated`,
342
- * `onUpdated`, `onDeleted`, `onResolved`, `onUnresolved`, and
343
- * `versionHistory`'s `onCreated`, `onRestored` are all honoured.
1700
+ * Every method may reject; the editor surfaces the failure through the
1701
+ * editor's `onError` callback and leaves its in-memory list untouched.
344
1702
  *
345
- * `render` is not a key here at all, unlike `init()` — Cloud renders
346
- * server-side for test email, sends and exports, so a supplied renderer
347
- * would change only what you preview and export, never what Cloud delivers.
348
- * `resolvePreview` is the same key with the same type on both entry points,
349
- * so upgrading an OSS integration is a deletion. `savedBlocks`, `testEmail`
350
- * and `media` are the same key on both entry points too, but Cloud widens
351
- * each type to also accept an events-only shape — `boolean |
352
- * SavedBlocksOptions | SavedBlocksProvider`, `Pick<TestEmailOptions,
353
- * "onSent" | "defaultRecipient"> | TestEmailProvider`, and
354
- * `false | MediaOptions | MediaProvider` — so upgrading is still a
355
- * deletion: drop the key to adopt Cloud's store or sender, or leave it
356
- * exactly as it is to keep your own.
1703
+ * **Each mutation can be turned off by passing `false` instead of a function.**
1704
+ * The editor then hides the affordance rather than letting the user try and
1705
+ * fail. They are required rather than optional precisely so that disabling is a
1706
+ * decision you state, never something you get by forgetting a method. Setting
1707
+ * all three yields a **read-only library**: users still browse, preview and
1708
+ * insert, because insertion only touches the canvas and never your store.
357
1709
  *
358
- * `user` is not a key either: Cloud signs comment writes against the auth token's
359
- * `user` claim, so it fills `init({ user })` from there rather than letting a
360
- * browser name someone else.
1710
+ * ```ts
1711
+ * const provider: SavedBlocksProvider = {
1712
+ * list: ({ search } = {}) =>
1713
+ * fetch(`/api/saved-blocks?search=${search ?? ""}`).then((r) => r.json()),
1714
+ * create: (input) =>
1715
+ * fetch("/api/saved-blocks", {
1716
+ * method: "POST",
1717
+ * headers: { "Content-Type": "application/json" },
1718
+ * body: JSON.stringify(input),
1719
+ * }).then((r) => r.json()),
1720
+ * update: (id, patch) =>
1721
+ * fetch(`/api/saved-blocks/${id}`, {
1722
+ * method: "PUT",
1723
+ * headers: { "Content-Type": "application/json" },
1724
+ * body: JSON.stringify(patch),
1725
+ * }).then((r) => r.json()),
1726
+ * delete: (id) =>
1727
+ * fetch(`/api/saved-blocks/${id}`, { method: "DELETE" }).then(() => undefined),
1728
+ * };
1729
+ * ```
361
1730
  */
362
- export declare function initCloud(config: TemplaticalCloudEditorConfig): Promise<TemplaticalCloudEditor>;
363
-
364
- /** Check if a locale has cloud translations (matched by exact locale, then base). */
365
- export declare function isCloudLocaleSupported(locale: string): boolean;
1731
+ export declare interface SavedBlocksProvider extends SavedBlocksOptions {
1732
+ /**
1733
+ * Fetch saved blocks. The editor calls this with no arguments and expects
1734
+ * everything the current user may see — scoping the result per user, tenant
1735
+ * or permission is yours to do here. {@link SavedBlocksListParams} is only
1736
+ * populated by headless callers; honouring it is optional.
1737
+ *
1738
+ * The one method that cannot be disabled: without it the feature has nothing
1739
+ * to show.
1740
+ */
1741
+ list(params?: SavedBlocksListParams): Promise<SavedBlock[]>;
1742
+ /**
1743
+ * Persist a new saved block and return it with its store-assigned `id`, or
1744
+ * `false` to disable saving entirely — the block chrome's bookmark action
1745
+ * disappears and no pick session can be started.
1746
+ */
1747
+ create: false | ((input: SavedBlockInput) => Promise<SavedBlock>);
1748
+ /**
1749
+ * Apply a partial update and return the stored result, or `false` to disable
1750
+ * editing entirely. Renaming is `update(id, { name })` and recategorising is
1751
+ * `update(id, { category })` — there are no separate methods for either.
1752
+ *
1753
+ * To allow editing in general but forbid it on particular entries, keep the
1754
+ * function and set {@link SavedBlock.canUpdate} to `false` on those.
1755
+ */
1756
+ update: false | ((id: string, patch: SavedBlockPatch) => Promise<SavedBlock>);
1757
+ /**
1758
+ * Remove a saved block, resolving once the store has applied the delete, or
1759
+ * `false` to disable deletion entirely. Per-entry exceptions go through
1760
+ * {@link SavedBlock.canDelete}.
1761
+ */
1762
+ delete: false | ((id: string) => Promise<void>);
1763
+ }
366
1764
 
367
- /** Check if a locale has OSS translations (matched by exact locale, then base). */
368
- export declare function isLocaleSupported(locale: string): boolean;
1765
+ declare interface SectionBlock extends BaseBlock {
1766
+ type: "section";
1767
+ columns: ColumnLayout;
1768
+ children: Block[][];
1769
+ /**
1770
+ * Whether columns stack vertically on mobile. Absent or `true` keeps MJML's
1771
+ * default responsive behavior (columns stack below 480px). `false` renders
1772
+ * the columns inside an `<mj-group>` so they stay side-by-side on mobile,
1773
+ * proportionally shrunk to fit.
1774
+ */
1775
+ stackOnMobile?: boolean;
1776
+ /**
1777
+ * Corner radius in px — one number, or a radius per corner. Omitted/0 =
1778
+ * square corners.
1779
+ */
1780
+ borderRadius?: BorderRadiusValue;
1781
+ /**
1782
+ * Border around the section box, per side. Omitted = no border; a side with
1783
+ * width 0 is not drawn.
1784
+ */
1785
+ border?: BorderValue;
1786
+ /** Optional outer frame (rendered as an `mj-wrapper` around the section). */
1787
+ wrapper?: SectionWrapper;
1788
+ }
369
1789
 
370
- export { isSlot }
1790
+ /**
1791
+ * Optional outer frame for a section. When present, the section is rendered
1792
+ * inside an `mj-wrapper` — a full-width band (its own background + padding)
1793
+ * that frames the section, e.g. a white card sitting on a colored band.
1794
+ */
1795
+ declare interface SectionWrapper {
1796
+ backgroundColor?: string;
1797
+ padding?: SpacingValue;
1798
+ /**
1799
+ * Corner radius in px for the outer frame — one number, or a radius per
1800
+ * corner. Omitted/0 = square corners.
1801
+ */
1802
+ borderRadius?: BorderRadiusValue;
1803
+ }
371
1804
 
372
- export { isWrapper }
1805
+ declare interface SelectOption {
1806
+ label: string;
1807
+ value: string;
1808
+ }
373
1809
 
374
- export { layoutWrapsSlot }
1810
+ declare type Severity = "error" | "warning" | "info" | "off";
375
1811
 
376
- export { LocalStorageMediaProviderOptions }
1812
+ /**
1813
+ * Layout-only hole where authored content is spliced in. Extends BaseBlock so
1814
+ * Block walkers stay typed (`styles` unused; spliced out before render).
1815
+ */
1816
+ declare interface SlotBlock extends BaseBlock {
1817
+ type: "slot";
1818
+ }
377
1819
 
378
- export { LocalStorageSavedBlocksOptions }
1820
+ declare interface SocialIcon {
1821
+ id: string;
1822
+ platform: SocialPlatform;
1823
+ url: string;
1824
+ }
379
1825
 
380
- export { LogicPair }
1826
+ declare interface SocialIconsBlock extends BaseBlock {
1827
+ type: "social";
1828
+ icons: SocialIcon[];
1829
+ iconStyle: SocialIconStyle;
1830
+ iconSize: SocialIconSize;
1831
+ spacing: number;
1832
+ align: "left" | "center" | "right";
1833
+ }
381
1834
 
382
- export { LogicTag }
1835
+ declare type SocialIconSize = "small" | "medium" | "large";
383
1836
 
384
- export { LogicTagsConfig }
1837
+ declare type SocialIconStyle = "solid" | "outlined" | "rounded" | "square" | "circle";
385
1838
 
386
- export { MediaAsset }
1839
+ declare type SocialPlatform = "facebook" | "twitter" | "instagram" | "linkedin" | "youtube" | "tiktok" | "pinterest" | "email" | "whatsapp" | "telegram" | "discord" | "snapchat" | "reddit" | "github" | "dribbble" | "behance" | "website";
387
1840
 
388
- export { MediaOptions }
1841
+ declare interface SpacerBlock extends BaseBlock {
1842
+ type: "spacer";
1843
+ height: number;
1844
+ }
389
1845
 
390
- export { MediaProvider_2 as MediaProvider }
1846
+ declare interface SpacingValue {
1847
+ top: number;
1848
+ right: number;
1849
+ bottom: number;
1850
+ left: number;
1851
+ }
391
1852
 
392
- export { MediaRequestContext }
1853
+ /** Options consumed only by the structure linter. */
1854
+ declare interface StructureLintOptions {
1855
+ rules?: RuleOverrides;
1856
+ }
393
1857
 
394
- export { MergeTagsConfig }
1858
+ declare interface SyntaxPreset {
1859
+ value: RegExp;
1860
+ logic: RegExp;
1861
+ }
395
1862
 
396
- /** Function type for media browser requests, used by both OSS and Cloud editors. */
397
- export declare type OnRequestMedia = (context?: MediaRequestContext) => Promise<MediaResult | null>;
1863
+ declare type SyntaxPresetName = "liquid" | "handlebars" | "mailchimp" | "ampscript";
1864
+
1865
+ declare interface TableBlock extends BaseBlock {
1866
+ type: "table";
1867
+ rows: TableRowData[];
1868
+ hasHeaderRow: boolean;
1869
+ headerBackgroundColor?: string;
1870
+ borderColor: string;
1871
+ borderWidth: number;
1872
+ cellPadding: number;
1873
+ fontSize: number;
1874
+ fontFamily?: string;
1875
+ /** Text color. Unset = inherit the document-level `textColor`. */
1876
+ color?: string;
1877
+ textAlign: "left" | "center" | "right";
1878
+ }
398
1879
 
399
- export { RenderPayload }
1880
+ declare interface TableCellData {
1881
+ id: string;
1882
+ content: string;
1883
+ }
400
1884
 
401
- export { RenderProvider }
1885
+ declare interface TableRowData {
1886
+ id: string;
1887
+ cells: TableCellData[];
1888
+ }
402
1889
 
403
1890
  /**
404
- * Display-only resolver for image `src` values (`config.resolveImageUrl`,
405
- * #415). Maps a canonical src (e.g. a plain file name like `logo.png`) to a
406
- * URL the canvas can actually display (e.g. an ephemeral `blob:` URL).
407
- * Returning `null` (or the input value) means "use the src as-is". The
408
- * resolved value never enters the content model or the MJML export.
1891
+ * A stored template: its identity, an optional human-readable name, and the
1892
+ * content the editor edits.
1893
+ *
1894
+ * The store owns `id` — it is returned by {@link TemplatesProvider.create} and
1895
+ * never generated by the editor, so identity stays a property of your storage
1896
+ * (database primary key, slug, document id).
409
1897
  */
410
- export declare type ResolveImageUrl = (src: string) => string | null | Promise<string | null>;
411
-
412
- export { SavedBlock }
1898
+ export declare interface Template {
1899
+ id: string;
1900
+ /**
1901
+ * Optional display name, shown in the editor header and editable inline.
1902
+ *
1903
+ * Optional because a store may not have a name column, in which case the
1904
+ * header renders nothing to edit. A rename is persisted as an ordinary
1905
+ * `save(id, { name })` patch — there is no separate rename method.
1906
+ */
1907
+ name?: string;
1908
+ /**
1909
+ * When your store created the template, ISO 8601.
1910
+ *
1911
+ * A fallback for {@link Template.updatedAt}: the header prefers `updatedAt`
1912
+ * and reads this only in its absence, so a store that records creation but not
1913
+ * modification still shows something.
1914
+ */
1915
+ createdAt?: string;
1916
+ /**
1917
+ * When your store last wrote the template, ISO 8601.
1918
+ *
1919
+ * Display-only — the header renders it as a relative label ("Updated 5m ago")
1920
+ * with the absolute date on hover. Both timestamps are absent from
1921
+ * {@link TemplatePatch}, so the editor never writes them: they are yours to
1922
+ * set, and the value the editor shows is whatever `load` or `save` returned.
1923
+ * Absent, or a value that does not parse, renders nothing.
1924
+ */
1925
+ updatedAt?: string;
1926
+ content: TemplateContent;
1927
+ }
413
1928
 
414
- export { SavedBlocksListParams }
1929
+ export declare interface TemplateContent {
1930
+ blocks: Block[];
1931
+ settings: TemplateSettings;
1932
+ }
415
1933
 
416
- export { SavedBlocksOptions }
1934
+ export declare type TemplateDefaults = Partial<TemplateSettings>;
417
1935
 
418
- export { SavedBlocksProvider }
1936
+ declare type TemplateOperation = "addBlock" | "updateBlock" | "deleteBlock" | "moveBlock" | "updateSettings" | "setContent" | "updateBlockStyle";
419
1937
 
420
- export { Template }
1938
+ declare interface TemplateOperationPayload {
1939
+ operation: TemplateOperation;
1940
+ data: Record<string, unknown>;
1941
+ timestamp: number;
1942
+ }
421
1943
 
422
- export { TemplateContent }
1944
+ /**
1945
+ * Partial patch for {@link TemplatesProvider.save}. Only the keys present are
1946
+ * being changed, so a rename can travel without content and vice versa.
1947
+ *
1948
+ * A patch (rather than bare content) because retrofitting `save(id, content)`
1949
+ * into `save(id, patch)` later would break every implementation — the same
1950
+ * reasoning, and the same shape, as `SavedBlockPatch`.
1951
+ */
1952
+ export declare type TemplatePatch = Partial<{
1953
+ name: string;
1954
+ content: TemplateContent;
1955
+ }>;
423
1956
 
424
- export { TemplateDefaults }
1957
+ /**
1958
+ * Why a save happened, as reported to {@link TemplatesOptions.onSaved}.
1959
+ *
1960
+ * `manual` covers both the header's Save button and Cmd+S: one intent, and
1961
+ * splitting them would invite handling one and silently missing the other.
1962
+ *
1963
+ * The other three are separate because treating them as a Save press is wrong.
1964
+ * A `rename` writes through `save()` but the user only edited the name, and a
1965
+ * `restore` write is the confirmation step of restoring a version — navigating
1966
+ * away on either strands the user mid-task.
1967
+ */
1968
+ export declare type TemplateSaveTrigger = "manual" | "autosave" | "rename" | "restore" | "api";
425
1969
 
426
- export { TemplatePatch }
1970
+ declare interface TemplateSettings {
1971
+ width: number;
1972
+ backgroundColor: string;
1973
+ /**
1974
+ * Document-level default text color: the `<mj-text>` default in the rendered
1975
+ * MJML, inherited by every text block (Title, Paragraph, Menu, Table) that
1976
+ * doesn't set its own `color`. Required, defaulting to `#1a1a1a` (see
1977
+ * `DEFAULT_TEMPLATE_DEFAULTS`); customize the default per-consumer via
1978
+ * `templateDefaults`. A block's explicit `color` or an inline text-color
1979
+ * mark overrides it.
1980
+ */
1981
+ textColor: string;
1982
+ /**
1983
+ * Document-level link color: emitted as the global `a { color }` rule in the
1984
+ * rendered MJML, so it cascades to every link — rich-text and menu alike.
1985
+ * Optional: when unset, links inherit the surrounding text color
1986
+ * (`color: inherit`), preserving the pre-#352 default. A per-block or
1987
+ * per-item color (a Menu item's `color`, `MenuBlock.linkColor`) still
1988
+ * overrides it.
1989
+ */
1990
+ linkColor?: string;
1991
+ /**
1992
+ * Whether links are underlined document-wide: drives the global
1993
+ * `a { text-decoration }` rule (`underline` when true, `none` when false).
1994
+ * Required, defaulting to `true` (see `DEFAULT_TEMPLATE_DEFAULTS`) — the
1995
+ * common, more accessible email default. Applies to body (rich-text) links;
1996
+ * buttons and menu items carry their own inline `text-decoration` and are
1997
+ * unaffected. Set `false` to render links without an underline.
1998
+ */
1999
+ linkUnderline: boolean;
2000
+ fontFamily: string;
2001
+ preheaderText?: string;
2002
+ /**
2003
+ * BCP-47 language code for the rendered email's `<html lang>`. Drives
2004
+ * screen-reader pronunciation. Default `'en'` via `DEFAULT_TEMPLATE_DEFAULTS`.
2005
+ */
2006
+ locale: string;
2007
+ /**
2008
+ * Writing direction of the delivered email: the canvas `dir` and the
2009
+ * rendered `<mjml dir>`. Independent of the editor chrome
2010
+ * (`init({ locale })`). Optional: when unset, `resolveContentDirection`
2011
+ * falls through to the content language (`ar` / `he` / `fa` / … → `"rtl"`,
2012
+ * otherwise `"ltr"`). An explicit `"ltr"` on an Arabic template is a
2013
+ * stated decision, not a missing value.
2014
+ */
2015
+ direction?: ContentDirection;
2016
+ }
427
2017
 
428
- export { TemplateSaveTrigger }
2018
+ export declare interface TemplateSettingsConfig {
2019
+ /**
2020
+ * Which template settings the Settings panel exposes.
2021
+ *
2022
+ * - `true` / omitted — every setting is editable (the default).
2023
+ * - `false` — none; the Settings tab itself stops rendering.
2024
+ * - `Array<keyof TemplateSettings>` — an allowlist of settings to keep
2025
+ * (`['width', 'backgroundColor', 'fontFamily']`). A card renders when at
2026
+ * least one of its settings survives, so hiding `locale` removes the
2027
+ * Language card and hiding `preheaderText` removes the Preheader card. An
2028
+ * empty array means none, the same as `false`.
2029
+ *
2030
+ * The list narrows, it never reorders — settings sit in fixed cards, so
2031
+ * unlike `paletteBlocks` there is no order to express. An entry that isn't a
2032
+ * `TemplateSettings` member is a compile error for TypeScript callers, and is
2033
+ * logged with a warning and skipped at runtime, so a typo narrows the panel
2034
+ * rather than silently restoring every setting.
2035
+ *
2036
+ * Presentation only. Hiding a setting never changes its value: whatever the
2037
+ * loaded content carries keeps rendering and keeps round-tripping through
2038
+ * `getContent()`. Set the values you hide from the content you hand the
2039
+ * editor — `init({ content })`, or your own `templates.load`.
2040
+ */
2041
+ fields?: boolean | Array<keyof TemplateSettings>;
2042
+ }
429
2043
 
430
- export { TemplateSettingsConfig }
2044
+ /**
2045
+ * Configuration and outward notifications for the template lifecycle. Every
2046
+ * member is optional.
2047
+ *
2048
+ * Separate from the storage methods so `initCloud()` can accept this half alone:
2049
+ * Cloud owns template storage, because the id it issues is the join key for
2050
+ * collaboration, version history, comments, AI rewrite, scoring and the
2051
+ * server-side export. {@link TemplatesProvider} extends this, so one object
2052
+ * satisfies both entry points and moving to Cloud edits nothing.
2053
+ */
2054
+ export declare interface TemplatesOptions {
2055
+ /**
2056
+ * Save automatically, debounced, after the user stops editing.
2057
+ *
2058
+ * Lives here because it is meaningless without somewhere to save to. The
2059
+ * cadence is `changeDebounce` at the config root, since one timer drives both
2060
+ * this and the `onChange` notification, and `onChange` works with no provider.
2061
+ *
2062
+ * Defaults to off for `init()` and on for `initCloud()` — a Cloud session
2063
+ * always has a store.
2064
+ */
2065
+ autoSave?: boolean;
2066
+ /**
2067
+ * Warn before the tab closes with unsaved changes. Defaults to on.
2068
+ *
2069
+ * Cannot cover client-side route changes; `onDirtyChange` at the config root
2070
+ * is what guards a router, and it fires with or without a provider.
2071
+ */
2072
+ unsavedChangesGuard?: boolean;
2073
+ /**
2074
+ * Render the inline template-name field in the header. Defaults to on.
2075
+ *
2076
+ * `false` hides the field and nothing else: `create({ name })`, `setName()`
2077
+ * and the `name` in each save patch keep working, so your own chrome can still
2078
+ * manage names.
2079
+ */
2080
+ nameField?: boolean;
2081
+ /**
2082
+ * A save completed, with `isDirty` cleared and `isSaving` false — so a
2083
+ * handler may navigate without tripping the consumer's own unsaved-changes
2084
+ * guard. Never fires for a failed save; that reaches `onError`.
2085
+ *
2086
+ * `template` is the provider's response to `save()` as returned, not
2087
+ * necessarily what got adopted: a rename that lands mid-flight keeps the
2088
+ * local name, and under a concurrent `load()` it may describe a template
2089
+ * that is no longer open.
2090
+ */
2091
+ onSaved?: (template: Template, meta: {
2092
+ trigger: TemplateSaveTrigger;
2093
+ }) => void;
2094
+ /** A template was created and attached to the editor. */
2095
+ onCreated?: (template: Template) => void;
2096
+ /** A template was fetched and its content put on the canvas. */
2097
+ onLoaded?: (template: Template) => void;
2098
+ }
431
2099
 
432
- export { TemplatesOptions }
2100
+ /**
2101
+ * Storage contract for the template itself — the save/load lifecycle around
2102
+ * whatever the editor is editing.
2103
+ *
2104
+ * Deliberately **not** CRUD: there is no `list` and no `delete`, and the editor
2105
+ * has no template browser. Choosing *which* template to open belongs to your
2106
+ * own application, which then hands the id to `editor.load(id)`.
2107
+ *
2108
+ * Pass an implementation as `templates` to `init()`. When omitted, the header's
2109
+ * name field, save button and save-status indicator do not render, and
2110
+ * `create()` / `load()` / `save()` reject with an explanatory error.
2111
+ *
2112
+ * Every method may reject; the editor reports the failure through `onError`,
2113
+ * surfaces it in the header, and leaves its state untouched — nothing is marked
2114
+ * saved that wasn't.
2115
+ *
2116
+ * **Each mutation can be turned off by passing `false` instead of a function**,
2117
+ * mirroring `SavedBlocksProvider`. They are required rather than optional
2118
+ * precisely so that disabling is a decision you state, never something you get
2119
+ * by forgetting a method. `save: false` yields a genuine read-only mode (a
2120
+ * template still loads and can be edited locally, there is simply nothing to
2121
+ * persist); `create: false` yields a no-new-templates mode.
2122
+ *
2123
+ * ```ts
2124
+ * const provider: TemplatesProvider = {
2125
+ * load: (id) => fetch(`/api/templates/${id}`).then((r) => r.json()),
2126
+ * create: (input) =>
2127
+ * fetch("/api/templates", {
2128
+ * method: "POST",
2129
+ * headers: { "Content-Type": "application/json" },
2130
+ * body: JSON.stringify(input),
2131
+ * }).then((r) => r.json()),
2132
+ * save: (id, patch) =>
2133
+ * fetch(`/api/templates/${id}`, {
2134
+ * method: "PATCH",
2135
+ * headers: { "Content-Type": "application/json" },
2136
+ * body: JSON.stringify(patch),
2137
+ * }).then((r) => r.json()),
2138
+ * };
2139
+ * ```
2140
+ */
2141
+ export declare interface TemplatesProvider extends TemplatesOptions {
2142
+ /**
2143
+ * Fetch a template by id. The one method that cannot be disabled: without it
2144
+ * there is nothing to open.
2145
+ */
2146
+ load(id: string): Promise<Template>;
2147
+ /**
2148
+ * Persist a new template and return it with its store-assigned `id`, or
2149
+ * `false` to disable creation entirely.
2150
+ */
2151
+ create: false | ((input: {
2152
+ name?: string;
2153
+ content: TemplateContent;
2154
+ }) => Promise<Template>);
2155
+ /**
2156
+ * Apply a partial update to an existing template and return the stored
2157
+ * result, or `false` to disable saving entirely — the editor then hides both
2158
+ * the save button and the save-status indicator.
2159
+ */
2160
+ save: false | ((id: string, patch: TemplatePatch) => Promise<Template>);
2161
+ }
433
2162
 
434
- export { TemplatesProvider }
2163
+ /**
2164
+ * One recorded version of a template.
2165
+ *
2166
+ * Called a *version* rather than a snapshot deliberately: "snapshot" is an
2167
+ * implementation word, and it collides with the editor's undo/redo history,
2168
+ * which is a different thing entirely (in-session, unsaved, per-keystroke).
2169
+ */
2170
+ declare interface TemplateVersion {
2171
+ /** Store-assigned. The editor never generates one. */
2172
+ id: string;
2173
+ /** ISO 8601. Drives the relative-time label in the history dropdown. */
2174
+ createdAt: string;
2175
+ /**
2176
+ * Recorded by a save rather than by a person. Drives the "auto" badge, and is
2177
+ * where a future filter would key off.
2178
+ */
2179
+ isAutomatic?: boolean;
2180
+ /** Short human label, when your store lets someone name a version. */
2181
+ label?: string;
2182
+ author?: {
2183
+ id?: string;
2184
+ name?: string;
2185
+ };
2186
+ /**
2187
+ * Optional eager content — a **cache hint**, never an alternative to
2188
+ * {@link VersionHistoryProvider.get}.
2189
+ *
2190
+ * When present the editor previews this version instantly and never calls
2191
+ * `get` for it. Evaluated **per entry**, so a provider may hydrate the recent
2192
+ * versions and omit the older ones.
2193
+ *
2194
+ * This exists because scrubbing through history is synchronous: once the
2195
+ * preview is open, stepping to another version swaps the canvas in the same
2196
+ * tick. A provider that eager-loads keeps that; one that omits `content` pays
2197
+ * a single round-trip the first time each version is visited, and the editor
2198
+ * caches it thereafter.
2199
+ */
2200
+ content?: TemplateContent;
2201
+ }
435
2202
 
436
2203
  /**
437
2204
  * `initCloud()` returns the **same** editor `init()` does.
@@ -1359,15 +3126,161 @@ export declare interface TemplaticalEditorConfig {
1359
3126
  lint?: LintOptions;
1360
3127
  }
1361
3128
 
1362
- export { TestEmailOptions }
3129
+ /**
3130
+ * Configuration and outward notifications for test-email sending. Every
3131
+ * member is optional.
3132
+ *
3133
+ * Separate from `send` so `initCloud()` can accept this half alone: these are
3134
+ * meaningful whichever backend does the sending, Cloud's own or yours.
3135
+ * {@link TestEmailProvider} extends this, adding only `send`.
3136
+ */
3137
+ export declare interface TestEmailOptions {
3138
+ /**
3139
+ * Also render the template to MJML and pass it as
3140
+ * {@link TestEmailPayload.mjml}, saving you a `renderToMjml()` call.
3141
+ *
3142
+ * **Requires `@templatical/renderer`**, an optional peer dependency. If it
3143
+ * isn't installed the send still happens with JSON only and one warning is
3144
+ * logged — opting in never breaks sending. A *rendering* failure is different:
3145
+ * that fails the send, because it means the template itself is broken and
3146
+ * silently sending without the MJML would hide it.
3147
+ */
3148
+ includeMjml?: boolean;
3149
+ /**
3150
+ * Restrict who may be sent to:
3151
+ *
3152
+ * - **omitted** — the dialog accepts free text, validated for shape only;
3153
+ * - **one entry** — a read-only field, pre-filled with it;
3154
+ * - **several** — a picker of exactly those addresses;
3155
+ * - **empty array** — nobody may be sent to, so the feature reports itself
3156
+ * unavailable and no trigger renders. `[]` is read as a decision, not as
3157
+ * "unset".
3158
+ *
3159
+ * A picker constraint, **not a security boundary**: this array lives in the
3160
+ * user's browser and is trivially edited there. Validate the recipient on your
3161
+ * server regardless of what the dialog offered.
3162
+ */
3163
+ allowedRecipients?: string[];
3164
+ /**
3165
+ * Pre-fills the recipient field. Ignored when it isn't in
3166
+ * {@link allowedRecipients}.
3167
+ */
3168
+ defaultRecipient?: string;
3169
+ /**
3170
+ * A test send completed. Fires once `send` resolves, with the same payload
3171
+ * it was given — never for a rejected send, since that surfaces through the
3172
+ * dialog's own inline error instead.
3173
+ */
3174
+ onSent?: (payload: TestEmailPayload) => void;
3175
+ }
3176
+
3177
+ /**
3178
+ * What a {@link TestEmailProvider} receives when the user asks to send a test.
3179
+ */
3180
+ export declare interface TestEmailPayload {
3181
+ recipient: string;
3182
+ /**
3183
+ * The editor's current content, exactly as `getContent()` would return it.
3184
+ * Always present.
3185
+ */
3186
+ content: TemplateContent;
3187
+ /**
3188
+ * The template rendered to MJML.
3189
+ *
3190
+ * Present only when {@link TestEmailOptions.includeMjml} is set **and**
3191
+ * `@templatical/renderer` resolved. Always guard for absence: opting in
3192
+ * without the renderer installed still sends, just without this field (the
3193
+ * editor logs one warning naming the package).
3194
+ *
3195
+ * You still have to compile MJML to HTML — the editor never does, and
3196
+ * deliberately doesn't bundle a compiler.
3197
+ */
3198
+ mjml?: string;
3199
+ /**
3200
+ * Echo of {@link TestEmailOptions.allowedRecipients}, present only when one
3201
+ * was configured.
3202
+ *
3203
+ * **Untrusted.** It is read out of the browser and carries no signature, so it
3204
+ * is not authoritative about anything. Two things it is genuinely useful for:
3205
+ * keeping one `send` implementation portable between your own backend and
3206
+ * Templatical Cloud, and comparing it against `recipient` server-side — a
3207
+ * mismatch means the client was tampered with or is buggy, which is worth
3208
+ * logging. Never treat it *as* the allowlist; that list belongs on your
3209
+ * server.
3210
+ */
3211
+ allowedRecipients?: string[];
3212
+ }
1363
3213
 
1364
- export { TestEmailPayload }
3214
+ /**
3215
+ * Sending contract for test emails. Implement it to let users send a test of the
3216
+ * template they're editing through your own infrastructure — the editor owns the
3217
+ * trigger, the dialog, recipient validation and the sending/success/error
3218
+ * states; you own delivery.
3219
+ *
3220
+ * Pass an implementation as `testEmail` to `init()`. When omitted the feature
3221
+ * stays off entirely and no trigger renders.
3222
+ *
3223
+ * ```ts
3224
+ * const provider: TestEmailProvider = {
3225
+ * send: ({ recipient, content }) =>
3226
+ * fetch("/api/test-email", {
3227
+ * method: "POST",
3228
+ * headers: { "Content-Type": "application/json" },
3229
+ * body: JSON.stringify({ recipient, content }),
3230
+ * }).then((r) => {
3231
+ * if (!r.ok) throw new Error("Could not send the test email");
3232
+ * }),
3233
+ * };
3234
+ * ```
3235
+ */
3236
+ export declare interface TestEmailProvider extends TestEmailOptions {
3237
+ /**
3238
+ * Deliver a test of the current template.
3239
+ *
3240
+ * Resolve on success — the dialog confirms, then closes. Reject with a
3241
+ * **user-presentable** message on failure: the dialog renders `error.message`
3242
+ * inline and stays open so the user can retry.
3243
+ */
3244
+ send(payload: TestEmailPayload): Promise<void>;
3245
+ }
1365
3246
 
1366
- export { TestEmailProvider }
3247
+ export declare interface ThemeOverrides {
3248
+ bg?: string;
3249
+ bgElevated?: string;
3250
+ bgHover?: string;
3251
+ bgActive?: string;
3252
+ border?: string;
3253
+ borderLight?: string;
3254
+ text?: string;
3255
+ textMuted?: string;
3256
+ textDim?: string;
3257
+ primary?: string;
3258
+ primaryHover?: string;
3259
+ primaryLight?: string;
3260
+ secondary?: string;
3261
+ secondaryHover?: string;
3262
+ secondaryLight?: string;
3263
+ success?: string;
3264
+ successLight?: string;
3265
+ warning?: string;
3266
+ warningLight?: string;
3267
+ danger?: string;
3268
+ dangerLight?: string;
3269
+ canvasBg?: string;
3270
+ dark?: Omit<ThemeOverrides, "dark">;
3271
+ }
1367
3272
 
1368
- export { ThemeOverrides }
3273
+ declare interface TitleBlock extends BaseBlock {
3274
+ type: "title";
3275
+ content: string;
3276
+ level: HeadingLevel;
3277
+ /** Text color. Unset = inherit the document-level `textColor`. */
3278
+ color?: string;
3279
+ textAlign: "left" | "center" | "right";
3280
+ fontFamily?: string;
3281
+ }
1369
3282
 
1370
- export { UiTheme }
3283
+ export declare type UiTheme = "light" | "dark" | "auto";
1371
3284
 
1372
3285
  /**
1373
3286
  * Unmount the most-recently-created OSS editor. Single-instance legacy
@@ -1376,24 +3289,165 @@ export { UiTheme }
1376
3289
  */
1377
3290
  export declare function unmount(): void;
1378
3291
 
1379
- export declare function useFonts(config?: FontsConfig): UseFontsReturn;
3292
+ /** Throw if `layout` does not contain exactly one legal slot. */
3293
+ export declare function validateLayout(layout: TemplateContent): void;
3294
+
3295
+ /**
3296
+ * Page request for {@link VersionHistoryProvider.list}.
3297
+ *
3298
+ * The editor always calls `list` bare — it loads one page and renders it. Both
3299
+ * fields exist for headless callers and for providers that page their own
3300
+ * storage; a provider free to return everything at once may ignore them.
3301
+ */
3302
+ declare interface VersionHistoryListParams {
3303
+ /** Maximum entries to return. A provider may return fewer, never more. */
3304
+ limit?: number;
3305
+ /**
3306
+ * Opaque cursor taken from a previous result's
3307
+ * {@link VersionHistoryListResult.nextCursor}.
3308
+ */
3309
+ cursor?: string;
3310
+ }
3311
+
3312
+ /**
3313
+ * What {@link VersionHistoryProvider.list} resolves to.
3314
+ *
3315
+ * An envelope rather than a bare array **so that adding pagination never breaks
3316
+ * an implementation**: a cursor has somewhere to live from day one. Reserving
3317
+ * only the params object would have solved the request side and left the
3318
+ * response side needing a breaking change.
3319
+ */
3320
+ declare interface VersionHistoryListResult {
3321
+ /**
3322
+ * The versions to offer, newest first. The editor renders this order verbatim
3323
+ * and never re-sorts — ordering is your store's call.
3324
+ */
3325
+ versions: TemplateVersion[];
3326
+ /**
3327
+ * Cursor for the next page, or absent when this is the last one. Opaque to
3328
+ * the editor: pass it back as {@link VersionHistoryListParams.cursor}.
3329
+ * A provider that returns its whole history at once omits it.
3330
+ */
3331
+ nextCursor?: string;
3332
+ }
3333
+
3334
+ /**
3335
+ * Outward notifications about a template's version history. Every member is
3336
+ * optional.
3337
+ *
3338
+ * Separate from the storage methods so `initCloud()` can accept this half
3339
+ * alone: a version is keyed to a template id Cloud issued, so Cloud owns
3340
+ * version-history storage. {@link VersionHistoryProvider} extends this, so one
3341
+ * object satisfies both entry points.
3342
+ */
3343
+ export declare interface VersionHistoryOptions {
3344
+ /**
3345
+ * A version was recorded through {@link VersionHistoryProvider.create}. Not
3346
+ * called for a version your `save` implementation records automatically —
3347
+ * that happens inside your own backend, outside anything the editor can see.
3348
+ */
3349
+ onCreated?: (version: TemplateVersion) => void;
3350
+ /**
3351
+ * A past version was made current. Takes the **resulting `Template`** that
3352
+ * {@link VersionHistoryProvider.restore} resolves to, not the
3353
+ * {@link TemplateVersion} that was restored from.
3354
+ */
3355
+ onRestored?: (template: Template) => void;
3356
+ }
1380
3357
 
1381
- export declare interface UseFontsReturn {
1382
- fonts: ComputedRef<FontOption[]>;
1383
- defaultFont: ComputedRef<string>;
1384
- defaultFallback: ComputedRef<string>;
1385
- customFonts: Ref<CustomFont[]>;
1386
- isLoaded: Ref<boolean>;
1387
- loadCustomFonts: () => Promise<void>;
1388
- cleanupFontLinks: () => void;
1389
- getFontWithFallback: (fontName: string) => string;
1390
- getDefaultFont: () => string;
3358
+ /**
3359
+ * Storage contract for a template's version history.
3360
+ *
3361
+ * Pass an implementation as `versionHistory` to `init()` or `initCloud()`. With
3362
+ * no provider the history control does not render and none of its UI is
3363
+ * downloaded.
3364
+ *
3365
+ * **The editor never creates versions on its own.** Whoever implements
3366
+ * `TemplatesProvider.save` decides whether a save also records a version, which
3367
+ * keeps throttling, retention and dedupe policy on the side that pays for the
3368
+ * storage. {@link create} is for versions a *person* asks for.
3369
+ *
3370
+ * ```ts
3371
+ * const provider: VersionHistoryProvider = {
3372
+ * list: (templateId) =>
3373
+ * fetch(`/api/templates/${templateId}/versions`).then((r) => r.json()),
3374
+ * get: (templateId, versionId) =>
3375
+ * fetch(`/api/templates/${templateId}/versions/${versionId}`)
3376
+ * .then((r) => r.json())
3377
+ * .then((v) => v.content),
3378
+ * create: false,
3379
+ * // No atomic restore endpoint? Compose it: read the old content, save it.
3380
+ * restore: async (templateId, versionId) => {
3381
+ * const content = await provider.get(templateId, versionId);
3382
+ * return templates.save(templateId, { content });
3383
+ * },
3384
+ * };
3385
+ * ```
3386
+ */
3387
+ declare interface VersionHistoryProvider extends VersionHistoryOptions {
3388
+ /** One page of versions. See {@link VersionHistoryListResult}. */
3389
+ list(templateId: string, params?: VersionHistoryListParams): Promise<VersionHistoryListResult>;
3390
+ /**
3391
+ * Fetch one version's content. **The operation, and always required** — the
3392
+ * editor must always be able to obtain a version's content.
3393
+ * {@link TemplateVersion.content} is the optimisation, and optimisations don't
3394
+ * get to be mandatory.
3395
+ */
3396
+ get(templateId: string, versionId: string): Promise<TemplateContent>;
3397
+ /**
3398
+ * Record the current content as a version on demand, or `false` to disable
3399
+ * it — the editor then offers no way to create one by hand, and the history
3400
+ * is whatever `save` recorded.
3401
+ *
3402
+ * `false`-able and required rather than optional, mirroring
3403
+ * `SavedBlocksProvider` and `TemplatesProvider`: disabling is a decision you
3404
+ * state, never something you get by forgetting a method.
3405
+ */
3406
+ create: false | ((templateId: string, content: TemplateContent, meta?: {
3407
+ label?: string;
3408
+ }) => Promise<TemplateVersion>);
3409
+ /**
3410
+ * Make a past version current and return the resulting template, or `false`
3411
+ * to make history read-only — versions can then be browsed and previewed, but
3412
+ * the Restore action does not render.
3413
+ *
3414
+ * **History is append-only:** a restore adds an entry rather than rewriting
3415
+ * one, so undo stays coherent and two backends can't disagree about what
3416
+ * history looks like afterwards. A store with no atomic endpoint composes it
3417
+ * in one line — `get` the old content, then `save` it — accepting two
3418
+ * round-trips and a narrower failure window.
3419
+ */
3420
+ restore: false | ((templateId: string, versionId: string) => Promise<Template>);
1391
3421
  }
1392
3422
 
1393
- export { validateLayout }
3423
+ declare interface VideoBlock extends BaseBlock {
3424
+ type: "video";
3425
+ url: string;
3426
+ openInNewTab?: boolean;
3427
+ thumbnailUrl: string;
3428
+ alt: string;
3429
+ width: number | "full";
3430
+ /**
3431
+ * Height in pixels for the thumbnail. Absent means the height is derived from
3432
+ * the width, so the thumbnail keeps its aspect ratio — setting both stretches
3433
+ * it, since email clients don't support `object-fit`.
3434
+ */
3435
+ height?: number;
3436
+ align: "left" | "center" | "right";
3437
+ placeholderUrl?: string;
3438
+ }
1394
3439
 
1395
- export { VersionHistoryOptions }
3440
+ export declare type ViewportSize = "desktop" | "mobile";
1396
3441
 
1397
- export { ViewportSize }
3442
+ /**
3443
+ * Layout-only band rendered as `mj-wrapper`. The block is the band:
3444
+ * `styles.backgroundColor` / `styles.padding` / `borderRadius` map to the
3445
+ * wrapper. Legal in layout only this ship.
3446
+ */
3447
+ declare interface WrapperBlock extends BaseBlock {
3448
+ type: "wrapper";
3449
+ children: Block[];
3450
+ borderRadius?: number;
3451
+ }
1398
3452
 
1399
3453
  export { }