@riverbankcms/sdk 0.76.0 → 0.77.1

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 (151) hide show
  1. package/dist/{PreviewEditorSidebar-X2HWXFOL.mjs → PreviewEditorSidebar-DVUG7GK4.mjs} +1019 -442
  2. package/dist/{PreviewEditorUI-AQXIXPOH.mjs → PreviewEditorUI-P7RN4CDS.mjs} +2 -2
  3. package/dist/SdkPreviewModeRuntime-KLPX47SE.mjs +8 -0
  4. package/dist/_dts/ai/src/contracts/proposals.d.ts +538 -20
  5. package/dist/_dts/api/src/contentRuntime.d.ts +5 -0
  6. package/dist/_dts/api/src/siteAssets.d.ts +1 -0
  7. package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +26 -2
  8. package/dist/_dts/block-form/src/blockDraftSaveability.d.ts +28 -0
  9. package/dist/_dts/block-form/src/client/index.d.ts +2 -2
  10. package/dist/_dts/block-form/src/index.d.ts +2 -1
  11. package/dist/_dts/block-form/src/widgets/DateField/dateFieldSchema.d.ts +1 -1
  12. package/dist/_dts/block-form/src/widgets/DateTimeField/dateTimeFieldSchema.d.ts +1 -1
  13. package/dist/_dts/block-form/src/widgets/TimeField/timeFieldSchema.d.ts +1 -1
  14. package/dist/_dts/blocks/src/PageRenderer.d.ts +10 -10
  15. package/dist/_dts/blocks/src/client/site-chrome.d.ts +22 -0
  16. package/dist/_dts/blocks/src/customBlockRegistry.d.ts +57 -0
  17. package/dist/_dts/blocks/src/index.d.ts +2 -0
  18. package/dist/_dts/blocks/src/system/blocks/faq.d.ts +20 -2
  19. package/dist/_dts/blocks/src/system/blocks/feature-cards.d.ts +25 -0
  20. package/dist/_dts/blocks/src/system/blocks/index.d.ts +2 -0
  21. package/dist/_dts/blocks/src/system/blocks/intro.d.ts +24 -0
  22. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +3 -34
  23. package/dist/_dts/blocks/src/system/blocks/site-header.d.ts +2 -2
  24. package/dist/_dts/blocks/src/system/constants/blockKinds.d.ts +1 -1
  25. package/dist/_dts/blocks/src/system/fields/aspectRatio.d.ts +30 -0
  26. package/dist/_dts/blocks/src/system/fields/boxStyles.d.ts +4 -1
  27. package/dist/_dts/blocks/src/system/fields/ctas.d.ts +7 -0
  28. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +16 -2
  29. package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +1 -1
  30. package/dist/_dts/blocks/src/system/runtime/components/multi-step/runtimeFormValidation.d.ts +28 -0
  31. package/dist/_dts/blocks/src/system/runtime/nodes/accordion.d.ts +2 -0
  32. package/dist/_dts/blocks/src/system/runtime/nodes/accordion.server.d.ts +2 -0
  33. package/dist/_dts/blocks/src/system/runtime/nodes/accordionIconTreatment.d.ts +5 -0
  34. package/dist/_dts/blocks/src/system/transforms/registry/layout.d.ts +5 -4
  35. package/dist/_dts/blocks/src/system/transforms/typed.d.ts +2 -1
  36. package/dist/_dts/content-editor/src/controller/EntryControllerProvider.d.ts +2 -0
  37. package/dist/_dts/content-editor/src/hooks/blockDraftLifecycle.d.ts +48 -0
  38. package/dist/_dts/content-editor/src/hooks/invalidBlockSelectors.d.ts +4 -0
  39. package/dist/_dts/content-editor/src/hooks/publishability.d.ts +47 -0
  40. package/dist/_dts/content-editor/src/index.d.ts +1 -0
  41. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +20 -0
  42. package/dist/_dts/editor-blocks/src/BlockEditor.d.ts +2 -1
  43. package/dist/_dts/editor-blocks/src/block-editor/SortableBlockItem.d.ts +2 -1
  44. package/dist/_dts/editor-blocks/src/block-editor/customBlockRegistry.d.ts +2 -0
  45. package/dist/_dts/editor-blocks/src/block-editor/sdkBlockMerger.d.ts +9 -37
  46. package/dist/_dts/editor-blocks/src/index.d.ts +2 -2
  47. package/dist/_dts/preview-next/src/client/index.d.ts +3 -0
  48. package/dist/_dts/preview-next/src/client/layout/PreviewLayoutContext.d.ts +5 -1
  49. package/dist/_dts/preview-next/src/client/preview/PreviewDesignCoordinatorContext.d.ts +20 -0
  50. package/dist/_dts/preview-next/src/client/preview/PreviewEditorSidebar.d.ts +7 -2
  51. package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +7 -2
  52. package/dist/_dts/preview-next/src/client/preview/PreviewShellLayout.d.ts +7 -2
  53. package/dist/_dts/preview-next/src/client/preview/PreviewThemedSiteDocument.d.ts +18 -0
  54. package/dist/_dts/preview-next/src/client/preview/SiteChromeCustomizeContext.d.ts +20 -3
  55. package/dist/_dts/preview-next/src/client/preview/StyleConfigurator.d.ts +6 -1
  56. package/dist/_dts/preview-next/src/client/preview/StyleConfigurator.state.d.ts +14 -4
  57. package/dist/_dts/preview-next/src/client/preview/previewDesignCoordinator.d.ts +73 -0
  58. package/dist/_dts/preview-next/src/client/preview/siteChromeLookRows.d.ts +12 -4
  59. package/dist/_dts/preview-next/src/client/preview/siteStylePreviewPlanner.d.ts +46 -0
  60. package/dist/_dts/preview-next/src/client/preview/styleConfiguratorSnapshot.d.ts +6 -1
  61. package/dist/_dts/preview-next/src/client/preview/typographySelectionInput.d.ts +14 -0
  62. package/dist/_dts/preview-next/src/client/sdk/SdkPreviewModeRuntime.d.ts +1 -1
  63. package/dist/_dts/sdk/src/cli/commands/style.d.ts +8 -0
  64. package/dist/_dts/sdk/src/client/management/types.d.ts +6 -1
  65. package/dist/_dts/sdk/src/client/types.d.ts +9 -0
  66. package/dist/_dts/sdk/src/client.d.ts +1 -1
  67. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +35 -0
  68. package/dist/_dts/sdk/src/contracts/theme.d.ts +1 -1
  69. package/dist/_dts/sdk/src/index.d.ts +2 -1
  70. package/dist/_dts/sdk/src/public-api/contracts.d.ts +5 -0
  71. package/dist/_dts/sdk/src/rendering/components/Layout.d.ts +2 -2
  72. package/dist/_dts/sdk/src/types/block-kinds.d.ts +1 -1
  73. package/dist/_dts/sdk/src/version.d.ts +1 -1
  74. package/dist/_dts/site-commands/src/commands.d.ts +15 -0
  75. package/dist/_dts/theme-core/src/buttons/personalities/earth-pill.d.ts +16 -0
  76. package/dist/_dts/theme-core/src/buttons/personalities/index.d.ts +11 -0
  77. package/dist/_dts/theme-core/src/fonts/types.d.ts +1 -1
  78. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  79. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +72 -27
  80. package/dist/_dts/theme-core/src/palette/variants/index.d.ts +15 -0
  81. package/dist/_dts/theme-core/src/palette/variants/warm-neutral.d.ts +16 -0
  82. package/dist/_dts/theme-core/src/schema.d.ts +306 -30
  83. package/dist/_dts/theme-core/src/site-styles/appearancePresetCatalog.d.ts +81 -0
  84. package/dist/_dts/theme-core/src/site-styles/appearancePresetTypes.d.ts +6 -4
  85. package/dist/_dts/theme-core/src/site-styles/blockAppearancePresets.d.ts +81 -0
  86. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +119 -3
  87. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +4 -4
  88. package/dist/_dts/theme-core/src/site-styles/footerLooks.d.ts +13 -0
  89. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -2
  90. package/dist/_dts/theme-core/src/site-styles/headerLooks.d.ts +402 -13
  91. package/dist/_dts/theme-core/src/site-styles/headerOpeningSection.d.ts +6 -0
  92. package/dist/_dts/theme-core/src/site-styles/index.d.ts +2 -0
  93. package/dist/_dts/theme-core/src/site-styles/persistedIdParseResult.d.ts +9 -0
  94. package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +20 -4
  95. package/dist/_dts/theme-core/src/site-styles/types.d.ts +74 -2
  96. package/dist/_dts/theme-core/src/site-styles/typographyPresets.d.ts +189 -0
  97. package/dist/{chunk-Q4OLG66B.mjs → chunk-2SPENC5M.mjs} +476 -153
  98. package/dist/{chunk-Z7JLNTJR.mjs → chunk-6SMUFTXD.mjs} +2 -1
  99. package/dist/{chunk-TW2Y4RR5.mjs → chunk-IENWFSLO.mjs} +365 -52
  100. package/dist/{chunk-LYI7TEMH.mjs → chunk-KWJPDZH2.mjs} +4682 -1463
  101. package/dist/{chunk-V5VORNEJ.mjs → chunk-ZYEJLCTN.mjs} +1 -1
  102. package/dist/cli/index.mjs +2314 -267
  103. package/dist/client/{accordion-2YHIBOZH.mjs → accordion-RZMQSYLR.mjs} +1 -1
  104. package/dist/client/{bookingForm-35IBDWAS.mjs → bookingForm-NF4ASVNV.mjs} +2 -2
  105. package/dist/client/bookings.mjs +92 -0
  106. package/dist/client/{carousel-K6XG7NZQ.mjs → carousel-LDX47ZIK.mjs} +1 -1
  107. package/dist/client/{cart-HCFYUTTU.mjs → cart-RGXDS5F4.mjs} +3 -3
  108. package/dist/client/{checkout-J3U4XR7K.mjs → checkout-P7WWSNNY.mjs} +3 -3
  109. package/dist/client/{chunk-BOIHIU7E.mjs → chunk-E2TDNXCL.mjs} +1 -1
  110. package/dist/client/{chunk-PFBX3IQL.mjs → chunk-GMVTOKYG.mjs} +2 -2
  111. package/dist/client/{chunk-FGUIVT2B.mjs → chunk-JNC7CGHX.mjs} +1 -1
  112. package/dist/client/{chunk-SNSWVZEP.mjs → chunk-L3DNHIJX.mjs} +3 -3
  113. package/dist/client/{chunk-7UOSCYY4.mjs → chunk-R56FQ3RR.mjs} +92 -0
  114. package/dist/client/{chunk-J227A3CQ.mjs → chunk-UFSWADAO.mjs} +1 -1
  115. package/dist/client/client.mjs +3579 -1351
  116. package/dist/client/{courseRegistration-ZBXS7VGI.mjs → courseRegistration-7F65K5ZR.mjs} +2 -2
  117. package/dist/client/{eventCalendar-EQQZHJBA.mjs → eventCalendar-GLSC5KTL.mjs} +4 -4
  118. package/dist/client/{eventCombined-JCFTGBFQ.mjs → eventCombined-ZHK4WKUR.mjs} +4 -4
  119. package/dist/client/{eventDetails-HLUTQSYY.mjs → eventDetails-E3PJYBIN.mjs} +4 -4
  120. package/dist/client/{eventListing-FG4MICVB.mjs → eventListing-EMS3WC3E.mjs} +4 -4
  121. package/dist/client/{eventRegistration-GUEB7LBT.mjs → eventRegistration-NZOTVNPI.mjs} +4 -4
  122. package/dist/client/{form-YQEW4YIT.mjs → form-ETFZFATW.mjs} +1 -1
  123. package/dist/client/{headerSection-GTR3TTRM.mjs → headerSection-4LBP7ULT.mjs} +2 -2
  124. package/dist/client/hooks.mjs +2226 -402
  125. package/dist/client/{locationMap-SPC7VGUH.mjs → locationMap-5M655YSJ.mjs} +1 -1
  126. package/dist/client/{newsletterForm-4IW5BLID.mjs → newsletterForm-QOUYW44O.mjs} +1 -1
  127. package/dist/client/{productDetail-KP5H2WJO.mjs → productDetail-O4DOCWQG.mjs} +3 -3
  128. package/dist/client/{productList-3M3M72XZ.mjs → productList-A5RO5WIS.mjs} +3 -3
  129. package/dist/client/rendering/client.mjs +198 -32
  130. package/dist/client/rendering/islands.mjs +18 -18
  131. package/dist/client/rendering.mjs +3573 -1355
  132. package/dist/client/{shop-ACQKVGOM.mjs → shop-L4MZH4I2.mjs} +3 -3
  133. package/dist/preview-next/client/runtime.mjs +4 -4
  134. package/dist/{sdk-runtime-K7HNB4U2.mjs → sdk-runtime-6UT2S7KZ.mjs} +5 -5
  135. package/dist/server/components.mjs +3497 -1333
  136. package/dist/server/config-validation.mjs +2229 -402
  137. package/dist/server/config.mjs +2229 -402
  138. package/dist/server/data.mjs +2226 -402
  139. package/dist/server/index.mjs +31 -2
  140. package/dist/server/next.mjs +3503 -1329
  141. package/dist/server/page-converter.mjs +2213 -403
  142. package/dist/server/prebuild.mjs +1 -1
  143. package/dist/server/rendering/server.mjs +3497 -1333
  144. package/dist/server/rendering.mjs +3497 -1333
  145. package/dist/server/routing.mjs +2250 -363
  146. package/dist/server/server.mjs +2243 -400
  147. package/dist/server/theme-bridge.mjs +1313 -1035
  148. package/dist/server/theme.mjs +237 -12
  149. package/dist/styles/index.css +1081 -1028
  150. package/package.json +1 -1
  151. package/dist/SdkPreviewModeRuntime-ZQNDCASH.mjs +0 -8
@@ -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-TW2Y4RR5.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, DropdownMenu, DropdownMenuTrigger, EllipsisVertical, DropdownMenuContent, DropdownMenuItem, Settings, DropdownMenuSeparator, Copy, Trash2, DialogFooter, DialogClose, CardContent, transformSdkBlockToDefinition, 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, compileSiteThemeFromSelections, paletteOverridesSchema, findButtonPersonality, findPaletteVariant, findHeaderLook, findFooterLook, headerLookCatalog, footerLookCatalog, isObjectRecord, EMPTY_DOC, getRichTextImagePositionFromTarget, insertOrReplaceRichTextImage, Pencil, foldSubroute, SUBROUTE_KINDS, Smartphone, Tablet, Monitor, Maximize2, DropdownMenuLabel, 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-LYI7TEMH.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-IENWFSLO.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-KWJPDZH2.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';
@@ -2962,45 +2962,37 @@ function createBlockFormLogger(scope) {
2962
2962
  }
2963
2963
 
2964
2964
  // ../editor-blocks/src/block-editor/sdkBlockMerger.ts
2965
- var logger = createBlockFormLogger("sdkBlockMerger");
2966
- function transformToBlockDescriptor(sdkBlock) {
2967
- return {
2968
- name: sdkBlock.id,
2969
- title: sdkBlock.title,
2970
- description: sdkBlock.description,
2971
- category: sdkBlock.category,
2972
- tags: sdkBlock.tags,
2973
- icon: sdkBlock.icon,
2974
- definition: transformSdkBlockToDefinition(sdkBlock),
2975
- source: "sdk"
2976
- };
2977
- }
2965
+ var logger = createBlockFormLogger("customBlockRegistry");
2978
2966
  function mergeBlockDescriptors(systemBlocks, sdkConfig, dbCustomBlocks) {
2979
- const existingNames = new Set(systemBlocks.map((b) => b.name));
2980
- const result = [...systemBlocks];
2981
- if (sdkConfig?.customBlocks?.length) {
2982
- for (const sdkBlock of sdkConfig.customBlocks) {
2983
- if (existingNames.has(sdkBlock.id)) {
2984
- logger.warn(`Skipping SDK block "${sdkBlock.id}" - conflicts with system block`);
2985
- continue;
2986
- }
2987
- result.push(transformToBlockDescriptor(sdkBlock));
2988
- existingNames.add(sdkBlock.id);
2989
- }
2990
- }
2991
- 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
+ );
2992
2977
  }
2993
2978
  function getBlockManifestFromSdkConfig(blockKind, sdkConfig, dbCustomBlocks) {
2994
- if (!blockKind.startsWith("custom.")) {
2995
- return void 0;
2996
- }
2997
- if (sdkConfig?.customBlocks) {
2998
- const sdkBlock = sdkConfig.customBlocks.find((b) => b.id === blockKind);
2999
- if (sdkBlock) {
3000
- 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;
3001
2990
  }
3002
- }
3003
- return void 0;
2991
+ return {
2992
+ manifest: entry,
2993
+ source: { kind: "managed-database" }
2994
+ };
2995
+ });
3004
2996
  }
3005
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: [
3006
2998
  /* @__PURE__ */ jsx("div", { className: "w-full border-b" }),
@@ -3092,18 +3084,20 @@ function getCategoryLabel(category) {
3092
3084
  // ../editor-blocks/src/block-editor/blockDiscovery.ts
3093
3085
  var CORE_ESSENTIAL_BLOCKS = [
3094
3086
  "block.hero",
3095
- "block.bodyText",
3096
- "block.mediaText",
3097
- "block.imageGallery",
3098
- "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",
3099
3093
  "block.testimonials",
3100
3094
  "block.faq",
3101
3095
  "block.form"
3102
3096
  ];
3103
3097
  var CONTEXTUAL_ESSENTIAL_GROUPS = [
3104
- ["block.blogListing"],
3105
- ["block.eventListing", "block.eventCalendar"],
3106
- ["block.appointmentBooking"],
3098
+ ["block.blog-listing"],
3099
+ ["block.event-listing", "block.event-calendar"],
3100
+ ["block.appointment-booking"],
3107
3101
  ["block.shop"]
3108
3102
  ];
3109
3103
  function filterBlocksByContentType(blocks, enabledContentTypes) {
@@ -3194,21 +3188,26 @@ function getRecentBlocks(blocks, recentBlockNames) {
3194
3188
  }
3195
3189
  function suggestBlocks(blocks, existingBlockNames) {
3196
3190
  if (existingBlockNames.length === 0) {
3197
- 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);
3198
3192
  }
3199
3193
  const hasHero = existingBlockNames.includes("block.hero");
3194
+ const hasIntro = existingBlockNames.includes("block.intro");
3200
3195
  const hasBody = existingBlockNames.includes("block.body-text");
3196
+ const hasFeatureCards = existingBlockNames.includes("block.feature-cards");
3201
3197
  const hasCta = existingBlockNames.includes("block.cta-full");
3202
3198
  const hasFaq = existingBlockNames.includes("block.faq");
3203
3199
  const hasTestimonials = existingBlockNames.includes("block.testimonials");
3204
3200
  const suggestions = [];
3205
- if (hasHero && !hasBody) {
3206
- suggestions.push("block.body-text");
3201
+ if (hasHero && !hasIntro && !hasBody) {
3202
+ suggestions.push("block.intro", "block.body-text");
3207
3203
  }
3208
- if ((hasHero || hasBody) && !hasCta) {
3204
+ if ((hasHero || hasIntro || hasBody) && !hasCta) {
3209
3205
  suggestions.push("block.cta-full");
3210
3206
  }
3211
- 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) {
3212
3211
  suggestions.push("block.testimonials", "block.faq");
3213
3212
  }
3214
3213
  const blockMap = new Map(blocks.map((b) => [b.name, b]));
@@ -4978,20 +4977,28 @@ var SortableBlockItem = memo(function SortableBlockItem2({
4978
4977
  widgetOverrides,
4979
4978
  uiStore,
4980
4979
  sdkConfig,
4980
+ customBlocks = [],
4981
4981
  hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false,
4982
4982
  pageDesignBlock = null,
4983
4983
  reorderControlMode = DEFAULT_REORDER_CONTROL_MODE,
4984
4984
  actionControlMode = DEFAULT_BLOCK_ACTION_CONTROL_MODE
4985
4985
  }) {
4986
- const sortable = useSortable({ id: block.id, disabled: !isDraggable });
4986
+ const sortable = useSortable({
4987
+ id: block.id,
4988
+ disabled: !isDraggable
4989
+ });
4987
4990
  const manifest = useMemo(() => {
4988
- const sdkManifest = getBlockManifestFromSdkConfig(block.kind, sdkConfig ?? null);
4991
+ const sdkManifest = getBlockManifestFromSdkConfig(
4992
+ block.kind,
4993
+ sdkConfig ?? null,
4994
+ customBlocks
4995
+ );
4989
4996
  if (sdkManifest) {
4990
4997
  return sdkManifest;
4991
4998
  }
4992
4999
  const blockDefinition = getBlockDefinition(block.kind);
4993
5000
  return blockDefinition?.manifest;
4994
- }, [block.kind, sdkConfig]);
5001
+ }, [block.kind, customBlocks, sdkConfig]);
4995
5002
  const [mounted, setMounted] = useState(false);
4996
5003
  const blockRef = useRef(null);
4997
5004
  useEffect(() => setMounted(true), []);
@@ -5063,360 +5070,439 @@ var SortableBlockItem = memo(function SortableBlockItem2({
5063
5070
  }
5064
5071
  );
5065
5072
  });
5066
- var BlockEditor = forwardRef(function BlockEditor2({
5067
- siteId,
5068
- pageId,
5069
- blocks,
5070
- recoveryConflictBlockIds = [],
5071
- recoveryConflictReviewRequestId = null,
5072
- apiClient,
5073
- uiStore,
5074
- enableReordering = true,
5075
- enableCreation = true,
5076
- onBlockSave,
5077
- onBlocksChange,
5078
- theme,
5079
- widgetOverrides,
5080
- headerLeftSlot,
5081
- hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2 = false,
5082
- pageDesignEditorModel = null,
5083
- enabledContentTypes = [],
5084
- sdkConfig = null,
5085
- reorderControlMode = DEFAULT_REORDER_CONTROL_MODE,
5086
- actionControlMode = DEFAULT_BLOCK_ACTION_CONTROL_MODE
5087
- }, ref) {
5088
- const [blockList, setBlockList] = useState(() => normalizeBlockListInitial(blocks));
5089
- const [errorMessage, setErrorMessage] = useState(null);
5090
- const [highlightedBlockId, setHighlightedBlockId] = useState(null);
5091
- const blockRefs = useRef(/* @__PURE__ */ new Map());
5092
- const highlightTimerRef = useRef(null);
5093
- const { blockFieldExtensions } = useSdkConfig();
5094
- const activeBlockId = useStoreSelector(uiStore, (state) => state.activeBlockId);
5095
- const setActiveBlockId = useCallback(
5096
- (id) => {
5097
- uiStore.getState().setActiveBlockId(id);
5098
- },
5099
- [uiStore]
5100
- );
5101
- const [fetchedContentTypes, setFetchedContentTypes] = useState(enabledContentTypes);
5102
- const [fetchingContentTypes, setFetchingContentTypes] = useState(false);
5103
- const allowReordering = enableReordering;
5104
- const allowCreation = enableCreation;
5105
- const conflictedBlockIdSet = useMemo(
5106
- () => new Set(recoveryConflictBlockIds),
5107
- [recoveryConflictBlockIds]
5108
- );
5109
- const blockIds = useMemo(
5110
- () => blockList.map((block) => block.id).filter((blockId) => typeof blockId === "string"),
5111
- [blockList]
5112
- );
5113
- const pageDesignBlockById = useMemo(
5114
- () => new Map(
5115
- (pageDesignEditorModel?.blocks ?? []).map((block) => [String(block.blockId), block])
5116
- ),
5117
- [pageDesignEditorModel]
5118
- );
5119
- const { isCollapsed, setCollapsed } = useBlockExpandState({
5073
+ var BlockEditor = forwardRef(
5074
+ function BlockEditor2({
5075
+ siteId,
5120
5076
  pageId,
5121
- blockIds
5122
- });
5123
- useEffect(() => {
5124
- setBlockList(normalizeBlockListInitial(blocks));
5125
- }, [blocks]);
5126
- useEffect(() => {
5127
- const unsubscribe = uiStore.subscribe(() => {
5128
- const structureList = uiStore.getState().structure.getList();
5129
- setBlockList((currentList) => {
5130
- const nextList = structureList.map((block) => ({ ...block }));
5131
- const hasChanges = currentList.length !== nextList.length || currentList.some((block, index) => {
5132
- const nextBlock = nextList[index];
5133
- 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;
5134
5155
  });
5135
- return hasChanges ? nextList : currentList;
5136
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
5137
5193
  });
5138
- return unsubscribe;
5139
- }, [uiStore]);
5140
- useEffect(() => {
5141
- let cancelled = false;
5142
- async function fetchContentTypesFromApi() {
5143
- if (fetchingContentTypes) return;
5144
- setFetchingContentTypes(true);
5145
- try {
5146
- const result = await listContentTypes(apiClient, { siteId });
5147
- if (!cancelled) {
5148
- const keys = (result.contentTypes || []).map(
5149
- (ct) => typeof ct === "string" ? ct : ct.key
5150
- );
5151
- 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();
5152
5210
  }
5153
- } catch (error) {
5154
- console.error("[block-editor] Failed to fetch content types:", error);
5155
- if (!cancelled) {
5156
- 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);
5157
5291
  }
5158
- } finally {
5159
- if (!cancelled) {
5160
- 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);
5161
5307
  }
5162
- }
5163
- }
5164
- fetchContentTypesFromApi();
5165
- return () => {
5166
- cancelled = true;
5167
- };
5168
- }, [siteId, apiClient, enabledContentTypes]);
5169
- useBlockBaselineSync({ enabled: true, blockList, blockFieldExtensions, uiStore });
5170
- const focusRequest = useStoreSelector(uiStore, (state) => state.focusRequest);
5171
- const triggerFocus = useCallback(
5172
- (target) => {
5173
- const { blockId, fieldPath, itemIndex } = target;
5174
- uiStore.getState().requestFocus({ blockId, fieldPath, itemIndex });
5175
- },
5176
- [uiStore]
5177
- );
5178
- const handleFocusHandled = useCallback(
5179
- (requestId) => {
5180
- const current = uiStore.getState().focusRequest;
5181
- if (current && current.requestId === requestId) {
5182
- uiStore.getState().clearFocus();
5183
- }
5184
- },
5185
- [uiStore]
5186
- );
5187
- useImperativeHandle(
5188
- ref,
5189
- () => ({
5190
- focusField: triggerFocus
5191
- }),
5192
- [triggerFocus]
5193
- );
5194
- const availableBlocks = useMemo(
5195
- () => mergeBlockDescriptors(listBlockDescriptors(), sdkConfig),
5196
- [sdkConfig]
5197
- );
5198
- const pointerSensor = useSensor(PointerSensor, { activationConstraint: { distance: 5 } });
5199
- const sensors = useSensors(pointerSensor);
5200
- const activeSensors = allowReordering ? sensors : [];
5201
- const { pickerOpen, creatingBlockName, openPicker, closePicker, handleCreateBlock } = useBlockCreation({
5202
- allowCreation,
5203
- apiClient,
5204
- siteId,
5205
- pageId,
5206
- blockList,
5207
- setBlockList,
5208
- onBlocksChange,
5209
- triggerFocus,
5210
- uiStore
5211
- });
5212
- const { reorderId, handleMove, onDragEnd } = useBlockReorder({
5213
- allowReordering,
5214
- apiClient,
5215
- siteId,
5216
- pageId,
5217
- blockList,
5218
- setBlockList,
5219
- onBlocksChange,
5220
- setErrorMessage,
5221
- uiStore
5222
- });
5223
- const { duplicatingBlockId, duplicateBlock } = useBlockDuplication({
5224
- allowCreation,
5225
- apiClient,
5226
- siteId,
5227
- pageId,
5228
- blockList,
5229
- setBlockList,
5230
- onBlocksChange,
5231
- triggerFocus,
5232
- setErrorMessage,
5233
- uiStore
5234
- });
5235
- const handleDelete = useCallback(async (blockId) => {
5236
- try {
5237
- setErrorMessage(null);
5238
- const removedBlock = blockList.find((block) => block.id === blockId);
5239
- if (!removedBlock) {
5308
+ };
5309
+ }, []);
5310
+ useEffect(() => {
5311
+ if (!focusRequest?.blockId) {
5240
5312
  return;
5241
5313
  }
5242
- await deleteBlock(apiClient, { siteId, pageId, blockId });
5243
- const updatedList = blockList.filter((b) => b.id !== blockId);
5244
- setBlockList(updatedList);
5245
- if (uiStore) {
5246
- uiStore.getState().history.applyCommand?.({
5247
- ...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"
5248
5322
  });
5249
- }
5250
- onBlocksChange?.(updatedList);
5251
- } catch (error) {
5252
- console.error("[BlockEditor] Delete failed:", error);
5253
- const message = error instanceof Error ? error.message : "Failed to delete block";
5254
- setErrorMessage(message);
5255
- }
5256
- }, [apiClient, siteId, pageId, blockList, setBlockList, onBlocksChange, uiStore]);
5257
- useEffect(() => {
5258
- return () => {
5323
+ }, 100);
5259
5324
  if (highlightTimerRef.current !== null) {
5260
5325
  window.clearTimeout(highlightTimerRef.current);
5261
5326
  }
5262
- };
5263
- }, []);
5264
- useEffect(() => {
5265
- if (!focusRequest?.blockId) {
5266
- return;
5267
- }
5268
- const focusedBlockId = String(focusRequest.blockId);
5269
- setCollapsed(focusedBlockId, false);
5270
- uiStore.getState().setActiveBlockId(focusRequest.blockId);
5271
- setHighlightedBlockId(focusedBlockId);
5272
- window.setTimeout(() => {
5273
- blockRefs.current.get(focusedBlockId)?.scrollIntoView({
5274
- behavior: "smooth",
5275
- block: "center"
5276
- });
5277
- }, 100);
5278
- if (highlightTimerRef.current !== null) {
5279
- window.clearTimeout(highlightTimerRef.current);
5280
- }
5281
- highlightTimerRef.current = window.setTimeout(() => {
5282
- setHighlightedBlockId((current) => current === focusedBlockId ? null : current);
5283
- highlightTimerRef.current = null;
5284
- }, 1500);
5285
- }, [focusRequest?.requestId, focusRequest?.blockId, setCollapsed, uiStore]);
5286
- useEffect(() => {
5287
- if (recoveryConflictReviewRequestId === null || recoveryConflictBlockIds.length === 0) {
5288
- return;
5289
- }
5290
- for (const blockId of recoveryConflictBlockIds) {
5291
- setCollapsed(blockId, false);
5292
- }
5293
- const firstRecoveryBlockId = recoveryConflictBlockIds[0];
5294
- if (!firstRecoveryBlockId) {
5295
- return;
5296
- }
5297
- uiStore.getState().requestFocus({ blockId: asEditorBlockId(firstRecoveryBlockId) });
5298
- }, [recoveryConflictBlockIds, recoveryConflictReviewRequestId, setCollapsed, uiStore]);
5299
- useEffect(() => {
5300
- if (!activeBlockId) {
5301
- return;
5302
- }
5303
- const stillExists = blockList.some(
5304
- (block) => typeof block.id === "string" && asEditorBlockId(block.id) === activeBlockId
5305
- );
5306
- if (!stillExists) {
5307
- uiStore.getState().setActiveBlockId(null);
5308
- }
5309
- }, [activeBlockId, blockList, uiStore]);
5310
- const hasBlocks = blockList.length > 0;
5311
- const contextValue = useMemo(
5312
- () => ({
5313
- blockList,
5314
- setBlockList,
5315
- reorderId,
5316
- focusRequest,
5317
- triggerFocus,
5318
- handleMove,
5319
- onDragEnd,
5320
- activeBlockId,
5321
- setActiveBlockId,
5322
- theme
5323
- }),
5324
- [blockList, reorderId, focusRequest, triggerFocus, handleMove, onDragEnd, activeBlockId, setActiveBlockId, theme]
5325
- );
5326
- return /* @__PURE__ */ jsx(BlockListProvider, { value: contextValue, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 touch-pan-y", children: [
5327
- /* @__PURE__ */ jsx(HeaderActions, { headerLeftSlot }),
5328
- errorMessage ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive bg-destructive/10 px-4 py-2 text-sm text-destructive", children: errorMessage }) : null,
5329
- hasBlocks ? /* @__PURE__ */ jsx(
5330
- DndContext,
5331
- {
5332
- sensors: activeSensors,
5333
- 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,
5334
5372
  onDragEnd,
5335
- children: /* @__PURE__ */ jsx(SortableContext, { items: blockList.map((b) => b.id), strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
5336
- allowCreation ? /* @__PURE__ */ jsx(AddBlockDivider, { onOpen: openPicker, position: 0 }) : null,
5337
- blockList.map((block, index) => {
5338
- const focusSignal = focusRequest && block.id && focusRequest.blockId === asEditorBlockId(block.id) ? focusRequest : null;
5339
- const collapsed = isCollapsed(block.id);
5340
- return /* @__PURE__ */ jsxs(
5341
- "div",
5342
- {
5343
- className: "group",
5344
- ref: (node) => {
5345
- if (!block.id) return;
5346
- if (node) {
5347
- blockRefs.current.set(block.id, node);
5348
- } else {
5349
- blockRefs.current.delete(block.id);
5350
- }
5351
- },
5352
- children: [
5353
- /* @__PURE__ */ jsx(
5354
- 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",
5355
5410
  {
5356
- siteId,
5357
- block,
5358
- onMove: handleMove,
5359
- onDuplicate: duplicateBlock,
5360
- onDelete: handleDelete,
5361
- isFirst: index === 0,
5362
- isLast: index === blockList.length - 1,
5363
- isMoving: reorderId === block.id || duplicatingBlockId === block.id,
5364
- isDraggable: allowReordering,
5365
- collapsed,
5366
- onCollapsedChange: (nextCollapsed) => {
5411
+ className: "group",
5412
+ ref: (node) => {
5367
5413
  if (!block.id) return;
5368
- setCollapsed(block.id, nextCollapsed);
5369
- if (nextCollapsed) {
5370
- if (activeBlockId === asEditorBlockId(block.id)) {
5371
- uiStore.getState().setActiveBlockId(null);
5372
- }
5373
- return;
5414
+ if (node) {
5415
+ blockRefs.current.set(block.id, node);
5416
+ } else {
5417
+ blockRefs.current.delete(block.id);
5374
5418
  }
5375
- uiStore.getState().setActiveBlockId(asEditorBlockId(block.id));
5376
5419
  },
5377
- highlighted: highlightedBlockId === block.id,
5378
- hasRecoveryConflict: typeof block.id === "string" && conflictedBlockIdSet.has(block.id),
5379
- focusSignal,
5380
- onFocusHandled: handleFocusHandled,
5381
- onBlockSave,
5382
- theme,
5383
- widgetOverrides,
5384
- uiStore,
5385
- sdkConfig,
5386
- hideThemeV2RawStyleFields: hideThemeV2RawStyleFields2,
5387
- pageDesignBlock: block.id ? pageDesignBlockById.get(block.id) ?? null : null,
5388
- reorderControlMode,
5389
- actionControlMode
5390
- }
5391
- ),
5392
- allowCreation && index < blockList.length - 1 ? /* @__PURE__ */ jsx(AddBlockDivider, { onOpen: openPicker, position: index + 1 }) : null
5393
- ]
5394
- },
5395
- block.id ?? index
5396
- );
5397
- })
5398
- ] }) })
5399
- }
5400
- ) : 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." }),
5401
- allowCreation && hasBlocks ? /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "secondary", onClick: () => openPicker(blockList.length), children: [
5402
- /* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
5403
- "Add block"
5404
- ] }) }) : null,
5405
- allowCreation ? /* @__PURE__ */ jsx(
5406
- EnhancedBlockPickerDialog,
5407
- {
5408
- open: pickerOpen,
5409
- onOpenChange: (open) => open ? openPicker(blockList.length) : closePicker(),
5410
- creatingBlockName,
5411
- descriptors: availableBlocks,
5412
- siteId,
5413
- existingBlockNames: blockList.map((b) => b.kind).filter(Boolean),
5414
- enabledContentTypes: fetchedContentTypes,
5415
- onSelect: (descriptor) => void handleCreateBlock(descriptor)
5416
- }
5417
- ) : null
5418
- ] }) });
5419
- });
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
+ );
5420
5506
  function PageBlockEditorPanel({
5421
5507
  siteId,
5422
5508
  pageId,
@@ -5743,12 +5829,19 @@ function buildApplySiteStyleSelectionBody(input) {
5743
5829
  buttonPersonalityId: input.previewed.buttonPersonalityId,
5744
5830
  paletteVariantId: input.previewed.paletteVariantId,
5745
5831
  paletteOverrides: input.previewed.paletteOverrides,
5832
+ typographyPresetId: input.previewed.typographyPresetId,
5833
+ typographyBodyFontOverride: input.previewed.typographyBodyFontOverride,
5834
+ typographyHeadingFontOverride: input.previewed.typographyHeadingFontOverride,
5746
5835
  headerLookId: input.previewed.headerLookId,
5836
+ headerLayoutId: input.previewed.headerLayoutId,
5837
+ headerStyleId: input.previewed.headerStyleId,
5747
5838
  footerLookId: input.previewed.footerLookId,
5748
5839
  baseSelectionVersion: input.base.selectionVersion,
5749
5840
  selectionId: input.selectionId ?? null
5750
5841
  };
5751
5842
  }
5843
+ var MAX_FONT_RESULTS_WHEN_SEARCHING = 12;
5844
+ var MAX_FONT_RESULTS_WHEN_BROWSING = 8;
5752
5845
  function StyleConfigurator({
5753
5846
  siteId,
5754
5847
  apiClient,
@@ -5763,7 +5856,14 @@ function StyleConfigurator({
5763
5856
  siteStyleAccess
5764
5857
  }) {
5765
5858
  const router = useRouter();
5766
- const { setOverrideTheme } = usePreviewThemeOverlay();
5859
+ const previewDesign = usePreviewDesignCoordinator();
5860
+ const {
5861
+ applyFailed,
5862
+ applyStarted,
5863
+ applySucceeded,
5864
+ clearPreview,
5865
+ previewTheme
5866
+ } = previewDesign;
5767
5867
  const [isRefreshPending, startRefreshTransition] = useTransition();
5768
5868
  const baseSnapshot = useMemo(
5769
5869
  () => resolveStyleConfiguratorInitialSnapshot({
@@ -5783,50 +5883,47 @@ function StyleConfigurator({
5783
5883
  );
5784
5884
  const hasLoadedState = state !== null;
5785
5885
  const stateRef = useRef(state);
5786
- const setOverrideThemeRef = useRef(setOverrideTheme);
5886
+ const clearPreviewRef = useRef(clearPreview);
5787
5887
  const runnerRef = useRef(createOptimisticActionRunner());
5788
5888
  const appliedExpandedRowRequestIdRef = useRef(null);
5789
5889
  useEffect(() => {
5790
5890
  stateRef.current = state;
5791
5891
  }, [state]);
5792
5892
  useEffect(() => {
5793
- setOverrideThemeRef.current = setOverrideTheme;
5794
- }, [setOverrideTheme]);
5893
+ clearPreviewRef.current = clearPreview;
5894
+ }, [clearPreview]);
5795
5895
  useEffect(() => {
5796
5896
  if (!baseSnapshot) return;
5797
5897
  setState((current) => current ? syncStyleConfiguratorState(current, baseSnapshot) : createStyleConfiguratorState(baseSnapshot));
5798
5898
  }, [baseSnapshot]);
5799
5899
  useEffect(() => {
5800
5900
  if (!state || !baseSnapshot) {
5801
- setOverrideTheme(null, { source: "style-configurator" });
5901
+ clearPreview("style-configurator");
5802
5902
  return;
5803
5903
  }
5804
5904
  if (!isPreviewingChange(state)) {
5805
- setOverrideTheme(null, { source: "style-configurator" });
5905
+ clearPreview("style-configurator");
5806
5906
  return;
5807
5907
  }
5808
5908
  const siteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
5809
5909
  if (!siteStyle) {
5810
- setOverrideTheme(null, { source: "style-configurator" });
5910
+ clearPreview("style-configurator");
5811
5911
  return;
5812
5912
  }
5813
- const result = compileSiteThemeFromSelections({
5913
+ const result = planSiteStylePreviewChange({
5814
5914
  siteStyle,
5815
- buttonPersonalityId: state.previewed.buttonPersonalityId,
5816
- paletteVariantId: state.previewed.paletteVariantId,
5817
- paletteOverrides: state.previewed.paletteOverrides,
5818
- headerLookId: state.previewed.headerLookId,
5819
- footerLookId: state.previewed.footerLookId,
5915
+ selection: state.previewed,
5916
+ baseTheme: theme,
5820
5917
  headerSelectionContext,
5821
5918
  footerSelectionContext
5822
5919
  });
5823
- setOverrideTheme(result.theme, { source: "style-configurator" });
5824
- }, [state, baseSnapshot, setOverrideTheme, headerSelectionContext, footerSelectionContext]);
5920
+ previewTheme("style-configurator", result.themeOverlay, "site-style");
5921
+ }, [state, baseSnapshot, clearPreview, previewTheme, headerSelectionContext, footerSelectionContext, theme]);
5825
5922
  useEffect(() => {
5826
5923
  const runner = runnerRef.current;
5827
5924
  return () => {
5828
5925
  runner.invalidate();
5829
- setOverrideThemeRef.current(null, { source: "style-configurator" });
5926
+ clearPreviewRef.current("style-configurator");
5830
5927
  };
5831
5928
  }, []);
5832
5929
  useEffect(() => {
@@ -5835,7 +5932,7 @@ function StyleConfigurator({
5835
5932
  setState((current) => {
5836
5933
  if (!current) return current;
5837
5934
  appliedExpandedRowRequestIdRef.current = expandedRowRequest.id;
5838
- return openRow(current, expandedRowRequest.row);
5935
+ return openRow(current, expandedRowRequest.row === "header" ? "header-layout" : expandedRowRequest.row);
5839
5936
  });
5840
5937
  }, [expandedRowRequest, hasLoadedState]);
5841
5938
  const handlePreviewSiteStyle = useCallback((siteStyleId) => {
@@ -5844,13 +5941,10 @@ function StyleConfigurator({
5844
5941
  const next = previewSiteStyle(current, siteStyleId);
5845
5942
  const siteStyle = getCuratedSiteStyle(siteStyleId);
5846
5943
  if (!siteStyle) return next;
5847
- const result = compileSiteThemeFromSelections({
5944
+ const result = planSiteStylePreviewChange({
5848
5945
  siteStyle,
5849
- buttonPersonalityId: next.previewed.buttonPersonalityId,
5850
- paletteVariantId: next.previewed.paletteVariantId,
5851
- paletteOverrides: next.previewed.paletteOverrides,
5852
- headerLookId: next.previewed.headerLookId,
5853
- footerLookId: next.previewed.footerLookId,
5946
+ selection: next.previewed,
5947
+ baseTheme: theme,
5854
5948
  headerSelectionContext,
5855
5949
  footerSelectionContext
5856
5950
  });
@@ -5859,14 +5953,17 @@ function StyleConfigurator({
5859
5953
  warnings: result.warnings
5860
5954
  });
5861
5955
  });
5862
- }, [footerSelectionContext, headerSelectionContext]);
5956
+ }, [footerSelectionContext, headerSelectionContext, theme]);
5863
5957
  const handlePreviewPersonality = useCallback((id) => {
5864
5958
  setState((current) => current ? previewButtonPersonality(current, id) : current);
5865
5959
  }, []);
5866
- const handlePreviewHeaderLook = useCallback((id) => {
5867
- const plan = planSiteChromeLookSelection({ surface: "header", lookId: id });
5868
- if (plan.kind !== "header") return;
5869
- 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);
5870
5967
  }, []);
5871
5968
  const handlePreviewFooterLook = useCallback((id) => {
5872
5969
  const plan = planSiteChromeLookSelection({ surface: "footer", lookId: id });
@@ -5876,6 +5973,18 @@ function StyleConfigurator({
5876
5973
  const handlePreviewPalette = useCallback((id) => {
5877
5974
  setState((current) => current ? previewPaletteVariant(current, id) : current);
5878
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
+ }, []);
5879
5988
  const handleTokenOverride = useCallback((role, hex) => {
5880
5989
  setState((current) => {
5881
5990
  if (!current) return current;
@@ -5906,13 +6015,10 @@ function StyleConfigurator({
5906
6015
  const previewed = live.previewed;
5907
6016
  const siteStyle = getCuratedSiteStyle(previewed.siteStyleId);
5908
6017
  if (!siteStyle) return;
5909
- const compiled = compileSiteThemeFromSelections({
6018
+ const compiled = planSiteStylePreviewChange({
5910
6019
  siteStyle,
5911
- buttonPersonalityId: previewed.buttonPersonalityId,
5912
- paletteVariantId: previewed.paletteVariantId,
5913
- paletteOverrides: previewed.paletteOverrides,
5914
- headerLookId: previewed.headerLookId,
5915
- footerLookId: previewed.footerLookId,
6020
+ selection: previewed,
6021
+ baseTheme: theme,
5916
6022
  headerSelectionContext,
5917
6023
  footerSelectionContext
5918
6024
  });
@@ -5921,12 +6027,19 @@ function StyleConfigurator({
5921
6027
  previewed: {
5922
6028
  ...previewed,
5923
6029
  buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
5924
- 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
5925
6037
  },
5926
6038
  selectionId
5927
6039
  });
5928
6040
  await runnerRef.current.run({
5929
6041
  optimistic: () => {
6042
+ applyStarted("style-configurator");
5930
6043
  setState((current) => current ? markApplySaving(current) : current);
5931
6044
  },
5932
6045
  network: async () => {
@@ -5946,15 +6059,22 @@ function StyleConfigurator({
5946
6059
  buttonPersonalityId: saved.buttonPersonalityId,
5947
6060
  paletteVariantId: saved.paletteVariantId,
5948
6061
  paletteOverrides: saved.paletteOverrides ?? null,
6062
+ typographyPresetId: saved.typographyPresetId,
6063
+ typographyBodyFontOverride: saved.typographyBodyFontOverride,
6064
+ typographyHeadingFontOverride: saved.typographyHeadingFontOverride,
5949
6065
  headerLookId: saved.headerLookId,
6066
+ headerLayoutId: saved.headerLayoutId,
6067
+ headerStyleId: saved.headerStyleId,
5950
6068
  footerLookId: saved.footerLookId
5951
6069
  }) : current
5952
6070
  );
6071
+ applySucceeded("style-configurator");
5953
6072
  startRefreshTransition(() => {
5954
6073
  router.refresh();
5955
6074
  });
5956
6075
  },
5957
6076
  onError: (error) => {
6077
+ applyFailed("style-configurator");
5958
6078
  const applyError = classifyStyleApplyError(error);
5959
6079
  if (applyError.kind === "stale") {
5960
6080
  startRefreshTransition(() => {
@@ -5969,10 +6089,14 @@ function StyleConfigurator({
5969
6089
  apiClient,
5970
6090
  footerSelectionContext,
5971
6091
  headerSelectionContext,
6092
+ applyFailed,
6093
+ applyStarted,
6094
+ applySucceeded,
5972
6095
  router,
5973
6096
  selectionId,
5974
6097
  siteId,
5975
- startRefreshTransition
6098
+ startRefreshTransition,
6099
+ theme
5976
6100
  ]);
5977
6101
  if (!state || !baseSnapshot) {
5978
6102
  return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading design controls..." });
@@ -5986,13 +6110,15 @@ function StyleConfigurator({
5986
6110
  }
5987
6111
  const currentPersonality = findButtonPersonality(state.previewed.buttonPersonalityId);
5988
6112
  const currentPaletteVariant = findPaletteVariant(state.previewed.paletteVariantId);
6113
+ const currentTypographyPreset = state.previewed.typographyPresetId ? findTypographyPreset(state.previewed.typographyPresetId) : null;
5989
6114
  const chromeLookModels = buildSiteChromeLookModels({
5990
6115
  state,
5991
6116
  siteStyle: currentSiteStyle,
5992
6117
  headerSelectionContext,
5993
6118
  footerSelectionContext
5994
6119
  });
5995
- 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);
5996
6122
  const currentFooterLook = state.previewed.footerLookId ? findFooterLook(state.previewed.footerLookId) : findFooterLook(chromeLookModels.footer.renderedLookId);
5997
6123
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
5998
6124
  state.warnings.length > 0 ? /* @__PURE__ */ jsx(WarningNotice, { warnings: state.warnings, onDismiss: handleDismissWarnings }) : null,
@@ -6010,22 +6136,42 @@ function StyleConfigurator({
6010
6136
  /* @__PURE__ */ jsx(
6011
6137
  SiteChromeLookRow,
6012
6138
  {
6013
- label: "Header",
6014
- ariaLabel: "Header",
6015
- defaultDescription: "Use the header chosen for this site style.",
6016
- catalog: headerLookCatalog,
6017
- expanded: state.expandedRow === "header",
6018
- onToggle: () => handleToggleRow("header"),
6019
- currentId: state.previewed.headerLookId,
6020
- currentLabel: state.previewed.headerLookId ? currentHeaderLook?.label ?? "Unknown header Look" : `${currentHeaderLook?.label ?? "Site style"} (default)`,
6021
- scopeDescription: "Changes the header on every page.",
6022
- options: chromeLookModels.header.options,
6023
- 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: [],
6024
6170
  onCycle: (direction) => {
6025
- const next = cycleSiteChromeLookId(chromeLookModels.header.options, state.previewed.headerLookId, direction);
6026
- if (next !== void 0) handlePreviewHeaderLook(next);
6171
+ const next = cycleSiteChromeLookId(chromeLookModels.headerStyle.options, state.previewed.headerStyleId, direction);
6172
+ if (next !== void 0) handlePreviewHeaderStyle(next);
6027
6173
  },
6028
- onPreview: handlePreviewHeaderLook,
6174
+ onPreview: handlePreviewHeaderStyle,
6029
6175
  disabled: saving
6030
6176
  }
6031
6177
  ),
@@ -6063,6 +6209,24 @@ function StyleConfigurator({
6063
6209
  disabled: saving
6064
6210
  }
6065
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
+ ),
6066
6230
  /* @__PURE__ */ jsx(
6067
6231
  PaletteRow,
6068
6232
  {
@@ -6154,6 +6318,407 @@ function PersonalityRow({ expanded, onToggle, siteStyle, currentId, currentLabel
6154
6318
  personality.id
6155
6319
  )) }) });
6156
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
+ }
6157
6722
  function SiteChromeLookRow({
6158
6723
  label,
6159
6724
  ariaLabel,
@@ -6166,7 +6731,7 @@ function SiteChromeLookRow({
6166
6731
  description: defaultDescription,
6167
6732
  available: true
6168
6733
  },
6169
- ...catalog.map((look) => ({ ...look, available: true }))
6734
+ ...(catalog ?? []).map((look) => ({ ...look, available: true }))
6170
6735
  ],
6171
6736
  diagnostics,
6172
6737
  scopeDescription,
@@ -6188,7 +6753,7 @@ function SiteChromeLookRow({
6188
6753
  type: "button",
6189
6754
  onClick: () => onCycle("prev"),
6190
6755
  className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
6191
- "aria-label": `Previous ${label.toLowerCase()} Look`,
6756
+ "aria-label": `Previous ${label.toLowerCase()}`,
6192
6757
  disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
6193
6758
  children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
6194
6759
  }
@@ -6199,7 +6764,7 @@ function SiteChromeLookRow({
6199
6764
  type: "button",
6200
6765
  onClick: () => onCycle("next"),
6201
6766
  className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
6202
- "aria-label": `Next ${label.toLowerCase()} Look`,
6767
+ "aria-label": `Next ${label.toLowerCase()}`,
6203
6768
  disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
6204
6769
  children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
6205
6770
  }
@@ -6369,6 +6934,8 @@ function describeWarning(warning) {
6369
6934
  return `Button style changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
6370
6935
  case "rejected-palette":
6371
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.`;
6372
6939
  default: {
6373
6940
  const exhaustive = warning;
6374
6941
  return exhaustive;
@@ -6393,6 +6960,11 @@ function PreviewEditorSidebar({
6393
6960
  themeSelectionButtonPersonalityId = null,
6394
6961
  themeSelectionPaletteVariantId = null,
6395
6962
  themeSelectionPaletteOverrides = null,
6963
+ themeSelectionHeaderLayoutId = null,
6964
+ themeSelectionHeaderStyleId = null,
6965
+ themeSelectionTypographyPresetId = null,
6966
+ themeSelectionTypographyBodyFontOverride = null,
6967
+ themeSelectionTypographyHeadingFontOverride = null,
6396
6968
  themeSelectionHeaderLookId = null,
6397
6969
  themeSelectionFooterLookId = null,
6398
6970
  themeSelectionHeaderContext,
@@ -6552,6 +7124,11 @@ function PreviewEditorSidebar({
6552
7124
  buttonPersonalityId: themeSelectionButtonPersonalityId,
6553
7125
  paletteVariantId: themeSelectionPaletteVariantId,
6554
7126
  paletteOverrides: themeSelectionPaletteOverrides,
7127
+ headerLayoutId: themeSelectionHeaderLayoutId,
7128
+ headerStyleId: themeSelectionHeaderStyleId,
7129
+ typographyPresetId: themeSelectionTypographyPresetId,
7130
+ typographyBodyFontOverride: themeSelectionTypographyBodyFontOverride,
7131
+ typographyHeadingFontOverride: themeSelectionTypographyHeadingFontOverride,
6555
7132
  headerLookId: themeSelectionHeaderLookId,
6556
7133
  footerLookId: themeSelectionFooterLookId
6557
7134
  },