@riverbankcms/sdk 0.75.0 → 0.77.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 (177) hide show
  1. package/README.md +2 -0
  2. package/dist/{PreviewEditorSidebar-U2IHCBYN.mjs → PreviewEditorSidebar-XSX7QKHQ.mjs} +1179 -519
  3. package/dist/{PreviewEditorUI-ZGH2RQNP.mjs → PreviewEditorUI-YZNEZ3QD.mjs} +2 -2
  4. package/dist/SdkPreviewModeRuntime-5FS5E6BI.mjs +8 -0
  5. package/dist/_dts/ai/src/contracts/proposals.d.ts +528 -20
  6. package/dist/_dts/api/src/bookingOperations.d.ts +12 -0
  7. package/dist/_dts/api/src/contentRuntime.d.ts +5 -0
  8. package/dist/_dts/api/src/index.d.ts +1 -1
  9. package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +11 -3
  10. package/dist/_dts/api/src/siteAssets.d.ts +1 -0
  11. package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +26 -2
  12. package/dist/_dts/block-form/src/blockDraftSaveability.d.ts +28 -1
  13. package/dist/_dts/block-form/src/client/index.d.ts +2 -2
  14. package/dist/_dts/block-form/src/index.d.ts +2 -1
  15. package/dist/_dts/block-form/src/manifest/utils.d.ts +24 -0
  16. package/dist/_dts/block-form/src/widgets/DateField/dateFieldSchema.d.ts +1 -1
  17. package/dist/_dts/block-form/src/widgets/DateTimeField/dateTimeFieldSchema.d.ts +1 -1
  18. package/dist/_dts/block-form/src/widgets/TimeField/timeFieldSchema.d.ts +1 -1
  19. package/dist/_dts/blocks/src/PageRenderer.d.ts +10 -10
  20. package/dist/_dts/blocks/src/client/events.d.ts +3 -1
  21. package/dist/_dts/blocks/src/client/site-chrome.d.ts +22 -0
  22. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +4 -4
  23. package/dist/_dts/blocks/src/customBlockRegistry.d.ts +38 -0
  24. package/dist/_dts/blocks/src/index.d.ts +2 -0
  25. package/dist/_dts/blocks/src/system/blocks/faq.d.ts +20 -2
  26. package/dist/_dts/blocks/src/system/blocks/feature-cards.d.ts +25 -0
  27. package/dist/_dts/blocks/src/system/blocks/index.d.ts +2 -0
  28. package/dist/_dts/blocks/src/system/blocks/intro.d.ts +24 -0
  29. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +3 -34
  30. package/dist/_dts/blocks/src/system/constants/blockKinds.d.ts +1 -1
  31. package/dist/_dts/blocks/src/system/fields/aspectRatio.d.ts +30 -0
  32. package/dist/_dts/blocks/src/system/fields/boxStyles.d.ts +4 -1
  33. package/dist/_dts/blocks/src/system/fields/ctas.d.ts +7 -0
  34. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +11 -2
  35. package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +1 -1
  36. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +6 -6
  37. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +3 -3
  38. package/dist/_dts/blocks/src/system/runtime/nodes/accordion.d.ts +2 -0
  39. package/dist/_dts/blocks/src/system/runtime/nodes/accordion.server.d.ts +2 -0
  40. package/dist/_dts/blocks/src/system/runtime/nodes/accordionIconTreatment.d.ts +5 -0
  41. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.interactive.d.ts +3 -27
  42. package/dist/_dts/blocks/src/system/runtime/nodes/booking-runtime-sources.d.ts +163 -0
  43. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration.interactive.d.ts +5 -5
  44. package/dist/_dts/blocks/src/system/runtime/shared/components/BookingOptionCompactRow.d.ts +29 -0
  45. package/dist/_dts/blocks/src/system/runtime/shared/components/BookingOptionList.d.ts +13 -0
  46. package/dist/_dts/blocks/src/system/runtime/shared/components/index.d.ts +4 -0
  47. package/dist/_dts/blocks/src/system/runtime/shared/index.d.ts +4 -3
  48. package/dist/_dts/blocks/src/system/runtime/shared/utils/bookingRoute.d.ts +20 -0
  49. package/dist/_dts/blocks/src/system/runtime/shared/utils/index.d.ts +2 -1
  50. package/dist/_dts/blocks/src/system/transforms/registry/layout.d.ts +1 -0
  51. package/dist/_dts/blocks/src/system/transforms/typed.d.ts +2 -1
  52. package/dist/_dts/blocks/src/system/types/link.d.ts +12 -0
  53. package/dist/_dts/content-editor/src/controller/EntryControllerProvider.d.ts +2 -0
  54. package/dist/_dts/content-editor/src/hooks/blockDraftLifecycle.d.ts +48 -0
  55. package/dist/_dts/content-editor/src/hooks/invalidBlockSelectors.d.ts +4 -0
  56. package/dist/_dts/content-editor/src/hooks/publishability.d.ts +47 -0
  57. package/dist/_dts/content-editor/src/index.d.ts +1 -0
  58. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +20 -0
  59. package/dist/_dts/editor-blocks/src/BlockEditor.d.ts +4 -1
  60. package/dist/_dts/editor-blocks/src/block-editor/BlockCard.d.ts +3 -1
  61. package/dist/_dts/editor-blocks/src/block-editor/BlockCardHeader.d.ts +3 -1
  62. package/dist/_dts/editor-blocks/src/block-editor/BlockSettingsModal.d.ts +5 -1
  63. package/dist/_dts/editor-blocks/src/block-editor/SortableBlockItem.d.ts +4 -1
  64. package/dist/_dts/editor-blocks/src/block-editor/actionControlMode.d.ts +2 -0
  65. package/dist/_dts/editor-blocks/src/block-editor/customBlockRegistry.d.ts +2 -0
  66. package/dist/_dts/editor-blocks/src/block-editor/sdkBlockMerger.d.ts +9 -37
  67. package/dist/_dts/editor-blocks/src/index.d.ts +3 -3
  68. package/dist/_dts/preview-next/src/client/index.d.ts +3 -0
  69. package/dist/_dts/preview-next/src/client/layout/PreviewLayoutContext.d.ts +5 -1
  70. package/dist/_dts/preview-next/src/client/preview/PreviewDesignCoordinatorContext.d.ts +20 -0
  71. package/dist/_dts/preview-next/src/client/preview/PreviewEditorSidebar.d.ts +7 -2
  72. package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +7 -2
  73. package/dist/_dts/preview-next/src/client/preview/PreviewShellLayout.d.ts +7 -2
  74. package/dist/_dts/preview-next/src/client/preview/PreviewThemedSiteDocument.d.ts +18 -0
  75. package/dist/_dts/preview-next/src/client/preview/SiteChromeCustomizeContext.d.ts +20 -3
  76. package/dist/_dts/preview-next/src/client/preview/StyleConfigurator.d.ts +6 -1
  77. package/dist/_dts/preview-next/src/client/preview/StyleConfigurator.state.d.ts +14 -4
  78. package/dist/_dts/preview-next/src/client/preview/previewDesignCoordinator.d.ts +73 -0
  79. package/dist/_dts/preview-next/src/client/preview/siteChromeLookRows.d.ts +12 -4
  80. package/dist/_dts/preview-next/src/client/preview/siteStylePreviewPlanner.d.ts +46 -0
  81. package/dist/_dts/preview-next/src/client/preview/styleConfiguratorSnapshot.d.ts +6 -1
  82. package/dist/_dts/preview-next/src/client/preview/themeOverlay.d.ts +21 -3
  83. package/dist/_dts/preview-next/src/client/preview/typographySelectionInput.d.ts +14 -0
  84. package/dist/_dts/preview-next/src/client/sdk/SdkPreviewModeRuntime.d.ts +1 -1
  85. package/dist/_dts/sdk/src/cli/commands/style.d.ts +8 -0
  86. package/dist/_dts/sdk/src/client/management/types.d.ts +6 -1
  87. package/dist/_dts/sdk/src/client/types.d.ts +9 -0
  88. package/dist/_dts/sdk/src/client.d.ts +1 -1
  89. package/dist/_dts/sdk/src/components.d.ts +2 -2
  90. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +35 -0
  91. package/dist/_dts/sdk/src/index.d.ts +2 -1
  92. package/dist/_dts/sdk/src/public-api/contracts.d.ts +5 -0
  93. package/dist/_dts/sdk/src/rendering/index.d.ts +2 -2
  94. package/dist/_dts/sdk/src/rendering/overrideResolution.d.ts +6 -0
  95. package/dist/_dts/sdk/src/rendering/overrides.d.ts +21 -1
  96. package/dist/_dts/sdk/src/rendering/server.d.ts +4 -3
  97. package/dist/_dts/sdk/src/types/block-kinds.d.ts +1 -1
  98. package/dist/_dts/sdk/src/version.d.ts +1 -1
  99. package/dist/_dts/site-commands/src/commands.d.ts +15 -0
  100. package/dist/_dts/theme-core/src/buttons/personalities/earth-pill.d.ts +16 -0
  101. package/dist/_dts/theme-core/src/buttons/personalities/index.d.ts +11 -0
  102. package/dist/_dts/theme-core/src/fonts/types.d.ts +1 -1
  103. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +45 -0
  104. package/dist/_dts/theme-core/src/palette/variants/index.d.ts +15 -0
  105. package/dist/_dts/theme-core/src/palette/variants/warm-neutral.d.ts +16 -0
  106. package/dist/_dts/theme-core/src/schema.d.ts +280 -0
  107. package/dist/_dts/theme-core/src/site-styles/appearancePresetCatalog.d.ts +81 -0
  108. package/dist/_dts/theme-core/src/site-styles/appearancePresetTypes.d.ts +6 -4
  109. package/dist/_dts/theme-core/src/site-styles/blockAppearancePresets.d.ts +81 -0
  110. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +118 -2
  111. package/dist/_dts/theme-core/src/site-styles/footerLooks.d.ts +13 -0
  112. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -2
  113. package/dist/_dts/theme-core/src/site-styles/headerLooks.d.ts +364 -5
  114. package/dist/_dts/theme-core/src/site-styles/headerOpeningSection.d.ts +6 -0
  115. package/dist/_dts/theme-core/src/site-styles/index.d.ts +2 -0
  116. package/dist/_dts/theme-core/src/site-styles/persistedIdParseResult.d.ts +9 -0
  117. package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +20 -4
  118. package/dist/_dts/theme-core/src/site-styles/types.d.ts +74 -2
  119. package/dist/_dts/theme-core/src/site-styles/typographyPresets.d.ts +189 -0
  120. package/dist/{chunk-Z7JLNTJR.mjs → chunk-6SMUFTXD.mjs} +2 -1
  121. package/dist/{chunk-ZSHFGFUG.mjs → chunk-ANKDYZ2E.mjs} +1 -1
  122. package/dist/{chunk-BGMNDF6T.mjs → chunk-EAWHBECW.mjs} +476 -153
  123. package/dist/{chunk-RWXQBRQZ.mjs → chunk-GSQX43EZ.mjs} +450 -63
  124. package/dist/{chunk-GFE74WRD.mjs → chunk-WLQBJN2Z.mjs} +3275 -435
  125. package/dist/cli/index.mjs +2023 -181
  126. package/dist/client/{accordion-T2UD2ZTH.mjs → accordion-RZMQSYLR.mjs} +1 -1
  127. package/dist/client/{bookingForm-GUP75FKN.mjs → bookingForm-NF4ASVNV.mjs} +3 -13
  128. package/dist/client/bookings.mjs +103 -11
  129. package/dist/client/{carousel-2NQA6V57.mjs → carousel-LDX47ZIK.mjs} +1 -1
  130. package/dist/client/{cart-5TFIAFDS.mjs → cart-RGXDS5F4.mjs} +4 -4
  131. package/dist/client/{checkout-TP4WAHZE.mjs → checkout-P7WWSNNY.mjs} +4 -4
  132. package/dist/client/{chunk-ZLMS67RR.mjs → chunk-E2TDNXCL.mjs} +1 -1
  133. package/dist/client/{chunk-NWOUL6JY.mjs → chunk-GMVTOKYG.mjs} +3 -3
  134. package/dist/client/{chunk-CY2ZTSE5.mjs → chunk-JNC7CGHX.mjs} +1 -1
  135. package/dist/client/{chunk-VUAOE3AG.mjs → chunk-L3DNHIJX.mjs} +4 -4
  136. package/dist/client/{chunk-FU2QRZMX.mjs → chunk-R56FQ3RR.mjs} +163 -24
  137. package/dist/client/{chunk-WJXXNDSE.mjs → chunk-UFSWADAO.mjs} +1 -1
  138. package/dist/client/client.mjs +4392 -2504
  139. package/dist/client/{courseRegistration-5DPU6PDY.mjs → courseRegistration-7F65K5ZR.mjs} +3 -3
  140. package/dist/client/{eventCalendar-F3GZGTEF.mjs → eventCalendar-GLSC5KTL.mjs} +5 -5
  141. package/dist/client/{eventCombined-BQOB6QMY.mjs → eventCombined-ZHK4WKUR.mjs} +5 -5
  142. package/dist/client/{eventDetails-PEM2EJWE.mjs → eventDetails-E3PJYBIN.mjs} +5 -5
  143. package/dist/client/{eventListing-YMIQPVME.mjs → eventListing-EMS3WC3E.mjs} +5 -5
  144. package/dist/client/{eventRegistration-4I6ZNDUF.mjs → eventRegistration-NZOTVNPI.mjs} +5 -5
  145. package/dist/client/{form-ZJVAYZ6G.mjs → form-ETFZFATW.mjs} +1 -1
  146. package/dist/client/{headerSection-TOT5MGTF.mjs → headerSection-4LBP7ULT.mjs} +2 -2
  147. package/dist/client/hooks.mjs +1918 -293
  148. package/dist/client/{locationMap-J66NZREU.mjs → locationMap-5M655YSJ.mjs} +1 -1
  149. package/dist/client/{newsletterForm-ET2XUMRO.mjs → newsletterForm-QOUYW44O.mjs} +1 -1
  150. package/dist/client/{productDetail-C2XIH7C5.mjs → productDetail-O4DOCWQG.mjs} +4 -4
  151. package/dist/client/{productList-T2AZLFTP.mjs → productList-A5RO5WIS.mjs} +4 -4
  152. package/dist/client/rendering/client.mjs +3328 -3177
  153. package/dist/client/rendering/islands.mjs +18 -18
  154. package/dist/client/rendering.mjs +5461 -3584
  155. package/dist/client/{shop-UZ6IUZXD.mjs → shop-L4MZH4I2.mjs} +4 -4
  156. package/dist/preview-next/chrome.css +1 -1
  157. package/dist/preview-next/client/runtime.mjs +4 -4
  158. package/dist/preview-next/editor-base.css +2 -148
  159. package/dist/preview-next/editor-chrome.css +179 -0
  160. package/dist/{sdk-runtime-JCUQM63Y.mjs → sdk-runtime-Q6MTR4KL.mjs} +5 -5
  161. package/dist/server/components.mjs +2983 -1093
  162. package/dist/server/config-validation.mjs +1921 -293
  163. package/dist/server/config.mjs +1921 -293
  164. package/dist/server/data.mjs +1918 -293
  165. package/dist/server/index.mjs +31 -2
  166. package/dist/server/next.mjs +2951 -1043
  167. package/dist/server/page-converter.mjs +1894 -281
  168. package/dist/server/prebuild.mjs +1 -1
  169. package/dist/server/rendering/server.mjs +2995 -1096
  170. package/dist/server/rendering.mjs +2988 -1098
  171. package/dist/server/routing.mjs +1963 -275
  172. package/dist/server/server.mjs +1932 -288
  173. package/dist/server/theme-bridge.mjs +158 -1
  174. package/dist/server/theme.mjs +158 -1
  175. package/package.json +1 -1
  176. package/dist/SdkPreviewModeRuntime-MTUGBTLQ.mjs +0 -8
  177. /package/dist/client/{chunk-2PHQZ666.mjs → chunk-JEZECJ3W.mjs} +0 -0
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { resolvePreviewDashboardSetupHandoff, buildPreviewDashboardSetupUrl, resolvePreviewSidebarChromeModel, resolvePreviewEditorGuidanceModel, canInspectSiteStyle, usePreviewThemeOverlay, resolveStyleConfiguratorInitialSnapshot, createStyleConfiguratorState, createOptimisticActionRunner, syncStyleConfiguratorState, isPreviewingChange, openRow, previewSiteStyle, applyResolvedSelections, previewButtonPersonality, planSiteChromeLookSelection, previewHeaderLook, previewFooterLook, previewPaletteVariant, setPaletteOverrides, toggleRow, cancelPreview, dismissWarnings, dismissNotices, markApplyFailed, markApplySaved, markApplySaving, canApplySiteStyle, buildSiteChromeLookModels, cycleSiteChromeLookId, cycleableSiteChromeOptions } from './chunk-RWXQBRQZ.mjs';
3
- import { KeyboardCode, getBlockDefinition, CSS, useSdkConfig, listBlockDescriptors, useSensor, PointerSensor, useSensors, deleteBlock, createEditorRemoveBlockCommand, asEditorBlockId, DndContext, closestCenter, Button, Plus, useDroppable, useDraggable, useCombinedRefs, isKeyboardEvent, transformSdkBlockToManifest, Card, cn, Collapsible, CollapsibleContent, listContentTypes, createBlock, createEditorReorderBlocksCommand, reorderBlocks, mutateBlockContent, useDndContext, useUniqueId, useIsomorphicLayoutEffect, EmptyState, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Search, getClientRect, resolveBlockTitle, CardHeader, GripVertical, ChevronRight, CardTitle, ArrowUp, ArrowDown, Copy, Trash2, DialogFooter, DialogClose, CardContent, transformSdkBlockToDefinition, createEditorInsertBlockCommand, LoaderCircle, ChevronDown, createManifestFormAdapter, getModalConfig, createStoreBackend, validateBlockManifestIssues, isNonEmptyValue, Settings, ScrollableDialogContent, FormRenderer, Tabs, TabsList, TabsTrigger, TabsContent, normalEditorCuratedPromotedFieldPaths, resolveVisiblePromotedFieldPathsForEditorBlock, Callout, CalloutTitle, unfinishedBlockCopy, CalloutDescription, recoveryConflictCopy, ManifestForm, asEditorBlockValues, Box, mergeManifestWithSdkOptions, augmentManifestWithStyleGroups, lucide_react_exports, isThemeV2NormalEditorRawStylePath, getFieldIntent, isHeaderField, isThemeV2NormalEditorRawStyleFieldName, unwrapResponse, useEntryController, useEditorUiStoreInstance, useUISelector, resolvePreviewSidebarTab, BackgroundGradientField, BackgroundColorField, X, omitRouteTitleFieldFromManifest, getRouteMetadataManifestForType, createManifestValidation, previewAssistantEnabled, useField, FieldFrame, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, isCtaIconName, Combobox, CTA_ICON_OPTIONS, getCtaIconComponent, useBackendWatch, useAsyncPickerOptions, shouldShowPickerSearch, normalizePickerSearchThreshold, Badge, resolvePickerSelectionLabel, useMediaEditorOptional, useRichTextEditor, RichTextEditorSurface, ThemeScope, useBlockContext, resolveMediaSlotContract, asRecord, parseAspectRatioValue, MediaEditor, PUBLIC_FILE_FIELD_KINDS, CollapsibleTrigger, ContentFieldsForm, getCuratedSiteStyle, compileSiteThemeFromSelections, paletteOverridesSchema, findButtonPersonality, findPaletteVariant, findHeaderLook, findFooterLook, headerLookCatalog, footerLookCatalog, isObjectRecord, EMPTY_DOC, getRichTextImagePositionFromTarget, insertOrReplaceRichTextImage, Pencil, foldSubroute, SUBROUTE_KINDS, Smartphone, Tablet, Monitor, Maximize2, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, asSiteId, buildThemeRuntime, PaletteProvider, MediaEditorProvider, curatedSiteStyles, ChevronLeft, requireEntriesForCuratedChoices, buttonPersonalities, paletteVariants, resolveImageUrlWithContext, Root2, List, Trigger, Content, Input, Check, Label, isInternalResolvedLinkValue, Separator, toRoutableLinkPayload, useMediaEditor, MediaPickerMini, MediaPickerDialog, Surface, ColorPicker, getDirectImageUrlForMediaAsset, isSvgMimeType } from './chunk-GFE74WRD.mjs';
2
+ import { resolvePreviewDashboardSetupHandoff, buildPreviewDashboardSetupUrl, resolvePreviewSidebarChromeModel, resolvePreviewEditorGuidanceModel, canInspectSiteStyle, usePreviewDesignCoordinator, resolveStyleConfiguratorInitialSnapshot, createStyleConfiguratorState, createOptimisticActionRunner, syncStyleConfiguratorState, isPreviewingChange, planSiteStylePreviewChange, openRow, previewSiteStyle, applyResolvedSelections, previewButtonPersonality, previewHeaderLayout, previewHeaderStyle, planSiteChromeLookSelection, previewFooterLook, previewPaletteVariant, previewTypographyPreset, setTypographyBodyFontOverride, setTypographyHeadingFontOverride, clearTypographyFontOverrides, setPaletteOverrides, toggleRow, cancelPreview, dismissWarnings, dismissNotices, markApplyFailed, markApplySaved, markApplySaving, canApplySiteStyle, buildSiteChromeLookModels, cycleSiteChromeLookId, cycleableSiteChromeOptions } from './chunk-GSQX43EZ.mjs';
3
+ import { KeyboardCode, getBlockDefinition, CSS, useSdkConfig, listBlockDescriptors, useSensor, PointerSensor, useSensors, deleteBlock, createEditorRemoveBlockCommand, asEditorBlockId, DndContext, closestCenter, Button, Plus, useDroppable, useDraggable, useCombinedRefs, isKeyboardEvent, createCustomBlockRegistry, Card, cn, Collapsible, CollapsibleContent, listContentTypes, mergeBlockDescriptorsWithRegistry, createBlock, createEditorReorderBlocksCommand, reorderBlocks, mutateBlockContent, useDndContext, useUniqueId, useIsomorphicLayoutEffect, EmptyState, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Search, getClientRect, resolveBlockTitle, CardHeader, GripVertical, ChevronRight, CardTitle, ArrowUp, ArrowDown, DropdownMenu, DropdownMenuTrigger, EllipsisVertical, DropdownMenuContent, DropdownMenuItem, Settings, DropdownMenuSeparator, Copy, Trash2, DialogFooter, DialogClose, CardContent, createEditorInsertBlockCommand, LoaderCircle, ChevronDown, normalEditorCuratedPromotedFieldPaths, resolveVisiblePromotedFieldPathsForEditorBlock, createStoreBackend, validateBlockManifestIssues, Callout, CalloutTitle, unfinishedBlockCopy, CalloutDescription, recoveryConflictCopy, ManifestForm, asEditorBlockValues, Box, createManifestFormAdapter, getModalConfig, isNonEmptyValue, ScrollableDialogContent, FormRenderer, Tabs, TabsList, TabsTrigger, TabsContent, mergeManifestWithSdkOptions, augmentManifestWithStyleGroups, lucide_react_exports, isThemeV2NormalEditorRawStylePath, getFieldIntent, isHeaderField, isThemeV2NormalEditorRawStyleFieldName, unwrapResponse, useEntryController, useEditorUiStoreInstance, useUISelector, resolvePreviewSidebarTab, BackgroundGradientField, BackgroundColorField, X, omitRouteTitleFieldFromManifest, getRouteMetadataManifestForType, createManifestValidation, previewAssistantEnabled, useField, FieldFrame, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, isCtaIconName, Combobox, CTA_ICON_OPTIONS, getCtaIconComponent, useBackendWatch, useAsyncPickerOptions, shouldShowPickerSearch, normalizePickerSearchThreshold, Badge, resolvePickerSelectionLabel, useMediaEditorOptional, useRichTextEditor, RichTextEditorSurface, ThemeScope, useBlockContext, resolveMediaSlotContract, asRecord, parseAspectRatioValue, MediaEditor, PUBLIC_FILE_FIELD_KINDS, CollapsibleTrigger, ContentFieldsForm, getCuratedSiteStyle, paletteOverridesSchema, findButtonPersonality, findPaletteVariant, findTypographyPreset, getHeaderLayout, getHeaderStyle, findFooterLook, footerLookCatalog, isObjectRecord, EMPTY_DOC, getRichTextImagePositionFromTarget, insertOrReplaceRichTextImage, Pencil, foldSubroute, SUBROUTE_KINDS, Smartphone, Tablet, Monitor, Maximize2, DropdownMenuLabel, asSiteId, buildThemeRuntime, PaletteProvider, MediaEditorProvider, curatedSiteStyles, ChevronLeft, requireEntriesForCuratedChoices, buttonPersonalities, typographyPresets, paletteVariants, resolveImageUrlWithContext, Root2, List, Trigger, Content, Input, Check, Label, isInternalResolvedLinkValue, evaluateTypographyFontSlot, Separator, toRoutableLinkPayload, useMediaEditor, MediaPickerMini, MediaPickerDialog, Surface, ColorPicker, getDirectImageUrlForMediaAsset, isSvgMimeType } from './chunk-WLQBJN2Z.mjs';
4
4
  import { usePreviewAuth, getDashboardUrl } from './chunk-VX7B5VAC.mjs';
5
5
  import './chunk-XGB3TDIC.mjs';
6
6
  import React2, { createContext, memo, useMemo, useState, useRef, useEffect, forwardRef, useCallback, useImperativeHandle, useContext, useSyncExternalStore, useLayoutEffect, useTransition, useId } from 'react';
@@ -2227,33 +2227,31 @@ function PreviewViewportControls({
2227
2227
  }) {
2228
2228
  const activePreset = VIEWPORT_PRESETS.find((p) => p.width === width);
2229
2229
  const ActiveIcon = activePreset?.icon ?? Monitor;
2230
+ const activeLabel = mode === "squeeze" ? "Auto fit" : activePreset?.label ?? `${width}px`;
2230
2231
  const showZoomControls = mode === "fixed" && zoom !== void 0 && onZoomChange !== void 0;
2231
2232
  return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
2232
2233
  /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2233
2234
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "gap-2", children: [
2234
2235
  /* @__PURE__ */ jsx(ActiveIcon, { className: "h-4 w-4" }),
2235
- /* @__PURE__ */ jsxs("span", { className: "text-xs", children: [
2236
- mode === "squeeze" && "Responsive",
2237
- mode === "fixed" && (width === "full" ? "Full Width" : `${width}px`)
2238
- ] })
2236
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: activeLabel })
2239
2237
  ] }) }),
2240
2238
  /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
2241
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Display Mode" }),
2239
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Preview size" }),
2242
2240
  /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
2243
2241
  /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onModeChange("squeeze"), className: cn(mode === "squeeze" && "bg-accent"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2244
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Responsive" }),
2245
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Content adapts to available width" })
2242
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Auto fit" }),
2243
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Matches the space beside the editor" })
2246
2244
  ] }) }),
2247
2245
  /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onModeChange("fixed"), className: cn(mode === "fixed" && "bg-accent"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2248
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Fixed Width" }),
2249
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Set specific viewport width" })
2246
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Choose size" }),
2247
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Preview mobile, tablet, or desktop widths" })
2250
2248
  ] }) })
2251
2249
  ] })
2252
2250
  ] }),
2253
2251
  mode === "fixed" && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2254
2252
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: activePreset?.label ?? `${width}px` }) }),
2255
2253
  /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
2256
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Viewport Width" }),
2254
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Screen size" }),
2257
2255
  /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
2258
2256
  VIEWPORT_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
2259
2257
  DropdownMenuItem,
@@ -2964,45 +2962,37 @@ function createBlockFormLogger(scope) {
2964
2962
  }
2965
2963
 
2966
2964
  // ../editor-blocks/src/block-editor/sdkBlockMerger.ts
2967
- var logger = createBlockFormLogger("sdkBlockMerger");
2968
- function transformToBlockDescriptor(sdkBlock) {
2969
- return {
2970
- name: sdkBlock.id,
2971
- title: sdkBlock.title,
2972
- description: sdkBlock.description,
2973
- category: sdkBlock.category,
2974
- tags: sdkBlock.tags,
2975
- icon: sdkBlock.icon,
2976
- definition: transformSdkBlockToDefinition(sdkBlock),
2977
- source: "sdk"
2978
- };
2979
- }
2965
+ var logger = createBlockFormLogger("customBlockRegistry");
2980
2966
  function mergeBlockDescriptors(systemBlocks, sdkConfig, dbCustomBlocks) {
2981
- const existingNames = new Set(systemBlocks.map((b) => b.name));
2982
- const result = [...systemBlocks];
2983
- if (sdkConfig?.customBlocks?.length) {
2984
- for (const sdkBlock of sdkConfig.customBlocks) {
2985
- if (existingNames.has(sdkBlock.id)) {
2986
- logger.warn(`Skipping SDK block "${sdkBlock.id}" - conflicts with system block`);
2987
- continue;
2988
- }
2989
- result.push(transformToBlockDescriptor(sdkBlock));
2990
- existingNames.add(sdkBlock.id);
2991
- }
2992
- }
2993
- return result;
2967
+ const registry = createCustomBlockRegistry({
2968
+ sdkConfig,
2969
+ customBlocks: normalizeCustomBlockEntries(dbCustomBlocks),
2970
+ onConflict: (message) => logger.warn(message)
2971
+ });
2972
+ return mergeBlockDescriptorsWithRegistry(
2973
+ systemBlocks,
2974
+ registry,
2975
+ (message) => logger.warn(message)
2976
+ );
2994
2977
  }
2995
2978
  function getBlockManifestFromSdkConfig(blockKind, sdkConfig, dbCustomBlocks) {
2996
- if (!blockKind.startsWith("custom.")) {
2997
- return void 0;
2998
- }
2999
- if (sdkConfig?.customBlocks) {
3000
- const sdkBlock = sdkConfig.customBlocks.find((b) => b.id === blockKind);
3001
- if (sdkBlock) {
3002
- return transformSdkBlockToManifest(sdkBlock);
2979
+ return createCustomBlockRegistry({
2980
+ sdkConfig,
2981
+ customBlocks: normalizeCustomBlockEntries(dbCustomBlocks),
2982
+ onConflict: (message) => logger.warn(message)
2983
+ }).findManifest(blockKind)?.manifest;
2984
+ }
2985
+ function normalizeCustomBlockEntries(customBlocks) {
2986
+ if (!customBlocks) return [];
2987
+ return customBlocks.map((entry) => {
2988
+ if ("manifest" in entry && "source" in entry) {
2989
+ return entry;
3003
2990
  }
3004
- }
3005
- return void 0;
2991
+ return {
2992
+ manifest: entry,
2993
+ source: { kind: "managed-database" }
2994
+ };
2995
+ });
3006
2996
  }
3007
2997
  var AddBlockDivider = ({ onOpen, position }) => /* @__PURE__ */ jsxs("div", { className: "relative flex w-full items-center justify-center h-8 opacity-0 transition-opacity hover:opacity-100", children: [
3008
2998
  /* @__PURE__ */ jsx("div", { className: "w-full border-b" }),
@@ -3094,18 +3084,20 @@ function getCategoryLabel(category) {
3094
3084
  // ../editor-blocks/src/block-editor/blockDiscovery.ts
3095
3085
  var CORE_ESSENTIAL_BLOCKS = [
3096
3086
  "block.hero",
3097
- "block.bodyText",
3098
- "block.mediaText",
3099
- "block.imageGallery",
3100
- "block.ctaFull",
3087
+ "block.intro",
3088
+ "block.body-text",
3089
+ "block.media-text",
3090
+ "block.feature-cards",
3091
+ "block.image-gallery",
3092
+ "block.cta-full",
3101
3093
  "block.testimonials",
3102
3094
  "block.faq",
3103
3095
  "block.form"
3104
3096
  ];
3105
3097
  var CONTEXTUAL_ESSENTIAL_GROUPS = [
3106
- ["block.blogListing"],
3107
- ["block.eventListing", "block.eventCalendar"],
3108
- ["block.appointmentBooking"],
3098
+ ["block.blog-listing"],
3099
+ ["block.event-listing", "block.event-calendar"],
3100
+ ["block.appointment-booking"],
3109
3101
  ["block.shop"]
3110
3102
  ];
3111
3103
  function filterBlocksByContentType(blocks, enabledContentTypes) {
@@ -3196,21 +3188,26 @@ function getRecentBlocks(blocks, recentBlockNames) {
3196
3188
  }
3197
3189
  function suggestBlocks(blocks, existingBlockNames) {
3198
3190
  if (existingBlockNames.length === 0) {
3199
- return blocks.filter((b) => ["block.hero", "block.body-text", "block.cta-full"].includes(b.name)).slice(0, 3);
3191
+ return blocks.filter((b) => ["block.hero", "block.intro", "block.cta-full"].includes(b.name)).slice(0, 3);
3200
3192
  }
3201
3193
  const hasHero = existingBlockNames.includes("block.hero");
3194
+ const hasIntro = existingBlockNames.includes("block.intro");
3202
3195
  const hasBody = existingBlockNames.includes("block.body-text");
3196
+ const hasFeatureCards = existingBlockNames.includes("block.feature-cards");
3203
3197
  const hasCta = existingBlockNames.includes("block.cta-full");
3204
3198
  const hasFaq = existingBlockNames.includes("block.faq");
3205
3199
  const hasTestimonials = existingBlockNames.includes("block.testimonials");
3206
3200
  const suggestions = [];
3207
- if (hasHero && !hasBody) {
3208
- suggestions.push("block.body-text");
3201
+ if (hasHero && !hasIntro && !hasBody) {
3202
+ suggestions.push("block.intro", "block.body-text");
3209
3203
  }
3210
- if ((hasHero || hasBody) && !hasCta) {
3204
+ if ((hasHero || hasIntro || hasBody) && !hasCta) {
3211
3205
  suggestions.push("block.cta-full");
3212
3206
  }
3213
- if ((hasBody || hasCta) && !hasTestimonials && !hasFaq) {
3207
+ if ((hasIntro || hasBody) && !hasFeatureCards) {
3208
+ suggestions.push("block.feature-cards");
3209
+ }
3210
+ if ((hasIntro || hasBody || hasFeatureCards || hasCta) && !hasTestimonials && !hasFaq) {
3214
3211
  suggestions.push("block.testimonials", "block.faq");
3215
3212
  }
3216
3213
  const blockMap = new Map(blocks.map((b) => [b.name, b]));
@@ -4000,6 +3997,9 @@ function useBlockReorder({
4000
3997
  return { reorderId, handleMove, onDragEnd };
4001
3998
  }
4002
3999
 
4000
+ // ../editor-blocks/src/block-editor/actionControlMode.ts
4001
+ var DEFAULT_BLOCK_ACTION_CONTROL_MODE = "inline";
4002
+
4003
4003
  // ../editor-blocks/src/block-editor/reorderControlMode.ts
4004
4004
  var DEFAULT_REORDER_CONTROL_MODE = "full";
4005
4005
  function shouldShowMoveButtons(mode) {
@@ -4433,10 +4433,23 @@ function BlockSettingsModal({
4433
4433
  uiStore,
4434
4434
  widgetOverrides,
4435
4435
  theme,
4436
- hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false
4436
+ hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false,
4437
+ showSettingsTrigger = true,
4438
+ open: controlledOpen,
4439
+ onOpenChange,
4440
+ onAvailabilityChange
4437
4441
  }) {
4438
- const [open, setOpen] = useState(false);
4439
- const [directSectionStylesOpen, setDirectSectionStylesOpen] = useState(false);
4442
+ const [internalOpen, setInternalOpen] = useState(false);
4443
+ const open = controlledOpen ?? internalOpen;
4444
+ const setOpen = useCallback(
4445
+ (nextOpen) => {
4446
+ if (controlledOpen === void 0) {
4447
+ setInternalOpen(nextOpen);
4448
+ }
4449
+ onOpenChange?.(nextOpen);
4450
+ },
4451
+ [controlledOpen, onOpenChange]
4452
+ );
4440
4453
  const blockId = typeof block.id === "string" ? asEditorBlockId(block.id) : null;
4441
4454
  const hasDraftSnapshot = useStoreSelector(
4442
4455
  uiStore,
@@ -4537,12 +4550,16 @@ function BlockSettingsModal({
4537
4550
  () => settingsSourceFields.some((field) => isNonEmptyValue(valueSource[field.id])),
4538
4551
  [settingsSourceFields, valueSource]
4539
4552
  );
4540
- if (!blockId || visibleTabs.length === 0 || !backend) {
4553
+ const settingsAvailable = Boolean(blockId && visibleTabs.length > 0 && backend);
4554
+ useEffect(() => {
4555
+ onAvailabilityChange?.(settingsAvailable);
4556
+ }, [onAvailabilityChange, settingsAvailable]);
4557
+ if (!settingsAvailable || !blockId || !backend) {
4541
4558
  return null;
4542
4559
  }
4543
4560
  const defaultTab = visibleTabs[0]?.id ?? "display";
4544
4561
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4545
- /* @__PURE__ */ jsx(
4562
+ showSettingsTrigger ? /* @__PURE__ */ jsx(
4546
4563
  Button,
4547
4564
  {
4548
4565
  type: "button",
@@ -4550,10 +4567,6 @@ function BlockSettingsModal({
4550
4567
  size: "icon",
4551
4568
  onClick: (event) => {
4552
4569
  event.stopPropagation();
4553
- if (directSectionStylesModal) {
4554
- setDirectSectionStylesOpen(true);
4555
- return;
4556
- }
4557
4570
  setOpen(true);
4558
4571
  },
4559
4572
  disabled: !hasDraftSnapshot,
@@ -4561,13 +4574,13 @@ function BlockSettingsModal({
4561
4574
  title: "Block settings",
4562
4575
  children: /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" })
4563
4576
  }
4564
- ),
4565
- directSectionStylesModal ? /* @__PURE__ */ jsx(Dialog, { open: directSectionStylesOpen, onOpenChange: setDirectSectionStylesOpen, children: /* @__PURE__ */ jsx(
4577
+ ) : null,
4578
+ directSectionStylesModal ? /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(
4566
4579
  ScrollableDialogContent,
4567
4580
  {
4568
4581
  title: directSectionStylesTitle,
4569
4582
  description: directSectionStylesDescription,
4570
- footer: /* @__PURE__ */ jsx(Button, { type: "button", onClick: () => setDirectSectionStylesOpen(false), children: "Done" }),
4583
+ footer: /* @__PURE__ */ jsx(Button, { type: "button", onClick: () => setOpen(false), children: "Done" }),
4571
4584
  children: /* @__PURE__ */ jsx(
4572
4585
  FormRenderer,
4573
4586
  {
@@ -4579,7 +4592,7 @@ function BlockSettingsModal({
4579
4592
  )
4580
4593
  }
4581
4594
  ) }) : null,
4582
- /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(
4595
+ !directSectionStylesModal ? /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(
4583
4596
  ScrollableDialogContent,
4584
4597
  {
4585
4598
  title: "Block settings",
@@ -4597,7 +4610,7 @@ function BlockSettingsModal({
4597
4610
  ) }, tab.id))
4598
4611
  ] })
4599
4612
  }
4600
- ) })
4613
+ ) }) : null
4601
4614
  ] });
4602
4615
  }
4603
4616
  function useDraftContent(uiStore, blockId) {
@@ -4635,10 +4648,14 @@ function BlockCardHeader({
4635
4648
  widgetOverrides,
4636
4649
  theme,
4637
4650
  hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false,
4638
- reorderControlMode = "full"
4651
+ reorderControlMode = "full",
4652
+ actionControlMode = DEFAULT_BLOCK_ACTION_CONTROL_MODE
4639
4653
  }) {
4640
4654
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
4655
+ const [settingsDialogOpen, setSettingsDialogOpen] = useState(false);
4656
+ const [settingsAvailable, setSettingsAvailable] = useState(true);
4641
4657
  const showMoveButtons = shouldShowMoveButtons(reorderControlMode);
4658
+ const showActionsMenu = actionControlMode === "menu";
4642
4659
  const baselineContent = useDraftContent(
4643
4660
  uiStore,
4644
4661
  typeof block.id === "string" ? asEditorBlockId(block.id) : null
@@ -4649,6 +4666,22 @@ function BlockCardHeader({
4649
4666
  contentToUse,
4650
4667
  block.kind
4651
4668
  );
4669
+ const hasMenuActions = settingsAvailable || Boolean(onDuplicate) || Boolean(onDelete);
4670
+ const settingsModal = /* @__PURE__ */ jsx(
4671
+ BlockSettingsModal,
4672
+ {
4673
+ block,
4674
+ manifest,
4675
+ uiStore,
4676
+ widgetOverrides,
4677
+ theme,
4678
+ hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2,
4679
+ showSettingsTrigger: !showActionsMenu,
4680
+ open: showActionsMenu ? settingsDialogOpen : void 0,
4681
+ onOpenChange: showActionsMenu ? setSettingsDialogOpen : void 0,
4682
+ onAvailabilityChange: showActionsMenu ? setSettingsAvailable : void 0
4683
+ }
4684
+ );
4652
4685
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4653
4686
  /* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-wrap items-start gap-1 px-2 py-2", "data-testid": "block-card-header", children: [
4654
4687
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
@@ -4724,49 +4757,91 @@ function BlockCardHeader({
4724
4757
  }
4725
4758
  )
4726
4759
  ] }) : null,
4727
- /* @__PURE__ */ jsx(
4728
- BlockSettingsModal,
4729
- {
4730
- block,
4731
- manifest,
4732
- uiStore,
4733
- widgetOverrides,
4734
- theme,
4735
- hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2
4736
- }
4737
- ),
4738
- onDuplicate ? /* @__PURE__ */ jsx(
4739
- Button,
4740
- {
4741
- type: "button",
4742
- variant: "ghost",
4743
- size: "icon",
4744
- onClick: () => {
4745
- if (block.id) {
4746
- onDuplicate(block.id);
4760
+ showActionsMenu ? /* @__PURE__ */ jsxs(Fragment, { children: [
4761
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
4762
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
4763
+ Button,
4764
+ {
4765
+ type: "button",
4766
+ variant: "ghost",
4767
+ size: "icon",
4768
+ disabled: !hasMenuActions,
4769
+ "aria-label": `More actions for ${displayTitle}`,
4770
+ title: "More actions",
4771
+ children: /* @__PURE__ */ jsx(EllipsisVertical, { className: "h-4 w-4" })
4747
4772
  }
4748
- },
4749
- disabled: isMoving || !block.id,
4750
- "aria-label": "Duplicate block",
4751
- title: "Duplicate block",
4752
- children: /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" })
4753
- }
4754
- ) : null,
4755
- onDelete && /* @__PURE__ */ jsx(
4756
- Button,
4757
- {
4758
- type: "button",
4759
- variant: "ghost",
4760
- size: "icon",
4761
- onClick: () => {
4762
- setDeleteDialogOpen(true);
4763
- },
4764
- disabled: !block.id,
4765
- "aria-label": "Delete block",
4766
- title: "Delete block",
4767
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
4768
- }
4769
- )
4773
+ ) }),
4774
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-44", children: [
4775
+ settingsAvailable ? /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => setSettingsDialogOpen(true), children: [
4776
+ /* @__PURE__ */ jsx(Settings, { className: "mr-2 h-4 w-4" }),
4777
+ "Settings"
4778
+ ] }) : null,
4779
+ settingsAvailable && (onDuplicate || onDelete) ? /* @__PURE__ */ jsx(DropdownMenuSeparator, {}) : null,
4780
+ onDuplicate ? /* @__PURE__ */ jsxs(
4781
+ DropdownMenuItem,
4782
+ {
4783
+ disabled: isMoving || !block.id,
4784
+ onSelect: () => {
4785
+ if (block.id) {
4786
+ onDuplicate(block.id);
4787
+ }
4788
+ },
4789
+ children: [
4790
+ /* @__PURE__ */ jsx(Copy, { className: "mr-2 h-4 w-4" }),
4791
+ "Duplicate"
4792
+ ]
4793
+ }
4794
+ ) : null,
4795
+ onDelete ? /* @__PURE__ */ jsxs(
4796
+ DropdownMenuItem,
4797
+ {
4798
+ disabled: !block.id,
4799
+ className: "text-destructive focus:text-destructive",
4800
+ onSelect: () => setDeleteDialogOpen(true),
4801
+ children: [
4802
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }),
4803
+ "Delete"
4804
+ ]
4805
+ }
4806
+ ) : null
4807
+ ] })
4808
+ ] }),
4809
+ settingsModal
4810
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4811
+ settingsModal,
4812
+ onDuplicate ? /* @__PURE__ */ jsx(
4813
+ Button,
4814
+ {
4815
+ type: "button",
4816
+ variant: "ghost",
4817
+ size: "icon",
4818
+ onClick: () => {
4819
+ if (block.id) {
4820
+ onDuplicate(block.id);
4821
+ }
4822
+ },
4823
+ disabled: isMoving || !block.id,
4824
+ "aria-label": "Duplicate block",
4825
+ title: "Duplicate block",
4826
+ children: /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" })
4827
+ }
4828
+ ) : null,
4829
+ onDelete && /* @__PURE__ */ jsx(
4830
+ Button,
4831
+ {
4832
+ type: "button",
4833
+ variant: "ghost",
4834
+ size: "icon",
4835
+ onClick: () => {
4836
+ setDeleteDialogOpen(true);
4837
+ },
4838
+ disabled: !block.id,
4839
+ "aria-label": "Delete block",
4840
+ title: "Delete block",
4841
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
4842
+ }
4843
+ )
4844
+ ] })
4770
4845
  ]
4771
4846
  }
4772
4847
  )
@@ -4825,7 +4900,8 @@ function BlockCard({
4825
4900
  uiStore,
4826
4901
  hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false,
4827
4902
  pageDesignBlock = null,
4828
- reorderControlMode = DEFAULT_REORDER_CONTROL_MODE
4903
+ reorderControlMode = DEFAULT_REORDER_CONTROL_MODE,
4904
+ actionControlMode = DEFAULT_BLOCK_ACTION_CONTROL_MODE
4829
4905
  }) {
4830
4906
  const contentId = block.id ? `builder-block-content-${block.id}` : void 0;
4831
4907
  return /* @__PURE__ */ jsx(
@@ -4855,7 +4931,8 @@ function BlockCard({
4855
4931
  widgetOverrides,
4856
4932
  theme,
4857
4933
  hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2,
4858
- reorderControlMode
4934
+ reorderControlMode,
4935
+ actionControlMode
4859
4936
  }
4860
4937
  ),
4861
4938
  /* @__PURE__ */ jsx(CollapsibleContent, { id: contentId, children: /* @__PURE__ */ jsx(
@@ -4900,19 +4977,28 @@ var SortableBlockItem = memo(function SortableBlockItem2({
4900
4977
  widgetOverrides,
4901
4978
  uiStore,
4902
4979
  sdkConfig,
4980
+ customBlocks = [],
4903
4981
  hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false,
4904
4982
  pageDesignBlock = null,
4905
- reorderControlMode = DEFAULT_REORDER_CONTROL_MODE
4983
+ reorderControlMode = DEFAULT_REORDER_CONTROL_MODE,
4984
+ actionControlMode = DEFAULT_BLOCK_ACTION_CONTROL_MODE
4906
4985
  }) {
4907
- const sortable = useSortable({ id: block.id, disabled: !isDraggable });
4986
+ const sortable = useSortable({
4987
+ id: block.id,
4988
+ disabled: !isDraggable
4989
+ });
4908
4990
  const manifest = useMemo(() => {
4909
- const sdkManifest = getBlockManifestFromSdkConfig(block.kind, sdkConfig ?? null);
4991
+ const sdkManifest = getBlockManifestFromSdkConfig(
4992
+ block.kind,
4993
+ sdkConfig ?? null,
4994
+ customBlocks
4995
+ );
4910
4996
  if (sdkManifest) {
4911
4997
  return sdkManifest;
4912
4998
  }
4913
4999
  const blockDefinition = getBlockDefinition(block.kind);
4914
5000
  return blockDefinition?.manifest;
4915
- }, [block.kind, sdkConfig]);
5001
+ }, [block.kind, customBlocks, sdkConfig]);
4916
5002
  const [mounted, setMounted] = useState(false);
4917
5003
  const blockRef = useRef(null);
4918
5004
  useEffect(() => setMounted(true), []);
@@ -4977,364 +5063,446 @@ var SortableBlockItem = memo(function SortableBlockItem2({
4977
5063
  uiStore,
4978
5064
  hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2,
4979
5065
  pageDesignBlock,
4980
- reorderControlMode
5066
+ reorderControlMode,
5067
+ actionControlMode
4981
5068
  }
4982
5069
  )
4983
5070
  }
4984
5071
  );
4985
5072
  });
4986
- var BlockEditor = forwardRef(function BlockEditor2({
4987
- siteId,
4988
- pageId,
4989
- blocks,
4990
- recoveryConflictBlockIds = [],
4991
- recoveryConflictReviewRequestId = null,
4992
- apiClient,
4993
- uiStore,
4994
- enableReordering = true,
4995
- enableCreation = true,
4996
- onBlockSave,
4997
- onBlocksChange,
4998
- theme,
4999
- widgetOverrides,
5000
- headerLeftSlot,
5001
- hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false,
5002
- pageDesignEditorModel = null,
5003
- enabledContentTypes = [],
5004
- sdkConfig = null,
5005
- reorderControlMode = DEFAULT_REORDER_CONTROL_MODE
5006
- }, ref) {
5007
- const [blockList, setBlockList] = useState(() => normalizeBlockListInitial(blocks));
5008
- const [errorMessage, setErrorMessage] = useState(null);
5009
- const [highlightedBlockId, setHighlightedBlockId] = useState(null);
5010
- const blockRefs = useRef(/* @__PURE__ */ new Map());
5011
- const highlightTimerRef = useRef(null);
5012
- const { blockFieldExtensions } = useSdkConfig();
5013
- const activeBlockId = useStoreSelector(uiStore, (state) => state.activeBlockId);
5014
- const setActiveBlockId = useCallback(
5015
- (id) => {
5016
- uiStore.getState().setActiveBlockId(id);
5017
- },
5018
- [uiStore]
5019
- );
5020
- const [fetchedContentTypes, setFetchedContentTypes] = useState(enabledContentTypes);
5021
- const [fetchingContentTypes, setFetchingContentTypes] = useState(false);
5022
- const allowReordering = enableReordering;
5023
- const allowCreation = enableCreation;
5024
- const conflictedBlockIdSet = useMemo(
5025
- () => new Set(recoveryConflictBlockIds),
5026
- [recoveryConflictBlockIds]
5027
- );
5028
- const blockIds = useMemo(
5029
- () => blockList.map((block) => block.id).filter((blockId) => typeof blockId === "string"),
5030
- [blockList]
5031
- );
5032
- const pageDesignBlockById = useMemo(
5033
- () => new Map(
5034
- (pageDesignEditorModel?.blocks ?? []).map((block) => [String(block.blockId), block])
5035
- ),
5036
- [pageDesignEditorModel]
5037
- );
5038
- const { isCollapsed, setCollapsed } = useBlockExpandState({
5073
+ var BlockEditor = forwardRef(
5074
+ function BlockEditor2({
5075
+ siteId,
5039
5076
  pageId,
5040
- blockIds
5041
- });
5042
- useEffect(() => {
5043
- setBlockList(normalizeBlockListInitial(blocks));
5044
- }, [blocks]);
5045
- useEffect(() => {
5046
- const unsubscribe = uiStore.subscribe(() => {
5047
- const structureList = uiStore.getState().structure.getList();
5048
- setBlockList((currentList) => {
5049
- const nextList = structureList.map((block) => ({ ...block }));
5050
- const hasChanges = currentList.length !== nextList.length || currentList.some((block, index) => {
5051
- const nextBlock = nextList[index];
5052
- return !nextBlock || block.id !== nextBlock.id || block.orderIndex !== nextBlock.orderIndex || block.kind !== nextBlock.kind;
5077
+ blocks,
5078
+ recoveryConflictBlockIds = [],
5079
+ recoveryConflictReviewRequestId = null,
5080
+ apiClient,
5081
+ uiStore,
5082
+ enableReordering = true,
5083
+ enableCreation = true,
5084
+ onBlockSave,
5085
+ onBlocksChange,
5086
+ theme,
5087
+ widgetOverrides,
5088
+ headerLeftSlot,
5089
+ hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false,
5090
+ pageDesignEditorModel = null,
5091
+ enabledContentTypes = [],
5092
+ sdkConfig = null,
5093
+ customBlocks = [],
5094
+ reorderControlMode = DEFAULT_REORDER_CONTROL_MODE,
5095
+ actionControlMode = DEFAULT_BLOCK_ACTION_CONTROL_MODE
5096
+ }, ref) {
5097
+ const [blockList, setBlockList] = useState(
5098
+ () => normalizeBlockListInitial(blocks)
5099
+ );
5100
+ const [errorMessage, setErrorMessage] = useState(null);
5101
+ const [highlightedBlockId, setHighlightedBlockId] = useState(
5102
+ null
5103
+ );
5104
+ const blockRefs = useRef(/* @__PURE__ */ new Map());
5105
+ const highlightTimerRef = useRef(null);
5106
+ const { blockFieldExtensions } = useSdkConfig();
5107
+ const activeBlockId = useStoreSelector(
5108
+ uiStore,
5109
+ (state) => state.activeBlockId
5110
+ );
5111
+ const setActiveBlockId = useCallback(
5112
+ (id) => {
5113
+ uiStore.getState().setActiveBlockId(id);
5114
+ },
5115
+ [uiStore]
5116
+ );
5117
+ const [fetchedContentTypes, setFetchedContentTypes] = useState(enabledContentTypes);
5118
+ const [fetchingContentTypes, setFetchingContentTypes] = useState(false);
5119
+ const allowReordering = enableReordering;
5120
+ const allowCreation = enableCreation;
5121
+ const conflictedBlockIdSet = useMemo(
5122
+ () => new Set(recoveryConflictBlockIds),
5123
+ [recoveryConflictBlockIds]
5124
+ );
5125
+ const blockIds = useMemo(
5126
+ () => blockList.map((block) => block.id).filter((blockId) => typeof blockId === "string"),
5127
+ [blockList]
5128
+ );
5129
+ const pageDesignBlockById = useMemo(
5130
+ () => new Map(
5131
+ (pageDesignEditorModel?.blocks ?? []).map((block) => [
5132
+ String(block.blockId),
5133
+ block
5134
+ ])
5135
+ ),
5136
+ [pageDesignEditorModel]
5137
+ );
5138
+ const { isCollapsed, setCollapsed } = useBlockExpandState({
5139
+ pageId,
5140
+ blockIds
5141
+ });
5142
+ useEffect(() => {
5143
+ setBlockList(normalizeBlockListInitial(blocks));
5144
+ }, [blocks]);
5145
+ useEffect(() => {
5146
+ const unsubscribe = uiStore.subscribe(() => {
5147
+ const structureList = uiStore.getState().structure.getList();
5148
+ setBlockList((currentList) => {
5149
+ const nextList = structureList.map((block) => ({ ...block }));
5150
+ const hasChanges = currentList.length !== nextList.length || currentList.some((block, index) => {
5151
+ const nextBlock = nextList[index];
5152
+ return !nextBlock || block.id !== nextBlock.id || block.orderIndex !== nextBlock.orderIndex || block.kind !== nextBlock.kind;
5153
+ });
5154
+ return hasChanges ? nextList : currentList;
5053
5155
  });
5054
- return hasChanges ? nextList : currentList;
5055
5156
  });
5157
+ return unsubscribe;
5158
+ }, [uiStore]);
5159
+ useEffect(() => {
5160
+ let cancelled = false;
5161
+ async function fetchContentTypesFromApi() {
5162
+ if (fetchingContentTypes) return;
5163
+ setFetchingContentTypes(true);
5164
+ try {
5165
+ const result = await listContentTypes(apiClient, { siteId });
5166
+ if (!cancelled) {
5167
+ const keys = (result.contentTypes || []).map(
5168
+ (ct) => typeof ct === "string" ? ct : ct.key
5169
+ );
5170
+ setFetchedContentTypes(keys);
5171
+ }
5172
+ } catch (error) {
5173
+ console.error("[block-editor] Failed to fetch content types:", error);
5174
+ if (!cancelled) {
5175
+ setFetchedContentTypes(enabledContentTypes);
5176
+ }
5177
+ } finally {
5178
+ if (!cancelled) {
5179
+ setFetchingContentTypes(false);
5180
+ }
5181
+ }
5182
+ }
5183
+ fetchContentTypesFromApi();
5184
+ return () => {
5185
+ cancelled = true;
5186
+ };
5187
+ }, [siteId, apiClient, enabledContentTypes]);
5188
+ useBlockBaselineSync({
5189
+ enabled: true,
5190
+ blockList,
5191
+ blockFieldExtensions,
5192
+ uiStore
5056
5193
  });
5057
- return unsubscribe;
5058
- }, [uiStore]);
5059
- useEffect(() => {
5060
- let cancelled = false;
5061
- async function fetchContentTypesFromApi() {
5062
- if (fetchingContentTypes) return;
5063
- setFetchingContentTypes(true);
5064
- try {
5065
- const result = await listContentTypes(apiClient, { siteId });
5066
- if (!cancelled) {
5067
- const keys = (result.contentTypes || []).map(
5068
- (ct) => typeof ct === "string" ? ct : ct.key
5069
- );
5070
- setFetchedContentTypes(keys);
5194
+ const focusRequest = useStoreSelector(
5195
+ uiStore,
5196
+ (state) => state.focusRequest
5197
+ );
5198
+ const triggerFocus = useCallback(
5199
+ (target) => {
5200
+ const { blockId, fieldPath, itemIndex } = target;
5201
+ uiStore.getState().requestFocus({ blockId, fieldPath, itemIndex });
5202
+ },
5203
+ [uiStore]
5204
+ );
5205
+ const handleFocusHandled = useCallback(
5206
+ (requestId) => {
5207
+ const current = uiStore.getState().focusRequest;
5208
+ if (current && current.requestId === requestId) {
5209
+ uiStore.getState().clearFocus();
5071
5210
  }
5072
- } catch (error) {
5073
- console.error("[block-editor] Failed to fetch content types:", error);
5074
- if (!cancelled) {
5075
- setFetchedContentTypes(enabledContentTypes);
5211
+ },
5212
+ [uiStore]
5213
+ );
5214
+ useImperativeHandle(
5215
+ ref,
5216
+ () => ({
5217
+ focusField: triggerFocus
5218
+ }),
5219
+ [triggerFocus]
5220
+ );
5221
+ const availableBlocks = useMemo(
5222
+ () => mergeBlockDescriptors(listBlockDescriptors(), sdkConfig, customBlocks),
5223
+ [customBlocks, sdkConfig]
5224
+ );
5225
+ const pointerSensor = useSensor(PointerSensor, {
5226
+ activationConstraint: { distance: 5 }
5227
+ });
5228
+ const sensors = useSensors(pointerSensor);
5229
+ const activeSensors = allowReordering ? sensors : [];
5230
+ const {
5231
+ pickerOpen,
5232
+ creatingBlockName,
5233
+ openPicker,
5234
+ closePicker,
5235
+ handleCreateBlock
5236
+ } = useBlockCreation({
5237
+ allowCreation,
5238
+ apiClient,
5239
+ siteId,
5240
+ pageId,
5241
+ blockList,
5242
+ setBlockList,
5243
+ onBlocksChange,
5244
+ triggerFocus,
5245
+ uiStore
5246
+ });
5247
+ const { reorderId, handleMove, onDragEnd } = useBlockReorder({
5248
+ allowReordering,
5249
+ apiClient,
5250
+ siteId,
5251
+ pageId,
5252
+ blockList,
5253
+ setBlockList,
5254
+ onBlocksChange,
5255
+ setErrorMessage,
5256
+ uiStore
5257
+ });
5258
+ const { duplicatingBlockId, duplicateBlock } = useBlockDuplication({
5259
+ allowCreation,
5260
+ apiClient,
5261
+ siteId,
5262
+ pageId,
5263
+ blockList,
5264
+ setBlockList,
5265
+ onBlocksChange,
5266
+ triggerFocus,
5267
+ setErrorMessage,
5268
+ uiStore
5269
+ });
5270
+ const handleDelete = useCallback(
5271
+ async (blockId) => {
5272
+ try {
5273
+ setErrorMessage(null);
5274
+ const removedBlock = blockList.find((block) => block.id === blockId);
5275
+ if (!removedBlock) {
5276
+ return;
5277
+ }
5278
+ await deleteBlock(apiClient, { siteId, pageId, blockId });
5279
+ const updatedList = blockList.filter((b) => b.id !== blockId);
5280
+ setBlockList(updatedList);
5281
+ if (uiStore) {
5282
+ uiStore.getState().history.applyCommand?.({
5283
+ ...createEditorRemoveBlockCommand(asEditorBlockId(blockId))
5284
+ });
5285
+ }
5286
+ onBlocksChange?.(updatedList);
5287
+ } catch (error) {
5288
+ console.error("[BlockEditor] Delete failed:", error);
5289
+ const message = error instanceof Error ? error.message : "Failed to delete block";
5290
+ setErrorMessage(message);
5076
5291
  }
5077
- } finally {
5078
- if (!cancelled) {
5079
- setFetchingContentTypes(false);
5292
+ },
5293
+ [
5294
+ apiClient,
5295
+ siteId,
5296
+ pageId,
5297
+ blockList,
5298
+ setBlockList,
5299
+ onBlocksChange,
5300
+ uiStore
5301
+ ]
5302
+ );
5303
+ useEffect(() => {
5304
+ return () => {
5305
+ if (highlightTimerRef.current !== null) {
5306
+ window.clearTimeout(highlightTimerRef.current);
5080
5307
  }
5081
- }
5082
- }
5083
- fetchContentTypesFromApi();
5084
- return () => {
5085
- cancelled = true;
5086
- };
5087
- }, [siteId, apiClient, enabledContentTypes]);
5088
- useBlockBaselineSync({ enabled: true, blockList, blockFieldExtensions, uiStore });
5089
- const focusRequest = useStoreSelector(uiStore, (state) => state.focusRequest);
5090
- const triggerFocus = useCallback(
5091
- (target) => {
5092
- const { blockId, fieldPath, itemIndex } = target;
5093
- uiStore.getState().requestFocus({ blockId, fieldPath, itemIndex });
5094
- },
5095
- [uiStore]
5096
- );
5097
- const handleFocusHandled = useCallback(
5098
- (requestId) => {
5099
- const current = uiStore.getState().focusRequest;
5100
- if (current && current.requestId === requestId) {
5101
- uiStore.getState().clearFocus();
5102
- }
5103
- },
5104
- [uiStore]
5105
- );
5106
- useImperativeHandle(
5107
- ref,
5108
- () => ({
5109
- focusField: triggerFocus
5110
- }),
5111
- [triggerFocus]
5112
- );
5113
- const availableBlocks = useMemo(
5114
- () => mergeBlockDescriptors(listBlockDescriptors(), sdkConfig),
5115
- [sdkConfig]
5116
- );
5117
- const pointerSensor = useSensor(PointerSensor, { activationConstraint: { distance: 5 } });
5118
- const sensors = useSensors(pointerSensor);
5119
- const activeSensors = allowReordering ? sensors : [];
5120
- const { pickerOpen, creatingBlockName, openPicker, closePicker, handleCreateBlock } = useBlockCreation({
5121
- allowCreation,
5122
- apiClient,
5123
- siteId,
5124
- pageId,
5125
- blockList,
5126
- setBlockList,
5127
- onBlocksChange,
5128
- triggerFocus,
5129
- uiStore
5130
- });
5131
- const { reorderId, handleMove, onDragEnd } = useBlockReorder({
5132
- allowReordering,
5133
- apiClient,
5134
- siteId,
5135
- pageId,
5136
- blockList,
5137
- setBlockList,
5138
- onBlocksChange,
5139
- setErrorMessage,
5140
- uiStore
5141
- });
5142
- const { duplicatingBlockId, duplicateBlock } = useBlockDuplication({
5143
- allowCreation,
5144
- apiClient,
5145
- siteId,
5146
- pageId,
5147
- blockList,
5148
- setBlockList,
5149
- onBlocksChange,
5150
- triggerFocus,
5151
- setErrorMessage,
5152
- uiStore
5153
- });
5154
- const handleDelete = useCallback(async (blockId) => {
5155
- try {
5156
- setErrorMessage(null);
5157
- const removedBlock = blockList.find((block) => block.id === blockId);
5158
- if (!removedBlock) {
5308
+ };
5309
+ }, []);
5310
+ useEffect(() => {
5311
+ if (!focusRequest?.blockId) {
5159
5312
  return;
5160
5313
  }
5161
- await deleteBlock(apiClient, { siteId, pageId, blockId });
5162
- const updatedList = blockList.filter((b) => b.id !== blockId);
5163
- setBlockList(updatedList);
5164
- if (uiStore) {
5165
- uiStore.getState().history.applyCommand?.({
5166
- ...createEditorRemoveBlockCommand(asEditorBlockId(blockId))
5314
+ const focusedBlockId = String(focusRequest.blockId);
5315
+ setCollapsed(focusedBlockId, false);
5316
+ uiStore.getState().setActiveBlockId(focusRequest.blockId);
5317
+ setHighlightedBlockId(focusedBlockId);
5318
+ window.setTimeout(() => {
5319
+ blockRefs.current.get(focusedBlockId)?.scrollIntoView({
5320
+ behavior: "smooth",
5321
+ block: "center"
5167
5322
  });
5168
- }
5169
- onBlocksChange?.(updatedList);
5170
- } catch (error) {
5171
- console.error("[BlockEditor] Delete failed:", error);
5172
- const message = error instanceof Error ? error.message : "Failed to delete block";
5173
- setErrorMessage(message);
5174
- }
5175
- }, [apiClient, siteId, pageId, blockList, setBlockList, onBlocksChange, uiStore]);
5176
- useEffect(() => {
5177
- return () => {
5323
+ }, 100);
5178
5324
  if (highlightTimerRef.current !== null) {
5179
5325
  window.clearTimeout(highlightTimerRef.current);
5180
5326
  }
5181
- };
5182
- }, []);
5183
- useEffect(() => {
5184
- if (!focusRequest?.blockId) {
5185
- return;
5186
- }
5187
- const focusedBlockId = String(focusRequest.blockId);
5188
- setCollapsed(focusedBlockId, false);
5189
- uiStore.getState().setActiveBlockId(focusRequest.blockId);
5190
- setHighlightedBlockId(focusedBlockId);
5191
- window.setTimeout(() => {
5192
- blockRefs.current.get(focusedBlockId)?.scrollIntoView({
5193
- behavior: "smooth",
5194
- block: "center"
5195
- });
5196
- }, 100);
5197
- if (highlightTimerRef.current !== null) {
5198
- window.clearTimeout(highlightTimerRef.current);
5199
- }
5200
- highlightTimerRef.current = window.setTimeout(() => {
5201
- setHighlightedBlockId((current) => current === focusedBlockId ? null : current);
5202
- highlightTimerRef.current = null;
5203
- }, 1500);
5204
- }, [focusRequest?.requestId, focusRequest?.blockId, setCollapsed, uiStore]);
5205
- useEffect(() => {
5206
- if (recoveryConflictReviewRequestId === null || recoveryConflictBlockIds.length === 0) {
5207
- return;
5208
- }
5209
- for (const blockId of recoveryConflictBlockIds) {
5210
- setCollapsed(blockId, false);
5211
- }
5212
- const firstRecoveryBlockId = recoveryConflictBlockIds[0];
5213
- if (!firstRecoveryBlockId) {
5214
- return;
5215
- }
5216
- uiStore.getState().requestFocus({ blockId: asEditorBlockId(firstRecoveryBlockId) });
5217
- }, [recoveryConflictBlockIds, recoveryConflictReviewRequestId, setCollapsed, uiStore]);
5218
- useEffect(() => {
5219
- if (!activeBlockId) {
5220
- return;
5221
- }
5222
- const stillExists = blockList.some(
5223
- (block) => typeof block.id === "string" && asEditorBlockId(block.id) === activeBlockId
5224
- );
5225
- if (!stillExists) {
5226
- uiStore.getState().setActiveBlockId(null);
5227
- }
5228
- }, [activeBlockId, blockList, uiStore]);
5229
- const hasBlocks = blockList.length > 0;
5230
- const contextValue = useMemo(
5231
- () => ({
5232
- blockList,
5233
- setBlockList,
5234
- reorderId,
5235
- focusRequest,
5236
- triggerFocus,
5237
- handleMove,
5238
- onDragEnd,
5239
- activeBlockId,
5240
- setActiveBlockId,
5241
- theme
5242
- }),
5243
- [blockList, reorderId, focusRequest, triggerFocus, handleMove, onDragEnd, activeBlockId, setActiveBlockId, theme]
5244
- );
5245
- return /* @__PURE__ */ jsx(BlockListProvider, { value: contextValue, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 touch-pan-y", children: [
5246
- /* @__PURE__ */ jsx(HeaderActions, { headerLeftSlot }),
5247
- errorMessage ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive bg-destructive/10 px-4 py-2 text-sm text-destructive", children: errorMessage }) : null,
5248
- hasBlocks ? /* @__PURE__ */ jsx(
5249
- DndContext,
5250
- {
5251
- sensors: activeSensors,
5252
- collisionDetection: closestCenter,
5327
+ highlightTimerRef.current = window.setTimeout(() => {
5328
+ setHighlightedBlockId(
5329
+ (current) => current === focusedBlockId ? null : current
5330
+ );
5331
+ highlightTimerRef.current = null;
5332
+ }, 1500);
5333
+ }, [focusRequest?.requestId, focusRequest?.blockId, setCollapsed, uiStore]);
5334
+ useEffect(() => {
5335
+ if (recoveryConflictReviewRequestId === null || recoveryConflictBlockIds.length === 0) {
5336
+ return;
5337
+ }
5338
+ for (const blockId of recoveryConflictBlockIds) {
5339
+ setCollapsed(blockId, false);
5340
+ }
5341
+ const firstRecoveryBlockId = recoveryConflictBlockIds[0];
5342
+ if (!firstRecoveryBlockId) {
5343
+ return;
5344
+ }
5345
+ uiStore.getState().requestFocus({ blockId: asEditorBlockId(firstRecoveryBlockId) });
5346
+ }, [
5347
+ recoveryConflictBlockIds,
5348
+ recoveryConflictReviewRequestId,
5349
+ setCollapsed,
5350
+ uiStore
5351
+ ]);
5352
+ useEffect(() => {
5353
+ if (!activeBlockId) {
5354
+ return;
5355
+ }
5356
+ const stillExists = blockList.some(
5357
+ (block) => typeof block.id === "string" && asEditorBlockId(block.id) === activeBlockId
5358
+ );
5359
+ if (!stillExists) {
5360
+ uiStore.getState().setActiveBlockId(null);
5361
+ }
5362
+ }, [activeBlockId, blockList, uiStore]);
5363
+ const hasBlocks = blockList.length > 0;
5364
+ const contextValue = useMemo(
5365
+ () => ({
5366
+ blockList,
5367
+ setBlockList,
5368
+ reorderId,
5369
+ focusRequest,
5370
+ triggerFocus,
5371
+ handleMove,
5253
5372
  onDragEnd,
5254
- children: /* @__PURE__ */ jsx(SortableContext, { items: blockList.map((b) => b.id), strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
5255
- allowCreation ? /* @__PURE__ */ jsx(AddBlockDivider, { onOpen: openPicker, position: 0 }) : null,
5256
- blockList.map((block, index) => {
5257
- const focusSignal = focusRequest && block.id && focusRequest.blockId === asEditorBlockId(block.id) ? focusRequest : null;
5258
- const collapsed = isCollapsed(block.id);
5259
- return /* @__PURE__ */ jsxs(
5260
- "div",
5261
- {
5262
- className: "group",
5263
- ref: (node) => {
5264
- if (!block.id) return;
5265
- if (node) {
5266
- blockRefs.current.set(block.id, node);
5267
- } else {
5268
- blockRefs.current.delete(block.id);
5269
- }
5270
- },
5271
- children: [
5272
- /* @__PURE__ */ jsx(
5273
- SortableBlockItem,
5373
+ activeBlockId,
5374
+ setActiveBlockId,
5375
+ theme
5376
+ }),
5377
+ [
5378
+ blockList,
5379
+ reorderId,
5380
+ focusRequest,
5381
+ triggerFocus,
5382
+ handleMove,
5383
+ onDragEnd,
5384
+ activeBlockId,
5385
+ setActiveBlockId,
5386
+ theme
5387
+ ]
5388
+ );
5389
+ return /* @__PURE__ */ jsx(BlockListProvider, { value: contextValue, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 touch-pan-y", children: [
5390
+ /* @__PURE__ */ jsx(HeaderActions, { headerLeftSlot }),
5391
+ errorMessage ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive bg-destructive/10 px-4 py-2 text-sm text-destructive", children: errorMessage }) : null,
5392
+ hasBlocks ? /* @__PURE__ */ jsx(
5393
+ DndContext,
5394
+ {
5395
+ sensors: activeSensors,
5396
+ collisionDetection: closestCenter,
5397
+ onDragEnd,
5398
+ children: /* @__PURE__ */ jsx(
5399
+ SortableContext,
5400
+ {
5401
+ items: blockList.map((b) => b.id),
5402
+ strategy: verticalListSortingStrategy,
5403
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
5404
+ allowCreation ? /* @__PURE__ */ jsx(AddBlockDivider, { onOpen: openPicker, position: 0 }) : null,
5405
+ blockList.map((block, index) => {
5406
+ const focusSignal = focusRequest && block.id && focusRequest.blockId === asEditorBlockId(block.id) ? focusRequest : null;
5407
+ const collapsed = isCollapsed(block.id);
5408
+ return /* @__PURE__ */ jsxs(
5409
+ "div",
5274
5410
  {
5275
- siteId,
5276
- block,
5277
- onMove: handleMove,
5278
- onDuplicate: duplicateBlock,
5279
- onDelete: handleDelete,
5280
- isFirst: index === 0,
5281
- isLast: index === blockList.length - 1,
5282
- isMoving: reorderId === block.id || duplicatingBlockId === block.id,
5283
- isDraggable: allowReordering,
5284
- collapsed,
5285
- onCollapsedChange: (nextCollapsed) => {
5411
+ className: "group",
5412
+ ref: (node) => {
5286
5413
  if (!block.id) return;
5287
- setCollapsed(block.id, nextCollapsed);
5288
- if (nextCollapsed) {
5289
- if (activeBlockId === asEditorBlockId(block.id)) {
5290
- uiStore.getState().setActiveBlockId(null);
5291
- }
5292
- return;
5414
+ if (node) {
5415
+ blockRefs.current.set(block.id, node);
5416
+ } else {
5417
+ blockRefs.current.delete(block.id);
5293
5418
  }
5294
- uiStore.getState().setActiveBlockId(asEditorBlockId(block.id));
5295
5419
  },
5296
- highlighted: highlightedBlockId === block.id,
5297
- hasRecoveryConflict: typeof block.id === "string" && conflictedBlockIdSet.has(block.id),
5298
- focusSignal,
5299
- onFocusHandled: handleFocusHandled,
5300
- onBlockSave,
5301
- theme,
5302
- widgetOverrides,
5303
- uiStore,
5304
- sdkConfig,
5305
- hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2,
5306
- pageDesignBlock: block.id ? pageDesignBlockById.get(block.id) ?? null : null,
5307
- reorderControlMode
5308
- }
5309
- ),
5310
- allowCreation && index < blockList.length - 1 ? /* @__PURE__ */ jsx(AddBlockDivider, { onOpen: openPicker, position: index + 1 }) : null
5311
- ]
5312
- },
5313
- block.id ?? index
5314
- );
5315
- })
5316
- ] }) })
5317
- }
5318
- ) : hasBlocks ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-4 py-6 text-center text-sm text-muted-foreground", children: "Loading block editor..." }) : allowCreation ? /* @__PURE__ */ jsx(EmptyState2, { onAdd: () => openPicker(0) }) : /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-4 py-6 text-center text-sm text-muted-foreground", children: "No blocks available for this page." }),
5319
- allowCreation && hasBlocks ? /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "secondary", onClick: () => openPicker(blockList.length), children: [
5320
- /* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
5321
- "Add block"
5322
- ] }) }) : null,
5323
- allowCreation ? /* @__PURE__ */ jsx(
5324
- EnhancedBlockPickerDialog,
5325
- {
5326
- open: pickerOpen,
5327
- onOpenChange: (open) => open ? openPicker(blockList.length) : closePicker(),
5328
- creatingBlockName,
5329
- descriptors: availableBlocks,
5330
- siteId,
5331
- existingBlockNames: blockList.map((b) => b.kind).filter(Boolean),
5332
- enabledContentTypes: fetchedContentTypes,
5333
- onSelect: (descriptor) => void handleCreateBlock(descriptor)
5334
- }
5335
- ) : null
5336
- ] }) });
5337
- });
5420
+ children: [
5421
+ /* @__PURE__ */ jsx(
5422
+ SortableBlockItem,
5423
+ {
5424
+ siteId,
5425
+ block,
5426
+ onMove: handleMove,
5427
+ onDuplicate: duplicateBlock,
5428
+ onDelete: handleDelete,
5429
+ isFirst: index === 0,
5430
+ isLast: index === blockList.length - 1,
5431
+ isMoving: reorderId === block.id || duplicatingBlockId === block.id,
5432
+ isDraggable: allowReordering,
5433
+ collapsed,
5434
+ onCollapsedChange: (nextCollapsed) => {
5435
+ if (!block.id) return;
5436
+ setCollapsed(block.id, nextCollapsed);
5437
+ if (nextCollapsed) {
5438
+ if (activeBlockId === asEditorBlockId(block.id)) {
5439
+ uiStore.getState().setActiveBlockId(null);
5440
+ }
5441
+ return;
5442
+ }
5443
+ uiStore.getState().setActiveBlockId(asEditorBlockId(block.id));
5444
+ },
5445
+ highlighted: highlightedBlockId === block.id,
5446
+ hasRecoveryConflict: typeof block.id === "string" && conflictedBlockIdSet.has(block.id),
5447
+ focusSignal,
5448
+ onFocusHandled: handleFocusHandled,
5449
+ onBlockSave,
5450
+ theme,
5451
+ widgetOverrides,
5452
+ uiStore,
5453
+ sdkConfig,
5454
+ customBlocks,
5455
+ hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2,
5456
+ pageDesignBlock: block.id ? pageDesignBlockById.get(block.id) ?? null : null,
5457
+ reorderControlMode,
5458
+ actionControlMode
5459
+ }
5460
+ ),
5461
+ allowCreation && index < blockList.length - 1 ? /* @__PURE__ */ jsx(
5462
+ AddBlockDivider,
5463
+ {
5464
+ onOpen: openPicker,
5465
+ position: index + 1
5466
+ }
5467
+ ) : null
5468
+ ]
5469
+ },
5470
+ block.id ?? index
5471
+ );
5472
+ })
5473
+ ] })
5474
+ }
5475
+ )
5476
+ }
5477
+ ) : hasBlocks ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-4 py-6 text-center text-sm text-muted-foreground", children: "Loading block editor..." }) : allowCreation ? /* @__PURE__ */ jsx(EmptyState2, { onAdd: () => openPicker(0) }) : /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-4 py-6 text-center text-sm text-muted-foreground", children: "No blocks available for this page." }),
5478
+ allowCreation && hasBlocks ? /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs(
5479
+ Button,
5480
+ {
5481
+ type: "button",
5482
+ variant: "secondary",
5483
+ onClick: () => openPicker(blockList.length),
5484
+ children: [
5485
+ /* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
5486
+ "Add block"
5487
+ ]
5488
+ }
5489
+ ) }) : null,
5490
+ allowCreation ? /* @__PURE__ */ jsx(
5491
+ EnhancedBlockPickerDialog,
5492
+ {
5493
+ open: pickerOpen,
5494
+ onOpenChange: (open) => open ? openPicker(blockList.length) : closePicker(),
5495
+ creatingBlockName,
5496
+ descriptors: availableBlocks,
5497
+ siteId,
5498
+ existingBlockNames: blockList.map((b) => b.kind).filter(Boolean),
5499
+ enabledContentTypes: fetchedContentTypes,
5500
+ onSelect: (descriptor) => void handleCreateBlock(descriptor)
5501
+ }
5502
+ ) : null
5503
+ ] }) });
5504
+ }
5505
+ );
5338
5506
  function PageBlockEditorPanel({
5339
5507
  siteId,
5340
5508
  pageId,
@@ -5357,7 +5525,7 @@ function PageBlockEditorPanel({
5357
5525
  onViewportWidthChange,
5358
5526
  onZoomChange
5359
5527
  }) {
5360
- const [designOpen, setDesignOpen] = useState(true);
5528
+ const [designOpen, setDesignOpen] = useState(false);
5361
5529
  const [contentOpen, setContentOpen] = useState(true);
5362
5530
  const [detailsOpen, setDetailsOpen] = useState(false);
5363
5531
  return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", children: [
@@ -5406,7 +5574,8 @@ function PageBlockEditorPanel({
5406
5574
  onBlocksChange,
5407
5575
  widgetOverrides: previewWidgetOverrides,
5408
5576
  sdkConfig: sdkConfig ?? null,
5409
- reorderControlMode: "drag-handle-only"
5577
+ reorderControlMode: "drag-handle-only",
5578
+ actionControlMode: "menu"
5410
5579
  },
5411
5580
  pageId
5412
5581
  )
@@ -5660,12 +5829,19 @@ function buildApplySiteStyleSelectionBody(input) {
5660
5829
  buttonPersonalityId: input.previewed.buttonPersonalityId,
5661
5830
  paletteVariantId: input.previewed.paletteVariantId,
5662
5831
  paletteOverrides: input.previewed.paletteOverrides,
5832
+ typographyPresetId: input.previewed.typographyPresetId,
5833
+ typographyBodyFontOverride: input.previewed.typographyBodyFontOverride,
5834
+ typographyHeadingFontOverride: input.previewed.typographyHeadingFontOverride,
5663
5835
  headerLookId: input.previewed.headerLookId,
5836
+ headerLayoutId: input.previewed.headerLayoutId,
5837
+ headerStyleId: input.previewed.headerStyleId,
5664
5838
  footerLookId: input.previewed.footerLookId,
5665
5839
  baseSelectionVersion: input.base.selectionVersion,
5666
5840
  selectionId: input.selectionId ?? null
5667
5841
  };
5668
5842
  }
5843
+ var MAX_FONT_RESULTS_WHEN_SEARCHING = 12;
5844
+ var MAX_FONT_RESULTS_WHEN_BROWSING = 8;
5669
5845
  function StyleConfigurator({
5670
5846
  siteId,
5671
5847
  apiClient,
@@ -5680,7 +5856,14 @@ function StyleConfigurator({
5680
5856
  siteStyleAccess
5681
5857
  }) {
5682
5858
  const router = useRouter();
5683
- const { setOverrideTheme } = usePreviewThemeOverlay();
5859
+ const previewDesign = usePreviewDesignCoordinator();
5860
+ const {
5861
+ applyFailed,
5862
+ applyStarted,
5863
+ applySucceeded,
5864
+ clearPreview,
5865
+ previewTheme
5866
+ } = previewDesign;
5684
5867
  const [isRefreshPending, startRefreshTransition] = useTransition();
5685
5868
  const baseSnapshot = useMemo(
5686
5869
  () => resolveStyleConfiguratorInitialSnapshot({
@@ -5700,50 +5883,47 @@ function StyleConfigurator({
5700
5883
  );
5701
5884
  const hasLoadedState = state !== null;
5702
5885
  const stateRef = useRef(state);
5703
- const setOverrideThemeRef = useRef(setOverrideTheme);
5886
+ const clearPreviewRef = useRef(clearPreview);
5704
5887
  const runnerRef = useRef(createOptimisticActionRunner());
5705
5888
  const appliedExpandedRowRequestIdRef = useRef(null);
5706
5889
  useEffect(() => {
5707
5890
  stateRef.current = state;
5708
5891
  }, [state]);
5709
5892
  useEffect(() => {
5710
- setOverrideThemeRef.current = setOverrideTheme;
5711
- }, [setOverrideTheme]);
5893
+ clearPreviewRef.current = clearPreview;
5894
+ }, [clearPreview]);
5712
5895
  useEffect(() => {
5713
5896
  if (!baseSnapshot) return;
5714
5897
  setState((current) => current ? syncStyleConfiguratorState(current, baseSnapshot) : createStyleConfiguratorState(baseSnapshot));
5715
5898
  }, [baseSnapshot]);
5716
5899
  useEffect(() => {
5717
5900
  if (!state || !baseSnapshot) {
5718
- setOverrideTheme(null);
5901
+ clearPreview("style-configurator");
5719
5902
  return;
5720
5903
  }
5721
5904
  if (!isPreviewingChange(state)) {
5722
- setOverrideTheme(null);
5905
+ clearPreview("style-configurator");
5723
5906
  return;
5724
5907
  }
5725
5908
  const siteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
5726
5909
  if (!siteStyle) {
5727
- setOverrideTheme(null);
5910
+ clearPreview("style-configurator");
5728
5911
  return;
5729
5912
  }
5730
- const result = compileSiteThemeFromSelections({
5913
+ const result = planSiteStylePreviewChange({
5731
5914
  siteStyle,
5732
- buttonPersonalityId: state.previewed.buttonPersonalityId,
5733
- paletteVariantId: state.previewed.paletteVariantId,
5734
- paletteOverrides: state.previewed.paletteOverrides,
5735
- headerLookId: state.previewed.headerLookId,
5736
- footerLookId: state.previewed.footerLookId,
5915
+ selection: state.previewed,
5916
+ baseTheme: theme,
5737
5917
  headerSelectionContext,
5738
5918
  footerSelectionContext
5739
5919
  });
5740
- setOverrideTheme(result.theme);
5741
- }, [state, baseSnapshot, setOverrideTheme, headerSelectionContext, footerSelectionContext]);
5920
+ previewTheme("style-configurator", result.themeOverlay, "site-style");
5921
+ }, [state, baseSnapshot, clearPreview, previewTheme, headerSelectionContext, footerSelectionContext, theme]);
5742
5922
  useEffect(() => {
5743
5923
  const runner = runnerRef.current;
5744
5924
  return () => {
5745
5925
  runner.invalidate();
5746
- setOverrideThemeRef.current(null);
5926
+ clearPreviewRef.current("style-configurator");
5747
5927
  };
5748
5928
  }, []);
5749
5929
  useEffect(() => {
@@ -5752,7 +5932,7 @@ function StyleConfigurator({
5752
5932
  setState((current) => {
5753
5933
  if (!current) return current;
5754
5934
  appliedExpandedRowRequestIdRef.current = expandedRowRequest.id;
5755
- return openRow(current, expandedRowRequest.row);
5935
+ return openRow(current, expandedRowRequest.row === "header" ? "header-layout" : expandedRowRequest.row);
5756
5936
  });
5757
5937
  }, [expandedRowRequest, hasLoadedState]);
5758
5938
  const handlePreviewSiteStyle = useCallback((siteStyleId) => {
@@ -5761,13 +5941,10 @@ function StyleConfigurator({
5761
5941
  const next = previewSiteStyle(current, siteStyleId);
5762
5942
  const siteStyle = getCuratedSiteStyle(siteStyleId);
5763
5943
  if (!siteStyle) return next;
5764
- const result = compileSiteThemeFromSelections({
5944
+ const result = planSiteStylePreviewChange({
5765
5945
  siteStyle,
5766
- buttonPersonalityId: next.previewed.buttonPersonalityId,
5767
- paletteVariantId: next.previewed.paletteVariantId,
5768
- paletteOverrides: next.previewed.paletteOverrides,
5769
- headerLookId: next.previewed.headerLookId,
5770
- footerLookId: next.previewed.footerLookId,
5946
+ selection: next.previewed,
5947
+ baseTheme: theme,
5771
5948
  headerSelectionContext,
5772
5949
  footerSelectionContext
5773
5950
  });
@@ -5776,14 +5953,17 @@ function StyleConfigurator({
5776
5953
  warnings: result.warnings
5777
5954
  });
5778
5955
  });
5779
- }, [footerSelectionContext, headerSelectionContext]);
5956
+ }, [footerSelectionContext, headerSelectionContext, theme]);
5780
5957
  const handlePreviewPersonality = useCallback((id) => {
5781
5958
  setState((current) => current ? previewButtonPersonality(current, id) : current);
5782
5959
  }, []);
5783
- const handlePreviewHeaderLook = useCallback((id) => {
5784
- const plan = planSiteChromeLookSelection({ surface: "header", lookId: id });
5785
- if (plan.kind !== "header") return;
5786
- setState((current) => current ? previewHeaderLook(current, plan.next.headerLookId) : current);
5960
+ const handlePreviewHeaderLayout = useCallback((id) => {
5961
+ if (!id) return;
5962
+ setState((current) => current ? previewHeaderLayout(current, id) : current);
5963
+ }, []);
5964
+ const handlePreviewHeaderStyle = useCallback((id) => {
5965
+ if (!id) return;
5966
+ setState((current) => current ? previewHeaderStyle(current, id) : current);
5787
5967
  }, []);
5788
5968
  const handlePreviewFooterLook = useCallback((id) => {
5789
5969
  const plan = planSiteChromeLookSelection({ surface: "footer", lookId: id });
@@ -5793,6 +5973,18 @@ function StyleConfigurator({
5793
5973
  const handlePreviewPalette = useCallback((id) => {
5794
5974
  setState((current) => current ? previewPaletteVariant(current, id) : current);
5795
5975
  }, []);
5976
+ const handlePreviewTypographyPreset = useCallback((id) => {
5977
+ setState((current) => current ? previewTypographyPreset(current, id) : current);
5978
+ }, []);
5979
+ const handleBodyFontOverride = useCallback((fontFamily) => {
5980
+ setState((current) => current ? setTypographyBodyFontOverride(current, fontFamily) : current);
5981
+ }, []);
5982
+ const handleHeadingFontOverride = useCallback((fontFamily) => {
5983
+ setState((current) => current ? setTypographyHeadingFontOverride(current, fontFamily) : current);
5984
+ }, []);
5985
+ const handleClearTypographyOverrides = useCallback(() => {
5986
+ setState((current) => current ? clearTypographyFontOverrides(current) : current);
5987
+ }, []);
5796
5988
  const handleTokenOverride = useCallback((role, hex) => {
5797
5989
  setState((current) => {
5798
5990
  if (!current) return current;
@@ -5823,13 +6015,10 @@ function StyleConfigurator({
5823
6015
  const previewed = live.previewed;
5824
6016
  const siteStyle = getCuratedSiteStyle(previewed.siteStyleId);
5825
6017
  if (!siteStyle) return;
5826
- const compiled = compileSiteThemeFromSelections({
6018
+ const compiled = planSiteStylePreviewChange({
5827
6019
  siteStyle,
5828
- buttonPersonalityId: previewed.buttonPersonalityId,
5829
- paletteVariantId: previewed.paletteVariantId,
5830
- paletteOverrides: previewed.paletteOverrides,
5831
- headerLookId: previewed.headerLookId,
5832
- footerLookId: previewed.footerLookId,
6020
+ selection: previewed,
6021
+ baseTheme: theme,
5833
6022
  headerSelectionContext,
5834
6023
  footerSelectionContext
5835
6024
  });
@@ -5838,12 +6027,19 @@ function StyleConfigurator({
5838
6027
  previewed: {
5839
6028
  ...previewed,
5840
6029
  buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
5841
- paletteVariantId: compiled.resolvedSelections.paletteVariantId
6030
+ paletteVariantId: compiled.resolvedSelections.paletteVariantId,
6031
+ headerLayoutId: compiled.resolvedSelections.headerLayoutId,
6032
+ headerStyleId: compiled.resolvedSelections.headerStyleId,
6033
+ headerLookId: compiled.resolvedSelections.headerLookId,
6034
+ typographyPresetId: compiled.resolvedSelections.typographyPresetId,
6035
+ typographyBodyFontOverride: compiled.resolvedSelections.typographyBodyFontOverride,
6036
+ typographyHeadingFontOverride: compiled.resolvedSelections.typographyHeadingFontOverride
5842
6037
  },
5843
6038
  selectionId
5844
6039
  });
5845
6040
  await runnerRef.current.run({
5846
6041
  optimistic: () => {
6042
+ applyStarted("style-configurator");
5847
6043
  setState((current) => current ? markApplySaving(current) : current);
5848
6044
  },
5849
6045
  network: async () => {
@@ -5863,15 +6059,22 @@ function StyleConfigurator({
5863
6059
  buttonPersonalityId: saved.buttonPersonalityId,
5864
6060
  paletteVariantId: saved.paletteVariantId,
5865
6061
  paletteOverrides: saved.paletteOverrides ?? null,
6062
+ typographyPresetId: saved.typographyPresetId,
6063
+ typographyBodyFontOverride: saved.typographyBodyFontOverride,
6064
+ typographyHeadingFontOverride: saved.typographyHeadingFontOverride,
5866
6065
  headerLookId: saved.headerLookId,
6066
+ headerLayoutId: saved.headerLayoutId,
6067
+ headerStyleId: saved.headerStyleId,
5867
6068
  footerLookId: saved.footerLookId
5868
6069
  }) : current
5869
6070
  );
6071
+ applySucceeded("style-configurator");
5870
6072
  startRefreshTransition(() => {
5871
6073
  router.refresh();
5872
6074
  });
5873
6075
  },
5874
6076
  onError: (error) => {
6077
+ applyFailed("style-configurator");
5875
6078
  const applyError = classifyStyleApplyError(error);
5876
6079
  if (applyError.kind === "stale") {
5877
6080
  startRefreshTransition(() => {
@@ -5886,10 +6089,14 @@ function StyleConfigurator({
5886
6089
  apiClient,
5887
6090
  footerSelectionContext,
5888
6091
  headerSelectionContext,
6092
+ applyFailed,
6093
+ applyStarted,
6094
+ applySucceeded,
5889
6095
  router,
5890
6096
  selectionId,
5891
6097
  siteId,
5892
- startRefreshTransition
6098
+ startRefreshTransition,
6099
+ theme
5893
6100
  ]);
5894
6101
  if (!state || !baseSnapshot) {
5895
6102
  return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading design controls..." });
@@ -5903,13 +6110,15 @@ function StyleConfigurator({
5903
6110
  }
5904
6111
  const currentPersonality = findButtonPersonality(state.previewed.buttonPersonalityId);
5905
6112
  const currentPaletteVariant = findPaletteVariant(state.previewed.paletteVariantId);
6113
+ const currentTypographyPreset = state.previewed.typographyPresetId ? findTypographyPreset(state.previewed.typographyPresetId) : null;
5906
6114
  const chromeLookModels = buildSiteChromeLookModels({
5907
6115
  state,
5908
6116
  siteStyle: currentSiteStyle,
5909
6117
  headerSelectionContext,
5910
6118
  footerSelectionContext
5911
6119
  });
5912
- const currentHeaderLook = state.previewed.headerLookId ? findHeaderLook(state.previewed.headerLookId) : findHeaderLook(chromeLookModels.header.renderedLookId);
6120
+ const currentHeaderLayout = getHeaderLayout(state.previewed.headerLayoutId);
6121
+ const currentHeaderStyle = getHeaderStyle(state.previewed.headerStyleId);
5913
6122
  const currentFooterLook = state.previewed.footerLookId ? findFooterLook(state.previewed.footerLookId) : findFooterLook(chromeLookModels.footer.renderedLookId);
5914
6123
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
5915
6124
  state.warnings.length > 0 ? /* @__PURE__ */ jsx(WarningNotice, { warnings: state.warnings, onDismiss: handleDismissWarnings }) : null,
@@ -5927,22 +6136,42 @@ function StyleConfigurator({
5927
6136
  /* @__PURE__ */ jsx(
5928
6137
  SiteChromeLookRow,
5929
6138
  {
5930
- label: "Header",
5931
- ariaLabel: "Header",
5932
- defaultDescription: "Use the header chosen for this site style.",
5933
- catalog: headerLookCatalog,
5934
- expanded: state.expandedRow === "header",
5935
- onToggle: () => handleToggleRow("header"),
5936
- currentId: state.previewed.headerLookId,
5937
- currentLabel: state.previewed.headerLookId ? currentHeaderLook?.label ?? "Unknown header Look" : `${currentHeaderLook?.label ?? "Site style"} (default)`,
5938
- scopeDescription: "Changes the header on every page.",
5939
- options: chromeLookModels.header.options,
5940
- diagnostics: chromeLookModels.header.diagnostics,
6139
+ label: "Header layout",
6140
+ ariaLabel: "Header layout",
6141
+ defaultDescription: "Choose the header structure.",
6142
+ expanded: state.expandedRow === "header-layout",
6143
+ onToggle: () => handleToggleRow("header-layout"),
6144
+ currentId: state.previewed.headerLayoutId,
6145
+ currentLabel: currentHeaderLayout?.label ?? state.previewed.headerLayoutId,
6146
+ scopeDescription: "Changes the header structure on every page.",
6147
+ options: chromeLookModels.headerLayout.options,
6148
+ diagnostics: [],
6149
+ onCycle: (direction) => {
6150
+ const next = cycleSiteChromeLookId(chromeLookModels.headerLayout.options, state.previewed.headerLayoutId, direction);
6151
+ if (next !== void 0) handlePreviewHeaderLayout(next);
6152
+ },
6153
+ onPreview: handlePreviewHeaderLayout,
6154
+ disabled: saving
6155
+ }
6156
+ ),
6157
+ /* @__PURE__ */ jsx(
6158
+ SiteChromeLookRow,
6159
+ {
6160
+ label: "Header look",
6161
+ ariaLabel: "Header look",
6162
+ defaultDescription: "Choose the header visual style.",
6163
+ expanded: state.expandedRow === "header-style",
6164
+ onToggle: () => handleToggleRow("header-style"),
6165
+ currentId: state.previewed.headerStyleId,
6166
+ currentLabel: currentHeaderStyle?.label ?? state.previewed.headerStyleId,
6167
+ scopeDescription: "Changes the header surface and link style.",
6168
+ options: chromeLookModels.headerStyle.options,
6169
+ diagnostics: [],
5941
6170
  onCycle: (direction) => {
5942
- const next = cycleSiteChromeLookId(chromeLookModels.header.options, state.previewed.headerLookId, direction);
5943
- if (next !== void 0) handlePreviewHeaderLook(next);
6171
+ const next = cycleSiteChromeLookId(chromeLookModels.headerStyle.options, state.previewed.headerStyleId, direction);
6172
+ if (next !== void 0) handlePreviewHeaderStyle(next);
5944
6173
  },
5945
- onPreview: handlePreviewHeaderLook,
6174
+ onPreview: handlePreviewHeaderStyle,
5946
6175
  disabled: saving
5947
6176
  }
5948
6177
  ),
@@ -5980,6 +6209,24 @@ function StyleConfigurator({
5980
6209
  disabled: saving
5981
6210
  }
5982
6211
  ),
6212
+ /* @__PURE__ */ jsx(
6213
+ TypographyRow,
6214
+ {
6215
+ expanded: state.expandedRow === "typography",
6216
+ onToggle: () => handleToggleRow("typography"),
6217
+ apiClient,
6218
+ siteStyle: currentSiteStyle,
6219
+ currentPresetId: state.previewed.typographyPresetId,
6220
+ currentLabel: currentTypographyPreset?.name ?? "Custom typography",
6221
+ bodyFontOverride: state.previewed.typographyBodyFontOverride,
6222
+ headingFontOverride: state.previewed.typographyHeadingFontOverride,
6223
+ onPreviewPreset: handlePreviewTypographyPreset,
6224
+ onBodyFontOverride: handleBodyFontOverride,
6225
+ onHeadingFontOverride: handleHeadingFontOverride,
6226
+ onClearOverrides: handleClearTypographyOverrides,
6227
+ disabled: saving
6228
+ }
6229
+ ),
5983
6230
  /* @__PURE__ */ jsx(
5984
6231
  PaletteRow,
5985
6232
  {
@@ -6042,7 +6289,7 @@ function StyleRowFrame({ label, currentLabel, swatch, expanded, onToggle, childr
6042
6289
  ] }) });
6043
6290
  }
6044
6291
  function SiteStyleRow({ expanded, onToggle, current, onPreview, disabled }) {
6045
- return /* @__PURE__ */ jsx(StyleRowFrame, { label: "Site style", currentLabel: current.name, expanded, onToggle, children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Site style", className: "space-y-1", children: curatedSiteStyles.map((style) => /* @__PURE__ */ jsx(
6292
+ return /* @__PURE__ */ jsx(StyleRowFrame, { label: "Site style", currentLabel: current.name, expanded, onToggle, children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Site style", className: "list-none space-y-1 p-0", children: curatedSiteStyles.map((style) => /* @__PURE__ */ jsx(
6046
6293
  PickerOption,
6047
6294
  {
6048
6295
  selected: style.id === current.id,
@@ -6059,7 +6306,7 @@ function PersonalityRow({ expanded, onToggle, siteStyle, currentId, currentLabel
6059
6306
  () => requireEntriesForCuratedChoices(buttonPersonalities, siteStyle.buttonPersonalityChoices),
6060
6307
  [siteStyle.buttonPersonalityChoices]
6061
6308
  );
6062
- return /* @__PURE__ */ jsx(StyleRowFrame, { label: "Button style", currentLabel, expanded, onToggle, children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Button style", className: "space-y-1", children: options.map((personality) => /* @__PURE__ */ jsx(
6309
+ return /* @__PURE__ */ jsx(StyleRowFrame, { label: "Button style", currentLabel, expanded, onToggle, children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Button style", className: "list-none space-y-1 p-0", children: options.map((personality) => /* @__PURE__ */ jsx(
6063
6310
  PickerOption,
6064
6311
  {
6065
6312
  selected: personality.id === currentId,
@@ -6071,6 +6318,407 @@ function PersonalityRow({ expanded, onToggle, siteStyle, currentId, currentLabel
6071
6318
  personality.id
6072
6319
  )) }) });
6073
6320
  }
6321
+ function TypographyRow({
6322
+ expanded,
6323
+ onToggle,
6324
+ apiClient,
6325
+ siteStyle,
6326
+ currentPresetId,
6327
+ currentLabel,
6328
+ bodyFontOverride,
6329
+ headingFontOverride,
6330
+ onPreviewPreset,
6331
+ onBodyFontOverride,
6332
+ onHeadingFontOverride,
6333
+ onClearOverrides,
6334
+ disabled
6335
+ }) {
6336
+ const options = useMemo(
6337
+ () => requireEntriesForCuratedChoices(typographyPresets, siteStyle.typographyPresetChoices),
6338
+ [siteStyle.typographyPresetChoices]
6339
+ );
6340
+ const fontCatalog = useFontCatalog(apiClient, expanded);
6341
+ const selectedPreset = currentPresetId ? findTypographyPreset(currentPresetId) : null;
6342
+ return /* @__PURE__ */ jsxs(StyleRowFrame, { label: "Typography", currentLabel, expanded, onToggle, children: [
6343
+ currentPresetId === null ? /* @__PURE__ */ jsx("div", { className: "mb-3 rounded-md border border-muted-foreground/20 bg-muted/30 px-3 py-2 text-xs text-muted-foreground", children: "Current custom setup" }) : null,
6344
+ /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Typography preset", className: "space-y-1", children: options.map((preset) => /* @__PURE__ */ jsx(
6345
+ TypographyPresetOption,
6346
+ {
6347
+ preset,
6348
+ selected: preset.id === currentPresetId,
6349
+ onSelect: () => onPreviewPreset(preset.id),
6350
+ disabled
6351
+ },
6352
+ preset.id
6353
+ )) }),
6354
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-3 border-t pt-3", children: [
6355
+ selectedPreset ? /* @__PURE__ */ jsxs(Fragment, { children: [
6356
+ /* @__PURE__ */ jsx(
6357
+ TypographyFontSlotPicker,
6358
+ {
6359
+ label: "Body font",
6360
+ slot: "body",
6361
+ preset: selectedPreset,
6362
+ catalog: fontCatalog,
6363
+ value: bodyFontOverride,
6364
+ onChange: onBodyFontOverride,
6365
+ disabled
6366
+ }
6367
+ ),
6368
+ /* @__PURE__ */ jsx(
6369
+ TypographyFontSlotPicker,
6370
+ {
6371
+ label: "Heading font",
6372
+ slot: "heading",
6373
+ preset: selectedPreset,
6374
+ catalog: fontCatalog,
6375
+ value: headingFontOverride,
6376
+ onChange: onHeadingFontOverride,
6377
+ disabled
6378
+ }
6379
+ )
6380
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Choose a typography preset to use curated font overrides." }),
6381
+ /* @__PURE__ */ jsx(
6382
+ Button,
6383
+ {
6384
+ type: "button",
6385
+ size: "sm",
6386
+ variant: "outline",
6387
+ onClick: onClearOverrides,
6388
+ disabled: disabled || !bodyFontOverride && !headingFontOverride,
6389
+ children: "Use preset fonts"
6390
+ }
6391
+ )
6392
+ ] })
6393
+ ] });
6394
+ }
6395
+ function TypographyPresetOption({
6396
+ preset,
6397
+ selected,
6398
+ onSelect,
6399
+ disabled
6400
+ }) {
6401
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
6402
+ "button",
6403
+ {
6404
+ type: "button",
6405
+ role: "radio",
6406
+ "aria-checked": selected,
6407
+ onClick: onSelect,
6408
+ disabled,
6409
+ className: cn(
6410
+ "w-full rounded-md border px-3 py-2 text-left transition disabled:cursor-not-allowed disabled:opacity-50",
6411
+ selected ? "border-primary bg-primary/5" : "border-border hover:bg-muted/60"
6412
+ ),
6413
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
6414
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
6415
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: preset.name }),
6416
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: preset.description })
6417
+ ] }),
6418
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
6419
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-semibold uppercase tracking-wide", children: preset.defaultFonts.heading.split(",")[0] }),
6420
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: preset.defaultFonts.body.split(",")[0] })
6421
+ ] })
6422
+ ] })
6423
+ }
6424
+ ) });
6425
+ }
6426
+ function TypographyFontSlotPicker({
6427
+ label,
6428
+ slot,
6429
+ preset,
6430
+ catalog,
6431
+ value,
6432
+ onChange,
6433
+ disabled
6434
+ }) {
6435
+ const [query, setQuery] = useState("");
6436
+ const defaultFont = primaryFontFamily(preset.defaultFonts[slot]);
6437
+ const selectedFamily = value ?? defaultFont;
6438
+ const recordsByFamily = useMemo(() => indexFontsByFamily(catalog.fonts), [catalog.fonts]);
6439
+ const selectedFit = evaluateTypographyFontSlot({
6440
+ slot,
6441
+ policy: preset.slotPolicies[slot],
6442
+ capability: capabilityForFamily(selectedFamily, recordsByFamily)
6443
+ });
6444
+ const recommendedFamilies = preset.recommendedFonts[slot];
6445
+ const recommendedOptions = useMemo(
6446
+ () => uniqueFontFamilies(recommendedFamilies),
6447
+ [recommendedFamilies]
6448
+ );
6449
+ const browseOptions = useMemo(() => {
6450
+ const normalizedQuery = query.trim().toLowerCase();
6451
+ const source = normalizedQuery.length > 0 ? catalog.fonts.filter((font) => font.family.toLowerCase().includes(normalizedQuery)) : catalog.fonts;
6452
+ return [...source].sort((a, b) => sortFontForPresetSlot(a, b, slot, preset, recordsByFamily)).slice(0, normalizedQuery.length > 0 ? MAX_FONT_RESULTS_WHEN_SEARCHING : MAX_FONT_RESULTS_WHEN_BROWSING);
6453
+ }, [catalog.fonts, preset, query, recordsByFamily, slot]);
6454
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-2", "aria-label": label, children: [
6455
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
6456
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
6457
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: label }),
6458
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm", children: value ? selectedFamily : `${defaultFont} (preset)` })
6459
+ ] }),
6460
+ /* @__PURE__ */ jsx(TypographyFontFitBadge, { fit: selectedFit })
6461
+ ] }),
6462
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-1", children: [
6463
+ /* @__PURE__ */ jsxs(
6464
+ "button",
6465
+ {
6466
+ type: "button",
6467
+ className: cn(
6468
+ "rounded-md border px-3 py-2 text-left text-sm transition disabled:cursor-not-allowed disabled:opacity-50",
6469
+ value === null ? "border-foreground bg-accent/40" : "border-transparent hover:border-foreground/40 hover:bg-accent/20"
6470
+ ),
6471
+ onClick: () => onChange(null),
6472
+ disabled,
6473
+ children: [
6474
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Use preset font" }),
6475
+ /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: defaultFont })
6476
+ ]
6477
+ }
6478
+ ),
6479
+ recommendedOptions.map((family) => /* @__PURE__ */ jsx(
6480
+ FontFamilyOption,
6481
+ {
6482
+ family,
6483
+ selected: value === family,
6484
+ fit: evaluateTypographyFontSlot({
6485
+ slot,
6486
+ policy: preset.slotPolicies[slot],
6487
+ capability: capabilityForFamily(family, recordsByFamily)
6488
+ }),
6489
+ onSelect: () => onChange(family),
6490
+ disabled
6491
+ },
6492
+ family
6493
+ ))
6494
+ ] }),
6495
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border bg-muted/20 p-2", children: [
6496
+ /* @__PURE__ */ jsx(
6497
+ "input",
6498
+ {
6499
+ type: "search",
6500
+ value: query,
6501
+ placeholder: `Search ${slot} fonts`,
6502
+ onChange: (event) => setQuery(event.currentTarget.value),
6503
+ disabled: disabled || catalog.kind === "loading",
6504
+ className: "h-8 w-full rounded-md border bg-background px-2 text-sm outline-none focus:border-primary disabled:cursor-not-allowed disabled:opacity-50"
6505
+ }
6506
+ ),
6507
+ catalog.kind === "loading" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading font catalog..." }) : catalog.kind === "failed" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: catalog.message }) : null,
6508
+ browseOptions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-1 overflow-y-auto pr-1", children: browseOptions.map((font) => /* @__PURE__ */ jsx(
6509
+ FontFamilyOption,
6510
+ {
6511
+ family: font.family,
6512
+ selected: value === font.family,
6513
+ fit: evaluateTypographyFontSlot({
6514
+ slot,
6515
+ policy: preset.slotPolicies[slot],
6516
+ capability: capabilityFromFontRecord(font)
6517
+ }),
6518
+ onSelect: () => onChange(font.family),
6519
+ disabled
6520
+ },
6521
+ font.family
6522
+ )) }) : catalog.kind === "loaded" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No matching Google Fonts." }) : null
6523
+ ] }),
6524
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
6525
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Brand or uploaded font name" }),
6526
+ /* @__PURE__ */ jsx(
6527
+ "input",
6528
+ {
6529
+ type: "text",
6530
+ value: value ?? "",
6531
+ placeholder: "Enter any font family",
6532
+ onChange: (event) => onChange(normalizeFontFamilyOverride(event.currentTarget.value)),
6533
+ disabled,
6534
+ className: "h-8 w-full rounded-md border bg-background px-2 text-sm outline-none focus:border-primary disabled:cursor-not-allowed disabled:opacity-50"
6535
+ }
6536
+ )
6537
+ ] })
6538
+ ] });
6539
+ }
6540
+ function FontFamilyOption({
6541
+ family,
6542
+ selected,
6543
+ fit,
6544
+ onSelect,
6545
+ disabled
6546
+ }) {
6547
+ return /* @__PURE__ */ jsxs(
6548
+ "button",
6549
+ {
6550
+ type: "button",
6551
+ className: cn(
6552
+ "flex w-full items-center justify-between gap-3 rounded-md border px-3 py-2 text-left text-sm transition disabled:cursor-not-allowed disabled:opacity-50",
6553
+ selected ? "border-foreground bg-accent/40" : "border-transparent bg-background hover:border-foreground/40 hover:bg-accent/20"
6554
+ ),
6555
+ onClick: onSelect,
6556
+ disabled,
6557
+ children: [
6558
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: family }),
6559
+ /* @__PURE__ */ jsx(TypographyFontFitBadge, { fit })
6560
+ ]
6561
+ }
6562
+ );
6563
+ }
6564
+ function TypographyFontFitBadge({ fit }) {
6565
+ return /* @__PURE__ */ jsx(
6566
+ "span",
6567
+ {
6568
+ className: cn(
6569
+ "shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium",
6570
+ fit.kind === "recommended" && "bg-emerald-100 text-emerald-800",
6571
+ fit.kind === "compatible" && "bg-sky-100 text-sky-800",
6572
+ fit.kind === "limited" && "bg-amber-100 text-amber-900",
6573
+ fit.kind === "risky" && "bg-rose-100 text-rose-800",
6574
+ fit.kind === "unknown" && "bg-muted text-muted-foreground"
6575
+ ),
6576
+ title: fit.reason,
6577
+ children: typographyFontFitLabel(fit.kind)
6578
+ }
6579
+ );
6580
+ }
6581
+ function useFontCatalog(apiClient, enabled) {
6582
+ const [catalog, setCatalog] = useState({ kind: "idle", fonts: [] });
6583
+ const requestedClientRef = useRef(null);
6584
+ useEffect(() => {
6585
+ if (!enabled) return;
6586
+ if (requestedClientRef.current === apiClient) return;
6587
+ requestedClientRef.current = apiClient;
6588
+ let cancelled = false;
6589
+ setCatalog({ kind: "loading", fonts: [] });
6590
+ async function loadFonts() {
6591
+ try {
6592
+ const response = await apiClient({ endpoint: "listFonts" });
6593
+ const fonts = unwrapResponse(response);
6594
+ if (!cancelled) {
6595
+ setCatalog({
6596
+ kind: "loaded",
6597
+ fonts: Array.isArray(fonts) ? fonts : []
6598
+ });
6599
+ }
6600
+ } catch (error) {
6601
+ if (!cancelled) {
6602
+ setCatalog({
6603
+ kind: "failed",
6604
+ fonts: [],
6605
+ message: error instanceof Error ? error.message : "Unable to load the font catalog."
6606
+ });
6607
+ }
6608
+ }
6609
+ }
6610
+ void loadFonts();
6611
+ return () => {
6612
+ cancelled = true;
6613
+ };
6614
+ }, [apiClient, enabled]);
6615
+ return catalog;
6616
+ }
6617
+ function indexFontsByFamily(fonts) {
6618
+ return new Map(fonts.map((font) => [font.family.toLowerCase(), font]));
6619
+ }
6620
+ function capabilityForFamily(family, recordsByFamily) {
6621
+ const record = recordsByFamily.get(family.toLowerCase());
6622
+ if (record) return capabilityFromFontRecord(record);
6623
+ return {
6624
+ source: "unknown",
6625
+ family,
6626
+ category: "unknown",
6627
+ variants: []
6628
+ };
6629
+ }
6630
+ function capabilityFromFontRecord(font) {
6631
+ return {
6632
+ source: "google",
6633
+ family: font.family,
6634
+ category: normalizeGoogleFontCategory(font.category),
6635
+ variants: font.variants
6636
+ };
6637
+ }
6638
+ function normalizeGoogleFontCategory(category) {
6639
+ switch (category) {
6640
+ case "serif":
6641
+ case "sans-serif":
6642
+ case "display":
6643
+ case "handwriting":
6644
+ case "monospace":
6645
+ return category;
6646
+ default:
6647
+ return "unknown";
6648
+ }
6649
+ }
6650
+ function sortFontForPresetSlot(a, b, slot, preset, recordsByFamily) {
6651
+ const aFit = evaluateTypographyFontSlot({
6652
+ slot,
6653
+ policy: preset.slotPolicies[slot],
6654
+ capability: capabilityForFamily(a.family, recordsByFamily)
6655
+ });
6656
+ const bFit = evaluateTypographyFontSlot({
6657
+ slot,
6658
+ policy: preset.slotPolicies[slot],
6659
+ capability: capabilityForFamily(b.family, recordsByFamily)
6660
+ });
6661
+ const fitDelta = typographyFontFitRank(aFit.kind) - typographyFontFitRank(bFit.kind);
6662
+ if (fitDelta !== 0) return fitDelta;
6663
+ const popularityDelta = (a.popularity ?? Infinity) - (b.popularity ?? Infinity);
6664
+ if (popularityDelta !== 0) return popularityDelta;
6665
+ return a.family.localeCompare(b.family);
6666
+ }
6667
+ function typographyFontFitRank(kind) {
6668
+ switch (kind) {
6669
+ case "recommended":
6670
+ return 0;
6671
+ case "compatible":
6672
+ return 1;
6673
+ case "limited":
6674
+ return 2;
6675
+ case "risky":
6676
+ return 3;
6677
+ case "unknown":
6678
+ return 4;
6679
+ default: {
6680
+ const exhaustive = kind;
6681
+ return exhaustive;
6682
+ }
6683
+ }
6684
+ }
6685
+ function typographyFontFitLabel(kind) {
6686
+ switch (kind) {
6687
+ case "recommended":
6688
+ return "Recommended";
6689
+ case "compatible":
6690
+ return "Compatible";
6691
+ case "limited":
6692
+ return "Limited";
6693
+ case "risky":
6694
+ return "Risky";
6695
+ case "unknown":
6696
+ return "Unknown";
6697
+ default: {
6698
+ const exhaustive = kind;
6699
+ return exhaustive;
6700
+ }
6701
+ }
6702
+ }
6703
+ function primaryFontFamily(cssFamily) {
6704
+ return cssFamily.split(",")[0]?.trim().replace(/^["']|["']$/gu, "") || cssFamily;
6705
+ }
6706
+ function uniqueFontFamilies(families) {
6707
+ const seen = /* @__PURE__ */ new Set();
6708
+ const result = [];
6709
+ for (const family of families) {
6710
+ const normalized = primaryFontFamily(family);
6711
+ const key = normalized.toLowerCase();
6712
+ if (seen.has(key)) continue;
6713
+ seen.add(key);
6714
+ result.push(normalized);
6715
+ }
6716
+ return result;
6717
+ }
6718
+ function normalizeFontFamilyOverride(value) {
6719
+ const trimmed = value.trim();
6720
+ return trimmed.length > 0 ? trimmed : null;
6721
+ }
6074
6722
  function SiteChromeLookRow({
6075
6723
  label,
6076
6724
  ariaLabel,
@@ -6083,7 +6731,7 @@ function SiteChromeLookRow({
6083
6731
  description: defaultDescription,
6084
6732
  available: true
6085
6733
  },
6086
- ...catalog.map((look) => ({ ...look, available: true }))
6734
+ ...(catalog ?? []).map((look) => ({ ...look, available: true }))
6087
6735
  ],
6088
6736
  diagnostics,
6089
6737
  scopeDescription,
@@ -6105,7 +6753,7 @@ function SiteChromeLookRow({
6105
6753
  type: "button",
6106
6754
  onClick: () => onCycle("prev"),
6107
6755
  className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
6108
- "aria-label": `Previous ${label.toLowerCase()} Look`,
6756
+ "aria-label": `Previous ${label.toLowerCase()}`,
6109
6757
  disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
6110
6758
  children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
6111
6759
  }
@@ -6116,15 +6764,15 @@ function SiteChromeLookRow({
6116
6764
  type: "button",
6117
6765
  onClick: () => onCycle("next"),
6118
6766
  className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
6119
- "aria-label": `Next ${label.toLowerCase()} Look`,
6767
+ "aria-label": `Next ${label.toLowerCase()}`,
6120
6768
  disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
6121
6769
  children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
6122
6770
  }
6123
6771
  )
6124
6772
  ] })
6125
6773
  ] }),
6126
- diagnostics.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "mb-3 space-y-1 text-xs text-amber-700", children: diagnostics.map((diagnostic) => /* @__PURE__ */ jsx("li", { children: diagnostic }, diagnostic)) }) : null,
6127
- /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": ariaLabel, className: "space-y-1", children: options.map((look) => /* @__PURE__ */ jsx(
6774
+ diagnostics.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "mb-3 list-none space-y-1 p-0 text-xs text-amber-700", children: diagnostics.map((diagnostic) => /* @__PURE__ */ jsx("li", { children: diagnostic }, diagnostic)) }) : null,
6775
+ /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": ariaLabel, className: "list-none space-y-1 p-0", children: options.map((look) => /* @__PURE__ */ jsx(
6128
6776
  PickerOption,
6129
6777
  {
6130
6778
  selected: look.id === currentId,
@@ -6150,7 +6798,7 @@ function PaletteRow({ expanded, onToggle, siteStyle, currentId, currentLabel, cu
6150
6798
  swatch: currentSwatch,
6151
6799
  expanded,
6152
6800
  onToggle,
6153
- children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Palette", className: "space-y-1", children: options.map((variant) => {
6801
+ children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Palette", className: "list-none space-y-1 p-0", children: options.map((variant) => {
6154
6802
  const primaryHex = variant.colors.find((c) => c.name === "primary")?.hex;
6155
6803
  return /* @__PURE__ */ jsx(
6156
6804
  PickerOption,
@@ -6211,7 +6859,7 @@ function ColoursRow({ expanded, onToggle, variant, overrides, onTokenChange, onR
6211
6859
  );
6212
6860
  }
6213
6861
  function PickerOption({ selected, label, description, swatch, onSelect, disabled }) {
6214
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
6862
+ return /* @__PURE__ */ jsx("li", { className: "list-none", children: /* @__PURE__ */ jsxs(
6215
6863
  "button",
6216
6864
  {
6217
6865
  type: "button",
@@ -6286,6 +6934,8 @@ function describeWarning(warning) {
6286
6934
  return `Button style changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
6287
6935
  case "rejected-palette":
6288
6936
  return `Palette changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
6937
+ case "rejected-typography-preset":
6938
+ return `Typography changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
6289
6939
  default: {
6290
6940
  const exhaustive = warning;
6291
6941
  return exhaustive;
@@ -6310,6 +6960,11 @@ function PreviewEditorSidebar({
6310
6960
  themeSelectionButtonPersonalityId = null,
6311
6961
  themeSelectionPaletteVariantId = null,
6312
6962
  themeSelectionPaletteOverrides = null,
6963
+ themeSelectionHeaderLayoutId = null,
6964
+ themeSelectionHeaderStyleId = null,
6965
+ themeSelectionTypographyPresetId = null,
6966
+ themeSelectionTypographyBodyFontOverride = null,
6967
+ themeSelectionTypographyHeadingFontOverride = null,
6313
6968
  themeSelectionHeaderLookId = null,
6314
6969
  themeSelectionFooterLookId = null,
6315
6970
  themeSelectionHeaderContext,
@@ -6469,6 +7124,11 @@ function PreviewEditorSidebar({
6469
7124
  buttonPersonalityId: themeSelectionButtonPersonalityId,
6470
7125
  paletteVariantId: themeSelectionPaletteVariantId,
6471
7126
  paletteOverrides: themeSelectionPaletteOverrides,
7127
+ headerLayoutId: themeSelectionHeaderLayoutId,
7128
+ headerStyleId: themeSelectionHeaderStyleId,
7129
+ typographyPresetId: themeSelectionTypographyPresetId,
7130
+ typographyBodyFontOverride: themeSelectionTypographyBodyFontOverride,
7131
+ typographyHeadingFontOverride: themeSelectionTypographyHeadingFontOverride,
6472
7132
  headerLookId: themeSelectionHeaderLookId,
6473
7133
  footerLookId: themeSelectionFooterLookId
6474
7134
  },