@riverbankcms/sdk 0.79.0 → 0.80.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 (160) hide show
  1. package/dist/PreviewEditorSidebar-QZG37QIF.mjs +2655 -0
  2. package/dist/{PreviewEditorUI-27KJVEET.mjs → PreviewEditorUI-AXIKSMZA.mjs} +27 -4
  3. package/dist/SdkPreviewModeRuntime-XE5M4UKM.mjs +8 -0
  4. package/dist/_dts/ai/src/contracts/proposals.d.ts +20 -0
  5. package/dist/_dts/api/src/endpoints.d.ts +24 -0
  6. package/dist/_dts/api/src/index.d.ts +1 -1
  7. package/dist/_dts/api/src/siteInfrastructure.d.ts +124 -0
  8. package/dist/_dts/api/src/siteOnboarding.d.ts +13 -0
  9. package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +10 -1
  10. package/dist/_dts/block-form/src/client/index.d.ts +2 -0
  11. package/dist/_dts/block-form/src/index.d.ts +2 -0
  12. package/dist/_dts/block-form/src/utils/evaluateVisibility.d.ts +2 -1
  13. package/dist/_dts/blocks/src/client/header.d.ts +1 -1
  14. package/dist/_dts/blocks/src/client.d.ts +12 -12
  15. package/dist/_dts/blocks/src/index.d.ts +1 -1
  16. package/dist/_dts/blocks/src/system/blocks/events/event-listing.d.ts +2 -2
  17. package/dist/_dts/blocks/src/system/blocks/events/event-spotlight.d.ts +2 -1
  18. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +4 -1
  19. package/dist/_dts/blocks/src/system/blocks/testimonials.d.ts +7 -1
  20. package/dist/_dts/blocks/src/system/customize/backgrounds.d.ts +21 -12
  21. package/dist/_dts/blocks/src/system/fields/boxStyles.d.ts +4 -2
  22. package/dist/_dts/blocks/src/system/fragments/library/faqHeading.d.ts +1 -0
  23. package/dist/_dts/blocks/src/system/fragments/library/testimonialsCarousel.d.ts +5 -0
  24. package/dist/_dts/blocks/src/system/node/fragments/headingDecoration.d.ts +1 -4
  25. package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +1 -1
  26. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +7 -7
  27. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +3 -3
  28. package/dist/_dts/blocks/src/system/runtime/header/enhanceHeaderSection.d.ts +10 -2
  29. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventListing.interactive.d.ts +5 -5
  30. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventListing.server.d.ts +2 -2
  31. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventSpotlight.d.ts +2 -2
  32. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventCard.d.ts +2 -2
  33. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventDetailModal.d.ts +2 -2
  34. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventModals.d.ts +2 -2
  35. package/dist/_dts/blocks/src/system/runtime/nodes/header-section.client.d.ts +2 -2
  36. package/dist/_dts/blocks/src/system/transforms/registry/boxStyles.d.ts +3 -0
  37. package/dist/_dts/blocks/src/system/transforms/registry/layout.d.ts +9 -0
  38. package/dist/_dts/blocks/src/system/transforms/typed.d.ts +14 -1
  39. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +9 -3
  40. package/dist/_dts/editor-blocks/src/block-editor/BlockCard.d.ts +2 -6
  41. package/dist/_dts/editor-blocks/src/block-editor/BlockCardBody.d.ts +2 -6
  42. package/dist/_dts/editor-blocks/src/block-editor/BlockCardHeader.d.ts +2 -6
  43. package/dist/_dts/editor-blocks/src/block-editor/FeatureCardIconsPanel.d.ts +2 -6
  44. package/dist/_dts/editor-blocks/src/block-editor/GeneratedIconSetDialog.d.ts +6 -4
  45. package/dist/_dts/editor-blocks/src/block-editor/SortableBlockItem.d.ts +2 -6
  46. package/dist/_dts/editor-blocks/src/index.d.ts +2 -0
  47. package/dist/_dts/preview-next/src/client/blocks/BlockToolbar.d.ts +5 -1
  48. package/dist/_dts/preview-next/src/client/blocks/previewCustomizeControlPlan.d.ts +24 -0
  49. package/dist/_dts/preview-next/src/client/blocks/useBlockCustomizeControls.d.ts +34 -0
  50. package/dist/_dts/preview-next/src/client/preview/PageBlockEditorPanel.d.ts +2 -5
  51. package/dist/_dts/preview-next/src/client/preview/PageDesignEditorStateContext.d.ts +4 -1
  52. package/dist/_dts/preview-next/src/client/preview/PreviewBlockCustomizeSelectionContext.d.ts +20 -0
  53. package/dist/_dts/preview-next/src/client/preview/PreviewOpenEditorButton.d.ts +2 -0
  54. package/dist/_dts/preview-next/src/client/preview/SelectedBlockCustomizePanel.d.ts +13 -0
  55. package/dist/_dts/preview-next/src/client/preview/previewCustomizeEditorDraftPatch.d.ts +13 -0
  56. package/dist/_dts/preview-next/src/client/preview/previewCustomizeRuntimePatch.d.ts +2 -0
  57. package/dist/_dts/preview-next/src/client/preview/storeTypes.d.ts +4 -0
  58. package/dist/_dts/preview-next/src/client/widgets/widgetOverrides.d.ts +3 -1
  59. package/dist/_dts/sdk/src/cli/commands/webhooks.d.ts +44 -0
  60. package/dist/_dts/sdk/src/client/management/index.d.ts +1 -1
  61. package/dist/_dts/sdk/src/client/management/types.d.ts +56 -0
  62. package/dist/_dts/sdk/src/client/management/webhooks.d.ts +3 -0
  63. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +6 -3
  64. package/dist/_dts/sdk/src/version.d.ts +1 -1
  65. package/dist/_dts/theme-core/src/accordions/siteStyleDefaults.d.ts +220 -0
  66. package/dist/_dts/theme-core/src/accordions/types.d.ts +45 -2
  67. package/dist/_dts/theme-core/src/blockOverrides.d.ts +8 -0
  68. package/dist/_dts/theme-core/src/blocks/types.d.ts +1 -1
  69. package/dist/_dts/theme-core/src/blocks/vars/testimonials.d.ts +4 -1
  70. package/dist/_dts/theme-core/src/buttons/personalities/types.d.ts +1 -1
  71. package/dist/_dts/theme-core/src/cards/core.d.ts +20 -0
  72. package/dist/_dts/theme-core/src/cards/generateDefaultCardSystem.d.ts +6 -3
  73. package/dist/_dts/theme-core/src/cards/siteStyleDefaults.d.ts +105 -0
  74. package/dist/_dts/theme-core/src/cards/types.d.ts +177 -0
  75. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  76. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +156 -18
  77. package/dist/_dts/theme-core/src/palette/variants/index.d.ts +15 -0
  78. package/dist/_dts/theme-core/src/palette/variants/soft-natural.d.ts +16 -0
  79. package/dist/_dts/theme-core/src/palette/variants/types.d.ts +1 -1
  80. package/dist/_dts/theme-core/src/schema.d.ts +446 -52
  81. package/dist/_dts/theme-core/src/site-styles/appearancePresetCatalog.d.ts +86 -17
  82. package/dist/_dts/theme-core/src/site-styles/appearancePresetTypes.d.ts +2 -3
  83. package/dist/_dts/theme-core/src/site-styles/blockAppearancePresets.d.ts +86 -17
  84. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +110 -1
  85. package/dist/_dts/theme-core/src/site-styles/footerLooks.d.ts +20 -1
  86. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -0
  87. package/dist/_dts/theme-core/src/site-styles/headerLooks.d.ts +286 -6
  88. package/dist/_dts/theme-core/src/site-styles/index.d.ts +2 -0
  89. package/dist/_dts/theme-core/src/site-styles/logoBranding.d.ts +193 -0
  90. package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +2 -8
  91. package/dist/_dts/theme-core/src/site-styles/rawStyleFields.d.ts +2 -0
  92. package/dist/_dts/theme-core/src/site-styles/runtimeCompiler.d.ts +10 -7
  93. package/dist/_dts/theme-core/src/site-styles/siteStyleThemeCompiler.d.ts +11 -0
  94. package/dist/_dts/theme-core/src/site-styles/sourceCatalogs.d.ts +2 -2
  95. package/dist/_dts/theme-core/src/site-styles/styleTags.d.ts +1 -1
  96. package/dist/_dts/theme-core/src/site-styles/testimonialQuotePresets.d.ts +15 -0
  97. package/dist/_dts/theme-core/src/site-styles/tokenRecipes.d.ts +1 -1
  98. package/dist/_dts/theme-core/src/site-styles/types.d.ts +7 -29
  99. package/dist/{PreviewEditorSidebar-56NMJMIK.mjs → chunk-5NZGBSJF.mjs} +1423 -2451
  100. package/dist/{chunk-4HCW7FHJ.mjs → chunk-7PCYG6YV.mjs} +215 -137
  101. package/dist/{chunk-K2IIF34C.mjs → chunk-BBLZSQEY.mjs} +24 -0
  102. package/dist/{chunk-V47ZU33I.mjs → chunk-GYXKAUFV.mjs} +2785 -785
  103. package/dist/{chunk-GP555I66.mjs → chunk-HTB4BMKD.mjs} +94 -46
  104. package/dist/{chunk-A3HHFKVT.mjs → chunk-MPBNEVPB.mjs} +1 -1
  105. package/dist/cli/index.mjs +2100 -545
  106. package/dist/cli/init-docs/content/workflow-isr-revalidation.md +16 -4
  107. package/dist/client/{accordion-QLA7KBYO.mjs → accordion-QFZMNB7O.mjs} +1 -1
  108. package/dist/client/{bookingForm-PYZIPA5G.mjs → bookingForm-NCIXFWIG.mjs} +3 -3
  109. package/dist/client/bookings.mjs +1144 -329
  110. package/dist/client/{carousel-5EIULWBI.mjs → carousel-EO4BJMWD.mjs} +2 -2
  111. package/dist/client/{cart-DOP3FNI4.mjs → cart-FTYCLYUW.mjs} +5 -5
  112. package/dist/client/{checkout-BVMF6FBV.mjs → checkout-NFGLVCOX.mjs} +5 -5
  113. package/dist/client/{chunk-TYONZMST.mjs → chunk-4W66ALYT.mjs} +1 -1
  114. package/dist/client/{chunk-44K2U3QI.mjs → chunk-6GVVWSPU.mjs} +4 -4
  115. package/dist/client/{chunk-UDS3NTWA.mjs → chunk-IWFZSQNZ.mjs} +1 -1
  116. package/dist/client/{chunk-R5ESWNKX.mjs → chunk-PUOELU4O.mjs} +89 -5
  117. package/dist/client/{chunk-YLDYVRZL.mjs → chunk-PYRBIURC.mjs} +4 -4
  118. package/dist/client/{chunk-GG4M2NP6.mjs → chunk-T6UIP6UZ.mjs} +1 -1
  119. package/dist/client/{chunk-V6M5FCR5.mjs → chunk-VDS5YFLX.mjs} +1 -1
  120. package/dist/client/{chunk-CCNTDRCF.mjs → chunk-Y37KDO7P.mjs} +1 -1
  121. package/dist/client/client.mjs +4205 -2324
  122. package/dist/client/{courseRegistration-E6EAA6NL.mjs → courseRegistration-2EORROPS.mjs} +3 -3
  123. package/dist/client/{eventCalendar-HBG3HAK6.mjs → eventCalendar-WZI4VL6N.mjs} +5 -5
  124. package/dist/client/{eventCombined-6K5ZZWAK.mjs → eventCombined-QCB2TCNL.mjs} +5 -5
  125. package/dist/client/{eventDetails-Q75ICXLT.mjs → eventDetails-TFTATAPH.mjs} +5 -5
  126. package/dist/client/{eventListing-27P5F7DZ.mjs → eventListing-A2CQEHEQ.mjs} +5 -5
  127. package/dist/client/{eventRegistration-IQ7AMASR.mjs → eventRegistration-OELPVLUY.mjs} +5 -5
  128. package/dist/client/{form-JRTD65FN.mjs → form-NTIFD6WQ.mjs} +1 -1
  129. package/dist/client/{headerSection-7FPSSZHS.mjs → headerSection-7TBNKCLA.mjs} +65 -24
  130. package/dist/client/hooks.mjs +3354 -2006
  131. package/dist/client/{locationMap-7NGFNYBH.mjs → locationMap-LRLB5A3H.mjs} +1 -1
  132. package/dist/client/{newsletterForm-F3BADCI2.mjs → newsletterForm-XI52RZSN.mjs} +1 -1
  133. package/dist/client/{productDetail-V3HP7ITJ.mjs → productDetail-GSNKBFLY.mjs} +5 -5
  134. package/dist/client/{productList-L3EGJUF2.mjs → productList-BGCGT4KZ.mjs} +5 -5
  135. package/dist/client/rendering/client.mjs +2570 -1657
  136. package/dist/client/rendering/islands.mjs +18 -18
  137. package/dist/client/rendering.mjs +4275 -2394
  138. package/dist/client/{shop-DQUB5E2C.mjs → shop-RQHUCLNY.mjs} +5 -5
  139. package/dist/preview-next/before-render.mjs +24 -0
  140. package/dist/preview-next/client/runtime.mjs +5 -5
  141. package/dist/preview-next/middleware.mjs +24 -0
  142. package/dist/{sdk-runtime-AF6KXJU4.mjs → sdk-runtime-A4LHT2RS.mjs} +6 -6
  143. package/dist/server/components.mjs +4063 -2293
  144. package/dist/server/config-validation.mjs +3525 -2176
  145. package/dist/server/config.mjs +3525 -2176
  146. package/dist/server/data.mjs +3354 -2006
  147. package/dist/server/index.mjs +25 -1
  148. package/dist/server/next.mjs +4006 -2236
  149. package/dist/server/page-converter.mjs +2747 -1816
  150. package/dist/server/prebuild.mjs +1 -1
  151. package/dist/server/rendering/server.mjs +4063 -2293
  152. package/dist/server/rendering.mjs +4063 -2293
  153. package/dist/server/routing.mjs +3508 -2111
  154. package/dist/server/server.mjs +3372 -2024
  155. package/dist/server/theme-bridge.mjs +1436 -310
  156. package/dist/server/theme.mjs +409 -219
  157. package/dist/styles/index.css +300 -33
  158. package/package.json +1 -1
  159. package/dist/SdkPreviewModeRuntime-H4BSP6E7.mjs +0 -8
  160. package/dist/chunk-LCY32PXF.mjs +0 -1175
@@ -1,11 +1,241 @@
1
1
  "use client";
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-LCY32PXF.mjs';
3
- import { KeyboardCode, getBlockDefinition, CSS, useSdkConfig, unwrapResponse, parseSiteIconStyleGuideVersionId, parseBlockIconValue, listBlockDescriptors, useSensor, PointerSensor, useSensors, deleteBlock, createEditorRemoveBlockCommand, unsafeAsEditorBlockId, 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, Sparkles, RotateCcw, ImagePlus, Copy, Trash2, DialogFooter, DialogClose, CardContent, createEditorInsertBlockCommand, LoaderCircle, ChevronDown, getFeatureCardIconCompletion, toFeatureCardIconSuggestionInputs, getBlockIconSemanticFallbackId, suggestSemanticIconForFeatureCard, getGeneratedBlockIconStyleState, assertNever, suggestFeatureCardSemanticIcons, applyFeatureCardIconSuggestions, createFeatureCardIconKey, Textarea, Callout, CalloutDescription, normalEditorCuratedPromotedFieldPaths, resolveVisiblePromotedFieldPathsForEditorBlock, createStoreBackend, validateBlockManifestIssues, CalloutTitle, unfinishedBlockCopy, recoveryConflictCopy, ManifestForm, unsafeAsEditorBlockValues, Box, createManifestFormAdapter, getModalConfig, isNonEmptyValue, ScrollableDialogContent, FormRenderer, Tabs, TabsList, TabsTrigger, TabsContent, mergeManifestWithSdkOptions, augmentManifestWithStyleGroups, lucide_react_exports, isThemeV2NormalEditorRawStylePath, getFieldIntent, isHeaderField, isThemeV2NormalEditorRawStyleFieldName, useEntryController, useEditorUiStoreInstance, useUISelector, resolvePreviewSidebarTab, BackgroundGradientField, BackgroundColorField, X, omitRouteTitleFieldFromManifest, getRouteMetadataManifestForType, createManifestValidation, previewAssistantEnabled, useField, FieldFrame, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, resolveIconPickerPreset, isIconPickerPreset, getIconPickerOptionsForPreset, isKnownIconName, Combobox, getIconComponent, 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, resolveVideoSelectionIntentForMediaField, emitMediaCropDebug, Pencil, foldSubroute, SUBROUTE_KINDS, Smartphone, Tablet, Monitor, Maximize2, DropdownMenuLabel, expectParsedBrandedId, parseSiteId, buildThemeRuntime, PaletteProvider, MediaEditorProvider, curatedSiteStyles, ChevronLeft, requireEntriesForCuratedChoices, buttonPersonalities, typographyPresets, paletteVariants, resolveImageUrlWithContext, Root2, List, Trigger, Content, Input, Check, Label, isInternalResolvedLinkValue, evaluateTypographyFontSlot, Separator, classifyKnownMimeType, toRoutableLinkPayload, useMediaEditor, MediaPickerMini, MediaPickerDialog, Surface, ColorPicker, getDirectImageUrlForMediaAsset, isSvgMimeType } from './chunk-V47ZU33I.mjs';
4
- import { usePreviewAuth, getDashboardUrl } from './chunk-K2IIF34C.mjs';
5
- import './chunk-R7POPVJR.mjs';
6
- import React, { createContext, memo, useMemo, useState, useRef, useEffect, forwardRef, useCallback, useImperativeHandle, useContext, useSyncExternalStore, useLayoutEffect, useReducer, useTransition, useId } from 'react';
2
+ import { KeyboardCode, getBlockDefinition, CSS, useSdkConfig, unwrapResponse, parseSiteIconStyleGuideVersionId, parseBlockIconValue, listBlockDescriptors, useSensor, PointerSensor, useSensors, deleteBlock, createEditorRemoveBlockCommand, unsafeAsEditorBlockId, 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, Sparkles, RotateCcw, ImagePlus, Copy, Trash2, DialogFooter, DialogClose, CardContent, createEditorInsertBlockCommand, LoaderCircle, ChevronDown, getFeatureCardIconCompletion, toFeatureCardIconSuggestionInputs, getBlockIconSemanticFallbackId, suggestSemanticIconForFeatureCard, getGeneratedBlockIconStyleState, assertNever, suggestFeatureCardSemanticIcons, applyFeatureCardIconSuggestions, createFeatureCardIconKey, Textarea, Callout, CalloutDescription, normalEditorCuratedPromotedFieldPaths, resolveVisiblePromotedFieldPathsForEditorBlock, createStoreBackend, validateBlockManifestIssues, CalloutTitle, unfinishedBlockCopy, recoveryConflictCopy, ManifestForm, unsafeAsEditorBlockValues, Box, createManifestFormAdapter, getModalConfig, isNonEmptyValue, ScrollableDialogContent, FormRenderer, Tabs, TabsList, TabsTrigger, TabsContent, mergeManifestWithSdkOptions, augmentManifestWithStyleGroups, lucide_react_exports, isThemeV2NormalEditorRawStylePath, getFieldIntent, isHeaderField, isThemeV2NormalEditorRawStyleFieldName, useField, useMediaEditorOptional, useRichTextEditor, FieldFrame, RichTextEditorSurface, ThemeScope, isObjectRecord, EMPTY_DOC, useMediaEditor, MediaPickerMini, MediaPickerDialog, useAsyncPickerOptions, resolvePickerSelectionLabel, shouldShowPickerSearch, normalizePickerSearchThreshold, Combobox, Badge, useBlockContext, resolveMediaSlotContract, asRecord, parseAspectRatioValue, MediaEditor, PUBLIC_FILE_FIELD_KINDS, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, useBackendWatch, resolveIconPickerPreset, isIconPickerPreset, getIconPickerOptionsForPreset, isKnownIconName, getIconComponent, assertNever2, deriveNearestCompatibleHeaderStyle, findLegacyHeaderLookIdForSelection, compileSiteThemeFromSelections, footerLookCatalog, getOrderedHeaderStyleIdsForLayout, getOrderedHeaderLayoutIdsForCuration, footerLookRejectionReasonLabel, getHeaderStyle, getHeaderLayout, writePlanForLookCustomizationCommand, siteStyleIdFromTheme, getCuratedSiteStyle, getRichTextImagePositionFromTarget, insertOrReplaceRichTextImage, emitMediaCropDebug, Pencil, resolveVideoSelectionIntentForMediaField, evaluateVisibility, Root2, List, Trigger, Content, Input, Check, Label, isInternalResolvedLinkValue, resolveImageUrlWithContext, getDirectImageUrlForMediaAsset, isSvgMimeType, toRoutableLinkPayload, classifyKnownMimeType } from './chunk-GYXKAUFV.mjs';
3
+ import React2, { createContext, memo, useMemo, useState, useRef, useEffect, forwardRef, useCallback, useImperativeHandle, useContext, useSyncExternalStore, useLayoutEffect, useReducer } from 'react';
7
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { useRouter } from 'next/navigation';
5
+
6
+ // ../preview-next/src/client/preview/previewChromeSurfacePolicy.ts
7
+ var PREVIEW_CHROME_SURFACE_POLICIES = {
8
+ core: {
9
+ banner: {
10
+ tone: "default",
11
+ bannerClassName: "border border-emerald-200/70 bg-white/95 shadow-xl backdrop-blur supports-[backdrop-filter]:bg-white/92",
12
+ summaryClassName: "rounded-md border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-600",
13
+ dialogItemClassName: "rounded-md border border-slate-200 bg-slate-50 px-3 py-2",
14
+ titleClassName: "text-sm font-semibold tracking-tight text-foreground [font-family:var(--font-heading)]",
15
+ descriptionClassName: "text-sm text-muted-foreground [font-family:var(--font-body)]",
16
+ detailClassName: "text-xs text-muted-foreground [font-family:var(--font-body)]",
17
+ summaryTitleClassName: "font-medium text-foreground [font-family:var(--font-body)]",
18
+ summaryBodyClassName: "mt-1 text-muted-foreground [font-family:var(--font-body)]",
19
+ secondaryButtonClassName: "",
20
+ primaryButtonClassName: "",
21
+ dialogBodyClassName: "space-y-3 text-sm text-muted-foreground [font-family:var(--font-body)]",
22
+ dialogItemTitleClassName: "font-medium text-foreground [font-family:var(--font-heading)]",
23
+ dialogItemMetaClassName: "ml-2 text-xs font-normal uppercase tracking-wide text-muted-foreground [font-family:var(--font-body)]",
24
+ dialogItemDescriptionClassName: "mt-1 text-sm text-muted-foreground [font-family:var(--font-body)]",
25
+ ctaLabel: "Complete setup in dashboard",
26
+ copyByState: {
27
+ awaiting_confirmation: {
28
+ title: "Your draft preview is ready",
29
+ description: "Explore your site here, then confirm your email to unlock the dashboard and finish launch setup.",
30
+ detail: "Once your email is confirmed, Complete setup in dashboard will take you straight to the remaining checklist.",
31
+ checklistNote: "Email confirmation is the only blocker before dashboard access. The rest of the launch checklist lives in the dashboard."
32
+ },
33
+ dashboard_ready: {
34
+ title: "Your dashboard is unlocked",
35
+ description: "Keep exploring here, then use Complete setup in dashboard to review your pages, add offerings, connect payments, and finish launch setup.",
36
+ detail: "The dashboard is the place to complete the remaining checklist and get your site ready to launch.",
37
+ checklistNote: "The remaining launch steps are tracked in the same checklist you will see in the dashboard."
38
+ },
39
+ dashboard_resume: {
40
+ title: "Your draft site is ready",
41
+ description: "Keep exploring here, then use Complete setup in dashboard to review your pages, add offerings, connect payments, and finish launch setup.",
42
+ detail: "The dashboard is the normal place to finish setup for this site.",
43
+ checklistNote: "The remaining launch steps are tracked in the same checklist you will see in the dashboard."
44
+ }
45
+ },
46
+ dialog: {
47
+ title: "What still happens in the dashboard",
48
+ description: "Your preview is ready, but launch steps still happen in the dashboard so you can finish setup safely.",
49
+ pendingIntro: "Your preview is live now, but the launch checklist stays in the dashboard so the final setup steps happen in the right place.",
50
+ unlockedIntro: "Your preview is live now, and the same checklist will follow you into the dashboard when you are ready to finish setup.",
51
+ outro: "You can keep exploring this preview now, then jump back into the dashboard when you're ready to finish setup."
52
+ }
53
+ },
54
+ sidebar: {
55
+ tone: "default",
56
+ rootClassName: "bg-background",
57
+ headerClassName: "border-b",
58
+ guidance: {
59
+ title: "Make quick edits in preview",
60
+ description: "Use preview for copy and layout tweaks. Complete setup in dashboard to add offerings, connect payments, and finish the rest of your launch checklist.",
61
+ ctaLabel: "Complete setup in dashboard",
62
+ cardClassName: "rounded-lg border border-emerald-200/70 bg-emerald-50/70 px-3 py-3"
63
+ }
64
+ }
65
+ },
66
+ macadamia: {
67
+ banner: {
68
+ tone: "studio",
69
+ bannerClassName: "border border-border bg-background shadow-xl",
70
+ summaryClassName: "rounded-xl border border-border bg-surface px-3 py-2 text-xs text-foreground",
71
+ dialogItemClassName: "rounded-xl border border-border bg-surface px-3 py-2",
72
+ titleClassName: "text-sm font-semibold tracking-tight text-foreground [font-family:var(--font-heading)]",
73
+ descriptionClassName: "text-sm text-muted-foreground [font-family:var(--font-body)]",
74
+ detailClassName: "text-xs text-muted-foreground [font-family:var(--font-body)]",
75
+ summaryTitleClassName: "font-medium text-foreground [font-family:var(--font-body)]",
76
+ summaryBodyClassName: "mt-1 text-muted-foreground [font-family:var(--font-body)]",
77
+ secondaryButtonClassName: "border border-border bg-surface text-foreground hover:bg-button-outline-hover",
78
+ primaryButtonClassName: "border border-primary bg-primary text-primary-foreground hover:bg-primary-hover",
79
+ dialogBodyClassName: "space-y-3 text-sm text-muted-foreground [font-family:var(--font-body)]",
80
+ dialogItemTitleClassName: "font-medium text-foreground [font-family:var(--font-heading)]",
81
+ dialogItemMetaClassName: "ml-2 text-xs font-normal uppercase tracking-wide text-muted-foreground [font-family:var(--font-body)]",
82
+ dialogItemDescriptionClassName: "mt-1 text-sm text-muted-foreground [font-family:var(--font-body)]",
83
+ ctaLabel: "Complete setup in Macadamia",
84
+ copyByState: {
85
+ awaiting_confirmation: {
86
+ title: "Confirm your email to carry on",
87
+ description: "We've created the bones of your site. To carry on finishing setup, and to edit your site, you need to confirm your email to activate your account.",
88
+ detail: "",
89
+ checklistNote: ""
90
+ },
91
+ dashboard_ready: {
92
+ title: "Here's your site preview",
93
+ description: "We've created the bones of your site. Have a look around. If you like, you can add your site content here, or you can go straight to your dashboard to complete your setup. You'll be able to edit your site at any time, so don't worry if it's not perfect right now.",
94
+ detail: "",
95
+ checklistNote: ""
96
+ },
97
+ dashboard_resume: {
98
+ title: "Your studio site is ready",
99
+ description: "Keep exploring here, then use Complete setup in Macadamia to review your pages, add offerings, connect payments, and finish launch setup.",
100
+ detail: "Macadamia is the normal place to finish setup for this studio site.",
101
+ checklistNote: "The remaining launch steps are tracked in the same checklist you will see in Macadamia."
102
+ }
103
+ },
104
+ dialog: {
105
+ title: "What still happens in Macadamia",
106
+ description: "Your preview is ready, but launch steps still happen in Macadamia so you can finish setup safely.",
107
+ pendingIntro: "Your studio preview is live now, but the launch checklist stays in Macadamia so the final setup steps happen in the right place.",
108
+ unlockedIntro: "Your studio preview is live now, and the same checklist will follow you into Macadamia when you are ready to finish setup.",
109
+ outro: "You can keep exploring this preview now, then jump back into Macadamia when you're ready to finish setup."
110
+ }
111
+ },
112
+ sidebar: {
113
+ tone: "studio",
114
+ rootClassName: "bg-stone-50/80",
115
+ headerClassName: "border-b border-amber-200/70 bg-amber-50/70",
116
+ guidance: {
117
+ title: "Polish your studio preview",
118
+ description: "Use preview for copy and layout tweaks. Complete setup in Macadamia to add offerings, connect payments, and finish the rest of your launch checklist.",
119
+ ctaLabel: "Complete setup in Macadamia",
120
+ cardClassName: "rounded-2xl border border-amber-200/80 bg-gradient-to-br from-amber-50 via-white to-orange-50 px-4 py-4 shadow-sm"
121
+ }
122
+ }
123
+ }
124
+ };
125
+ function getPreviewChromeSurfacePolicy(surfaceId) {
126
+ return PREVIEW_CHROME_SURFACE_POLICIES[surfaceId];
127
+ }
128
+ function resolvePreviewSetupBannerModel(input) {
129
+ if (!input.bannerState) {
130
+ return null;
131
+ }
132
+ const policy = getPreviewChromeSurfacePolicy(input.surfaceId).banner;
133
+ return {
134
+ tone: policy.tone,
135
+ bannerClassName: policy.bannerClassName,
136
+ summaryClassName: policy.summaryClassName,
137
+ dialogItemClassName: policy.dialogItemClassName,
138
+ titleClassName: policy.titleClassName,
139
+ descriptionClassName: policy.descriptionClassName,
140
+ detailClassName: policy.detailClassName,
141
+ summaryTitleClassName: policy.summaryTitleClassName,
142
+ summaryBodyClassName: policy.summaryBodyClassName,
143
+ secondaryButtonClassName: policy.secondaryButtonClassName,
144
+ primaryButtonClassName: policy.primaryButtonClassName,
145
+ dialogBodyClassName: policy.dialogBodyClassName,
146
+ dialogItemTitleClassName: policy.dialogItemTitleClassName,
147
+ dialogItemMetaClassName: policy.dialogItemMetaClassName,
148
+ dialogItemDescriptionClassName: policy.dialogItemDescriptionClassName,
149
+ ctaLabel: policy.ctaLabel,
150
+ copy: policy.copyByState[input.bannerState],
151
+ dialogTitle: policy.dialog.title,
152
+ dialogDescription: policy.dialog.description,
153
+ dialogIntro: input.dashboardUnlocked ? policy.dialog.unlockedIntro : policy.dialog.pendingIntro,
154
+ dialogOutro: policy.dialog.outro
155
+ };
156
+ }
157
+ function resolvePreviewSidebarChromeModel(surfaceId) {
158
+ const policy = getPreviewChromeSurfacePolicy(surfaceId).sidebar;
159
+ return {
160
+ tone: policy.tone,
161
+ rootClassName: policy.rootClassName,
162
+ headerClassName: policy.headerClassName
163
+ };
164
+ }
165
+ function resolvePreviewEditorGuidanceModel(input) {
166
+ if (!input.canEdit || !input.hasDashboardSetupHandoff) {
167
+ return null;
168
+ }
169
+ const policy = getPreviewChromeSurfacePolicy(input.surfaceId).sidebar;
170
+ return {
171
+ tone: policy.tone,
172
+ title: policy.guidance.title,
173
+ description: policy.guidance.description,
174
+ ctaLabel: policy.guidance.ctaLabel,
175
+ cardClassName: policy.guidance.cardClassName
176
+ };
177
+ }
178
+
179
+ // ../preview-next/src/client/preview/sanitizeDashboardPath.ts
180
+ function sanitizeDashboardPath(value) {
181
+ if (!value) return null;
182
+ const trimmed = value.trim();
183
+ if (!trimmed || /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
184
+ return null;
185
+ }
186
+ const normalized = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
187
+ try {
188
+ const parsed = new URL(normalized, "https://preview.local");
189
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`;
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+
195
+ // ../preview-next/src/client/preview/previewDashboardHandoff.ts
196
+ function resolvePreviewDashboardSetupHandoffFromSearch(search) {
197
+ const params = new URLSearchParams(search);
198
+ if (params.get("preview_handoff") !== "dashboard_setup") {
199
+ return null;
200
+ }
201
+ const dashboardPath = sanitizeDashboardPath(params.get("preview_dashboard"));
202
+ if (!dashboardPath) {
203
+ return null;
204
+ }
205
+ return {
206
+ kind: "dashboard_setup",
207
+ dashboardPath
208
+ };
209
+ }
210
+ function resolvePreviewDashboardSetupHandoff() {
211
+ if (typeof window === "undefined") {
212
+ return null;
213
+ }
214
+ return resolvePreviewDashboardSetupHandoffFromSearch(window.location.search);
215
+ }
216
+ function buildPreviewDashboardSetupUrl(dashboardBaseUrl, handoff) {
217
+ if (!dashboardBaseUrl || !handoff) {
218
+ return null;
219
+ }
220
+ return new URL(handoff.dashboardPath, dashboardBaseUrl).toString();
221
+ }
222
+
223
+ // ../preview-next/src/client/preview/siteStyleAccess.ts
224
+ function resolveSiteStyleAccess(session) {
225
+ if (session?.kind === "userSession" && session.canManage) {
226
+ return { kind: "apply-allowed" };
227
+ }
228
+ if (session?.kind === "userSession" && session.canEdit) {
229
+ return { kind: "preview-only" };
230
+ }
231
+ return { kind: "hidden" };
232
+ }
233
+ function canInspectSiteStyle(access) {
234
+ return access.kind !== "hidden";
235
+ }
236
+ function canApplySiteStyle(access) {
237
+ return access.kind === "apply-allowed";
238
+ }
9
239
 
10
240
  // ../content-model/src/referenceFields.ts
11
241
  var BUILT_IN_REFERENCE_KINDS = [
@@ -47,7 +277,7 @@ function parseReferenceKind(referenceKind) {
47
277
  }
48
278
 
49
279
  // ../content-model/src/contentManifest.ts
50
- function assertNever2(value) {
280
+ function assertNever3(value) {
51
281
  throw new Error(`Unhandled content field mapping decision: ${JSON.stringify(value)}`);
52
282
  }
53
283
  function resolveContentReferenceKind(field) {
@@ -178,7 +408,7 @@ function toFieldDefinition(field, context = {}) {
178
408
  valueFrom: mapping.valueFrom
179
409
  };
180
410
  default:
181
- return assertNever2(mapping);
411
+ return assertNever3(mapping);
182
412
  }
183
413
  }
184
414
  case "link":
@@ -317,65 +547,35 @@ function normalizeFieldUi(value) {
317
547
  }
318
548
  return value;
319
549
  }
320
- var ButtonWidgetContext = createContext(null);
321
- function ButtonWidgetProvider({ buttonSystem, children }) {
322
- const value = useMemo(() => {
323
- const variants = buttonSystem ? buttonSystem.variants.filter((v) => v.enabled).map((v) => ({
324
- id: v.id,
325
- name: v.name,
326
- priority: v.priority
327
- })).sort((a, b) => a.priority - b.priority) : [];
328
- const defaultVariant = buttonSystem?.variants.find((v) => v.priority === 1)?.id ?? null;
329
- return {
330
- variants,
331
- defaultVariant,
332
- buttonSystem
333
- };
334
- }, [buttonSystem]);
335
- return /* @__PURE__ */ jsx(ButtonWidgetContext.Provider, { value, children });
336
- }
337
- function useButtonWidgetContext() {
338
- const context = useContext(ButtonWidgetContext);
339
- if (!context) {
340
- throw new Error("useButtonWidgetContext must be used within ButtonWidgetProvider");
341
- }
342
- return context;
343
- }
344
- var DEFAULT_VARIANTS = [
345
- { id: "primary", name: "Primary", priority: 1 },
346
- { id: "secondary", name: "Secondary", priority: 2 },
347
- { id: "outline", name: "Outline", priority: 3 }
348
- ];
349
- function ButtonVariantField({ path, backend, config }) {
350
- const { value, error, setValue } = useField(path, backend);
351
- const buttonContext = useButtonWidgetContext();
352
- const variants = buttonContext.variants.length > 0 ? buttonContext.variants : DEFAULT_VARIANTS;
353
- const options = variants.map((v) => ({ value: v.id, label: v.name }));
354
- const isUnknownVariant = value && !options.some((opt) => opt.value === value);
355
- if (isUnknownVariant && true) {
356
- console.warn(
357
- `[ButtonVariantField] Unknown button variant "${value}" at path "${path}". Available variants: ${options.map((o) => o.value).join(", ")}. The button may not render with expected styling.`
358
- );
359
- }
360
- return /* @__PURE__ */ jsx(
361
- FieldFrame,
362
- {
363
- label: config.label,
364
- description: config.description,
365
- error: error?.message,
366
- children: () => /* @__PURE__ */ jsxs(
367
- Select,
368
- {
369
- value: typeof value === "string" && value !== "" ? value : void 0,
370
- onValueChange: setValue,
371
- children: [
372
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: `Select ${config.label.toLowerCase()}` }) }),
373
- /* @__PURE__ */ jsx(SelectContent, { children: options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
374
- ]
375
- }
376
- )
377
- }
550
+ var PreviewBlockCustomizeSelectionContext = createContext(null);
551
+ function PreviewBlockCustomizeSelectionProvider({
552
+ children
553
+ }) {
554
+ const [selection, setSelection] = useState({
555
+ kind: "none"
556
+ });
557
+ const inspectBlock = useCallback((blockId) => {
558
+ setSelection((current) => ({
559
+ kind: "selected",
560
+ blockId,
561
+ requestId: current.kind === "selected" ? current.requestId + 1 : 1
562
+ }));
563
+ }, []);
564
+ const clearInspection = useCallback(() => {
565
+ setSelection({ kind: "none" });
566
+ }, []);
567
+ const value = useMemo(
568
+ () => ({
569
+ selection,
570
+ inspectBlock,
571
+ clearInspection
572
+ }),
573
+ [clearInspection, inspectBlock, selection]
378
574
  );
575
+ return /* @__PURE__ */ jsx(PreviewBlockCustomizeSelectionContext.Provider, { value, children });
576
+ }
577
+ function usePreviewBlockCustomizeSelectionOptional() {
578
+ return useContext(PreviewBlockCustomizeSelectionContext);
379
579
  }
380
580
  function useStoreSelector(store, selector, isEqual = Object.is) {
381
581
  const cacheRef = useRef(null);
@@ -513,7 +713,7 @@ function getItemGap$1(clientRects, index, activeIndex) {
513
713
  return nextRect ? nextRect.top - (currentRect.top + currentRect.height) : previousRect ? currentRect.top - (previousRect.top + previousRect.height) : 0;
514
714
  }
515
715
  var ID_PREFIX = "Sortable";
516
- var Context = /* @__PURE__ */ React.createContext({
716
+ var Context = /* @__PURE__ */ React2.createContext({
517
717
  activeIndex: -1,
518
718
  containerId: ID_PREFIX,
519
719
  disableTransforms: false,
@@ -575,7 +775,7 @@ function SortableContext(_ref) {
575
775
  // eslint-disable-next-line react-hooks/exhaustive-deps
576
776
  [activeIndex, containerId, disabled.draggable, disabled.droppable, disableTransforms, items, overIndex, droppableRects, useDragOverlay, strategy]
577
777
  );
578
- return React.createElement(Context.Provider, {
778
+ return React2.createElement(Context.Provider, {
579
779
  value: contextValue
580
780
  }, children);
581
781
  }
@@ -2485,16 +2685,30 @@ function GeneratedIconSetDialog({
2485
2685
  }) {
2486
2686
  const [state, dispatch] = useReducer(reducer, { kind: "idle" });
2487
2687
  const [styleNote, setStyleNote] = useState("");
2688
+ const activeGenerationRef = useRef(null);
2488
2689
  const canGenerate = Boolean(onGenerate) && cards.length > 0 && state.kind !== "generating";
2489
2690
  const canApply = Boolean(onApply) && state.kind === "preview_ready";
2691
+ function abortActiveGeneration() {
2692
+ activeGenerationRef.current?.abort();
2693
+ activeGenerationRef.current = null;
2694
+ }
2695
+ useEffect(() => () => {
2696
+ activeGenerationRef.current?.abort();
2697
+ activeGenerationRef.current = null;
2698
+ }, []);
2490
2699
  async function generate() {
2491
2700
  if (!onGenerate || cards.length === 0) return;
2701
+ abortActiveGeneration();
2702
+ const generation = new AbortController();
2703
+ activeGenerationRef.current = generation;
2492
2704
  dispatch({ type: "generating" });
2493
2705
  try {
2494
2706
  const result = await onGenerate({
2495
2707
  cards,
2496
- userStyleNote: styleNote.trim().length > 0 ? styleNote.trim() : null
2708
+ userStyleNote: styleNote.trim().length > 0 ? styleNote.trim() : null,
2709
+ signal: generation.signal
2497
2710
  });
2711
+ if (activeGenerationRef.current !== generation || generation.signal.aborted) return;
2498
2712
  if (result.kind === "preview_ready") {
2499
2713
  const validation = validateGeneratedIconSetPreview(result.preview, cards);
2500
2714
  if (validation.kind === "invalid") {
@@ -2506,10 +2720,16 @@ function GeneratedIconSetDialog({
2506
2720
  }
2507
2721
  dispatch({ type: "failed", message: result.message });
2508
2722
  } catch (error) {
2723
+ if (activeGenerationRef.current !== generation || generation.signal.aborted) return;
2509
2724
  dispatch({ type: "failed", message: getGenerationErrorMessage(error) });
2725
+ } finally {
2726
+ if (activeGenerationRef.current === generation) {
2727
+ activeGenerationRef.current = null;
2728
+ }
2510
2729
  }
2511
2730
  }
2512
2731
  function close(nextOpen) {
2732
+ if (!nextOpen) abortActiveGeneration();
2513
2733
  onOpenChange(nextOpen);
2514
2734
  if (!nextOpen) dispatch({ type: "reset" });
2515
2735
  }
@@ -3684,7 +3904,8 @@ var BlockEditor = forwardRef(
3684
3904
  bodyText: card.bodyText,
3685
3905
  semanticIconId: card.semanticIconId
3686
3906
  }))
3687
- }
3907
+ },
3908
+ options: input.signal ? { signal: input.signal } : void 0
3688
3909
  }));
3689
3910
  const assets = response.preview.assets.map((asset) => {
3690
3911
  const iconValue = parseBlockIconValue(asset.iconValue);
@@ -4101,6 +4322,22 @@ var BlockEditor = forwardRef(
4101
4322
  ] }) });
4102
4323
  }
4103
4324
  );
4325
+ var LinkWidgetContext = createContext(null);
4326
+ function LinkWidgetProvider({
4327
+ children,
4328
+ siteId,
4329
+ adapters,
4330
+ initialItems
4331
+ }) {
4332
+ return /* @__PURE__ */ jsx(LinkWidgetContext.Provider, { value: { siteId, adapters, initialItems }, children });
4333
+ }
4334
+ function useLinkWidgetContext() {
4335
+ const ctx = useContext(LinkWidgetContext);
4336
+ if (!ctx) {
4337
+ return {};
4338
+ }
4339
+ return ctx;
4340
+ }
4104
4341
 
4105
4342
  // ../editor-blocks/src/widgets/link/utils.ts
4106
4343
  function toInternalLinkValue(item) {
@@ -4530,22 +4767,6 @@ function LinkWidget({
4530
4767
  )
4531
4768
  ] }) }) });
4532
4769
  }
4533
- var LinkWidgetContext = createContext(null);
4534
- function LinkWidgetProvider({
4535
- children,
4536
- siteId,
4537
- adapters,
4538
- initialItems
4539
- }) {
4540
- return /* @__PURE__ */ jsx(LinkWidgetContext.Provider, { value: { siteId, adapters, initialItems }, children });
4541
- }
4542
- function useLinkWidgetContext() {
4543
- const ctx = useContext(LinkWidgetContext);
4544
- if (!ctx) {
4545
- return {};
4546
- }
4547
- return ctx;
4548
- }
4549
4770
  function LinkField({ path, backend, config }) {
4550
4771
  const { value, setValue } = useField(path, backend);
4551
4772
  const { siteId, adapters, initialItems } = useLinkWidgetContext();
@@ -5375,7 +5596,66 @@ function filenameExtension(filename) {
5375
5596
  if (dotIndex < 0 || dotIndex === filename.length - 1) return null;
5376
5597
  return filename.slice(dotIndex + 1).trim().toLowerCase() || null;
5377
5598
  }
5378
- createContext(null);
5599
+ var ButtonWidgetContext = createContext(null);
5600
+ function ButtonWidgetProvider({ buttonSystem, children }) {
5601
+ const value = useMemo(() => {
5602
+ const variants = buttonSystem ? buttonSystem.variants.filter((v) => v.enabled).map((v) => ({
5603
+ id: v.id,
5604
+ name: v.name,
5605
+ priority: v.priority
5606
+ })).sort((a, b) => a.priority - b.priority) : [];
5607
+ const defaultVariant = buttonSystem?.variants.find((v) => v.priority === 1)?.id ?? null;
5608
+ return {
5609
+ variants,
5610
+ defaultVariant,
5611
+ buttonSystem
5612
+ };
5613
+ }, [buttonSystem]);
5614
+ return /* @__PURE__ */ jsx(ButtonWidgetContext.Provider, { value, children });
5615
+ }
5616
+ function useButtonWidgetContext() {
5617
+ const context = useContext(ButtonWidgetContext);
5618
+ if (!context) {
5619
+ throw new Error("useButtonWidgetContext must be used within ButtonWidgetProvider");
5620
+ }
5621
+ return context;
5622
+ }
5623
+ var DEFAULT_VARIANTS = [
5624
+ { id: "primary", name: "Primary", priority: 1 },
5625
+ { id: "secondary", name: "Secondary", priority: 2 },
5626
+ { id: "outline", name: "Outline", priority: 3 }
5627
+ ];
5628
+ function ButtonVariantField({ path, backend, config }) {
5629
+ const { value, error, setValue } = useField(path, backend);
5630
+ const buttonContext = useButtonWidgetContext();
5631
+ const variants = buttonContext.variants.length > 0 ? buttonContext.variants : DEFAULT_VARIANTS;
5632
+ const options = variants.map((v) => ({ value: v.id, label: v.name }));
5633
+ const isUnknownVariant = value && !options.some((opt) => opt.value === value);
5634
+ if (isUnknownVariant && true) {
5635
+ console.warn(
5636
+ `[ButtonVariantField] Unknown button variant "${value}" at path "${path}". Available variants: ${options.map((o) => o.value).join(", ")}. The button may not render with expected styling.`
5637
+ );
5638
+ }
5639
+ return /* @__PURE__ */ jsx(
5640
+ FieldFrame,
5641
+ {
5642
+ label: config.label,
5643
+ description: config.description,
5644
+ error: error?.message,
5645
+ children: () => /* @__PURE__ */ jsxs(
5646
+ Select,
5647
+ {
5648
+ value: typeof value === "string" && value !== "" ? value : void 0,
5649
+ onValueChange: setValue,
5650
+ children: [
5651
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: `Select ${config.label.toLowerCase()}` }) }),
5652
+ /* @__PURE__ */ jsx(SelectContent, { children: options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
5653
+ ]
5654
+ }
5655
+ )
5656
+ }
5657
+ );
5658
+ }
5379
5659
  var EntryPickerWidgetContext = createContext(null);
5380
5660
  function EntryPickerWidgetProvider({
5381
5661
  siteId,
@@ -5534,2482 +5814,1174 @@ function IconPickerField({ path, backend, config }) {
5534
5814
  }
5535
5815
  ) });
5536
5816
  }
5817
+ createContext(null);
5537
5818
 
5538
- // ../editor-blocks/src/widgets/entry-picker/loaders.ts
5539
- function getStatusBadgeVariant(status) {
5540
- switch (status) {
5541
- case "published":
5542
- return "default";
5543
- case "draft":
5544
- return "secondary";
5545
- case "scheduled":
5546
- return "outline";
5547
- case "archived":
5548
- return "destructive";
5549
- default:
5550
- return "secondary";
5551
- }
5552
- }
5553
- function mapEntryPickerItems(entries, valueFrom) {
5554
- return entries.map((entry) => ({
5555
- value: valueFrom === "identifier" ? entry.identifier ?? entry.id : valueFrom === "slug" ? entry.slug ?? entry.id : entry.id,
5556
- label: entry.draftTitle || entry.title || entry.slug || entry.id,
5557
- id: entry.id,
5558
- identifier: entry.identifier ?? null,
5559
- slug: entry.slug ?? null,
5560
- badge: {
5561
- label: entry.status,
5562
- variant: getStatusBadgeVariant(entry.status)
5563
- }
5564
- }));
5565
- }
5566
- async function loadEntryPickerItems({
5567
- apiClient,
5568
- siteId,
5569
- contentType,
5570
- valueFrom
5571
- }) {
5572
- const result = await apiClient({
5573
- endpoint: "listContentEntries",
5574
- params: { siteId, type: contentType }
5575
- });
5576
- const { entries } = unwrapResponse(result);
5577
- return mapEntryPickerItems(entries, valueFrom);
5819
+ // ../preview-next/src/client/utils/rfc6902.ts
5820
+ function cloneJson(value) {
5821
+ if (typeof structuredClone === "function") {
5822
+ return structuredClone(value);
5823
+ }
5824
+ return JSON.parse(JSON.stringify(value));
5578
5825
  }
5579
-
5580
- // ../editor-blocks/src/widgets/link/loaders.ts
5581
- async function loadRoutableContentItems({
5582
- apiClient,
5583
- siteId
5584
- }) {
5585
- const result = await apiClient({
5586
- endpoint: "getRoutableContent",
5587
- params: { siteId }
5588
- });
5589
- const payload = unwrapResponse(result);
5590
- return Array.isArray(payload?.items) ? payload.items : [];
5826
+ function decodePointerSegment(segment) {
5827
+ return segment.replace(/~1/g, "/").replace(/~0/g, "~");
5591
5828
  }
5592
-
5593
- // ../editor-blocks/src/widgets/reference/dynamicAdapters.ts
5594
- function createDynamicReferenceAdapters(staticAdapters, createDynamicContentAdapter) {
5595
- return new Proxy(staticAdapters, {
5596
- get(target, prop) {
5597
- if (typeof prop !== "string") {
5598
- return void 0;
5599
- }
5600
- if (prop in target) {
5601
- return target[prop];
5602
- }
5603
- const parsed = parseReferenceKind(prop);
5604
- if (parsed.kind === "dynamicContent") {
5605
- return createDynamicContentAdapter(parsed.contentTypeKey);
5606
- }
5607
- return void 0;
5608
- }
5609
- });
5829
+ function splitPointer(pointer) {
5830
+ if (pointer === "") return [];
5831
+ if (!pointer.startsWith("/")) {
5832
+ throw new Error(`Invalid JSON pointer (must start with "/"): ${pointer}`);
5833
+ }
5834
+ return pointer.split("/").slice(1).map((seg) => decodePointerSegment(seg));
5610
5835
  }
5611
-
5612
- // ../editor-blocks/src/widgets/reference/loaders.ts
5613
- function defineReferenceLoader(referenceKind, loader) {
5614
- return Object.assign(loader, { referenceKind });
5836
+ function isJsonObject(value) {
5837
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5615
5838
  }
5616
- function formatStatusBadge(status) {
5617
- if (status === "published") {
5618
- return { label: "Published", variant: "default" };
5619
- }
5620
- if (status === "scheduled") {
5621
- return { label: "Scheduled", variant: "secondary" };
5839
+ function getContainerAndKey(root, pointer) {
5840
+ const segments = splitPointer(pointer);
5841
+ if (segments.length === 0) {
5842
+ throw new Error("Patching the root value is not supported");
5622
5843
  }
5623
- if (status === "archived") {
5624
- return { label: "Archived", variant: "outline" };
5844
+ const key = segments[segments.length - 1];
5845
+ if (key === void 0) {
5846
+ throw new Error("Invalid path (missing key)");
5625
5847
  }
5626
- return { label: "Draft", variant: "outline" };
5627
- }
5628
- function hasSlug(entry) {
5629
- return entry.slug !== null;
5630
- }
5631
- var loadPostReferenceItems = defineReferenceLoader("post", async ({ apiClient, siteId }) => {
5632
- const result = await apiClient({
5633
- endpoint: "listContentEntries",
5634
- params: { siteId, type: "post" }
5635
- });
5636
- const { entries } = unwrapResponse(result);
5637
- return entries.filter(hasSlug).filter((entry) => entry.status === "published").map((entry) => ({
5638
- value: entry.slug,
5639
- label: entry.title || entry.slug || entry.id,
5640
- badge: formatStatusBadge(entry.status)
5641
- }));
5642
- });
5643
- var loadFormReferenceItems = defineReferenceLoader("form", async ({ apiClient, siteId }) => {
5644
- const result = await apiClient({
5645
- endpoint: "listForms",
5646
- params: { siteId }
5647
- });
5648
- const { forms } = unwrapResponse(result);
5649
- return forms.map((form) => ({
5650
- value: form.slug,
5651
- label: form.name
5652
- }));
5653
- });
5654
- var loadBookingFormReferenceItems = defineReferenceLoader("bookingForm", async ({ apiClient, siteId }) => {
5655
- const result = await apiClient({
5656
- endpoint: "listBookingForms",
5657
- params: { siteId }
5658
- });
5659
- const { forms } = unwrapResponse(result);
5660
- return forms.map((form) => ({
5661
- value: form.id,
5662
- label: form.name
5663
- }));
5664
- });
5665
- var loadAppointmentServiceReferenceItems = defineReferenceLoader(
5666
- "appointmentService",
5667
- async ({ apiClient, siteId }) => {
5668
- const result = await apiClient({
5669
- endpoint: "getAppointmentServicesReference",
5670
- params: { siteId }
5671
- });
5672
- const { options } = unwrapResponse(result);
5673
- return options.map((option) => ({
5674
- value: option.id,
5675
- label: option.label
5676
- }));
5677
- }
5678
- );
5679
- var loadAppointmentResourceReferenceItems = defineReferenceLoader(
5680
- "appointmentResource",
5681
- async ({ apiClient, siteId }) => {
5682
- const result = await apiClient({
5683
- endpoint: "getAppointmentResourcesReference",
5684
- params: { siteId }
5685
- });
5686
- const { options } = unwrapResponse(result);
5687
- return options.map((option) => ({
5688
- value: option.id,
5689
- label: option.label
5690
- }));
5691
- }
5692
- );
5693
- var loadPageReferenceItems = defineReferenceLoader("page", async ({ apiClient, siteId }) => {
5694
- const result = await apiClient({
5695
- endpoint: "getRoutableContent",
5696
- params: { siteId }
5697
- });
5698
- const { items } = unwrapResponse(result);
5699
- return items.filter((item) => item.entityType === "page").map((page) => ({
5700
- value: page.entityId,
5701
- label: page.title || "Untitled"
5702
- }));
5703
- });
5704
- var loadNewsletterListReferenceItems = defineReferenceLoader(
5705
- "newsletterList",
5706
- async ({ apiClient, siteId }) => {
5707
- const result = await apiClient({
5708
- endpoint: "listNewsletterLists",
5709
- params: { siteId }
5710
- });
5711
- const { lists } = unwrapResponse(result);
5712
- return lists.map((list) => ({
5713
- value: list.id,
5714
- label: list.name
5715
- }));
5716
- }
5717
- );
5718
- async function loadDynamicContentReferenceItems({
5719
- apiClient,
5720
- siteId,
5721
- contentTypeKey
5722
- }) {
5723
- const result = await apiClient({
5724
- endpoint: "listContentEntries",
5725
- params: { siteId, type: contentTypeKey }
5726
- });
5727
- const { entries } = unwrapResponse(result);
5728
- return entries.filter((entry) => entry.status === "published").map((entry) => ({
5729
- value: entry.id,
5730
- label: entry.title || entry.slug || entry.id,
5731
- badge: { label: "Published", variant: "default" }
5732
- }));
5733
- }
5734
- var referenceLoaders = {
5735
- post: loadPostReferenceItems,
5736
- form: loadFormReferenceItems,
5737
- bookingForm: loadBookingFormReferenceItems,
5738
- appointmentService: loadAppointmentServiceReferenceItems,
5739
- appointmentResource: loadAppointmentResourceReferenceItems,
5740
- page: loadPageReferenceItems,
5741
- newsletterList: loadNewsletterListReferenceItems
5742
- };
5743
-
5744
- // ../preview-next/src/client/widgets/widgetAdapters.ts
5745
- var routableContentCache = /* @__PURE__ */ new Map();
5746
- var entryPickerCache = /* @__PURE__ */ new Map();
5747
- var referenceItemsCache = /* @__PURE__ */ new Map();
5748
- var dynamicReferenceItemsCache = /* @__PURE__ */ new Map();
5749
- var MAX_CACHE_ENTRIES = 50;
5750
- function setBoundedCache(map, key, value) {
5751
- if (!map.has(key) && map.size >= MAX_CACHE_ENTRIES) {
5752
- const firstKey = map.keys().next().value;
5753
- if (firstKey !== void 0) {
5754
- map.delete(firstKey);
5848
+ let container = root;
5849
+ for (const segment of segments.slice(0, -1)) {
5850
+ if (container == null) {
5851
+ throw new Error(`Invalid path: ${pointer}`);
5755
5852
  }
5756
- }
5757
- map.set(key, value);
5758
- }
5759
- function createCachedDynamicContentReferenceAdapter(contentTypeKey, previewClient, fallbackSiteId) {
5760
- return {
5761
- labels: {
5762
- loading: "Loading entries\u2026",
5763
- select: "Select an entry",
5764
- empty: "No entries found",
5765
- emptySelection: "Select an entry to continue."
5766
- },
5767
- loadItems: async ({ siteId }) => {
5768
- const resolvedSiteId = siteId || fallbackSiteId;
5769
- if (!resolvedSiteId) {
5770
- return [];
5853
+ if (Array.isArray(container)) {
5854
+ const index = Number.parseInt(segment, 10);
5855
+ if (!Number.isFinite(index) || String(index) !== segment) {
5856
+ throw new Error(`Invalid array index "${segment}" in path: ${pointer}`);
5771
5857
  }
5772
- const cacheKey = `${resolvedSiteId}:content:${contentTypeKey}`;
5773
- const cached = dynamicReferenceItemsCache.get(cacheKey);
5774
- if (cached) return cached;
5775
- const promise = (async () => {
5776
- try {
5777
- return await loadDynamicContentReferenceItems({
5778
- apiClient: previewClient,
5779
- siteId: resolvedSiteId,
5780
- contentTypeKey
5781
- });
5782
- } catch (error) {
5783
- dynamicReferenceItemsCache.delete(cacheKey);
5784
- throw error;
5785
- }
5786
- })();
5787
- setBoundedCache(dynamicReferenceItemsCache, cacheKey, promise);
5788
- return promise;
5789
- }
5790
- };
5791
- }
5792
- function createCachedReferenceAdapterLoader(kind, loader, previewClient, fallbackSiteId) {
5793
- return async ({ siteId }) => {
5794
- const resolvedSiteId = siteId || fallbackSiteId;
5795
- if (!resolvedSiteId) {
5796
- return [];
5858
+ container = container[index];
5859
+ continue;
5797
5860
  }
5798
- const cacheKey = `${kind}:${resolvedSiteId}`;
5799
- const cached = referenceItemsCache.get(cacheKey);
5800
- if (cached) return cached;
5801
- const promise = (async () => {
5802
- try {
5803
- return await loader({ apiClient: previewClient, siteId: resolvedSiteId });
5804
- } catch (error) {
5805
- referenceItemsCache.delete(cacheKey);
5806
- throw error;
5807
- }
5808
- })();
5809
- setBoundedCache(referenceItemsCache, cacheKey, promise);
5810
- return promise;
5811
- };
5812
- }
5813
- function createPreviewLinkAdapters(previewClient) {
5814
- return {
5815
- fetchRoutableContent: async (currentSiteId) => {
5816
- const cached = routableContentCache.get(currentSiteId);
5817
- if (cached) return cached;
5818
- const promise = (async () => {
5819
- try {
5820
- return await loadRoutableContentItems({ apiClient: previewClient, siteId: currentSiteId });
5821
- } catch (error) {
5822
- routableContentCache.delete(currentSiteId);
5823
- throw error;
5824
- }
5825
- })();
5826
- setBoundedCache(routableContentCache, currentSiteId, promise);
5827
- return promise;
5861
+ if (isJsonObject(container)) {
5862
+ container = container[segment];
5863
+ continue;
5828
5864
  }
5829
- };
5865
+ throw new Error(`Invalid container for path: ${pointer}`);
5866
+ }
5867
+ return { container, key };
5830
5868
  }
5831
- function createPreviewEntryPickerAdapter(previewClient) {
5832
- return {
5833
- loadItems: async ({ siteId, contentType, valueFrom }) => {
5834
- if (!siteId || !contentType) return [];
5835
- const cacheKey = `${siteId}:${contentType}:${valueFrom ?? "id"}`;
5836
- const cached = entryPickerCache.get(cacheKey);
5837
- if (cached) return cached;
5838
- const request = (async () => {
5839
- try {
5840
- return await loadEntryPickerItems({
5841
- apiClient: previewClient,
5842
- siteId,
5843
- contentType,
5844
- valueFrom
5845
- });
5846
- } catch (error) {
5847
- entryPickerCache.delete(cacheKey);
5848
- throw error;
5849
- }
5850
- })();
5851
- setBoundedCache(entryPickerCache, cacheKey, request);
5852
- return request;
5853
- },
5854
- labels: {
5855
- loading: "Loading entries...",
5856
- select: "Select an entry",
5857
- empty: "No entries found",
5858
- emptySelection: "Select an entry to embed.",
5859
- noContentType: "Select a content type first."
5869
+ function applyAdd(container, key, value, pointer) {
5870
+ if (Array.isArray(container)) {
5871
+ if (key === "-") {
5872
+ container.push(value);
5873
+ return;
5860
5874
  }
5861
- };
5862
- }
5863
- var previewReferenceChrome = {
5864
- post: {
5865
- labels: {
5866
- loading: "Loading posts\u2026",
5867
- select: "Select a post",
5868
- empty: "No posts found",
5869
- emptySelection: "Select a post to continue."
5875
+ const index = Number.parseInt(key, 10);
5876
+ if (!Number.isFinite(index) || String(index) !== key) {
5877
+ throw new Error(`Invalid array index "${key}" in path: ${pointer}`);
5870
5878
  }
5871
- },
5872
- form: {
5873
- labels: {
5874
- loading: "Loading forms\u2026",
5875
- select: "Select a form",
5876
- empty: "No forms found",
5877
- emptySelection: "Select a form to continue."
5879
+ if (index < 0 || index > container.length) {
5880
+ throw new Error(`Array index out of bounds "${key}" in path: ${pointer}`);
5878
5881
  }
5879
- },
5880
- bookingForm: {
5881
- labels: {
5882
- loading: "Loading booking forms\u2026",
5883
- select: "Select a booking form",
5884
- empty: "No booking forms yet",
5885
- emptySelection: "Select a booking form to enable appointment booking."
5882
+ container.splice(index, 0, value);
5883
+ return;
5884
+ }
5885
+ if (isJsonObject(container)) {
5886
+ container[key] = value;
5887
+ return;
5888
+ }
5889
+ throw new Error(`Invalid container for path: ${pointer}`);
5890
+ }
5891
+ function applyReplace(container, key, value, pointer) {
5892
+ if (Array.isArray(container)) {
5893
+ const index = Number.parseInt(key, 10);
5894
+ if (!Number.isFinite(index) || String(index) !== key) {
5895
+ throw new Error(`Invalid array index "${key}" in path: ${pointer}`);
5886
5896
  }
5887
- },
5888
- appointmentService: {
5889
- labels: {
5890
- loading: "Loading appointment services\u2026",
5891
- select: "Select a service",
5892
- empty: "No services available",
5893
- emptySelection: "Select a service to enable booking."
5897
+ if (index < 0 || index >= container.length) {
5898
+ throw new Error(`Array index out of bounds "${key}" in path: ${pointer}`);
5894
5899
  }
5895
- },
5896
- appointmentResource: {
5897
- labels: {
5898
- loading: "Loading resources\u2026",
5899
- select: "Select a resource",
5900
- empty: "No resources available",
5901
- emptySelection: "Select a resource (optional)."
5900
+ container[index] = value;
5901
+ return;
5902
+ }
5903
+ if (isJsonObject(container)) {
5904
+ if (!(key in container)) {
5905
+ throw new Error(`Cannot replace missing key "${key}" in path: ${pointer}`);
5902
5906
  }
5903
- },
5904
- page: {
5905
- labels: {
5906
- loading: "Loading pages\u2026",
5907
- select: "Select a page",
5908
- empty: "No pages available",
5909
- emptySelection: "Select a page."
5907
+ container[key] = value;
5908
+ return;
5909
+ }
5910
+ throw new Error(`Invalid container for path: ${pointer}`);
5911
+ }
5912
+ function applyRemove(container, key, pointer) {
5913
+ if (Array.isArray(container)) {
5914
+ const index = Number.parseInt(key, 10);
5915
+ if (!Number.isFinite(index) || String(index) !== key) {
5916
+ throw new Error(`Invalid array index "${key}" in path: ${pointer}`);
5910
5917
  }
5911
- },
5912
- newsletterList: {
5913
- labels: {
5914
- loading: "Loading lists\u2026",
5915
- select: "Select a list",
5916
- empty: "No lists yet",
5917
- emptySelection: "Select a newsletter list to subscribe users to."
5918
+ if (index < 0 || index >= container.length) {
5919
+ throw new Error(`Array index out of bounds "${key}" in path: ${pointer}`);
5918
5920
  }
5921
+ container.splice(index, 1);
5922
+ return;
5919
5923
  }
5920
- };
5921
- function createPreviewReferenceAdapters(previewClient, fallbackSiteId) {
5922
- const brandedFallbackSiteId = void 0;
5923
- const adapters = {};
5924
- for (const kind of BUILT_IN_REFERENCE_KINDS) {
5925
- adapters[kind] = {
5926
- loadItems: createCachedReferenceAdapterLoader(
5927
- kind,
5928
- referenceLoaders[kind],
5929
- previewClient,
5930
- brandedFallbackSiteId
5931
- ),
5932
- labels: previewReferenceChrome[kind].labels
5933
- };
5924
+ if (isJsonObject(container)) {
5925
+ if (!(key in container)) {
5926
+ throw new Error(`Cannot remove missing key "${key}" in path: ${pointer}`);
5927
+ }
5928
+ delete container[key];
5929
+ return;
5934
5930
  }
5935
- return createDynamicReferenceAdapters(
5936
- adapters,
5937
- (contentTypeKey) => createCachedDynamicContentReferenceAdapter(contentTypeKey, previewClient, brandedFallbackSiteId)
5931
+ throw new Error(`Invalid container for path: ${pointer}`);
5932
+ }
5933
+ function applyRfc6902Patch(base, ops) {
5934
+ const next = cloneJson(base);
5935
+ for (const op of ops) {
5936
+ const { container, key } = getContainerAndKey(next, op.path);
5937
+ if (op.op === "add") {
5938
+ applyAdd(container, key, op.value, op.path);
5939
+ continue;
5940
+ }
5941
+ if (op.op === "replace") {
5942
+ applyReplace(container, key, op.value, op.path);
5943
+ continue;
5944
+ }
5945
+ applyRemove(container, key, op.path);
5946
+ }
5947
+ return next;
5948
+ }
5949
+ var PreviewThemeOverlayContext = createContext(null);
5950
+ var NO_THEME_OVERLAY = { kind: "none" };
5951
+ function isDebugEnabled() {
5952
+ return process.env.NEXT_PUBLIC_AI_ASSISTANT_DEBUG === "1";
5953
+ }
5954
+ function PreviewThemeOverlayProvider({ baseTheme, children }) {
5955
+ const [overlay, setOverlay] = useState(NO_THEME_OVERLAY);
5956
+ const overlayOps = overlay.kind === "patch" ? [...overlay.ops] : null;
5957
+ const overlayOpsSource = overlay.kind === "patch" ? overlay.source : null;
5958
+ const overrideTheme = overlay.kind === "theme" ? overlay.theme : null;
5959
+ const overrideThemeSource = overlay.kind === "theme" ? overlay.source : null;
5960
+ const setOverlayOps = useCallback(
5961
+ (ops, options) => {
5962
+ setOverlay((current) => {
5963
+ if (ops === null) {
5964
+ if (current.kind !== "patch") return current;
5965
+ if (options?.source && current.source !== options.source) return current;
5966
+ return NO_THEME_OVERLAY;
5967
+ }
5968
+ return {
5969
+ kind: "patch",
5970
+ source: options?.source ?? "unknown",
5971
+ ops
5972
+ };
5973
+ });
5974
+ },
5975
+ []
5938
5976
  );
5939
- }
5940
- function PreviewFormProviderInner({ theme, children }) {
5941
- const { onPickImage, picker } = useRichTextImagePicker();
5942
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5943
- /* @__PURE__ */ jsx(RichTextWidgetProvider, { theme, onPickImage, children }),
5944
- picker
5945
- ] });
5946
- }
5947
- function PreviewFormProvider({
5948
- siteId: rawSiteId,
5949
- theme = null,
5950
- previewClient,
5951
- bearerToken,
5952
- supabaseUrl,
5953
- mediaAssets,
5954
- children
5955
- }) {
5956
- const siteId = useMemo(() => expectParsedBrandedId(parseSiteId(rawSiteId)), [rawSiteId]);
5957
- const linkAdapters = useMemo(() => createPreviewLinkAdapters(previewClient), [previewClient]);
5958
- const entryPickerAdapter = useMemo(() => createPreviewEntryPickerAdapter(previewClient), [previewClient]);
5959
- const referenceAdapters = useMemo(
5960
- () => createPreviewReferenceAdapters(previewClient),
5961
- [previewClient]
5977
+ const setOverrideTheme = useCallback(
5978
+ (theme, options) => {
5979
+ setOverlay((current) => {
5980
+ if (theme === null) {
5981
+ if (current.kind !== "theme") return current;
5982
+ if (options?.source && current.source !== options.source) return current;
5983
+ return NO_THEME_OVERLAY;
5984
+ }
5985
+ return {
5986
+ kind: "theme",
5987
+ source: options?.source ?? "unknown",
5988
+ theme
5989
+ };
5990
+ });
5991
+ },
5992
+ []
5962
5993
  );
5963
- const paletteTokens = useMemo(() => {
5964
- if (!theme) return void 0;
5994
+ const clearOverlay = useCallback((options) => {
5995
+ setOverlay((current) => {
5996
+ const source = options?.source;
5997
+ if (current.kind === "none") return current;
5998
+ if (source && current.source !== source) return current;
5999
+ return NO_THEME_OVERLAY;
6000
+ });
6001
+ }, []);
6002
+ const effectiveTheme = useMemo(() => {
6003
+ if (overlay.kind === "theme") return overlay.theme;
6004
+ if (overlay.kind !== "patch" || overlay.ops.length === 0) return baseTheme;
5965
6005
  try {
5966
- const runtime = buildThemeRuntime(theme);
5967
- return runtime.paletteTokens;
5968
- } catch {
5969
- return void 0;
5970
- }
5971
- }, [theme]);
5972
- return /* @__PURE__ */ jsx(PaletteProvider, { tokens: paletteTokens, children: /* @__PURE__ */ jsx(LinkWidgetProvider, { siteId, adapters: linkAdapters, children: /* @__PURE__ */ jsx(ReferenceWidgetProvider, { siteId, adapters: referenceAdapters, children: /* @__PURE__ */ jsx(EntryPickerWidgetProvider, { siteId, adapter: entryPickerAdapter, children: /* @__PURE__ */ jsx(
5973
- MediaEditorProvider,
5974
- {
5975
- siteId,
5976
- apiClient: previewClient,
5977
- bearerToken,
5978
- supabaseUrl,
5979
- mediaAssets,
5980
- children: /* @__PURE__ */ jsx(ButtonWidgetProvider, { buttonSystem: theme?.buttons ?? null, children: /* @__PURE__ */ jsx(PreviewFormProviderInner, { theme, children }) })
5981
- }
5982
- ) }) }) }) });
5983
- }
5984
- var previewWidgetOverrides = {
5985
- link: LinkField,
5986
- file: FileField,
5987
- media: MediaField,
5988
- richText: RichTextField,
5989
- reference: ReferenceField,
5990
- backgroundColor: BackgroundColorField,
5991
- backgroundGradient: BackgroundGradientField,
5992
- entryPicker: EntryPickerField,
5993
- iconPicker: IconPickerField,
5994
- buttonVariant: ButtonVariantField
5995
- };
5996
- function PreviewZoomControls({ zoom, onZoomChange, disabled }) {
5997
- return /* @__PURE__ */ jsxs(
5998
- Select,
5999
- {
6000
- value: String(zoom),
6001
- onValueChange: (value) => onZoomChange(value === "fit" ? "fit" : Number(value)),
6002
- disabled,
6003
- children: [
6004
- /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-20", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
6005
- /* @__PURE__ */ jsxs(SelectContent, { children: [
6006
- /* @__PURE__ */ jsx(SelectItem, { value: "33", children: "33%" }),
6007
- /* @__PURE__ */ jsx(SelectItem, { value: "50", children: "50%" }),
6008
- /* @__PURE__ */ jsx(SelectItem, { value: "75", children: "75%" }),
6009
- /* @__PURE__ */ jsx(SelectItem, { value: "100", children: "100%" }),
6010
- /* @__PURE__ */ jsx(SelectItem, { value: "fit", children: "Fit" })
6011
- ] })
6012
- ]
6006
+ return applyRfc6902Patch(baseTheme, [...overlay.ops]);
6007
+ } catch (error) {
6008
+ if (isDebugEnabled()) {
6009
+ console.warn("[preview-theme-overlay] failed to apply theme overlay patch", {
6010
+ error: error instanceof Error ? error.message : String(error),
6011
+ overlayOps: overlay.ops
6012
+ });
6013
+ }
6014
+ return baseTheme;
6013
6015
  }
6016
+ }, [baseTheme, overlay]);
6017
+ const value = useMemo(
6018
+ () => ({
6019
+ baseTheme,
6020
+ overlay,
6021
+ overlayOps,
6022
+ overlayOpsSource,
6023
+ setOverlayOps,
6024
+ overrideTheme,
6025
+ overrideThemeSource,
6026
+ setOverrideTheme,
6027
+ effectiveTheme,
6028
+ clearOverlay
6029
+ }),
6030
+ [
6031
+ baseTheme,
6032
+ clearOverlay,
6033
+ effectiveTheme,
6034
+ overlay,
6035
+ overlayOps,
6036
+ overlayOpsSource,
6037
+ overrideTheme,
6038
+ overrideThemeSource,
6039
+ setOverlayOps,
6040
+ setOverrideTheme
6041
+ ]
6014
6042
  );
6043
+ return /* @__PURE__ */ jsx(PreviewThemeOverlayContext.Provider, { value, children });
6015
6044
  }
6016
- var VIEWPORT_PRESETS = [
6017
- { label: "Mobile", width: 375, icon: Smartphone },
6018
- { label: "Tablet", width: 768, icon: Tablet },
6019
- { label: "Desktop", width: 1440, icon: Monitor },
6020
- { label: "Full Width", width: "full", icon: Maximize2 }
6021
- ];
6022
- function PreviewViewportControls({
6023
- mode,
6024
- width,
6025
- onModeChange,
6026
- onWidthChange,
6027
- className,
6028
- zoom,
6029
- onZoomChange
6030
- }) {
6031
- const activePreset = VIEWPORT_PRESETS.find((p) => p.width === width);
6032
- const ActiveIcon = activePreset?.icon ?? Monitor;
6033
- const activeLabel = mode === "squeeze" ? "Auto fit" : activePreset?.label ?? `${width}px`;
6034
- const showZoomControls = mode === "fixed" && zoom !== void 0 && onZoomChange !== void 0;
6035
- return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
6036
- /* @__PURE__ */ jsxs(DropdownMenu, { children: [
6037
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "gap-2", children: [
6038
- /* @__PURE__ */ jsx(ActiveIcon, { className: "h-4 w-4" }),
6039
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: activeLabel })
6040
- ] }) }),
6041
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
6042
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Preview size" }),
6043
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
6044
- /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onModeChange("squeeze"), className: cn(mode === "squeeze" && "bg-accent"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
6045
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Auto fit" }),
6046
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Matches the space beside the editor" })
6047
- ] }) }),
6048
- /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onModeChange("fixed"), className: cn(mode === "fixed" && "bg-accent"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
6049
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Choose size" }),
6050
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Preview mobile, tablet, or desktop widths" })
6051
- ] }) })
6052
- ] })
6053
- ] }),
6054
- mode === "fixed" && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
6055
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: activePreset?.label ?? `${width}px` }) }),
6056
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
6057
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Screen size" }),
6058
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
6059
- VIEWPORT_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
6060
- DropdownMenuItem,
6061
- {
6062
- onClick: () => onWidthChange(preset.width),
6063
- className: cn(width === preset.width && "bg-accent"),
6064
- children: [
6065
- /* @__PURE__ */ jsx(preset.icon, { className: "mr-2 h-4 w-4" }),
6066
- preset.label,
6067
- typeof preset.width === "number" && /* @__PURE__ */ jsxs("span", { className: "ml-auto text-xs text-muted-foreground", children: [
6068
- preset.width,
6069
- "px"
6070
- ] })
6071
- ]
6072
- },
6073
- preset.label
6074
- ))
6075
- ] })
6076
- ] }),
6077
- showZoomControls ? /* @__PURE__ */ jsx(PreviewZoomControls, { zoom, onZoomChange }) : null
6078
- ] });
6079
- }
6080
- function SubrouteNotice({ model }) {
6081
- return /* @__PURE__ */ jsxs("div", { className: "border-b bg-amber-50 px-4 py-3 text-amber-950", children: [
6082
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: model.title }),
6083
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm", children: model.body }),
6084
- model.cta ? /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: /* @__PURE__ */ jsx(
6085
- "a",
6086
- {
6087
- className: "inline-flex items-center rounded-md bg-white px-3 py-1.5 text-sm font-medium text-amber-950 shadow-sm ring-1 ring-amber-900/10 hover:bg-amber-100",
6088
- href: model.cta.href,
6089
- target: "_blank",
6090
- rel: "noopener noreferrer",
6091
- children: model.cta.label
6092
- }
6093
- ) }) : null,
6094
- model.fallbackMessage ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-amber-900/90", children: model.fallbackMessage }) : null
6095
- ] });
6045
+ function usePreviewThemeOverlay() {
6046
+ const ctx = useContext(PreviewThemeOverlayContext);
6047
+ if (!ctx) {
6048
+ throw new Error("usePreviewThemeOverlay must be used within PreviewThemeOverlayProvider");
6049
+ }
6050
+ return ctx;
6096
6051
  }
6097
6052
 
6098
- // ../preview-next/src/client/preview/subrouteNotice.ts
6099
- function buildNoticeFromMeta(meta, payload, siteId) {
6100
- const name = meta.formatInstanceName(payload);
6101
- const title = name ? `You're previewing the ${name} ${meta.instanceNoun} of this ${meta.containerNoun}` : `You're previewing one ${meta.instanceNoun} of this ${meta.containerNoun}`;
6102
- const body = `Edits in this sidebar apply to the whole ${meta.containerNoun}, so they'll show up on every ${meta.instanceNoun}. Need to change just this ${meta.instanceNoun}? You can do that from your ${meta.listLabel}.`;
6103
- const ctaLabel = `Edit just this ${meta.instanceNoun}`;
6104
- let href = null;
6105
- try {
6106
- href = getDashboardUrl(`sites/${siteId}/${meta.subEditorPath(payload)}`);
6107
- } catch {
6108
- href = null;
6053
+ // ../preview-next/src/client/preview/previewDesignCoordinator.ts
6054
+ var NO_PREVIEW_DESIGN_STATE = { kind: "idle" };
6055
+ function transitionPreviewDesignState(state, command) {
6056
+ switch (command.kind) {
6057
+ case "preview-theme-patch":
6058
+ return acceptPreview(state, {
6059
+ kind: "active",
6060
+ owner: command.producer,
6061
+ surface: "theme-json-patch",
6062
+ apply: "idle"
6063
+ }, {
6064
+ kind: "set-patch",
6065
+ source: command.producer,
6066
+ ops: command.ops
6067
+ });
6068
+ case "preview-theme":
6069
+ return acceptPreview(state, {
6070
+ kind: "active",
6071
+ owner: command.producer,
6072
+ surface: command.sourcePlan,
6073
+ apply: "idle"
6074
+ }, {
6075
+ kind: "set-theme",
6076
+ source: command.producer,
6077
+ theme: command.theme
6078
+ });
6079
+ case "clear-preview":
6080
+ if (state.kind === "idle") {
6081
+ return ignored(state, "no-active-preview");
6082
+ }
6083
+ if (state.owner !== command.producer) {
6084
+ return ignored(state, "producer-does-not-own-preview");
6085
+ }
6086
+ return accepted(NO_PREVIEW_DESIGN_STATE, { kind: "clear" });
6087
+ case "base-theme-changed":
6088
+ if (state.kind === "idle") {
6089
+ return accepted(state, { kind: "none" });
6090
+ }
6091
+ return accepted(NO_PREVIEW_DESIGN_STATE, { kind: "clear" });
6092
+ case "apply-started":
6093
+ if (state.kind === "idle") {
6094
+ return ignored(state, "no-active-preview");
6095
+ }
6096
+ if (state.owner !== command.producer) {
6097
+ return ignored(state, "producer-does-not-own-preview");
6098
+ }
6099
+ return accepted({ ...state, apply: "pending" }, { kind: "none" });
6100
+ case "apply-succeeded":
6101
+ if (state.kind === "idle") {
6102
+ return ignored(state, "apply-event-for-displaced-preview");
6103
+ }
6104
+ if (state.owner !== command.producer) {
6105
+ return ignored(state, "apply-event-for-displaced-preview");
6106
+ }
6107
+ return accepted(NO_PREVIEW_DESIGN_STATE, { kind: "clear" });
6108
+ case "apply-failed":
6109
+ if (state.kind === "idle") {
6110
+ return ignored(state, "apply-event-for-displaced-preview");
6111
+ }
6112
+ if (state.owner !== command.producer) {
6113
+ return ignored(state, "apply-event-for-displaced-preview");
6114
+ }
6115
+ return accepted({ ...state, apply: "idle" }, { kind: "none" });
6116
+ default:
6117
+ return assertNever2(command, "Unhandled preview design command");
6109
6118
  }
6119
+ }
6120
+ function acceptPreview(current, next, effect) {
6121
+ return accepted(next, effect);
6122
+ }
6123
+ function accepted(state, effect) {
6110
6124
  return {
6111
- title,
6112
- body,
6113
- cta: href ? { label: ctaLabel, href } : null,
6114
- fallbackMessage: href ? null : "Dashboard links are unavailable. Set `NEXT_PUBLIC_DASHBOARD_URL` so preview can link back to the dashboard."
6125
+ kind: "accepted",
6126
+ state,
6127
+ effect
6115
6128
  };
6116
6129
  }
6117
- function resolveSubrouteNotice(ctx, args) {
6118
- if (!ctx) return null;
6119
- return foldSubroute(ctx, {
6120
- "event-occurrence": (occurrence) => buildNoticeFromMeta(SUBROUTE_KINDS["event-occurrence"], occurrence, args.siteId),
6121
- "course-run": (run) => buildNoticeFromMeta(SUBROUTE_KINDS["course-run"], run, args.siteId)
6122
- });
6130
+ function ignored(state, reason) {
6131
+ return {
6132
+ kind: "ignored",
6133
+ state,
6134
+ reason
6135
+ };
6123
6136
  }
6124
- function EntryEditorPanel({
6125
- siteId,
6126
- entryData,
6127
- contentTypeSchema,
6128
- referenceContentTypeKeys,
6129
- draftContent,
6130
- currentTemplate,
6131
- theme,
6132
- apiClient,
6133
- bearerToken,
6134
- supabaseUrl,
6135
- entrySubrouteContext,
6136
- metadataPanel,
6137
- mediaAssets,
6138
- widgets,
6139
- viewportMode,
6140
- viewportWidth,
6141
- zoom,
6142
- onViewportModeChange,
6143
- onViewportWidthChange,
6144
- onZoomChange
6145
- }) {
6146
- const [contentOpen, setContentOpen] = useState(true);
6147
- const [templateOpen, setTemplateOpen] = useState(false);
6148
- const [detailsOpen, setDetailsOpen] = useState(false);
6149
- const contentFormFields = useMemo(
6150
- // content-model keeps a source-local structural field type so it can typecheck natively
6151
- // without importing @riverbankcms/blocks source. Preview is the integration boundary where
6152
- // those normalized fields are consumed as a block-form manifest.
6153
- () => contentTypeSchema ? mapContentSchemaToFormFields(contentTypeSchema, {
6154
- referenceContentTypeKeys
6155
- }) : [],
6156
- [contentTypeSchema, referenceContentTypeKeys]
6137
+ var PreviewDesignCoordinatorContext = createContext(null);
6138
+ function PreviewDesignCoordinatorProvider({ children }) {
6139
+ const themeOverlay = usePreviewThemeOverlay();
6140
+ const {
6141
+ baseTheme,
6142
+ clearOverlay,
6143
+ overlay,
6144
+ setOverlayOps,
6145
+ setOverrideTheme
6146
+ } = themeOverlay;
6147
+ const [state, setState] = useState(NO_PREVIEW_DESIGN_STATE);
6148
+ const stateRef = useRef(state);
6149
+ const previousBaseThemeRef = useRef(null);
6150
+ useEffect(() => {
6151
+ stateRef.current = state;
6152
+ }, [state]);
6153
+ const dispatch = useCallback(
6154
+ (command) => {
6155
+ const transition = transitionPreviewDesignState(stateRef.current, command);
6156
+ if (transition.kind === "accepted") {
6157
+ stateRef.current = transition.state;
6158
+ setState(transition.state);
6159
+ switch (transition.effect.kind) {
6160
+ case "none":
6161
+ break;
6162
+ case "clear":
6163
+ clearOverlay();
6164
+ break;
6165
+ case "set-patch":
6166
+ setOverlayOps([...transition.effect.ops], {
6167
+ source: transition.effect.source
6168
+ });
6169
+ break;
6170
+ case "set-theme":
6171
+ setOverrideTheme(transition.effect.theme, {
6172
+ source: transition.effect.source
6173
+ });
6174
+ break;
6175
+ default:
6176
+ assertNever2(transition.effect, "Unhandled preview design overlay effect");
6177
+ }
6178
+ }
6179
+ return transition;
6180
+ },
6181
+ [clearOverlay, setOverlayOps, setOverrideTheme]
6182
+ );
6183
+ useEffect(() => {
6184
+ if (previousBaseThemeRef.current && previousBaseThemeRef.current !== baseTheme) {
6185
+ dispatch({ kind: "base-theme-changed" });
6186
+ }
6187
+ previousBaseThemeRef.current = baseTheme;
6188
+ }, [baseTheme, dispatch]);
6189
+ const previewTheme = useCallback(
6190
+ (producer, theme, sourcePlan) => dispatch({ kind: "preview-theme", producer, theme, sourcePlan }),
6191
+ [dispatch]
6192
+ );
6193
+ const previewThemePatch = useCallback(
6194
+ (producer, ops) => dispatch({ kind: "preview-theme-patch", producer, ops }),
6195
+ [dispatch]
6196
+ );
6197
+ const clearPreview = useCallback(
6198
+ (producer) => dispatch({ kind: "clear-preview", producer }),
6199
+ [dispatch]
6157
6200
  );
6158
- const contentFormSpec = useMemo(
6201
+ const applyStarted = useCallback(
6202
+ (producer) => dispatch({ kind: "apply-started", producer }),
6203
+ [dispatch]
6204
+ );
6205
+ const applySucceeded = useCallback(
6206
+ (producer) => dispatch({ kind: "apply-succeeded", producer }),
6207
+ [dispatch]
6208
+ );
6209
+ const applyFailed = useCallback(
6210
+ (producer) => dispatch({ kind: "apply-failed", producer }),
6211
+ [dispatch]
6212
+ );
6213
+ const value = useMemo(
6159
6214
  () => ({
6160
- name: "content-fields",
6161
- version: "1.0",
6162
- title: "Content",
6163
- component: "content-form",
6164
- description: "",
6165
- fields: contentFormFields,
6166
- slots: []
6215
+ state,
6216
+ overlay,
6217
+ dispatch,
6218
+ previewTheme,
6219
+ previewThemePatch,
6220
+ clearPreview,
6221
+ applyStarted,
6222
+ applySucceeded,
6223
+ applyFailed
6167
6224
  }),
6168
- [contentFormFields]
6169
- );
6170
- const contentInitialValues = useMemo(() => draftContent ?? {}, [draftContent]);
6171
- const subrouteNotice = useMemo(
6172
- () => resolveSubrouteNotice(entrySubrouteContext ?? null, { siteId }),
6173
- [entrySubrouteContext, siteId]
6225
+ [
6226
+ applyFailed,
6227
+ applyStarted,
6228
+ applySucceeded,
6229
+ clearPreview,
6230
+ dispatch,
6231
+ previewTheme,
6232
+ previewThemePatch,
6233
+ state,
6234
+ overlay
6235
+ ]
6174
6236
  );
6175
- return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", children: [
6176
- /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-2", children: /* @__PURE__ */ jsx(
6177
- PreviewViewportControls,
6178
- {
6179
- mode: viewportMode,
6180
- width: viewportWidth,
6181
- onModeChange: onViewportModeChange,
6182
- onWidthChange: onViewportWidthChange,
6183
- zoom,
6184
- onZoomChange
6185
- }
6186
- ) }),
6187
- subrouteNotice ? /* @__PURE__ */ jsx(SubrouteNotice, { model: subrouteNotice }) : null,
6188
- /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 space-y-0 overflow-auto", children: [
6189
- contentTypeSchema && entryData.type ? /* @__PURE__ */ jsx(Collapsible, { open: contentOpen, onOpenChange: setContentOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
6190
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
6191
- /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${contentOpen ? "rotate-90" : ""}` }),
6192
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Content" })
6193
- ] }) }),
6194
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
6195
- PreviewFormProvider,
6196
- {
6197
- siteId,
6198
- theme: theme ?? null,
6199
- previewClient: apiClient,
6200
- bearerToken,
6201
- supabaseUrl,
6202
- mediaAssets,
6203
- children: /* @__PURE__ */ jsx(
6204
- ContentFieldsForm,
6205
- {
6206
- siteId,
6207
- form: contentFormSpec,
6208
- initialValues: contentInitialValues,
6209
- widgets
6210
- }
6211
- )
6212
- }
6213
- ) }) })
6214
- ] }) }) : /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-3", children: /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-4 py-6 text-sm text-muted-foreground", children: "Content schema not available for this entry type." }) }),
6215
- currentTemplate && entryData.type ? /* @__PURE__ */ jsx(Collapsible, { open: templateOpen, onOpenChange: setTemplateOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
6216
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
6217
- /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${templateOpen ? "rotate-90" : ""}` }),
6218
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Template" })
6219
- ] }) }),
6220
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-4", children: [
6221
- /* @__PURE__ */ jsx("div", { className: "rounded-md border px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
6222
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: currentTemplate.name }),
6223
- currentTemplate.description ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: currentTemplate.description }) : null
6224
- ] }) }),
6225
- /* @__PURE__ */ jsx(
6226
- "a",
6227
- {
6228
- href: (() => {
6229
- try {
6230
- return getDashboardUrl(`sites/${siteId}/content/${entryData.type}/template`);
6231
- } catch {
6232
- return "#";
6233
- }
6234
- })(),
6235
- target: "_blank",
6236
- rel: "noopener noreferrer",
6237
- className: "inline-flex items-center text-sm text-primary hover:underline",
6238
- children: "Edit template in dashboard \u2192"
6239
- }
6240
- )
6241
- ] }) })
6242
- ] }) }) : null,
6243
- metadataPanel ? /* @__PURE__ */ jsx(Collapsible, { open: detailsOpen, onOpenChange: setDetailsOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
6244
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
6245
- /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${detailsOpen ? "rotate-90" : ""}` }),
6246
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Details" })
6247
- ] }) }),
6248
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: metadataPanel }) })
6249
- ] }) }) : null
6250
- ] })
6251
- ] });
6237
+ return /* @__PURE__ */ jsx(PreviewDesignCoordinatorContext.Provider, { value, children });
6252
6238
  }
6253
- function PageBlockEditorPanel({
6254
- siteId,
6255
- pageId,
6256
- theme,
6257
- apiClient,
6258
- bearerToken,
6259
- supabaseUrl,
6260
- blockItems,
6261
- uiStoreForBlockEditor,
6262
- blockEditorRef,
6263
- onBlocksChange,
6264
- metadataPanel,
6265
- designPanel,
6266
- sdkConfig,
6267
- mediaAssets,
6268
- viewportMode,
6269
- viewportWidth,
6270
- zoom,
6271
- onViewportModeChange,
6272
- onViewportWidthChange,
6273
- onZoomChange
6274
- }) {
6275
- const [designOpen, setDesignOpen] = useState(false);
6276
- const [contentOpen, setContentOpen] = useState(true);
6277
- const [detailsOpen, setDetailsOpen] = useState(false);
6278
- return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", children: [
6279
- /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-2", children: /* @__PURE__ */ jsx(
6280
- PreviewViewportControls,
6281
- {
6282
- mode: viewportMode,
6283
- width: viewportWidth,
6284
- onModeChange: onViewportModeChange,
6285
- onWidthChange: onViewportWidthChange,
6286
- zoom,
6287
- onZoomChange
6239
+ function usePreviewDesignCoordinator() {
6240
+ const ctx = useContext(PreviewDesignCoordinatorContext);
6241
+ if (!ctx) {
6242
+ throw new Error("usePreviewDesignCoordinator must be used within PreviewDesignCoordinatorProvider");
6243
+ }
6244
+ return ctx;
6245
+ }
6246
+
6247
+ // ../preview-next/src/client/blocks/runOptimisticCustomizeAction.ts
6248
+ function createOptimisticActionRunner() {
6249
+ let latest = 0;
6250
+ return {
6251
+ async run(action) {
6252
+ latest += 1;
6253
+ const seq = latest;
6254
+ action.optimistic?.();
6255
+ try {
6256
+ const success = await action.network();
6257
+ if (seq !== latest) return seq;
6258
+ action.onSuccess(success);
6259
+ } catch (error) {
6260
+ if (seq !== latest) return seq;
6261
+ action.onError(error);
6288
6262
  }
6289
- ) }),
6290
- /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 space-y-0 overflow-auto", children: [
6291
- designPanel ? /* @__PURE__ */ jsx(Collapsible, { open: designOpen, onOpenChange: setDesignOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
6292
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
6293
- /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${designOpen ? "rotate-90" : ""}` }),
6294
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Design" })
6295
- ] }) }),
6296
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: designPanel }) })
6297
- ] }) }) : null,
6298
- /* @__PURE__ */ jsx(Collapsible, { open: contentOpen, onOpenChange: setContentOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
6299
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
6300
- /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${contentOpen ? "rotate-90" : ""}` }),
6301
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Page Blocks" })
6302
- ] }) }),
6303
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
6304
- PreviewFormProvider,
6305
- {
6306
- siteId,
6307
- theme: theme ?? null,
6308
- previewClient: apiClient,
6309
- bearerToken,
6310
- supabaseUrl,
6311
- mediaAssets,
6312
- children: /* @__PURE__ */ jsx(
6313
- BlockEditor,
6314
- {
6315
- ref: blockEditorRef,
6316
- siteId,
6317
- pageId,
6318
- blocks: blockItems,
6319
- uiStore: uiStoreForBlockEditor,
6320
- apiClient,
6321
- onBlocksChange,
6322
- widgetOverrides: previewWidgetOverrides,
6323
- sdkConfig: sdkConfig ?? null,
6324
- reorderControlMode: "drag-handle-only",
6325
- actionControlMode: "menu"
6326
- },
6327
- pageId
6328
- )
6329
- }
6330
- ) }) })
6331
- ] }) }),
6332
- metadataPanel ? /* @__PURE__ */ jsx(Collapsible, { open: detailsOpen, onOpenChange: setDetailsOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
6333
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
6334
- /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${detailsOpen ? "rotate-90" : ""}` }),
6335
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Page Details" })
6336
- ] }) }),
6337
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: metadataPanel }) })
6338
- ] }) }) : null
6339
- ] })
6340
- ] });
6341
- }
6342
- function PreviewEditorGuidanceCard({
6343
- guidance,
6344
- completeSetupDisabled,
6345
- onCompleteSetup
6346
- }) {
6347
- return /* @__PURE__ */ jsxs(
6348
- "div",
6349
- {
6350
- className: guidance.cardClassName,
6351
- "data-preview-guidance-tone": guidance.tone,
6352
- children: [
6353
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold tracking-tight text-slate-900", children: guidance.title }),
6354
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs leading-5 text-slate-700", children: guidance.description }),
6355
- /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
6356
- Button,
6357
- {
6358
- type: "button",
6359
- size: "sm",
6360
- variant: "outline",
6361
- onClick: onCompleteSetup,
6362
- disabled: completeSetupDisabled,
6363
- children: guidance.ctaLabel
6364
- }
6365
- ) })
6366
- ]
6367
- }
6368
- );
6263
+ return seq;
6264
+ },
6265
+ invalidate() {
6266
+ latest += 1;
6267
+ },
6268
+ current() {
6269
+ return latest;
6270
+ }
6271
+ };
6369
6272
  }
6370
6273
 
6371
- // ../ui/src/palette/hex.ts
6372
- function normalizeHex(input, fallback = "#000000") {
6373
- if (!input) return fallback;
6374
- const trimmed = input.trim().replace(/^#/, "").toLowerCase();
6375
- const expanded = trimmed.length === 3 ? trimmed.split("").map((char) => char + char).join("") : trimmed;
6376
- if (!/^[0-9a-f]{6}$/i.test(expanded)) {
6377
- return fallback;
6378
- }
6379
- return `#${expanded}`;
6380
- }
6381
- function hexToRgb(hex) {
6382
- const normalized = normalizeHex(hex).replace("#", "");
6383
- const match = normalized.match(/.{2}/g);
6384
- if (!match || match.length < 3) return null;
6385
- const [r, g, b] = match.map((value) => parseInt(value, 16));
6386
- return [r, g, b];
6387
- }
6388
- function luminanceChannel(raw) {
6389
- const channel = raw / 255;
6390
- return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
6391
- }
6392
- function contrastRatio(hexA, hexB) {
6393
- const rgbA = hexToRgb(hexA);
6394
- const rgbB = hexToRgb(hexB);
6395
- if (!rgbA || !rgbB) return 1;
6396
- const lumA = 0.2126 * luminanceChannel(rgbA[0]) + 0.7152 * luminanceChannel(rgbA[1]) + 0.0722 * luminanceChannel(rgbA[2]);
6397
- const lumB = 0.2126 * luminanceChannel(rgbB[0]) + 0.7152 * luminanceChannel(rgbB[1]) + 0.0722 * luminanceChannel(rgbB[2]);
6398
- const brighter = Math.max(lumA, lumB);
6399
- const darker = Math.min(lumA, lumB);
6400
- return Number(((brighter + 0.05) / (darker + 0.05)).toFixed(2));
6401
- }
6402
- var DEFAULT_BRAND_ROLES = [
6403
- { key: "primary", label: "Primary" },
6404
- { key: "secondary", label: "Secondary" },
6405
- { key: "accent", label: "Accent" }
6406
- ];
6407
- var DEFAULT_FEEDBACK_ROLES = [
6408
- { key: "success", label: "Success" },
6409
- { key: "warning", label: "Warning" },
6410
- { key: "destructive", label: "Destructive" }
6411
- ];
6412
- var DEFAULT_SURFACE_ROLES = [
6413
- { key: "background", label: "Background" },
6414
- { key: "surface", label: "Surface" },
6415
- { key: "text", label: "Text" },
6416
- { key: "border", label: "Border" }
6417
- ];
6418
- function PaletteTokenEditor({
6419
- palette,
6420
- onChange,
6421
- hasInvalidValues = false,
6422
- brandRoles = DEFAULT_BRAND_ROLES,
6423
- feedbackRoles = DEFAULT_FEEDBACK_ROLES,
6424
- surfaceRoles = DEFAULT_SURFACE_ROLES,
6425
- showContrastChecks = true,
6426
- className
6427
- }) {
6428
- const paletteValues = useMemo(() => {
6429
- const map = /* @__PURE__ */ new Map();
6430
- for (const entry of palette) {
6431
- if (entry?.name) map.set(entry.name, normalizeHex(entry.hex));
6432
- }
6433
- return map;
6434
- }, [palette]);
6435
- const getColor = (role) => paletteValues.get(role) ?? "#000000";
6436
- const handleRoleChange = (role, hex) => {
6437
- if (!isPlausibleHexInput(hex)) return;
6438
- onChange(role, normalizeHex(hex));
6274
+ // ../preview-next/src/client/preview/StyleConfigurator.state.ts
6275
+ function createStyleConfiguratorState(base) {
6276
+ return {
6277
+ base,
6278
+ previewed: base,
6279
+ saveStatus: { kind: "idle" },
6280
+ warnings: [],
6281
+ notices: [],
6282
+ paletteOverrideDrafts: paletteOverrideDraftsFromSnapshot(base),
6283
+ expandedRow: null
6439
6284
  };
6440
- const backgroundHex = getColor("background");
6441
- const textHex = getColor("text");
6442
- const primaryHex = getColor("primary");
6443
- const contrast = contrastRatio(backgroundHex, textHex);
6444
- const actionContrast = contrastRatio(primaryHex, backgroundHex);
6445
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-6", className), children: [
6446
- hasInvalidValues ? /* @__PURE__ */ jsx(Callout, { tone: "danger", role: "alert", className: "p-3", children: /* @__PURE__ */ jsx(CalloutDescription, { className: "mt-0", children: "Some palette values are invalid or incomplete." }) }) : null,
6447
- /* @__PURE__ */ jsx(ColorRoleGroup, { title: "Brand & accents", roles: brandRoles, getColor, onChange: handleRoleChange }),
6448
- /* @__PURE__ */ jsx(ColorRoleGroup, { title: "Feedback", roles: feedbackRoles, getColor, onChange: handleRoleChange }),
6449
- /* @__PURE__ */ jsx(ColorRoleGroup, { title: "Surfaces & text", roles: surfaceRoles, getColor, onChange: handleRoleChange }),
6450
- showContrastChecks ? /* @__PURE__ */ jsxs(Fragment, { children: [
6451
- /* @__PURE__ */ jsx(Separator, {}),
6452
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
6453
- /* @__PURE__ */ jsx(
6454
- ContrastCard,
6455
- {
6456
- title: "Background \u2194 Text contrast",
6457
- value: contrast,
6458
- threshold: 4.5,
6459
- primaryLabel: "Background",
6460
- primaryHex: backgroundHex,
6461
- secondaryLabel: "Text",
6462
- secondaryHex: textHex
6463
- }
6464
- ),
6465
- /* @__PURE__ */ jsx(
6466
- ContrastCard,
6467
- {
6468
- title: "Primary action contrast",
6469
- value: actionContrast,
6470
- threshold: 3,
6471
- primaryLabel: "Primary",
6472
- primaryHex,
6473
- secondaryLabel: "Background",
6474
- secondaryHex: backgroundHex
6475
- }
6476
- )
6477
- ] })
6478
- ] }) : null
6479
- ] });
6480
6285
  }
6481
- function isPlausibleHexInput(value) {
6482
- const trimmed = value.trim();
6483
- return /^#?[0-9a-fA-F]{3}$/.test(trimmed) || /^#?[0-9a-fA-F]{6}$/.test(trimmed);
6286
+ function openRow(state, row) {
6287
+ return state.expandedRow === row ? state : { ...state, expandedRow: row };
6484
6288
  }
6485
- function ColorRoleGroup({ title, roles, getColor, onChange }) {
6486
- if (roles.length === 0) return null;
6487
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
6488
- /* @__PURE__ */ jsxs("div", { children: [
6489
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-foreground", children: title }),
6490
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Adjust hex values or drag the color picker." })
6491
- ] }),
6492
- /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: roles.map((role) => /* @__PURE__ */ jsx(ColorRoleField, { role, value: getColor(role.key), onChange }, role.key)) })
6493
- ] });
6289
+ function isPreviewingChange(state) {
6290
+ return !selectionSnapshotsEqual(state.base, state.previewed);
6494
6291
  }
6495
- function ColorRoleField({ role, value, onChange }) {
6496
- const inputId = useId();
6497
- const colorPickerLabel = `${role.label} colour picker`;
6498
- return /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-muted p-3 space-y-2", children: [
6499
- /* @__PURE__ */ jsx(
6500
- "label",
6501
- {
6502
- htmlFor: inputId,
6503
- className: "text-xs uppercase text-muted-foreground font-medium leading-5 block",
6504
- children: role.label
6505
- }
6506
- ),
6507
- /* @__PURE__ */ jsx(
6508
- ColorPicker,
6509
- {
6510
- value,
6511
- onChange: ((next) => {
6512
- if (typeof next === "string") {
6513
- onChange(role.key, next);
6514
- }
6515
- }),
6516
- className: "w-full",
6517
- "aria-label": colorPickerLabel
6518
- }
6519
- ),
6520
- /* @__PURE__ */ jsx(
6521
- Input,
6522
- {
6523
- id: inputId,
6524
- value,
6525
- onChange: (event) => onChange(role.key, event.target.value),
6526
- className: "font-mono text-xs uppercase",
6527
- placeholder: "#000000"
6528
- }
6529
- )
6530
- ] });
6292
+ function previewSiteStyle(state, siteStyleId) {
6293
+ return {
6294
+ ...state,
6295
+ previewed: { ...state.previewed, siteStyleId },
6296
+ saveStatus: { kind: "idle" }
6297
+ };
6531
6298
  }
6532
- function ContrastCard({ title, value, threshold, primaryLabel, primaryHex, secondaryLabel, secondaryHex }) {
6533
- const passes = value >= threshold;
6534
- return /* @__PURE__ */ jsxs(
6535
- Surface,
6536
- {
6537
- variant: "background",
6538
- radius: "lg",
6539
- padding: "md",
6540
- className: cn(
6541
- "space-y-2 border text-sm",
6542
- passes ? "border-success/40 bg-success/10 text-success" : "border-warning/40 bg-warning/10 text-warning-foreground"
6543
- ),
6544
- children: [
6545
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
6546
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: title }),
6547
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold", children: [
6548
- value,
6549
- ":1"
6550
- ] })
6551
- ] }),
6552
- /* @__PURE__ */ jsx("p", { className: "text-xs", children: passes ? "Contrast meets WCAG AA guidance." : `Increase contrast to reach ${threshold}:1 or higher.` }),
6553
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 pt-2 text-xs", children: [
6554
- /* @__PURE__ */ jsx(ContrastSwatch, { hex: primaryHex, label: primaryLabel }),
6555
- /* @__PURE__ */ jsx(ContrastSwatch, { hex: secondaryHex, label: secondaryLabel })
6556
- ] })
6557
- ]
6558
- }
6299
+ function previewButtonPersonality(state, buttonPersonalityId) {
6300
+ return {
6301
+ ...state,
6302
+ previewed: { ...state.previewed, buttonPersonalityId },
6303
+ saveStatus: { kind: "idle" }
6304
+ };
6305
+ }
6306
+ function previewHeaderLayout(state, headerLayoutId) {
6307
+ const headerStyleId = deriveNearestCompatibleHeaderStyle(
6308
+ state.previewed.headerStyleId,
6309
+ headerLayoutId
6559
6310
  );
6311
+ return previewHeaderSelection(state, { headerLayoutId, headerStyleId });
6560
6312
  }
6561
- function ContrastSwatch({ hex, label }) {
6562
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
6563
- /* @__PURE__ */ jsx("span", { className: "h-6 w-6 rounded-full border", style: { backgroundColor: hex } }),
6564
- /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs uppercase tracking-wide text-muted-foreground", children: [
6565
- label,
6566
- ": ",
6567
- hex
6568
- ] })
6569
- ] });
6313
+ function previewHeaderStyle(state, headerStyleId) {
6314
+ return previewHeaderSelection(state, {
6315
+ headerLayoutId: state.previewed.headerLayoutId,
6316
+ headerStyleId
6317
+ });
6570
6318
  }
6571
-
6572
- // ../preview-next/src/client/preview/styleConfiguratorApplyPayload.ts
6573
- function buildApplySiteStyleSelectionBody(input) {
6319
+ function previewFooterLook(state, footerLookId) {
6574
6320
  return {
6575
- siteStyleId: input.previewed.siteStyleId,
6576
- buttonPersonalityId: input.previewed.buttonPersonalityId,
6577
- paletteVariantId: input.previewed.paletteVariantId,
6578
- paletteOverrides: input.previewed.paletteOverrides,
6579
- typographyPresetId: input.previewed.typographyPresetId,
6580
- typographyBodyFontOverride: input.previewed.typographyBodyFontOverride,
6581
- typographyHeadingFontOverride: input.previewed.typographyHeadingFontOverride,
6582
- headerLookId: input.previewed.headerLookId,
6583
- headerLayoutId: input.previewed.headerLayoutId,
6584
- headerStyleId: input.previewed.headerStyleId,
6585
- footerLookId: input.previewed.footerLookId,
6586
- baseSelectionVersion: input.base.selectionVersion,
6587
- selectionId: input.selectionId ?? null
6321
+ ...state,
6322
+ previewed: { ...state.previewed, footerLookId },
6323
+ saveStatus: { kind: "idle" }
6588
6324
  };
6589
6325
  }
6590
- var MAX_FONT_RESULTS_WHEN_SEARCHING = 12;
6591
- var MAX_FONT_RESULTS_WHEN_BROWSING = 8;
6592
- function StyleConfigurator({
6593
- siteId,
6594
- apiClient,
6595
- theme,
6596
- selectionId,
6597
- selectionVersion = null,
6598
- selectionUpdatedAt,
6599
- initialSelections,
6600
- headerSelectionContext,
6601
- footerSelectionContext,
6602
- expandedRowRequest = null,
6603
- siteStyleAccess
6604
- }) {
6605
- const router = useRouter();
6606
- const previewDesign = usePreviewDesignCoordinator();
6607
- const {
6608
- applyFailed,
6609
- applyStarted,
6610
- applySucceeded,
6611
- clearPreview,
6612
- previewTheme
6613
- } = previewDesign;
6614
- const [isRefreshPending, startRefreshTransition] = useTransition();
6615
- const baseSnapshot = useMemo(
6616
- () => resolveStyleConfiguratorInitialSnapshot({
6617
- theme,
6618
- initialSelections: {
6619
- ...initialSelections,
6620
- selectionVersion: initialSelections.selectionVersion ?? selectionVersion ?? 0,
6621
- selectionUpdatedAt: initialSelections.selectionUpdatedAt ?? selectionUpdatedAt ?? null
6622
- },
6623
- headerSelectionContext,
6624
- footerSelectionContext
6625
- }),
6626
- [footerSelectionContext, headerSelectionContext, theme, initialSelections, selectionUpdatedAt, selectionVersion]
6627
- );
6628
- const [state, setState] = useState(
6629
- () => baseSnapshot ? createStyleConfiguratorState(baseSnapshot) : null
6630
- );
6631
- const hasLoadedState = state !== null;
6632
- const stateRef = useRef(state);
6633
- const clearPreviewRef = useRef(clearPreview);
6634
- const runnerRef = useRef(createOptimisticActionRunner());
6635
- const appliedExpandedRowRequestIdRef = useRef(null);
6636
- useEffect(() => {
6637
- stateRef.current = state;
6638
- }, [state]);
6639
- useEffect(() => {
6640
- clearPreviewRef.current = clearPreview;
6641
- }, [clearPreview]);
6642
- useEffect(() => {
6643
- if (!baseSnapshot) return;
6644
- setState((current) => current ? syncStyleConfiguratorState(current, baseSnapshot) : createStyleConfiguratorState(baseSnapshot));
6645
- }, [baseSnapshot]);
6646
- useEffect(() => {
6647
- if (!state || !baseSnapshot) {
6648
- clearPreview("style-configurator");
6649
- return;
6650
- }
6651
- if (!isPreviewingChange(state)) {
6652
- clearPreview("style-configurator");
6653
- return;
6654
- }
6655
- const siteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
6656
- if (!siteStyle) {
6657
- clearPreview("style-configurator");
6658
- return;
6659
- }
6660
- const result = planSiteStylePreviewChange({
6661
- siteStyle,
6662
- selection: state.previewed,
6663
- baseTheme: theme,
6664
- headerSelectionContext,
6665
- footerSelectionContext
6666
- });
6667
- previewTheme("style-configurator", result.themeOverlay, "site-style");
6668
- }, [state, baseSnapshot, clearPreview, previewTheme, headerSelectionContext, footerSelectionContext, theme]);
6669
- useEffect(() => {
6670
- const runner = runnerRef.current;
6671
- return () => {
6672
- runner.invalidate();
6673
- clearPreviewRef.current("style-configurator");
6674
- };
6675
- }, []);
6676
- useEffect(() => {
6677
- if (!expandedRowRequest) return;
6678
- if (appliedExpandedRowRequestIdRef.current === expandedRowRequest.id) return;
6679
- setState((current) => {
6680
- if (!current) return current;
6681
- appliedExpandedRowRequestIdRef.current = expandedRowRequest.id;
6682
- return openRow(current, expandedRowRequest.row === "header" ? "header-layout" : expandedRowRequest.row);
6683
- });
6684
- }, [expandedRowRequest, hasLoadedState]);
6685
- const handlePreviewSiteStyle = useCallback((siteStyleId) => {
6686
- setState((current) => {
6687
- if (!current) return current;
6688
- const next = previewSiteStyle(current, siteStyleId);
6689
- const siteStyle = getCuratedSiteStyle(siteStyleId);
6690
- if (!siteStyle) return next;
6691
- const result = planSiteStylePreviewChange({
6692
- siteStyle,
6693
- selection: next.previewed,
6694
- baseTheme: theme,
6695
- headerSelectionContext,
6696
- footerSelectionContext
6697
- });
6698
- return applyResolvedSelections(next, {
6699
- resolved: result.resolvedSelections,
6700
- warnings: result.warnings
6701
- });
6702
- });
6703
- }, [footerSelectionContext, headerSelectionContext, theme]);
6704
- const handlePreviewPersonality = useCallback((id) => {
6705
- setState((current) => current ? previewButtonPersonality(current, id) : current);
6706
- }, []);
6707
- const handlePreviewHeaderLayout = useCallback((id) => {
6708
- if (!id) return;
6709
- setState((current) => current ? previewHeaderLayout(current, id) : current);
6710
- }, []);
6711
- const handlePreviewHeaderStyle = useCallback((id) => {
6712
- if (!id) return;
6713
- setState((current) => current ? previewHeaderStyle(current, id) : current);
6714
- }, []);
6715
- const handlePreviewFooterLook = useCallback((id) => {
6716
- const plan = planSiteChromeLookSelection({ surface: "footer", lookId: id });
6717
- if (plan.kind !== "footer") return;
6718
- setState((current) => current ? previewFooterLook(current, plan.next.footerLookId) : current);
6719
- }, []);
6720
- const handlePreviewPalette = useCallback((id) => {
6721
- setState((current) => current ? previewPaletteVariant(current, id) : current);
6722
- }, []);
6723
- const handlePreviewTypographyPreset = useCallback((id) => {
6724
- setState((current) => current ? previewTypographyPreset(current, id) : current);
6725
- }, []);
6726
- const handleBodyFontOverride = useCallback((fontFamily) => {
6727
- setState((current) => current ? setTypographyBodyFontOverride(current, fontFamily) : current);
6728
- }, []);
6729
- const handleHeadingFontOverride = useCallback((fontFamily) => {
6730
- setState((current) => current ? setTypographyHeadingFontOverride(current, fontFamily) : current);
6731
- }, []);
6732
- const handleClearTypographyOverrides = useCallback(() => {
6733
- setState((current) => current ? clearTypographyFontOverrides(current) : current);
6734
- }, []);
6735
- const handleTokenOverride = useCallback((role, hex) => {
6736
- setState((current) => {
6737
- if (!current) return current;
6738
- const next = { ...current.previewed.paletteOverrides ?? {}, [role]: hex };
6739
- const parsed = paletteOverridesSchema.safeParse(next);
6740
- if (!parsed.success) return current;
6741
- return setPaletteOverrides(current, parsed.data);
6742
- });
6743
- }, []);
6744
- const handleResetOverrides = useCallback(() => {
6745
- setState((current) => current ? setPaletteOverrides(current, null) : current);
6746
- }, []);
6747
- const handleToggleRow = useCallback((row) => {
6748
- setState((current) => current ? toggleRow(current, row) : current);
6749
- }, []);
6750
- const handleCancel = useCallback(() => {
6751
- setState((current) => current ? cancelPreview(current) : current);
6752
- }, []);
6753
- const handleDismissWarnings = useCallback(() => {
6754
- setState((current) => current ? dismissWarnings(current) : current);
6755
- }, []);
6756
- const handleDismissNotices = useCallback(() => {
6757
- setState((current) => current ? dismissNotices(current) : current);
6758
- }, []);
6759
- const handleApply = useCallback(async () => {
6760
- const live = stateRef.current;
6761
- if (!live || !isPreviewingChange(live)) return;
6762
- const previewed = live.previewed;
6763
- const siteStyle = getCuratedSiteStyle(previewed.siteStyleId);
6764
- if (!siteStyle) return;
6765
- const compiled = planSiteStylePreviewChange({
6766
- siteStyle,
6767
- selection: previewed,
6768
- baseTheme: theme,
6769
- headerSelectionContext,
6770
- footerSelectionContext
6771
- });
6772
- const body = buildApplySiteStyleSelectionBody({
6773
- base: live.base,
6774
- previewed: {
6775
- ...previewed,
6776
- buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
6777
- paletteVariantId: compiled.resolvedSelections.paletteVariantId,
6778
- headerLayoutId: compiled.resolvedSelections.headerLayoutId,
6779
- headerStyleId: compiled.resolvedSelections.headerStyleId,
6780
- headerLookId: compiled.resolvedSelections.headerLookId,
6781
- typographyPresetId: compiled.resolvedSelections.typographyPresetId,
6782
- typographyBodyFontOverride: compiled.resolvedSelections.typographyBodyFontOverride,
6783
- typographyHeadingFontOverride: compiled.resolvedSelections.typographyHeadingFontOverride
6784
- },
6785
- selectionId
6786
- });
6787
- await runnerRef.current.run({
6788
- optimistic: () => {
6789
- applyStarted("style-configurator");
6790
- setState((current) => current ? markApplySaving(current) : current);
6791
- },
6792
- network: async () => {
6793
- const response = await apiClient({
6794
- endpoint: "applySiteStyleSelection",
6795
- params: { siteId },
6796
- body
6797
- });
6798
- return unwrapResponse(response);
6799
- },
6800
- onSuccess: (saved) => {
6801
- setState(
6802
- (current) => current ? markApplySaved(current, {
6803
- siteStyleId: previewed.siteStyleId,
6804
- selectionVersion: saved.selectionVersion,
6805
- selectionUpdatedAt: saved.selectionUpdatedAt ?? previewed.selectionUpdatedAt ?? null,
6806
- buttonPersonalityId: saved.buttonPersonalityId,
6807
- paletteVariantId: saved.paletteVariantId,
6808
- paletteOverrides: saved.paletteOverrides ?? null,
6809
- typographyPresetId: saved.typographyPresetId,
6810
- typographyBodyFontOverride: saved.typographyBodyFontOverride,
6811
- typographyHeadingFontOverride: saved.typographyHeadingFontOverride,
6812
- headerLookId: saved.headerLookId,
6813
- headerLayoutId: saved.headerLayoutId,
6814
- headerStyleId: saved.headerStyleId,
6815
- footerLookId: saved.footerLookId
6816
- }) : current
6817
- );
6818
- applySucceeded("style-configurator");
6819
- startRefreshTransition(() => {
6820
- router.refresh();
6821
- });
6822
- },
6823
- onError: (error) => {
6824
- applyFailed("style-configurator");
6825
- const applyError = classifyStyleApplyError(error);
6826
- if (applyError.kind === "stale") {
6827
- startRefreshTransition(() => {
6828
- router.refresh();
6829
- });
6830
- }
6831
- const message = applyError.message;
6832
- setState((current) => current ? markApplyFailed(current, message) : current);
6833
- }
6834
- });
6835
- }, [
6836
- apiClient,
6837
- footerSelectionContext,
6838
- headerSelectionContext,
6839
- applyFailed,
6840
- applyStarted,
6841
- applySucceeded,
6842
- router,
6843
- selectionId,
6844
- siteId,
6845
- startRefreshTransition,
6846
- theme
6847
- ]);
6848
- if (!state || !baseSnapshot) {
6849
- return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading design controls..." });
6850
- }
6851
- const applyAllowed = canApplySiteStyle(siteStyleAccess);
6852
- const previewingChange = isPreviewingChange(state);
6853
- const saving = state.saveStatus.kind === "saving" || isRefreshPending;
6854
- const currentSiteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
6855
- if (!currentSiteStyle) {
6856
- return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "This site uses a Site Style that cannot be configured here." });
6857
- }
6858
- const currentPersonality = findButtonPersonality(state.previewed.buttonPersonalityId);
6859
- const currentPaletteVariant = findPaletteVariant(state.previewed.paletteVariantId);
6860
- const currentTypographyPreset = state.previewed.typographyPresetId ? findTypographyPreset(state.previewed.typographyPresetId) : null;
6861
- const chromeLookModels = buildSiteChromeLookModels({
6862
- state,
6863
- siteStyle: currentSiteStyle,
6864
- headerSelectionContext,
6865
- footerSelectionContext
6866
- });
6867
- const currentHeaderLayout = getHeaderLayout(state.previewed.headerLayoutId);
6868
- const currentHeaderStyle = getHeaderStyle(state.previewed.headerStyleId);
6869
- const currentFooterLook = state.previewed.footerLookId ? findFooterLook(state.previewed.footerLookId) : findFooterLook(chromeLookModels.footer.renderedLookId);
6870
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
6871
- state.warnings.length > 0 ? /* @__PURE__ */ jsx(WarningNotice, { warnings: state.warnings, onDismiss: handleDismissWarnings }) : null,
6872
- state.notices.length > 0 ? /* @__PURE__ */ jsx(NoticeBanner, { notices: state.notices, onDismiss: handleDismissNotices }) : null,
6873
- /* @__PURE__ */ jsx(
6874
- SiteStyleRow,
6875
- {
6876
- expanded: state.expandedRow === "style",
6877
- onToggle: () => handleToggleRow("style"),
6878
- current: currentSiteStyle,
6879
- onPreview: handlePreviewSiteStyle,
6880
- disabled: saving
6881
- }
6882
- ),
6883
- /* @__PURE__ */ jsx(
6884
- SiteChromeLookRow,
6885
- {
6886
- label: "Header layout",
6887
- ariaLabel: "Header layout",
6888
- defaultDescription: "Choose the header structure.",
6889
- expanded: state.expandedRow === "header-layout",
6890
- onToggle: () => handleToggleRow("header-layout"),
6891
- currentId: state.previewed.headerLayoutId,
6892
- currentLabel: currentHeaderLayout?.label ?? state.previewed.headerLayoutId,
6893
- scopeDescription: "Changes the header structure on every page.",
6894
- options: chromeLookModels.headerLayout.options,
6895
- diagnostics: [],
6896
- onCycle: (direction) => {
6897
- const next = cycleSiteChromeLookId(chromeLookModels.headerLayout.options, state.previewed.headerLayoutId, direction);
6898
- if (next !== void 0) handlePreviewHeaderLayout(next);
6899
- },
6900
- onPreview: handlePreviewHeaderLayout,
6901
- disabled: saving
6902
- }
6903
- ),
6904
- /* @__PURE__ */ jsx(
6905
- SiteChromeLookRow,
6906
- {
6907
- label: "Header look",
6908
- ariaLabel: "Header look",
6909
- defaultDescription: "Choose the header visual style.",
6910
- expanded: state.expandedRow === "header-style",
6911
- onToggle: () => handleToggleRow("header-style"),
6912
- currentId: state.previewed.headerStyleId,
6913
- currentLabel: currentHeaderStyle?.label ?? state.previewed.headerStyleId,
6914
- scopeDescription: "Changes the header surface and link style.",
6915
- options: chromeLookModels.headerStyle.options,
6916
- diagnostics: [],
6917
- onCycle: (direction) => {
6918
- const next = cycleSiteChromeLookId(chromeLookModels.headerStyle.options, state.previewed.headerStyleId, direction);
6919
- if (next !== void 0) handlePreviewHeaderStyle(next);
6920
- },
6921
- onPreview: handlePreviewHeaderStyle,
6922
- disabled: saving
6923
- }
6924
- ),
6925
- /* @__PURE__ */ jsx(
6926
- SiteChromeLookRow,
6927
- {
6928
- label: "Footer",
6929
- ariaLabel: "Footer",
6930
- defaultDescription: "Use the footer chosen for this site style.",
6931
- catalog: footerLookCatalog,
6932
- expanded: state.expandedRow === "footer",
6933
- onToggle: () => handleToggleRow("footer"),
6934
- currentId: state.previewed.footerLookId,
6935
- currentLabel: state.previewed.footerLookId ? currentFooterLook?.label ?? "Unknown footer Look" : `${currentFooterLook?.label ?? "Site style"} (default)`,
6936
- scopeDescription: "Changes the footer on every page.",
6937
- options: chromeLookModels.footer.options,
6938
- diagnostics: chromeLookModels.footer.diagnostics,
6939
- onCycle: (direction) => {
6940
- const next = cycleSiteChromeLookId(chromeLookModels.footer.options, state.previewed.footerLookId, direction);
6941
- if (next !== void 0) handlePreviewFooterLook(next);
6942
- },
6943
- onPreview: handlePreviewFooterLook,
6944
- disabled: saving
6945
- }
6946
- ),
6947
- /* @__PURE__ */ jsx(
6948
- PersonalityRow,
6949
- {
6950
- expanded: state.expandedRow === "buttons",
6951
- onToggle: () => handleToggleRow("buttons"),
6952
- siteStyle: currentSiteStyle,
6953
- currentId: state.previewed.buttonPersonalityId,
6954
- currentLabel: currentPersonality?.name ?? state.previewed.buttonPersonalityId,
6955
- onPreview: handlePreviewPersonality,
6956
- disabled: saving
6957
- }
6958
- ),
6959
- /* @__PURE__ */ jsx(
6960
- TypographyRow,
6961
- {
6962
- expanded: state.expandedRow === "typography",
6963
- onToggle: () => handleToggleRow("typography"),
6964
- apiClient,
6965
- siteStyle: currentSiteStyle,
6966
- currentPresetId: state.previewed.typographyPresetId,
6967
- currentLabel: currentTypographyPreset?.name ?? "Custom typography",
6968
- bodyFontOverride: state.previewed.typographyBodyFontOverride,
6969
- headingFontOverride: state.previewed.typographyHeadingFontOverride,
6970
- onPreviewPreset: handlePreviewTypographyPreset,
6971
- onBodyFontOverride: handleBodyFontOverride,
6972
- onHeadingFontOverride: handleHeadingFontOverride,
6973
- onClearOverrides: handleClearTypographyOverrides,
6974
- disabled: saving
6975
- }
6976
- ),
6977
- /* @__PURE__ */ jsx(
6978
- PaletteRow,
6979
- {
6980
- expanded: state.expandedRow === "palette",
6981
- onToggle: () => handleToggleRow("palette"),
6982
- siteStyle: currentSiteStyle,
6983
- currentId: state.previewed.paletteVariantId,
6984
- currentLabel: currentPaletteVariant?.name ?? state.previewed.paletteVariantId,
6985
- currentSwatch: currentPaletteVariant?.colors.find((c) => c.name === "primary")?.hex ?? null,
6986
- onPreview: handlePreviewPalette,
6987
- disabled: saving
6988
- }
6989
- ),
6990
- currentPaletteVariant ? /* @__PURE__ */ jsx(
6991
- ColoursRow,
6992
- {
6993
- expanded: state.expandedRow === "colours",
6994
- onToggle: () => handleToggleRow("colours"),
6995
- variant: currentPaletteVariant,
6996
- overrides: state.previewed.paletteOverrides,
6997
- onTokenChange: handleTokenOverride,
6998
- onReset: handleResetOverrides,
6999
- disabled: saving
7000
- }
7001
- ) : null,
7002
- previewingChange ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2", children: [
7003
- /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: handleCancel, disabled: saving, children: "Cancel" }),
7004
- applyAllowed ? /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: handleApply, disabled: saving, children: saving ? "Applying\u2026" : "Apply" }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Preview only \u2014 ask an admin to apply." })
7005
- ] }) : null,
7006
- state.saveStatus.kind === "saved" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-success", children: "Saved." }) : null,
7007
- state.saveStatus.kind === "failed" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: state.saveStatus.message }) : null
7008
- ] });
6326
+ function previewTypographyPreset(state, typographyPresetId) {
6327
+ return {
6328
+ ...state,
6329
+ previewed: { ...state.previewed, typographyPresetId },
6330
+ saveStatus: { kind: "idle" }
6331
+ };
7009
6332
  }
7010
- function StyleRowFrame({ label, currentLabel, swatch, expanded, onToggle, children }) {
7011
- return /* @__PURE__ */ jsx(Collapsible, { open: expanded, onOpenChange: onToggle, children: /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-background", children: [
7012
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
7013
- "button",
7014
- {
7015
- type: "button",
7016
- className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-accent/30",
7017
- "aria-expanded": expanded,
7018
- children: [
7019
- /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-4 w-4 transition", expanded && "rotate-90") }),
7020
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
7021
- /* @__PURE__ */ jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }),
7022
- /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: currentLabel })
7023
- ] }),
7024
- swatch ? /* @__PURE__ */ jsx(
7025
- "span",
7026
- {
7027
- className: "h-5 w-5 shrink-0 rounded-full border border-black/10",
7028
- style: { backgroundColor: swatch },
7029
- "aria-hidden": "true"
7030
- }
7031
- ) : null
7032
- ]
7033
- }
7034
- ) }),
7035
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "border-t px-3 py-3", children }) })
7036
- ] }) });
6333
+ function setTypographyBodyFontOverride(state, typographyBodyFontOverride) {
6334
+ return {
6335
+ ...state,
6336
+ previewed: { ...state.previewed, typographyBodyFontOverride },
6337
+ saveStatus: { kind: "idle" }
6338
+ };
7037
6339
  }
7038
- function SiteStyleRow({ expanded, onToggle, current, onPreview, disabled }) {
7039
- 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(
7040
- PickerOption,
7041
- {
7042
- selected: style.id === current.id,
7043
- label: style.name,
7044
- description: style.description,
7045
- onSelect: () => onPreview(style.id),
7046
- disabled
6340
+ function setTypographyHeadingFontOverride(state, typographyHeadingFontOverride) {
6341
+ return {
6342
+ ...state,
6343
+ previewed: { ...state.previewed, typographyHeadingFontOverride },
6344
+ saveStatus: { kind: "idle" }
6345
+ };
6346
+ }
6347
+ function clearTypographyFontOverrides(state) {
6348
+ return {
6349
+ ...state,
6350
+ previewed: {
6351
+ ...state.previewed,
6352
+ typographyBodyFontOverride: null,
6353
+ typographyHeadingFontOverride: null
7047
6354
  },
7048
- style.id
7049
- )) }) });
6355
+ saveStatus: { kind: "idle" }
6356
+ };
6357
+ }
6358
+ function previewPaletteVariant(state, paletteVariantId) {
6359
+ return movePreviewToPaletteVariant(state, paletteVariantId);
7050
6360
  }
7051
- function PersonalityRow({ expanded, onToggle, siteStyle, currentId, currentLabel, onPreview, disabled }) {
7052
- const options = useMemo(
7053
- () => requireEntriesForCuratedChoices(buttonPersonalities, siteStyle.buttonPersonalityChoices),
7054
- [siteStyle.buttonPersonalityChoices]
6361
+ function setPaletteOverrides(state, paletteOverrides) {
6362
+ const paletteOverrideDrafts = withPaletteOverrideDraft(
6363
+ state.paletteOverrideDrafts,
6364
+ state.previewed.paletteVariantId,
6365
+ paletteOverrides
7055
6366
  );
7056
- 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(
7057
- PickerOption,
7058
- {
7059
- selected: personality.id === currentId,
7060
- label: personality.name,
7061
- description: personality.description,
7062
- onSelect: () => onPreview(personality.id),
7063
- disabled
6367
+ return {
6368
+ ...state,
6369
+ previewed: { ...state.previewed, paletteOverrides },
6370
+ paletteOverrideDrafts,
6371
+ saveStatus: { kind: "idle" }
6372
+ };
6373
+ }
6374
+ function applyResolvedSelections(state, input) {
6375
+ const paletteScoped = movePreviewToPaletteVariant(state, input.resolved.paletteVariantId);
6376
+ return {
6377
+ ...paletteScoped,
6378
+ previewed: {
6379
+ ...paletteScoped.previewed,
6380
+ buttonPersonalityId: input.resolved.buttonPersonalityId,
6381
+ headerLayoutId: input.resolved.headerLayoutId,
6382
+ headerStyleId: input.resolved.headerStyleId,
6383
+ headerLookId: input.resolved.headerLookId,
6384
+ typographyPresetId: input.resolved.typographyPresetId,
6385
+ typographyBodyFontOverride: input.resolved.typographyBodyFontOverride,
6386
+ typographyHeadingFontOverride: input.resolved.typographyHeadingFontOverride
7064
6387
  },
7065
- personality.id
7066
- )) }) });
6388
+ warnings: input.warnings
6389
+ };
7067
6390
  }
7068
- function TypographyRow({
7069
- expanded,
7070
- onToggle,
7071
- apiClient,
7072
- siteStyle,
7073
- currentPresetId,
7074
- currentLabel,
7075
- bodyFontOverride,
7076
- headingFontOverride,
7077
- onPreviewPreset,
7078
- onBodyFontOverride,
7079
- onHeadingFontOverride,
7080
- onClearOverrides,
7081
- disabled
7082
- }) {
7083
- const options = useMemo(
7084
- () => requireEntriesForCuratedChoices(typographyPresets, siteStyle.typographyPresetChoices),
7085
- [siteStyle.typographyPresetChoices]
7086
- );
7087
- const fontCatalog = useFontCatalog(apiClient, expanded);
7088
- const selectedPreset = currentPresetId ? findTypographyPreset(currentPresetId) : null;
7089
- return /* @__PURE__ */ jsxs(StyleRowFrame, { label: "Typography", currentLabel, expanded, onToggle, children: [
7090
- 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,
7091
- /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Typography preset", className: "space-y-1", children: options.map((preset) => /* @__PURE__ */ jsx(
7092
- TypographyPresetOption,
7093
- {
7094
- preset,
7095
- selected: preset.id === currentPresetId,
7096
- onSelect: () => onPreviewPreset(preset.id),
7097
- disabled
7098
- },
7099
- preset.id
7100
- )) }),
7101
- /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-3 border-t pt-3", children: [
7102
- selectedPreset ? /* @__PURE__ */ jsxs(Fragment, { children: [
7103
- /* @__PURE__ */ jsx(
7104
- TypographyFontSlotPicker,
7105
- {
7106
- label: "Body font",
7107
- slot: "body",
7108
- preset: selectedPreset,
7109
- catalog: fontCatalog,
7110
- value: bodyFontOverride,
7111
- onChange: onBodyFontOverride,
7112
- disabled
7113
- }
7114
- ),
7115
- /* @__PURE__ */ jsx(
7116
- TypographyFontSlotPicker,
7117
- {
7118
- label: "Heading font",
7119
- slot: "heading",
7120
- preset: selectedPreset,
7121
- catalog: fontCatalog,
7122
- value: headingFontOverride,
7123
- onChange: onHeadingFontOverride,
7124
- disabled
7125
- }
7126
- )
7127
- ] }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Choose a typography preset to use curated font overrides." }),
7128
- /* @__PURE__ */ jsx(
7129
- Button,
7130
- {
7131
- type: "button",
7132
- size: "sm",
7133
- variant: "outline",
7134
- onClick: onClearOverrides,
7135
- disabled: disabled || !bodyFontOverride && !headingFontOverride,
7136
- children: "Use preset fonts"
7137
- }
7138
- )
7139
- ] })
7140
- ] });
6391
+ function cancelPreview(state) {
6392
+ return {
6393
+ ...state,
6394
+ previewed: state.base,
6395
+ saveStatus: { kind: "idle" },
6396
+ warnings: [],
6397
+ notices: [],
6398
+ paletteOverrideDrafts: paletteOverrideDraftsFromSnapshot(state.base)
6399
+ };
7141
6400
  }
7142
- function TypographyPresetOption({
7143
- preset,
7144
- selected,
7145
- onSelect,
7146
- disabled
7147
- }) {
7148
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
7149
- "button",
7150
- {
7151
- type: "button",
7152
- role: "radio",
7153
- "aria-checked": selected,
7154
- onClick: onSelect,
7155
- disabled,
7156
- className: cn(
7157
- "w-full rounded-md border px-3 py-2 text-left transition disabled:cursor-not-allowed disabled:opacity-50",
7158
- selected ? "border-primary bg-primary/5" : "border-border hover:bg-muted/60"
7159
- ),
7160
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
7161
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7162
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: preset.name }),
7163
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: preset.description })
7164
- ] }),
7165
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
7166
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-semibold uppercase tracking-wide", children: preset.defaultFonts.heading.split(",")[0] }),
7167
- /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: preset.defaultFonts.body.split(",")[0] })
7168
- ] })
7169
- ] })
7170
- }
7171
- ) });
6401
+ function markApplySaving(state) {
6402
+ return { ...state, saveStatus: { kind: "saving" } };
7172
6403
  }
7173
- function TypographyFontSlotPicker({
7174
- label,
7175
- slot,
7176
- preset,
7177
- catalog,
7178
- value,
7179
- onChange,
7180
- disabled
7181
- }) {
7182
- const [query, setQuery] = useState("");
7183
- const defaultFont = primaryFontFamily(preset.defaultFonts[slot]);
7184
- const selectedFamily = value ?? defaultFont;
7185
- const recordsByFamily = useMemo(() => indexFontsByFamily(catalog.fonts), [catalog.fonts]);
7186
- const selectedFit = evaluateTypographyFontSlot({
7187
- slot,
7188
- policy: preset.slotPolicies[slot],
7189
- capability: capabilityForFamily(selectedFamily, recordsByFamily)
7190
- });
7191
- const recommendedFamilies = preset.recommendedFonts[slot];
7192
- const recommendedOptions = useMemo(
7193
- () => uniqueFontFamilies(recommendedFamilies),
7194
- [recommendedFamilies]
7195
- );
7196
- const browseOptions = useMemo(() => {
7197
- const normalizedQuery = query.trim().toLowerCase();
7198
- const source = normalizedQuery.length > 0 ? catalog.fonts.filter((font) => font.family.toLowerCase().includes(normalizedQuery)) : catalog.fonts;
7199
- 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);
7200
- }, [catalog.fonts, preset, query, recordsByFamily, slot]);
7201
- return /* @__PURE__ */ jsxs("section", { className: "space-y-2", "aria-label": label, children: [
7202
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
7203
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7204
- /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: label }),
7205
- /* @__PURE__ */ jsx("div", { className: "truncate text-sm", children: value ? selectedFamily : `${defaultFont} (preset)` })
7206
- ] }),
7207
- /* @__PURE__ */ jsx(TypographyFontFitBadge, { fit: selectedFit })
7208
- ] }),
7209
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-1", children: [
7210
- /* @__PURE__ */ jsxs(
7211
- "button",
7212
- {
7213
- type: "button",
7214
- className: cn(
7215
- "rounded-md border px-3 py-2 text-left text-sm transition disabled:cursor-not-allowed disabled:opacity-50",
7216
- value === null ? "border-foreground bg-accent/40" : "border-transparent hover:border-foreground/40 hover:bg-accent/20"
7217
- ),
7218
- onClick: () => onChange(null),
7219
- disabled,
7220
- children: [
7221
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Use preset font" }),
7222
- /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: defaultFont })
7223
- ]
7224
- }
7225
- ),
7226
- recommendedOptions.map((family) => /* @__PURE__ */ jsx(
7227
- FontFamilyOption,
7228
- {
7229
- family,
7230
- selected: value === family,
7231
- fit: evaluateTypographyFontSlot({
7232
- slot,
7233
- policy: preset.slotPolicies[slot],
7234
- capability: capabilityForFamily(family, recordsByFamily)
7235
- }),
7236
- onSelect: () => onChange(family),
7237
- disabled
7238
- },
7239
- family
7240
- ))
7241
- ] }),
7242
- /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border bg-muted/20 p-2", children: [
7243
- /* @__PURE__ */ jsx(
7244
- "input",
7245
- {
7246
- type: "search",
7247
- value: query,
7248
- placeholder: `Search ${slot} fonts`,
7249
- onChange: (event) => setQuery(event.currentTarget.value),
7250
- disabled: disabled || catalog.kind === "loading",
7251
- 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"
7252
- }
7253
- ),
7254
- 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,
7255
- browseOptions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-1 overflow-y-auto pr-1", children: browseOptions.map((font) => /* @__PURE__ */ jsx(
7256
- FontFamilyOption,
7257
- {
7258
- family: font.family,
7259
- selected: value === font.family,
7260
- fit: evaluateTypographyFontSlot({
7261
- slot,
7262
- policy: preset.slotPolicies[slot],
7263
- capability: capabilityFromFontRecord(font)
7264
- }),
7265
- onSelect: () => onChange(font.family),
7266
- disabled
7267
- },
7268
- font.family
7269
- )) }) : catalog.kind === "loaded" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No matching Google Fonts." }) : null
7270
- ] }),
7271
- /* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
7272
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Brand or uploaded font name" }),
7273
- /* @__PURE__ */ jsx(
7274
- "input",
7275
- {
7276
- type: "text",
7277
- value: value ?? "",
7278
- placeholder: "Enter any font family",
7279
- onChange: (event) => onChange(normalizeFontFamilyOverride(event.currentTarget.value)),
7280
- disabled,
7281
- 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"
7282
- }
7283
- )
7284
- ] })
7285
- ] });
6404
+ function markApplySaved(state, persisted) {
6405
+ return {
6406
+ ...state,
6407
+ base: persisted,
6408
+ previewed: persisted,
6409
+ saveStatus: { kind: "saved" },
6410
+ warnings: [],
6411
+ notices: [],
6412
+ paletteOverrideDrafts: paletteOverrideDraftsFromSnapshot(persisted)
6413
+ };
7286
6414
  }
7287
- function FontFamilyOption({
7288
- family,
7289
- selected,
7290
- fit,
7291
- onSelect,
7292
- disabled
7293
- }) {
7294
- return /* @__PURE__ */ jsxs(
7295
- "button",
7296
- {
7297
- type: "button",
7298
- className: cn(
7299
- "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",
7300
- selected ? "border-foreground bg-accent/40" : "border-transparent bg-background hover:border-foreground/40 hover:bg-accent/20"
7301
- ),
7302
- onClick: onSelect,
7303
- disabled,
7304
- children: [
7305
- /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: family }),
7306
- /* @__PURE__ */ jsx(TypographyFontFitBadge, { fit })
7307
- ]
7308
- }
7309
- );
6415
+ function markApplyFailed(state, message) {
6416
+ return {
6417
+ ...state,
6418
+ saveStatus: { kind: "failed", message }
6419
+ };
7310
6420
  }
7311
- function TypographyFontFitBadge({ fit }) {
7312
- return /* @__PURE__ */ jsx(
7313
- "span",
7314
- {
7315
- className: cn(
7316
- "shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium",
7317
- fit.kind === "recommended" && "bg-emerald-100 text-emerald-800",
7318
- fit.kind === "compatible" && "bg-sky-100 text-sky-800",
7319
- fit.kind === "limited" && "bg-amber-100 text-amber-900",
7320
- fit.kind === "risky" && "bg-rose-100 text-rose-800",
7321
- fit.kind === "unknown" && "bg-muted text-muted-foreground"
7322
- ),
7323
- title: fit.reason,
7324
- children: typographyFontFitLabel(fit.kind)
7325
- }
7326
- );
6421
+ function toggleRow(state, row) {
6422
+ return {
6423
+ ...state,
6424
+ expandedRow: state.expandedRow === row ? null : row
6425
+ };
7327
6426
  }
7328
- function useFontCatalog(apiClient, enabled) {
7329
- const [catalog, setCatalog] = useState({ kind: "idle", fonts: [] });
7330
- const requestedClientRef = useRef(null);
7331
- useEffect(() => {
7332
- if (!enabled) return;
7333
- if (requestedClientRef.current === apiClient) return;
7334
- requestedClientRef.current = apiClient;
7335
- let cancelled = false;
7336
- setCatalog({ kind: "loading", fonts: [] });
7337
- async function loadFonts() {
7338
- try {
7339
- const response = await apiClient({ endpoint: "listFonts" });
7340
- const fonts = unwrapResponse(response);
7341
- if (!cancelled) {
7342
- setCatalog({
7343
- kind: "loaded",
7344
- fonts: Array.isArray(fonts) ? fonts : []
7345
- });
7346
- }
7347
- } catch (error) {
7348
- if (!cancelled) {
7349
- setCatalog({
7350
- kind: "failed",
7351
- fonts: [],
7352
- message: error instanceof Error ? error.message : "Unable to load the font catalog."
7353
- });
7354
- }
7355
- }
7356
- }
7357
- void loadFonts();
7358
- return () => {
7359
- cancelled = true;
7360
- };
7361
- }, [apiClient, enabled]);
7362
- return catalog;
6427
+ function dismissWarnings(state) {
6428
+ if (state.warnings.length === 0) return state;
6429
+ return { ...state, warnings: [] };
7363
6430
  }
7364
- function indexFontsByFamily(fonts) {
7365
- return new Map(fonts.map((font) => [font.family.toLowerCase(), font]));
6431
+ function dismissNotices(state) {
6432
+ if (state.notices.length === 0) return state;
6433
+ return { ...state, notices: [] };
7366
6434
  }
7367
- function capabilityForFamily(family, recordsByFamily) {
7368
- const record = recordsByFamily.get(family.toLowerCase());
7369
- if (record) return capabilityFromFontRecord(record);
6435
+ function syncStyleConfiguratorState(state, base) {
6436
+ if (selectionSnapshotsEqual(state.base, base)) return state;
6437
+ if (selectionSnapshotsEqual(state.previewed, base)) {
6438
+ return {
6439
+ ...state,
6440
+ base,
6441
+ saveStatus: state.saveStatus.kind === "saving" ? { kind: "saved" } : state.saveStatus,
6442
+ warnings: [],
6443
+ notices: [],
6444
+ paletteOverrideDrafts: paletteOverrideDraftsFromSnapshot(base)
6445
+ };
6446
+ }
6447
+ return {
6448
+ ...state,
6449
+ base,
6450
+ previewed: base,
6451
+ saveStatus: { kind: "idle" },
6452
+ warnings: [],
6453
+ notices: [],
6454
+ paletteOverrideDrafts: paletteOverrideDraftsFromSnapshot(base)
6455
+ };
6456
+ }
6457
+ function movePreviewToPaletteVariant(state, paletteVariantId) {
6458
+ if (state.previewed.paletteVariantId === paletteVariantId) {
6459
+ return {
6460
+ ...state,
6461
+ saveStatus: { kind: "idle" }
6462
+ };
6463
+ }
6464
+ const paletteOverrideDrafts = withPaletteOverrideDraft(
6465
+ state.paletteOverrideDrafts,
6466
+ state.previewed.paletteVariantId,
6467
+ state.previewed.paletteOverrides
6468
+ );
6469
+ const restoredOverrides = paletteOverrideDrafts[paletteVariantId] ?? null;
7370
6470
  return {
7371
- source: "unknown",
7372
- family,
7373
- category: "unknown",
7374
- variants: []
6471
+ ...state,
6472
+ previewed: {
6473
+ ...state.previewed,
6474
+ paletteVariantId,
6475
+ paletteOverrides: restoredOverrides
6476
+ },
6477
+ paletteOverrideDrafts,
6478
+ notices: appendPaletteOverrideScopeNotice(state.notices, {
6479
+ previousOverrides: state.previewed.paletteOverrides,
6480
+ restoredOverrides
6481
+ }),
6482
+ saveStatus: { kind: "idle" }
7375
6483
  };
7376
6484
  }
7377
- function capabilityFromFontRecord(font) {
6485
+ function previewHeaderSelection(state, selection) {
6486
+ const headerStyleId = deriveNearestCompatibleHeaderStyle(
6487
+ selection.headerStyleId,
6488
+ selection.headerLayoutId
6489
+ );
6490
+ const headerLookId = findLegacyHeaderLookIdForSelection({
6491
+ layoutId: selection.headerLayoutId,
6492
+ styleId: headerStyleId
6493
+ });
7378
6494
  return {
7379
- source: "google",
7380
- family: font.family,
7381
- category: normalizeGoogleFontCategory(font.category),
7382
- variants: font.variants
6495
+ ...state,
6496
+ previewed: {
6497
+ ...state.previewed,
6498
+ headerLayoutId: selection.headerLayoutId,
6499
+ headerStyleId,
6500
+ headerLookId
6501
+ },
6502
+ saveStatus: { kind: "idle" }
7383
6503
  };
7384
6504
  }
7385
- function normalizeGoogleFontCategory(category) {
7386
- switch (category) {
7387
- case "serif":
7388
- case "sans-serif":
7389
- case "display":
7390
- case "handwriting":
7391
- case "monospace":
7392
- return category;
7393
- default:
7394
- return "unknown";
6505
+ function withPaletteOverrideDraft(drafts, paletteVariantId, paletteOverrides) {
6506
+ const next = { ...drafts };
6507
+ if (paletteOverrides === null) {
6508
+ delete next[paletteVariantId];
6509
+ } else {
6510
+ next[paletteVariantId] = paletteOverrides;
7395
6511
  }
6512
+ return next;
7396
6513
  }
7397
- function sortFontForPresetSlot(a, b, slot, preset, recordsByFamily) {
7398
- const aFit = evaluateTypographyFontSlot({
7399
- slot,
7400
- policy: preset.slotPolicies[slot],
7401
- capability: capabilityForFamily(a.family, recordsByFamily)
7402
- });
7403
- const bFit = evaluateTypographyFontSlot({
7404
- slot,
7405
- policy: preset.slotPolicies[slot],
7406
- capability: capabilityForFamily(b.family, recordsByFamily)
7407
- });
7408
- const fitDelta = typographyFontFitRank(aFit.kind) - typographyFontFitRank(bFit.kind);
7409
- if (fitDelta !== 0) return fitDelta;
7410
- const popularityDelta = (a.popularity ?? Infinity) - (b.popularity ?? Infinity);
7411
- if (popularityDelta !== 0) return popularityDelta;
7412
- return a.family.localeCompare(b.family);
7413
- }
7414
- function typographyFontFitRank(kind) {
7415
- switch (kind) {
7416
- case "recommended":
7417
- return 0;
7418
- case "compatible":
7419
- return 1;
7420
- case "limited":
7421
- return 2;
7422
- case "risky":
7423
- return 3;
7424
- case "unknown":
7425
- return 4;
7426
- default: {
7427
- const exhaustive = kind;
7428
- return exhaustive;
7429
- }
6514
+ function paletteOverrideDraftsFromSnapshot(snapshot) {
6515
+ return snapshot.paletteOverrides ? { [snapshot.paletteVariantId]: snapshot.paletteOverrides } : {};
6516
+ }
6517
+ function appendPaletteOverrideScopeNotice(notices, input) {
6518
+ const kind = input.restoredOverrides !== null ? "palette-overrides-restored" : input.previousOverrides !== null ? "palette-overrides-hidden" : null;
6519
+ if (!kind || notices.some((notice) => notice.kind === kind)) {
6520
+ return notices;
7430
6521
  }
6522
+ return [...notices, { kind }];
7431
6523
  }
7432
- function typographyFontFitLabel(kind) {
7433
- switch (kind) {
7434
- case "recommended":
7435
- return "Recommended";
7436
- case "compatible":
7437
- return "Compatible";
7438
- case "limited":
7439
- return "Limited";
7440
- case "risky":
7441
- return "Risky";
7442
- case "unknown":
7443
- return "Unknown";
7444
- default: {
7445
- const exhaustive = kind;
7446
- return exhaustive;
6524
+ function selectionSnapshotsEqual(a, b) {
6525
+ return a.siteStyleId === b.siteStyleId && a.selectionVersion === b.selectionVersion && a.selectionUpdatedAt === b.selectionUpdatedAt && a.headerLayoutId === b.headerLayoutId && a.headerStyleId === b.headerStyleId && a.headerLookId === b.headerLookId && a.footerLookId === b.footerLookId && a.buttonPersonalityId === b.buttonPersonalityId && a.paletteVariantId === b.paletteVariantId && a.typographyPresetId === b.typographyPresetId && a.typographyBodyFontOverride === b.typographyBodyFontOverride && a.typographyHeadingFontOverride === b.typographyHeadingFontOverride && paletteOverridesEqual(a.paletteOverrides, b.paletteOverrides);
6526
+ }
6527
+ function paletteOverridesEqual(a, b) {
6528
+ if (a === b) return true;
6529
+ if (!a || !b) return false;
6530
+ const keysA = Object.keys(a);
6531
+ const keysB = Object.keys(b);
6532
+ if (keysA.length !== keysB.length) return false;
6533
+ return keysA.every((key) => a[key] === b[key]);
6534
+ }
6535
+
6536
+ // ../preview-next/src/client/preview/siteChromeLookRows.ts
6537
+ function buildSiteChromeLookModels(input) {
6538
+ const selection = input.state.previewed;
6539
+ const common = {
6540
+ siteStyle: input.siteStyle,
6541
+ buttonPersonalityId: selection.buttonPersonalityId,
6542
+ paletteVariantId: selection.paletteVariantId,
6543
+ paletteOverrides: selection.paletteOverrides,
6544
+ headerLayoutId: selection.headerLayoutId,
6545
+ headerStyleId: selection.headerStyleId,
6546
+ headerLookId: selection.headerLookId,
6547
+ footerLookId: selection.footerLookId,
6548
+ headerSelectionContext: input.headerSelectionContext,
6549
+ footerSelectionContext: input.footerSelectionContext
6550
+ };
6551
+ const current = compileSiteThemeFromSelections(common);
6552
+ const renderedHeaderLayoutId = current.resolvedSelections.headerLayoutId;
6553
+ const renderedHeaderStyleId = current.resolvedSelections.headerStyleId;
6554
+ return {
6555
+ headerLayout: {
6556
+ renderedLayoutId: renderedHeaderLayoutId,
6557
+ options: getOrderedHeaderLayoutIdsForCuration(input.siteStyle).map(
6558
+ (layoutId) => {
6559
+ const layout = getHeaderLayout(layoutId);
6560
+ return {
6561
+ id: layoutId,
6562
+ label: layout?.label ?? layoutId,
6563
+ description: layout?.description,
6564
+ available: true
6565
+ };
6566
+ }
6567
+ )
6568
+ },
6569
+ headerStyle: {
6570
+ renderedStyleId: renderedHeaderStyleId,
6571
+ options: getOrderedHeaderStyleIdsForLayout(
6572
+ input.siteStyle,
6573
+ selection.headerLayoutId
6574
+ ).map((styleId) => {
6575
+ const style = getHeaderStyle(styleId);
6576
+ return {
6577
+ id: styleId,
6578
+ label: style?.label ?? styleId,
6579
+ description: style?.description,
6580
+ available: true
6581
+ };
6582
+ })
6583
+ },
6584
+ footer: {
6585
+ renderedLookId: current.resolvedSelections.footerLookId,
6586
+ options: [
6587
+ {
6588
+ id: null,
6589
+ label: "Site style default",
6590
+ description: "Use the footer chosen for this site style.",
6591
+ available: true
6592
+ },
6593
+ ...footerLookCatalog.map((look) => {
6594
+ const result = compileSiteThemeFromSelections({
6595
+ ...common,
6596
+ footerLookId: look.id
6597
+ });
6598
+ const rejected = result.footerSelection.rejected.find(
6599
+ (item) => item.lookId === look.id
6600
+ );
6601
+ return {
6602
+ id: look.id,
6603
+ label: look.label,
6604
+ description: rejected ? rejected.reasons.map(footerLookRejectionReasonLabel).join(" ") : void 0,
6605
+ available: !rejected
6606
+ };
6607
+ })
6608
+ ],
6609
+ diagnostics: diagnosticsForRequestedFooterSelection(
6610
+ selection.footerLookId,
6611
+ current.resolvedSelections.footerLookId,
6612
+ current.footerSelection.rejected
6613
+ )
7447
6614
  }
6615
+ };
6616
+ }
6617
+ function cycleSiteChromeLookId(options, currentId, direction) {
6618
+ const cycleable = cycleableSiteChromeOptions(options);
6619
+ if (cycleable.length <= 1) return void 0;
6620
+ const currentIndex = cycleable.findIndex((option) => option.id === currentId);
6621
+ const safeIndex = currentIndex === -1 ? 0 : currentIndex;
6622
+ const nextIndex = direction === "next" ? (safeIndex + 1) % cycleable.length : safeIndex === 0 ? cycleable.length - 1 : safeIndex - 1;
6623
+ return cycleable[nextIndex]?.id;
6624
+ }
6625
+ function cycleableSiteChromeOptions(options) {
6626
+ return options.filter((option) => option.available);
6627
+ }
6628
+ function planSiteChromeLookSelection(command) {
6629
+ if (command.surface === "header") {
6630
+ const headerLookId = findLegacyHeaderLookIdForSelection({
6631
+ layoutId: command.layoutId,
6632
+ styleId: command.styleId
6633
+ });
6634
+ const lookCommand2 = headerLookId ? {
6635
+ kind: "select",
6636
+ surface: { kind: "site-header" },
6637
+ lookId: headerLookId
6638
+ } : {
6639
+ kind: "clear",
6640
+ surface: { kind: "site-header" }
6641
+ };
6642
+ return {
6643
+ kind: "header",
6644
+ writePlan: writePlanForLookCustomizationCommand(lookCommand2),
6645
+ next: {
6646
+ headerLayoutId: command.layoutId,
6647
+ headerStyleId: command.styleId,
6648
+ headerLookId
6649
+ }
6650
+ };
7448
6651
  }
6652
+ const lookCommand = command.lookId ? {
6653
+ kind: "select",
6654
+ surface: { kind: "site-footer" },
6655
+ lookId: command.lookId
6656
+ } : {
6657
+ kind: "clear",
6658
+ surface: { kind: "site-footer" }
6659
+ };
6660
+ return {
6661
+ kind: "footer",
6662
+ writePlan: writePlanForLookCustomizationCommand(lookCommand),
6663
+ next: { footerLookId: command.lookId }
6664
+ };
7449
6665
  }
7450
- function primaryFontFamily(cssFamily) {
7451
- return cssFamily.split(",")[0]?.trim().replace(/^["']|["']$/gu, "") || cssFamily;
6666
+ function diagnosticsForRequestedFooterSelection(requestedLookId, renderedLookId, rejected) {
6667
+ if (!requestedLookId || requestedLookId === renderedLookId) return [];
6668
+ const rejectedRequest = rejected.find(
6669
+ (item) => item.lookId === requestedLookId
6670
+ );
6671
+ return rejectedRequest?.reasons.map(footerLookRejectionReasonLabel) ?? [];
7452
6672
  }
7453
- function uniqueFontFamilies(families) {
7454
- const seen = /* @__PURE__ */ new Set();
7455
- const result = [];
7456
- for (const family of families) {
7457
- const normalized = primaryFontFamily(family);
7458
- const key = normalized.toLowerCase();
7459
- if (seen.has(key)) continue;
7460
- seen.add(key);
7461
- result.push(normalized);
6673
+
6674
+ // ../preview-next/src/client/preview/typographySelectionInput.ts
6675
+ function typographySelectionInputForSnapshot(snapshot, theme) {
6676
+ if (snapshot.typographyPresetId === null && theme?.typography) {
6677
+ return {
6678
+ typographySelection: {
6679
+ kind: "custom",
6680
+ typography: theme.typography
6681
+ }
6682
+ };
7462
6683
  }
7463
- return result;
6684
+ return {
6685
+ typographyPresetId: snapshot.typographyPresetId,
6686
+ typographyBodyFontOverride: snapshot.typographyBodyFontOverride,
6687
+ typographyHeadingFontOverride: snapshot.typographyHeadingFontOverride
6688
+ };
7464
6689
  }
7465
- function normalizeFontFamilyOverride(value) {
7466
- const trimmed = value.trim();
7467
- return trimmed.length > 0 ? trimmed : null;
6690
+
6691
+ // ../preview-next/src/client/preview/styleConfiguratorSnapshot.ts
6692
+ function resolveStyleConfiguratorInitialSnapshot(input) {
6693
+ const themeSiteStyleId = siteStyleIdFromTheme(input.theme);
6694
+ if (!themeSiteStyleId) return null;
6695
+ const siteStyle = getCuratedSiteStyle(themeSiteStyleId);
6696
+ if (!siteStyle) return null;
6697
+ const compiled = compileSiteThemeFromSelections({
6698
+ siteStyle,
6699
+ buttonPersonalityId: input.initialSelections.buttonPersonalityId,
6700
+ paletteVariantId: input.initialSelections.paletteVariantId,
6701
+ paletteOverrides: input.initialSelections.paletteOverrides,
6702
+ headerLayoutId: input.initialSelections.headerLayoutId ?? null,
6703
+ headerStyleId: input.initialSelections.headerStyleId ?? null,
6704
+ ...typographySelectionInputForSnapshot(input.initialSelections, input.theme),
6705
+ headerLookId: input.initialSelections.headerLookId ?? null,
6706
+ footerLookId: input.initialSelections.footerLookId ?? null,
6707
+ headerSelectionContext: input.headerSelectionContext,
6708
+ footerSelectionContext: input.footerSelectionContext
6709
+ });
6710
+ const paletteChanged = input.initialSelections.paletteVariantId !== null && compiled.resolvedSelections.paletteVariantId !== input.initialSelections.paletteVariantId;
6711
+ return {
6712
+ siteStyleId: siteStyle.id,
6713
+ selectionVersion: input.initialSelections.selectionVersion ?? 0,
6714
+ selectionUpdatedAt: input.initialSelections.selectionUpdatedAt ?? null,
6715
+ headerLayoutId: compiled.resolvedSelections.headerLayoutId,
6716
+ headerStyleId: compiled.resolvedSelections.headerStyleId,
6717
+ headerLookId: compiled.requestedSelections.headerLookId,
6718
+ footerLookId: compiled.requestedSelections.footerLookId,
6719
+ buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
6720
+ paletteVariantId: compiled.resolvedSelections.paletteVariantId,
6721
+ paletteOverrides: paletteChanged ? null : input.initialSelections.paletteOverrides,
6722
+ typographyPresetId: compiled.resolvedSelections.typographyPresetId,
6723
+ typographyBodyFontOverride: compiled.resolvedSelections.typographyBodyFontOverride,
6724
+ typographyHeadingFontOverride: compiled.resolvedSelections.typographyHeadingFontOverride
6725
+ };
7468
6726
  }
7469
- function SiteChromeLookRow({
7470
- label,
7471
- ariaLabel,
7472
- defaultDescription,
7473
- catalog,
7474
- options = [
7475
- {
7476
- id: null,
7477
- label: "Site style default",
7478
- description: defaultDescription,
7479
- available: true
6727
+
6728
+ // ../preview-next/src/client/preview/siteStylePreviewPlanner.ts
6729
+ function planSiteStylePreviewChange(input) {
6730
+ const compiled = compileSiteThemeFromSelections({
6731
+ siteStyle: input.siteStyle,
6732
+ buttonPersonalityId: input.selection.buttonPersonalityId,
6733
+ paletteVariantId: input.selection.paletteVariantId,
6734
+ paletteOverrides: input.selection.paletteOverrides,
6735
+ ...typographySelectionInputForSnapshot(input.selection, input.baseTheme),
6736
+ headerLayoutId: input.selection.headerLayoutId,
6737
+ headerStyleId: input.selection.headerStyleId,
6738
+ headerLookId: input.selection.headerLookId,
6739
+ footerLookId: input.selection.footerLookId,
6740
+ headerSelectionContext: input.headerSelectionContext,
6741
+ footerSelectionContext: input.footerSelectionContext
6742
+ });
6743
+ return {
6744
+ themeOverlay: compiled.theme,
6745
+ resolvedSelections: compiled.resolvedSelections,
6746
+ chromeSelection: {
6747
+ headerLookId: input.selection.headerLookId,
6748
+ footerLookId: input.selection.footerLookId
7480
6749
  },
7481
- ...(catalog ?? []).map((look) => ({ ...look, available: true }))
6750
+ warnings: compiled.warnings
6751
+ };
6752
+ }
6753
+
6754
+ // ../preview-next/src/client/blocks/previewCustomizeControlPlan.ts
6755
+ var ABSOLUTE_MAX_QUICK_FIELDS = 3;
6756
+ var QUICK_FIELD_PRIORITY_BY_BLOCK_KIND = {
6757
+ "block.testimonials": [
6758
+ "avatarLayout",
6759
+ "gridColumns",
6760
+ "slidesToShow",
6761
+ "maxEntries",
6762
+ "autoplay"
7482
6763
  ],
7483
- diagnostics,
7484
- scopeDescription,
7485
- expanded,
7486
- onToggle,
7487
- currentId,
7488
- currentLabel,
7489
- onCycle,
7490
- onPreview,
7491
- disabled
6764
+ "block.image-gallery": [
6765
+ "aspectRatio",
6766
+ "slidesToShow",
6767
+ "fullBleed",
6768
+ "marqueeSpeed",
6769
+ "marqueePauseOnHover"
6770
+ ],
6771
+ "block.event-calendar": [
6772
+ "listLayout",
6773
+ "eventsPerPage",
6774
+ "showFilters",
6775
+ "calendarView",
6776
+ "eventSurface"
6777
+ ],
6778
+ "block.event-listing": [
6779
+ "layout",
6780
+ "columns",
6781
+ "eventsPerPage",
6782
+ "showFilters",
6783
+ "eventSurface"
6784
+ ]
6785
+ };
6786
+ function createPreviewCustomizeControlPlan({
6787
+ blockKind,
6788
+ fields,
6789
+ content,
6790
+ maxQuickFields = ABSOLUTE_MAX_QUICK_FIELDS
7492
6791
  }) {
7493
- return /* @__PURE__ */ jsxs(StyleRowFrame, { label, currentLabel, expanded, onToggle, children: [
7494
- /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-start justify-between gap-3", children: [
7495
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: scopeDescription }),
7496
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
7497
- /* @__PURE__ */ jsx(
7498
- "button",
7499
- {
7500
- type: "button",
7501
- onClick: () => onCycle("prev"),
7502
- className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
7503
- "aria-label": `Previous ${label.toLowerCase()}`,
7504
- disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
7505
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
7506
- }
7507
- ),
7508
- /* @__PURE__ */ jsx(
7509
- "button",
7510
- {
7511
- type: "button",
7512
- onClick: () => onCycle("next"),
7513
- className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
7514
- "aria-label": `Next ${label.toLowerCase()}`,
7515
- disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
7516
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
7517
- }
7518
- )
7519
- ] })
7520
- ] }),
7521
- 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,
7522
- /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": ariaLabel, className: "list-none space-y-1 p-0", children: options.map((look) => /* @__PURE__ */ jsx(
7523
- PickerOption,
7524
- {
7525
- selected: look.id === currentId,
7526
- label: look.label,
7527
- description: look.description,
7528
- onSelect: () => onPreview(look.id),
7529
- disabled: disabled || !look.available
7530
- },
7531
- look.id ?? "site-style-default"
7532
- )) })
7533
- ] });
7534
- }
7535
- function PaletteRow({ expanded, onToggle, siteStyle, currentId, currentLabel, currentSwatch, onPreview, disabled }) {
7536
- const options = useMemo(
7537
- () => {
7538
- const recommended = requireEntriesForCuratedChoices(paletteVariants, siteStyle.paletteVariantChoices);
7539
- if (recommended.some((variant) => variant.id === currentId)) {
7540
- return recommended;
7541
- }
7542
- const current = findPaletteVariant(currentId);
7543
- return current ? [current, ...recommended] : recommended;
7544
- },
7545
- [currentId, siteStyle.paletteVariantChoices]
6792
+ const valueReader = createVisibilityValueReader(content);
6793
+ const quickPriority = QUICK_FIELD_PRIORITY_BY_BLOCK_KIND[blockKind] ?? [];
6794
+ const quickPriorityIndex = new Map(
6795
+ quickPriority.map((fieldId, index) => [fieldId, index])
7546
6796
  );
7547
- return /* @__PURE__ */ jsx(
7548
- StyleRowFrame,
7549
- {
7550
- label: "Palette",
7551
- currentLabel,
7552
- swatch: currentSwatch,
7553
- expanded,
7554
- onToggle,
7555
- children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Palette", className: "list-none space-y-1 p-0", children: options.map((variant) => {
7556
- const primaryHex = variant.colors.find((c) => c.name === "primary")?.hex;
7557
- return /* @__PURE__ */ jsx(
7558
- PickerOption,
7559
- {
7560
- selected: variant.id === currentId,
7561
- label: variant.name,
7562
- description: variant.description,
7563
- swatch: primaryHex,
7564
- onSelect: () => onPreview(variant.id),
7565
- disabled
7566
- },
7567
- variant.id
7568
- );
7569
- }) })
6797
+ const maxQuick = Math.max(0, Math.min(ABSOLUTE_MAX_QUICK_FIELDS, maxQuickFields));
6798
+ const visibleControls = [];
6799
+ const hiddenControls = [];
6800
+ for (const field of fields) {
6801
+ if (isThemeV2NormalEditorRawStyleFieldName(field.id)) {
6802
+ hiddenControls.push({
6803
+ fieldId: field.id,
6804
+ reason: "theme-v2-raw-style-field"
6805
+ });
6806
+ continue;
7570
6807
  }
7571
- );
7572
- }
7573
- function ColoursRow({ expanded, onToggle, variant, overrides, onTokenChange, onReset, disabled }) {
7574
- const mergedPalette = useMemo(() => {
7575
- return variant.colors.map((color) => ({
7576
- name: color.name,
7577
- hex: overrides?.[color.name] ?? color.hex
7578
- }));
7579
- }, [variant, overrides]);
7580
- const overrideCount = overrides ? Object.keys(overrides).length : 0;
7581
- const currentLabel = overrideCount > 0 ? `${overrideCount} colour${overrideCount === 1 ? "" : "s"} customised` : "Use variant colours";
7582
- return /* @__PURE__ */ jsx(
7583
- StyleRowFrame,
7584
- {
7585
- label: "Brand colours",
7586
- currentLabel,
7587
- expanded,
7588
- onToggle,
7589
- children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
7590
- /* @__PURE__ */ jsx(
7591
- PaletteTokenEditor,
7592
- {
7593
- palette: mergedPalette,
7594
- onChange: onTokenChange,
7595
- feedbackRoles: [],
7596
- surfaceRoles: [],
7597
- showContrastChecks: false
7598
- }
7599
- ),
7600
- overrideCount > 0 ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
7601
- Button,
7602
- {
7603
- type: "button",
7604
- variant: "outline",
7605
- size: "sm",
7606
- onClick: onReset,
7607
- disabled,
7608
- children: "Reset to variant"
7609
- }
7610
- ) }) : null
7611
- ] })
6808
+ if (isHeaderField(field)) {
6809
+ hiddenControls.push({ fieldId: field.id, reason: "header-field" });
6810
+ continue;
7612
6811
  }
7613
- );
7614
- }
7615
- function PickerOption({ selected, label, description, swatch, onSelect, disabled }) {
7616
- return /* @__PURE__ */ jsx("li", { className: "list-none", children: /* @__PURE__ */ jsxs(
7617
- "button",
7618
- {
7619
- type: "button",
7620
- role: "radio",
7621
- "aria-checked": selected,
7622
- onClick: onSelect,
7623
- disabled,
7624
- className: cn(
7625
- "flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left transition",
7626
- selected ? "border-foreground bg-accent/40 shadow-sm" : "border-transparent bg-background hover:border-foreground/40 hover:bg-accent/20",
7627
- disabled && "cursor-not-allowed opacity-60"
7628
- ),
7629
- children: [
7630
- swatch ? /* @__PURE__ */ jsx(
7631
- "span",
7632
- {
7633
- className: "h-5 w-5 shrink-0 rounded-full border border-black/10",
7634
- style: { backgroundColor: swatch },
7635
- "aria-hidden": "true"
7636
- }
7637
- ) : null,
7638
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
7639
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: label }),
7640
- description ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: description }) : null
7641
- ] })
7642
- ]
6812
+ const intent = getFieldIntent(field);
6813
+ if (intent !== "display" && intent !== "behavior") {
6814
+ hiddenControls.push({
6815
+ fieldId: field.id,
6816
+ reason: "unsupported-intent"
6817
+ });
6818
+ continue;
7643
6819
  }
7644
- ) });
7645
- }
7646
- function classifyStyleApplyError(error) {
7647
- if (isRecord(error)) {
7648
- const status = error.status;
7649
- const code = error.code;
7650
- if (status === 409 || code === "resource:conflict") {
7651
- return {
7652
- kind: "stale",
7653
- message: "This design changed elsewhere. Refreshing the latest version now."
7654
- };
6820
+ if (!evaluateVisibility(field, valueReader, field.id)) {
6821
+ hiddenControls.push({ fieldId: field.id, reason: "not-visible" });
6822
+ continue;
7655
6823
  }
6824
+ visibleControls.push({
6825
+ kind: "manifest-field",
6826
+ field,
6827
+ placement: "panel",
6828
+ intent
6829
+ });
7656
6830
  }
6831
+ const quickFieldIds = new Set(
6832
+ visibleControls.filter((control) => quickPriorityIndex.has(control.field.id)).sort(
6833
+ (left, right) => (quickPriorityIndex.get(left.field.id) ?? Number.MAX_SAFE_INTEGER) - (quickPriorityIndex.get(right.field.id) ?? Number.MAX_SAFE_INTEGER)
6834
+ ).slice(0, maxQuick).map((control) => control.field.id)
6835
+ );
7657
6836
  return {
7658
- kind: "other",
7659
- message: error instanceof Error ? error.message : "Unable to apply your selection."
6837
+ quickControls: visibleControls.filter((control) => quickFieldIds.has(control.field.id)).map((control) => ({ ...control, placement: "quick" })),
6838
+ panelControls: visibleControls.filter(
6839
+ (control) => !quickFieldIds.has(control.field.id)
6840
+ ),
6841
+ hiddenControls
7660
6842
  };
7661
6843
  }
7662
- function isRecord(value) {
7663
- return typeof value === "object" && value !== null;
7664
- }
7665
- function WarningNotice({ warnings, onDismiss }) {
7666
- return /* @__PURE__ */ jsxs("div", { role: "status", className: "rounded-md border border-warning/40 bg-warning/10 px-3 py-2 text-xs text-warning-foreground", children: [
7667
- /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: warnings.map((warning, index) => /* @__PURE__ */ jsx("li", { children: describeWarning(warning) }, `${warning.kind}-${index}`)) }),
7668
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, className: "mt-2 underline", children: "Dismiss" })
7669
- ] });
7670
- }
7671
- function NoticeBanner({ notices, onDismiss }) {
7672
- return /* @__PURE__ */ jsxs("div", { role: "status", className: "rounded-md border border-muted-foreground/20 bg-muted/40 px-3 py-2 text-xs", children: [
7673
- /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: notices.map((notice, index) => /* @__PURE__ */ jsx("li", { children: describeNotice(notice) }, `${notice.kind}-${index}`)) }),
7674
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, className: "mt-2 underline", children: "Dismiss" })
7675
- ] });
6844
+ function createVisibilityValueReader(content) {
6845
+ return {
6846
+ getValue: (path) => getValueAtPath(content, path)
6847
+ };
7676
6848
  }
7677
- function describeNotice(notice) {
7678
- switch (notice.kind) {
7679
- case "palette-overrides-hidden":
7680
- return "Custom colours are saved for the previous palette.";
7681
- case "palette-overrides-restored":
7682
- return "Custom colours restored for this palette.";
7683
- }
7684
- }
7685
- function describeWarning(warning) {
7686
- switch (warning.kind) {
7687
- case "rejected-personality":
7688
- return `Button style changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
7689
- case "rejected-palette":
7690
- return `Palette changed to ${warning.fellBackTo} because ${warning.requested} is not available.`;
7691
- case "rejected-typography-preset":
7692
- return `Typography changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
7693
- default: {
7694
- const exhaustive = warning;
7695
- return exhaustive;
6849
+ function getValueAtPath(source, path) {
6850
+ if (!path) return source;
6851
+ let current = source;
6852
+ for (const segment of path.split(".")) {
6853
+ if (!current || typeof current !== "object") {
6854
+ return void 0;
7696
6855
  }
6856
+ current = current[segment];
7697
6857
  }
6858
+ return current;
7698
6859
  }
7699
- function PreviewEditorSidebar({
7700
- surfaceId,
7701
- siteId,
7702
- dashboardBaseUrl = null,
7703
- pageId,
7704
- entryData,
7705
- contentTypeSchema,
7706
- referenceContentTypeKeys = [],
7707
- draftContent,
7708
- currentTemplate,
6860
+
6861
+ // ../preview-next/src/client/blocks/useBlockCustomizeControls.ts
6862
+ var MAX_CUSTOMIZE_POPOVER_ROWS = 6;
6863
+ var MAX_QUICK_MANIFEST_FIELDS = 3;
6864
+ function useBlockCustomizeControls({
6865
+ blockId,
6866
+ blockKind,
6867
+ content,
6868
+ uiStore,
7709
6869
  theme,
7710
- themeId = null,
7711
- themeSelectionId = null,
7712
- themeSelectionVersion = null,
7713
- themeSelectionUpdatedAt = null,
7714
- themeSelectionButtonPersonalityId = null,
7715
- themeSelectionPaletteVariantId = null,
7716
- themeSelectionPaletteOverrides = null,
7717
- themeSelectionHeaderLayoutId = null,
7718
- themeSelectionHeaderStyleId = null,
7719
- themeSelectionTypographyPresetId = null,
7720
- themeSelectionTypographyBodyFontOverride = null,
7721
- themeSelectionTypographyHeadingFontOverride = null,
7722
- themeSelectionHeaderLookId = null,
7723
- themeSelectionFooterLookId = null,
7724
- themeSelectionHeaderContext,
7725
- themeSelectionFooterContext,
7726
- designRowRequest = null,
7727
- apiClient,
7728
- bearerToken,
7729
- supabaseUrl,
7730
- entrySubrouteContext,
7731
- assistantPanel,
7732
6870
  sdkConfig,
7733
- mediaAssets,
7734
- siteStyleAccess = { kind: "hidden" },
7735
- activeTab,
7736
- onTabChange,
7737
- onClose,
7738
- viewportMode,
7739
- viewportWidth,
7740
- zoom,
7741
- onViewportModeChange,
7742
- onViewportWidthChange,
7743
- onZoomChange
6871
+ maxQuickFields = MAX_QUICK_MANIFEST_FIELDS,
6872
+ maxPopoverRows,
6873
+ builtInPopoverRows = 0
7744
6874
  }) {
7745
- const controller = useEntryController();
7746
- const controllerBlocks = controller.blocks;
7747
- const uiStore = useEditorUiStoreInstance();
7748
- const uiStoreForBlockEditor = useMemo(
7749
- () => ({
7750
- getState: uiStore.getState,
7751
- subscribe: (listener) => uiStore.subscribe(() => listener())
7752
- }),
7753
- [uiStore]
7754
- );
7755
- const focusRequest = useUISelector(uiStore, (s) => s.focusRequest);
7756
- const { canEdit } = usePreviewAuth();
7757
- const dashboardSetupHandoff = useMemo(
7758
- () => resolvePreviewDashboardSetupHandoff(),
7759
- []
7760
- );
7761
- const dashboardContinueUrl = useMemo(
7762
- () => buildPreviewDashboardSetupUrl(dashboardBaseUrl, dashboardSetupHandoff),
7763
- [dashboardBaseUrl, dashboardSetupHandoff]
7764
- );
7765
- const sidebarChrome = useMemo(
7766
- () => resolvePreviewSidebarChromeModel(surfaceId),
7767
- [surfaceId]
6875
+ const {
6876
+ blockFieldOptions,
6877
+ blockFieldExtensions,
6878
+ sdkConfig: contextSdkConfig
6879
+ } = useSdkConfig();
6880
+ const effectiveSdkConfig = sdkConfig ?? contextSdkConfig;
6881
+ const manifest = useMemo(
6882
+ () => resolveBlockManifest(blockKind, effectiveSdkConfig ?? null),
6883
+ [blockKind, effectiveSdkConfig]
7768
6884
  );
7769
- const editorGuidance = useMemo(
7770
- () => resolvePreviewEditorGuidanceModel({
7771
- surfaceId,
7772
- canEdit,
7773
- hasDashboardSetupHandoff: Boolean(dashboardSetupHandoff)
6885
+ const preparedManifest = useMemo(() => {
6886
+ if (!manifest) return null;
6887
+ return prepareBlockEditorManifest({
6888
+ manifest,
6889
+ blockKind,
6890
+ styleGroups: theme?.styleGroups,
6891
+ blockFieldOptions,
6892
+ blockFieldExtensions,
6893
+ visibilityPolicy: hideThemeV2RawStyleFields
6894
+ });
6895
+ }, [
6896
+ blockFieldExtensions,
6897
+ blockFieldOptions,
6898
+ blockKind,
6899
+ manifest,
6900
+ theme?.styleGroups
6901
+ ]);
6902
+ const fields = useMemo(() => {
6903
+ if (!preparedManifest) return [];
6904
+ return createManifestFormAdapter(preparedManifest.renderManifest).fields;
6905
+ }, [preparedManifest]);
6906
+ const preliminaryPlan = useMemo(
6907
+ () => createPreviewCustomizeControlPlan({
6908
+ blockKind,
6909
+ fields,
6910
+ content,
6911
+ maxQuickFields
7774
6912
  }),
7775
- [canEdit, dashboardSetupHandoff, surfaceId]
6913
+ [blockKind, content, fields, maxQuickFields]
7776
6914
  );
7777
- const showAssistantTab = previewAssistantEnabled && Boolean(assistantPanel);
7778
- const assistantAvailable = showAssistantTab && Boolean(pageId && bearerToken && canEdit);
7779
- const effectiveActiveTab = showAssistantTab ? resolvePreviewSidebarTab(activeTab) : "editor";
7780
- const blockItems = useMemo(
7781
- () => controllerBlocks.list,
7782
- [controllerBlocks.list]
6915
+ const budgetedMaxQuickFields = resolveQuickFieldBudget({
6916
+ requestedMaxQuickFields: maxQuickFields,
6917
+ maxPopoverRows,
6918
+ builtInPopoverRows,
6919
+ hasOverflowControls: preliminaryPlan.panelControls.length > 0
6920
+ });
6921
+ const controlPlan = useMemo(() => {
6922
+ if (budgetedMaxQuickFields === maxQuickFields) {
6923
+ return preliminaryPlan;
6924
+ }
6925
+ return createPreviewCustomizeControlPlan({
6926
+ blockKind,
6927
+ fields,
6928
+ content,
6929
+ maxQuickFields: budgetedMaxQuickFields
6930
+ });
6931
+ }, [
6932
+ blockKind,
6933
+ budgetedMaxQuickFields,
6934
+ content,
6935
+ fields,
6936
+ maxQuickFields,
6937
+ preliminaryPlan
6938
+ ]);
6939
+ const quickFields = useMemo(
6940
+ () => controlPlan.quickControls.map((control) => stripTopLevelRoutingUi(control.field)),
6941
+ [controlPlan.quickControls]
7783
6942
  );
7784
- const blockEditorRef = useRef(null);
7785
- const contentFormWidgets = useMemo(() => previewWidgetOverrides, []);
7786
- const handleBlocksChange = useCallback(
7787
- (nextItems) => {
7788
- controllerBlocks.actions.setList(nextItems);
7789
- const drafts = uiStore.getState().drafts;
7790
- const extractData = (content) => {
7791
- if (!content || typeof content !== "object") return null;
7792
- const obj = content;
7793
- if ("data" in obj && obj.data && typeof obj.data === "object") {
7794
- return obj.data;
7795
- }
7796
- return obj;
7797
- };
7798
- for (const block of nextItems) {
7799
- if (!block.id) continue;
7800
- const baseline = extractData(
7801
- block.content ?? block.draftContent ?? block.publishedContent ?? null
7802
- );
7803
- const blockId = unsafeAsEditorBlockId(block.id);
7804
- if (drafts.getBlockSnapshot(blockId)) {
7805
- drafts.updateBlockBaseline(blockId, baseline, { kind: block.kind });
7806
- } else {
7807
- drafts.initNewBlock(blockId, baseline, { kind: block.kind });
7808
- }
7809
- }
7810
- },
7811
- [controllerBlocks.actions, uiStore]
6943
+ const panelFields = useMemo(
6944
+ () => controlPlan.panelControls.map((control) => stripTopLevelRoutingUi(control.field)),
6945
+ [controlPlan.panelControls]
7812
6946
  );
7813
- useEffect(() => {
7814
- const req = focusRequest;
7815
- if (!req?.blockId) return;
7816
- blockEditorRef.current?.focusField({
7817
- blockId: req.blockId,
7818
- fieldPath: req.fieldPath ?? void 0,
7819
- itemIndex: req.itemIndex ?? void 0
6947
+ const backend = useMemo(() => {
6948
+ if (!preparedManifest) return null;
6949
+ return createStoreBackend({
6950
+ scope: "blocks",
6951
+ store: uiStore,
6952
+ blockId: unsafeAsEditorBlockId(blockId),
6953
+ validateFn: (values) => validateBlockManifestIssues(preparedManifest.validationManifest, values, "strict")
7820
6954
  });
7821
- uiStore.getState().clearFocus();
7822
- }, [focusRequest, uiStore]);
7823
- const metadataSectionContent = useMemo(() => {
7824
- if (!pageId && !entryData) return null;
7825
- return /* @__PURE__ */ jsx(PreviewMetadataPanel, { contentTypeKey: entryData?.type ?? null });
7826
- }, [entryData, pageId]);
7827
- const editorPanel = entryData ? /* @__PURE__ */ jsx(
7828
- EntryEditorPanel,
7829
- {
7830
- siteId,
7831
- entryData,
7832
- contentTypeSchema,
7833
- referenceContentTypeKeys,
7834
- draftContent,
7835
- currentTemplate,
7836
- theme,
7837
- apiClient,
7838
- bearerToken,
7839
- supabaseUrl,
7840
- entrySubrouteContext,
7841
- metadataPanel: metadataSectionContent,
7842
- mediaAssets,
7843
- widgets: contentFormWidgets,
7844
- viewportMode,
7845
- viewportWidth,
7846
- zoom,
7847
- onViewportModeChange,
7848
- onViewportWidthChange,
7849
- onZoomChange
7850
- }
7851
- ) : pageId ? /* @__PURE__ */ jsx(
7852
- PageBlockEditorPanel,
7853
- {
7854
- siteId,
7855
- pageId,
7856
- theme,
7857
- apiClient,
7858
- bearerToken,
7859
- supabaseUrl,
7860
- blockItems,
7861
- uiStoreForBlockEditor,
7862
- blockEditorRef,
7863
- onBlocksChange: handleBlocksChange,
7864
- metadataPanel: metadataSectionContent,
7865
- designPanel: canInspectSiteStyle(siteStyleAccess) ? /* @__PURE__ */ jsx(
7866
- StyleConfigurator,
7867
- {
7868
- siteId,
7869
- apiClient,
7870
- theme,
7871
- themeId,
7872
- selectionId: themeSelectionId,
7873
- selectionVersion: themeSelectionVersion,
7874
- selectionUpdatedAt: themeSelectionUpdatedAt,
7875
- initialSelections: {
7876
- selectionVersion: themeSelectionVersion,
7877
- selectionUpdatedAt: themeSelectionUpdatedAt,
7878
- buttonPersonalityId: themeSelectionButtonPersonalityId,
7879
- paletteVariantId: themeSelectionPaletteVariantId,
7880
- paletteOverrides: themeSelectionPaletteOverrides,
7881
- headerLayoutId: themeSelectionHeaderLayoutId,
7882
- headerStyleId: themeSelectionHeaderStyleId,
7883
- typographyPresetId: themeSelectionTypographyPresetId,
7884
- typographyBodyFontOverride: themeSelectionTypographyBodyFontOverride,
7885
- typographyHeadingFontOverride: themeSelectionTypographyHeadingFontOverride,
7886
- headerLookId: themeSelectionHeaderLookId,
7887
- footerLookId: themeSelectionFooterLookId
7888
- },
7889
- headerSelectionContext: themeSelectionHeaderContext,
7890
- footerSelectionContext: themeSelectionFooterContext,
7891
- expandedRowRequest: designRowRequest,
7892
- siteStyleAccess
7893
- }
7894
- ) : null,
7895
- sdkConfig: sdkConfig ?? null,
7896
- mediaAssets,
7897
- viewportMode,
7898
- viewportWidth,
7899
- zoom,
7900
- onViewportModeChange,
7901
- onViewportWidthChange,
7902
- onZoomChange
7903
- }
7904
- ) : /* @__PURE__ */ jsx("div", { className: "flex h-full flex-col", children: /* @__PURE__ */ jsx("div", { className: "flex-1 p-4", children: /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-4 py-6 text-sm text-muted-foreground", children: "Block editing is only available when a page is selected." }) }) });
7905
- const assistantPanelDisplay = assistantAvailable ? assistantPanel : /* @__PURE__ */ jsx("div", { className: "flex h-full flex-col items-center justify-center p-6 text-center text-sm text-muted-foreground", children: "AI Assistant is available for pages in preview mode." });
7906
- return /* @__PURE__ */ jsx(
7907
- "div",
7908
- {
7909
- className: `flex h-full min-h-0 flex-col overflow-hidden pb-[calc(var(--builder-editor-chrome-offset,0px)+var(--builder-preview-banner-offset,0px))] ${sidebarChrome.rootClassName}`,
7910
- "data-preview-sidebar-tone": sidebarChrome.tone,
7911
- children: /* @__PURE__ */ jsxs(
7912
- Tabs,
7913
- {
7914
- value: effectiveActiveTab,
7915
- onValueChange: (value) => onTabChange(
7916
- resolvePreviewSidebarTab(value)
7917
- ),
7918
- className: "flex h-full min-h-0 flex-col overflow-hidden",
7919
- children: [
7920
- /* @__PURE__ */ jsxs(
7921
- "div",
7922
- {
7923
- className: `shrink-0 flex items-center justify-between px-4 py-3 ${sidebarChrome.headerClassName}`,
7924
- children: [
7925
- /* @__PURE__ */ jsxs(TabsList, { className: "h-8", children: [
7926
- /* @__PURE__ */ jsx(TabsTrigger, { value: "editor", children: "Editor" }),
7927
- showAssistantTab ? /* @__PURE__ */ jsx(TabsTrigger, { value: "assistant", children: "AI Assistant" }) : null
7928
- ] }),
7929
- /* @__PURE__ */ jsx(
7930
- Button,
7931
- {
7932
- variant: "ghost",
7933
- size: "icon",
7934
- onClick: onClose,
7935
- "aria-label": "Close sidebar",
7936
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
7937
- }
7938
- )
7939
- ]
7940
- }
7941
- ),
7942
- editorGuidance ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-3", children: /* @__PURE__ */ jsx(
7943
- PreviewEditorGuidanceCard,
7944
- {
7945
- guidance: editorGuidance,
7946
- completeSetupDisabled: !dashboardContinueUrl,
7947
- onCompleteSetup: () => {
7948
- if (!dashboardContinueUrl || typeof window === "undefined")
7949
- return;
7950
- window.location.href = dashboardContinueUrl;
7951
- }
7952
- }
7953
- ) }) : null,
7954
- /* @__PURE__ */ jsx(
7955
- TabsContent,
7956
- {
7957
- value: "editor",
7958
- forceMount: true,
7959
- className: "mt-0 min-h-0 flex-1 overflow-hidden data-[state=inactive]:hidden",
7960
- children: editorPanel
7961
- }
7962
- ),
7963
- showAssistantTab ? /* @__PURE__ */ jsx(
7964
- TabsContent,
7965
- {
7966
- value: "assistant",
7967
- forceMount: true,
7968
- className: "mt-0 min-h-0 flex-1 overflow-hidden data-[state=inactive]:hidden",
7969
- children: assistantPanelDisplay
7970
- }
7971
- ) : null
7972
- ]
7973
- }
7974
- )
7975
- }
7976
- );
6955
+ }, [blockId, preparedManifest, uiStore]);
6956
+ return {
6957
+ manifest,
6958
+ controlPlan,
6959
+ quickFields,
6960
+ panelFields,
6961
+ backend,
6962
+ hasControls: quickFields.length > 0 || panelFields.length > 0,
6963
+ hasOverflowControls: panelFields.length > 0
6964
+ };
7977
6965
  }
7978
- function PreviewMetadataPanel({
7979
- contentTypeKey
6966
+ function resolveQuickFieldBudget({
6967
+ requestedMaxQuickFields,
6968
+ maxPopoverRows,
6969
+ builtInPopoverRows,
6970
+ hasOverflowControls
7980
6971
  }) {
7981
- const uiStore = useEditorUiStoreInstance();
7982
- const manifest = useMemo(() => {
7983
- return omitRouteTitleFieldFromManifest(
7984
- getRouteMetadataManifestForType(contentTypeKey)
7985
- );
7986
- }, [contentTypeKey]);
7987
- const validation = useMemo(
7988
- () => createManifestValidation(manifest),
7989
- [manifest]
7990
- );
7991
- const backend = useMemo(
7992
- () => createStoreBackend({
7993
- scope: "metadata",
7994
- store: uiStore,
7995
- validateFn: (values) => {
7996
- const result = validation.schema.safeParse(values);
7997
- if (!result.success) {
7998
- return result.error.issues;
7999
- }
8000
- return [];
8001
- }
8002
- }),
8003
- [uiStore, validation]
6972
+ const cappedRequested = Math.max(
6973
+ 0,
6974
+ Math.min(MAX_QUICK_MANIFEST_FIELDS, requestedMaxQuickFields)
8004
6975
  );
8005
- return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
8006
- ManifestForm,
8007
- {
8008
- manifest,
8009
- backend,
8010
- className: "space-y-4"
8011
- }
8012
- ) });
6976
+ if (typeof maxPopoverRows !== "number") {
6977
+ return cappedRequested;
6978
+ }
6979
+ const settingsRowCount = hasOverflowControls ? 1 : 0;
6980
+ const availableRows = maxPopoverRows - builtInPopoverRows - settingsRowCount;
6981
+ return Math.max(0, Math.min(cappedRequested, availableRows));
6982
+ }
6983
+ function resolveBlockManifest(blockKind, sdkConfig) {
6984
+ return getBlockManifestFromSdkConfig(blockKind, sdkConfig) ?? getBlockDefinition(blockKind)?.manifest ?? null;
8013
6985
  }
8014
6986
 
8015
- export { PreviewEditorSidebar };
6987
+ export { BUILT_IN_REFERENCE_KINDS, BlockEditor, ButtonVariantField, ButtonWidgetProvider, EntryPickerField, EntryPickerWidgetProvider, FileField, IconPickerField, LinkField, LinkWidgetProvider, MAX_CUSTOMIZE_POPOVER_ROWS, MAX_QUICK_MANIFEST_FIELDS, MediaField, PreviewBlockCustomizeSelectionProvider, PreviewDesignCoordinatorProvider, PreviewThemeOverlayProvider, ReferenceField, ReferenceWidgetProvider, RichTextField, RichTextWidgetProvider, applyResolvedSelections, buildPreviewDashboardSetupUrl, buildSiteChromeLookModels, canApplySiteStyle, canInspectSiteStyle, cancelPreview, clearTypographyFontOverrides, createOptimisticActionRunner, createStyleConfiguratorState, cycleSiteChromeLookId, cycleableSiteChromeOptions, dismissNotices, dismissWarnings, isPreviewingChange, mapContentSchemaToFormFields, markApplyFailed, markApplySaved, markApplySaving, openRow, parseReferenceKind, planSiteChromeLookSelection, planSiteStylePreviewChange, previewButtonPersonality, previewFooterLook, previewHeaderLayout, previewHeaderStyle, previewPaletteVariant, previewSiteStyle, previewTypographyPreset, resolvePreviewDashboardSetupHandoff, resolvePreviewEditorGuidanceModel, resolvePreviewSetupBannerModel, resolvePreviewSidebarChromeModel, resolveSiteStyleAccess, resolveStyleConfiguratorInitialSnapshot, sanitizeDashboardPath, setPaletteOverrides, setTypographyBodyFontOverride, setTypographyHeadingFontOverride, syncStyleConfiguratorState, toggleRow, useBlockCustomizeControls, usePreviewBlockCustomizeSelectionOptional, usePreviewDesignCoordinator, usePreviewThemeOverlay, useRichTextImagePicker };