@riverbankcms/sdk 0.80.0 → 0.81.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 (265) hide show
  1. package/dist/{PreviewEditorSidebar-QZG37QIF.mjs → PreviewEditorSidebar-RDCMEXIJ.mjs} +763 -255
  2. package/dist/{PreviewEditorUI-AXIKSMZA.mjs → PreviewEditorUI-NYGQ6K7B.mjs} +170 -29
  3. package/dist/SdkPreviewModeRuntime-AKCSCZEC.mjs +8 -0
  4. package/dist/_dts/api/src/contentRuntime.d.ts +12 -0
  5. package/dist/_dts/api/src/contentTypes.d.ts +6 -0
  6. package/dist/_dts/api/src/index.d.ts +2 -2
  7. package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +53 -0
  8. package/dist/_dts/block-form/src/backend/store-backend.d.ts +3 -1
  9. package/dist/_dts/block-form/src/backend/types.d.ts +3 -3
  10. package/dist/_dts/blocks/src/PageRenderer.d.ts +3 -0
  11. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +20 -28
  12. package/dist/_dts/blocks/src/index.d.ts +6 -4
  13. package/dist/_dts/blocks/src/primitives/content/components/Text.d.ts +1 -1
  14. package/dist/_dts/blocks/src/primitives/content/primitives.d.ts +1 -0
  15. package/dist/_dts/blocks/src/server.d.ts +1 -1
  16. package/dist/_dts/blocks/src/system/blocks/events/event-calendar.d.ts +3 -2
  17. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +135 -0
  18. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +5 -0
  19. package/dist/_dts/blocks/src/system/blocks/feature-cards.d.ts +2 -0
  20. package/dist/_dts/blocks/src/system/blocks/form.d.ts +20 -1
  21. package/dist/_dts/blocks/src/system/blocks/hero.d.ts +2 -0
  22. package/dist/_dts/blocks/src/system/blocks/offerings-preview/index.d.ts +19 -0
  23. package/dist/_dts/blocks/src/system/blocks/offerings-preview/model.d.ts +134 -0
  24. package/dist/_dts/blocks/src/system/componentFactory.d.ts +1 -0
  25. package/dist/_dts/blocks/src/system/constants/background.d.ts +1 -1
  26. package/dist/_dts/blocks/src/system/constants/blockKinds.d.ts +1 -1
  27. package/dist/_dts/blocks/src/system/customize/backgrounds.d.ts +13 -3
  28. package/dist/_dts/blocks/src/system/data/index.d.ts +2 -0
  29. package/dist/_dts/blocks/src/system/data/linkedContentEntries.d.ts +64 -0
  30. package/dist/_dts/blocks/src/system/data/loaderEndpoints.d.ts +1 -1
  31. package/dist/_dts/blocks/src/system/designCapabilities.d.ts +2 -2
  32. package/dist/_dts/blocks/src/system/fields/boxStyles.d.ts +6 -1
  33. package/dist/_dts/blocks/src/system/fragments/library/faqHeading.d.ts +0 -1
  34. package/dist/_dts/blocks/src/system/media/slotContracts.d.ts +2 -0
  35. package/dist/_dts/blocks/src/system/node/types.d.ts +8 -1
  36. package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +1 -1
  37. package/dist/_dts/blocks/src/system/registry.d.ts +1 -0
  38. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +44 -50
  39. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +21 -24
  40. package/dist/_dts/blocks/src/system/runtime/headingDecoration.d.ts +21 -0
  41. package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.d.ts +45 -14
  42. package/dist/_dts/blocks/src/system/runtime/hooks/useEventFilters.d.ts +2 -9
  43. package/dist/_dts/blocks/src/system/runtime/hooks/useEventPagination.d.ts +1 -10
  44. package/dist/_dts/blocks/src/system/runtime/nodes/basic.d.ts +1 -0
  45. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCalendar.interactive.d.ts +21 -16
  46. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCombined.client.d.ts +3 -2
  47. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCombined.interactive.d.ts +21 -16
  48. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventListing.interactive.d.ts +17 -28
  49. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventPaginatedListView.client.d.ts +2 -3
  50. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekAgendaView.d.ts +3 -3
  51. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/listOptions.d.ts +50 -0
  52. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/utils.d.ts +2 -1
  53. package/dist/_dts/blocks/src/system/runtime/nodes/media.d.ts +29 -14
  54. package/dist/_dts/blocks/src/system/runtime/nodes/offerings-preview.d.ts +21 -0
  55. package/dist/_dts/blocks/src/system/runtime/resolver.d.ts +7 -0
  56. package/dist/_dts/blocks/src/system/transforms/registry/_utils/sectionForegroundStyles.d.ts +2 -0
  57. package/dist/_dts/blocks/src/system/transforms/registry/boxStyles.d.ts +4 -2
  58. package/dist/_dts/calendar/src/grid.d.ts +27 -0
  59. package/dist/_dts/calendar/src/index.d.ts +2 -2
  60. package/dist/_dts/content-editor/src/controller/useAutosaveTriggers.d.ts +3 -1
  61. package/dist/_dts/content-editor/src/editorTransactionState.d.ts +1 -0
  62. package/dist/_dts/content-editor/src/hooks/autosaveScopePlanning.d.ts +26 -0
  63. package/dist/_dts/content-editor/src/hooks/publishability.d.ts +1 -0
  64. package/dist/_dts/content-editor/src/hooks/useLinkedEntryAutosave.d.ts +23 -0
  65. package/dist/_dts/content-editor/src/hooks/useReactiveToolbar.d.ts +1 -1
  66. package/dist/_dts/content-editor/src/hooks/useSimpleAutosave.d.ts +2 -2
  67. package/dist/_dts/content-editor/src/index.d.ts +3 -1
  68. package/dist/_dts/content-editor/src/lib/json.d.ts +2 -0
  69. package/dist/_dts/content-editor/src/linkedEntries/domain.d.ts +8 -0
  70. package/dist/_dts/content-editor/src/ui/editorSnapshot.d.ts +58 -0
  71. package/dist/_dts/content-editor/src/ui/editorTransactionState.d.ts +93 -0
  72. package/dist/_dts/content-editor/src/ui/editorUiStore.d.ts +60 -3
  73. package/dist/_dts/content-editor/src/ui/stores/autosaveStore.d.ts +2 -0
  74. package/dist/_dts/content-editor/src/ui/stores/types.d.ts +1 -1
  75. package/dist/_dts/content-model/src/content-types/builtins.d.ts +43 -0
  76. package/dist/_dts/content-model/src/metadataConfig.d.ts +87 -47
  77. package/dist/_dts/core/src/branded.d.ts +5 -0
  78. package/dist/_dts/core/src/index.d.ts +1 -0
  79. package/dist/_dts/core/src/stable-json.d.ts +1 -0
  80. package/dist/_dts/editor/src/EditorProvider.d.ts +26 -0
  81. package/dist/_dts/editor/src/chrome/EditorChrome.d.ts +26 -0
  82. package/dist/_dts/editor/src/chrome/EditorStatusBar.d.ts +45 -0
  83. package/dist/_dts/editor/src/chrome/statusBarPresentation.d.ts +25 -0
  84. package/dist/_dts/editor/src/components/EditingToggle.d.ts +9 -0
  85. package/dist/_dts/editor/src/hooks/useEditableElement.d.ts +18 -0
  86. package/dist/_dts/editor/src/index.d.ts +29 -0
  87. package/dist/_dts/editor/src/inline/InlineTextEditor.d.ts +1 -0
  88. package/dist/_dts/editor/src/lib/utils.d.ts +1 -0
  89. package/dist/_dts/editor/src/primitives/EditorPrimitivesProvider.d.ts +20 -0
  90. package/dist/_dts/editor/src/primitives/InlineFieldContext.d.ts +11 -0
  91. package/dist/_dts/editor/src/primitives/SaveContext.d.ts +12 -0
  92. package/dist/_dts/editor/src/primitives/helpers.d.ts +1 -0
  93. package/dist/_dts/editor/src/primitives/rich-text/RichTextEditorSurface.d.ts +26 -0
  94. package/dist/_dts/editor/src/primitives/rich-text/RichTextToolbar.d.ts +16 -0
  95. package/dist/_dts/editor/src/primitives/rich-text/config.d.ts +23 -0
  96. package/dist/_dts/editor/src/primitives/rich-text/extensions/RichTextImage.d.ts +2 -0
  97. package/dist/_dts/editor/src/primitives/rich-text/extensions/RichTextLink.d.ts +1 -0
  98. package/dist/_dts/editor/src/primitives/rich-text/imageActions.d.ts +22 -0
  99. package/dist/_dts/editor/src/primitives/rich-text/sanitize.d.ts +23 -0
  100. package/dist/_dts/editor/src/primitives/rich-text/toolbar/BlockTypeDropdown.d.ts +6 -0
  101. package/dist/_dts/editor/src/primitives/rich-text/toolbar/LinkPopover.d.ts +10 -0
  102. package/dist/_dts/editor/src/primitives/rich-text/toolbar/ToolbarIconButton.d.ts +11 -0
  103. package/dist/_dts/editor/src/primitives/rich-text/toolbar/linkPopoverUtils.d.ts +58 -0
  104. package/dist/_dts/editor/src/primitives/rich-text/useRichTextEditor.d.ts +19 -0
  105. package/dist/_dts/editor/src/primitives/rich-text/utils.d.ts +20 -0
  106. package/dist/_dts/editor/src/utils/optimisticTokens.d.ts +27 -0
  107. package/dist/_dts/editor-blocks/src/BlockEditor.d.ts +2 -2
  108. package/dist/_dts/editor-blocks/src/block-editor/BlockCard.d.ts +3 -3
  109. package/dist/_dts/editor-blocks/src/block-editor/BlockCardHeader.d.ts +4 -4
  110. package/dist/_dts/editor-blocks/src/block-editor/SortableBlockItem.d.ts +2 -2
  111. package/dist/_dts/editor-blocks/src/block-editor/actionPresentation.d.ts +15 -0
  112. package/dist/_dts/editor-blocks/src/block-editor/blockHeaderActionPlan.d.ts +49 -0
  113. package/dist/_dts/editor-blocks/src/widgets/index.d.ts +1 -0
  114. package/dist/_dts/editor-blocks/src/widgets/rich-text/RichTextWidgetProvider.d.ts +1 -1
  115. package/dist/_dts/editor-blocks/src/widgets/rich-text/useImagePicker.d.ts +1 -1
  116. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/SiteOptionPickerField.d.ts +3 -0
  117. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/SiteOptionWidgetProvider.d.ts +14 -0
  118. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/index.d.ts +6 -0
  119. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/loaders.d.ts +51 -0
  120. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/types.d.ts +20 -0
  121. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/utils.d.ts +5 -0
  122. package/dist/_dts/media-core/src/index.d.ts +1 -0
  123. package/dist/_dts/media-core/src/renderableMediaAsset.d.ts +18 -0
  124. package/dist/_dts/media-editor/src/MediaEditor.d.ts +4 -1
  125. package/dist/_dts/media-editor/src/MediaPickerDialog.d.ts +2 -1
  126. package/dist/_dts/media-editor/src/index.d.ts +1 -1
  127. package/dist/_dts/preview-next/src/client/blocks/BlockToolbar.d.ts +3 -2
  128. package/dist/_dts/preview-next/src/client/blocks/EditablePageRenderer.d.ts +5 -1
  129. package/dist/_dts/preview-next/src/client/blocks/blockCustomizePopoverRows.d.ts +25 -0
  130. package/dist/_dts/preview-next/src/client/blocks/composer/mergeDrafts.d.ts +2 -1
  131. package/dist/_dts/preview-next/src/client/blocks/composer/types.d.ts +4 -30
  132. package/dist/_dts/preview-next/src/client/blocks/linkedEntryRenderOverlay.d.ts +11 -0
  133. package/dist/_dts/preview-next/src/client/blocks/previewCustomizeActions.d.ts +3 -9
  134. package/dist/_dts/preview-next/src/client/blocks/useBlockCustomizeControls.d.ts +2 -1
  135. package/dist/_dts/preview-next/src/client/index.d.ts +0 -1
  136. package/dist/_dts/preview-next/src/client/preview/EntryEditorPanel.d.ts +3 -1
  137. package/dist/_dts/preview-next/src/client/preview/LinkedEntryEditEventBridge.d.ts +1 -0
  138. package/dist/_dts/preview-next/src/client/preview/PageBlockEditorPanel.d.ts +3 -1
  139. package/dist/_dts/preview-next/src/client/preview/PreviewChromeLayer.d.ts +20 -0
  140. package/dist/_dts/preview-next/src/client/preview/PreviewDesignHistoryOrderContext.d.ts +13 -0
  141. package/dist/_dts/preview-next/src/client/preview/PreviewOpenEditorButton.d.ts +1 -1
  142. package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +19 -19
  143. package/dist/_dts/preview-next/src/client/preview/PreviewSidebarSection.d.ts +13 -0
  144. package/dist/_dts/preview-next/src/client/preview/SiteChromeCustomizeContext.d.ts +47 -4
  145. package/dist/_dts/preview-next/src/client/preview/StyleConfigurator.state.d.ts +14 -1
  146. package/dist/_dts/preview-next/src/client/preview/linkedEntryEditEvents.d.ts +5 -0
  147. package/dist/_dts/preview-next/src/client/preview/previewChromePlacement.d.ts +46 -0
  148. package/dist/_dts/preview-next/src/client/preview/previewCustomizeControls.d.ts +68 -0
  149. package/dist/_dts/preview-next/src/client/preview/previewCustomizeHistory.d.ts +8 -1
  150. package/dist/_dts/preview-next/src/client/preview/previewCustomizeWorkflowState.d.ts +11 -9
  151. package/dist/_dts/preview-next/src/client/preview/previewDesignHistoryOrder.d.ts +18 -0
  152. package/dist/_dts/preview-next/src/client/preview/previewUndoDomains.d.ts +1 -1
  153. package/dist/_dts/preview-next/src/client/preview/siteChromeBrandPreferences.d.ts +41 -0
  154. package/dist/_dts/preview-next/src/client/preview/siteChromeQuickControlHistoryState.d.ts +101 -0
  155. package/dist/_dts/preview-next/src/client/preview/siteChromeSavePlan.d.ts +29 -0
  156. package/dist/_dts/preview-next/src/client/preview/siteStyleHistoryState.d.ts +55 -0
  157. package/dist/_dts/preview-next/src/client/preview/styleConfiguratorApplyPayload.d.ts +1 -0
  158. package/dist/_dts/preview-next/src/client/widgets/widgetAdapters.d.ts +2 -1
  159. package/dist/_dts/preview-next/src/client/widgets/widgetOverrides.d.ts +3 -2
  160. package/dist/_dts/preview-next/src/constants.d.ts +0 -1
  161. package/dist/_dts/sdk/src/cli/config-loader.d.ts +3 -1
  162. package/dist/_dts/sdk/src/cli/content/reader.d.ts +5 -3
  163. package/dist/_dts/sdk/src/cli/site-commands/commandSurfaceDispatch.d.ts +1 -0
  164. package/dist/_dts/sdk/src/cli/site-commands/pageCommands.d.ts +3 -1
  165. package/dist/_dts/sdk/src/cli/site-commands/pushExecution.d.ts +1 -0
  166. package/dist/_dts/sdk/src/config/types.d.ts +1 -1
  167. package/dist/_dts/sdk/src/config/validation.d.ts +3 -3
  168. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +28 -2
  169. package/dist/_dts/sdk/src/public-api/contracts.d.ts +10 -0
  170. package/dist/_dts/sdk/src/rendering/islands/types.d.ts +42 -0
  171. package/dist/_dts/sdk/src/rendering/mediaAssets.d.ts +4 -0
  172. package/dist/_dts/sdk/src/types/block-kinds.d.ts +1 -1
  173. package/dist/_dts/sdk/src/version.d.ts +1 -1
  174. package/dist/_dts/theme-core/src/buttons/constants.d.ts +26 -1
  175. package/dist/_dts/theme-core/src/buttons/personalities/index.d.ts +11 -0
  176. package/dist/_dts/theme-core/src/buttons/personalities/joy-stamp.d.ts +22 -0
  177. package/dist/_dts/theme-core/src/buttons/personalities/types.d.ts +1 -0
  178. package/dist/_dts/theme-core/src/buttons/types.d.ts +9 -0
  179. package/dist/_dts/theme-core/src/cards/siteStyleDefaults.d.ts +44 -0
  180. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  181. package/dist/_dts/theme-core/src/interactive/types.d.ts +10 -0
  182. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +27 -6
  183. package/dist/_dts/theme-core/src/palette/variants/index.d.ts +15 -0
  184. package/dist/_dts/theme-core/src/palette/variants/warm-dark.d.ts +27 -0
  185. package/dist/_dts/theme-core/src/schema.d.ts +40 -3
  186. package/dist/_dts/theme-core/src/site-styles/accessibleSurfaces.d.ts +4 -1
  187. package/dist/_dts/theme-core/src/site-styles/appearancePresetCatalog.d.ts +87 -31
  188. package/dist/_dts/theme-core/src/site-styles/appearancePresetResolution.d.ts +6 -2
  189. package/dist/_dts/theme-core/src/site-styles/blockAppearancePresets.d.ts +87 -31
  190. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +30 -1
  191. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +18 -2
  192. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +6 -2
  193. package/dist/_dts/theme-core/src/site-styles/headerLooks.d.ts +5 -0
  194. package/dist/_dts/theme-core/src/site-styles/index.d.ts +1 -0
  195. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorModel.d.ts +3 -23
  196. package/dist/_dts/theme-core/src/site-styles/pageDesignIntent.d.ts +276 -4
  197. package/dist/_dts/theme-core/src/site-styles/runtimeCompiler.d.ts +33 -3
  198. package/dist/_dts/theme-core/src/site-styles/sectionTransitions.d.ts +508 -0
  199. package/dist/_dts/theme-core/src/site-styles/sourceCatalogs.d.ts +10 -10
  200. package/dist/_dts/theme-core/src/site-styles/types.d.ts +24 -6
  201. package/dist/_dts/theme-core/src/site-styles/typographyPresets.d.ts +16 -1
  202. package/dist/{chunk-7PCYG6YV.mjs → chunk-3PTVPAT4.mjs} +1863 -1099
  203. package/dist/{chunk-5NZGBSJF.mjs → chunk-3QWQ2MUQ.mjs} +469 -1000
  204. package/dist/{chunk-HTB4BMKD.mjs → chunk-3VT7LZY2.mjs} +321 -113
  205. package/dist/{chunk-MPBNEVPB.mjs → chunk-4T5JIPWY.mjs} +1 -1
  206. package/dist/{chunk-BBLZSQEY.mjs → chunk-E3NZYEKF.mjs} +1 -7
  207. package/dist/{chunk-GYXKAUFV.mjs → chunk-WUHKQBB6.mjs} +9509 -2658
  208. package/dist/cli/index.mjs +1347 -324
  209. package/dist/client/{accordion-QFZMNB7O.mjs → accordion-SFIIZWNH.mjs} +2 -2
  210. package/dist/client/{bookingForm-NCIXFWIG.mjs → bookingForm-CFKZVWGA.mjs} +5 -5
  211. package/dist/client/bookings.mjs +281 -19
  212. package/dist/client/{carousel-EO4BJMWD.mjs → carousel-ELXWSBLA.mjs} +3 -3
  213. package/dist/client/{cart-FTYCLYUW.mjs → cart-6SSCPYY5.mjs} +6 -6
  214. package/dist/client/{checkout-NFGLVCOX.mjs → checkout-UFQ34HPV.mjs} +6 -6
  215. package/dist/client/{chunk-6GVVWSPU.mjs → chunk-76JUETXQ.mjs} +139 -186
  216. package/dist/client/{chunk-AUJ5DBZJ.mjs → chunk-7BXX7WXH.mjs} +112 -28
  217. package/dist/client/{chunk-IWFZSQNZ.mjs → chunk-G56BSZAM.mjs} +2 -2
  218. package/dist/client/{chunk-VDS5YFLX.mjs → chunk-IUEDYLFJ.mjs} +1 -1
  219. package/dist/client/{chunk-PYRBIURC.mjs → chunk-NP47RHIX.mjs} +5 -5
  220. package/dist/client/{chunk-PUOELU4O.mjs → chunk-NX6X3GRF.mjs} +330 -80
  221. package/dist/client/{chunk-Y37KDO7P.mjs → chunk-PA2LJH2M.mjs} +2 -2
  222. package/dist/client/{chunk-CE7IBGVW.mjs → chunk-RMWMT3ZD.mjs} +1 -1
  223. package/dist/client/{chunk-4W66ALYT.mjs → chunk-TCJS3NY5.mjs} +1 -1
  224. package/dist/client/{chunk-T6UIP6UZ.mjs → chunk-V6XI4M2Q.mjs} +1 -1
  225. package/dist/client/client.mjs +5924 -3138
  226. package/dist/client/{courseRegistration-2EORROPS.mjs → courseRegistration-HJIISAAG.mjs} +5 -5
  227. package/dist/client/{eventCalendar-WZI4VL6N.mjs → eventCalendar-F6YVUCZR.mjs} +7 -7
  228. package/dist/client/{eventCombined-QCB2TCNL.mjs → eventCombined-B5DSTIOK.mjs} +7 -7
  229. package/dist/client/{eventDetails-TFTATAPH.mjs → eventDetails-SEJDDHQC.mjs} +7 -7
  230. package/dist/client/{eventListing-A2CQEHEQ.mjs → eventListing-TPMWIIBO.mjs} +7 -7
  231. package/dist/client/{eventRegistration-OELPVLUY.mjs → eventRegistration-QU6W2PV6.mjs} +7 -7
  232. package/dist/client/{form-NTIFD6WQ.mjs → form-LVTYNTDE.mjs} +2 -2
  233. package/dist/client/{headerSection-7TBNKCLA.mjs → headerSection-SO3MFC4U.mjs} +54 -13
  234. package/dist/client/hooks.mjs +2266 -1179
  235. package/dist/client/{locationMap-LRLB5A3H.mjs → locationMap-5E5FLAKO.mjs} +2 -2
  236. package/dist/client/{newsletterForm-XI52RZSN.mjs → newsletterForm-GN32JEBQ.mjs} +2 -2
  237. package/dist/client/{productDetail-GSNKBFLY.mjs → productDetail-Q346KSVR.mjs} +6 -6
  238. package/dist/client/{productList-BGCGT4KZ.mjs → productList-DTDMQYGZ.mjs} +6 -6
  239. package/dist/client/rendering/client.mjs +4618 -3318
  240. package/dist/client/rendering/islands.mjs +32 -20
  241. package/dist/client/rendering.mjs +4898 -2108
  242. package/dist/client/{shop-RQHUCLNY.mjs → shop-OFUW7KPN.mjs} +6 -6
  243. package/dist/preview-next/client/runtime.mjs +5 -5
  244. package/dist/preview-next/constants.mjs +0 -2
  245. package/dist/preview-next/index.mjs +0 -2
  246. package/dist/{sdk-runtime-A4LHT2RS.mjs → sdk-runtime-2GIX6I7X.mjs} +6 -6
  247. package/dist/server/components.mjs +4377 -1852
  248. package/dist/server/config-validation.mjs +2283 -1303
  249. package/dist/server/config.mjs +2283 -1303
  250. package/dist/server/data.mjs +2267 -1180
  251. package/dist/server/index.mjs +2 -1
  252. package/dist/server/next.mjs +4394 -1773
  253. package/dist/server/page-converter.mjs +1999 -1015
  254. package/dist/server/prebuild.mjs +1 -1
  255. package/dist/server/rendering/server.mjs +4482 -1857
  256. package/dist/server/rendering.mjs +4484 -1859
  257. package/dist/server/routing.mjs +2302 -1120
  258. package/dist/server/server.mjs +2284 -1197
  259. package/dist/server/theme-bridge.mjs +757 -85
  260. package/dist/server/theme.mjs +69 -4
  261. package/dist/styles/index.css +474 -65
  262. package/package.json +1 -1
  263. package/dist/SdkPreviewModeRuntime-XE5M4UKM.mjs +0 -8
  264. package/dist/_dts/editor-blocks/src/block-editor/actionControlMode.d.ts +0 -2
  265. package/dist/_dts/preview-next/src/client/preview/previewSurface.d.ts +0 -3
@@ -1,11 +1,12 @@
1
1
  "use client";
2
- import { useOptionalPreviewLayout, PreviewQueryProvider } from './chunk-MPBNEVPB.mjs';
3
- import { PreviewThemeOverlayProvider, PreviewDesignCoordinatorProvider, PreviewBlockCustomizeSelectionProvider, resolveSiteStyleAccess, usePreviewDesignCoordinator, createOptimisticActionRunner, resolveStyleConfiguratorInitialSnapshot, createStyleConfiguratorState, syncStyleConfiguratorState, buildSiteChromeLookModels, planSiteStylePreviewChange, markApplyFailed, markApplySaved, markApplySaving, previewHeaderStyle, previewHeaderLayout, previewFooterLook, cycleSiteChromeLookId, usePreviewThemeOverlay, resolvePreviewDashboardSetupHandoff, buildPreviewDashboardSetupUrl, resolvePreviewSetupBannerModel, planSiteChromeLookSelection, usePreviewBlockCustomizeSelectionOptional, useBlockCustomizeControls, MAX_CUSTOMIZE_POPOVER_ROWS, MAX_QUICK_MANIFEST_FIELDS, sanitizeDashboardPath } from './chunk-5NZGBSJF.mjs';
4
- import { resolvePreviewChromeSurface, getPreviewSidebarTab, onPreviewSidebarOpenRequest, getPreviewChromeSurfaceDataProps, PREVIEW_CHROME_THEME_CLASS_NAME, setPreviewSidebarTab, PreviewPageDesignEditorStateProvider, usePreviewPageDesignEditorState, PREVIEW_SURFACE_PARAM } from './chunk-HTB4BMKD.mjs';
5
- import { clientComponentRegistry, deriveLaunchReadiness, DEFAULT_LAUNCH_READINESS_INPUT, useEditorUiStore, ResizableSplitPane, BlockApiProvider, EditorProvider, MediaEditorProvider, getCuratedSiteStyle, useEditor, createRoutingHelpers, createSnapshotFromPageRecord, EntryControllerProvider, useEntryEditorController, useEditorUiStoreInstance, surfaceResolutionContextFromTheme, unsafeAsEditorBlockId, EditableBlockProvider, asDesignBlockId, PageRenderer, planSiteDocument, ThemeScope, FixedBottomSurface, Button, unwrapResponse, X, ChevronLeft, ChevronRight, RotateCcw, parseBackgroundOverrideFromContent, rankBackgroundImagePoolItems, imagePoolForOverride, resolveBackgroundCapability, enabledBackgroundModes, FormRenderer, PreviewRichTextEditor, RichText, PreviewTextEditor, Text, buildThemeRuntime, renderBlock, siteHeaderManifest, siteFooterManifest, SiteDocumentLayout, SITE_CHROME_CLASS_NAMES, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, buildEditorModelRuntimeFieldsByBlockId, applyCompiledRuntimeFieldsToContent, assertNever2, getBlockDefinition, systemCustomizeFacetDefinitionsForBlock, systemCustomizeFacetAppliesToLayoutVariant, systemCustomizeFacetCurrentValue, systemCustomizeFacetLabelForValue, backgroundColorTokens, Image, useMediaEditorOptional, defaultBackgroundOverrideForMode, Upload, MediaEditor, visibleHeaderClearancePx, systemCustomizeFacetNextValue, planBackgroundTransition, GRADIENT_PRESETS, gradientToCss, parseMediaAssetId, planBackgroundOverride, EDITABLE_SURFACE_FORWARDING_PROP, assertNever, SITE_CHROME_VISIBLE_HEADER_SELECTOR } from './chunk-GYXKAUFV.mjs';
6
- import { API_ENDPOINTS, createBearerAPIClient, isPreviewRuntimeSessionEditable, DEFAULT_SURFACE_ID, initPreviewAuth, usePreviewAuth, usePreviewAuthFetch, markPreviewAuthExpired, usePreviewToken, parseSurfaceId } from './chunk-BBLZSQEY.mjs';
7
- import React9, { createContext, useMemo, useState, useEffect, useRef, useCallback, useId, useReducer, Children, isValidElement, Fragment as Fragment$1, cloneElement, useContext } from 'react';
2
+ import { useOptionalPreviewLayout, PreviewQueryProvider } from './chunk-4T5JIPWY.mjs';
3
+ import { PreviewBlockCustomizeSelectionProvider, resolveSiteStyleAccess, resolvePreviewDashboardSetupHandoff, buildPreviewDashboardSetupUrl, resolvePreviewSetupBannerModel, usePreviewBlockCustomizeSelectionOptional, useBlockCustomizeControls, MAX_CUSTOMIZE_POPOVER_ROWS, MAX_QUICK_MANIFEST_FIELDS, sanitizeDashboardPath } from './chunk-3QWQ2MUQ.mjs';
4
+ import { resolvePreviewChromeSurface, getPreviewSidebarTab, onPreviewSidebarOpenRequest, getPreviewChromeSurfaceDataProps, PREVIEW_CHROME_THEME_CLASS_NAME, setPreviewSidebarTab, PreviewPageDesignEditorStateProvider, usePreviewPageDesignEditorState } from './chunk-3VT7LZY2.mjs';
5
+ import { clientComponentRegistry, deriveLaunchReadiness, DEFAULT_LAUNCH_READINESS_INPUT, useEditorUiStore, PreviewThemeOverlayProvider, PreviewDesignCoordinatorProvider, SiteChromeCustomizeProvider, ResizableSplitPane, BlockApiProvider, EditorProvider, PreviewDesignHistoryOrderProvider, MediaEditorProvider, useEditorUiStoreInstance, useEditor, createRoutingHelpers, createSnapshotFromPageRecord, EntryControllerProvider, useEntryEditorController, surfaceResolutionContextFromTheme, unsafeAsEditorBlockId, EditableBlockProvider, asDesignBlockId, PageRenderer, usePreviewThemeOverlay, planSiteDocument, ThemeScope, FixedBottomSurface, Button, sectionTransitionVariantForBoundaryOptionId, sectionTransitionFamilyForBoundaryOptionId, sectionTransitionFamilies, X, defaultSectionTransitionVariantForFamily, RotateCcw, parseBackgroundOverrideFromContent, rankBackgroundImagePoolItems, imagePoolForOverride, resolveBackgroundCapability, enabledBackgroundModes, FormRenderer, stableSerialize, prefetchBlockData, isSupportedLoaderEndpoint, PreviewRichTextEditor, RichText, PreviewTextEditor, Text, buildThemeRuntime, renderBlock, siteHeaderManifest, siteFooterManifest, SiteDocumentLayout, SITE_CHROME_CLASS_NAMES, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, buildEditorModelRuntimeFieldsByBlockId, applyCompiledRuntimeFieldsToContent, assertNever2, getBlockDefinition, systemCustomizeFacetDefinitionsForBlock, systemCustomizeFacetAppliesToLayoutVariant, systemCustomizeFacetCurrentValue, systemCustomizeFacetLabelForValue, backgroundColorTokens, ChevronLeft, ChevronRight, createMicrobatcher, unwrapResponse, normalizePublicApiLoaderParams, visibleHeaderClearancePx, LINKED_CONTENT_ENTRY_META_KEY, unsafeAsLinkedContentEntryId, useMediaEditorOptional, Image, defaultBackgroundOverrideForMode, decodePublicProductCategorySelectorQuery, EDITABLE_SURFACE_FORWARDING_PROP, useSiteChromeCustomizeOptional, assertNever, SITE_CHROME_VISIBLE_HEADER_SELECTOR, GRADIENT_PRESETS, resolveImageUrlWithContext, gradientToCss, Upload, MediaEditor, SECTION_BACKGROUND_MIN_SOURCE_WIDTH, SECTION_BACKGROUND_MIN_SOURCE_HEIGHT, parseMediaAssetId, planBackgroundOverride, planBackgroundTransition, systemCustomizeFacetNextValue } from './chunk-WUHKQBB6.mjs';
6
+ import { API_ENDPOINTS, createBearerAPIClient, isPreviewRuntimeSessionEditable, DEFAULT_SURFACE_ID, initPreviewAuth, usePreviewAuth, usePreviewAuthFetch, markPreviewAuthExpired, usePreviewToken } from './chunk-E3NZYEKF.mjs';
7
+ import React10, { createContext, useMemo, useState, useEffect, useRef, useCallback, useId, useReducer, Children, isValidElement, Fragment as Fragment$1, cloneElement, useContext } from 'react';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
+ import { createPortal } from 'react-dom';
9
10
 
10
11
  // ../preview-next/src/client/utils/path.ts
11
12
  var PATH_SEPARATOR_REGEX = /\./g;
@@ -252,12 +253,90 @@ function blockHasPreviewContentFieldProjection(blockId, projections) {
252
253
  );
253
254
  }
254
255
 
256
+ // ../preview-next/src/client/blocks/linkedEntryRenderOverlay.ts
257
+ function applyLinkedEntryRenderOverlay(value, overlays) {
258
+ if (overlays.size === 0) return value;
259
+ return overlayValue(value, overlays);
260
+ }
261
+ function applyLinkedEntryRenderOverlayToDataContext(dataContext, overlays) {
262
+ if (!dataContext?.resolvedData || overlays.size === 0) {
263
+ return dataContext;
264
+ }
265
+ const resolvedData = applyLinkedEntryRenderOverlay(
266
+ dataContext.resolvedData,
267
+ overlays
268
+ );
269
+ if (resolvedData === dataContext.resolvedData) {
270
+ return dataContext;
271
+ }
272
+ return {
273
+ ...dataContext,
274
+ resolvedData
275
+ };
276
+ }
277
+ function overlayValue(value, overlays) {
278
+ if (Array.isArray(value)) {
279
+ let changed2 = false;
280
+ const next2 = value.map((item) => {
281
+ const overlaid = overlayValue(item, overlays);
282
+ if (overlaid !== item) changed2 = true;
283
+ return overlaid;
284
+ });
285
+ return changed2 ? next2 : value;
286
+ }
287
+ if (!isRecord3(value)) return value;
288
+ const meta = parseLinkedEntryMeta(value[LINKED_CONTENT_ENTRY_META_KEY]);
289
+ if (meta) {
290
+ const overlay = overlays.get(unsafeAsLinkedContentEntryId(meta.entryId));
291
+ if (overlay) {
292
+ const title = resolveOverlayTitle(overlay.content, meta);
293
+ return {
294
+ ...value,
295
+ title,
296
+ content: overlay.content,
297
+ optimisticHash: overlay.optimisticHash,
298
+ [LINKED_CONTENT_ENTRY_META_KEY]: {
299
+ ...meta,
300
+ title,
301
+ optimisticHash: overlay.optimisticHash
302
+ }
303
+ };
304
+ }
305
+ }
306
+ let changed = false;
307
+ const next = {};
308
+ for (const [key, child] of Object.entries(value)) {
309
+ const overlaid = overlayValue(child, overlays);
310
+ if (overlaid !== child) changed = true;
311
+ next[key] = overlaid;
312
+ }
313
+ return changed ? next : value;
314
+ }
315
+ function resolveOverlayTitle(content, meta) {
316
+ const titleField = meta.contentType.titleField;
317
+ if (titleField) {
318
+ const value = content[titleField];
319
+ if (typeof value === "string" && value.trim().length > 0) {
320
+ return value;
321
+ }
322
+ }
323
+ return meta.title;
324
+ }
325
+ function parseLinkedEntryMeta(value) {
326
+ if (!isRecord3(value)) return null;
327
+ return value.kind === "linked-content-entry" && typeof value.entryId === "string" ? value : null;
328
+ }
329
+ function isRecord3(value) {
330
+ return typeof value === "object" && value !== null && !Array.isArray(value);
331
+ }
332
+
255
333
  // ../preview-next/src/client/blocks/composer/mergeDrafts.ts
334
+ var EMPTY_LINKED_ENTRY_OVERLAY = /* @__PURE__ */ new Map();
256
335
  function getDraftSnapshots(uiStore) {
257
336
  const snapshots = uiStore.getState().drafts.inspect();
258
337
  return new Map(snapshots.map((snap) => [snap.id, snap]));
259
338
  }
260
- function mergeBlockWithDraft(block, draftMap, index, projections = []) {
339
+ function mergeBlockWithDraft(block, draftMap, index, projections = [], linkedEntryOverlays = EMPTY_LINKED_ENTRY_OVERLAY) {
261
340
  const id = block.id ?? null;
262
341
  const editorBlockId = id ? unsafeAsEditorBlockId(id) : null;
263
342
  const snap = editorBlockId ? draftMap.get(editorBlockId) ?? null : null;
@@ -269,20 +348,22 @@ function mergeBlockWithDraft(block, draftMap, index, projections = []) {
269
348
  kind: block.kind,
270
349
  purpose: "content",
271
350
  orderIndex: block.orderIndex ?? index,
272
- content: renderContent(block, baseline, projections),
273
- draftContent: renderContent(block, draftContent, projections),
351
+ content: renderContent(block, baseline, projections, linkedEntryOverlays),
352
+ draftContent: renderContent(block, draftContent, projections, linkedEntryOverlays),
274
353
  publishedContent: renderContent(
275
354
  block,
276
355
  block.publishedContent ?? null,
277
- projections
356
+ projections,
357
+ EMPTY_LINKED_ENTRY_OVERLAY
278
358
  ),
279
359
  bindings: block.bindings ?? null
280
360
  };
281
361
  }
282
- function renderContent(block, content, projections) {
362
+ function renderContent(block, content, projections, linkedEntryOverlays) {
363
+ const overlaidContent = applyLinkedEntryRenderOverlay(content, linkedEntryOverlays);
283
364
  return applyPreviewRuntimeContentProjections(
284
365
  block,
285
- content,
366
+ overlaidContent,
286
367
  projections
287
368
  );
288
369
  }
@@ -333,18 +414,20 @@ function composePageFromBlocks(args, cacheRef) {
333
414
  const aiProjections = previewProjectionsFromAiOverlays(
334
415
  uiStore.getState().aiOverlays
335
416
  );
417
+ const linkedEntryOverlays = getLinkedEntryRenderOverlays(uiStore);
336
418
  return composePreviewRenderPage(
337
419
  {
338
420
  page,
339
421
  blockItems,
340
422
  draftSnapshots: Array.from(draftMap.values()),
341
- projections: [...aiProjections, ...projections]
423
+ projections: [...aiProjections, ...projections],
424
+ linkedEntryOverlays
342
425
  },
343
426
  cacheRef
344
427
  );
345
428
  }
346
429
  function composePreviewRenderPage(args, cacheRef) {
347
- const { page, blockItems, draftSnapshots, projections } = args;
430
+ const { page, blockItems, draftSnapshots, projections, linkedEntryOverlays } = args;
348
431
  if (!page) return page;
349
432
  if (!blockItems.length) return page;
350
433
  const draftMap = draftSnapshotMap(draftSnapshots);
@@ -353,7 +436,7 @@ function composePreviewRenderPage(args, cacheRef) {
353
436
  projections
354
437
  );
355
438
  const enrichedBlocks = projectedBlockItems.map(
356
- (block, index) => mergeBlockWithDraft(block, draftMap, index, projections)
439
+ (block, index) => mergeBlockWithDraft(block, draftMap, index, projections, linkedEntryOverlays)
357
440
  );
358
441
  const sortedBlocks = [...enrichedBlocks].sort(
359
442
  (a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0)
@@ -361,9 +444,35 @@ function composePreviewRenderPage(args, cacheRef) {
361
444
  const renderedBlocks = applyCacheOptimization(sortedBlocks, cacheRef);
362
445
  return { ...page, blocks: renderedBlocks };
363
446
  }
447
+ function getLinkedEntryRenderOverlays(uiStore) {
448
+ const linkedEntries = uiStore.getState().linkedEntries.inspect();
449
+ const overlays = /* @__PURE__ */ new Map();
450
+ for (const linkedEntry of linkedEntries) {
451
+ const content = linkedEntry.current ?? linkedEntry.baseline;
452
+ if (!content) continue;
453
+ overlays.set(linkedEntry.meta.entryId, {
454
+ content,
455
+ optimisticHash: linkedEntry.meta.optimisticHash
456
+ });
457
+ }
458
+ return overlays;
459
+ }
364
460
  function draftSnapshotMap(snapshots) {
365
461
  return new Map(snapshots.map((snap) => [snap.id, snap]));
366
462
  }
463
+
464
+ // ../preview-next/src/client/preview/linkedEntryEditEvents.ts
465
+ var LINKED_ENTRY_EDIT_EVENT = "riverbank:preview:linked-entry-edit";
466
+ function dispatchLinkedEntryEditEvent(target) {
467
+ window.dispatchEvent(new CustomEvent(LINKED_ENTRY_EDIT_EVENT, {
468
+ detail: target
469
+ }));
470
+ }
471
+ function isLinkedEntryEditEvent(event) {
472
+ if (!(event instanceof CustomEvent)) return false;
473
+ const detail = event.detail;
474
+ return Boolean(detail) && typeof detail === "object" && detail.kind === "linked-content-entry";
475
+ }
367
476
  function requireClientRegistryComponent(key) {
368
477
  const component = clientComponentRegistry[key];
369
478
  if (!component) {
@@ -388,6 +497,37 @@ var RichTextWithEditor = ({ value, path, ...rest }) => {
388
497
  }
389
498
  return /* @__PURE__ */ jsx(RichText, { ...rest, path, doc: value });
390
499
  };
500
+ var LinkedEntryEditFrame = ({ target, children }) => {
501
+ const { isEditingMode } = useEditor();
502
+ if (!isEditingMode) {
503
+ return /* @__PURE__ */ jsx(Fragment, { children });
504
+ }
505
+ return /* @__PURE__ */ jsxs(
506
+ "div",
507
+ {
508
+ className: "group/linked-entry-edit relative",
509
+ "data-linked-entry-id": target.entryId,
510
+ "data-linked-entry-type": target.contentTypeKey,
511
+ children: [
512
+ children,
513
+ /* @__PURE__ */ jsx(
514
+ "button",
515
+ {
516
+ type: "button",
517
+ "aria-label": `Edit ${target.contentTypeLabel}: ${target.title}`,
518
+ className: "absolute right-2 top-2 z-20 rounded-full border border-white/80 bg-neutral-950/85 px-3 py-1 text-xs font-medium text-white opacity-0 shadow-sm transition focus:opacity-100 group-hover/linked-entry-edit:opacity-100",
519
+ onClick: (event) => {
520
+ event.preventDefault();
521
+ event.stopPropagation();
522
+ dispatchLinkedEntryEditEvent(target);
523
+ },
524
+ children: "Edit"
525
+ }
526
+ )
527
+ ]
528
+ }
529
+ );
530
+ };
391
531
  var ButtonWithEditorInterception = (props) => {
392
532
  const { isEditingMode } = useEditor();
393
533
  if (!isEditingMode) {
@@ -411,14 +551,599 @@ var editorRegistry = {
411
551
  text: TextWithEditor,
412
552
  richText: RichTextWithEditor,
413
553
  button: ButtonWithEditorInterception,
414
- media: requireClientRegistryComponent("media")
554
+ media: requireClientRegistryComponent("media"),
555
+ linkedEntryEditFrame: LinkedEntryEditFrame
415
556
  };
416
557
 
558
+ // ../preview-next/src/client/preview/previewChromePlacement.ts
559
+ var PREVIEW_CHROME_EDGE_OFFSET_PX = 8;
560
+ var DEFAULT_PREVIEW_CHROME_FLOATING_PLACEMENT = Object.freeze({
561
+ kind: "anchor-top-right",
562
+ topPx: PREVIEW_CHROME_EDGE_OFFSET_PX,
563
+ rightPx: PREVIEW_CHROME_EDGE_OFFSET_PX
564
+ });
565
+ function headerClearanceFromPx(px) {
566
+ return Number.isFinite(px) && px > 0 ? { kind: "overlay", px: Math.ceil(px) } : { kind: "none" };
567
+ }
568
+ function findFirstEditableContentBlockIndex(blocks) {
569
+ const index = blocks.findIndex((block) => block.kind !== "block.site-header");
570
+ return index >= 0 ? index : null;
571
+ }
572
+ function blockReceivesHeaderClearance(blocks, blockIndex) {
573
+ return findFirstEditableContentBlockIndex(blocks) === blockIndex;
574
+ }
575
+ function planBlockToolbarPlacement(input) {
576
+ if (input.headerClearance.kind === "overlay" && blockReceivesHeaderClearance(input.blocks, input.blockIndex)) {
577
+ return {
578
+ kind: "anchor-top-right",
579
+ topPx: input.headerClearance.px + PREVIEW_CHROME_EDGE_OFFSET_PX,
580
+ rightPx: PREVIEW_CHROME_EDGE_OFFSET_PX
581
+ };
582
+ }
583
+ return {
584
+ kind: "anchor-top-right",
585
+ topPx: PREVIEW_CHROME_EDGE_OFFSET_PX,
586
+ rightPx: PREVIEW_CHROME_EDGE_OFFSET_PX
587
+ };
588
+ }
589
+ function planSiteChromeCustomizePlacement(input) {
590
+ if (input.surface === "header" && !isUsableAnchorBox(input.anchorBox)) {
591
+ return {
592
+ kind: "viewport-top-right",
593
+ topPx: PREVIEW_CHROME_EDGE_OFFSET_PX * 2,
594
+ rightPx: PREVIEW_CHROME_EDGE_OFFSET_PX * 2
595
+ };
596
+ }
597
+ return {
598
+ kind: "anchor-top-right",
599
+ topPx: PREVIEW_CHROME_EDGE_OFFSET_PX,
600
+ rightPx: PREVIEW_CHROME_EDGE_OFFSET_PX
601
+ };
602
+ }
603
+ function resolvePreviewChromeFloatingPosition(input) {
604
+ if (input.placement.kind === "viewport-top-right") {
605
+ return {
606
+ topPx: input.placement.topPx,
607
+ rightPx: input.placement.rightPx
608
+ };
609
+ }
610
+ const anchorBox = input.anchorBox;
611
+ if (!isUsableAnchorBox(anchorBox)) {
612
+ return {
613
+ topPx: input.placement.topPx,
614
+ rightPx: input.placement.rightPx
615
+ };
616
+ }
617
+ return {
618
+ topPx: Math.max(
619
+ PREVIEW_CHROME_EDGE_OFFSET_PX,
620
+ anchorBox.top - (input.containerTopEdgePx ?? 0) + input.placement.topPx
621
+ ),
622
+ rightPx: Math.max(
623
+ PREVIEW_CHROME_EDGE_OFFSET_PX,
624
+ input.containerRightEdgePx - anchorBox.right + input.placement.rightPx
625
+ )
626
+ };
627
+ }
628
+ function isUsableAnchorBox(box) {
629
+ return Boolean(
630
+ box && Number.isFinite(box.top) && Number.isFinite(box.right) && Number.isFinite(box.bottom) && Number.isFinite(box.height) && box.height > 0
631
+ );
632
+ }
633
+ var PreviewChromeLayerContext = createContext(null);
634
+ function PreviewChromeLayerProvider({
635
+ children
636
+ }) {
637
+ const [portalTarget, setPortalTarget] = useState(null);
638
+ const listenersRef = useRef(/* @__PURE__ */ new Set());
639
+ const notifyLayout = useCallback(() => {
640
+ listenersRef.current.forEach((listener) => listener());
641
+ }, []);
642
+ const subscribeLayout = useCallback((listener) => {
643
+ listenersRef.current.add(listener);
644
+ return () => {
645
+ listenersRef.current.delete(listener);
646
+ };
647
+ }, []);
648
+ useEffect(() => {
649
+ let frame = null;
650
+ const scheduleNotify = () => {
651
+ if (frame !== null) return;
652
+ frame = window.requestAnimationFrame(() => {
653
+ frame = null;
654
+ notifyLayout();
655
+ });
656
+ };
657
+ const mutationObserver = typeof MutationObserver !== "undefined" ? new MutationObserver(scheduleNotify) : null;
658
+ mutationObserver?.observe(document.body, {
659
+ attributes: true,
660
+ childList: true,
661
+ subtree: true
662
+ });
663
+ window.addEventListener("resize", scheduleNotify);
664
+ window.addEventListener("scroll", scheduleNotify, true);
665
+ return () => {
666
+ if (frame !== null) {
667
+ window.cancelAnimationFrame(frame);
668
+ }
669
+ mutationObserver?.disconnect();
670
+ window.removeEventListener("resize", scheduleNotify);
671
+ window.removeEventListener("scroll", scheduleNotify, true);
672
+ };
673
+ }, [notifyLayout]);
674
+ const value = useMemo(
675
+ () => ({ portalTarget, subscribeLayout }),
676
+ [portalTarget, subscribeLayout]
677
+ );
678
+ return /* @__PURE__ */ jsxs(PreviewChromeLayerContext.Provider, { value, children: [
679
+ children,
680
+ /* @__PURE__ */ jsx(
681
+ "div",
682
+ {
683
+ ref: setPortalTarget,
684
+ className: "pointer-events-none fixed inset-0 z-[70]",
685
+ "data-preview-chrome-layer": ""
686
+ }
687
+ )
688
+ ] });
689
+ }
690
+ function usePreviewChromeLayerOptional() {
691
+ return useContext(PreviewChromeLayerContext);
692
+ }
693
+ function PreviewChromeFloatingControl({
694
+ anchorSelector,
695
+ placement,
696
+ fallbackClassName,
697
+ fallbackStyle,
698
+ portalClassName = "absolute z-[70] flex gap-1 transition",
699
+ forceVisible = false,
700
+ children
701
+ }) {
702
+ const layer = usePreviewChromeLayerOptional();
703
+ const probeRef = useRef(null);
704
+ const anchorElementRef = useRef(null);
705
+ const [controlElement, setControlElement] = useState(
706
+ null
707
+ );
708
+ const [anchorElement, setAnchorElement] = useState(null);
709
+ const [anchorBox, setAnchorBox] = useState(
710
+ null
711
+ );
712
+ const [themeCustomProperties, setThemeCustomProperties] = useState({});
713
+ const [isActive, setIsActive] = useState(false);
714
+ const hoverStateRef = useRef({ anchor: false, control: false });
715
+ useEffect(() => {
716
+ if (!layer?.portalTarget) return;
717
+ let resizeObserver = null;
718
+ const readAnchor = () => probeRef.current?.closest(anchorSelector);
719
+ const update = () => {
720
+ const anchor = readAnchor();
721
+ if (anchor !== anchorElementRef.current) {
722
+ resizeObserver?.disconnect();
723
+ resizeObserver = null;
724
+ anchorElementRef.current = anchor;
725
+ setAnchorElement(anchor);
726
+ if (anchor && typeof ResizeObserver !== "undefined") {
727
+ resizeObserver = new ResizeObserver(update);
728
+ resizeObserver.observe(anchor);
729
+ }
730
+ }
731
+ if (!anchor) {
732
+ setAnchorBox(null);
733
+ setThemeCustomProperties({});
734
+ return;
735
+ }
736
+ const rect = anchor.getBoundingClientRect();
737
+ setThemeCustomProperties(readThemeCustomProperties(anchor));
738
+ setAnchorBox({
739
+ top: rect.top,
740
+ right: rect.right,
741
+ bottom: rect.bottom,
742
+ height: rect.height
743
+ });
744
+ };
745
+ update();
746
+ const unsubscribe = layer.subscribeLayout(update);
747
+ return () => {
748
+ resizeObserver?.disconnect();
749
+ anchorElementRef.current = null;
750
+ unsubscribe();
751
+ };
752
+ }, [anchorSelector, layer]);
753
+ useEffect(() => {
754
+ if (!anchorElement || !controlElement) return;
755
+ hoverStateRef.current = { anchor: false, control: false };
756
+ const show = (event) => {
757
+ updatePreviewChromeHoverState(
758
+ hoverStateRef.current,
759
+ event.currentTarget,
760
+ anchorElement,
761
+ controlElement,
762
+ true
763
+ );
764
+ setIsActive(true);
765
+ };
766
+ const hideIfFocusLeft = (event) => {
767
+ updatePreviewChromeHoverState(
768
+ hoverStateRef.current,
769
+ event.currentTarget,
770
+ anchorElement,
771
+ controlElement,
772
+ false
773
+ );
774
+ window.setTimeout(() => {
775
+ const active = document.activeElement;
776
+ const hoverState = hoverStateRef.current;
777
+ const nextIsActive = Boolean(
778
+ hoverState.anchor || hoverState.control || active && (anchorElement.contains(active) || controlElement.contains(active))
779
+ );
780
+ setIsActive(nextIsActive);
781
+ }, 0);
782
+ };
783
+ anchorElement.addEventListener("mouseenter", show);
784
+ anchorElement.addEventListener("mouseleave", hideIfFocusLeft);
785
+ anchorElement.addEventListener("focusin", show);
786
+ anchorElement.addEventListener("focusout", hideIfFocusLeft);
787
+ controlElement.addEventListener("mouseenter", show);
788
+ controlElement.addEventListener("mouseleave", hideIfFocusLeft);
789
+ controlElement.addEventListener("focusin", show);
790
+ controlElement.addEventListener("focusout", hideIfFocusLeft);
791
+ return () => {
792
+ anchorElement.removeEventListener("mouseenter", show);
793
+ anchorElement.removeEventListener("mouseleave", hideIfFocusLeft);
794
+ anchorElement.removeEventListener("focusin", show);
795
+ anchorElement.removeEventListener("focusout", hideIfFocusLeft);
796
+ controlElement.removeEventListener("mouseenter", show);
797
+ controlElement.removeEventListener("mouseleave", hideIfFocusLeft);
798
+ controlElement.removeEventListener("focusin", show);
799
+ controlElement.removeEventListener("focusout", hideIfFocusLeft);
800
+ };
801
+ }, [anchorElement, controlElement]);
802
+ if (!layer?.portalTarget) {
803
+ return /* @__PURE__ */ jsx("div", { className: fallbackClassName, style: fallbackStyle, children });
804
+ }
805
+ const resolvedPlacement = typeof placement === "function" ? placement(anchorBox) : placement;
806
+ const position = resolvePreviewChromeFloatingPosition({
807
+ anchorBox,
808
+ containerTopEdgePx: resolvePortalTopEdgePx(layer.portalTarget),
809
+ containerRightEdgePx: resolvePortalRightEdgePx(layer.portalTarget),
810
+ placement: resolvedPlacement
811
+ });
812
+ const isVisible = forceVisible || isActive;
813
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
814
+ /* @__PURE__ */ jsx("span", { ref: probeRef, "aria-hidden": "true", style: { display: "contents" } }),
815
+ createPortal(
816
+ /* @__PURE__ */ jsx(
817
+ "div",
818
+ {
819
+ ref: setControlElement,
820
+ className: joinClassNames(
821
+ portalClassName,
822
+ isVisible ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
823
+ ),
824
+ style: {
825
+ ...themeCustomProperties,
826
+ top: position.topPx,
827
+ right: position.rightPx
828
+ },
829
+ children
830
+ }
831
+ ),
832
+ layer.portalTarget
833
+ )
834
+ ] });
835
+ }
836
+ function readThemeCustomProperties(element) {
837
+ if (typeof window === "undefined") return {};
838
+ const computedStyle = window.getComputedStyle(element);
839
+ const properties = {};
840
+ copyThemeCustomProperties(computedStyle, properties);
841
+ for (let current = element; current; current = current.parentElement) {
842
+ copyThemeCustomProperties(current.style, properties);
843
+ }
844
+ return properties;
845
+ }
846
+ function copyThemeCustomProperties(style, target) {
847
+ for (let index = 0; index < style.length; index += 1) {
848
+ const propertyName = style.item(index);
849
+ if (!propertyName.startsWith("--tb-")) continue;
850
+ const propertyValue = style.getPropertyValue(propertyName).trim();
851
+ if (propertyValue.length > 0 && target[propertyName] === void 0) {
852
+ target[propertyName] = propertyValue;
853
+ }
854
+ }
855
+ }
856
+ function resolvePortalRightEdgePx(portalTarget) {
857
+ const rect = portalTarget.getBoundingClientRect();
858
+ if (Number.isFinite(rect.right) && rect.right > 0) {
859
+ return rect.right;
860
+ }
861
+ if (typeof window === "undefined") {
862
+ return 0;
863
+ }
864
+ return window.innerWidth;
865
+ }
866
+ function resolvePortalTopEdgePx(portalTarget) {
867
+ const rect = portalTarget.getBoundingClientRect();
868
+ return Number.isFinite(rect.top) ? rect.top : 0;
869
+ }
870
+ function joinClassNames(...values) {
871
+ const className = values.filter(Boolean).join(" ");
872
+ return className.length > 0 ? className : void 0;
873
+ }
874
+ function updatePreviewChromeHoverState(hoverState, currentTarget, anchorElement, controlElement, value) {
875
+ if (currentTarget === anchorElement) {
876
+ hoverState.anchor = value;
877
+ } else if (currentTarget === controlElement) {
878
+ hoverState.control = value;
879
+ }
880
+ }
881
+ function useDialogFocusTrap(input) {
882
+ const { open, onClose } = input;
883
+ const dialogRef = useRef(null);
884
+ const initialFocusRef = useRef(null);
885
+ const returnFocusRef = useRef(null);
886
+ useEffect(() => {
887
+ if (open) {
888
+ initialFocusRef.current?.focus();
889
+ return;
890
+ }
891
+ const returnFocusTarget = returnFocusRef.current;
892
+ returnFocusRef.current = null;
893
+ returnFocusTarget?.focus();
894
+ }, [open]);
895
+ const registerInitialFocus = useCallback((node) => {
896
+ initialFocusRef.current = node;
897
+ }, []);
898
+ const setReturnFocusTarget = useCallback((node) => {
899
+ returnFocusRef.current = node;
900
+ }, []);
901
+ const handleDialogKeyDown = useCallback(
902
+ (event) => {
903
+ if (event.key === "Escape") {
904
+ event.preventDefault();
905
+ onClose();
906
+ return;
907
+ }
908
+ if (event.key !== "Tab") return;
909
+ const focusableElements = focusableElementsIn(dialogRef.current);
910
+ if (focusableElements.length === 0) {
911
+ event.preventDefault();
912
+ return;
913
+ }
914
+ const firstElement = focusableElements[0];
915
+ const lastElement = focusableElements[focusableElements.length - 1];
916
+ if (!firstElement || !lastElement) return;
917
+ if (event.shiftKey && document.activeElement === firstElement) {
918
+ event.preventDefault();
919
+ lastElement.focus();
920
+ return;
921
+ }
922
+ if (!event.shiftKey && document.activeElement === lastElement) {
923
+ event.preventDefault();
924
+ firstElement.focus();
925
+ }
926
+ },
927
+ [onClose]
928
+ );
929
+ return {
930
+ dialogRef,
931
+ registerInitialFocus,
932
+ setReturnFocusTarget,
933
+ handleDialogKeyDown
934
+ };
935
+ }
936
+ function focusableElementsIn(root) {
937
+ if (!root) return [];
938
+ return Array.from(
939
+ root.querySelectorAll(
940
+ 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
941
+ )
942
+ ).filter((element) => !element.hasAttribute("disabled") && element.getAttribute("aria-hidden") !== "true");
943
+ }
944
+
417
945
  // ../preview-next/src/client/preview/previewCustomizeControlStyles.ts
418
946
  var previewCustomizePillButtonClassName = "rounded-full bg-black/80 px-2.5 py-1 text-xs text-white shadow hover:bg-black";
947
+ function useCustomizePopoverController() {
948
+ const panelId = useId();
949
+ const descriptionId = `${panelId}-description`;
950
+ const [open, setOpen] = useState(false);
951
+ const buttonRef = useRef(null);
952
+ const close = useCallback(() => {
953
+ setOpen(false);
954
+ }, []);
955
+ const {
956
+ dialogRef,
957
+ registerInitialFocus,
958
+ setReturnFocusTarget,
959
+ handleDialogKeyDown
960
+ } = useDialogFocusTrap({
961
+ open,
962
+ onClose: close
963
+ });
964
+ const toggle = useCallback(
965
+ (event) => {
966
+ event.preventDefault();
967
+ event.stopPropagation();
968
+ if (!open) {
969
+ setReturnFocusTarget(
970
+ event.currentTarget instanceof HTMLElement ? event.currentTarget : buttonRef.current
971
+ );
972
+ }
973
+ setOpen((current) => !current);
974
+ },
975
+ [open, setReturnFocusTarget]
976
+ );
977
+ return {
978
+ open,
979
+ close,
980
+ toggle,
981
+ buttonRef,
982
+ panelId,
983
+ descriptionId,
984
+ dialogRef,
985
+ registerInitialFocus,
986
+ handleDialogKeyDown
987
+ };
988
+ }
989
+ function CustomizePillButton({
990
+ buttonRef,
991
+ ariaLabel,
992
+ open,
993
+ controlsId,
994
+ onClick,
995
+ disabled = false
996
+ }) {
997
+ return /* @__PURE__ */ jsx(
998
+ "button",
999
+ {
1000
+ ref: buttonRef,
1001
+ type: "button",
1002
+ onClick,
1003
+ className: previewCustomizePillButtonClassName,
1004
+ "aria-label": ariaLabel,
1005
+ "aria-expanded": open,
1006
+ "aria-haspopup": "dialog",
1007
+ "aria-controls": open ? controlsId : void 0,
1008
+ disabled,
1009
+ children: "Customize"
1010
+ }
1011
+ );
1012
+ }
1013
+ function CustomizePopoverShell({
1014
+ panelId,
1015
+ descriptionId,
1016
+ dialogLabel,
1017
+ eyebrow,
1018
+ title,
1019
+ dialogRef,
1020
+ closeButtonRef,
1021
+ onClose,
1022
+ onKeyDown,
1023
+ spacing = "normal",
1024
+ children
1025
+ }) {
1026
+ return /* @__PURE__ */ jsxs(
1027
+ "div",
1028
+ {
1029
+ id: panelId,
1030
+ className: "builder-dashboard-chrome-theme fixed inset-x-3 bottom-[calc(var(--builder-editor-chrome-offset,0px)+1rem)] z-[70] rounded-lg border bg-background p-3 text-foreground shadow-xl sm:absolute sm:inset-x-auto sm:bottom-auto sm:right-0 sm:top-9 sm:w-72",
1031
+ role: "dialog",
1032
+ "aria-modal": "true",
1033
+ "aria-label": dialogLabel,
1034
+ "aria-describedby": descriptionId,
1035
+ ref: dialogRef,
1036
+ onKeyDown,
1037
+ onClick: (event) => event.stopPropagation(),
1038
+ children: [
1039
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-start justify-between gap-3", children: [
1040
+ /* @__PURE__ */ jsxs("div", { children: [
1041
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase text-muted-foreground", children: eyebrow }),
1042
+ /* @__PURE__ */ jsx("div", { id: descriptionId, className: "text-sm font-semibold", children: title })
1043
+ ] }),
1044
+ /* @__PURE__ */ jsx(
1045
+ "button",
1046
+ {
1047
+ ref: closeButtonRef,
1048
+ type: "button",
1049
+ className: "rounded-full px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
1050
+ onClick: (event) => {
1051
+ event.preventDefault();
1052
+ event.stopPropagation();
1053
+ onClose();
1054
+ },
1055
+ children: "Close"
1056
+ }
1057
+ )
1058
+ ] }),
1059
+ /* @__PURE__ */ jsx("div", { className: spacing === "compact" ? "space-y-2" : "space-y-3", children })
1060
+ ]
1061
+ }
1062
+ );
1063
+ }
1064
+ function CustomizeCycleRow({
1065
+ label,
1066
+ valueLabel,
1067
+ previousLabel,
1068
+ nextLabel,
1069
+ availability,
1070
+ onPrevious,
1071
+ onNext
1072
+ }) {
1073
+ const reasonId = useId();
1074
+ const disabled = availability.kind === "unavailable";
1075
+ const unavailableReason = availability.kind === "unavailable" ? cycleUnavailableReasonLabel(availability.reason) : null;
1076
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1077
+ /* @__PURE__ */ jsxs("div", { children: [
1078
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: label }),
1079
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: valueLabel })
1080
+ ] }),
1081
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
1082
+ unavailableReason ? /* @__PURE__ */ jsx("span", { id: reasonId, className: "sr-only", children: unavailableReason }) : null,
1083
+ /* @__PURE__ */ jsx(
1084
+ "button",
1085
+ {
1086
+ type: "button",
1087
+ onClick: (event) => {
1088
+ event.preventDefault();
1089
+ event.stopPropagation();
1090
+ onPrevious();
1091
+ },
1092
+ className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1093
+ "aria-label": previousLabel,
1094
+ "aria-describedby": unavailableReason ? reasonId : void 0,
1095
+ title: unavailableReason ?? void 0,
1096
+ disabled,
1097
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
1098
+ }
1099
+ ),
1100
+ /* @__PURE__ */ jsx(
1101
+ "button",
1102
+ {
1103
+ type: "button",
1104
+ onClick: (event) => {
1105
+ event.preventDefault();
1106
+ event.stopPropagation();
1107
+ onNext();
1108
+ },
1109
+ className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1110
+ "aria-label": nextLabel,
1111
+ "aria-describedby": unavailableReason ? reasonId : void 0,
1112
+ title: unavailableReason ?? void 0,
1113
+ disabled,
1114
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
1115
+ }
1116
+ )
1117
+ ] })
1118
+ ] });
1119
+ }
1120
+ function cycleAvailability(input) {
1121
+ if (input.kind === "available") {
1122
+ return input;
1123
+ }
1124
+ return { kind: "unavailable", reason: input.kind };
1125
+ }
1126
+ function cycleUnavailableReasonLabel(reason) {
1127
+ if (reason === "preview-only") {
1128
+ return "Preview-only users cannot change this control.";
1129
+ }
1130
+ if (reason === "single-option") {
1131
+ return "There are no other options to cycle to.";
1132
+ }
1133
+ return reason;
1134
+ }
1135
+ function cycleAvailabilityFromFlags(input) {
1136
+ if (!input.applyAllowed) {
1137
+ return { kind: "unavailable", reason: "preview-only" };
1138
+ }
1139
+ if (!input.canCycle) {
1140
+ return { kind: "unavailable", reason: "single-option" };
1141
+ }
1142
+ return { kind: "available" };
1143
+ }
419
1144
 
420
1145
  // ../preview-next/src/client/blocks/isRecord.ts
421
- function isRecord3(value) {
1146
+ function isRecord4(value) {
422
1147
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
423
1148
  }
424
1149
 
@@ -484,35 +1209,18 @@ function setPageDesignBlockBackground(input) {
484
1209
  });
485
1210
  const plan = planBackgroundOverride(current, capability, input.override);
486
1211
  if (plan.kind !== "apply") return null;
1212
+ const imageAsset = plan.override.kind === "image" ? imagePoolAssetForMediaId(input.imagePool, plan.override.mediaId) : void 0;
487
1213
  return {
488
1214
  kind: "set-background",
489
1215
  blockId: input.block.blockId,
490
1216
  override: plan.override,
491
1217
  fromOverride: plan.fromOverride,
492
- baseContentId: input.block.contentRevision?.contentId ?? null
1218
+ baseContentId: input.block.contentRevision?.contentId ?? null,
1219
+ ...imageAsset ? { imageAsset } : {}
493
1220
  };
494
1221
  }
495
- function cyclePageDesignBoundaryBreak(boundary, direction) {
496
- if (boundary.availableBreaks.length === 0) return null;
497
- const breakIds = boundary.availableBreaks.map((option) => option.id);
498
- const values = [null, ...breakIds];
499
- const currentIndex = cycleChoiceIndex(boundary.breakChoice, values, direction);
500
- if (currentIndex === -1) return null;
501
- const nextIndex = direction === "next" ? (currentIndex + 1) % values.length : currentIndex === 0 ? values.length - 1 : currentIndex - 1;
502
- const boundaryOptionId = values[nextIndex];
503
- if (boundaryOptionId === void 0) return null;
504
- return {
505
- ...boundaryOptionId ? {
506
- kind: "set-boundary",
507
- previousBlockId: boundary.previousBlockId,
508
- nextBlockId: boundary.nextBlockId,
509
- boundaryOptionId
510
- } : {
511
- kind: "reset-boundary",
512
- previousBlockId: boundary.previousBlockId,
513
- nextBlockId: boundary.nextBlockId
514
- }
515
- };
1222
+ function imagePoolAssetForMediaId(imagePool, mediaId) {
1223
+ return imagePool?.find((item) => item.mediaId === mediaId)?.mediaAsset;
516
1224
  }
517
1225
  function cycleChoiceIndex(choice, values, direction) {
518
1226
  if (choice.kind === "user-pinned" && choice.status === "supported") {
@@ -543,68 +1251,37 @@ function previewCustomizeFacetControlsFor(blockKind, content, designBlock) {
543
1251
  };
544
1252
  });
545
1253
  }
546
- function useDialogFocusTrap(input) {
547
- const { open, onClose } = input;
548
- const dialogRef = useRef(null);
549
- const initialFocusRef = useRef(null);
550
- const returnFocusRef = useRef(null);
551
- useEffect(() => {
552
- if (open) {
553
- initialFocusRef.current?.focus();
554
- return;
555
- }
556
- const returnFocusTarget = returnFocusRef.current;
557
- returnFocusRef.current = null;
558
- returnFocusTarget?.focus();
559
- }, [open]);
560
- const registerInitialFocus = useCallback((node) => {
561
- initialFocusRef.current = node;
562
- }, []);
563
- const setReturnFocusTarget = useCallback((node) => {
564
- returnFocusRef.current = node;
565
- }, []);
566
- const handleDialogKeyDown = useCallback(
567
- (event) => {
568
- if (event.key === "Escape") {
569
- event.preventDefault();
570
- onClose();
571
- return;
572
- }
573
- if (event.key !== "Tab") return;
574
- const focusableElements = focusableElementsIn(dialogRef.current);
575
- if (focusableElements.length === 0) {
576
- event.preventDefault();
577
- return;
578
- }
579
- const firstElement = focusableElements[0];
580
- const lastElement = focusableElements[focusableElements.length - 1];
581
- if (!firstElement || !lastElement) return;
582
- if (event.shiftKey && document.activeElement === firstElement) {
583
- event.preventDefault();
584
- lastElement.focus();
585
- return;
586
- }
587
- if (!event.shiftKey && document.activeElement === lastElement) {
588
- event.preventDefault();
589
- firstElement.focus();
590
- }
591
- },
592
- [onClose]
1254
+
1255
+ // ../preview-next/src/client/blocks/blockCustomizePopoverRows.ts
1256
+ function buildBlockCustomizeBuiltinRows({
1257
+ designBlock,
1258
+ facetControls,
1259
+ hasLayoutControls,
1260
+ hasLookControls,
1261
+ hasBackgroundControls
1262
+ }) {
1263
+ const rows = [];
1264
+ if (designBlock?.layout && hasLayoutControls) {
1265
+ rows.push({ kind: "variant", block: designBlock });
1266
+ }
1267
+ if (designBlock?.layout?.status === "unsupported") {
1268
+ rows.push({ kind: "unsupported-variant-warning" });
1269
+ }
1270
+ if (designBlock && hasLookControls) {
1271
+ rows.push({ kind: "look", block: designBlock });
1272
+ }
1273
+ if (hasBackgroundControls) {
1274
+ rows.push({ kind: "background" });
1275
+ }
1276
+ rows.push(
1277
+ ...facetControls.map(
1278
+ (control) => ({ kind: "facet", control })
1279
+ )
593
1280
  );
594
- return {
595
- dialogRef,
596
- registerInitialFocus,
597
- setReturnFocusTarget,
598
- handleDialogKeyDown
599
- };
1281
+ return rows;
600
1282
  }
601
- function focusableElementsIn(root) {
602
- if (!root) return [];
603
- return Array.from(
604
- root.querySelectorAll(
605
- 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
606
- )
607
- ).filter((element) => !element.hasAttribute("disabled") && element.getAttribute("aria-hidden") !== "true");
1283
+ function blockCustomizeBuiltinRowBudget(rows) {
1284
+ return rows.length;
608
1285
  }
609
1286
  function isUnknownArray(value) {
610
1287
  return Array.isArray(value);
@@ -614,7 +1291,7 @@ function BlockToolbar({
614
1291
  blockKind,
615
1292
  content,
616
1293
  theme = null,
617
- headerClearancePx = 0
1294
+ placement = DEFAULT_PREVIEW_CHROME_FLOATING_PLACEMENT
618
1295
  }) {
619
1296
  const uiStore = useEditorUiStoreInstance();
620
1297
  const editorBlockId = useMemo(() => unsafeAsEditorBlockId(blockId), [blockId]);
@@ -622,41 +1299,25 @@ function BlockToolbar({
622
1299
  (state) => state.drafts.getCurrentValues(editorBlockId)
623
1300
  );
624
1301
  const customizeContent = useMemo(
625
- () => isRecord3(draftContent) ? draftContent : content,
1302
+ () => isRecord4(draftContent) ? draftContent : content,
626
1303
  [content, draftContent]
627
1304
  );
628
1305
  const customizeSelection = usePreviewBlockCustomizeSelectionOptional();
629
- const customizePanelId = useId();
630
- const customizeDescriptionId = `${customizePanelId}-description`;
1306
+ const popover = useCustomizePopoverController();
631
1307
  const focusTarget = useMemo(
632
1308
  () => computeDefaultFocus(blockKind, customizeContent),
633
1309
  [blockKind, customizeContent]
634
1310
  );
635
- const [customizeOpen, setCustomizeOpen] = useState(false);
636
1311
  const [backgroundChooserOpen, setBackgroundChooserOpen] = useState(false);
637
1312
  const [backgroundColorPalette, setBackgroundColorPalette] = useState(null);
638
- const customizeButtonRef = useRef(null);
639
1313
  const {
640
1314
  state: pageDesignState,
641
1315
  applyAction: applyPageDesignAction,
642
- isHistoryPending,
643
1316
  historyFailure,
644
1317
  backgroundImagePool = [],
645
1318
  backgroundImagePoolStatus = "idle",
646
1319
  loadBackgroundImagePool = async () => []
647
1320
  } = usePreviewPageDesignEditorState();
648
- const closeCustomize = useCallback(() => {
649
- setCustomizeOpen(false);
650
- }, []);
651
- const {
652
- dialogRef: customizeDialogRef,
653
- registerInitialFocus: registerCustomizeInitialFocus,
654
- setReturnFocusTarget: setCustomizeReturnFocusTarget,
655
- handleDialogKeyDown: handleCustomizeDialogKeyDown
656
- } = useDialogFocusTrap({
657
- open: customizeOpen,
658
- onClose: closeCustomize
659
- });
660
1321
  const designBlock = useMemo(() => {
661
1322
  if (pageDesignState.kind !== "loaded") return null;
662
1323
  return pageDesignState.editorModel.blocks.find(
@@ -669,11 +1330,12 @@ function BlockToolbar({
669
1330
  const hasLookControls = Boolean(
670
1331
  designBlock && designBlock.availableLooks.length > 0
671
1332
  );
672
- const canCycleLook = Boolean(
673
- designBlock && designBlock.availableLooks.length > 1
674
- );
675
1333
  const facetControls = useMemo(
676
- () => previewCustomizeFacetControlsFor(blockKind, customizeContent, designBlock),
1334
+ () => previewCustomizeFacetControlsFor(
1335
+ blockKind,
1336
+ customizeContent,
1337
+ designBlock
1338
+ ),
677
1339
  [blockKind, customizeContent, designBlock]
678
1340
  );
679
1341
  const hasFacetControls = facetControls.length > 0;
@@ -690,6 +1352,26 @@ function BlockToolbar({
690
1352
  ),
691
1353
  [backgroundImagePool, currentBackground]
692
1354
  );
1355
+ const backgroundCycleImagePool = useMemo(
1356
+ () => {
1357
+ if (currentBackground.kind === "supported" && currentBackground.override.kind === "image") {
1358
+ const currentOverride = currentBackground.override;
1359
+ const currentExistsInPool = backgroundImagePool.some(
1360
+ (item) => item.mediaId === currentOverride.mediaId
1361
+ );
1362
+ if (!currentExistsInPool) {
1363
+ return rankBackgroundImagePoolItems(
1364
+ dedupeBackgroundImagePool([
1365
+ ...imagePoolForOverride(currentOverride),
1366
+ ...backgroundImagePool
1367
+ ])
1368
+ );
1369
+ }
1370
+ }
1371
+ return rankBackgroundImagePoolItems(backgroundImagePool);
1372
+ },
1373
+ [backgroundImagePool, currentBackground]
1374
+ );
693
1375
  const backgroundCapability = useMemo(
694
1376
  () => designBlock ? resolveBackgroundCapability({
695
1377
  block: designBlock,
@@ -698,14 +1380,34 @@ function BlockToolbar({
698
1380
  }) : null,
699
1381
  [backgroundColorPalette, designBlock, mergedBackgroundImagePool]
700
1382
  );
1383
+ const backgroundTransitionCapability = useMemo(
1384
+ () => backgroundCapability ? { ...backgroundCapability, imagePool: backgroundCycleImagePool } : null,
1385
+ [backgroundCapability, backgroundCycleImagePool]
1386
+ );
701
1387
  const enabledBackgroundModeList = useMemo(
702
1388
  () => backgroundCapability ? enabledBackgroundModes(backgroundCapability) : [],
703
1389
  [backgroundCapability]
704
1390
  );
705
1391
  const hasBackgroundControls = currentBackground.kind === "supported" && enabledBackgroundModeList.length > 1;
706
- const builtInCustomizeRowCount = (hasLayoutControls ? 1 : 0) + facetControls.length + (hasBackgroundControls ? 1 : 0) + (hasLookControls ? 1 : 0);
1392
+ const builtInCustomizeRows = useMemo(
1393
+ () => buildBlockCustomizeBuiltinRows({
1394
+ designBlock,
1395
+ facetControls,
1396
+ hasLayoutControls,
1397
+ hasLookControls,
1398
+ hasBackgroundControls
1399
+ }),
1400
+ [
1401
+ designBlock,
1402
+ facetControls,
1403
+ hasBackgroundControls,
1404
+ hasLayoutControls,
1405
+ hasLookControls
1406
+ ]
1407
+ );
1408
+ const builtInCustomizeRowCount = blockCustomizeBuiltinRowBudget(builtInCustomizeRows);
707
1409
  const manifestCustomize = useBlockCustomizeControls({
708
- blockId,
1410
+ blockId: editorBlockId,
709
1411
  blockKind,
710
1412
  content: customizeContent,
711
1413
  uiStore,
@@ -719,24 +1421,23 @@ function BlockToolbar({
719
1421
  const hasOverflowCustomizeControls = manifestCustomize.hasOverflowControls;
720
1422
  const quickManifestBackend = manifestCustomize.backend;
721
1423
  const canCycleBackgroundMode = enabledBackgroundModeList.length > 1;
722
- const canCycleBackgroundValue = currentBackground.kind === "supported" && backgroundCapability ? canCycleBackgroundOverrideValue(
1424
+ const canCycleBackgroundValue = currentBackground.kind === "supported" && backgroundTransitionCapability ? canCycleBackgroundOverrideValue(
723
1425
  currentBackground.override,
724
- backgroundCapability
1426
+ backgroundTransitionCapability
725
1427
  ) : false;
726
1428
  const hasCustomizeControls = hasLayoutControls || hasLookControls || hasFacetControls || hasBackgroundControls || quickManifestFields.length > 0 || hasSidebarCustomizeControls;
727
- const isSavingCustomize = isHistoryPending;
728
1429
  useEffect(() => {
729
1430
  if (!hasCustomizeControls) {
730
- setCustomizeOpen(false);
1431
+ popover.close();
731
1432
  setBackgroundChooserOpen(false);
732
1433
  }
733
- }, [hasCustomizeControls]);
1434
+ }, [hasCustomizeControls, popover.close]);
734
1435
  useEffect(() => {
735
- if (!customizeOpen) return;
1436
+ if (!popover.open) return;
736
1437
  setBackgroundColorPalette(readBackgroundColorPaletteSnapshot());
737
- }, [customizeOpen, pageDesignState]);
1438
+ }, [pageDesignState, popover.open]);
738
1439
  useEffect(() => {
739
- if (!customizeOpen || !backgroundCapability?.modes.includes("image")) {
1440
+ if (!popover.open || !backgroundCapability?.modes.includes("image")) {
740
1441
  return;
741
1442
  }
742
1443
  if (backgroundImagePoolStatus !== "idle") return;
@@ -744,8 +1445,8 @@ function BlockToolbar({
744
1445
  }, [
745
1446
  backgroundCapability,
746
1447
  backgroundImagePoolStatus,
747
- customizeOpen,
748
- loadBackgroundImagePool
1448
+ loadBackgroundImagePool,
1449
+ popover.open
749
1450
  ]);
750
1451
  const handleEdit = (e) => {
751
1452
  e.preventDefault();
@@ -763,40 +1464,32 @@ function BlockToolbar({
763
1464
  const inspectBlock = useCallback(() => {
764
1465
  customizeSelection?.inspectBlock(unsafeAsEditorBlockId(blockId));
765
1466
  }, [blockId, customizeSelection]);
766
- const openCustomizeSettings = useCallback((e) => {
767
- e.preventDefault();
768
- e.stopPropagation();
769
- inspectBlock();
770
- const state = uiStore.getState();
771
- setPreviewSidebarTab("editor");
772
- state.setPreviewSidebarTab("editor");
773
- state.setDrawerOpen(true);
774
- }, [inspectBlock, uiStore]);
775
- const cycleLayout = (direction) => (e) => {
776
- e.preventDefault();
777
- e.stopPropagation();
1467
+ const openSidebarCustomize = useCallback(
1468
+ (e) => {
1469
+ e.preventDefault();
1470
+ e.stopPropagation();
1471
+ inspectBlock();
1472
+ const state = uiStore.getState();
1473
+ setPreviewSidebarTab("editor");
1474
+ state.setPreviewSidebarTab("editor");
1475
+ state.setDrawerOpen(true);
1476
+ },
1477
+ [inspectBlock, uiStore]
1478
+ );
1479
+ const cycleLayout = (direction) => {
778
1480
  if (!designBlock) return;
779
1481
  const action = cyclePageDesignBlockLayout(designBlock, direction);
780
1482
  if (!action) return;
781
1483
  saveBlockCustomizeAction(action);
782
1484
  };
783
- const cycleLook = (direction) => (e) => {
784
- e.preventDefault();
785
- e.stopPropagation();
1485
+ const cycleLook = (direction) => {
786
1486
  if (!designBlock) return;
787
1487
  const action = cyclePageDesignBlockLook(designBlock, direction);
788
1488
  if (!action) return;
789
1489
  saveBlockCustomizeAction(action);
790
1490
  };
791
- const handleFacetCycle = (e) => {
792
- e.preventDefault();
793
- e.stopPropagation();
1491
+ const cycleFacet = (control, direction) => {
794
1492
  if (!designBlock) return;
795
- const facetId = e.currentTarget.dataset.facetId;
796
- const direction = e.currentTarget.dataset.direction;
797
- if (direction !== "prev" && direction !== "next") return;
798
- const control = facetControls.find((candidate) => candidate.facetId === facetId) ?? null;
799
- if (!control) return;
800
1493
  const action = cyclePageDesignBlockFacet({
801
1494
  block: designBlock,
802
1495
  facetId: control.facetId,
@@ -824,12 +1517,12 @@ function BlockToolbar({
824
1517
  setBackgroundChooserOpen(false);
825
1518
  };
826
1519
  const saveBackgroundTransition = (transition) => {
827
- if (!designBlock || !backgroundCapability || currentBackground.kind !== "supported") {
1520
+ if (!designBlock || !backgroundTransitionCapability || currentBackground.kind !== "supported") {
828
1521
  return;
829
1522
  }
830
1523
  const plan = planBackgroundTransition(
831
1524
  currentBackground.override,
832
- backgroundCapability,
1525
+ backgroundTransitionCapability,
833
1526
  transition
834
1527
  );
835
1528
  if (plan.kind !== "apply") return;
@@ -846,34 +1539,31 @@ function BlockToolbar({
846
1539
  void applyPageDesignAction(action, { runtimeContent: customizeContent });
847
1540
  };
848
1541
  const toggleCustomize = (e) => {
849
- e.preventDefault();
850
- e.stopPropagation();
851
- if (!customizeOpen) {
1542
+ if (!popover.open) {
852
1543
  inspectBlock();
853
- setCustomizeReturnFocusTarget(
854
- e.currentTarget instanceof HTMLElement ? e.currentTarget : customizeButtonRef.current
855
- );
856
1544
  }
857
- setCustomizeOpen((open) => !open);
1545
+ popover.toggle(e);
858
1546
  };
859
1547
  return /* @__PURE__ */ jsxs(
860
- "div",
1548
+ PreviewChromeFloatingControl,
861
1549
  {
862
- className: "pointer-events-auto absolute right-2 z-50 flex gap-1 opacity-0 transition group-focus-within:opacity-100 group-hover:opacity-100",
863
- style: { top: headerClearancePx > 0 ? headerClearancePx + 8 : 8 },
1550
+ anchorSelector: "[data-block-id]",
1551
+ placement,
1552
+ fallbackClassName: "pointer-events-auto absolute z-50 flex gap-1 opacity-0 transition group-focus-within:opacity-100 group-hover:opacity-100",
1553
+ fallbackStyle: {
1554
+ top: placement.topPx,
1555
+ right: placement.rightPx
1556
+ },
1557
+ forceVisible: popover.open,
864
1558
  children: [
865
1559
  hasCustomizeControls ? /* @__PURE__ */ jsx(
866
- "button",
1560
+ CustomizePillButton,
867
1561
  {
868
- ref: customizeButtonRef,
869
- type: "button",
870
- onClick: toggleCustomize,
871
- className: previewCustomizePillButtonClassName,
872
- "aria-label": "Customize",
873
- "aria-expanded": customizeOpen,
874
- "aria-haspopup": "dialog",
875
- "aria-controls": customizeOpen ? customizePanelId : void 0,
876
- children: isSavingCustomize ? "Saving..." : "Customize"
1562
+ buttonRef: popover.buttonRef,
1563
+ ariaLabel: "Customize",
1564
+ open: popover.open,
1565
+ controlsId: popover.panelId,
1566
+ onClick: toggleCustomize
877
1567
  }
878
1568
  ) : null,
879
1569
  /* @__PURE__ */ jsx(
@@ -885,266 +1575,59 @@ function BlockToolbar({
885
1575
  children: "Edit"
886
1576
  }
887
1577
  ),
888
- customizeOpen && (designBlock || hasSidebarCustomizeControls) ? /* @__PURE__ */ jsxs(
889
- "div",
1578
+ popover.open && (designBlock || hasSidebarCustomizeControls) ? /* @__PURE__ */ jsxs(
1579
+ CustomizePopoverShell,
890
1580
  {
891
- id: customizePanelId,
892
- className: "builder-dashboard-chrome-theme fixed inset-x-3 bottom-[calc(var(--builder-editor-chrome-offset,0px)+1rem)] z-[70] rounded-lg border bg-background p-3 text-foreground shadow-xl sm:absolute sm:inset-x-auto sm:bottom-auto sm:right-0 sm:top-9 sm:w-72",
893
- role: "dialog",
894
- "aria-modal": "true",
895
- "aria-label": "Customize section",
896
- "aria-describedby": customizeDescriptionId,
897
- ref: customizeDialogRef,
898
- onKeyDown: handleCustomizeDialogKeyDown,
899
- onClick: (event) => event.stopPropagation(),
1581
+ panelId: popover.panelId,
1582
+ descriptionId: popover.descriptionId,
1583
+ dialogLabel: "Customize section",
1584
+ eyebrow: "Section",
1585
+ title: formatBlockKindLabel(blockKind),
1586
+ dialogRef: popover.dialogRef,
1587
+ closeButtonRef: popover.registerInitialFocus,
1588
+ onClose: popover.close,
1589
+ onKeyDown: popover.handleDialogKeyDown,
1590
+ spacing: "compact",
900
1591
  children: [
901
- /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-start justify-between gap-3", children: [
902
- /* @__PURE__ */ jsxs("div", { children: [
903
- /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase text-muted-foreground", children: "Section" }),
904
- /* @__PURE__ */ jsx(
905
- "div",
906
- {
907
- id: customizeDescriptionId,
908
- className: "text-sm font-semibold",
909
- children: formatBlockKindLabel(blockKind)
910
- }
911
- )
912
- ] }),
913
- /* @__PURE__ */ jsx(
914
- "button",
915
- {
916
- ref: registerCustomizeInitialFocus,
917
- type: "button",
918
- className: "rounded-full px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
919
- onClick: (event) => {
920
- event.preventDefault();
921
- event.stopPropagation();
922
- closeCustomize();
923
- },
924
- children: "Close"
925
- }
926
- )
927
- ] }),
928
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
929
- designBlock?.layout && hasLayoutControls ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
930
- /* @__PURE__ */ jsxs("div", { children: [
931
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: "Layout" }),
932
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: layoutLabel(
933
- designBlock.availableLayouts,
934
- designBlock.layout.effectiveValue
935
- ) })
936
- ] }),
937
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
938
- /* @__PURE__ */ jsx(
939
- "button",
940
- {
941
- type: "button",
942
- onClick: cycleLayout("prev"),
943
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
944
- "aria-label": "Previous layout",
945
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
946
- }
947
- ),
948
- /* @__PURE__ */ jsx(
949
- "button",
950
- {
951
- type: "button",
952
- onClick: cycleLayout("next"),
953
- className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
954
- "aria-label": "Next layout",
955
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
956
- }
957
- )
958
- ] })
959
- ] }) : null,
960
- designBlock?.layout?.status === "unsupported" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-amber-700", children: "This layout is no longer available. The preview is using the closest safe option." }) : null,
961
- facetControls.map((control) => /* @__PURE__ */ jsxs(
962
- "div",
963
- {
964
- className: "flex items-center justify-between gap-2",
965
- children: [
966
- /* @__PURE__ */ jsxs("div", { children: [
967
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: control.label }),
968
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: control.valueLabel })
969
- ] }),
970
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
971
- /* @__PURE__ */ jsx(
972
- "button",
973
- {
974
- type: "button",
975
- onClick: handleFacetCycle,
976
- "data-facet-id": control.facetId,
977
- "data-direction": "prev",
978
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
979
- "aria-label": `Previous ${control.label.toLowerCase()}`,
980
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
981
- }
982
- ),
983
- /* @__PURE__ */ jsx(
984
- "button",
985
- {
986
- type: "button",
987
- onClick: handleFacetCycle,
988
- "data-facet-id": control.facetId,
989
- "data-direction": "next",
990
- className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
991
- "aria-label": `Next ${control.label.toLowerCase()}`,
992
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
993
- }
994
- )
995
- ] })
996
- ]
997
- },
998
- control.facetId
999
- )),
1000
- hasBackgroundControls && currentBackground.kind === "supported" && backgroundCapability ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1001
- /* @__PURE__ */ jsxs("div", { className: "flex items-stretch overflow-hidden rounded-full border bg-background", children: [
1002
- /* @__PURE__ */ jsx(
1003
- "button",
1004
- {
1005
- type: "button",
1006
- onClick: handleBackgroundCycle({
1007
- kind: "cycle-mode",
1008
- direction: "prev"
1009
- }),
1010
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1011
- "aria-label": "Previous background mode",
1012
- disabled: !canCycleBackgroundMode,
1013
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
1014
- }
1015
- ),
1016
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 border-x px-2 py-1 text-center", children: [
1017
- /* @__PURE__ */ jsx("div", { className: "text-xs font-medium leading-3 text-muted-foreground", children: "Background:" }),
1018
- /* @__PURE__ */ jsx("div", { className: "truncate text-xs font-semibold leading-4", children: backgroundModeLabel(currentBackground.override.kind) })
1019
- ] }),
1020
- /* @__PURE__ */ jsx(
1021
- "button",
1022
- {
1023
- type: "button",
1024
- onClick: handleBackgroundCycle({
1025
- kind: "cycle-mode",
1026
- direction: "next"
1027
- }),
1028
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1029
- "aria-label": "Next background mode",
1030
- disabled: !canCycleBackgroundMode,
1031
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
1032
- }
1033
- )
1034
- ] }),
1035
- /* @__PURE__ */ jsxs("div", { className: "flex items-stretch overflow-hidden rounded-full border bg-background", children: [
1036
- /* @__PURE__ */ jsx(
1037
- "button",
1038
- {
1039
- type: "button",
1040
- onClick: handleBackgroundCycle({
1041
- kind: "cycle-value",
1042
- direction: "prev"
1043
- }),
1044
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1045
- "aria-label": "Previous background value",
1046
- disabled: !canCycleBackgroundValue,
1047
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
1048
- }
1049
- ),
1050
- /* @__PURE__ */ jsxs(
1051
- "button",
1052
- {
1053
- type: "button",
1054
- className: "flex min-w-0 flex-1 items-center justify-center gap-2 border-x px-2 py-1 text-center hover:bg-muted",
1055
- "aria-expanded": backgroundChooserOpen,
1056
- "aria-label": `Choose background ${backgroundOverrideLabel(currentBackground.override).toLowerCase()}`,
1057
- onClick: (event) => {
1058
- event.preventDefault();
1059
- event.stopPropagation();
1060
- setBackgroundChooserOpen((open) => !open);
1061
- },
1062
- children: [
1063
- /* @__PURE__ */ jsx(BackgroundPreviewSwatch, { override: currentBackground.override }),
1064
- /* @__PURE__ */ jsx("span", { className: "truncate text-xs font-medium", children: backgroundOverrideValueLabel(
1065
- currentBackground.override,
1066
- mergedBackgroundImagePool
1067
- ) })
1068
- ]
1069
- }
1070
- ),
1071
- /* @__PURE__ */ jsx(
1072
- "button",
1073
- {
1074
- type: "button",
1075
- onClick: handleBackgroundCycle({
1076
- kind: "cycle-value",
1077
- direction: "next"
1078
- }),
1079
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1080
- "aria-label": "Next background value",
1081
- disabled: !canCycleBackgroundValue,
1082
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
1083
- }
1084
- )
1085
- ] }),
1086
- backgroundChooserOpen ? /* @__PURE__ */ jsx(
1087
- BackgroundChooser,
1088
- {
1089
- capability: backgroundCapability,
1090
- current: currentBackground.override,
1091
- enabledModes: enabledBackgroundModeList,
1092
- imagePoolStatus: backgroundImagePoolStatus,
1093
- onChoose: chooseBackgroundOverride
1094
- }
1095
- ) : null
1096
- ] }) : null,
1097
- quickManifestFields.length > 0 && quickManifestBackend ? /* @__PURE__ */ jsx("div", { className: "border-t pt-3", children: /* @__PURE__ */ jsx(
1098
- FormRenderer,
1099
- {
1100
- fields: quickManifestFields,
1101
- backend: quickManifestBackend,
1102
- showSettingsTrigger: false,
1103
- blockKind,
1104
- className: "space-y-3"
1105
- }
1106
- ) }) : null,
1107
- hasLookControls && designBlock ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1108
- /* @__PURE__ */ jsxs("div", { children: [
1109
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: "Look" }),
1110
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: lookLabel(designBlock) })
1111
- ] }),
1112
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
1113
- /* @__PURE__ */ jsx(
1114
- "button",
1115
- {
1116
- type: "button",
1117
- onClick: cycleLook("prev"),
1118
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1119
- "aria-label": "Previous look",
1120
- disabled: !canCycleLook,
1121
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
1122
- }
1123
- ),
1124
- /* @__PURE__ */ jsx(
1125
- "button",
1126
- {
1127
- type: "button",
1128
- onClick: cycleLook("next"),
1129
- className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1130
- "aria-label": "Next look",
1131
- disabled: !canCycleLook,
1132
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
1133
- }
1134
- )
1135
- ] })
1136
- ] }) : null,
1137
- historyFailure ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-700", children: historyFailure }) : null,
1138
- hasOverflowCustomizeControls ? /* @__PURE__ */ jsx("div", { className: "border-t pt-2", children: /* @__PURE__ */ jsx(
1139
- "button",
1140
- {
1141
- type: "button",
1142
- className: "w-full rounded-md border bg-background px-3 py-2 text-sm font-medium hover:bg-muted",
1143
- onClick: openCustomizeSettings,
1144
- children: "Settings"
1145
- }
1146
- ) }) : null
1147
- ] })
1592
+ builtInCustomizeRows.map(
1593
+ (row) => renderBlockCustomizeBuiltinRow(row, {
1594
+ backgroundCapability,
1595
+ backgroundChooserOpen,
1596
+ canCycleBackgroundMode,
1597
+ canCycleBackgroundValue,
1598
+ currentBackground,
1599
+ cycleLayout,
1600
+ cycleLook,
1601
+ cycleFacet,
1602
+ enabledBackgroundModeList,
1603
+ handleBackgroundCycle,
1604
+ imagePoolStatus: backgroundImagePoolStatus,
1605
+ mergedBackgroundImagePool,
1606
+ onChooseBackgroundOverride: chooseBackgroundOverride,
1607
+ onToggleBackgroundChooser: () => setBackgroundChooserOpen((open) => !open)
1608
+ })
1609
+ ),
1610
+ quickManifestFields.length > 0 && quickManifestBackend ? /* @__PURE__ */ jsx("div", { className: "border-t pt-3", children: /* @__PURE__ */ jsx(
1611
+ FormRenderer,
1612
+ {
1613
+ fields: quickManifestFields,
1614
+ backend: quickManifestBackend,
1615
+ showSettingsTrigger: false,
1616
+ blockKind,
1617
+ className: "space-y-3"
1618
+ }
1619
+ ) }) : null,
1620
+ historyFailure ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-700", children: historyFailure }) : null,
1621
+ hasOverflowCustomizeControls ? /* @__PURE__ */ jsx("div", { className: "border-t pt-2", children: /* @__PURE__ */ jsx(
1622
+ "button",
1623
+ {
1624
+ type: "button",
1625
+ "aria-label": "Open detailed customization settings",
1626
+ className: "w-full rounded-md border bg-background px-3 py-2 text-sm font-medium hover:bg-muted",
1627
+ onClick: openSidebarCustomize,
1628
+ children: "Customize"
1629
+ }
1630
+ ) }) : null
1148
1631
  ]
1149
1632
  }
1150
1633
  ) : null
@@ -1152,16 +1635,178 @@ function BlockToolbar({
1152
1635
  }
1153
1636
  );
1154
1637
  }
1638
+ function renderBlockCustomizeBuiltinRow(row, context) {
1639
+ switch (row.kind) {
1640
+ case "variant":
1641
+ return /* @__PURE__ */ jsx(
1642
+ CustomizeCycleRow,
1643
+ {
1644
+ label: "Variant",
1645
+ valueLabel: layoutLabel(
1646
+ row.block.availableLayouts,
1647
+ row.block.layout?.effectiveValue ?? ""
1648
+ ),
1649
+ previousLabel: "Previous variant",
1650
+ nextLabel: "Next variant",
1651
+ availability: cycleAvailability({ kind: "available" }),
1652
+ onPrevious: () => context.cycleLayout("prev"),
1653
+ onNext: () => context.cycleLayout("next")
1654
+ },
1655
+ "variant"
1656
+ );
1657
+ case "unsupported-variant-warning":
1658
+ return /* @__PURE__ */ jsx("p", { className: "text-xs text-amber-700", children: "This layout is no longer available. The preview is using the closest safe option." }, "unsupported-variant-warning");
1659
+ case "look":
1660
+ return /* @__PURE__ */ jsx(
1661
+ CustomizeCycleRow,
1662
+ {
1663
+ label: "Look",
1664
+ valueLabel: lookLabel(row.block),
1665
+ previousLabel: "Previous look",
1666
+ nextLabel: "Next look",
1667
+ availability: cycleAvailability(
1668
+ canCycleLookForRow(row.block) ? { kind: "available" } : { kind: "single-option" }
1669
+ ),
1670
+ onPrevious: () => context.cycleLook("prev"),
1671
+ onNext: () => context.cycleLook("next")
1672
+ },
1673
+ "look"
1674
+ );
1675
+ case "background":
1676
+ if (context.currentBackground.kind !== "supported" || !context.backgroundCapability) {
1677
+ return null;
1678
+ }
1679
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1680
+ /* @__PURE__ */ jsxs("div", { className: "flex items-stretch overflow-hidden rounded-full border bg-background", children: [
1681
+ /* @__PURE__ */ jsx(
1682
+ "button",
1683
+ {
1684
+ type: "button",
1685
+ onClick: context.handleBackgroundCycle({
1686
+ kind: "cycle-mode",
1687
+ direction: "prev"
1688
+ }),
1689
+ className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1690
+ "aria-label": "Previous background mode",
1691
+ disabled: !context.canCycleBackgroundMode,
1692
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
1693
+ }
1694
+ ),
1695
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 border-x px-2 py-1 text-center", children: [
1696
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium leading-3 text-muted-foreground", children: "Background:" }),
1697
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs font-semibold leading-4", children: backgroundModeLabel(context.currentBackground.override.kind) })
1698
+ ] }),
1699
+ /* @__PURE__ */ jsx(
1700
+ "button",
1701
+ {
1702
+ type: "button",
1703
+ onClick: context.handleBackgroundCycle({
1704
+ kind: "cycle-mode",
1705
+ direction: "next"
1706
+ }),
1707
+ className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1708
+ "aria-label": "Next background mode",
1709
+ disabled: !context.canCycleBackgroundMode,
1710
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
1711
+ }
1712
+ )
1713
+ ] }),
1714
+ /* @__PURE__ */ jsxs("div", { className: "flex items-stretch overflow-hidden rounded-full border bg-background", children: [
1715
+ /* @__PURE__ */ jsx(
1716
+ "button",
1717
+ {
1718
+ type: "button",
1719
+ onClick: context.handleBackgroundCycle({
1720
+ kind: "cycle-value",
1721
+ direction: "prev"
1722
+ }),
1723
+ className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1724
+ "aria-label": "Previous background value",
1725
+ disabled: !context.canCycleBackgroundValue,
1726
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
1727
+ }
1728
+ ),
1729
+ /* @__PURE__ */ jsxs(
1730
+ "button",
1731
+ {
1732
+ type: "button",
1733
+ className: "flex min-w-0 flex-1 items-center justify-center gap-2 border-x px-2 py-1 text-center hover:bg-muted",
1734
+ "aria-expanded": context.backgroundChooserOpen,
1735
+ "aria-label": `Choose background ${backgroundOverrideLabel(context.currentBackground.override).toLowerCase()}`,
1736
+ onClick: (event) => {
1737
+ event.preventDefault();
1738
+ event.stopPropagation();
1739
+ context.onToggleBackgroundChooser();
1740
+ },
1741
+ children: [
1742
+ /* @__PURE__ */ jsx(
1743
+ BackgroundPreviewSwatch,
1744
+ {
1745
+ override: context.currentBackground.override,
1746
+ imagePool: context.mergedBackgroundImagePool
1747
+ }
1748
+ ),
1749
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs font-medium", children: backgroundOverrideValueLabel(
1750
+ context.currentBackground.override,
1751
+ context.mergedBackgroundImagePool
1752
+ ) })
1753
+ ]
1754
+ }
1755
+ ),
1756
+ /* @__PURE__ */ jsx(
1757
+ "button",
1758
+ {
1759
+ type: "button",
1760
+ onClick: context.handleBackgroundCycle({
1761
+ kind: "cycle-value",
1762
+ direction: "next"
1763
+ }),
1764
+ className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
1765
+ "aria-label": "Next background value",
1766
+ disabled: !context.canCycleBackgroundValue,
1767
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
1768
+ }
1769
+ )
1770
+ ] }),
1771
+ context.backgroundChooserOpen ? /* @__PURE__ */ jsx(
1772
+ BackgroundChooser,
1773
+ {
1774
+ capability: context.backgroundCapability,
1775
+ current: context.currentBackground.override,
1776
+ imagePoolStatus: context.imagePoolStatus,
1777
+ onChoose: context.onChooseBackgroundOverride
1778
+ }
1779
+ ) : null
1780
+ ] }, "background");
1781
+ case "facet":
1782
+ return /* @__PURE__ */ jsx(
1783
+ CustomizeCycleRow,
1784
+ {
1785
+ label: row.control.label,
1786
+ valueLabel: row.control.valueLabel,
1787
+ previousLabel: `Previous ${row.control.label.toLowerCase()}`,
1788
+ nextLabel: `Next ${row.control.label.toLowerCase()}`,
1789
+ availability: cycleAvailability({ kind: "available" }),
1790
+ onPrevious: () => context.cycleFacet(row.control, "prev"),
1791
+ onNext: () => context.cycleFacet(row.control, "next")
1792
+ },
1793
+ `facet:${row.control.facetId}`
1794
+ );
1795
+ default:
1796
+ return assertNever2(row, "Unhandled block customize builtin row");
1797
+ }
1798
+ }
1799
+ function canCycleLookForRow(block) {
1800
+ return block.availableLooks.length > 1;
1801
+ }
1155
1802
  function BackgroundChooser({
1156
1803
  capability,
1157
1804
  current,
1158
- enabledModes,
1159
1805
  imagePoolStatus,
1160
1806
  onChoose
1161
1807
  }) {
1162
1808
  const mediaEditorContext = useMediaEditorOptional();
1163
1809
  const [mediaPickerOpenKey, setMediaPickerOpenKey] = useState(0);
1164
- const enabledModeSet = new Set(enabledModes);
1165
1810
  const images = rankBackgroundImagePoolItems(capability.imagePool ?? []);
1166
1811
  const recommendedTokens = capability.recommendedColorTokens ?? capability.colorTokens ?? [];
1167
1812
  const advancedTokens = capability.advancedColorTokens ?? [];
@@ -1177,21 +1822,30 @@ function BackgroundChooser({
1177
1822
  onChoose({ kind: "image", mediaId: parsed.value });
1178
1823
  setMediaPickerOpenKey(0);
1179
1824
  };
1180
- return /* @__PURE__ */ jsxs("div", { className: "mt-2 max-h-80 overflow-y-auto rounded-md border bg-muted/20 p-2", children: [
1181
- /* @__PURE__ */ jsx("div", { className: "mb-2 grid grid-cols-4 gap-1", children: ["default", "color", "gradient", "image"].map((mode) => /* @__PURE__ */ jsx(
1825
+ const renderColorOptions = () => recommendedTokens.length || advancedTokens.length ? /* @__PURE__ */ jsxs("div", { children: [
1826
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Colors" }),
1827
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-1", children: recommendedTokens.map((token) => /* @__PURE__ */ jsxs(
1182
1828
  "button",
1183
1829
  {
1184
1830
  type: "button",
1185
- className: `rounded-md border px-2 py-1 text-xs ${current.kind === mode ? "border-foreground bg-foreground text-background" : "bg-background hover:bg-muted"} disabled:cursor-not-allowed disabled:opacity-50`,
1186
- disabled: !enabledModeSet.has(mode),
1187
- onClick: chooseFromClick(defaultBackgroundOverrideForMode(mode, capability)),
1188
- children: backgroundModeLabel(mode)
1831
+ className: `flex min-w-0 items-center gap-2 rounded-md border px-2 py-1 text-left text-xs hover:bg-muted ${current.kind === "color" && current.token === token ? "border-foreground" : "bg-background"}`,
1832
+ onClick: chooseFromClick({ kind: "color", token }),
1833
+ children: [
1834
+ /* @__PURE__ */ jsx(
1835
+ "span",
1836
+ {
1837
+ className: "h-3 w-3 shrink-0 rounded-full border",
1838
+ style: { backgroundColor: tokenSwatchCss(token) }
1839
+ }
1840
+ ),
1841
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: formatTokenLabel(token) })
1842
+ ]
1189
1843
  },
1190
- mode
1844
+ token
1191
1845
  )) }),
1192
- recommendedTokens.length || advancedTokens.length ? /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
1193
- /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Colors" }),
1194
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-1", children: recommendedTokens.map((token) => /* @__PURE__ */ jsxs(
1846
+ advancedTokens.length ? /* @__PURE__ */ jsxs("details", { className: "mt-1", children: [
1847
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-xs text-muted-foreground", children: "More colors" }),
1848
+ /* @__PURE__ */ jsx("div", { className: "mt-1 grid grid-cols-2 gap-1", children: advancedTokens.map((token) => /* @__PURE__ */ jsxs(
1195
1849
  "button",
1196
1850
  {
1197
1851
  type: "button",
@@ -1209,78 +1863,72 @@ function BackgroundChooser({
1209
1863
  ]
1210
1864
  },
1211
1865
  token
1212
- )) }),
1213
- advancedTokens.length ? /* @__PURE__ */ jsxs("details", { className: "mt-1", children: [
1214
- /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-xs text-muted-foreground", children: "More colors" }),
1215
- /* @__PURE__ */ jsx("div", { className: "mt-1 grid grid-cols-2 gap-1", children: advancedTokens.map((token) => /* @__PURE__ */ jsxs(
1216
- "button",
1217
- {
1218
- type: "button",
1219
- className: `flex min-w-0 items-center gap-2 rounded-md border px-2 py-1 text-left text-xs hover:bg-muted ${current.kind === "color" && current.token === token ? "border-foreground" : "bg-background"}`,
1220
- onClick: chooseFromClick({ kind: "color", token }),
1221
- children: [
1222
- /* @__PURE__ */ jsx(
1223
- "span",
1224
- {
1225
- className: "h-3 w-3 shrink-0 rounded-full border",
1226
- style: { backgroundColor: tokenSwatchCss(token) }
1227
- }
1228
- ),
1229
- /* @__PURE__ */ jsx("span", { className: "truncate", children: formatTokenLabel(token) })
1230
- ]
1231
- },
1232
- token
1233
- )) })
1234
- ] }) : null
1235
- ] }) : null,
1236
- capability.gradientIds?.length ? /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
1237
- /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Gradients" }),
1238
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: capability.gradientIds.map((gradientId) => /* @__PURE__ */ jsx(
1239
- "button",
1240
- {
1241
- type: "button",
1242
- className: `w-full rounded-md border px-2 py-1 text-left text-xs hover:bg-muted ${current.kind === "gradient" && current.gradientId === gradientId ? "border-foreground" : "bg-background"}`,
1243
- onClick: chooseFromClick({ kind: "gradient", gradientId }),
1244
- children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1245
- /* @__PURE__ */ jsx(
1246
- "span",
1247
- {
1248
- className: "h-4 w-8 shrink-0 rounded border",
1249
- style: { backgroundImage: gradientPreviewCss(gradientId) }
1250
- }
1251
- ),
1252
- /* @__PURE__ */ jsx("span", { className: "truncate", children: gradientLabel(gradientId) })
1253
- ] })
1254
- },
1255
- gradientId
1256
1866
  )) })
1257
- ] }) : null,
1258
- capability.modes.includes("image") ? /* @__PURE__ */ jsxs("div", { children: [
1259
- /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Images" }),
1260
- mediaEditorContext ? /* @__PURE__ */ jsxs(
1261
- "button",
1262
- {
1263
- type: "button",
1264
- className: "mb-2 inline-flex w-full items-center justify-center gap-2 rounded-md border bg-background px-2 py-1.5 text-xs font-medium hover:bg-muted",
1265
- onClick: (event) => {
1266
- event.preventDefault();
1267
- event.stopPropagation();
1268
- setMediaPickerOpenKey((key) => key + 1);
1269
- },
1270
- children: [
1271
- /* @__PURE__ */ jsx(Upload, { size: 13 }),
1272
- "Media library"
1273
- ]
1274
- }
1275
- ) : null,
1276
- images.length ? /* @__PURE__ */ jsx("div", { className: "space-y-1", children: images.map((item) => /* @__PURE__ */ jsx(
1867
+ ] }) : null
1868
+ ] }) : null;
1869
+ const renderGradientOptions = () => capability.gradientIds?.length ? /* @__PURE__ */ jsxs("div", { children: [
1870
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Gradients" }),
1871
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: capability.gradientIds.map((gradientId) => /* @__PURE__ */ jsx(
1872
+ "button",
1873
+ {
1874
+ type: "button",
1875
+ className: `w-full rounded-md border px-2 py-1 text-left text-xs hover:bg-muted ${current.kind === "gradient" && current.gradientId === gradientId ? "border-foreground" : "bg-background"}`,
1876
+ onClick: chooseFromClick({ kind: "gradient", gradientId }),
1877
+ children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1878
+ /* @__PURE__ */ jsx(
1879
+ "span",
1880
+ {
1881
+ className: "h-4 w-8 shrink-0 rounded border",
1882
+ style: { backgroundImage: gradientPreviewCss(gradientId) }
1883
+ }
1884
+ ),
1885
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: gradientLabel(gradientId) })
1886
+ ] })
1887
+ },
1888
+ gradientId
1889
+ )) })
1890
+ ] }) : null;
1891
+ const renderImageOptions = () => capability.modes.includes("image") ? /* @__PURE__ */ jsxs("div", { children: [
1892
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Images" }),
1893
+ mediaEditorContext ? /* @__PURE__ */ jsxs(
1894
+ "button",
1895
+ {
1896
+ type: "button",
1897
+ className: "mb-2 inline-flex w-full items-center justify-center gap-2 rounded-md border bg-background px-2 py-1.5 text-xs font-medium hover:bg-muted",
1898
+ onClick: (event) => {
1899
+ event.preventDefault();
1900
+ event.stopPropagation();
1901
+ setMediaPickerOpenKey((key) => key + 1);
1902
+ },
1903
+ children: [
1904
+ /* @__PURE__ */ jsx(Upload, { size: 13 }),
1905
+ "Media library"
1906
+ ]
1907
+ }
1908
+ ) : null,
1909
+ images.length ? /* @__PURE__ */ jsx("div", { className: "space-y-1", children: images.map((item) => {
1910
+ const thumbnailUrl = backgroundImagePreviewUrl(
1911
+ item,
1912
+ mediaEditorContext
1913
+ );
1914
+ return /* @__PURE__ */ jsx(
1277
1915
  "button",
1278
1916
  {
1279
1917
  type: "button",
1280
1918
  className: `w-full rounded-md border px-2 py-1 text-left text-xs hover:bg-muted ${current.kind === "image" && current.mediaId === item.mediaId ? "border-foreground" : "bg-background"}`,
1281
- onClick: chooseFromClick({ kind: "image", mediaId: item.mediaId }),
1919
+ onClick: chooseFromClick({
1920
+ kind: "image",
1921
+ mediaId: item.mediaId
1922
+ }),
1282
1923
  children: /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
1283
- /* @__PURE__ */ jsx("span", { className: "grid h-7 w-9 shrink-0 place-items-center rounded border bg-muted", children: /* @__PURE__ */ jsx(Image, { size: 14 }) }),
1924
+ /* @__PURE__ */ jsx("span", { className: "grid h-7 w-9 shrink-0 place-items-center overflow-hidden rounded border bg-muted", children: thumbnailUrl ? /* @__PURE__ */ jsx(
1925
+ "img",
1926
+ {
1927
+ alt: "",
1928
+ className: "h-full w-full object-cover",
1929
+ src: thumbnailUrl
1930
+ }
1931
+ ) : /* @__PURE__ */ jsx(Image, { size: 14 }) }),
1284
1932
  /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
1285
1933
  /* @__PURE__ */ jsx("span", { className: "block truncate", children: item.label ?? "Image" }),
1286
1934
  /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: imageSourceLabel(item.source) })
@@ -1288,25 +1936,51 @@ function BackgroundChooser({
1288
1936
  ] })
1289
1937
  },
1290
1938
  `${item.source}:${item.mediaId}`
1291
- )) }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: imagePoolStatus === "loading" ? "Loading images..." : "No images available yet." }),
1292
- mediaPickerOpenKey > 0 && mediaEditorContext ? /* @__PURE__ */ jsx("div", { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx(
1293
- MediaEditor,
1294
- {
1295
- value: void 0,
1296
- onChange: chooseMediaValue,
1297
- allowedKinds: ["image"],
1298
- mediaPickerAutoOpen: true,
1299
- showPreview: false,
1300
- showAltInput: false,
1301
- uploadIntent: { tag: "media-library", allowedRenderUses: ["image"] },
1302
- onUploadComplete: (asset) => {
1303
- onChoose({ kind: "image", mediaId: asset.id });
1304
- setMediaPickerOpenKey(0);
1305
- }
1939
+ );
1940
+ }) }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: imagePoolStatus === "loading" ? "Loading images..." : "No background-ready images yet." }),
1941
+ mediaPickerOpenKey > 0 && mediaEditorContext ? /* @__PURE__ */ jsx("div", { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx(
1942
+ MediaEditor,
1943
+ {
1944
+ value: void 0,
1945
+ onChange: chooseMediaValue,
1946
+ allowedKinds: ["image"],
1947
+ mediaPickerAutoOpen: true,
1948
+ mediaPickerPolicy: {
1949
+ assetSelection: backgroundMediaPickerAssetSelection
1306
1950
  },
1307
- mediaPickerOpenKey
1308
- ) }) : null
1309
- ] }) : null
1951
+ showPreview: false,
1952
+ showAltInput: false,
1953
+ uploadIntent: {
1954
+ tag: "media-library",
1955
+ allowedRenderUses: ["image"]
1956
+ },
1957
+ onUploadComplete: (asset) => {
1958
+ if (backgroundMediaPickerAssetSelection(asset).tag === "disabled") {
1959
+ return;
1960
+ }
1961
+ onChoose({ kind: "image", mediaId: asset.id });
1962
+ setMediaPickerOpenKey(0);
1963
+ }
1964
+ },
1965
+ mediaPickerOpenKey
1966
+ ) }) : null
1967
+ ] }) : null;
1968
+ return /* @__PURE__ */ jsxs("div", { className: "mt-2 max-h-80 overflow-y-auto rounded-md border bg-muted/20 p-2", children: [
1969
+ current.kind === "default" ? /* @__PURE__ */ jsxs(
1970
+ "button",
1971
+ {
1972
+ type: "button",
1973
+ className: "flex w-full items-center gap-2 rounded-md border border-foreground bg-background px-2 py-1 text-left text-xs",
1974
+ onClick: chooseFromClick(defaultBackgroundOverrideForMode("default", capability)),
1975
+ children: [
1976
+ /* @__PURE__ */ jsx(BackgroundPreviewSwatch, { override: current, imagePool: images }),
1977
+ /* @__PURE__ */ jsx("span", { children: "Default background" })
1978
+ ]
1979
+ }
1980
+ ) : null,
1981
+ current.kind === "color" ? renderColorOptions() : null,
1982
+ current.kind === "gradient" ? renderGradientOptions() : null,
1983
+ current.kind === "image" ? renderImageOptions() : null
1310
1984
  ] });
1311
1985
  }
1312
1986
  function computeDefaultFocus(blockKind, content) {
@@ -1331,7 +2005,7 @@ function findFirstFieldPath(fields, data, prefix) {
1331
2005
  }
1332
2006
  const nextPrefix = [...prefix, field.id];
1333
2007
  if (field.type === "group") {
1334
- const groupValue = isRecord3(data) ? data[field.id] : void 0;
2008
+ const groupValue = isRecord4(data) ? data[field.id] : void 0;
1335
2009
  const nested = findFirstFieldPath(
1336
2010
  field.schema.fields,
1337
2011
  groupValue,
@@ -1341,7 +2015,7 @@ function findFirstFieldPath(fields, data, prefix) {
1341
2015
  continue;
1342
2016
  }
1343
2017
  if (field.type === "repeater" && field.schema) {
1344
- const collection = isRecord3(data) ? data[field.id] : void 0;
2018
+ const collection = isRecord4(data) ? data[field.id] : void 0;
1345
2019
  const items = isUnknownArray(collection) ? collection : [];
1346
2020
  if (items.length > 0) {
1347
2021
  const firstItem = items[0];
@@ -1414,7 +2088,11 @@ function canCycleBackgroundOverrideValue(override, capability) {
1414
2088
  return rankBackgroundImagePoolItems(capability.imagePool ?? []).length > 1;
1415
2089
  }
1416
2090
  }
1417
- function BackgroundPreviewSwatch({ override }) {
2091
+ function BackgroundPreviewSwatch({
2092
+ override,
2093
+ imagePool
2094
+ }) {
2095
+ const mediaEditorContext = useMediaEditorOptional();
1418
2096
  const baseClassName = "grid h-6 w-9 shrink-0 place-items-center overflow-hidden rounded-md border";
1419
2097
  switch (override.kind) {
1420
2098
  case "default":
@@ -1436,8 +2114,47 @@ function BackgroundPreviewSwatch({ override }) {
1436
2114
  }
1437
2115
  );
1438
2116
  case "image":
1439
- return /* @__PURE__ */ jsx("span", { className: `${baseClassName} bg-muted`, children: /* @__PURE__ */ jsx(Image, { size: 15 }) });
2117
+ const imageItem = imagePool.find((item) => item.mediaId === override.mediaId);
2118
+ const thumbnailUrl = imageItem ? backgroundImagePreviewUrl(imageItem, mediaEditorContext) : void 0;
2119
+ return /* @__PURE__ */ jsx("span", { className: `${baseClassName} bg-muted`, children: thumbnailUrl ? /* @__PURE__ */ jsx("img", { alt: "", className: "h-full w-full object-cover", src: thumbnailUrl }) : /* @__PURE__ */ jsx(Image, { size: 15 }) });
2120
+ }
2121
+ }
2122
+ function backgroundImagePreviewUrl(item, mediaEditorContext) {
2123
+ const asset = item.mediaAsset;
2124
+ if (!asset) return void 0;
2125
+ return resolveImageUrlWithContext(
2126
+ {
2127
+ type: "image",
2128
+ assetId: asset.id,
2129
+ ...asset.identifier ? { identifier: asset.identifier } : {},
2130
+ storageBucket: asset.storageBucket,
2131
+ storagePath: asset.storagePath,
2132
+ ...asset.width != null ? { width: asset.width } : {},
2133
+ ...asset.height != null ? { height: asset.height } : {},
2134
+ mimeType: asset.mimeType
2135
+ },
2136
+ { width: 72, height: 48, resize: "cover", quality: 70 },
2137
+ mediaEditorContext?.imageResolveContext ?? null
2138
+ );
2139
+ }
2140
+ function backgroundMediaPickerAssetSelection(asset) {
2141
+ if (isMarkedBackgroundAsset(asset)) return { tag: "selectable" };
2142
+ if (asset.width == null || asset.height == null) {
2143
+ return {
2144
+ tag: "disabled",
2145
+ reason: "This image is missing dimensions, so it cannot be checked for background use."
2146
+ };
2147
+ }
2148
+ if (asset.width < SECTION_BACKGROUND_MIN_SOURCE_WIDTH || asset.height < SECTION_BACKGROUND_MIN_SOURCE_HEIGHT) {
2149
+ return {
2150
+ tag: "disabled",
2151
+ reason: `Background images need to be at least ${SECTION_BACKGROUND_MIN_SOURCE_WIDTH}x${SECTION_BACKGROUND_MIN_SOURCE_HEIGHT}. This image is ${asset.width}x${asset.height}.`
2152
+ };
1440
2153
  }
2154
+ return { tag: "selectable" };
2155
+ }
2156
+ function isMarkedBackgroundAsset(asset) {
2157
+ return asset.fileUses?.some((fileUse) => fileUse.tag === "site-background-asset") ?? false;
1441
2158
  }
1442
2159
  function backgroundModeLabel(mode) {
1443
2160
  return mode === "default" ? "Default" : formatVariantLabel(mode);
@@ -1461,7 +2178,9 @@ function tokenSwatchCss(token) {
1461
2178
  return `rgb(var(--tb-${token}))`;
1462
2179
  }
1463
2180
  function gradientPreviewCss(gradientId) {
1464
- const preset = GRADIENT_PRESETS.find((candidate) => candidate.id === gradientId);
2181
+ const preset = GRADIENT_PRESETS.find(
2182
+ (candidate) => candidate.id === gradientId
2183
+ );
1465
2184
  if (!preset) {
1466
2185
  return "linear-gradient(135deg, rgb(var(--tb-primary)), rgb(var(--tb-secondary)))";
1467
2186
  }
@@ -1491,7 +2210,21 @@ function dedupeBackgroundImagePool(items) {
1491
2210
  const seen = /* @__PURE__ */ new Set();
1492
2211
  const deduped = [];
1493
2212
  for (const item of items) {
1494
- if (seen.has(item.mediaId)) continue;
2213
+ if (seen.has(item.mediaId)) {
2214
+ const existingIndex = deduped.findIndex(
2215
+ (candidate) => candidate.mediaId === item.mediaId
2216
+ );
2217
+ const existing = deduped[existingIndex];
2218
+ if (existing && !existing.mediaAsset && item.mediaAsset) {
2219
+ deduped[existingIndex] = {
2220
+ ...existing,
2221
+ mediaAsset: item.mediaAsset,
2222
+ label: existing.label ?? item.label,
2223
+ rank: existing.rank ?? item.rank
2224
+ };
2225
+ }
2226
+ continue;
2227
+ }
1495
2228
  seen.add(item.mediaId);
1496
2229
  deduped.push(item);
1497
2230
  }
@@ -1564,12 +2297,15 @@ function BoundaryControl({
1564
2297
  });
1565
2298
  });
1566
2299
  };
1567
- const applyBoundaryBreak = (direction) => (event) => {
2300
+ const applyBoundaryOption = (boundaryOptionId) => (event) => {
1568
2301
  event.preventDefault();
1569
2302
  event.stopPropagation();
1570
- const action = cyclePageDesignBoundaryBreak(boundary, direction);
1571
- if (!action) return;
1572
- saveBoundaryAction(action);
2303
+ saveBoundaryAction({
2304
+ kind: "set-boundary",
2305
+ previousBlockId: boundary.previousBlockId,
2306
+ nextBlockId: boundary.nextBlockId,
2307
+ boundaryOptionId
2308
+ });
1573
2309
  };
1574
2310
  const resetBoundaryBreak = (event) => {
1575
2311
  event.preventDefault();
@@ -1580,19 +2316,26 @@ function BoundaryControl({
1580
2316
  nextBlockId: boundary.nextBlockId
1581
2317
  });
1582
2318
  };
2319
+ const selectedVariant = sectionTransitionVariantForBoundaryOptionId(
2320
+ boundary.breakChoice.effectiveValue
2321
+ );
2322
+ const selectedFamily = sectionTransitionFamilyForBoundaryOptionId(
2323
+ boundary.breakChoice.effectiveValue
2324
+ ) ?? sectionTransitionFamilies[0];
2325
+ const variantOptions = selectedFamily.variants.length > 1 ? selectedFamily.variants : null;
1583
2326
  return /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-16 -translate-y-1/2", children: /* @__PURE__ */ jsxs("div", { className: "group/boundary relative flex h-full items-center justify-center", children: [
1584
2327
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-sky-500/10 opacity-0 transition-opacity duration-150 group-hover/boundary:opacity-100 group-focus-within/boundary:opacity-100" }),
1585
2328
  /* @__PURE__ */ jsx(
1586
2329
  "button",
1587
2330
  {
1588
2331
  type: "button",
1589
- className: "pointer-events-auto relative z-10 rounded-full border border-white/20 bg-zinc-950/85 px-3 py-1 text-xs font-semibold text-white opacity-0 shadow-lg shadow-zinc-950/25 backdrop-blur transition hover:bg-zinc-900 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-sky-300 group-hover/boundary:opacity-100 group-focus-within/boundary:opacity-100",
2332
+ className: "pointer-events-auto relative z-10 flex h-12 min-w-32 items-center justify-center rounded-full opacity-0 transition hover:opacity-100 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-sky-300 group-hover/boundary:opacity-100 group-focus-within/boundary:opacity-100",
1590
2333
  "aria-controls": open ? panelId : void 0,
1591
2334
  "aria-expanded": open,
1592
2335
  "aria-haspopup": "dialog",
1593
2336
  "aria-label": "Customize section break",
1594
2337
  onClick: openPanel,
1595
- children: "Break"
2338
+ children: /* @__PURE__ */ jsx("span", { className: "rounded-full border border-white/20 bg-zinc-950/85 px-3 py-1 text-xs font-semibold text-white shadow-lg shadow-zinc-950/25 backdrop-blur transition hover:bg-zinc-900", children: "Break" })
1596
2339
  }
1597
2340
  ),
1598
2341
  open ? /* @__PURE__ */ jsxs(
@@ -1629,32 +2372,39 @@ function BoundaryControl({
1629
2372
  }
1630
2373
  )
1631
2374
  ] }),
1632
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-slate-200 bg-slate-50 p-2", children: [
1633
- /* @__PURE__ */ jsx(
1634
- "button",
2375
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-slate-200 bg-slate-50 p-2 text-center", children: [
2376
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: boundaryBreakLabel(boundary) }),
2377
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-xs text-slate-500", children: boundaryBreakModeLabel(boundary) })
2378
+ ] }),
2379
+ /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
2380
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs font-medium text-slate-500", children: "Type" }),
2381
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 rounded-lg border border-slate-200 bg-slate-50 p-1", children: sectionTransitionFamilies.map((family) => /* @__PURE__ */ jsx(
2382
+ SectionTransitionFamilyButton,
1635
2383
  {
1636
- type: "button",
1637
- className: "rounded-md p-2 text-slate-600 transition hover:bg-white hover:text-slate-950 focus:outline-none focus:ring-2 focus:ring-sky-400",
1638
- "aria-label": "Previous section break",
1639
- onClick: applyBoundaryBreak("prev"),
1640
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4", "aria-hidden": "true" })
1641
- }
1642
- ),
1643
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 text-center", children: [
1644
- /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: boundaryBreakLabel(boundary) }),
1645
- /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-xs text-slate-500", children: boundaryBreakModeLabel(boundary) })
2384
+ family,
2385
+ active: selectedFamily.id === family.id,
2386
+ onClick: applyBoundaryOption(
2387
+ defaultSectionTransitionVariantForFamily(family.id).boundaryOption.id
2388
+ )
2389
+ },
2390
+ family.id
2391
+ )) })
2392
+ ] }),
2393
+ variantOptions ? /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
2394
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 text-xs font-medium text-slate-500", children: [
2395
+ selectedFamily.label,
2396
+ " style"
1646
2397
  ] }),
1647
- /* @__PURE__ */ jsx(
1648
- "button",
2398
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 rounded-lg border border-slate-200 bg-slate-50 p-1", children: variantOptions.map((variant) => /* @__PURE__ */ jsx(
2399
+ SectionTransitionVariantButton,
1649
2400
  {
1650
- type: "button",
1651
- className: "rounded-md p-2 text-slate-600 transition hover:bg-white hover:text-slate-950 focus:outline-none focus:ring-2 focus:ring-sky-400",
1652
- "aria-label": "Next section break",
1653
- onClick: applyBoundaryBreak("next"),
1654
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4", "aria-hidden": "true" })
1655
- }
1656
- )
1657
- ] }),
2401
+ variant,
2402
+ active: selectedVariant?.boundaryOption.id === variant.boundaryOption.id,
2403
+ onClick: applyBoundaryOption(variant.boundaryOption.id)
2404
+ },
2405
+ variant.boundaryOption.id
2406
+ )) })
2407
+ ] }) : null,
1658
2408
  panelStatus ? /* @__PURE__ */ jsx("p", { id: panelStatus.id, className: "mt-2 text-xs text-slate-500", children: panelStatus.text }) : null,
1659
2409
  display.canReset ? /* @__PURE__ */ jsxs(
1660
2410
  "button",
@@ -1675,6 +2425,46 @@ function BoundaryControl({
1675
2425
  ) : null
1676
2426
  ] }) });
1677
2427
  }
2428
+ function SectionTransitionFamilyButton({
2429
+ family,
2430
+ active,
2431
+ onClick
2432
+ }) {
2433
+ return /* @__PURE__ */ jsx(
2434
+ "button",
2435
+ {
2436
+ type: "button",
2437
+ className: sectionTransitionChoiceClassName(active),
2438
+ "aria-pressed": active,
2439
+ "aria-label": `Use ${family.label} section break`,
2440
+ onClick,
2441
+ children: family.label
2442
+ }
2443
+ );
2444
+ }
2445
+ function SectionTransitionVariantButton({
2446
+ variant,
2447
+ active,
2448
+ onClick
2449
+ }) {
2450
+ return /* @__PURE__ */ jsx(
2451
+ "button",
2452
+ {
2453
+ type: "button",
2454
+ className: sectionTransitionChoiceClassName(active),
2455
+ "aria-pressed": active,
2456
+ "aria-label": `Use ${variant.label} section break`,
2457
+ onClick,
2458
+ children: variant.label
2459
+ }
2460
+ );
2461
+ }
2462
+ function sectionTransitionChoiceClassName(active) {
2463
+ return [
2464
+ "min-h-8 flex-1 rounded-md px-2.5 text-xs font-medium transition focus:outline-none focus:ring-2 focus:ring-sky-400",
2465
+ active ? "bg-slate-950 text-white shadow-sm" : "bg-transparent text-slate-600 hover:bg-white hover:text-slate-950"
2466
+ ].join(" ");
2467
+ }
1678
2468
  function boundaryBreakLabel(boundary) {
1679
2469
  return boundaryBreakDisplay(boundary).label;
1680
2470
  }
@@ -1738,7 +2528,9 @@ function resolveRuntimePreviewSource(input) {
1738
2528
  }
1739
2529
  return { kind: "preserve-server-runtime" };
1740
2530
  }
2531
+ var MAX_PREVIEW_BLOCK_DATA_BATCH_SIZE = 25;
1741
2532
  function EditablePageRenderer(props) {
2533
+ const { blockDataApiClient = null, ...pageRendererProps } = props;
1742
2534
  const { blocks } = useEntryEditorController();
1743
2535
  const uiStore = useEditorUiStoreInstance();
1744
2536
  const { state: pageDesignState } = usePreviewPageDesignEditorState();
@@ -1749,21 +2541,45 @@ function EditablePageRenderer(props) {
1749
2541
  (state) => state.aiOverlays.hasActiveProposals()
1750
2542
  );
1751
2543
  const allowInlineEditing = isEditingMode && !isStreaming && !hasProposals;
1752
- const isEntryTemplatePage = Boolean(props.dataContext?.contentEntry);
1753
- const headerClearancePx = usePreviewHeaderClearance();
2544
+ const isEntryTemplatePage = Boolean(pageRendererProps.dataContext?.contentEntry);
2545
+ const headerClearance = usePreviewHeaderClearance();
1754
2546
  const cacheRef = useRef(/* @__PURE__ */ new Map());
1755
- const draftsVersion = useEditorUiStore((state) => {
2547
+ const renderStateVersion = useEditorUiStore((state) => {
1756
2548
  const snapshots = state.drafts.inspect();
2549
+ const linkedEntries = state.linkedEntries.inspect();
1757
2550
  const overlayRevision = state.aiOverlays.revision;
1758
- return `${snapshots.map((s) => `${s.id}:${s.version}`).join("|")}|overlay:${overlayRevision}`;
2551
+ return [
2552
+ snapshots.map((s) => `${s.id}:${s.version}`).join("|"),
2553
+ linkedEntries.map((entry) => `${entry.meta.entryId}:${entry.version}`).join("|"),
2554
+ `overlay:${overlayRevision}`
2555
+ ].join("|");
1759
2556
  });
2557
+ const linkedEntryOverlays = useMemo(() => {
2558
+ const overlays = /* @__PURE__ */ new Map();
2559
+ for (const linkedEntry of uiStore.getState().linkedEntries.inspect()) {
2560
+ const content = linkedEntry.current ?? linkedEntry.baseline;
2561
+ if (!content) continue;
2562
+ overlays.set(linkedEntry.meta.entryId, {
2563
+ content,
2564
+ optimisticHash: linkedEntry.meta.optimisticHash
2565
+ });
2566
+ }
2567
+ return overlays;
2568
+ }, [uiStore, renderStateVersion]);
2569
+ const linkedEntryDataContext = useMemo(
2570
+ () => applyLinkedEntryRenderOverlayToDataContext(
2571
+ pageRendererProps.dataContext,
2572
+ linkedEntryOverlays
2573
+ ),
2574
+ [linkedEntryOverlays, pageRendererProps.dataContext]
2575
+ );
1760
2576
  const aiProjections = useMemo(
1761
2577
  () => previewProjectionsFromAiOverlays(uiStore.getState().aiOverlays),
1762
- [uiStore, draftsVersion]
2578
+ [uiStore, renderStateVersion]
1763
2579
  );
1764
2580
  const surfaceResolutionContext = useMemo(
1765
- () => hasThemePalette(props.theme) ? surfaceResolutionContextFromTheme(props.theme) : null,
1766
- [props.theme]
2581
+ () => hasThemePalette(pageRendererProps.theme) ? surfaceResolutionContextFromTheme(pageRendererProps.theme) : null,
2582
+ [pageRendererProps.theme]
1767
2583
  );
1768
2584
  const runtimePreviewSource = useMemo(
1769
2585
  () => resolveRuntimePreviewSource({
@@ -1786,19 +2602,34 @@ function EditablePageRenderer(props) {
1786
2602
  const mergedPage = useMemo(() => {
1787
2603
  return composePageFromBlocks(
1788
2604
  {
1789
- page: props.page,
2605
+ page: pageRendererProps.page,
1790
2606
  blockItems,
1791
2607
  uiStore,
1792
2608
  projections: renderProjections
1793
2609
  },
1794
2610
  cacheRef
1795
2611
  );
1796
- }, [blockItems, props.page, renderProjections, uiStore, draftsVersion]);
2612
+ }, [
2613
+ blockItems,
2614
+ pageRendererProps.page,
2615
+ renderProjections,
2616
+ uiStore,
2617
+ renderStateVersion
2618
+ ]);
2619
+ const renderPage = mergedPage ?? pageRendererProps.page;
1797
2620
  const wrapBlockWithProvider = useCallback(
1798
2621
  (blockId, rendered) => {
1799
- const blockIndex = blockItemsForRender.findIndex((b) => b.id === blockId);
1800
- const block = blockIndex >= 0 ? blockItemsForRender[blockIndex] ?? null : null;
1801
- const previousBlock = blockIndex > 0 ? blockItemsForRender[blockIndex - 1] ?? null : null;
2622
+ const renderedBlocks = renderPage?.blocks ?? [];
2623
+ const controllerBlockIndex = blockItemsForRender.findIndex(
2624
+ (b) => b.id === blockId
2625
+ );
2626
+ const renderedBlockIndex = renderedBlocks.findIndex(
2627
+ (b) => b.id === blockId
2628
+ );
2629
+ const blockIndex = controllerBlockIndex >= 0 ? controllerBlockIndex : renderedBlockIndex;
2630
+ const chromeBlocks = controllerBlockIndex >= 0 ? blockItemsForRender : renderedBlocks;
2631
+ const block = blockIndex >= 0 ? chromeBlocks[blockIndex] ?? null : null;
2632
+ const previousBlock = blockIndex > 0 ? chromeBlocks[blockIndex - 1] ?? null : null;
1802
2633
  const canonicalBlockIndex = blockItems.findIndex((b) => b.id === blockId);
1803
2634
  const canonicalPreviousBlock = canonicalBlockIndex > 0 ? blockItems[canonicalBlockIndex - 1] ?? null : null;
1804
2635
  const editorBlockId = unsafeAsEditorBlockId(blockId);
@@ -1839,8 +2670,12 @@ function EditablePageRenderer(props) {
1839
2670
  blockId,
1840
2671
  blockKind: block.kind,
1841
2672
  content,
1842
- theme: props.theme,
1843
- headerClearancePx: shouldClearHeaderForBlock(blockItemsForRender, blockIndex) ? headerClearancePx : 0
2673
+ theme: pageRendererProps.theme,
2674
+ placement: planBlockToolbarPlacement({
2675
+ blocks: chromeBlocks,
2676
+ blockIndex,
2677
+ headerClearance
2678
+ })
1844
2679
  }
1845
2680
  )
1846
2681
  ] }) : null
@@ -1854,16 +2689,29 @@ function EditablePageRenderer(props) {
1854
2689
  aiProjections,
1855
2690
  blockItems,
1856
2691
  blockItemsForRender,
1857
- headerClearancePx,
2692
+ headerClearance,
2693
+ isEntryTemplatePage,
2694
+ pageRendererProps.theme,
1858
2695
  renderProjections,
2696
+ renderPage?.blocks,
1859
2697
  uiStore
1860
2698
  ]
1861
2699
  );
2700
+ const liveResolvedData = usePreviewBlockResolvedData({
2701
+ page: renderPage,
2702
+ dataContext: linkedEntryDataContext,
2703
+ apiClient: blockDataApiClient
2704
+ });
2705
+ const dataContext = useMemo(
2706
+ () => mergeResolvedDataContext(linkedEntryDataContext, liveResolvedData),
2707
+ [linkedEntryDataContext, liveResolvedData]
2708
+ );
1862
2709
  const rendererProps = {
1863
- ...props,
1864
- page: mergedPage,
1865
- registry: allowInlineEditing ? editorRegistry : props.registry ?? clientComponentRegistry,
1866
- wrapBlock: allowInlineEditing ? wrapBlockWithProvider : props.wrapBlock,
2710
+ ...pageRendererProps,
2711
+ page: renderPage,
2712
+ registry: allowInlineEditing ? editorRegistry : pageRendererProps.registry ?? clientComponentRegistry,
2713
+ wrapBlock: allowInlineEditing ? wrapBlockWithProvider : pageRendererProps.wrapBlock,
2714
+ dataContext,
1867
2715
  // Phase 10: the legacy `isEditingMode` flag forced the renderer into
1868
2716
  // draft-mode validation; `usePlaceholders` consolidates that into a
1869
2717
  // narrowly-scoped fallback. The editor's contract is unchanged: a user
@@ -1875,16 +2723,258 @@ function EditablePageRenderer(props) {
1875
2723
  };
1876
2724
  return /* @__PURE__ */ jsx(PageRenderer, { ...rendererProps });
1877
2725
  }
2726
+ function usePreviewBlockResolvedData(args) {
2727
+ const { page, dataContext, apiClient } = args;
2728
+ const cacheKey = useMemo(() => {
2729
+ if (!apiClient || !dataContext?.siteId) return null;
2730
+ return stableSerialize({
2731
+ siteId: dataContext.siteId,
2732
+ pageId: dataContext.pageId ?? null,
2733
+ previewStage: dataContext.previewStage ?? "published",
2734
+ blocks: page.blocks.map((block) => ({
2735
+ id: block.id,
2736
+ kind: block.kind,
2737
+ content: block.content ?? null,
2738
+ draftContent: block.draftContent ?? null,
2739
+ publishedContent: block.publishedContent ?? null
2740
+ }))
2741
+ });
2742
+ }, [apiClient, dataContext?.pageId, dataContext?.previewStage, dataContext?.siteId, page]);
2743
+ const [resolvedData, setResolvedData] = useState(null);
2744
+ useEffect(() => {
2745
+ if (!apiClient || !dataContext?.siteId || !cacheKey) {
2746
+ setResolvedData(null);
2747
+ return;
2748
+ }
2749
+ let cancelled = false;
2750
+ const context = buildPreviewBlockDataContext(dataContext);
2751
+ const loaderClient = createPreviewBlockDataApiClient(apiClient, context);
2752
+ void prefetchBlockData(page, context, {
2753
+ apiClient: loaderClient,
2754
+ isValidEndpoint: isSupportedLoaderEndpoint,
2755
+ onError: () => {
2756
+ }
2757
+ }).then((data) => {
2758
+ if (!cancelled) setResolvedData({ key: cacheKey, data });
2759
+ }).catch(() => {
2760
+ });
2761
+ return () => {
2762
+ cancelled = true;
2763
+ };
2764
+ }, [apiClient, cacheKey, dataContext, page]);
2765
+ return resolvedData?.data ?? null;
2766
+ }
2767
+ function buildPreviewBlockDataContext(dataContext) {
2768
+ return {
2769
+ siteId: dataContext.siteId,
2770
+ pageId: dataContext.pageId,
2771
+ previewStage: dataContext.previewStage,
2772
+ root: {
2773
+ ...dataContext.routes ? { routes: dataContext.routes } : {},
2774
+ ...dataContext.pagesByIdentifier ? { pagesByIdentifier: dataContext.pagesByIdentifier } : {},
2775
+ ...dataContext.entriesByIdentifier ? { entriesByIdentifier: dataContext.entriesByIdentifier } : {},
2776
+ occurrenceContext: dataContext.occurrenceContext ?? null,
2777
+ entrySubrouteContext: dataContext.entrySubrouteContext ?? null,
2778
+ contentEntry: dataContext.contentEntry ?? null,
2779
+ bookingOfferingPresets: dataContext.bookingOfferingPresets ?? []
2780
+ }
2781
+ };
2782
+ }
2783
+ function mergeResolvedDataContext(dataContext, liveResolvedData) {
2784
+ if (!dataContext || !liveResolvedData) return dataContext;
2785
+ return {
2786
+ ...dataContext,
2787
+ resolvedData: {
2788
+ ...dataContext.resolvedData ?? {},
2789
+ ...liveResolvedData
2790
+ }
2791
+ };
2792
+ }
2793
+ function createPreviewBlockDataApiClient(dashboardApiClient, context) {
2794
+ const batcher = createMicrobatcher({
2795
+ maxBatchSize: MAX_PREVIEW_BLOCK_DATA_BATCH_SIZE,
2796
+ runBatch: async (requests) => {
2797
+ if (!context.siteId) {
2798
+ return requests.reduce((acc, request) => {
2799
+ acc[request.id] = {
2800
+ ok: false,
2801
+ error: { code: "missing_site_id", message: "Missing siteId" }
2802
+ };
2803
+ return acc;
2804
+ }, {});
2805
+ }
2806
+ const envelope = await dashboardApiClient({
2807
+ endpoint: "batchBlockLoaders",
2808
+ params: {
2809
+ siteId: context.siteId,
2810
+ stage: context.previewStage
2811
+ },
2812
+ body: { requests }
2813
+ });
2814
+ const payload = unwrapResponse(envelope);
2815
+ return payload.results;
2816
+ }
2817
+ });
2818
+ return async ({ endpoint, params }) => {
2819
+ if (!isSupportedLoaderEndpoint(endpoint)) {
2820
+ throw new Error(`Unsupported block loader endpoint: ${endpoint}`);
2821
+ }
2822
+ const normalizedParams = normalizePublicApiLoaderParams(endpoint, params, context);
2823
+ if (isBatchableEndpoint(endpoint)) {
2824
+ const batchParams = toBatchParams(endpoint, normalizedParams);
2825
+ const batchKey = `${endpoint}:${stableSerialize(batchParams ?? null)}`;
2826
+ return batcher.getOrEnqueue(
2827
+ batchKey,
2828
+ (id) => buildBatchLoaderRequest(id, endpoint, batchParams)
2829
+ );
2830
+ }
2831
+ return callDirectLoaderApiEndpoint(
2832
+ dashboardApiClient,
2833
+ endpoint,
2834
+ normalizedParams
2835
+ );
2836
+ };
2837
+ }
2838
+ async function callDirectLoaderApiEndpoint(dashboardApiClient, endpoint, params) {
2839
+ switch (endpoint) {
2840
+ case "listPublishedEntries":
2841
+ return unwrapResponse(await dashboardApiClient({
2842
+ endpoint,
2843
+ params
2844
+ }));
2845
+ case "getPublishedEntryPreview":
2846
+ return unwrapResponse(await dashboardApiClient({
2847
+ endpoint,
2848
+ params
2849
+ }));
2850
+ case "listPublicPasses":
2851
+ return unwrapResponse(await dashboardApiClient({
2852
+ endpoint,
2853
+ params
2854
+ }));
2855
+ case "listPublicMemberships":
2856
+ return unwrapResponse(await dashboardApiClient({
2857
+ endpoint,
2858
+ params
2859
+ }));
2860
+ }
2861
+ }
2862
+ function isBatchableEndpoint(endpoint) {
2863
+ return endpoint === "listPublicEvents" || endpoint === "getPublicEventDetails" || endpoint === "listPublicCourses" || endpoint === "listPublicRouteCandidates" || endpoint === "listPublicTeamMembers" || endpoint === "listPublicProducts" || endpoint === "getPublicBookingServices" || endpoint === "getPublicFormById";
2864
+ }
2865
+ function buildBatchLoaderRequest(id, endpoint, params) {
2866
+ switch (endpoint) {
2867
+ case "listPublicEvents": {
2868
+ const typedParams = params;
2869
+ return typedParams ? { id, endpoint, params: typedParams } : { id, endpoint };
2870
+ }
2871
+ case "getPublicEventDetails": {
2872
+ const typedParams = params;
2873
+ return typedParams ? { id, endpoint, params: typedParams } : { id, endpoint };
2874
+ }
2875
+ case "listPublicCourses": {
2876
+ const typedParams = params;
2877
+ return typedParams ? { id, endpoint, params: typedParams } : { id, endpoint };
2878
+ }
2879
+ case "listPublicRouteCandidates":
2880
+ return { id, endpoint };
2881
+ case "listPublicProducts": {
2882
+ const typedParams = params;
2883
+ return typedParams ? { id, endpoint, params: typedParams } : { id, endpoint };
2884
+ }
2885
+ case "listPublicTeamMembers":
2886
+ return { id, endpoint };
2887
+ case "getPublicBookingServices": {
2888
+ const typedParams = params;
2889
+ return typedParams ? { id, endpoint, params: typedParams } : { id, endpoint };
2890
+ }
2891
+ case "getPublicFormById":
2892
+ return {
2893
+ id,
2894
+ endpoint,
2895
+ params: params ?? { formId: "" }
2896
+ };
2897
+ }
2898
+ }
2899
+ function stripUndefined(obj) {
2900
+ const out = {};
2901
+ for (const [key, value] of Object.entries(obj)) {
2902
+ if (value !== void 0) out[key] = value;
2903
+ }
2904
+ return out;
2905
+ }
2906
+ function normalizeIdsParam(ids) {
2907
+ if (!ids) return void 0;
2908
+ const parts = ids.split(",").map((id) => id.trim()).filter(Boolean).sort();
2909
+ return parts.length ? parts.join(",") : void 0;
2910
+ }
2911
+ function toBatchParams(endpoint, params) {
2912
+ if (endpoint === "listPublicEvents") {
2913
+ const p = stripUndefined({
2914
+ limit: params.limit || void 0,
2915
+ from: params.from || void 0,
2916
+ to: params.to || void 0,
2917
+ afterStartsAt: params.afterStartsAt || void 0,
2918
+ afterId: params.afterId || void 0,
2919
+ categoryIds: params.categoryIds || void 0,
2920
+ venueId: params.venueId || void 0,
2921
+ staffMemberId: params.staffMemberId || void 0,
2922
+ kind: params.kind || void 0,
2923
+ eventSurface: params.eventSurface || void 0,
2924
+ eventPresets: params.eventPresets || void 0,
2925
+ scheduleScope: params.scheduleScope || void 0,
2926
+ entryId: params.entryId || void 0,
2927
+ seriesId: params.seriesId || void 0,
2928
+ includeOccurrenceId: params.includeOccurrenceId || void 0,
2929
+ detailLevel: params.detailLevel || void 0,
2930
+ readProfile: params.readProfile || void 0
2931
+ });
2932
+ return Object.keys(p).length ? p : void 0;
2933
+ }
2934
+ if (endpoint === "getPublicEventDetails") {
2935
+ const p = stripUndefined({
2936
+ limit: params.limit || void 0,
2937
+ from: params.from || void 0,
2938
+ to: params.to || void 0,
2939
+ afterStartsAt: params.afterStartsAt || void 0,
2940
+ afterId: params.afterId || void 0,
2941
+ categoryIds: params.categoryIds || void 0,
2942
+ venueId: params.venueId || void 0,
2943
+ staffMemberId: params.staffMemberId || void 0,
2944
+ kind: params.kind || void 0,
2945
+ eventSurface: params.eventSurface || void 0,
2946
+ eventPresets: params.eventPresets || void 0,
2947
+ scheduleScope: params.scheduleScope || void 0,
2948
+ entryId: params.entryId || void 0,
2949
+ seriesId: params.seriesId || void 0,
2950
+ includeOccurrenceId: params.includeOccurrenceId || void 0
2951
+ });
2952
+ return Object.keys(p).length ? p : void 0;
2953
+ }
2954
+ if (endpoint === "listPublicCourses") {
2955
+ const p = stripUndefined({ limit: params.limit || void 0 });
2956
+ return Object.keys(p).length ? p : void 0;
2957
+ }
2958
+ if (endpoint === "listPublicProducts") {
2959
+ const p = stripUndefined({
2960
+ categorySelector: decodePublicProductCategorySelectorQuery(params) ?? void 0
2961
+ });
2962
+ return Object.keys(p).length ? p : void 0;
2963
+ }
2964
+ if (endpoint === "getPublicBookingServices") {
2965
+ const p = stripUndefined({ ids: normalizeIdsParam(params.ids || void 0) });
2966
+ return Object.keys(p).length ? p : void 0;
2967
+ }
2968
+ if (endpoint === "getPublicFormById") {
2969
+ return { formId: params.formId ?? "" };
2970
+ }
2971
+ return void 0;
2972
+ }
1878
2973
  function hasThemePalette(theme) {
1879
2974
  return Boolean(theme?.palette);
1880
2975
  }
1881
- function shouldClearHeaderForBlock(blocks, blockIndex) {
1882
- const block = blocks[blockIndex];
1883
- if (block?.kind !== "block.hero") return false;
1884
- return blocks.slice(0, blockIndex).every((candidate) => candidate.kind === "block.site-header");
1885
- }
1886
2976
  function usePreviewHeaderClearance() {
1887
- const [clearance, setClearance] = useState(0);
2977
+ const [clearance, setClearance] = useState({ kind: "none" });
1888
2978
  useEffect(() => {
1889
2979
  let observedHeader = null;
1890
2980
  let resizeObserver = null;
@@ -1899,12 +2989,19 @@ function usePreviewHeaderClearance() {
1899
2989
  }
1900
2990
  }
1901
2991
  if (!header) {
1902
- setClearance(0);
2992
+ setClearance(
2993
+ (current) => current.kind === "none" ? current : { kind: "none" }
2994
+ );
1903
2995
  return;
1904
2996
  }
1905
2997
  const style = window.getComputedStyle(header);
1906
2998
  const isOverlayHeader = style.position === "fixed" || style.position === "sticky";
1907
- setClearance(isOverlayHeader ? measuredOverlayHeaderClearancePx(header) : 0);
2999
+ const nextClearance = headerClearanceFromPx(
3000
+ isOverlayHeader ? measuredOverlayHeaderClearancePx(header) : 0
3001
+ );
3002
+ setClearance(
3003
+ (current) => headerClearanceEquals(current, nextClearance) ? current : nextClearance
3004
+ );
1908
3005
  };
1909
3006
  update();
1910
3007
  const mutationObserver = new MutationObserver(update);
@@ -1923,6 +3020,15 @@ function usePreviewHeaderClearance() {
1923
3020
  }, []);
1924
3021
  return clearance;
1925
3022
  }
3023
+ function headerClearanceEquals(left, right) {
3024
+ if (left.kind !== right.kind) return false;
3025
+ switch (left.kind) {
3026
+ case "none":
3027
+ return true;
3028
+ case "overlay":
3029
+ return right.kind === "overlay" && left.px === right.px;
3030
+ }
3031
+ }
1926
3032
  function findPreviewOverlayHeader() {
1927
3033
  return document.querySelector(
1928
3034
  ".block-site-header.rb-fixed, .block-site-header.rb-sticky"
@@ -2388,25 +3494,25 @@ var INITIAL_ONBOARDING_CONFIRMATION_STATE = {
2388
3494
  resendFeedback: null,
2389
3495
  statusLoadFailed: false
2390
3496
  };
2391
- function isRecord4(value) {
3497
+ function isRecord5(value) {
2392
3498
  return value !== null && typeof value === "object";
2393
3499
  }
2394
3500
  function getErrorMessageFromEnvelope(payload, fallback) {
2395
- const error = isRecord4(payload) ? payload.error : null;
2396
- const message = isRecord4(error) ? error.message : null;
3501
+ const error = isRecord5(payload) ? payload.error : null;
3502
+ const message = isRecord5(error) ? error.message : null;
2397
3503
  return typeof message === "string" && message.trim().length > 0 ? message : fallback;
2398
3504
  }
2399
3505
  function isLaunchReadiness(value) {
2400
- return isRecord4(value) && Array.isArray(value.requirements) && typeof value.pendingRequiredCount === "number" && typeof value.pendingRecommendedCount === "number" && typeof value.pendingOptionalCount === "number" && isRecord4(value.goLiveDecision) && (value.goLiveDecision.kind === "allowed" || value.goLiveDecision.kind === "blocked");
3506
+ return isRecord5(value) && Array.isArray(value.requirements) && typeof value.pendingRequiredCount === "number" && typeof value.pendingRecommendedCount === "number" && typeof value.pendingOptionalCount === "number" && isRecord5(value.goLiveDecision) && (value.goLiveDecision.kind === "allowed" || value.goLiveDecision.kind === "blocked");
2401
3507
  }
2402
3508
  function isOnboardingPreviewStatusResponse(value) {
2403
- return isRecord4(value) && typeof value.dashboardUnlocked === "boolean" && typeof value.requiresEmailConfirmation === "boolean" && isLaunchReadiness(value.launchReadiness);
3509
+ return isRecord5(value) && typeof value.dashboardUnlocked === "boolean" && typeof value.requiresEmailConfirmation === "boolean" && isLaunchReadiness(value.launchReadiness);
2404
3510
  }
2405
3511
  function isResendOnboardingPreviewConfirmationResponse(value) {
2406
- return isRecord4(value) && (value.result === "resent" || value.result === "already_confirmed") && typeof value.dashboardUnlocked === "boolean" && typeof value.requiresEmailConfirmation === "boolean" && isLaunchReadiness(value.launchReadiness);
3512
+ return isRecord5(value) && (value.result === "resent" || value.result === "already_confirmed") && typeof value.dashboardUnlocked === "boolean" && typeof value.requiresEmailConfirmation === "boolean" && isLaunchReadiness(value.launchReadiness);
2407
3513
  }
2408
3514
  function getSuccessData(payload, isData) {
2409
- if (!isRecord4(payload) || payload.success !== true || !isData(payload.data)) {
3515
+ if (!isRecord5(payload) || payload.success !== true || !isData(payload.data)) {
2410
3516
  return null;
2411
3517
  }
2412
3518
  return payload.data;
@@ -2872,313 +3978,6 @@ function PreviewAuthBanner({
2872
3978
  )
2873
3979
  ] });
2874
3980
  }
2875
- var SiteChromeCustomizeContext = createContext(null);
2876
- function SiteChromeCustomizeProvider({
2877
- children,
2878
- siteId,
2879
- apiClient,
2880
- theme,
2881
- themeId = null,
2882
- selectionId = null,
2883
- selectionVersion = null,
2884
- selectionUpdatedAt = null,
2885
- initialSelections,
2886
- headerSelectionContext,
2887
- footerSelectionContext,
2888
- siteStyleAccess = { kind: "hidden" }
2889
- }) {
2890
- const previewDesign = usePreviewDesignCoordinator();
2891
- const { clearPreview, previewTheme } = previewDesign;
2892
- const runnerRef = useRef(createOptimisticActionRunner());
2893
- const saveQueueRef = useRef(Promise.resolve());
2894
- const latestSelectionUpdatedAtRef = useRef(
2895
- initialSelections.selectionUpdatedAt ?? selectionUpdatedAt ?? null
2896
- );
2897
- const initialSelectionVersion = initialSelections.selectionVersion ?? selectionVersion ?? 0;
2898
- const baseSnapshot = useMemo(
2899
- () => resolveStyleConfiguratorInitialSnapshot({
2900
- theme,
2901
- initialSelections: {
2902
- ...initialSelections,
2903
- selectionVersion: initialSelectionVersion,
2904
- selectionUpdatedAt: initialSelections.selectionUpdatedAt ?? selectionUpdatedAt ?? null
2905
- },
2906
- headerSelectionContext,
2907
- footerSelectionContext
2908
- }),
2909
- [footerSelectionContext, headerSelectionContext, initialSelectionVersion, initialSelections, selectionUpdatedAt, theme]
2910
- );
2911
- const [state, setState] = useState(
2912
- () => baseSnapshot ? createStyleConfiguratorState(baseSnapshot) : null
2913
- );
2914
- const stateRef = useRef(state);
2915
- const clearPreviewRef = useRef(clearPreview);
2916
- useEffect(() => {
2917
- stateRef.current = state;
2918
- }, [state]);
2919
- useEffect(() => {
2920
- clearPreviewRef.current = clearPreview;
2921
- }, [clearPreview]);
2922
- useEffect(() => {
2923
- latestSelectionUpdatedAtRef.current = baseSnapshot?.selectionUpdatedAt ?? null;
2924
- }, [baseSnapshot?.selectionUpdatedAt]);
2925
- useEffect(() => {
2926
- if (!baseSnapshot) {
2927
- setState(null);
2928
- return;
2929
- }
2930
- setState(
2931
- (current) => current ? syncStyleConfiguratorState(current, baseSnapshot) : createStyleConfiguratorState(baseSnapshot)
2932
- );
2933
- }, [baseSnapshot]);
2934
- useEffect(() => {
2935
- const runner = runnerRef.current;
2936
- return () => {
2937
- runner.invalidate();
2938
- clearPreviewRef.current("site-chrome");
2939
- };
2940
- }, []);
2941
- const lookModels = useMemo(() => {
2942
- if (!state) return null;
2943
- const siteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
2944
- if (!siteStyle) return null;
2945
- return buildSiteChromeLookModels({
2946
- state,
2947
- siteStyle,
2948
- headerSelectionContext,
2949
- footerSelectionContext
2950
- });
2951
- }, [footerSelectionContext, headerSelectionContext, state]);
2952
- const applyAllowed = siteStyleAccess.kind === "apply-allowed";
2953
- const getSurfaceState = useCallback(
2954
- (surface) => {
2955
- if (!state || !lookModels) return { kind: "unavailable" };
2956
- if (surface === "header") {
2957
- const currentLayoutId = state.previewed.headerLayoutId;
2958
- const selectedLayout = lookModels.headerLayout.options.find(
2959
- (option) => option.id === currentLayoutId
2960
- );
2961
- const renderedLayout = lookModels.headerLayout.options.find(
2962
- (option) => option.id === lookModels.headerLayout.renderedLayoutId
2963
- );
2964
- const currentId2 = state.previewed.headerStyleId;
2965
- const selected2 = lookModels.headerStyle.options.find(
2966
- (option) => option.id === currentId2
2967
- );
2968
- const rendered2 = lookModels.headerStyle.options.find(
2969
- (option) => option.id === lookModels.headerStyle.renderedStyleId
2970
- );
2971
- return {
2972
- kind: "available",
2973
- surface: "header",
2974
- label: "Header",
2975
- currentLayoutId,
2976
- currentLayoutLabel: selectedLayout?.label ?? (renderedLayout ? `${renderedLayout.label} (default)` : "Unknown Layout"),
2977
- layoutOptions: lookModels.headerLayout.options,
2978
- currentId: currentId2,
2979
- currentLabel: selected2?.label ?? (rendered2 ? `${rendered2.label} (default)` : "Unknown Look"),
2980
- options: lookModels.headerStyle.options,
2981
- diagnostics: [],
2982
- saving: state.saveStatus.kind === "saving",
2983
- failure: state.saveStatus.kind === "failed" ? state.saveStatus.message : null,
2984
- applyAllowed
2985
- };
2986
- }
2987
- const currentId = state.previewed.footerLookId;
2988
- const selected = lookModels.footer.options.find((option) => option.id === currentId);
2989
- const rendered = lookModels.footer.options.find(
2990
- (option) => option.id === lookModels.footer.renderedLookId
2991
- );
2992
- return {
2993
- kind: "available",
2994
- surface: "footer",
2995
- label: "Footer",
2996
- currentId,
2997
- currentLabel: selected?.label ?? (currentId ? "Unknown Look" : `${rendered?.label ?? "Site style"} (default)`),
2998
- options: lookModels.footer.options,
2999
- diagnostics: lookModels.footer.diagnostics,
3000
- saving: state.saveStatus.kind === "saving",
3001
- failure: state.saveStatus.kind === "failed" ? state.saveStatus.message : null,
3002
- applyAllowed
3003
- };
3004
- },
3005
- [applyAllowed, lookModels, state]
3006
- );
3007
- const persistSelection = useCallback(
3008
- (surface, nextSnapshot) => {
3009
- const siteStyle = getCuratedSiteStyle(nextSnapshot.siteStyleId);
3010
- if (!siteStyle) return;
3011
- const compiled = planSiteStylePreviewChange({
3012
- siteStyle,
3013
- selection: nextSnapshot,
3014
- baseTheme: theme,
3015
- headerSelectionContext,
3016
- footerSelectionContext
3017
- });
3018
- void runnerRef.current.run({
3019
- optimistic: () => {
3020
- previewTheme("site-chrome", compiled.themeOverlay, "site-chrome");
3021
- setState((latest) => {
3022
- if (!latest) return latest;
3023
- return markApplySaving({ ...latest, previewed: nextSnapshot });
3024
- });
3025
- },
3026
- network: async () => {
3027
- const runSave = async () => {
3028
- const body = surface === "header" ? {
3029
- surface,
3030
- lookId: nextSnapshot.headerLookId,
3031
- layoutId: nextSnapshot.headerLayoutId,
3032
- styleId: nextSnapshot.headerStyleId,
3033
- theme: compiled.themeOverlay,
3034
- themeId: themeId ?? null,
3035
- selectionId: selectionId ?? null,
3036
- baseSelectionUpdatedAt: latestSelectionUpdatedAtRef.current
3037
- } : {
3038
- surface,
3039
- lookId: nextSnapshot.footerLookId,
3040
- theme: compiled.themeOverlay,
3041
- themeId: themeId ?? null,
3042
- selectionId: selectionId ?? null,
3043
- baseSelectionUpdatedAt: latestSelectionUpdatedAtRef.current
3044
- };
3045
- const response = await apiClient({
3046
- endpoint: "saveSiteChromeLookSelection",
3047
- params: { siteId },
3048
- body
3049
- });
3050
- const saved = unwrapResponse(response);
3051
- latestSelectionUpdatedAtRef.current = saved.selectionUpdatedAt ?? latestSelectionUpdatedAtRef.current;
3052
- return saved;
3053
- };
3054
- const queuedSave = saveQueueRef.current.catch(() => void 0).then(runSave);
3055
- saveQueueRef.current = queuedSave.then(
3056
- () => void 0,
3057
- () => void 0
3058
- );
3059
- return queuedSave;
3060
- },
3061
- onSuccess: (saved) => {
3062
- latestSelectionUpdatedAtRef.current = saved.selectionUpdatedAt ?? latestSelectionUpdatedAtRef.current;
3063
- const persisted = {
3064
- siteStyleId: nextSnapshot.siteStyleId,
3065
- selectionVersion: nextSnapshot.selectionVersion,
3066
- selectionUpdatedAt: saved.selectionUpdatedAt ?? nextSnapshot.selectionUpdatedAt ?? null,
3067
- buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
3068
- paletteVariantId: compiled.resolvedSelections.paletteVariantId,
3069
- paletteOverrides: nextSnapshot.paletteOverrides,
3070
- typographyPresetId: compiled.resolvedSelections.typographyPresetId,
3071
- typographyBodyFontOverride: compiled.resolvedSelections.typographyBodyFontOverride,
3072
- typographyHeadingFontOverride: compiled.resolvedSelections.typographyHeadingFontOverride,
3073
- headerLookId: saved.headerLookId ?? nextSnapshot.headerLookId,
3074
- headerLayoutId: saved.headerLayoutId ?? compiled.resolvedSelections.headerLayoutId,
3075
- headerStyleId: saved.headerStyleId ?? compiled.resolvedSelections.headerStyleId,
3076
- footerLookId: saved.footerLookId ?? nextSnapshot.footerLookId
3077
- };
3078
- setState((latest) => latest ? markApplySaved(latest, persisted) : latest);
3079
- },
3080
- onError: (error) => {
3081
- const message = error instanceof Error ? error.message : "Unable to apply your selection.";
3082
- setState((latest) => latest ? markApplyFailed(latest, message) : latest);
3083
- }
3084
- });
3085
- },
3086
- [
3087
- apiClient,
3088
- footerSelectionContext,
3089
- headerSelectionContext,
3090
- previewTheme,
3091
- selectionId,
3092
- siteId,
3093
- themeId,
3094
- theme
3095
- ]
3096
- );
3097
- const selectHeaderStyle = useCallback(
3098
- (styleId) => {
3099
- if (!applyAllowed) return;
3100
- const current = stateRef.current;
3101
- if (!current) return;
3102
- const nextState = previewHeaderStyle(current, styleId);
3103
- persistSelection("header", nextState.previewed);
3104
- },
3105
- [applyAllowed, persistSelection]
3106
- );
3107
- const selectHeaderLayout = useCallback(
3108
- (layoutId) => {
3109
- if (!applyAllowed) return;
3110
- const current = stateRef.current;
3111
- if (!current) return;
3112
- const nextState = previewHeaderLayout(current, layoutId);
3113
- persistSelection("header", nextState.previewed);
3114
- },
3115
- [applyAllowed, persistSelection]
3116
- );
3117
- const selectFooterLook = useCallback(
3118
- (lookId) => {
3119
- if (!applyAllowed) return;
3120
- const current = stateRef.current;
3121
- if (!current) return;
3122
- const nextState = previewFooterLook(current, planFooterLookSelection(lookId));
3123
- persistSelection("footer", nextState.previewed);
3124
- },
3125
- [applyAllowed, persistSelection]
3126
- );
3127
- const cycleSurfaceLook = useCallback(
3128
- (surface, direction) => {
3129
- const currentState = getSurfaceState(surface);
3130
- if (currentState.kind !== "available") return;
3131
- if (currentState.surface === "header") {
3132
- const next2 = cycleSiteChromeLookId(
3133
- currentState.options,
3134
- currentState.currentId,
3135
- direction
3136
- );
3137
- if (next2 !== void 0 && next2 !== null) selectHeaderStyle(next2);
3138
- return;
3139
- }
3140
- const next = cycleSiteChromeLookId(
3141
- currentState.options,
3142
- currentState.currentId,
3143
- direction
3144
- );
3145
- if (next !== void 0) selectFooterLook(next);
3146
- },
3147
- [getSurfaceState, selectFooterLook, selectHeaderStyle]
3148
- );
3149
- const cycleSurfaceLayout = useCallback(
3150
- (surface, direction) => {
3151
- const currentState = getSurfaceState(surface);
3152
- if (currentState.kind !== "available" || currentState.surface !== "header") {
3153
- return;
3154
- }
3155
- const next = cycleSiteChromeLookId(
3156
- currentState.layoutOptions,
3157
- currentState.currentLayoutId,
3158
- direction
3159
- );
3160
- if (next !== void 0 && next !== null) selectHeaderLayout(next);
3161
- },
3162
- [getSurfaceState, selectHeaderLayout]
3163
- );
3164
- const value = useMemo(
3165
- () => ({
3166
- getSurfaceState,
3167
- cycleSurfaceLook,
3168
- cycleSurfaceLayout
3169
- }),
3170
- [cycleSurfaceLayout, cycleSurfaceLook, getSurfaceState]
3171
- );
3172
- return /* @__PURE__ */ jsx(SiteChromeCustomizeContext.Provider, { value, children });
3173
- }
3174
- function planFooterLookSelection(lookId) {
3175
- const plan = planSiteChromeLookSelection({ surface: "footer", lookId });
3176
- if (plan.kind !== "footer") return lookId;
3177
- return plan.next.footerLookId;
3178
- }
3179
- function useSiteChromeCustomizeOptional() {
3180
- return useContext(SiteChromeCustomizeContext);
3181
- }
3182
3981
 
3183
3982
  // ../blocks/src/client/site-chrome.tsx
3184
3983
  function renderSiteChromeBlock(model, surface, theme, options = {}) {
@@ -3212,22 +4011,7 @@ function SiteChromeCustomizeButton({
3212
4011
  containerClassName = "pointer-events-auto absolute right-2 top-2 z-50 opacity-0 transition group-focus-within/site-chrome:opacity-100 group-hover/site-chrome:opacity-100"
3213
4012
  }) {
3214
4013
  const customize = useSiteChromeCustomizeOptional();
3215
- const customizePanelId = useId();
3216
- const customizeDescriptionId = `${customizePanelId}-description`;
3217
- const [customizeOpen, setCustomizeOpen] = useState(false);
3218
- const customizeButtonRef = useRef(null);
3219
- const closeCustomize = useCallback(() => {
3220
- setCustomizeOpen(false);
3221
- }, []);
3222
- const {
3223
- dialogRef: customizeDialogRef,
3224
- registerInitialFocus: registerCustomizeInitialFocus,
3225
- setReturnFocusTarget: setCustomizeReturnFocusTarget,
3226
- handleDialogKeyDown: handleCustomizeDialogKeyDown
3227
- } = useDialogFocusTrap({
3228
- open: customizeOpen,
3229
- onClose: closeCustomize
3230
- });
4014
+ const popover = useCustomizePopoverController();
3231
4015
  const surfaceState = customize?.getSurfaceState(surface) ?? {
3232
4016
  kind: "unavailable"
3233
4017
  };
@@ -3242,157 +4026,133 @@ function SiteChromeCustomizeButton({
3242
4026
  }
3243
4027
  return surfaceState.layoutOptions.filter((option) => option.available).length;
3244
4028
  }, [surfaceState]);
3245
- const toggleCustomize = (event) => {
3246
- event.preventDefault();
3247
- event.stopPropagation();
3248
- if (!customizeOpen) {
3249
- setCustomizeReturnFocusTarget(
3250
- event.currentTarget instanceof HTMLElement ? event.currentTarget : customizeButtonRef.current
3251
- );
4029
+ const controlPlan = useMemo(() => {
4030
+ if (surfaceState.kind !== "available") return { cycleRows: [] };
4031
+ return {
4032
+ cycleRows: [
4033
+ ...surfaceState.surface === "header" ? [
4034
+ {
4035
+ label: "Layout",
4036
+ valueLabel: surfaceState.currentLayoutLabel,
4037
+ previousLabel: `Previous ${surface} Layout`,
4038
+ nextLabel: `Next ${surface} Layout`,
4039
+ availability: cycleAvailabilityFromFlags({
4040
+ canCycle: cycleableLayoutOptionCount > 1,
4041
+ applyAllowed: surfaceState.applyAllowed
4042
+ }),
4043
+ onPrevious: () => customize?.cycleSurfaceLayout(surface, "prev"),
4044
+ onNext: () => customize?.cycleSurfaceLayout(surface, "next")
4045
+ }
4046
+ ] : [],
4047
+ {
4048
+ label: "Look",
4049
+ valueLabel: surfaceState.currentLabel,
4050
+ previousLabel: `Previous ${surface} Look`,
4051
+ nextLabel: `Next ${surface} Look`,
4052
+ availability: cycleAvailabilityFromFlags({
4053
+ canCycle: cycleableOptionCount > 1,
4054
+ applyAllowed: surfaceState.applyAllowed
4055
+ }),
4056
+ onPrevious: () => customize?.cycleSurfaceLook(surface, "prev"),
4057
+ onNext: () => customize?.cycleSurfaceLook(surface, "next")
4058
+ }
4059
+ ]
4060
+ };
4061
+ }, [
4062
+ customize,
4063
+ cycleableLayoutOptionCount,
4064
+ cycleableOptionCount,
4065
+ surface,
4066
+ surfaceState
4067
+ ]);
4068
+ return /* @__PURE__ */ jsxs(
4069
+ PreviewChromeFloatingControl,
4070
+ {
4071
+ anchorSelector: "[data-preview-site-chrome-surface]",
4072
+ placement: (anchorBox) => planSiteChromeCustomizePlacement({ surface, anchorBox }),
4073
+ fallbackClassName: containerClassName,
4074
+ forceVisible: popover.open,
4075
+ children: [
4076
+ /* @__PURE__ */ jsx(
4077
+ CustomizePillButton,
4078
+ {
4079
+ buttonRef: popover.buttonRef,
4080
+ ariaLabel: `Customize ${surface}`,
4081
+ open: popover.open,
4082
+ controlsId: popover.panelId,
4083
+ onClick: popover.toggle,
4084
+ disabled: !hasCustomizeControls
4085
+ }
4086
+ ),
4087
+ popover.open && surfaceState.kind === "available" ? /* @__PURE__ */ jsxs(
4088
+ CustomizePopoverShell,
4089
+ {
4090
+ panelId: popover.panelId,
4091
+ descriptionId: popover.descriptionId,
4092
+ dialogLabel: `Customize ${surface}`,
4093
+ eyebrow: "Site",
4094
+ title: surfaceState.label,
4095
+ dialogRef: popover.dialogRef,
4096
+ closeButtonRef: popover.registerInitialFocus,
4097
+ onClose: popover.close,
4098
+ onKeyDown: popover.handleDialogKeyDown,
4099
+ children: [
4100
+ controlPlan.cycleRows.map((row) => /* @__PURE__ */ jsx(CustomizeCycleRow, { ...row }, row.label)),
4101
+ surfaceState.surface === "header" ? /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 border-t pt-3", children: [
4102
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium text-muted-foreground", children: [
4103
+ /* @__PURE__ */ jsx("span", { children: "Display" }),
4104
+ /* @__PURE__ */ jsx(
4105
+ "select",
4106
+ {
4107
+ className: "mt-1 h-8 w-full rounded-md border bg-background px-2 text-sm text-foreground disabled:cursor-not-allowed disabled:opacity-50",
4108
+ value: surfaceState.brandDisplay,
4109
+ onChange: (event) => {
4110
+ event.stopPropagation();
4111
+ customize?.setHeaderBrandDisplay(
4112
+ event.currentTarget.value
4113
+ );
4114
+ },
4115
+ disabled: !surfaceState.applyAllowed,
4116
+ children: surfaceState.brandDisplayOptions.map((option) => /* @__PURE__ */ jsx(
4117
+ "option",
4118
+ {
4119
+ value: option.value,
4120
+ disabled: option.disabled,
4121
+ children: option.label
4122
+ },
4123
+ option.value
4124
+ ))
4125
+ }
4126
+ )
4127
+ ] }),
4128
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium text-muted-foreground", children: [
4129
+ /* @__PURE__ */ jsx("span", { children: "Logo size" }),
4130
+ /* @__PURE__ */ jsx(
4131
+ "select",
4132
+ {
4133
+ className: "mt-1 h-8 w-full rounded-md border bg-background px-2 text-sm text-foreground disabled:cursor-not-allowed disabled:opacity-50",
4134
+ value: surfaceState.logoSize,
4135
+ onChange: (event) => {
4136
+ event.stopPropagation();
4137
+ customize?.setHeaderLogoSize(
4138
+ event.currentTarget.value
4139
+ );
4140
+ },
4141
+ disabled: !surfaceState.applyAllowed,
4142
+ children: surfaceState.logoSizeOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
4143
+ }
4144
+ )
4145
+ ] })
4146
+ ] }) : null,
4147
+ surfaceState.diagnostics.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "space-y-1 text-xs text-amber-700", children: surfaceState.diagnostics.map((diagnostic) => /* @__PURE__ */ jsx("li", { children: diagnostic }, diagnostic)) }) : null,
4148
+ surfaceState.failure ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-700", children: surfaceState.failure }) : null,
4149
+ !surfaceState.applyAllowed ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Preview only - ask an admin to apply." }) : null
4150
+ ]
4151
+ }
4152
+ ) : null
4153
+ ]
3252
4154
  }
3253
- setCustomizeOpen((open) => !open);
3254
- };
3255
- return /* @__PURE__ */ jsxs("div", { className: containerClassName, children: [
3256
- /* @__PURE__ */ jsx(
3257
- "button",
3258
- {
3259
- ref: customizeButtonRef,
3260
- type: "button",
3261
- onClick: toggleCustomize,
3262
- className: previewCustomizePillButtonClassName,
3263
- "aria-label": `Customize ${surface}`,
3264
- "aria-expanded": customizeOpen,
3265
- "aria-haspopup": "dialog",
3266
- "aria-controls": customizeOpen ? customizePanelId : void 0,
3267
- disabled: !hasCustomizeControls,
3268
- children: surfaceState.kind === "available" && surfaceState.saving ? "Saving..." : "Customize"
3269
- }
3270
- ),
3271
- customizeOpen && surfaceState.kind === "available" ? /* @__PURE__ */ jsxs(
3272
- "div",
3273
- {
3274
- id: customizePanelId,
3275
- className: "builder-dashboard-chrome-theme fixed inset-x-3 bottom-[calc(var(--builder-editor-chrome-offset,0px)+1rem)] z-[70] rounded-lg border bg-background p-3 text-foreground shadow-xl sm:absolute sm:inset-x-auto sm:bottom-auto sm:right-0 sm:top-9 sm:w-72",
3276
- role: "dialog",
3277
- "aria-modal": "true",
3278
- "aria-label": `Customize ${surface}`,
3279
- "aria-describedby": customizeDescriptionId,
3280
- ref: customizeDialogRef,
3281
- onKeyDown: handleCustomizeDialogKeyDown,
3282
- onClick: (event) => event.stopPropagation(),
3283
- children: [
3284
- /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-start justify-between gap-3", children: [
3285
- /* @__PURE__ */ jsxs("div", { children: [
3286
- /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase text-muted-foreground", children: "Site" }),
3287
- /* @__PURE__ */ jsx(
3288
- "div",
3289
- {
3290
- id: customizeDescriptionId,
3291
- className: "text-sm font-semibold",
3292
- children: surfaceState.label
3293
- }
3294
- )
3295
- ] }),
3296
- /* @__PURE__ */ jsx(
3297
- "button",
3298
- {
3299
- ref: registerCustomizeInitialFocus,
3300
- type: "button",
3301
- className: "rounded-full px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
3302
- onClick: (event) => {
3303
- event.preventDefault();
3304
- event.stopPropagation();
3305
- closeCustomize();
3306
- },
3307
- children: "Close"
3308
- }
3309
- )
3310
- ] }),
3311
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3312
- surfaceState.surface === "header" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
3313
- /* @__PURE__ */ jsxs("div", { children: [
3314
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: "Layout" }),
3315
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: surfaceState.currentLayoutLabel })
3316
- ] }),
3317
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
3318
- /* @__PURE__ */ jsx(
3319
- "button",
3320
- {
3321
- type: "button",
3322
- onClick: (event) => {
3323
- event.preventDefault();
3324
- event.stopPropagation();
3325
- customize?.cycleSurfaceLayout(surface, "prev");
3326
- },
3327
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
3328
- "aria-label": `Previous ${surface} Layout`,
3329
- disabled: surfaceState.saving || !surfaceState.applyAllowed || cycleableLayoutOptionCount <= 1,
3330
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
3331
- }
3332
- ),
3333
- /* @__PURE__ */ jsx(
3334
- "button",
3335
- {
3336
- type: "button",
3337
- onClick: (event) => {
3338
- event.preventDefault();
3339
- event.stopPropagation();
3340
- customize?.cycleSurfaceLayout(surface, "next");
3341
- },
3342
- className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
3343
- "aria-label": `Next ${surface} Layout`,
3344
- disabled: surfaceState.saving || !surfaceState.applyAllowed || cycleableLayoutOptionCount <= 1,
3345
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
3346
- }
3347
- )
3348
- ] })
3349
- ] }) : null,
3350
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
3351
- /* @__PURE__ */ jsxs("div", { children: [
3352
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: "Look" }),
3353
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: surfaceState.currentLabel })
3354
- ] }),
3355
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
3356
- /* @__PURE__ */ jsx(
3357
- "button",
3358
- {
3359
- type: "button",
3360
- onClick: (event) => {
3361
- event.preventDefault();
3362
- event.stopPropagation();
3363
- customize?.cycleSurfaceLook(surface, "prev");
3364
- },
3365
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
3366
- "aria-label": `Previous ${surface} Look`,
3367
- disabled: surfaceState.saving || !surfaceState.applyAllowed || cycleableOptionCount <= 1,
3368
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
3369
- }
3370
- ),
3371
- /* @__PURE__ */ jsx(
3372
- "button",
3373
- {
3374
- type: "button",
3375
- onClick: (event) => {
3376
- event.preventDefault();
3377
- event.stopPropagation();
3378
- customize?.cycleSurfaceLook(surface, "next");
3379
- },
3380
- className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
3381
- "aria-label": `Next ${surface} Look`,
3382
- disabled: surfaceState.saving || !surfaceState.applyAllowed || cycleableOptionCount <= 1,
3383
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
3384
- }
3385
- )
3386
- ] })
3387
- ] }),
3388
- surfaceState.diagnostics.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "space-y-1 text-xs text-amber-700", children: surfaceState.diagnostics.map((diagnostic) => /* @__PURE__ */ jsx("li", { children: diagnostic }, diagnostic)) }) : null,
3389
- surfaceState.failure ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-700", children: surfaceState.failure }) : null,
3390
- !surfaceState.applyAllowed ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Preview only - ask an admin to apply." }) : null
3391
- ] })
3392
- ]
3393
- }
3394
- ) : null
3395
- ] });
4155
+ );
3396
4156
  }
3397
4157
  function SiteChromeEditableSurface({
3398
4158
  surface,
@@ -3410,7 +4170,7 @@ function renderEditableSurface(children, surface, options) {
3410
4170
  const child = childArray.length === 1 ? childArray[0] : null;
3411
4171
  if (isValidElement(child) && child.type !== Fragment$1 && (isHostElement(child) || options.preserveSingleComponentRoot || isEditableSurfaceForwardingElement(child))) {
3412
4172
  return cloneElement(child, {
3413
- className: joinClassNames(child.props.className, "group/site-chrome relative"),
4173
+ className: joinClassNames2(child.props.className, "group/site-chrome relative"),
3414
4174
  "data-preview-site-chrome-surface": surface,
3415
4175
  children: /* @__PURE__ */ jsxs(Fragment, { children: [
3416
4176
  child.props.children,
@@ -3449,7 +4209,7 @@ function isHostElement(child) {
3449
4209
  function isEditableSurfaceForwardingElement(child) {
3450
4210
  return typeof child.type !== "string" && child.type !== Fragment$1 && child.type[EDITABLE_SURFACE_FORWARDING_PROP] === true;
3451
4211
  }
3452
- function joinClassNames(...values) {
4212
+ function joinClassNames2(...values) {
3453
4213
  const className = values.filter(Boolean).join(" ");
3454
4214
  return className.length > 0 ? className : void 0;
3455
4215
  }
@@ -3631,13 +4391,27 @@ function renderPreviewDocument({
3631
4391
  }
3632
4392
  return null;
3633
4393
  }
4394
+ function LinkedEntryEditEventBridge() {
4395
+ const uiStore = useEditorUiStoreInstance();
4396
+ useEffect(() => {
4397
+ const handleLinkedEntryEdit = (event) => {
4398
+ if (!isLinkedEntryEditEvent(event)) return;
4399
+ uiStore.getState().linkedEntries.open(event.detail);
4400
+ };
4401
+ window.addEventListener(LINKED_ENTRY_EDIT_EVENT, handleLinkedEntryEdit);
4402
+ return () => {
4403
+ window.removeEventListener(LINKED_ENTRY_EDIT_EVENT, handleLinkedEntryEdit);
4404
+ };
4405
+ }, [uiStore]);
4406
+ return null;
4407
+ }
3634
4408
  var previewContainerResponsiveEnabled = process.env.NEXT_PUBLIC_ENABLE_PREVIEW_CONTAINER_RESPONSIVE !== "false";
3635
- var PreviewEditorSidebar = React9.lazy(async () => {
3636
- const mod = await import('./PreviewEditorSidebar-QZG37QIF.mjs');
4409
+ var PreviewEditorSidebar = React10.lazy(async () => {
4410
+ const mod = await import('./PreviewEditorSidebar-RDCMEXIJ.mjs');
3637
4411
  return { default: mod.PreviewEditorSidebar };
3638
4412
  });
3639
- var PreviewEditorUI = React9.lazy(async () => {
3640
- const mod = await import('./PreviewEditorUI-AXIKSMZA.mjs');
4413
+ var PreviewEditorUI = React10.lazy(async () => {
4414
+ const mod = await import('./PreviewEditorUI-NYGQ6K7B.mjs');
3641
4415
  return { default: mod.PreviewEditorUI };
3642
4416
  });
3643
4417
  var previewPaneContainmentStyle = {
@@ -3853,7 +4627,7 @@ function PreviewShellLayout({
3853
4627
  {
3854
4628
  className: "relative flex h-full min-h-0 flex-col overflow-auto bg-muted/30",
3855
4629
  style: previewPaneContainmentStyle,
3856
- children: /* @__PURE__ */ jsx(
4630
+ children: /* @__PURE__ */ jsx(PreviewChromeLayerProvider, { children: /* @__PURE__ */ jsx(
3857
4631
  PreviewThemedSiteDocument,
3858
4632
  {
3859
4633
  useThemeScope,
@@ -3870,7 +4644,7 @@ function PreviewShellLayout({
3870
4644
  siteChromeRenderModel,
3871
4645
  children
3872
4646
  }
3873
- )
4647
+ ) })
3874
4648
  }
3875
4649
  );
3876
4650
  const rightPane = /* @__PURE__ */ jsx(
@@ -3878,7 +4652,7 @@ function PreviewShellLayout({
3878
4652
  {
3879
4653
  className: `${PREVIEW_CHROME_THEME_CLASS_NAME} flex h-full min-h-0 flex-col overflow-hidden ${surfaceDocument.surfaceClassName ?? ""}`,
3880
4654
  ...getPreviewChromeSurfaceDataProps(surfaceDocument),
3881
- children: /* @__PURE__ */ jsx(React9.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
4655
+ children: /* @__PURE__ */ jsx(React10.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
3882
4656
  PreviewEditorSidebar,
3883
4657
  {
3884
4658
  surfaceId,
@@ -3933,7 +4707,7 @@ function PreviewShellLayout({
3933
4707
  ) })
3934
4708
  }
3935
4709
  );
3936
- const editorUI = /* @__PURE__ */ jsx(React9.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
4710
+ const editorUI = /* @__PURE__ */ jsx(React10.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
3937
4711
  PreviewEditorUI,
3938
4712
  {
3939
4713
  inMaintenanceMode: maintenanceModeEnabled,
@@ -3978,6 +4752,7 @@ function PreviewShellLayout({
3978
4752
  footerSelectionContext: themeSelectionFooterContext,
3979
4753
  siteStyleAccess,
3980
4754
  children: /* @__PURE__ */ jsxs(PreviewBlockCustomizeSelectionProvider, { children: [
4755
+ /* @__PURE__ */ jsx(LinkedEntryEditEventBridge, {}),
3981
4756
  editorUI,
3982
4757
  previewBanner,
3983
4758
  /* @__PURE__ */ jsx("div", { className: "fixed inset-0 overflow-hidden bg-background", children: isOpen ? /* @__PURE__ */ jsx(
@@ -4077,7 +4852,7 @@ function PreviewShell({
4077
4852
  initialBlocks,
4078
4853
  pageMetadata,
4079
4854
  apiClient,
4080
- children: /* @__PURE__ */ jsx(
4855
+ children: /* @__PURE__ */ jsx(PreviewDesignHistoryOrderProvider, { children: /* @__PURE__ */ jsx(
4081
4856
  PreviewPageDesignEditorStateProvider,
4082
4857
  {
4083
4858
  apiClient,
@@ -4141,7 +4916,7 @@ function PreviewShell({
4141
4916
  }
4142
4917
  )
4143
4918
  }
4144
- )
4919
+ ) })
4145
4920
  }
4146
4921
  ) })
4147
4922
  }
@@ -4156,17 +4931,6 @@ function defaultDesignEditorEligibility(input) {
4156
4931
  }
4157
4932
  return { kind: "unsupported" };
4158
4933
  }
4159
-
4160
- // ../preview-next/src/client/preview/previewSurface.ts
4161
- function resolvePreviewSurfaceIdFromSearch(search) {
4162
- return parseSurfaceId(new URLSearchParams(search).get(PREVIEW_SURFACE_PARAM));
4163
- }
4164
- function resolvePreviewSurfaceId() {
4165
- if (typeof window === "undefined") {
4166
- return null;
4167
- }
4168
- return resolvePreviewSurfaceIdFromSearch(window.location.search);
4169
- }
4170
4934
  function SdkPreviewModeRuntime(props) {
4171
4935
  const {
4172
4936
  apiBaseUrl,
@@ -4180,7 +4944,7 @@ function SdkPreviewModeRuntime(props) {
4180
4944
  useThemeScope = true
4181
4945
  } = props;
4182
4946
  const resolvedDashboardBaseUrl = dashboardBaseUrl ?? deriveDashboardBaseUrlFromApiBaseUrl(apiBaseUrl);
4183
- const surfaceId = props.surfaceId ?? resolvePreviewSurfaceId() ?? DEFAULT_SURFACE_ID;
4947
+ const surfaceId = props.surfaceId ?? DEFAULT_SURFACE_ID;
4184
4948
  const theme = content.theme;
4185
4949
  const page = content.page;
4186
4950
  const routeMap = content.routeMap ?? void 0;