@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
@@ -0,0 +1,2655 @@
1
+ "use client";
2
+ import { resolvePreviewDashboardSetupHandoff, buildPreviewDashboardSetupUrl, resolvePreviewSidebarChromeModel, resolvePreviewEditorGuidanceModel, ButtonVariantField, IconPickerField, EntryPickerField, ReferenceField, RichTextField, MediaField, FileField, LinkField, canInspectSiteStyle, mapContentSchemaToFormFields, usePreviewBlockCustomizeSelectionOptional, BlockEditor, 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, LinkWidgetProvider, ReferenceWidgetProvider, EntryPickerWidgetProvider, ButtonWidgetProvider, cycleableSiteChromeOptions, BUILT_IN_REFERENCE_KINDS, useRichTextImagePicker, RichTextWidgetProvider, useBlockCustomizeControls, MAX_QUICK_MANIFEST_FIELDS, parseReferenceKind } from './chunk-5NZGBSJF.mjs';
3
+ import { unwrapResponse, useEntryController, useEditorUiStoreInstance, useUISelector, resolvePreviewSidebarTab, BackgroundGradientField, BackgroundColorField, unsafeAsEditorBlockId, Tabs, TabsList, TabsTrigger, Button, X, TabsContent, omitRouteTitleFieldFromManifest, getRouteMetadataManifestForType, createManifestValidation, createStoreBackend, ManifestForm, previewAssistantEnabled, Collapsible, CollapsibleTrigger, ChevronRight, CollapsibleContent, ContentFieldsForm, getCuratedSiteStyle, paletteOverridesSchema, findButtonPersonality, findPaletteVariant, findTypographyPreset, getHeaderLayout, getHeaderStyle, findFooterLook, footerLookCatalog, foldSubroute, SUBROUTE_KINDS, Smartphone, Tablet, Monitor, Maximize2, cn, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, expectParsedBrandedId, parseSiteId, buildThemeRuntime, SdkConfigProvider, PaletteProvider, MediaEditorProvider, curatedSiteStyles, ChevronLeft, requireEntriesForCuratedChoices, buttonPersonalities, typographyPresets, paletteVariants, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, FormRenderer, evaluateTypographyFontSlot, Callout, CalloutDescription, Separator, Surface, ColorPicker, Input } from './chunk-GYXKAUFV.mjs';
4
+ import { usePreviewAuth, getDashboardUrl } from './chunk-BBLZSQEY.mjs';
5
+ import './chunk-R7POPVJR.mjs';
6
+ import { useMemo, useRef, useCallback, useEffect, useState, useTransition, useSyncExternalStore, useId } from 'react';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { useRouter } from 'next/navigation';
9
+
10
+ // ../editor-blocks/src/widgets/entry-picker/loaders.ts
11
+ function getStatusBadgeVariant(status) {
12
+ switch (status) {
13
+ case "published":
14
+ return "default";
15
+ case "draft":
16
+ return "secondary";
17
+ case "scheduled":
18
+ return "outline";
19
+ case "archived":
20
+ return "destructive";
21
+ default:
22
+ return "secondary";
23
+ }
24
+ }
25
+ function mapEntryPickerItems(entries, valueFrom) {
26
+ return entries.map((entry) => ({
27
+ value: valueFrom === "identifier" ? entry.identifier ?? entry.id : valueFrom === "slug" ? entry.slug ?? entry.id : entry.id,
28
+ label: entry.draftTitle || entry.title || entry.slug || entry.id,
29
+ id: entry.id,
30
+ identifier: entry.identifier ?? null,
31
+ slug: entry.slug ?? null,
32
+ badge: {
33
+ label: entry.status,
34
+ variant: getStatusBadgeVariant(entry.status)
35
+ }
36
+ }));
37
+ }
38
+ async function loadEntryPickerItems({
39
+ apiClient,
40
+ siteId,
41
+ contentType,
42
+ valueFrom
43
+ }) {
44
+ const result = await apiClient({
45
+ endpoint: "listContentEntries",
46
+ params: { siteId, type: contentType }
47
+ });
48
+ const { entries } = unwrapResponse(result);
49
+ return mapEntryPickerItems(entries, valueFrom);
50
+ }
51
+
52
+ // ../editor-blocks/src/widgets/link/loaders.ts
53
+ async function loadRoutableContentItems({
54
+ apiClient,
55
+ siteId
56
+ }) {
57
+ const result = await apiClient({
58
+ endpoint: "getRoutableContent",
59
+ params: { siteId }
60
+ });
61
+ const payload = unwrapResponse(result);
62
+ return Array.isArray(payload?.items) ? payload.items : [];
63
+ }
64
+
65
+ // ../editor-blocks/src/widgets/reference/dynamicAdapters.ts
66
+ function createDynamicReferenceAdapters(staticAdapters, createDynamicContentAdapter) {
67
+ return new Proxy(staticAdapters, {
68
+ get(target, prop) {
69
+ if (typeof prop !== "string") {
70
+ return void 0;
71
+ }
72
+ if (prop in target) {
73
+ return target[prop];
74
+ }
75
+ const parsed = parseReferenceKind(prop);
76
+ if (parsed.kind === "dynamicContent") {
77
+ return createDynamicContentAdapter(parsed.contentTypeKey);
78
+ }
79
+ return void 0;
80
+ }
81
+ });
82
+ }
83
+
84
+ // ../editor-blocks/src/widgets/reference/loaders.ts
85
+ function defineReferenceLoader(referenceKind, loader) {
86
+ return Object.assign(loader, { referenceKind });
87
+ }
88
+ function formatStatusBadge(status) {
89
+ if (status === "published") {
90
+ return { label: "Published", variant: "default" };
91
+ }
92
+ if (status === "scheduled") {
93
+ return { label: "Scheduled", variant: "secondary" };
94
+ }
95
+ if (status === "archived") {
96
+ return { label: "Archived", variant: "outline" };
97
+ }
98
+ return { label: "Draft", variant: "outline" };
99
+ }
100
+ function hasSlug(entry) {
101
+ return entry.slug !== null;
102
+ }
103
+ var loadPostReferenceItems = defineReferenceLoader("post", async ({ apiClient, siteId }) => {
104
+ const result = await apiClient({
105
+ endpoint: "listContentEntries",
106
+ params: { siteId, type: "post" }
107
+ });
108
+ const { entries } = unwrapResponse(result);
109
+ return entries.filter(hasSlug).filter((entry) => entry.status === "published").map((entry) => ({
110
+ value: entry.slug,
111
+ label: entry.title || entry.slug || entry.id,
112
+ badge: formatStatusBadge(entry.status)
113
+ }));
114
+ });
115
+ var loadFormReferenceItems = defineReferenceLoader("form", async ({ apiClient, siteId }) => {
116
+ const result = await apiClient({
117
+ endpoint: "listForms",
118
+ params: { siteId }
119
+ });
120
+ const { forms } = unwrapResponse(result);
121
+ return forms.map((form) => ({
122
+ value: form.slug,
123
+ label: form.name
124
+ }));
125
+ });
126
+ var loadBookingFormReferenceItems = defineReferenceLoader("bookingForm", async ({ apiClient, siteId }) => {
127
+ const result = await apiClient({
128
+ endpoint: "listBookingForms",
129
+ params: { siteId }
130
+ });
131
+ const { forms } = unwrapResponse(result);
132
+ return forms.map((form) => ({
133
+ value: form.id,
134
+ label: form.name
135
+ }));
136
+ });
137
+ var loadAppointmentServiceReferenceItems = defineReferenceLoader(
138
+ "appointmentService",
139
+ async ({ apiClient, siteId }) => {
140
+ const result = await apiClient({
141
+ endpoint: "getAppointmentServicesReference",
142
+ params: { siteId }
143
+ });
144
+ const { options } = unwrapResponse(result);
145
+ return options.map((option) => ({
146
+ value: option.id,
147
+ label: option.label
148
+ }));
149
+ }
150
+ );
151
+ var loadAppointmentResourceReferenceItems = defineReferenceLoader(
152
+ "appointmentResource",
153
+ async ({ apiClient, siteId }) => {
154
+ const result = await apiClient({
155
+ endpoint: "getAppointmentResourcesReference",
156
+ params: { siteId }
157
+ });
158
+ const { options } = unwrapResponse(result);
159
+ return options.map((option) => ({
160
+ value: option.id,
161
+ label: option.label
162
+ }));
163
+ }
164
+ );
165
+ var loadPageReferenceItems = defineReferenceLoader("page", async ({ apiClient, siteId }) => {
166
+ const result = await apiClient({
167
+ endpoint: "getRoutableContent",
168
+ params: { siteId }
169
+ });
170
+ const { items } = unwrapResponse(result);
171
+ return items.filter((item) => item.entityType === "page").map((page) => ({
172
+ value: page.entityId,
173
+ label: page.title || "Untitled"
174
+ }));
175
+ });
176
+ var loadNewsletterListReferenceItems = defineReferenceLoader(
177
+ "newsletterList",
178
+ async ({ apiClient, siteId }) => {
179
+ const result = await apiClient({
180
+ endpoint: "listNewsletterLists",
181
+ params: { siteId }
182
+ });
183
+ const { lists } = unwrapResponse(result);
184
+ return lists.map((list) => ({
185
+ value: list.id,
186
+ label: list.name
187
+ }));
188
+ }
189
+ );
190
+ async function loadDynamicContentReferenceItems({
191
+ apiClient,
192
+ siteId,
193
+ contentTypeKey
194
+ }) {
195
+ const result = await apiClient({
196
+ endpoint: "listContentEntries",
197
+ params: { siteId, type: contentTypeKey }
198
+ });
199
+ const { entries } = unwrapResponse(result);
200
+ return entries.filter((entry) => entry.status === "published").map((entry) => ({
201
+ value: entry.id,
202
+ label: entry.title || entry.slug || entry.id,
203
+ badge: { label: "Published", variant: "default" }
204
+ }));
205
+ }
206
+ var referenceLoaders = {
207
+ post: loadPostReferenceItems,
208
+ form: loadFormReferenceItems,
209
+ bookingForm: loadBookingFormReferenceItems,
210
+ appointmentService: loadAppointmentServiceReferenceItems,
211
+ appointmentResource: loadAppointmentResourceReferenceItems,
212
+ page: loadPageReferenceItems,
213
+ newsletterList: loadNewsletterListReferenceItems
214
+ };
215
+
216
+ // ../preview-next/src/client/widgets/widgetAdapters.ts
217
+ var routableContentCache = /* @__PURE__ */ new Map();
218
+ var entryPickerCache = /* @__PURE__ */ new Map();
219
+ var referenceItemsCache = /* @__PURE__ */ new Map();
220
+ var dynamicReferenceItemsCache = /* @__PURE__ */ new Map();
221
+ var MAX_CACHE_ENTRIES = 50;
222
+ function setBoundedCache(map, key, value) {
223
+ if (!map.has(key) && map.size >= MAX_CACHE_ENTRIES) {
224
+ const firstKey = map.keys().next().value;
225
+ if (firstKey !== void 0) {
226
+ map.delete(firstKey);
227
+ }
228
+ }
229
+ map.set(key, value);
230
+ }
231
+ function createCachedDynamicContentReferenceAdapter(contentTypeKey, previewClient, fallbackSiteId) {
232
+ return {
233
+ labels: {
234
+ loading: "Loading entries\u2026",
235
+ select: "Select an entry",
236
+ empty: "No entries found",
237
+ emptySelection: "Select an entry to continue."
238
+ },
239
+ loadItems: async ({ siteId }) => {
240
+ const resolvedSiteId = siteId || fallbackSiteId;
241
+ if (!resolvedSiteId) {
242
+ return [];
243
+ }
244
+ const cacheKey = `${resolvedSiteId}:content:${contentTypeKey}`;
245
+ const cached = dynamicReferenceItemsCache.get(cacheKey);
246
+ if (cached) return cached;
247
+ const promise = (async () => {
248
+ try {
249
+ return await loadDynamicContentReferenceItems({
250
+ apiClient: previewClient,
251
+ siteId: resolvedSiteId,
252
+ contentTypeKey
253
+ });
254
+ } catch (error) {
255
+ dynamicReferenceItemsCache.delete(cacheKey);
256
+ throw error;
257
+ }
258
+ })();
259
+ setBoundedCache(dynamicReferenceItemsCache, cacheKey, promise);
260
+ return promise;
261
+ }
262
+ };
263
+ }
264
+ function createCachedReferenceAdapterLoader(kind, loader, previewClient, fallbackSiteId) {
265
+ return async ({ siteId }) => {
266
+ const resolvedSiteId = siteId || fallbackSiteId;
267
+ if (!resolvedSiteId) {
268
+ return [];
269
+ }
270
+ const cacheKey = `${kind}:${resolvedSiteId}`;
271
+ const cached = referenceItemsCache.get(cacheKey);
272
+ if (cached) return cached;
273
+ const promise = (async () => {
274
+ try {
275
+ return await loader({ apiClient: previewClient, siteId: resolvedSiteId });
276
+ } catch (error) {
277
+ referenceItemsCache.delete(cacheKey);
278
+ throw error;
279
+ }
280
+ })();
281
+ setBoundedCache(referenceItemsCache, cacheKey, promise);
282
+ return promise;
283
+ };
284
+ }
285
+ function createPreviewLinkAdapters(previewClient) {
286
+ return {
287
+ fetchRoutableContent: async (currentSiteId) => {
288
+ const cached = routableContentCache.get(currentSiteId);
289
+ if (cached) return cached;
290
+ const promise = (async () => {
291
+ try {
292
+ return await loadRoutableContentItems({ apiClient: previewClient, siteId: currentSiteId });
293
+ } catch (error) {
294
+ routableContentCache.delete(currentSiteId);
295
+ throw error;
296
+ }
297
+ })();
298
+ setBoundedCache(routableContentCache, currentSiteId, promise);
299
+ return promise;
300
+ }
301
+ };
302
+ }
303
+ function createPreviewEntryPickerAdapter(previewClient) {
304
+ return {
305
+ loadItems: async ({ siteId, contentType, valueFrom }) => {
306
+ if (!siteId || !contentType) return [];
307
+ const cacheKey = `${siteId}:${contentType}:${valueFrom ?? "id"}`;
308
+ const cached = entryPickerCache.get(cacheKey);
309
+ if (cached) return cached;
310
+ const request = (async () => {
311
+ try {
312
+ return await loadEntryPickerItems({
313
+ apiClient: previewClient,
314
+ siteId,
315
+ contentType,
316
+ valueFrom
317
+ });
318
+ } catch (error) {
319
+ entryPickerCache.delete(cacheKey);
320
+ throw error;
321
+ }
322
+ })();
323
+ setBoundedCache(entryPickerCache, cacheKey, request);
324
+ return request;
325
+ },
326
+ labels: {
327
+ loading: "Loading entries...",
328
+ select: "Select an entry",
329
+ empty: "No entries found",
330
+ emptySelection: "Select an entry to embed.",
331
+ noContentType: "Select a content type first."
332
+ }
333
+ };
334
+ }
335
+ var previewReferenceChrome = {
336
+ post: {
337
+ labels: {
338
+ loading: "Loading posts\u2026",
339
+ select: "Select a post",
340
+ empty: "No posts found",
341
+ emptySelection: "Select a post to continue."
342
+ }
343
+ },
344
+ form: {
345
+ labels: {
346
+ loading: "Loading forms\u2026",
347
+ select: "Select a form",
348
+ empty: "No forms found",
349
+ emptySelection: "Select a form to continue."
350
+ }
351
+ },
352
+ bookingForm: {
353
+ labels: {
354
+ loading: "Loading booking forms\u2026",
355
+ select: "Select a booking form",
356
+ empty: "No booking forms yet",
357
+ emptySelection: "Select a booking form to enable appointment booking."
358
+ }
359
+ },
360
+ appointmentService: {
361
+ labels: {
362
+ loading: "Loading appointment services\u2026",
363
+ select: "Select a service",
364
+ empty: "No services available",
365
+ emptySelection: "Select a service to enable booking."
366
+ }
367
+ },
368
+ appointmentResource: {
369
+ labels: {
370
+ loading: "Loading resources\u2026",
371
+ select: "Select a resource",
372
+ empty: "No resources available",
373
+ emptySelection: "Select a resource (optional)."
374
+ }
375
+ },
376
+ page: {
377
+ labels: {
378
+ loading: "Loading pages\u2026",
379
+ select: "Select a page",
380
+ empty: "No pages available",
381
+ emptySelection: "Select a page."
382
+ }
383
+ },
384
+ newsletterList: {
385
+ labels: {
386
+ loading: "Loading lists\u2026",
387
+ select: "Select a list",
388
+ empty: "No lists yet",
389
+ emptySelection: "Select a newsletter list to subscribe users to."
390
+ }
391
+ }
392
+ };
393
+ function createPreviewReferenceAdapters(previewClient, fallbackSiteId) {
394
+ const brandedFallbackSiteId = void 0;
395
+ const adapters = {};
396
+ for (const kind of BUILT_IN_REFERENCE_KINDS) {
397
+ adapters[kind] = {
398
+ loadItems: createCachedReferenceAdapterLoader(
399
+ kind,
400
+ referenceLoaders[kind],
401
+ previewClient,
402
+ brandedFallbackSiteId
403
+ ),
404
+ labels: previewReferenceChrome[kind].labels
405
+ };
406
+ }
407
+ return createDynamicReferenceAdapters(
408
+ adapters,
409
+ (contentTypeKey) => createCachedDynamicContentReferenceAdapter(contentTypeKey, previewClient, brandedFallbackSiteId)
410
+ );
411
+ }
412
+ function PreviewFormProviderInner({ theme, children }) {
413
+ const { onPickImage, picker } = useRichTextImagePicker();
414
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
415
+ /* @__PURE__ */ jsx(RichTextWidgetProvider, { theme, onPickImage, children }),
416
+ picker
417
+ ] });
418
+ }
419
+ function PreviewFormProvider({
420
+ siteId: rawSiteId,
421
+ theme = null,
422
+ previewClient,
423
+ bearerToken,
424
+ supabaseUrl,
425
+ mediaAssets,
426
+ sdkConfig,
427
+ children
428
+ }) {
429
+ const siteId = useMemo(() => expectParsedBrandedId(parseSiteId(rawSiteId)), [rawSiteId]);
430
+ const linkAdapters = useMemo(() => createPreviewLinkAdapters(previewClient), [previewClient]);
431
+ const entryPickerAdapter = useMemo(() => createPreviewEntryPickerAdapter(previewClient), [previewClient]);
432
+ const referenceAdapters = useMemo(
433
+ () => createPreviewReferenceAdapters(previewClient),
434
+ [previewClient]
435
+ );
436
+ const paletteTokens = useMemo(() => {
437
+ if (!theme) return void 0;
438
+ try {
439
+ const runtime = buildThemeRuntime(theme);
440
+ return runtime.paletteTokens;
441
+ } catch {
442
+ return void 0;
443
+ }
444
+ }, [theme]);
445
+ return /* @__PURE__ */ jsx(SdkConfigProvider, { sdkConfig: sdkConfig ?? null, children: /* @__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(
446
+ MediaEditorProvider,
447
+ {
448
+ siteId,
449
+ apiClient: previewClient,
450
+ bearerToken,
451
+ supabaseUrl,
452
+ mediaAssets,
453
+ children: /* @__PURE__ */ jsx(ButtonWidgetProvider, { buttonSystem: theme?.buttons ?? null, children: /* @__PURE__ */ jsx(PreviewFormProviderInner, { theme, children }) })
454
+ }
455
+ ) }) }) }) }) });
456
+ }
457
+ var previewWidgetOverrides = {
458
+ link: LinkField,
459
+ file: FileField,
460
+ media: MediaField,
461
+ richText: RichTextField,
462
+ reference: ReferenceField,
463
+ backgroundColor: BackgroundColorField,
464
+ backgroundGradient: BackgroundGradientField,
465
+ entryPicker: EntryPickerField,
466
+ iconPicker: IconPickerField,
467
+ buttonVariant: ButtonVariantField
468
+ };
469
+ function PreviewZoomControls({ zoom, onZoomChange, disabled }) {
470
+ return /* @__PURE__ */ jsxs(
471
+ Select,
472
+ {
473
+ value: String(zoom),
474
+ onValueChange: (value) => onZoomChange(value === "fit" ? "fit" : Number(value)),
475
+ disabled,
476
+ children: [
477
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-20", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
478
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
479
+ /* @__PURE__ */ jsx(SelectItem, { value: "33", children: "33%" }),
480
+ /* @__PURE__ */ jsx(SelectItem, { value: "50", children: "50%" }),
481
+ /* @__PURE__ */ jsx(SelectItem, { value: "75", children: "75%" }),
482
+ /* @__PURE__ */ jsx(SelectItem, { value: "100", children: "100%" }),
483
+ /* @__PURE__ */ jsx(SelectItem, { value: "fit", children: "Fit" })
484
+ ] })
485
+ ]
486
+ }
487
+ );
488
+ }
489
+ var VIEWPORT_PRESETS = [
490
+ { label: "Mobile", width: 375, icon: Smartphone },
491
+ { label: "Tablet", width: 768, icon: Tablet },
492
+ { label: "Desktop", width: 1440, icon: Monitor },
493
+ { label: "Full Width", width: "full", icon: Maximize2 }
494
+ ];
495
+ function PreviewViewportControls({
496
+ mode,
497
+ width,
498
+ onModeChange,
499
+ onWidthChange,
500
+ className,
501
+ zoom,
502
+ onZoomChange
503
+ }) {
504
+ const activePreset = VIEWPORT_PRESETS.find((p) => p.width === width);
505
+ const ActiveIcon = activePreset?.icon ?? Monitor;
506
+ const activeLabel = mode === "squeeze" ? "Auto fit" : activePreset?.label ?? `${width}px`;
507
+ const showZoomControls = mode === "fixed" && zoom !== void 0 && onZoomChange !== void 0;
508
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
509
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
510
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "gap-2", children: [
511
+ /* @__PURE__ */ jsx(ActiveIcon, { className: "h-4 w-4" }),
512
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: activeLabel })
513
+ ] }) }),
514
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
515
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Preview size" }),
516
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
517
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onModeChange("squeeze"), className: cn(mode === "squeeze" && "bg-accent"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
518
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Auto fit" }),
519
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Matches the space beside the editor" })
520
+ ] }) }),
521
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onModeChange("fixed"), className: cn(mode === "fixed" && "bg-accent"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
522
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Choose size" }),
523
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Preview mobile, tablet, or desktop widths" })
524
+ ] }) })
525
+ ] })
526
+ ] }),
527
+ mode === "fixed" && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
528
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: activePreset?.label ?? `${width}px` }) }),
529
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
530
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Screen size" }),
531
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
532
+ VIEWPORT_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
533
+ DropdownMenuItem,
534
+ {
535
+ onClick: () => onWidthChange(preset.width),
536
+ className: cn(width === preset.width && "bg-accent"),
537
+ children: [
538
+ /* @__PURE__ */ jsx(preset.icon, { className: "mr-2 h-4 w-4" }),
539
+ preset.label,
540
+ typeof preset.width === "number" && /* @__PURE__ */ jsxs("span", { className: "ml-auto text-xs text-muted-foreground", children: [
541
+ preset.width,
542
+ "px"
543
+ ] })
544
+ ]
545
+ },
546
+ preset.label
547
+ ))
548
+ ] })
549
+ ] }),
550
+ showZoomControls ? /* @__PURE__ */ jsx(PreviewZoomControls, { zoom, onZoomChange }) : null
551
+ ] });
552
+ }
553
+ function SubrouteNotice({ model }) {
554
+ return /* @__PURE__ */ jsxs("div", { className: "border-b bg-amber-50 px-4 py-3 text-amber-950", children: [
555
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: model.title }),
556
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm", children: model.body }),
557
+ model.cta ? /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: /* @__PURE__ */ jsx(
558
+ "a",
559
+ {
560
+ 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",
561
+ href: model.cta.href,
562
+ target: "_blank",
563
+ rel: "noopener noreferrer",
564
+ children: model.cta.label
565
+ }
566
+ ) }) : null,
567
+ model.fallbackMessage ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-amber-900/90", children: model.fallbackMessage }) : null
568
+ ] });
569
+ }
570
+
571
+ // ../preview-next/src/client/preview/subrouteNotice.ts
572
+ function buildNoticeFromMeta(meta, payload, siteId) {
573
+ const name = meta.formatInstanceName(payload);
574
+ const title = name ? `You're previewing the ${name} ${meta.instanceNoun} of this ${meta.containerNoun}` : `You're previewing one ${meta.instanceNoun} of this ${meta.containerNoun}`;
575
+ 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}.`;
576
+ const ctaLabel = `Edit just this ${meta.instanceNoun}`;
577
+ let href = null;
578
+ try {
579
+ href = getDashboardUrl(`sites/${siteId}/${meta.subEditorPath(payload)}`);
580
+ } catch {
581
+ href = null;
582
+ }
583
+ return {
584
+ title,
585
+ body,
586
+ cta: href ? { label: ctaLabel, href } : null,
587
+ fallbackMessage: href ? null : "Dashboard links are unavailable. Set `NEXT_PUBLIC_DASHBOARD_URL` so preview can link back to the dashboard."
588
+ };
589
+ }
590
+ function resolveSubrouteNotice(ctx, args) {
591
+ if (!ctx) return null;
592
+ return foldSubroute(ctx, {
593
+ "event-occurrence": (occurrence) => buildNoticeFromMeta(SUBROUTE_KINDS["event-occurrence"], occurrence, args.siteId),
594
+ "course-run": (run) => buildNoticeFromMeta(SUBROUTE_KINDS["course-run"], run, args.siteId)
595
+ });
596
+ }
597
+ function EntryEditorPanel({
598
+ siteId,
599
+ entryData,
600
+ contentTypeSchema,
601
+ referenceContentTypeKeys,
602
+ draftContent,
603
+ currentTemplate,
604
+ theme,
605
+ apiClient,
606
+ bearerToken,
607
+ supabaseUrl,
608
+ entrySubrouteContext,
609
+ metadataPanel,
610
+ mediaAssets,
611
+ widgets,
612
+ viewportMode,
613
+ viewportWidth,
614
+ zoom,
615
+ onViewportModeChange,
616
+ onViewportWidthChange,
617
+ onZoomChange
618
+ }) {
619
+ const [contentOpen, setContentOpen] = useState(true);
620
+ const [templateOpen, setTemplateOpen] = useState(false);
621
+ const [detailsOpen, setDetailsOpen] = useState(false);
622
+ const contentFormFields = useMemo(
623
+ // content-model keeps a source-local structural field type so it can typecheck natively
624
+ // without importing @riverbankcms/blocks source. Preview is the integration boundary where
625
+ // those normalized fields are consumed as a block-form manifest.
626
+ () => contentTypeSchema ? mapContentSchemaToFormFields(contentTypeSchema, {
627
+ referenceContentTypeKeys
628
+ }) : [],
629
+ [contentTypeSchema, referenceContentTypeKeys]
630
+ );
631
+ const contentFormSpec = useMemo(
632
+ () => ({
633
+ name: "content-fields",
634
+ version: "1.0",
635
+ title: "Content",
636
+ component: "content-form",
637
+ description: "",
638
+ fields: contentFormFields,
639
+ slots: []
640
+ }),
641
+ [contentFormFields]
642
+ );
643
+ const contentInitialValues = useMemo(() => draftContent ?? {}, [draftContent]);
644
+ const subrouteNotice = useMemo(
645
+ () => resolveSubrouteNotice(entrySubrouteContext ?? null, { siteId }),
646
+ [entrySubrouteContext, siteId]
647
+ );
648
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", children: [
649
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-2", children: /* @__PURE__ */ jsx(
650
+ PreviewViewportControls,
651
+ {
652
+ mode: viewportMode,
653
+ width: viewportWidth,
654
+ onModeChange: onViewportModeChange,
655
+ onWidthChange: onViewportWidthChange,
656
+ zoom,
657
+ onZoomChange
658
+ }
659
+ ) }),
660
+ subrouteNotice ? /* @__PURE__ */ jsx(SubrouteNotice, { model: subrouteNotice }) : null,
661
+ /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 space-y-0 overflow-auto", children: [
662
+ contentTypeSchema && entryData.type ? /* @__PURE__ */ jsx(Collapsible, { open: contentOpen, onOpenChange: setContentOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
663
+ /* @__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: [
664
+ /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${contentOpen ? "rotate-90" : ""}` }),
665
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Content" })
666
+ ] }) }),
667
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
668
+ PreviewFormProvider,
669
+ {
670
+ siteId,
671
+ theme: theme ?? null,
672
+ previewClient: apiClient,
673
+ bearerToken,
674
+ supabaseUrl,
675
+ mediaAssets,
676
+ children: /* @__PURE__ */ jsx(
677
+ ContentFieldsForm,
678
+ {
679
+ siteId,
680
+ form: contentFormSpec,
681
+ initialValues: contentInitialValues,
682
+ widgets
683
+ }
684
+ )
685
+ }
686
+ ) }) })
687
+ ] }) }) : /* @__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." }) }),
688
+ currentTemplate && entryData.type ? /* @__PURE__ */ jsx(Collapsible, { open: templateOpen, onOpenChange: setTemplateOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
689
+ /* @__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: [
690
+ /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${templateOpen ? "rotate-90" : ""}` }),
691
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Template" })
692
+ ] }) }),
693
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-4", children: [
694
+ /* @__PURE__ */ jsx("div", { className: "rounded-md border px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
695
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: currentTemplate.name }),
696
+ currentTemplate.description ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: currentTemplate.description }) : null
697
+ ] }) }),
698
+ /* @__PURE__ */ jsx(
699
+ "a",
700
+ {
701
+ href: (() => {
702
+ try {
703
+ return getDashboardUrl(`sites/${siteId}/content/${entryData.type}/template`);
704
+ } catch {
705
+ return "#";
706
+ }
707
+ })(),
708
+ target: "_blank",
709
+ rel: "noopener noreferrer",
710
+ className: "inline-flex items-center text-sm text-primary hover:underline",
711
+ children: "Edit template in dashboard \u2192"
712
+ }
713
+ )
714
+ ] }) })
715
+ ] }) }) : null,
716
+ metadataPanel ? /* @__PURE__ */ jsx(Collapsible, { open: detailsOpen, onOpenChange: setDetailsOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
717
+ /* @__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: [
718
+ /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${detailsOpen ? "rotate-90" : ""}` }),
719
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Details" })
720
+ ] }) }),
721
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: metadataPanel }) })
722
+ ] }) }) : null
723
+ ] })
724
+ ] });
725
+ }
726
+ var EMPTY_BLOCK_VALUES = {};
727
+ function SelectedBlockCustomizePanel({
728
+ blockItems,
729
+ uiStoreForBlockEditor,
730
+ theme,
731
+ sdkConfig
732
+ }) {
733
+ const customizeSelection = usePreviewBlockCustomizeSelectionOptional();
734
+ const selection = customizeSelection?.selection ?? { kind: "none" };
735
+ if (selection.kind === "none") {
736
+ return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-3 py-4 text-sm text-muted-foreground", children: "Select a section in the preview to customize it." });
737
+ }
738
+ const selectedBlock = blockItems.find((block) => block.id === selection.blockId) ?? null;
739
+ if (!selectedBlock) {
740
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border border-dashed px-3 py-4 text-sm text-muted-foreground", children: [
741
+ /* @__PURE__ */ jsx("p", { children: "This section is no longer on the page." }),
742
+ /* @__PURE__ */ jsx(
743
+ "button",
744
+ {
745
+ type: "button",
746
+ className: "text-xs font-medium text-foreground underline underline-offset-4",
747
+ onClick: customizeSelection?.clearInspection,
748
+ children: "Clear selection"
749
+ }
750
+ )
751
+ ] });
752
+ }
753
+ return /* @__PURE__ */ jsx(
754
+ SelectedBlockCustomizeFields,
755
+ {
756
+ block: selectedBlock,
757
+ blockId: selection.blockId,
758
+ uiStoreForBlockEditor,
759
+ theme,
760
+ sdkConfig
761
+ }
762
+ );
763
+ }
764
+ function SelectedBlockCustomizeFields({
765
+ block,
766
+ blockId,
767
+ uiStoreForBlockEditor,
768
+ theme,
769
+ sdkConfig
770
+ }) {
771
+ const blockContent = useBlockCurrentValues(
772
+ uiStoreForBlockEditor,
773
+ blockId,
774
+ block.content ?? EMPTY_BLOCK_VALUES
775
+ );
776
+ const customizeControls = useBlockCustomizeControls({
777
+ blockId,
778
+ blockKind: block.kind,
779
+ content: blockContent,
780
+ uiStore: uiStoreForBlockEditor,
781
+ theme,
782
+ sdkConfig,
783
+ maxQuickFields: MAX_QUICK_MANIFEST_FIELDS
784
+ });
785
+ const { backend, manifest, panelFields, quickFields } = customizeControls;
786
+ if (!manifest || !backend) {
787
+ return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-3 py-4 text-sm text-muted-foreground", children: "No customize controls are available for this section." });
788
+ }
789
+ if (quickFields.length === 0 && panelFields.length === 0) {
790
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1 rounded-md border border-dashed px-3 py-4 text-sm text-muted-foreground", children: [
791
+ /* @__PURE__ */ jsx("p", { children: "No safe customize controls are available for this section yet." }),
792
+ /* @__PURE__ */ jsx("p", { className: "text-xs", children: "Content fields remain in Page Blocks." })
793
+ ] });
794
+ }
795
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
796
+ /* @__PURE__ */ jsxs("div", { children: [
797
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", children: manifest.title }),
798
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: formatBlockKindLabel(block.kind) })
799
+ ] }),
800
+ quickFields.length > 0 ? /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
801
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase text-muted-foreground", children: "Quick controls" }),
802
+ /* @__PURE__ */ jsx(
803
+ FormRenderer,
804
+ {
805
+ fields: quickFields,
806
+ backend,
807
+ widgetOverrides: previewWidgetOverrides,
808
+ showSettingsTrigger: false,
809
+ blockKind: block.kind,
810
+ className: "space-y-4"
811
+ }
812
+ )
813
+ ] }) : null,
814
+ panelFields.length > 0 ? /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
815
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase text-muted-foreground", children: "More settings" }),
816
+ /* @__PURE__ */ jsx(
817
+ FormRenderer,
818
+ {
819
+ fields: panelFields,
820
+ backend,
821
+ widgetOverrides: previewWidgetOverrides,
822
+ showSettingsTrigger: false,
823
+ blockKind: block.kind,
824
+ className: "space-y-4"
825
+ }
826
+ )
827
+ ] }) : null
828
+ ] });
829
+ }
830
+ function useBlockCurrentValues(store, blockId, fallback) {
831
+ return useSyncExternalStore(
832
+ store.subscribe,
833
+ () => readBlockCurrentValues(store, blockId, fallback),
834
+ () => fallback
835
+ );
836
+ }
837
+ function readBlockCurrentValues(store, blockId, fallback) {
838
+ const values = store.getState().drafts.getCurrentValues(blockId);
839
+ return values ?? fallback;
840
+ }
841
+ function formatBlockKindLabel(blockKind) {
842
+ return blockKind.replace(/^block\./, "").replace(/^custom\./, "").replace(/[-_]+/g, " ");
843
+ }
844
+ function PageBlockEditorPanel({
845
+ siteId,
846
+ pageId,
847
+ theme,
848
+ apiClient,
849
+ bearerToken,
850
+ supabaseUrl,
851
+ blockItems,
852
+ uiStoreForBlockEditor,
853
+ blockEditorRef,
854
+ onBlocksChange,
855
+ metadataPanel,
856
+ designPanel,
857
+ sdkConfig,
858
+ mediaAssets,
859
+ viewportMode,
860
+ viewportWidth,
861
+ zoom,
862
+ onViewportModeChange,
863
+ onViewportWidthChange,
864
+ onZoomChange
865
+ }) {
866
+ const customizeSelection = usePreviewBlockCustomizeSelectionOptional();
867
+ const [designOpen, setDesignOpen] = useState(false);
868
+ const [customizeOpen, setCustomizeOpen] = useState(false);
869
+ const [contentOpen, setContentOpen] = useState(true);
870
+ const [detailsOpen, setDetailsOpen] = useState(false);
871
+ const selectionKind = customizeSelection?.selection.kind;
872
+ const selectionRequestId = customizeSelection?.selection.kind === "selected" ? customizeSelection.selection.requestId : 0;
873
+ useEffect(() => {
874
+ if (selectionKind === "selected") {
875
+ setCustomizeOpen(true);
876
+ }
877
+ }, [selectionKind, selectionRequestId]);
878
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", children: [
879
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-2", children: /* @__PURE__ */ jsx(
880
+ PreviewViewportControls,
881
+ {
882
+ mode: viewportMode,
883
+ width: viewportWidth,
884
+ onModeChange: onViewportModeChange,
885
+ onWidthChange: onViewportWidthChange,
886
+ zoom,
887
+ onZoomChange
888
+ }
889
+ ) }),
890
+ /* @__PURE__ */ jsx(
891
+ PreviewFormProvider,
892
+ {
893
+ siteId,
894
+ theme: theme ?? null,
895
+ previewClient: apiClient,
896
+ bearerToken,
897
+ supabaseUrl,
898
+ mediaAssets,
899
+ sdkConfig: sdkConfig ?? null,
900
+ children: /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 space-y-0 overflow-auto", children: [
901
+ designPanel ? /* @__PURE__ */ jsx(Collapsible, { open: designOpen, onOpenChange: setDesignOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
902
+ /* @__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: [
903
+ /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${designOpen ? "rotate-90" : ""}` }),
904
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Design" })
905
+ ] }) }),
906
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: designPanel }) })
907
+ ] }) }) : null,
908
+ /* @__PURE__ */ jsx(Collapsible, { open: customizeOpen, onOpenChange: setCustomizeOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
909
+ /* @__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: [
910
+ /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${customizeOpen ? "rotate-90" : ""}` }),
911
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Customize" })
912
+ ] }) }),
913
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
914
+ SelectedBlockCustomizePanel,
915
+ {
916
+ blockItems,
917
+ uiStoreForBlockEditor,
918
+ theme: theme ?? null,
919
+ sdkConfig: sdkConfig ?? null
920
+ }
921
+ ) }) })
922
+ ] }) }),
923
+ /* @__PURE__ */ jsx(Collapsible, { open: contentOpen, onOpenChange: setContentOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
924
+ /* @__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: [
925
+ /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${contentOpen ? "rotate-90" : ""}` }),
926
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Page Blocks" })
927
+ ] }) }),
928
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
929
+ BlockEditor,
930
+ {
931
+ ref: blockEditorRef,
932
+ siteId,
933
+ pageId,
934
+ blocks: blockItems,
935
+ uiStore: uiStoreForBlockEditor,
936
+ apiClient,
937
+ onBlocksChange,
938
+ widgetOverrides: previewWidgetOverrides,
939
+ sdkConfig: sdkConfig ?? null,
940
+ reorderControlMode: "drag-handle-only",
941
+ actionControlMode: "menu"
942
+ },
943
+ pageId
944
+ ) }) })
945
+ ] }) }),
946
+ metadataPanel ? /* @__PURE__ */ jsx(Collapsible, { open: detailsOpen, onOpenChange: setDetailsOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
947
+ /* @__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: [
948
+ /* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${detailsOpen ? "rotate-90" : ""}` }),
949
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Page Details" })
950
+ ] }) }),
951
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: metadataPanel }) })
952
+ ] }) }) : null
953
+ ] })
954
+ }
955
+ )
956
+ ] });
957
+ }
958
+ function PreviewEditorGuidanceCard({
959
+ guidance,
960
+ completeSetupDisabled,
961
+ onCompleteSetup
962
+ }) {
963
+ return /* @__PURE__ */ jsxs(
964
+ "div",
965
+ {
966
+ className: guidance.cardClassName,
967
+ "data-preview-guidance-tone": guidance.tone,
968
+ children: [
969
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold tracking-tight text-slate-900", children: guidance.title }),
970
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs leading-5 text-slate-700", children: guidance.description }),
971
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
972
+ Button,
973
+ {
974
+ type: "button",
975
+ size: "sm",
976
+ variant: "outline",
977
+ onClick: onCompleteSetup,
978
+ disabled: completeSetupDisabled,
979
+ children: guidance.ctaLabel
980
+ }
981
+ ) })
982
+ ]
983
+ }
984
+ );
985
+ }
986
+
987
+ // ../ui/src/palette/hex.ts
988
+ function normalizeHex(input, fallback = "#000000") {
989
+ if (!input) return fallback;
990
+ const trimmed = input.trim().replace(/^#/, "").toLowerCase();
991
+ const expanded = trimmed.length === 3 ? trimmed.split("").map((char) => char + char).join("") : trimmed;
992
+ if (!/^[0-9a-f]{6}$/i.test(expanded)) {
993
+ return fallback;
994
+ }
995
+ return `#${expanded}`;
996
+ }
997
+ function hexToRgb(hex) {
998
+ const normalized = normalizeHex(hex).replace("#", "");
999
+ const match = normalized.match(/.{2}/g);
1000
+ if (!match || match.length < 3) return null;
1001
+ const [r, g, b] = match.map((value) => parseInt(value, 16));
1002
+ return [r, g, b];
1003
+ }
1004
+ function luminanceChannel(raw) {
1005
+ const channel = raw / 255;
1006
+ return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
1007
+ }
1008
+ function contrastRatio(hexA, hexB) {
1009
+ const rgbA = hexToRgb(hexA);
1010
+ const rgbB = hexToRgb(hexB);
1011
+ if (!rgbA || !rgbB) return 1;
1012
+ const lumA = 0.2126 * luminanceChannel(rgbA[0]) + 0.7152 * luminanceChannel(rgbA[1]) + 0.0722 * luminanceChannel(rgbA[2]);
1013
+ const lumB = 0.2126 * luminanceChannel(rgbB[0]) + 0.7152 * luminanceChannel(rgbB[1]) + 0.0722 * luminanceChannel(rgbB[2]);
1014
+ const brighter = Math.max(lumA, lumB);
1015
+ const darker = Math.min(lumA, lumB);
1016
+ return Number(((brighter + 0.05) / (darker + 0.05)).toFixed(2));
1017
+ }
1018
+ var DEFAULT_BRAND_ROLES = [
1019
+ { key: "primary", label: "Primary" },
1020
+ { key: "secondary", label: "Secondary" },
1021
+ { key: "accent", label: "Accent" }
1022
+ ];
1023
+ var DEFAULT_FEEDBACK_ROLES = [
1024
+ { key: "success", label: "Success" },
1025
+ { key: "warning", label: "Warning" },
1026
+ { key: "destructive", label: "Destructive" }
1027
+ ];
1028
+ var DEFAULT_SURFACE_ROLES = [
1029
+ { key: "background", label: "Background" },
1030
+ { key: "surface", label: "Surface" },
1031
+ { key: "text", label: "Text" },
1032
+ { key: "border", label: "Border" }
1033
+ ];
1034
+ function PaletteTokenEditor({
1035
+ palette,
1036
+ onChange,
1037
+ hasInvalidValues = false,
1038
+ brandRoles = DEFAULT_BRAND_ROLES,
1039
+ feedbackRoles = DEFAULT_FEEDBACK_ROLES,
1040
+ surfaceRoles = DEFAULT_SURFACE_ROLES,
1041
+ showContrastChecks = true,
1042
+ className
1043
+ }) {
1044
+ const paletteValues = useMemo(() => {
1045
+ const map = /* @__PURE__ */ new Map();
1046
+ for (const entry of palette) {
1047
+ if (entry?.name) map.set(entry.name, normalizeHex(entry.hex));
1048
+ }
1049
+ return map;
1050
+ }, [palette]);
1051
+ const getColor = (role) => paletteValues.get(role) ?? "#000000";
1052
+ const handleRoleChange = (role, hex) => {
1053
+ if (!isPlausibleHexInput(hex)) return;
1054
+ onChange(role, normalizeHex(hex));
1055
+ };
1056
+ const backgroundHex = getColor("background");
1057
+ const textHex = getColor("text");
1058
+ const primaryHex = getColor("primary");
1059
+ const contrast = contrastRatio(backgroundHex, textHex);
1060
+ const actionContrast = contrastRatio(primaryHex, backgroundHex);
1061
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-6", className), children: [
1062
+ 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,
1063
+ /* @__PURE__ */ jsx(ColorRoleGroup, { title: "Brand & accents", roles: brandRoles, getColor, onChange: handleRoleChange }),
1064
+ /* @__PURE__ */ jsx(ColorRoleGroup, { title: "Feedback", roles: feedbackRoles, getColor, onChange: handleRoleChange }),
1065
+ /* @__PURE__ */ jsx(ColorRoleGroup, { title: "Surfaces & text", roles: surfaceRoles, getColor, onChange: handleRoleChange }),
1066
+ showContrastChecks ? /* @__PURE__ */ jsxs(Fragment, { children: [
1067
+ /* @__PURE__ */ jsx(Separator, {}),
1068
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
1069
+ /* @__PURE__ */ jsx(
1070
+ ContrastCard,
1071
+ {
1072
+ title: "Background \u2194 Text contrast",
1073
+ value: contrast,
1074
+ threshold: 4.5,
1075
+ primaryLabel: "Background",
1076
+ primaryHex: backgroundHex,
1077
+ secondaryLabel: "Text",
1078
+ secondaryHex: textHex
1079
+ }
1080
+ ),
1081
+ /* @__PURE__ */ jsx(
1082
+ ContrastCard,
1083
+ {
1084
+ title: "Primary action contrast",
1085
+ value: actionContrast,
1086
+ threshold: 3,
1087
+ primaryLabel: "Primary",
1088
+ primaryHex,
1089
+ secondaryLabel: "Background",
1090
+ secondaryHex: backgroundHex
1091
+ }
1092
+ )
1093
+ ] })
1094
+ ] }) : null
1095
+ ] });
1096
+ }
1097
+ function isPlausibleHexInput(value) {
1098
+ const trimmed = value.trim();
1099
+ return /^#?[0-9a-fA-F]{3}$/.test(trimmed) || /^#?[0-9a-fA-F]{6}$/.test(trimmed);
1100
+ }
1101
+ function ColorRoleGroup({ title, roles, getColor, onChange }) {
1102
+ if (roles.length === 0) return null;
1103
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1104
+ /* @__PURE__ */ jsxs("div", { children: [
1105
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-foreground", children: title }),
1106
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Adjust hex values or drag the color picker." })
1107
+ ] }),
1108
+ /* @__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)) })
1109
+ ] });
1110
+ }
1111
+ function ColorRoleField({ role, value, onChange }) {
1112
+ const inputId = useId();
1113
+ const colorPickerLabel = `${role.label} colour picker`;
1114
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-muted p-3 space-y-2", children: [
1115
+ /* @__PURE__ */ jsx(
1116
+ "label",
1117
+ {
1118
+ htmlFor: inputId,
1119
+ className: "text-xs uppercase text-muted-foreground font-medium leading-5 block",
1120
+ children: role.label
1121
+ }
1122
+ ),
1123
+ /* @__PURE__ */ jsx(
1124
+ ColorPicker,
1125
+ {
1126
+ value,
1127
+ onChange: ((next) => {
1128
+ if (typeof next === "string") {
1129
+ onChange(role.key, next);
1130
+ }
1131
+ }),
1132
+ className: "w-full",
1133
+ "aria-label": colorPickerLabel
1134
+ }
1135
+ ),
1136
+ /* @__PURE__ */ jsx(
1137
+ Input,
1138
+ {
1139
+ id: inputId,
1140
+ value,
1141
+ onChange: (event) => onChange(role.key, event.target.value),
1142
+ className: "font-mono text-xs uppercase",
1143
+ placeholder: "#000000"
1144
+ }
1145
+ )
1146
+ ] });
1147
+ }
1148
+ function ContrastCard({ title, value, threshold, primaryLabel, primaryHex, secondaryLabel, secondaryHex }) {
1149
+ const passes = value >= threshold;
1150
+ return /* @__PURE__ */ jsxs(
1151
+ Surface,
1152
+ {
1153
+ variant: "background",
1154
+ radius: "lg",
1155
+ padding: "md",
1156
+ className: cn(
1157
+ "space-y-2 border text-sm",
1158
+ passes ? "border-success/40 bg-success/10 text-success" : "border-warning/40 bg-warning/10 text-warning-foreground"
1159
+ ),
1160
+ children: [
1161
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1162
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: title }),
1163
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold", children: [
1164
+ value,
1165
+ ":1"
1166
+ ] })
1167
+ ] }),
1168
+ /* @__PURE__ */ jsx("p", { className: "text-xs", children: passes ? "Contrast meets WCAG AA guidance." : `Increase contrast to reach ${threshold}:1 or higher.` }),
1169
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 pt-2 text-xs", children: [
1170
+ /* @__PURE__ */ jsx(ContrastSwatch, { hex: primaryHex, label: primaryLabel }),
1171
+ /* @__PURE__ */ jsx(ContrastSwatch, { hex: secondaryHex, label: secondaryLabel })
1172
+ ] })
1173
+ ]
1174
+ }
1175
+ );
1176
+ }
1177
+ function ContrastSwatch({ hex, label }) {
1178
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1179
+ /* @__PURE__ */ jsx("span", { className: "h-6 w-6 rounded-full border", style: { backgroundColor: hex } }),
1180
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs uppercase tracking-wide text-muted-foreground", children: [
1181
+ label,
1182
+ ": ",
1183
+ hex
1184
+ ] })
1185
+ ] });
1186
+ }
1187
+
1188
+ // ../preview-next/src/client/preview/styleConfiguratorApplyPayload.ts
1189
+ function buildApplySiteStyleSelectionBody(input) {
1190
+ return {
1191
+ siteStyleId: input.previewed.siteStyleId,
1192
+ buttonPersonalityId: input.previewed.buttonPersonalityId,
1193
+ paletteVariantId: input.previewed.paletteVariantId,
1194
+ paletteOverrides: input.previewed.paletteOverrides,
1195
+ typographyPresetId: input.previewed.typographyPresetId,
1196
+ typographyBodyFontOverride: input.previewed.typographyBodyFontOverride,
1197
+ typographyHeadingFontOverride: input.previewed.typographyHeadingFontOverride,
1198
+ headerLookId: input.previewed.headerLookId,
1199
+ headerLayoutId: input.previewed.headerLayoutId,
1200
+ headerStyleId: input.previewed.headerStyleId,
1201
+ footerLookId: input.previewed.footerLookId,
1202
+ baseSelectionVersion: input.base.selectionVersion,
1203
+ selectionId: input.selectionId ?? null
1204
+ };
1205
+ }
1206
+ var MAX_FONT_RESULTS_WHEN_SEARCHING = 12;
1207
+ var MAX_FONT_RESULTS_WHEN_BROWSING = 8;
1208
+ var PALETTE_BRAND_ROLES = [
1209
+ { key: "primary", label: "Primary" },
1210
+ { key: "primaryForeground", label: "Primary text" },
1211
+ { key: "secondary", label: "Secondary" },
1212
+ { key: "accent", label: "Accent" }
1213
+ ];
1214
+ var PALETTE_FEEDBACK_ROLES = [
1215
+ { key: "success", label: "Success" },
1216
+ { key: "warning", label: "Warning" },
1217
+ { key: "destructive", label: "Destructive" }
1218
+ ];
1219
+ var PALETTE_SURFACE_ROLES = [
1220
+ { key: "background", label: "Background" },
1221
+ { key: "surface", label: "Surface" },
1222
+ { key: "surfaceAlt", label: "Alternate surface" },
1223
+ { key: "muted", label: "Muted surface" },
1224
+ { key: "text", label: "Text" },
1225
+ { key: "border", label: "Border" }
1226
+ ];
1227
+ function StyleConfigurator({
1228
+ siteId,
1229
+ apiClient,
1230
+ theme,
1231
+ selectionId,
1232
+ selectionVersion = null,
1233
+ selectionUpdatedAt,
1234
+ initialSelections,
1235
+ headerSelectionContext,
1236
+ footerSelectionContext,
1237
+ expandedRowRequest = null,
1238
+ siteStyleAccess
1239
+ }) {
1240
+ const router = useRouter();
1241
+ const previewDesign = usePreviewDesignCoordinator();
1242
+ const {
1243
+ applyFailed,
1244
+ applyStarted,
1245
+ applySucceeded,
1246
+ clearPreview,
1247
+ previewTheme
1248
+ } = previewDesign;
1249
+ const [isRefreshPending, startRefreshTransition] = useTransition();
1250
+ const baseSnapshot = useMemo(
1251
+ () => resolveStyleConfiguratorInitialSnapshot({
1252
+ theme,
1253
+ initialSelections: {
1254
+ ...initialSelections,
1255
+ selectionVersion: initialSelections.selectionVersion ?? selectionVersion ?? 0,
1256
+ selectionUpdatedAt: initialSelections.selectionUpdatedAt ?? selectionUpdatedAt ?? null
1257
+ },
1258
+ headerSelectionContext,
1259
+ footerSelectionContext
1260
+ }),
1261
+ [footerSelectionContext, headerSelectionContext, theme, initialSelections, selectionUpdatedAt, selectionVersion]
1262
+ );
1263
+ const [state, setState] = useState(
1264
+ () => baseSnapshot ? createStyleConfiguratorState(baseSnapshot) : null
1265
+ );
1266
+ const hasLoadedState = state !== null;
1267
+ const stateRef = useRef(state);
1268
+ const clearPreviewRef = useRef(clearPreview);
1269
+ const runnerRef = useRef(createOptimisticActionRunner());
1270
+ const appliedExpandedRowRequestIdRef = useRef(null);
1271
+ useEffect(() => {
1272
+ stateRef.current = state;
1273
+ }, [state]);
1274
+ useEffect(() => {
1275
+ clearPreviewRef.current = clearPreview;
1276
+ }, [clearPreview]);
1277
+ useEffect(() => {
1278
+ if (!baseSnapshot) return;
1279
+ setState((current) => current ? syncStyleConfiguratorState(current, baseSnapshot) : createStyleConfiguratorState(baseSnapshot));
1280
+ }, [baseSnapshot]);
1281
+ useEffect(() => {
1282
+ if (!state || !baseSnapshot) {
1283
+ clearPreview("style-configurator");
1284
+ return;
1285
+ }
1286
+ if (!isPreviewingChange(state)) {
1287
+ clearPreview("style-configurator");
1288
+ return;
1289
+ }
1290
+ const siteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
1291
+ if (!siteStyle) {
1292
+ clearPreview("style-configurator");
1293
+ return;
1294
+ }
1295
+ const result = planSiteStylePreviewChange({
1296
+ siteStyle,
1297
+ selection: state.previewed,
1298
+ baseTheme: theme,
1299
+ headerSelectionContext,
1300
+ footerSelectionContext
1301
+ });
1302
+ previewTheme("style-configurator", result.themeOverlay, "site-style");
1303
+ }, [state, baseSnapshot, clearPreview, previewTheme, headerSelectionContext, footerSelectionContext, theme]);
1304
+ useEffect(() => {
1305
+ const runner = runnerRef.current;
1306
+ return () => {
1307
+ runner.invalidate();
1308
+ clearPreviewRef.current("style-configurator");
1309
+ };
1310
+ }, []);
1311
+ useEffect(() => {
1312
+ if (!expandedRowRequest) return;
1313
+ if (appliedExpandedRowRequestIdRef.current === expandedRowRequest.id) return;
1314
+ setState((current) => {
1315
+ if (!current) return current;
1316
+ appliedExpandedRowRequestIdRef.current = expandedRowRequest.id;
1317
+ return openRow(current, expandedRowRequest.row === "header" ? "header-layout" : expandedRowRequest.row);
1318
+ });
1319
+ }, [expandedRowRequest, hasLoadedState]);
1320
+ const handlePreviewSiteStyle = useCallback((siteStyleId) => {
1321
+ setState((current) => {
1322
+ if (!current) return current;
1323
+ const next = previewSiteStyle(current, siteStyleId);
1324
+ const siteStyle = getCuratedSiteStyle(siteStyleId);
1325
+ if (!siteStyle) return next;
1326
+ const result = planSiteStylePreviewChange({
1327
+ siteStyle,
1328
+ selection: next.previewed,
1329
+ baseTheme: theme,
1330
+ headerSelectionContext,
1331
+ footerSelectionContext
1332
+ });
1333
+ return applyResolvedSelections(next, {
1334
+ resolved: result.resolvedSelections,
1335
+ warnings: result.warnings
1336
+ });
1337
+ });
1338
+ }, [footerSelectionContext, headerSelectionContext, theme]);
1339
+ const handlePreviewPersonality = useCallback((id) => {
1340
+ setState((current) => current ? previewButtonPersonality(current, id) : current);
1341
+ }, []);
1342
+ const handlePreviewHeaderLayout = useCallback((id) => {
1343
+ if (!id) return;
1344
+ setState((current) => current ? previewHeaderLayout(current, id) : current);
1345
+ }, []);
1346
+ const handlePreviewHeaderStyle = useCallback((id) => {
1347
+ if (!id) return;
1348
+ setState((current) => current ? previewHeaderStyle(current, id) : current);
1349
+ }, []);
1350
+ const handlePreviewFooterLook = useCallback((id) => {
1351
+ const plan = planSiteChromeLookSelection({ surface: "footer", lookId: id });
1352
+ if (plan.kind !== "footer") return;
1353
+ setState((current) => current ? previewFooterLook(current, plan.next.footerLookId) : current);
1354
+ }, []);
1355
+ const handlePreviewPalette = useCallback((id) => {
1356
+ setState((current) => current ? previewPaletteVariant(current, id) : current);
1357
+ }, []);
1358
+ const handlePreviewTypographyPreset = useCallback((id) => {
1359
+ setState((current) => current ? previewTypographyPreset(current, id) : current);
1360
+ }, []);
1361
+ const handleBodyFontOverride = useCallback((fontFamily) => {
1362
+ setState((current) => current ? setTypographyBodyFontOverride(current, fontFamily) : current);
1363
+ }, []);
1364
+ const handleHeadingFontOverride = useCallback((fontFamily) => {
1365
+ setState((current) => current ? setTypographyHeadingFontOverride(current, fontFamily) : current);
1366
+ }, []);
1367
+ const handleClearTypographyOverrides = useCallback(() => {
1368
+ setState((current) => current ? clearTypographyFontOverrides(current) : current);
1369
+ }, []);
1370
+ const handleTokenOverride = useCallback((role, hex) => {
1371
+ setState((current) => {
1372
+ if (!current) return current;
1373
+ const next = { ...current.previewed.paletteOverrides ?? {}, [role]: hex };
1374
+ const parsed = paletteOverridesSchema.safeParse(next);
1375
+ if (!parsed.success) return current;
1376
+ return setPaletteOverrides(current, parsed.data);
1377
+ });
1378
+ }, []);
1379
+ const handleResetOverrides = useCallback(() => {
1380
+ setState((current) => current ? setPaletteOverrides(current, null) : current);
1381
+ }, []);
1382
+ const handleToggleRow = useCallback((row) => {
1383
+ setState((current) => current ? toggleRow(current, row) : current);
1384
+ }, []);
1385
+ const handleCancel = useCallback(() => {
1386
+ setState((current) => current ? cancelPreview(current) : current);
1387
+ }, []);
1388
+ const handleDismissWarnings = useCallback(() => {
1389
+ setState((current) => current ? dismissWarnings(current) : current);
1390
+ }, []);
1391
+ const handleDismissNotices = useCallback(() => {
1392
+ setState((current) => current ? dismissNotices(current) : current);
1393
+ }, []);
1394
+ const handleApply = useCallback(async () => {
1395
+ const live = stateRef.current;
1396
+ if (!live || !isPreviewingChange(live)) return;
1397
+ const previewed = live.previewed;
1398
+ const siteStyle = getCuratedSiteStyle(previewed.siteStyleId);
1399
+ if (!siteStyle) return;
1400
+ const compiled = planSiteStylePreviewChange({
1401
+ siteStyle,
1402
+ selection: previewed,
1403
+ baseTheme: theme,
1404
+ headerSelectionContext,
1405
+ footerSelectionContext
1406
+ });
1407
+ const body = buildApplySiteStyleSelectionBody({
1408
+ base: live.base,
1409
+ previewed: {
1410
+ ...previewed,
1411
+ buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
1412
+ paletteVariantId: compiled.resolvedSelections.paletteVariantId,
1413
+ headerLayoutId: compiled.resolvedSelections.headerLayoutId,
1414
+ headerStyleId: compiled.resolvedSelections.headerStyleId,
1415
+ headerLookId: compiled.resolvedSelections.headerLookId,
1416
+ typographyPresetId: compiled.resolvedSelections.typographyPresetId,
1417
+ typographyBodyFontOverride: compiled.resolvedSelections.typographyBodyFontOverride,
1418
+ typographyHeadingFontOverride: compiled.resolvedSelections.typographyHeadingFontOverride
1419
+ },
1420
+ selectionId
1421
+ });
1422
+ await runnerRef.current.run({
1423
+ optimistic: () => {
1424
+ applyStarted("style-configurator");
1425
+ setState((current) => current ? markApplySaving(current) : current);
1426
+ },
1427
+ network: async () => {
1428
+ const response = await apiClient({
1429
+ endpoint: "applySiteStyleSelection",
1430
+ params: { siteId },
1431
+ body
1432
+ });
1433
+ return unwrapResponse(response);
1434
+ },
1435
+ onSuccess: (saved) => {
1436
+ setState(
1437
+ (current) => current ? markApplySaved(current, {
1438
+ siteStyleId: previewed.siteStyleId,
1439
+ selectionVersion: saved.selectionVersion,
1440
+ selectionUpdatedAt: saved.selectionUpdatedAt ?? previewed.selectionUpdatedAt ?? null,
1441
+ buttonPersonalityId: saved.buttonPersonalityId,
1442
+ paletteVariantId: saved.paletteVariantId,
1443
+ paletteOverrides: saved.paletteOverrides ?? null,
1444
+ typographyPresetId: saved.typographyPresetId,
1445
+ typographyBodyFontOverride: saved.typographyBodyFontOverride,
1446
+ typographyHeadingFontOverride: saved.typographyHeadingFontOverride,
1447
+ headerLookId: saved.headerLookId,
1448
+ headerLayoutId: saved.headerLayoutId,
1449
+ headerStyleId: saved.headerStyleId,
1450
+ footerLookId: saved.footerLookId
1451
+ }) : current
1452
+ );
1453
+ applySucceeded("style-configurator");
1454
+ startRefreshTransition(() => {
1455
+ router.refresh();
1456
+ });
1457
+ },
1458
+ onError: (error) => {
1459
+ applyFailed("style-configurator");
1460
+ const applyError = classifyStyleApplyError(error);
1461
+ if (applyError.kind === "stale") {
1462
+ startRefreshTransition(() => {
1463
+ router.refresh();
1464
+ });
1465
+ }
1466
+ const message = applyError.message;
1467
+ setState((current) => current ? markApplyFailed(current, message) : current);
1468
+ }
1469
+ });
1470
+ }, [
1471
+ apiClient,
1472
+ footerSelectionContext,
1473
+ headerSelectionContext,
1474
+ applyFailed,
1475
+ applyStarted,
1476
+ applySucceeded,
1477
+ router,
1478
+ selectionId,
1479
+ siteId,
1480
+ startRefreshTransition,
1481
+ theme
1482
+ ]);
1483
+ if (!state || !baseSnapshot) {
1484
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading design controls..." });
1485
+ }
1486
+ const applyAllowed = canApplySiteStyle(siteStyleAccess);
1487
+ const previewingChange = isPreviewingChange(state);
1488
+ const saving = state.saveStatus.kind === "saving" || isRefreshPending;
1489
+ const currentSiteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
1490
+ if (!currentSiteStyle) {
1491
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "This site uses a Site Style that cannot be configured here." });
1492
+ }
1493
+ const currentPersonality = findButtonPersonality(state.previewed.buttonPersonalityId);
1494
+ const currentPaletteVariant = findPaletteVariant(state.previewed.paletteVariantId);
1495
+ const currentTypographyPreset = state.previewed.typographyPresetId ? findTypographyPreset(state.previewed.typographyPresetId) : null;
1496
+ const chromeLookModels = buildSiteChromeLookModels({
1497
+ state,
1498
+ siteStyle: currentSiteStyle,
1499
+ headerSelectionContext,
1500
+ footerSelectionContext
1501
+ });
1502
+ const currentHeaderLayout = getHeaderLayout(state.previewed.headerLayoutId);
1503
+ const currentHeaderStyle = getHeaderStyle(state.previewed.headerStyleId);
1504
+ const currentFooterLook = state.previewed.footerLookId ? findFooterLook(state.previewed.footerLookId) : findFooterLook(chromeLookModels.footer.renderedLookId);
1505
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1506
+ state.warnings.length > 0 ? /* @__PURE__ */ jsx(WarningNotice, { warnings: state.warnings, onDismiss: handleDismissWarnings }) : null,
1507
+ state.notices.length > 0 ? /* @__PURE__ */ jsx(NoticeBanner, { notices: state.notices, onDismiss: handleDismissNotices }) : null,
1508
+ /* @__PURE__ */ jsx(
1509
+ SiteStyleRow,
1510
+ {
1511
+ expanded: state.expandedRow === "style",
1512
+ onToggle: () => handleToggleRow("style"),
1513
+ current: currentSiteStyle,
1514
+ onPreview: handlePreviewSiteStyle,
1515
+ disabled: saving
1516
+ }
1517
+ ),
1518
+ /* @__PURE__ */ jsx(
1519
+ SiteChromeLookRow,
1520
+ {
1521
+ label: "Header layout",
1522
+ ariaLabel: "Header layout",
1523
+ defaultDescription: "Choose the header structure.",
1524
+ expanded: state.expandedRow === "header-layout",
1525
+ onToggle: () => handleToggleRow("header-layout"),
1526
+ currentId: state.previewed.headerLayoutId,
1527
+ currentLabel: currentHeaderLayout?.label ?? state.previewed.headerLayoutId,
1528
+ scopeDescription: "Changes the header structure on every page.",
1529
+ options: chromeLookModels.headerLayout.options,
1530
+ diagnostics: [],
1531
+ onCycle: (direction) => {
1532
+ const next = cycleSiteChromeLookId(chromeLookModels.headerLayout.options, state.previewed.headerLayoutId, direction);
1533
+ if (next !== void 0) handlePreviewHeaderLayout(next);
1534
+ },
1535
+ onPreview: handlePreviewHeaderLayout,
1536
+ disabled: saving
1537
+ }
1538
+ ),
1539
+ /* @__PURE__ */ jsx(
1540
+ SiteChromeLookRow,
1541
+ {
1542
+ label: "Header look",
1543
+ ariaLabel: "Header look",
1544
+ defaultDescription: "Choose the header visual style.",
1545
+ expanded: state.expandedRow === "header-style",
1546
+ onToggle: () => handleToggleRow("header-style"),
1547
+ currentId: state.previewed.headerStyleId,
1548
+ currentLabel: currentHeaderStyle?.label ?? state.previewed.headerStyleId,
1549
+ scopeDescription: "Changes the header surface and link style.",
1550
+ options: chromeLookModels.headerStyle.options,
1551
+ diagnostics: [],
1552
+ onCycle: (direction) => {
1553
+ const next = cycleSiteChromeLookId(chromeLookModels.headerStyle.options, state.previewed.headerStyleId, direction);
1554
+ if (next !== void 0) handlePreviewHeaderStyle(next);
1555
+ },
1556
+ onPreview: handlePreviewHeaderStyle,
1557
+ disabled: saving
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ jsx(
1561
+ SiteChromeLookRow,
1562
+ {
1563
+ label: "Footer",
1564
+ ariaLabel: "Footer",
1565
+ defaultDescription: "Use the footer chosen for this site style.",
1566
+ catalog: footerLookCatalog,
1567
+ expanded: state.expandedRow === "footer",
1568
+ onToggle: () => handleToggleRow("footer"),
1569
+ currentId: state.previewed.footerLookId,
1570
+ currentLabel: state.previewed.footerLookId ? currentFooterLook?.label ?? "Unknown footer Look" : `${currentFooterLook?.label ?? "Site style"} (default)`,
1571
+ scopeDescription: "Changes the footer on every page.",
1572
+ options: chromeLookModels.footer.options,
1573
+ diagnostics: chromeLookModels.footer.diagnostics,
1574
+ onCycle: (direction) => {
1575
+ const next = cycleSiteChromeLookId(chromeLookModels.footer.options, state.previewed.footerLookId, direction);
1576
+ if (next !== void 0) handlePreviewFooterLook(next);
1577
+ },
1578
+ onPreview: handlePreviewFooterLook,
1579
+ disabled: saving
1580
+ }
1581
+ ),
1582
+ /* @__PURE__ */ jsx(
1583
+ PersonalityRow,
1584
+ {
1585
+ expanded: state.expandedRow === "buttons",
1586
+ onToggle: () => handleToggleRow("buttons"),
1587
+ siteStyle: currentSiteStyle,
1588
+ currentId: state.previewed.buttonPersonalityId,
1589
+ currentLabel: currentPersonality?.name ?? state.previewed.buttonPersonalityId,
1590
+ onPreview: handlePreviewPersonality,
1591
+ disabled: saving
1592
+ }
1593
+ ),
1594
+ /* @__PURE__ */ jsx(
1595
+ TypographyRow,
1596
+ {
1597
+ expanded: state.expandedRow === "typography",
1598
+ onToggle: () => handleToggleRow("typography"),
1599
+ apiClient,
1600
+ siteStyle: currentSiteStyle,
1601
+ currentPresetId: state.previewed.typographyPresetId,
1602
+ currentLabel: currentTypographyPreset?.name ?? "Custom typography",
1603
+ bodyFontOverride: state.previewed.typographyBodyFontOverride,
1604
+ headingFontOverride: state.previewed.typographyHeadingFontOverride,
1605
+ onPreviewPreset: handlePreviewTypographyPreset,
1606
+ onBodyFontOverride: handleBodyFontOverride,
1607
+ onHeadingFontOverride: handleHeadingFontOverride,
1608
+ onClearOverrides: handleClearTypographyOverrides,
1609
+ disabled: saving
1610
+ }
1611
+ ),
1612
+ /* @__PURE__ */ jsx(
1613
+ PaletteRow,
1614
+ {
1615
+ expanded: state.expandedRow === "palette",
1616
+ onToggle: () => handleToggleRow("palette"),
1617
+ siteStyle: currentSiteStyle,
1618
+ currentId: state.previewed.paletteVariantId,
1619
+ currentLabel: currentPaletteVariant?.name ?? state.previewed.paletteVariantId,
1620
+ currentSwatch: currentPaletteVariant?.colors.find((c) => c.name === "primary")?.hex ?? null,
1621
+ onPreview: handlePreviewPalette,
1622
+ disabled: saving
1623
+ }
1624
+ ),
1625
+ currentPaletteVariant ? /* @__PURE__ */ jsx(
1626
+ ColoursRow,
1627
+ {
1628
+ expanded: state.expandedRow === "colours",
1629
+ onToggle: () => handleToggleRow("colours"),
1630
+ variant: currentPaletteVariant,
1631
+ overrides: state.previewed.paletteOverrides,
1632
+ onTokenChange: handleTokenOverride,
1633
+ onReset: handleResetOverrides,
1634
+ disabled: saving
1635
+ }
1636
+ ) : null,
1637
+ previewingChange ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2", children: [
1638
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: handleCancel, disabled: saving, children: "Cancel" }),
1639
+ 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." })
1640
+ ] }) : null,
1641
+ state.saveStatus.kind === "saved" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-success", children: "Saved." }) : null,
1642
+ state.saveStatus.kind === "failed" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: state.saveStatus.message }) : null
1643
+ ] });
1644
+ }
1645
+ function StyleRowFrame({ label, currentLabel, swatch, expanded, onToggle, children }) {
1646
+ return /* @__PURE__ */ jsx(Collapsible, { open: expanded, onOpenChange: onToggle, children: /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-background", children: [
1647
+ /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1648
+ "button",
1649
+ {
1650
+ type: "button",
1651
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-accent/30",
1652
+ "aria-expanded": expanded,
1653
+ children: [
1654
+ /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-4 w-4 transition", expanded && "rotate-90") }),
1655
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1656
+ /* @__PURE__ */ jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }),
1657
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: currentLabel })
1658
+ ] }),
1659
+ swatch ? /* @__PURE__ */ jsx(
1660
+ "span",
1661
+ {
1662
+ className: "h-5 w-5 shrink-0 rounded-full border border-black/10",
1663
+ style: { backgroundColor: swatch },
1664
+ "aria-hidden": "true"
1665
+ }
1666
+ ) : null
1667
+ ]
1668
+ }
1669
+ ) }),
1670
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "border-t px-3 py-3", children }) })
1671
+ ] }) });
1672
+ }
1673
+ function SiteStyleRow({ expanded, onToggle, current, onPreview, disabled }) {
1674
+ 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(
1675
+ PickerOption,
1676
+ {
1677
+ selected: style.id === current.id,
1678
+ label: style.name,
1679
+ description: style.description,
1680
+ onSelect: () => onPreview(style.id),
1681
+ disabled
1682
+ },
1683
+ style.id
1684
+ )) }) });
1685
+ }
1686
+ function PersonalityRow({ expanded, onToggle, siteStyle, currentId, currentLabel, onPreview, disabled }) {
1687
+ const options = useMemo(
1688
+ () => requireEntriesForCuratedChoices(buttonPersonalities, siteStyle.buttonPersonalityChoices),
1689
+ [siteStyle.buttonPersonalityChoices]
1690
+ );
1691
+ 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(
1692
+ PickerOption,
1693
+ {
1694
+ selected: personality.id === currentId,
1695
+ label: personality.name,
1696
+ description: personality.description,
1697
+ onSelect: () => onPreview(personality.id),
1698
+ disabled
1699
+ },
1700
+ personality.id
1701
+ )) }) });
1702
+ }
1703
+ function TypographyRow({
1704
+ expanded,
1705
+ onToggle,
1706
+ apiClient,
1707
+ siteStyle,
1708
+ currentPresetId,
1709
+ currentLabel,
1710
+ bodyFontOverride,
1711
+ headingFontOverride,
1712
+ onPreviewPreset,
1713
+ onBodyFontOverride,
1714
+ onHeadingFontOverride,
1715
+ onClearOverrides,
1716
+ disabled
1717
+ }) {
1718
+ const options = useMemo(
1719
+ () => requireEntriesForCuratedChoices(typographyPresets, siteStyle.typographyPresetChoices),
1720
+ [siteStyle.typographyPresetChoices]
1721
+ );
1722
+ const fontCatalog = useFontCatalog(apiClient, expanded);
1723
+ const selectedPreset = currentPresetId ? findTypographyPreset(currentPresetId) : null;
1724
+ return /* @__PURE__ */ jsxs(StyleRowFrame, { label: "Typography", currentLabel, expanded, onToggle, children: [
1725
+ 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,
1726
+ /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Typography preset", className: "space-y-1", children: options.map((preset) => /* @__PURE__ */ jsx(
1727
+ TypographyPresetOption,
1728
+ {
1729
+ preset,
1730
+ selected: preset.id === currentPresetId,
1731
+ onSelect: () => onPreviewPreset(preset.id),
1732
+ disabled
1733
+ },
1734
+ preset.id
1735
+ )) }),
1736
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-3 border-t pt-3", children: [
1737
+ selectedPreset ? /* @__PURE__ */ jsxs(Fragment, { children: [
1738
+ /* @__PURE__ */ jsx(
1739
+ TypographyFontSlotPicker,
1740
+ {
1741
+ label: "Body font",
1742
+ slot: "body",
1743
+ preset: selectedPreset,
1744
+ catalog: fontCatalog,
1745
+ value: bodyFontOverride,
1746
+ onChange: onBodyFontOverride,
1747
+ disabled
1748
+ }
1749
+ ),
1750
+ /* @__PURE__ */ jsx(
1751
+ TypographyFontSlotPicker,
1752
+ {
1753
+ label: "Heading font",
1754
+ slot: "heading",
1755
+ preset: selectedPreset,
1756
+ catalog: fontCatalog,
1757
+ value: headingFontOverride,
1758
+ onChange: onHeadingFontOverride,
1759
+ disabled
1760
+ }
1761
+ )
1762
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Choose a typography preset to use curated font overrides." }),
1763
+ /* @__PURE__ */ jsx(
1764
+ Button,
1765
+ {
1766
+ type: "button",
1767
+ size: "sm",
1768
+ variant: "outline",
1769
+ onClick: onClearOverrides,
1770
+ disabled: disabled || !bodyFontOverride && !headingFontOverride,
1771
+ children: "Use preset fonts"
1772
+ }
1773
+ )
1774
+ ] })
1775
+ ] });
1776
+ }
1777
+ function TypographyPresetOption({
1778
+ preset,
1779
+ selected,
1780
+ onSelect,
1781
+ disabled
1782
+ }) {
1783
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
1784
+ "button",
1785
+ {
1786
+ type: "button",
1787
+ role: "radio",
1788
+ "aria-checked": selected,
1789
+ onClick: onSelect,
1790
+ disabled,
1791
+ className: cn(
1792
+ "w-full rounded-md border px-3 py-2 text-left transition disabled:cursor-not-allowed disabled:opacity-50",
1793
+ selected ? "border-primary bg-primary/5" : "border-border hover:bg-muted/60"
1794
+ ),
1795
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1796
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1797
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: preset.name }),
1798
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: preset.description })
1799
+ ] }),
1800
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
1801
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-semibold uppercase tracking-wide", children: preset.defaultFonts.heading.split(",")[0] }),
1802
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: preset.defaultFonts.body.split(",")[0] })
1803
+ ] })
1804
+ ] })
1805
+ }
1806
+ ) });
1807
+ }
1808
+ function TypographyFontSlotPicker({
1809
+ label,
1810
+ slot,
1811
+ preset,
1812
+ catalog,
1813
+ value,
1814
+ onChange,
1815
+ disabled
1816
+ }) {
1817
+ const [query, setQuery] = useState("");
1818
+ const defaultFont = primaryFontFamily(preset.defaultFonts[slot]);
1819
+ const selectedFamily = value ?? defaultFont;
1820
+ const recordsByFamily = useMemo(() => indexFontsByFamily(catalog.fonts), [catalog.fonts]);
1821
+ const selectedFit = evaluateTypographyFontSlot({
1822
+ slot,
1823
+ policy: preset.slotPolicies[slot],
1824
+ capability: capabilityForFamily(selectedFamily, recordsByFamily)
1825
+ });
1826
+ const recommendedFamilies = preset.recommendedFonts[slot];
1827
+ const recommendedOptions = useMemo(
1828
+ () => uniqueFontFamilies(recommendedFamilies),
1829
+ [recommendedFamilies]
1830
+ );
1831
+ const browseOptions = useMemo(() => {
1832
+ const normalizedQuery = query.trim().toLowerCase();
1833
+ const source = normalizedQuery.length > 0 ? catalog.fonts.filter((font) => font.family.toLowerCase().includes(normalizedQuery)) : catalog.fonts;
1834
+ 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);
1835
+ }, [catalog.fonts, preset, query, recordsByFamily, slot]);
1836
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-2", "aria-label": label, children: [
1837
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1838
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1839
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: label }),
1840
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm", children: value ? selectedFamily : `${defaultFont} (preset)` })
1841
+ ] }),
1842
+ /* @__PURE__ */ jsx(TypographyFontFitBadge, { fit: selectedFit })
1843
+ ] }),
1844
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-1", children: [
1845
+ /* @__PURE__ */ jsxs(
1846
+ "button",
1847
+ {
1848
+ type: "button",
1849
+ className: cn(
1850
+ "rounded-md border px-3 py-2 text-left text-sm transition disabled:cursor-not-allowed disabled:opacity-50",
1851
+ value === null ? "border-foreground bg-accent/40" : "border-transparent hover:border-foreground/40 hover:bg-accent/20"
1852
+ ),
1853
+ onClick: () => onChange(null),
1854
+ disabled,
1855
+ children: [
1856
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Use preset font" }),
1857
+ /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: defaultFont })
1858
+ ]
1859
+ }
1860
+ ),
1861
+ recommendedOptions.map((family) => /* @__PURE__ */ jsx(
1862
+ FontFamilyOption,
1863
+ {
1864
+ family,
1865
+ selected: value === family,
1866
+ fit: evaluateTypographyFontSlot({
1867
+ slot,
1868
+ policy: preset.slotPolicies[slot],
1869
+ capability: capabilityForFamily(family, recordsByFamily)
1870
+ }),
1871
+ onSelect: () => onChange(family),
1872
+ disabled
1873
+ },
1874
+ family
1875
+ ))
1876
+ ] }),
1877
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border bg-muted/20 p-2", children: [
1878
+ /* @__PURE__ */ jsx(
1879
+ "input",
1880
+ {
1881
+ type: "search",
1882
+ value: query,
1883
+ placeholder: `Search ${slot} fonts`,
1884
+ onChange: (event) => setQuery(event.currentTarget.value),
1885
+ disabled: disabled || catalog.kind === "loading",
1886
+ 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"
1887
+ }
1888
+ ),
1889
+ 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,
1890
+ browseOptions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-1 overflow-y-auto pr-1", children: browseOptions.map((font) => /* @__PURE__ */ jsx(
1891
+ FontFamilyOption,
1892
+ {
1893
+ family: font.family,
1894
+ selected: value === font.family,
1895
+ fit: evaluateTypographyFontSlot({
1896
+ slot,
1897
+ policy: preset.slotPolicies[slot],
1898
+ capability: capabilityFromFontRecord(font)
1899
+ }),
1900
+ onSelect: () => onChange(font.family),
1901
+ disabled
1902
+ },
1903
+ font.family
1904
+ )) }) : catalog.kind === "loaded" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No matching Google Fonts." }) : null
1905
+ ] }),
1906
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
1907
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Brand or uploaded font name" }),
1908
+ /* @__PURE__ */ jsx(
1909
+ "input",
1910
+ {
1911
+ type: "text",
1912
+ value: value ?? "",
1913
+ placeholder: "Enter any font family",
1914
+ onChange: (event) => onChange(normalizeFontFamilyOverride(event.currentTarget.value)),
1915
+ disabled,
1916
+ 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"
1917
+ }
1918
+ )
1919
+ ] })
1920
+ ] });
1921
+ }
1922
+ function FontFamilyOption({
1923
+ family,
1924
+ selected,
1925
+ fit,
1926
+ onSelect,
1927
+ disabled
1928
+ }) {
1929
+ return /* @__PURE__ */ jsxs(
1930
+ "button",
1931
+ {
1932
+ type: "button",
1933
+ className: cn(
1934
+ "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",
1935
+ selected ? "border-foreground bg-accent/40" : "border-transparent bg-background hover:border-foreground/40 hover:bg-accent/20"
1936
+ ),
1937
+ onClick: onSelect,
1938
+ disabled,
1939
+ children: [
1940
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: family }),
1941
+ /* @__PURE__ */ jsx(TypographyFontFitBadge, { fit })
1942
+ ]
1943
+ }
1944
+ );
1945
+ }
1946
+ function TypographyFontFitBadge({ fit }) {
1947
+ return /* @__PURE__ */ jsx(
1948
+ "span",
1949
+ {
1950
+ className: cn(
1951
+ "shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium",
1952
+ fit.kind === "recommended" && "bg-emerald-100 text-emerald-800",
1953
+ fit.kind === "compatible" && "bg-sky-100 text-sky-800",
1954
+ fit.kind === "limited" && "bg-amber-100 text-amber-900",
1955
+ fit.kind === "risky" && "bg-rose-100 text-rose-800",
1956
+ fit.kind === "unknown" && "bg-muted text-muted-foreground"
1957
+ ),
1958
+ title: fit.reason,
1959
+ children: typographyFontFitLabel(fit.kind)
1960
+ }
1961
+ );
1962
+ }
1963
+ function useFontCatalog(apiClient, enabled) {
1964
+ const [catalog, setCatalog] = useState({ kind: "idle", fonts: [] });
1965
+ const requestedClientRef = useRef(null);
1966
+ useEffect(() => {
1967
+ if (!enabled) return;
1968
+ if (requestedClientRef.current === apiClient) return;
1969
+ requestedClientRef.current = apiClient;
1970
+ let cancelled = false;
1971
+ setCatalog({ kind: "loading", fonts: [] });
1972
+ async function loadFonts() {
1973
+ try {
1974
+ const response = await apiClient({ endpoint: "listFonts" });
1975
+ const fonts = unwrapResponse(response);
1976
+ if (!cancelled) {
1977
+ setCatalog({
1978
+ kind: "loaded",
1979
+ fonts: Array.isArray(fonts) ? fonts : []
1980
+ });
1981
+ }
1982
+ } catch (error) {
1983
+ if (!cancelled) {
1984
+ setCatalog({
1985
+ kind: "failed",
1986
+ fonts: [],
1987
+ message: error instanceof Error ? error.message : "Unable to load the font catalog."
1988
+ });
1989
+ }
1990
+ }
1991
+ }
1992
+ void loadFonts();
1993
+ return () => {
1994
+ cancelled = true;
1995
+ };
1996
+ }, [apiClient, enabled]);
1997
+ return catalog;
1998
+ }
1999
+ function indexFontsByFamily(fonts) {
2000
+ return new Map(fonts.map((font) => [font.family.toLowerCase(), font]));
2001
+ }
2002
+ function capabilityForFamily(family, recordsByFamily) {
2003
+ const record = recordsByFamily.get(family.toLowerCase());
2004
+ if (record) return capabilityFromFontRecord(record);
2005
+ return {
2006
+ source: "unknown",
2007
+ family,
2008
+ category: "unknown",
2009
+ variants: []
2010
+ };
2011
+ }
2012
+ function capabilityFromFontRecord(font) {
2013
+ return {
2014
+ source: "google",
2015
+ family: font.family,
2016
+ category: normalizeGoogleFontCategory(font.category),
2017
+ variants: font.variants
2018
+ };
2019
+ }
2020
+ function normalizeGoogleFontCategory(category) {
2021
+ switch (category) {
2022
+ case "serif":
2023
+ case "sans-serif":
2024
+ case "display":
2025
+ case "handwriting":
2026
+ case "monospace":
2027
+ return category;
2028
+ default:
2029
+ return "unknown";
2030
+ }
2031
+ }
2032
+ function sortFontForPresetSlot(a, b, slot, preset, recordsByFamily) {
2033
+ const aFit = evaluateTypographyFontSlot({
2034
+ slot,
2035
+ policy: preset.slotPolicies[slot],
2036
+ capability: capabilityForFamily(a.family, recordsByFamily)
2037
+ });
2038
+ const bFit = evaluateTypographyFontSlot({
2039
+ slot,
2040
+ policy: preset.slotPolicies[slot],
2041
+ capability: capabilityForFamily(b.family, recordsByFamily)
2042
+ });
2043
+ const fitDelta = typographyFontFitRank(aFit.kind) - typographyFontFitRank(bFit.kind);
2044
+ if (fitDelta !== 0) return fitDelta;
2045
+ const popularityDelta = (a.popularity ?? Infinity) - (b.popularity ?? Infinity);
2046
+ if (popularityDelta !== 0) return popularityDelta;
2047
+ return a.family.localeCompare(b.family);
2048
+ }
2049
+ function typographyFontFitRank(kind) {
2050
+ switch (kind) {
2051
+ case "recommended":
2052
+ return 0;
2053
+ case "compatible":
2054
+ return 1;
2055
+ case "limited":
2056
+ return 2;
2057
+ case "risky":
2058
+ return 3;
2059
+ case "unknown":
2060
+ return 4;
2061
+ default: {
2062
+ const exhaustive = kind;
2063
+ return exhaustive;
2064
+ }
2065
+ }
2066
+ }
2067
+ function typographyFontFitLabel(kind) {
2068
+ switch (kind) {
2069
+ case "recommended":
2070
+ return "Recommended";
2071
+ case "compatible":
2072
+ return "Compatible";
2073
+ case "limited":
2074
+ return "Limited";
2075
+ case "risky":
2076
+ return "Risky";
2077
+ case "unknown":
2078
+ return "Unknown";
2079
+ default: {
2080
+ const exhaustive = kind;
2081
+ return exhaustive;
2082
+ }
2083
+ }
2084
+ }
2085
+ function primaryFontFamily(cssFamily) {
2086
+ return cssFamily.split(",")[0]?.trim().replace(/^["']|["']$/gu, "") || cssFamily;
2087
+ }
2088
+ function uniqueFontFamilies(families) {
2089
+ const seen = /* @__PURE__ */ new Set();
2090
+ const result = [];
2091
+ for (const family of families) {
2092
+ const normalized = primaryFontFamily(family);
2093
+ const key = normalized.toLowerCase();
2094
+ if (seen.has(key)) continue;
2095
+ seen.add(key);
2096
+ result.push(normalized);
2097
+ }
2098
+ return result;
2099
+ }
2100
+ function normalizeFontFamilyOverride(value) {
2101
+ const trimmed = value.trim();
2102
+ return trimmed.length > 0 ? trimmed : null;
2103
+ }
2104
+ function SiteChromeLookRow({
2105
+ label,
2106
+ ariaLabel,
2107
+ defaultDescription,
2108
+ catalog,
2109
+ options = [
2110
+ {
2111
+ id: null,
2112
+ label: "Site style default",
2113
+ description: defaultDescription,
2114
+ available: true
2115
+ },
2116
+ ...(catalog ?? []).map((look) => ({ ...look, available: true }))
2117
+ ],
2118
+ diagnostics,
2119
+ scopeDescription,
2120
+ expanded,
2121
+ onToggle,
2122
+ currentId,
2123
+ currentLabel,
2124
+ onCycle,
2125
+ onPreview,
2126
+ disabled
2127
+ }) {
2128
+ return /* @__PURE__ */ jsxs(StyleRowFrame, { label, currentLabel, expanded, onToggle, children: [
2129
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-start justify-between gap-3", children: [
2130
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: scopeDescription }),
2131
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
2132
+ /* @__PURE__ */ jsx(
2133
+ "button",
2134
+ {
2135
+ type: "button",
2136
+ onClick: () => onCycle("prev"),
2137
+ className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
2138
+ "aria-label": `Previous ${label.toLowerCase()}`,
2139
+ disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
2140
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
2141
+ }
2142
+ ),
2143
+ /* @__PURE__ */ jsx(
2144
+ "button",
2145
+ {
2146
+ type: "button",
2147
+ onClick: () => onCycle("next"),
2148
+ className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
2149
+ "aria-label": `Next ${label.toLowerCase()}`,
2150
+ disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
2151
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
2152
+ }
2153
+ )
2154
+ ] })
2155
+ ] }),
2156
+ 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,
2157
+ /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": ariaLabel, className: "list-none space-y-1 p-0", children: options.map((look) => /* @__PURE__ */ jsx(
2158
+ PickerOption,
2159
+ {
2160
+ selected: look.id === currentId,
2161
+ label: look.label,
2162
+ description: look.description,
2163
+ onSelect: () => onPreview(look.id),
2164
+ disabled: disabled || !look.available
2165
+ },
2166
+ look.id ?? "site-style-default"
2167
+ )) })
2168
+ ] });
2169
+ }
2170
+ function PaletteRow({ expanded, onToggle, siteStyle, currentId, currentLabel, currentSwatch, onPreview, disabled }) {
2171
+ const options = useMemo(
2172
+ () => {
2173
+ const recommended = requireEntriesForCuratedChoices(paletteVariants, siteStyle.paletteVariantChoices);
2174
+ if (recommended.some((variant) => variant.id === currentId)) {
2175
+ return recommended;
2176
+ }
2177
+ const current = findPaletteVariant(currentId);
2178
+ return current ? [current, ...recommended] : recommended;
2179
+ },
2180
+ [currentId, siteStyle.paletteVariantChoices]
2181
+ );
2182
+ return /* @__PURE__ */ jsxs(
2183
+ StyleRowFrame,
2184
+ {
2185
+ label: "Palette",
2186
+ currentLabel,
2187
+ swatch: currentSwatch,
2188
+ expanded,
2189
+ onToggle,
2190
+ children: [
2191
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-muted-foreground", children: "These palette bases are recommended for the current Site Style. Choose a base here, then edit exact colours in Palette colours." }),
2192
+ /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Palette", className: "list-none space-y-1 p-0", children: options.map((variant) => {
2193
+ const primaryHex = variant.colors.find((c) => c.name === "primary")?.hex;
2194
+ return /* @__PURE__ */ jsx(
2195
+ PickerOption,
2196
+ {
2197
+ selected: variant.id === currentId,
2198
+ label: variant.name,
2199
+ description: variant.description,
2200
+ swatch: primaryHex,
2201
+ onSelect: () => onPreview(variant.id),
2202
+ disabled
2203
+ },
2204
+ variant.id
2205
+ );
2206
+ }) })
2207
+ ]
2208
+ }
2209
+ );
2210
+ }
2211
+ function ColoursRow({ expanded, onToggle, variant, overrides, onTokenChange, onReset, disabled }) {
2212
+ const mergedPalette = useMemo(() => {
2213
+ return variant.colors.map((color) => ({
2214
+ name: color.name,
2215
+ hex: overrides?.[color.name] ?? color.hex
2216
+ }));
2217
+ }, [variant, overrides]);
2218
+ const overrideCount = overrides ? Object.keys(overrides).length : 0;
2219
+ const currentLabel = overrideCount > 0 ? `${overrideCount} colour${overrideCount === 1 ? "" : "s"} customised` : "Using selected palette";
2220
+ return /* @__PURE__ */ jsx(
2221
+ StyleRowFrame,
2222
+ {
2223
+ label: "Palette colours",
2224
+ currentLabel,
2225
+ expanded,
2226
+ onToggle,
2227
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
2228
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Customise the palette tokens used across the site, including backgrounds, surfaces, text and borders." }),
2229
+ /* @__PURE__ */ jsx(
2230
+ PaletteTokenEditor,
2231
+ {
2232
+ palette: mergedPalette,
2233
+ onChange: onTokenChange,
2234
+ brandRoles: PALETTE_BRAND_ROLES,
2235
+ feedbackRoles: PALETTE_FEEDBACK_ROLES,
2236
+ surfaceRoles: PALETTE_SURFACE_ROLES,
2237
+ showContrastChecks: false
2238
+ }
2239
+ ),
2240
+ overrideCount > 0 ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
2241
+ Button,
2242
+ {
2243
+ type: "button",
2244
+ variant: "outline",
2245
+ size: "sm",
2246
+ onClick: onReset,
2247
+ disabled,
2248
+ children: "Reset to variant"
2249
+ }
2250
+ ) }) : null
2251
+ ] })
2252
+ }
2253
+ );
2254
+ }
2255
+ function PickerOption({ selected, label, description, swatch, onSelect, disabled }) {
2256
+ return /* @__PURE__ */ jsx("li", { className: "list-none", children: /* @__PURE__ */ jsxs(
2257
+ "button",
2258
+ {
2259
+ type: "button",
2260
+ role: "radio",
2261
+ "aria-checked": selected,
2262
+ onClick: onSelect,
2263
+ disabled,
2264
+ className: cn(
2265
+ "flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left transition",
2266
+ selected ? "border-foreground bg-accent/40 shadow-sm" : "border-transparent bg-background hover:border-foreground/40 hover:bg-accent/20",
2267
+ disabled && "cursor-not-allowed opacity-60"
2268
+ ),
2269
+ children: [
2270
+ swatch ? /* @__PURE__ */ jsx(
2271
+ "span",
2272
+ {
2273
+ className: "h-5 w-5 shrink-0 rounded-full border border-black/10",
2274
+ style: { backgroundColor: swatch },
2275
+ "aria-hidden": "true"
2276
+ }
2277
+ ) : null,
2278
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2279
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: label }),
2280
+ description ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: description }) : null
2281
+ ] })
2282
+ ]
2283
+ }
2284
+ ) });
2285
+ }
2286
+ function classifyStyleApplyError(error) {
2287
+ if (isRecord(error)) {
2288
+ const status = error.status;
2289
+ const code = error.code;
2290
+ if (status === 409 || code === "resource:conflict") {
2291
+ return {
2292
+ kind: "stale",
2293
+ message: "This design changed elsewhere. Refreshing the latest version now."
2294
+ };
2295
+ }
2296
+ }
2297
+ return {
2298
+ kind: "other",
2299
+ message: error instanceof Error ? error.message : "Unable to apply your selection."
2300
+ };
2301
+ }
2302
+ function isRecord(value) {
2303
+ return typeof value === "object" && value !== null;
2304
+ }
2305
+ function WarningNotice({ warnings, onDismiss }) {
2306
+ 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: [
2307
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: warnings.map((warning, index) => /* @__PURE__ */ jsx("li", { children: describeWarning(warning) }, `${warning.kind}-${index}`)) }),
2308
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, className: "mt-2 underline", children: "Dismiss" })
2309
+ ] });
2310
+ }
2311
+ function NoticeBanner({ notices, onDismiss }) {
2312
+ return /* @__PURE__ */ jsxs("div", { role: "status", className: "rounded-md border border-muted-foreground/20 bg-muted/40 px-3 py-2 text-xs", children: [
2313
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: notices.map((notice, index) => /* @__PURE__ */ jsx("li", { children: describeNotice(notice) }, `${notice.kind}-${index}`)) }),
2314
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, className: "mt-2 underline", children: "Dismiss" })
2315
+ ] });
2316
+ }
2317
+ function describeNotice(notice) {
2318
+ switch (notice.kind) {
2319
+ case "palette-overrides-hidden":
2320
+ return "Custom colours are saved for the previous palette.";
2321
+ case "palette-overrides-restored":
2322
+ return "Custom colours restored for this palette.";
2323
+ }
2324
+ }
2325
+ function describeWarning(warning) {
2326
+ switch (warning.kind) {
2327
+ case "rejected-personality":
2328
+ return `Button style changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
2329
+ case "rejected-palette":
2330
+ return `Palette changed to ${warning.fellBackTo} because ${warning.requested} is not available.`;
2331
+ case "rejected-typography-preset":
2332
+ return `Typography changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
2333
+ default: {
2334
+ const exhaustive = warning;
2335
+ return exhaustive;
2336
+ }
2337
+ }
2338
+ }
2339
+ function PreviewEditorSidebar({
2340
+ surfaceId,
2341
+ siteId,
2342
+ dashboardBaseUrl = null,
2343
+ pageId,
2344
+ entryData,
2345
+ contentTypeSchema,
2346
+ referenceContentTypeKeys = [],
2347
+ draftContent,
2348
+ currentTemplate,
2349
+ theme,
2350
+ themeId = null,
2351
+ themeSelectionId = null,
2352
+ themeSelectionVersion = null,
2353
+ themeSelectionUpdatedAt = null,
2354
+ themeSelectionButtonPersonalityId = null,
2355
+ themeSelectionPaletteVariantId = null,
2356
+ themeSelectionPaletteOverrides = null,
2357
+ themeSelectionHeaderLayoutId = null,
2358
+ themeSelectionHeaderStyleId = null,
2359
+ themeSelectionTypographyPresetId = null,
2360
+ themeSelectionTypographyBodyFontOverride = null,
2361
+ themeSelectionTypographyHeadingFontOverride = null,
2362
+ themeSelectionHeaderLookId = null,
2363
+ themeSelectionFooterLookId = null,
2364
+ themeSelectionHeaderContext,
2365
+ themeSelectionFooterContext,
2366
+ designRowRequest = null,
2367
+ apiClient,
2368
+ bearerToken,
2369
+ supabaseUrl,
2370
+ entrySubrouteContext,
2371
+ assistantPanel,
2372
+ sdkConfig,
2373
+ mediaAssets,
2374
+ siteStyleAccess = { kind: "hidden" },
2375
+ activeTab,
2376
+ onTabChange,
2377
+ onClose,
2378
+ viewportMode,
2379
+ viewportWidth,
2380
+ zoom,
2381
+ onViewportModeChange,
2382
+ onViewportWidthChange,
2383
+ onZoomChange
2384
+ }) {
2385
+ const controller = useEntryController();
2386
+ const controllerBlocks = controller.blocks;
2387
+ const uiStore = useEditorUiStoreInstance();
2388
+ const uiStoreForBlockEditor = useMemo(
2389
+ () => ({
2390
+ getState: uiStore.getState,
2391
+ subscribe: (listener) => uiStore.subscribe(() => listener())
2392
+ }),
2393
+ [uiStore]
2394
+ );
2395
+ const focusRequest = useUISelector(uiStore, (s) => s.focusRequest);
2396
+ const { canEdit } = usePreviewAuth();
2397
+ const dashboardSetupHandoff = useMemo(
2398
+ () => resolvePreviewDashboardSetupHandoff(),
2399
+ []
2400
+ );
2401
+ const dashboardContinueUrl = useMemo(
2402
+ () => buildPreviewDashboardSetupUrl(dashboardBaseUrl, dashboardSetupHandoff),
2403
+ [dashboardBaseUrl, dashboardSetupHandoff]
2404
+ );
2405
+ const sidebarChrome = useMemo(
2406
+ () => resolvePreviewSidebarChromeModel(surfaceId),
2407
+ [surfaceId]
2408
+ );
2409
+ const editorGuidance = useMemo(
2410
+ () => resolvePreviewEditorGuidanceModel({
2411
+ surfaceId,
2412
+ canEdit,
2413
+ hasDashboardSetupHandoff: Boolean(dashboardSetupHandoff)
2414
+ }),
2415
+ [canEdit, dashboardSetupHandoff, surfaceId]
2416
+ );
2417
+ const showAssistantTab = previewAssistantEnabled && Boolean(assistantPanel);
2418
+ const assistantAvailable = showAssistantTab && Boolean(pageId && bearerToken && canEdit);
2419
+ const effectiveActiveTab = showAssistantTab ? resolvePreviewSidebarTab(activeTab) : "editor";
2420
+ const blockItems = useMemo(
2421
+ () => controllerBlocks.list,
2422
+ [controllerBlocks.list]
2423
+ );
2424
+ const blockEditorRef = useRef(null);
2425
+ const contentFormWidgets = useMemo(() => previewWidgetOverrides, []);
2426
+ const handleBlocksChange = useCallback(
2427
+ (nextItems) => {
2428
+ controllerBlocks.actions.setList(nextItems);
2429
+ const drafts = uiStore.getState().drafts;
2430
+ const extractData = (content) => {
2431
+ if (!content || typeof content !== "object") return null;
2432
+ const obj = content;
2433
+ if ("data" in obj && obj.data && typeof obj.data === "object") {
2434
+ return obj.data;
2435
+ }
2436
+ return obj;
2437
+ };
2438
+ for (const block of nextItems) {
2439
+ if (!block.id) continue;
2440
+ const baseline = extractData(
2441
+ block.content ?? block.draftContent ?? block.publishedContent ?? null
2442
+ );
2443
+ const blockId = unsafeAsEditorBlockId(block.id);
2444
+ if (drafts.getBlockSnapshot(blockId)) {
2445
+ drafts.updateBlockBaseline(blockId, baseline, { kind: block.kind });
2446
+ } else {
2447
+ drafts.initNewBlock(blockId, baseline, { kind: block.kind });
2448
+ }
2449
+ }
2450
+ },
2451
+ [controllerBlocks.actions, uiStore]
2452
+ );
2453
+ useEffect(() => {
2454
+ const req = focusRequest;
2455
+ if (!req?.blockId) return;
2456
+ blockEditorRef.current?.focusField({
2457
+ blockId: req.blockId,
2458
+ fieldPath: req.fieldPath ?? void 0,
2459
+ itemIndex: req.itemIndex ?? void 0
2460
+ });
2461
+ uiStore.getState().clearFocus();
2462
+ }, [focusRequest, uiStore]);
2463
+ const metadataSectionContent = useMemo(() => {
2464
+ if (!pageId && !entryData) return null;
2465
+ return /* @__PURE__ */ jsx(PreviewMetadataPanel, { contentTypeKey: entryData?.type ?? null });
2466
+ }, [entryData, pageId]);
2467
+ const editorPanel = entryData ? /* @__PURE__ */ jsx(
2468
+ EntryEditorPanel,
2469
+ {
2470
+ siteId,
2471
+ entryData,
2472
+ contentTypeSchema,
2473
+ referenceContentTypeKeys,
2474
+ draftContent,
2475
+ currentTemplate,
2476
+ theme,
2477
+ apiClient,
2478
+ bearerToken,
2479
+ supabaseUrl,
2480
+ entrySubrouteContext,
2481
+ metadataPanel: metadataSectionContent,
2482
+ mediaAssets,
2483
+ widgets: contentFormWidgets,
2484
+ viewportMode,
2485
+ viewportWidth,
2486
+ zoom,
2487
+ onViewportModeChange,
2488
+ onViewportWidthChange,
2489
+ onZoomChange
2490
+ }
2491
+ ) : pageId ? /* @__PURE__ */ jsx(
2492
+ PageBlockEditorPanel,
2493
+ {
2494
+ siteId,
2495
+ pageId,
2496
+ theme,
2497
+ apiClient,
2498
+ bearerToken,
2499
+ supabaseUrl,
2500
+ blockItems,
2501
+ uiStoreForBlockEditor,
2502
+ blockEditorRef,
2503
+ onBlocksChange: handleBlocksChange,
2504
+ metadataPanel: metadataSectionContent,
2505
+ designPanel: canInspectSiteStyle(siteStyleAccess) ? /* @__PURE__ */ jsx(
2506
+ StyleConfigurator,
2507
+ {
2508
+ siteId,
2509
+ apiClient,
2510
+ theme,
2511
+ themeId,
2512
+ selectionId: themeSelectionId,
2513
+ selectionVersion: themeSelectionVersion,
2514
+ selectionUpdatedAt: themeSelectionUpdatedAt,
2515
+ initialSelections: {
2516
+ selectionVersion: themeSelectionVersion,
2517
+ selectionUpdatedAt: themeSelectionUpdatedAt,
2518
+ buttonPersonalityId: themeSelectionButtonPersonalityId,
2519
+ paletteVariantId: themeSelectionPaletteVariantId,
2520
+ paletteOverrides: themeSelectionPaletteOverrides,
2521
+ headerLayoutId: themeSelectionHeaderLayoutId,
2522
+ headerStyleId: themeSelectionHeaderStyleId,
2523
+ typographyPresetId: themeSelectionTypographyPresetId,
2524
+ typographyBodyFontOverride: themeSelectionTypographyBodyFontOverride,
2525
+ typographyHeadingFontOverride: themeSelectionTypographyHeadingFontOverride,
2526
+ headerLookId: themeSelectionHeaderLookId,
2527
+ footerLookId: themeSelectionFooterLookId
2528
+ },
2529
+ headerSelectionContext: themeSelectionHeaderContext,
2530
+ footerSelectionContext: themeSelectionFooterContext,
2531
+ expandedRowRequest: designRowRequest,
2532
+ siteStyleAccess
2533
+ }
2534
+ ) : null,
2535
+ sdkConfig: sdkConfig ?? null,
2536
+ mediaAssets,
2537
+ viewportMode,
2538
+ viewportWidth,
2539
+ zoom,
2540
+ onViewportModeChange,
2541
+ onViewportWidthChange,
2542
+ onZoomChange
2543
+ }
2544
+ ) : /* @__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." }) }) });
2545
+ 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." });
2546
+ return /* @__PURE__ */ jsx(
2547
+ "div",
2548
+ {
2549
+ 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}`,
2550
+ "data-preview-sidebar-tone": sidebarChrome.tone,
2551
+ children: /* @__PURE__ */ jsxs(
2552
+ Tabs,
2553
+ {
2554
+ value: effectiveActiveTab,
2555
+ onValueChange: (value) => onTabChange(
2556
+ resolvePreviewSidebarTab(value)
2557
+ ),
2558
+ className: "flex h-full min-h-0 flex-col overflow-hidden",
2559
+ children: [
2560
+ /* @__PURE__ */ jsxs(
2561
+ "div",
2562
+ {
2563
+ className: `shrink-0 flex items-center justify-between px-4 py-3 ${sidebarChrome.headerClassName}`,
2564
+ children: [
2565
+ /* @__PURE__ */ jsxs(TabsList, { className: "h-8", children: [
2566
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "editor", children: "Editor" }),
2567
+ showAssistantTab ? /* @__PURE__ */ jsx(TabsTrigger, { value: "assistant", children: "AI Assistant" }) : null
2568
+ ] }),
2569
+ /* @__PURE__ */ jsx(
2570
+ Button,
2571
+ {
2572
+ variant: "ghost",
2573
+ size: "icon",
2574
+ onClick: onClose,
2575
+ "aria-label": "Close sidebar",
2576
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
2577
+ }
2578
+ )
2579
+ ]
2580
+ }
2581
+ ),
2582
+ editorGuidance ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-3", children: /* @__PURE__ */ jsx(
2583
+ PreviewEditorGuidanceCard,
2584
+ {
2585
+ guidance: editorGuidance,
2586
+ completeSetupDisabled: !dashboardContinueUrl,
2587
+ onCompleteSetup: () => {
2588
+ if (!dashboardContinueUrl || typeof window === "undefined")
2589
+ return;
2590
+ window.location.href = dashboardContinueUrl;
2591
+ }
2592
+ }
2593
+ ) }) : null,
2594
+ /* @__PURE__ */ jsx(
2595
+ TabsContent,
2596
+ {
2597
+ value: "editor",
2598
+ forceMount: true,
2599
+ className: "mt-0 min-h-0 flex-1 overflow-hidden data-[state=inactive]:hidden",
2600
+ children: editorPanel
2601
+ }
2602
+ ),
2603
+ showAssistantTab ? /* @__PURE__ */ jsx(
2604
+ TabsContent,
2605
+ {
2606
+ value: "assistant",
2607
+ forceMount: true,
2608
+ className: "mt-0 min-h-0 flex-1 overflow-hidden data-[state=inactive]:hidden",
2609
+ children: assistantPanelDisplay
2610
+ }
2611
+ ) : null
2612
+ ]
2613
+ }
2614
+ )
2615
+ }
2616
+ );
2617
+ }
2618
+ function PreviewMetadataPanel({
2619
+ contentTypeKey
2620
+ }) {
2621
+ const uiStore = useEditorUiStoreInstance();
2622
+ const manifest = useMemo(() => {
2623
+ return omitRouteTitleFieldFromManifest(
2624
+ getRouteMetadataManifestForType(contentTypeKey)
2625
+ );
2626
+ }, [contentTypeKey]);
2627
+ const validation = useMemo(
2628
+ () => createManifestValidation(manifest),
2629
+ [manifest]
2630
+ );
2631
+ const backend = useMemo(
2632
+ () => createStoreBackend({
2633
+ scope: "metadata",
2634
+ store: uiStore,
2635
+ validateFn: (values) => {
2636
+ const result = validation.schema.safeParse(values);
2637
+ if (!result.success) {
2638
+ return result.error.issues;
2639
+ }
2640
+ return [];
2641
+ }
2642
+ }),
2643
+ [uiStore, validation]
2644
+ );
2645
+ return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
2646
+ ManifestForm,
2647
+ {
2648
+ manifest,
2649
+ backend,
2650
+ className: "space-y-4"
2651
+ }
2652
+ ) });
2653
+ }
2654
+
2655
+ export { PreviewEditorSidebar };