@riverbankcms/sdk 0.151.0 → 0.153.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 (316) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/{EventEmbeddedCheckoutStep-BBHbM22D.mjs → EventEmbeddedCheckoutStep-veN-QiAE.mjs} +10 -9
  3. package/dist/{PreviewEditorSidebar-BHplI9HE.mjs → PreviewEditorSidebar-jI_4DTZY.mjs} +4 -3
  4. package/dist/{PreviewEditorUI-DuyN8nsD.mjs → PreviewEditorUI-ORkVZhfu.mjs} +5 -4
  5. package/dist/{SdkPreviewModeRuntime-CR5ypwxJ.mjs → SdkPreviewModeRuntime-DaNqevI1.mjs} +341 -164
  6. package/dist/{SiteChromeCustomizeContext-D5t6joF_.mjs → SiteChromeCustomizeContext-CgNuBHC0.mjs} +27124 -39230
  7. package/dist/ThemedButton-X2UxhW6F.mjs +16525 -0
  8. package/dist/_dts/ai/src/contracts/proposals.d.ts +52 -52
  9. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +2357 -7859
  10. package/dist/_dts/api/src/bookingOperations.d.ts +5 -3
  11. package/dist/_dts/api/src/commercePortalEndpoints.d.ts +4 -0
  12. package/dist/_dts/api/src/communications.d.ts +19 -5
  13. package/dist/_dts/api/src/endpoints.d.ts +7 -0
  14. package/dist/_dts/api/src/index.d.ts +1 -1
  15. package/dist/_dts/api/src/indexExtendedTypes.d.ts +1 -1
  16. package/dist/_dts/api/src/navigation/types.d.ts +7 -0
  17. package/dist/_dts/api/src/navigation.d.ts +1 -1
  18. package/dist/_dts/api/src/publicAppointmentBookingContracts.d.ts +10 -1
  19. package/dist/_dts/api/src/sdkThemeWire.d.ts +186 -1434
  20. package/dist/_dts/api/src/shared-contracts.d.ts +10 -0
  21. package/dist/_dts/api/src/siteAssets.d.ts +1 -0
  22. package/dist/_dts/api/src/siteInfrastructure.d.ts +2 -1
  23. package/dist/_dts/api/src/teamMembers.d.ts +2 -27
  24. package/dist/_dts/block-form/src/widgets/RepeaterField/components/layoutGridDomain.d.ts +3 -13
  25. package/dist/_dts/blocks/src/contracts/index.d.ts +1 -0
  26. package/dist/_dts/blocks/src/definitions.d.ts +1 -1
  27. package/dist/_dts/blocks/src/index.d.ts +2 -0
  28. package/dist/_dts/blocks/src/lib/media.d.ts +1 -0
  29. package/dist/_dts/blocks/src/lib/picsum.d.ts +2 -2
  30. package/dist/_dts/blocks/src/pageRenderPreparation.d.ts +1 -0
  31. package/dist/_dts/blocks/src/pageRendererContracts.d.ts +2 -1
  32. package/dist/_dts/blocks/src/primitives/content/utils/sanitizeHref.d.ts +1 -1
  33. package/dist/_dts/blocks/src/system/blocks/event-registration.d.ts +4 -4
  34. package/dist/_dts/blocks/src/system/blocks/events/eventSelectionFields.d.ts +1 -1
  35. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +212 -804
  36. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +7 -2
  37. package/dist/_dts/blocks/src/system/blocks/location-map.d.ts +1 -1
  38. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +45 -45
  39. package/dist/_dts/blocks/src/system/blocks/offerings-preview/model.d.ts +2 -0
  40. package/dist/_dts/blocks/src/system/blocks/shared/teamMembers.d.ts +4 -7
  41. package/dist/_dts/blocks/src/system/blocks/team-members.d.ts +2 -2
  42. package/dist/_dts/blocks/src/system/customize/backgrounds.d.ts +6 -6
  43. package/dist/_dts/blocks/src/system/fields/backgroundContent.d.ts +377 -1
  44. package/dist/_dts/blocks/src/system/helpers/backgroundMedia.d.ts +6 -2
  45. package/dist/_dts/blocks/src/system/helpers/normalizeMailingListBlockContent.d.ts +2 -0
  46. package/dist/_dts/blocks/src/system/helpers/resolveBackground.d.ts +3 -3
  47. package/dist/_dts/blocks/src/system/layout/repeaterGrid.d.ts +12 -5
  48. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +3 -3
  49. package/dist/_dts/blocks/src/system/manifest/schema.d.ts +24 -24
  50. package/dist/_dts/blocks/src/system/manifest/validation.d.ts +2 -0
  51. package/dist/_dts/blocks/src/system/node/fragments/lookSlot.d.ts +1 -1
  52. package/dist/_dts/blocks/src/system/registry.d.ts +14 -2
  53. package/dist/_dts/blocks/src/system/runtime/api/client.d.ts +0 -5
  54. package/dist/_dts/blocks/src/system/runtime/api/index.d.ts +2 -0
  55. package/dist/_dts/blocks/src/system/runtime/api/schemas/events.d.ts +971 -0
  56. package/dist/_dts/blocks/src/system/runtime/api/schemas/waitlist-claims.d.ts +42 -0
  57. package/dist/_dts/blocks/src/system/runtime/api/types/appointments.d.ts +10 -2
  58. package/dist/_dts/blocks/src/system/runtime/api/types/events.d.ts +10 -136
  59. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +48 -6
  60. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +24 -3
  61. package/dist/_dts/blocks/src/system/runtime/hooks/useBookingFlowController.d.ts +1 -1
  62. package/dist/_dts/blocks/src/system/runtime/hooks/useBookingSubmission.state.d.ts +2 -0
  63. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.d.ts +5 -1
  64. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.d.ts +1 -1
  65. package/dist/_dts/blocks/src/system/runtime/lookSurfaces.d.ts +2 -2
  66. package/dist/_dts/blocks/src/system/runtime/nodes/ImageResolveContext.d.ts +1 -1
  67. package/dist/_dts/blocks/src/system/runtime/nodes/appointment-services.d.ts +50 -202
  68. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +51 -1
  69. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +6 -2
  70. package/dist/_dts/blocks/src/system/runtime/nodes/carousel.client.hooks.d.ts +6 -0
  71. package/dist/_dts/blocks/src/system/runtime/nodes/carousel.client.state.d.ts +7 -0
  72. package/dist/_dts/blocks/src/system/runtime/nodes/event-details/lookPresentation.d.ts +2 -2
  73. package/dist/_dts/blocks/src/system/runtime/nodes/event-details/presentation.d.ts +1 -1
  74. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/BookingEntryViews.d.ts +42 -0
  75. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventEmbeddedCheckoutStep.d.ts +4 -1
  76. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationBookingLayer.d.ts +19 -9
  77. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationPayment.d.ts +4 -2
  78. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationSurface.d.ts +24 -9
  79. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationWizardContent.d.ts +4 -2
  80. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventSelectionStep.d.ts +5 -4
  81. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/GuestDetailsSection.d.ts +1 -1
  82. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/PaymentStatusStep.d.ts +2 -0
  83. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/SingleTicketEntitlementNote.d.ts +11 -0
  84. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/SuccessStep.d.ts +4 -0
  85. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/TicketSelectionStep.d.ts +5 -0
  86. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/VerifyingTimeoutStep.d.ts +2 -0
  87. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/bookingCardDisplay.d.ts +31 -0
  88. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/bookingEntry.d.ts +154 -0
  89. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/bookingFocus.d.ts +12 -0
  90. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +2 -2
  91. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.d.ts +2 -2
  92. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.d.ts +9 -0
  93. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/finishedBooking.d.ts +77 -0
  94. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/index.d.ts +0 -1
  95. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/offeringNoun.d.ts +12 -0
  96. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/paymentResponse.d.ts +2 -1
  97. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationCompletion.d.ts +33 -1
  98. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationDerivations.d.ts +11 -0
  99. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationDisplay.d.ts +31 -0
  100. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationDraft.state.d.ts +60 -0
  101. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.d.ts +1 -1
  102. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.d.ts +3 -0
  103. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationStateMachine.d.ts +58 -10
  104. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationSurface.d.ts +17 -0
  105. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +23 -7
  106. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useBookingBarFacts.d.ts +12 -0
  107. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationRecoveryRequest.d.ts +3 -0
  108. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +4 -0
  109. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +4 -11
  110. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +1563 -31
  111. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventSourceRecovery.d.ts +2 -12
  112. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.d.ts +147 -4
  113. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/utils.d.ts +50 -2
  114. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/waitlistClaimRestoration.d.ts +6 -5
  115. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/wizardSteps.d.ts +85 -15
  116. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration.interactive.d.ts +53 -8
  117. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/CardListItem.d.ts +1 -1
  118. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/MonthLookView.d.ts +10 -3
  119. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +290 -882
  120. package/dist/_dts/blocks/src/system/runtime/nodes/file-download.d.ts +3 -2
  121. package/dist/_dts/blocks/src/system/runtime/nodes/media.d.ts +22 -90
  122. package/dist/_dts/blocks/src/system/runtime/nodes/mediaContext.d.ts +69 -0
  123. package/dist/_dts/blocks/src/system/runtime/shared/bookingPaymentOptionCopy.d.ts +5 -1
  124. package/dist/_dts/blocks/src/system/runtime/shared/bookingPaymentOptions.d.ts +37 -1
  125. package/dist/_dts/blocks/src/system/runtime/shared/components/ModalShell.d.ts +9 -2
  126. package/dist/_dts/blocks/src/system/runtime/shared/components/PaymentChoiceRows.d.ts +28 -0
  127. package/dist/_dts/blocks/src/system/runtime/shared/components/StepIndicator.d.ts +6 -8
  128. package/dist/_dts/blocks/src/system/runtime/shared/components/index.d.ts +2 -2
  129. package/dist/_dts/blocks/src/system/runtime/shared/index.d.ts +2 -2
  130. package/dist/_dts/blocks/src/system/runtime/shared/useWaitlistClaim.d.ts +9 -12
  131. package/dist/_dts/blocks/src/system/runtime/shared/useWaitlistClaim.state.d.ts +15 -0
  132. package/dist/_dts/blocks/src/system/runtime/shared/utils/themePortalRoot.d.ts +10 -15
  133. package/dist/_dts/blocks/src/system/transforms/registry/index.d.ts +2 -0
  134. package/dist/_dts/blocks/src/system/transforms/registry/links.d.ts +2 -0
  135. package/dist/_dts/commerce/src/purchase/bookingInstalmentSchedule.d.ts +2 -0
  136. package/dist/_dts/commerce/src/purchase/bookingPaymentSelection.d.ts +6 -0
  137. package/dist/_dts/commerce/src/purchase/confirmationEmailPayment.d.ts +14 -14
  138. package/dist/_dts/commerce/src/purchase/confirmationPayment.d.ts +65 -0
  139. package/dist/_dts/commerce/src/purchase/confirmationPaymentSchema.d.ts +66 -0
  140. package/dist/_dts/commerce/src/purchase/index.d.ts +2 -0
  141. package/dist/_dts/commerce/src/purchase/orderPaymentState.d.ts +8 -0
  142. package/dist/_dts/content-model/src/content-types/schema.d.ts +3 -3
  143. package/dist/_dts/content-model/src/templates/defaults.d.ts +2 -2
  144. package/dist/_dts/core/src/appointment-booking-terms.d.ts +20 -0
  145. package/dist/_dts/core/src/booking-payment-schemas.d.ts +70 -0
  146. package/dist/_dts/core/src/branded.d.ts +0 -2
  147. package/dist/_dts/core/src/demo-claim-access.d.ts +1 -1
  148. package/dist/_dts/core/src/demo-claim-readiness.d.ts +18 -0
  149. package/dist/_dts/core/src/demo-lifecycle-status.d.ts +1 -1
  150. package/dist/_dts/core/src/demo-warm-pool-target.d.ts +1 -1
  151. package/dist/_dts/core/src/index.d.ts +4 -0
  152. package/dist/_dts/core/src/mailing-list-runtime.d.ts +2 -2
  153. package/dist/_dts/core/src/mailing-list-setup-parser.d.ts +9 -1
  154. package/dist/_dts/core/src/mailing-list-setup.d.ts +9 -1
  155. package/dist/_dts/core/src/mailing-list-submission.d.ts +1 -1
  156. package/dist/_dts/core/src/mailing-list-targeting-availability.d.ts +68 -0
  157. package/dist/_dts/core/src/mailing-list-targeting.d.ts +11 -1
  158. package/dist/_dts/core/src/ticket-quantity.d.ts +13 -0
  159. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +106 -36
  160. package/dist/_dts/editor/src/chrome/chromeLayers.d.ts +7 -0
  161. package/dist/_dts/editor/src/index.d.ts +1 -0
  162. package/dist/_dts/editor-blocks/src/index.d.ts +1 -0
  163. package/dist/_dts/editor-blocks/src/media/MediaField.d.ts +2 -2
  164. package/dist/_dts/editor-blocks/src/media/admitEditorMediaField.d.ts +4 -0
  165. package/dist/_dts/editor-blocks/src/widgets/link/LinkAddressInput.d.ts +8 -0
  166. package/dist/_dts/editor-blocks/src/widgets/link/LinkContentList.d.ts +12 -0
  167. package/dist/_dts/editor-blocks/src/widgets/link/LinkPicker.d.ts +5 -1
  168. package/dist/_dts/editor-blocks/src/widgets/link/LinkPickerDialog.d.ts +3 -3
  169. package/dist/_dts/editor-blocks/src/widgets/link/LinkSummary.d.ts +8 -2
  170. package/dist/_dts/editor-blocks/src/widgets/link/LinkWidget.d.ts +4 -3
  171. package/dist/_dts/editor-blocks/src/widgets/link/LinkWidgetContext.d.ts +2 -1
  172. package/dist/_dts/editor-blocks/src/widgets/link/draft.d.ts +44 -0
  173. package/dist/_dts/editor-blocks/src/widgets/link/presentation.d.ts +13 -0
  174. package/dist/_dts/editor-blocks/src/widgets/link/types.d.ts +14 -0
  175. package/dist/_dts/editor-blocks/src/widgets/link/useRoutableContent.d.ts +2 -0
  176. package/dist/_dts/editor-blocks/src/widgets/link/utils.d.ts +5 -4
  177. package/dist/_dts/editor-blocks/src/widgets/mailing-list-targeting/MailingListTargetingField.d.ts +1 -1
  178. package/dist/_dts/editor-blocks/src/widgets/mailing-list-targeting/targetingState.d.ts +13 -8
  179. package/dist/_dts/media-core/src/assetsInUseSummary.d.ts +6 -0
  180. package/dist/_dts/media-core/src/contentMediaAudit.d.ts +3 -2
  181. package/dist/_dts/media-core/src/fileStorage.d.ts +4 -3
  182. package/dist/_dts/media-core/src/index.d.ts +5 -0
  183. package/dist/_dts/media-core/src/media.d.ts +406 -0
  184. package/dist/_dts/media-core/src/mediaIdentifierSchema.d.ts +3 -0
  185. package/dist/_dts/media-core/src/mediaKinds.d.ts +2 -0
  186. package/dist/_dts/media-core/src/mediaUsageProjection.d.ts +13 -1
  187. package/dist/_dts/media-core/src/mediaWriteNormalizer.d.ts +2 -6
  188. package/dist/_dts/media-core/src/portableMediaReferences.d.ts +8 -21
  189. package/dist/_dts/media-core/src/renderableMediaAsset.d.ts +28 -14
  190. package/dist/_dts/media-core/src/renderableMediaAssetAdmission.d.ts +15 -0
  191. package/dist/_dts/media-core/src/storageObjectKey.d.ts +12 -0
  192. package/dist/_dts/media-editor/src/CropDialog.d.ts +6 -3
  193. package/dist/_dts/media-editor/src/MediaEditor.d.ts +2 -1
  194. package/dist/_dts/media-editor/src/MediaPreview.d.ts +3 -3
  195. package/dist/_dts/media-editor/src/cropEditorSource.d.ts +5 -7
  196. package/dist/_dts/media-editor/src/editorMediaState.d.ts +29 -0
  197. package/dist/_dts/media-editor/src/mediaSelection.d.ts +1 -2
  198. package/dist/_dts/media-editor/src/runtimeMediaAssetState.d.ts +4 -5
  199. package/dist/_dts/media-editor/src/types.d.ts +1 -1
  200. package/dist/_dts/media-editor/src/utils.d.ts +0 -1
  201. package/dist/_dts/media-react/src/components/cropChangePolicy.d.ts +4 -1
  202. package/dist/_dts/media-react/src/components/cropGeometry.d.ts +1 -0
  203. package/dist/_dts/media-react/src/components/useImageCropperController.d.ts +1 -0
  204. package/dist/_dts/media-react/src/hooks/useSupabaseMediaUploader.d.ts +1 -1
  205. package/dist/_dts/media-react/src/internal/supabaseResumableUpload.d.ts +13 -0
  206. package/dist/_dts/preview-next/src/client/blocks/BlockBackgroundChooser.d.ts +2 -1
  207. package/dist/_dts/preview-next/src/client/blocks/previewCustomizeSelectedMedia.d.ts +11 -12
  208. package/dist/_dts/preview-next/src/client/preview/previewModeRootAttribute.d.ts +11 -0
  209. package/dist/_dts/sdk/src/contracts/content.d.ts +2 -45
  210. package/dist/_dts/sdk/src/contracts/index.d.ts +1 -1
  211. package/dist/_dts/sdk/src/contracts/rendering.d.ts +3 -3
  212. package/dist/_dts/sdk/src/index.d.ts +1 -0
  213. package/dist/_dts/sdk/src/rendering/blocks-page-renderer.d.ts +0 -1
  214. package/dist/_dts/sdk/src/rendering/image.d.ts +14 -4
  215. package/dist/_dts/sdk/src/rendering/index.d.ts +5 -2
  216. package/dist/_dts/sdk/src/rendering/link.d.ts +5 -0
  217. package/dist/_dts/sdk/src/rendering/mediaAssets.d.ts +2 -4
  218. package/dist/_dts/sdk/src/rendering/overrides.d.ts +1 -0
  219. package/dist/_dts/sdk/src/version.d.ts +1 -1
  220. package/dist/_dts/theme-core/src/accordions/types.d.ts +1 -1
  221. package/dist/_dts/theme-core/src/blocks/opaquePanels.d.ts +3 -3
  222. package/dist/_dts/theme-core/src/boxBackgroundSchema.d.ts +397 -26
  223. package/dist/_dts/theme-core/src/buttons/types.d.ts +21 -21
  224. package/dist/_dts/theme-core/src/cards/types.d.ts +34 -34
  225. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  226. package/dist/_dts/theme-core/src/inputs/types.d.ts +3 -3
  227. package/dist/_dts/theme-core/src/lib/media.d.ts +2 -616
  228. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +261 -2619
  229. package/dist/_dts/theme-core/src/palette/curated/types.d.ts +1 -1
  230. package/dist/_dts/theme-core/src/schema.d.ts +548 -3187
  231. package/dist/_dts/theme-core/src/site-styles/backgroundAppearance.d.ts +2 -2
  232. package/dist/_dts/theme-core/src/site-styles/runtimeBackgroundResolution.d.ts +9 -9
  233. package/dist/_dts/theme-core/src/site-styles/styleTags.d.ts +1 -1
  234. package/dist/_dts/theme-core/src/site-styles/tokenRecipes.d.ts +1 -1
  235. package/dist/_dts/theme-core/src/styles/skinTokens.d.ts +2 -2
  236. package/dist/_dts/theme-core/src/typography/schema.d.ts +2 -2
  237. package/dist/client/BookingMailingListNote-DrchYW1m.mjs +386 -0
  238. package/dist/client/{CardListItem-ZDLYh6dL.mjs → CardListItem-CC_GVmF6.mjs} +5 -5
  239. package/dist/client/CheckIcon-CpyXr-Kg.mjs +35 -0
  240. package/dist/client/{CheckboxChoice-M-3HycLw.mjs → CheckboxChoice-Dq30m3DO.mjs} +1 -1
  241. package/dist/client/{DemoBookingPortalAccess-BMtvPTWE.mjs → DemoBookingPortalAccess-ojwbVUmx.mjs} +85 -85
  242. package/dist/client/{EventCombined.client-DUm_DOxW.mjs → EventCombined.client-DyN0v0Xk.mjs} +7 -9
  243. package/dist/client/{EventEmbeddedCheckoutStep-DsNNzgRm.mjs → EventEmbeddedCheckoutStep-CgfpKc3n.mjs} +11 -10
  244. package/dist/client/{EventListing.client-DjNYGUVm.mjs → EventListing.client-CuOuvYMz.mjs} +2 -2
  245. package/dist/client/{LeafletStaticMap-DuFiOHq_.mjs → LeafletStaticMap-B0WhRr3j.mjs} +1 -1
  246. package/dist/client/{ModalShell-CqkBvBIC.mjs → ModalShell-BXzucfR6.mjs} +83 -12
  247. package/dist/client/{RichText-LjPCUGGi.mjs → RichText-C1fIXBzA.mjs} +50 -34
  248. package/dist/client/{ThemedButton-DZKjtaDD.mjs → ThemedButton-B7HFqFBg.mjs} +1 -2
  249. package/dist/client/{accordion-Qe--SILD.mjs → accordion-DCDVWSye.mjs} +1 -1
  250. package/dist/client/{bookingForm-3BCl4g_f.mjs → bookingForm-BJ3C4GlM.mjs} +321 -154
  251. package/dist/client/bookings.mjs +118607 -22429
  252. package/dist/client/{carousel-h2YjKOS2.mjs → carousel-BSCBhfHY.mjs} +2 -2
  253. package/dist/client/{carousel.client-BLrd4hmY.mjs → carousel.client-GEtOGsxo.mjs} +46 -41
  254. package/dist/client/{cart-5NeiigVd.mjs → cart-Yv-aAtck.mjs} +3 -3
  255. package/dist/client/{checkout-G4UWjOEw.mjs → checkout-B9xDkl30.mjs} +3 -3
  256. package/dist/client/client.mjs +37511 -33793
  257. package/dist/client/{context-GK4-AVAh.mjs → context-BOGe3rIA.mjs} +275 -10
  258. package/dist/client/{eventCalendar-Prw3Yo_L.mjs → eventCalendar-D6M0T9hs.mjs} +18 -17
  259. package/dist/client/{eventCombined-BUugi6__.mjs → eventCombined-NM4UFBq8.mjs} +4 -4
  260. package/dist/client/{eventDetails-CBHUda-R.mjs → eventDetails-f_FhV4O8.mjs} +3 -4
  261. package/dist/client/{eventListing-CQeG45Dg.mjs → eventListing-WLkfAROh.mjs} +4 -4
  262. package/dist/client/{eventRegistration-Dif3p3oJ.mjs → eventRegistration-D3ejfLvW.mjs} +2209 -2144
  263. package/dist/client/{form-Cf0WMtfG.mjs → form-D5Or2Jyu.mjs} +5 -6
  264. package/dist/client/{headerSection-Bqdr3JWp.mjs → headerSection-hWgnv27V.mjs} +9 -9
  265. package/dist/client/hooks.mjs +17278 -16106
  266. package/dist/client/{locationMap-BVvPou4O.mjs → locationMap-Di1K0O2S.mjs} +3 -4
  267. package/dist/client/{media-47fcnhzD.mjs → media-D29-F1u_.mjs} +183 -362
  268. package/dist/client/mediaContext-CwbbimYB.mjs +135 -0
  269. package/dist/client/{newsletterForm-Cx-1dnic.mjs → newsletterForm-CwxcwTNO.mjs} +5 -5
  270. package/dist/client/{productDetail-DaUMaJYk.mjs → productDetail-BLSnhPdA.mjs} +3 -3
  271. package/dist/client/{productList-BKQlWEKE.mjs → productList-oGytONOX.mjs} +3 -3
  272. package/dist/client/{propsDecoder-DO5iXphQ.mjs → propsDecoder-lf_dTeZS.mjs} +18659 -14473
  273. package/dist/client/renderableMediaAssetAdmission-CHkXYDC_.mjs +143 -0
  274. package/dist/client/rendering/client.mjs +58971 -55722
  275. package/dist/client/rendering/islands.mjs +20 -23
  276. package/dist/client/rendering.mjs +41610 -37917
  277. package/dist/client/{routeIslandApiConfig-BNm0Z3-g.mjs → routeIslandApiConfig-Bc_WVPS8.mjs} +1 -1
  278. package/dist/client/{shop-D7ytcFBC.mjs → shop-BQ3bR7fA.mjs} +16 -14
  279. package/dist/client/{shop-commerce-D68-hHy2.mjs → shop-commerce-DDPll6ZC.mjs} +9 -9
  280. package/dist/client/{shop-commerce-store-Cu7X2lyu.mjs → shop-commerce-store-DO-e1pTe.mjs} +1 -1
  281. package/dist/client/{shopShared-D12rxE69.mjs → shopShared-BylM9jh1.mjs} +5 -5
  282. package/dist/client/themePortalRoot-DS92SRVZ.mjs +47 -0
  283. package/dist/client/{TerminalPanel-DOTSUCO0.mjs → useDiscountCodeUi-B1Puub2v.mjs} +60 -77
  284. package/dist/client/{useEventPagination-jgKa67KV.mjs → useEventPagination-BBhkmQaT.mjs} +6 -8
  285. package/dist/preview-next/before-render.mjs +96 -4
  286. package/dist/preview-next/client/runtime.mjs +3 -3
  287. package/dist/preview-next/keepAlive.mjs +7 -0
  288. package/dist/preview-next/middleware.mjs +7 -0
  289. package/dist/{sdk-runtime-4cCWO_3i.mjs → sdk-runtime-DWCvhJGu.mjs} +1 -1
  290. package/dist/server/components.mjs +21820 -19902
  291. package/dist/server/config-validation.mjs +1786 -642
  292. package/dist/server/config.mjs +1786 -642
  293. package/dist/server/data.mjs +17220 -16076
  294. package/dist/server/index.mjs +234 -2
  295. package/dist/server/next.mjs +22505 -20528
  296. package/dist/server/page-converter.mjs +7587 -6572
  297. package/dist/server/prebuild.mjs +1 -1
  298. package/dist/server/rendering/server.mjs +20487 -19044
  299. package/dist/server/rendering.mjs +21844 -19901
  300. package/dist/server/routing.mjs +19186 -17961
  301. package/dist/server/server.mjs +1874 -695
  302. package/dist/server/theme-bridge.mjs +1371 -914
  303. package/dist/server/theme.mjs +154 -37
  304. package/dist/{spinner-tSdG9YvG.mjs → spinner-BYM-Y81w.mjs} +1 -1
  305. package/dist/styles/index.css +1088 -721
  306. package/package.json +1 -1
  307. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/OrderSummary.d.ts +0 -15
  308. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useWaitlistClaimRestoration.d.ts +0 -22
  309. package/dist/_dts/blocks/src/system/runtime/shared/components/SelectableOptionCard.d.ts +0 -30
  310. package/dist/client/BookingMailingListNote-DlDSs5Vm.mjs +0 -567
  311. package/dist/client/colorStyles-DTunOThj.mjs +0 -1316
  312. package/dist/client/pricing-CMD0kADd.mjs +0 -314
  313. package/dist/client/result-DRW7JWXz.mjs +0 -30
  314. package/dist/client/scrollToBlock-C86BYkyV.mjs +0 -17
  315. package/dist/client/themePortalRoot-AGs4cE7t.mjs +0 -30
  316. package/dist/spinner-BL3kW9Uh.mjs +0 -803
@@ -1731,12 +1731,14 @@ const unsafeAsSiteId = (id) => id;
1731
1731
  const unsafeAsApiKeyId = (id) => id;
1732
1732
  const unsafeAsEventOfferingId = (id) => id;
1733
1733
  const unsafeAsEventSeriesId = (id) => id;
1734
+ const unsafeAsMediaAssetId = (id) => id;
1734
1735
  const unsafeAsUserId = (id) => id;
1735
1736
  const unsafeAsContentVersionId = (id) => id;
1736
1737
  const parseSiteId = (value) => parseUuidBrand(value, unsafeAsSiteId);
1737
1738
  const parseApiKeyId = (value) => parseUuidBrand(value, unsafeAsApiKeyId);
1738
1739
  const parseEventOfferingId = (value) => parseUuidBrand(value, unsafeAsEventOfferingId);
1739
1740
  const parseUserId = (value) => parseUuidBrand(value, unsafeAsUserId);
1741
+ const parseMediaAssetId = (value) => parseUuidBrand(value, unsafeAsMediaAssetId);
1740
1742
  const parseContentVersionId = (value) => parseUuidBrand(value, unsafeAsContentVersionId);
1741
1743
  const parseEventSeriesId = (value) => parseUuidBrand(value, unsafeAsEventSeriesId);
1742
1744
  //#endregion
@@ -1941,6 +1943,29 @@ function canonicalEventPricingDisplay(pricing) {
1941
1943
  default: return assertNever$2(pricing);
1942
1944
  }
1943
1945
  }
1946
+ /**
1947
+ * The single renderer for a precomputed booking price `display` ADT. This is
1948
+ * the one place booking price variants map to user-facing strings — every call
1949
+ * site (booking entry, events calendar, event/course details, listings, portal)
1950
+ * should route through here so labels never drift. Adding a variant to the ADT
1951
+ * breaks compilation here (via `assertNever`) until it is handled.
1952
+ */
1953
+ function formatBookingPriceDisplay(display, options = {}) {
1954
+ switch (display.kind) {
1955
+ case "free": return "Free";
1956
+ case "exact": return formatCurrencyAmount(display.amount, display.currency, options);
1957
+ case "from": return `From ${formatCurrencyAmount(display.amount, display.currency, options)}`;
1958
+ case "range": return `${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)}`;
1959
+ case "mixed": return display.paidToAmount > display.paidFromAmount ? `Free / ${formatCurrencyAmount(display.paidFromAmount, display.currency, options)} - ${formatCurrencyAmount(display.paidToAmount, display.currency, options)}` : `Free / ${formatCurrencyAmount(display.paidFromAmount, display.currency, options)}`;
1960
+ case "unavailable": return "Pricing unavailable";
1961
+ case "external": return display.label;
1962
+ default: return assertNever$2(display);
1963
+ }
1964
+ }
1965
+ /** Render an event's price directly from its pricing value. */
1966
+ function formatEventPricing(pricing, options = {}) {
1967
+ return formatBookingPriceDisplay(publicEventPricingDisplay(pricing), options);
1968
+ }
1944
1969
  //#endregion
1945
1970
  //#region ../core/src/event-page-surface-subject.ts
1946
1971
  function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
@@ -2084,6 +2109,7 @@ function parseBookingTimeZone(timeZone) {
2084
2109
  }
2085
2110
  //#endregion
2086
2111
  //#region ../core/src/booking-date-time.ts
2112
+ const DEFAULT_LOCALE = "en-US";
2087
2113
  const FALLBACK_TIMEZONE = "UTC";
2088
2114
  const START_OF_DAY = {
2089
2115
  hour: 0,
@@ -2206,6 +2232,34 @@ function toScheduledBookingDateTime(input) {
2206
2232
  if (result.ok) return result.value;
2207
2233
  throw new Error(formatParseError(result));
2208
2234
  }
2235
+ function formatBookingInstant(isoString, options) {
2236
+ const date = new Date(isoString);
2237
+ if (Number.isNaN(date.getTime())) return isoString;
2238
+ return getCachedDateTimeFormatter(options.locale ?? DEFAULT_LOCALE, getSafeTimeZone(options.timeZone), options.format).format(date);
2239
+ }
2240
+ function formatBookingDate(input, format = {
2241
+ weekday: "long",
2242
+ year: "numeric",
2243
+ month: "long",
2244
+ day: "numeric"
2245
+ }) {
2246
+ return formatBookingInstant(input.startsAt, {
2247
+ locale: input.locale,
2248
+ timeZone: input.timeZone,
2249
+ format
2250
+ });
2251
+ }
2252
+ function formatBookingTime(input, format = {
2253
+ hour: "numeric",
2254
+ minute: "2-digit",
2255
+ hour12: true
2256
+ }) {
2257
+ return formatBookingInstant(input.startsAt, {
2258
+ locale: input.locale,
2259
+ timeZone: input.timeZone,
2260
+ format
2261
+ });
2262
+ }
2209
2263
  function getBookingLocalDateKey(input) {
2210
2264
  return formatDateKey$1(getLocalScheduleDate(new Date(input.startsAt), input.timeZone));
2211
2265
  }
@@ -2335,7 +2389,7 @@ function parsePositiveInteger(value) {
2335
2389
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
2336
2390
  }
2337
2391
  function parseNewsletterListId(value) {
2338
- return parseTrimmedMatchingString(value, UUID_PATTERN);
2392
+ return parseTrimmedMatchingString(typeof value === "string" ? value.toLowerCase() : value, UUID_PATTERN);
2339
2393
  }
2340
2394
  function parseMailchimpAccountId(value) {
2341
2395
  return parseTrimmedMatchingString(value, OPAQUE_ID_PATTERN);
@@ -2377,8 +2431,15 @@ function parseMailingListProviderScope(value) {
2377
2431
  }
2378
2432
  return err("invalid_mailing_list_provider_scope");
2379
2433
  }
2380
- //#endregion
2381
- //#region ../core/src/mailing-list-setup-parser.ts
2434
+ z.unknown().transform((value, ctx) => {
2435
+ const parsed = parseNewsletterAudience(value);
2436
+ if (parsed.ok) return parsed.value;
2437
+ ctx.addIssue({
2438
+ code: "custom",
2439
+ message: "Choose a valid newsletter audience."
2440
+ });
2441
+ return z.NEVER;
2442
+ });
2382
2443
  function parseMailingListGlobalForm(value) {
2383
2444
  if (!isRecord$9(value)) return formError("shape");
2384
2445
  const fields = parseGlobalFields(value.fields);
@@ -2398,6 +2459,16 @@ function parseMailingListGlobalForm(value) {
2398
2459
  privacyNote
2399
2460
  });
2400
2461
  }
2462
+ function parseNewsletterAudience(value) {
2463
+ if (!isRecord$9(value)) return err("invalid_newsletter_audience");
2464
+ if (value.kind === "general" && Object.keys(value).length === 1) return ok({ kind: "general" });
2465
+ if (value.kind !== "topic" || Object.keys(value).some((key) => key !== "kind" && key !== "topicId")) return err("invalid_newsletter_audience");
2466
+ const topicId = parseNewsletterListId(value.topicId);
2467
+ return topicId === null ? err("invalid_newsletter_audience") : ok({
2468
+ kind: "topic",
2469
+ topicId
2470
+ });
2471
+ }
2401
2472
  function parseGlobalFields(value) {
2402
2473
  if (!isRecord$9(value) || typeof value.preset !== "string") return null;
2403
2474
  switch (value.preset) {
@@ -2447,44 +2518,144 @@ function formError(field) {
2447
2518
  }
2448
2519
  //#endregion
2449
2520
  //#region ../core/src/mailing-list-targeting.ts
2521
+ const DEFAULT_MAILING_LIST_TARGETING = { mode: "default" };
2522
+ function mailingListTargetingChoiceIds(targeting) {
2523
+ switch (targeting.provider) {
2524
+ case "native": return targeting.selection.tag === "general" ? [] : targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
2525
+ case "brevo": return targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
2526
+ case "mailchimp": return targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
2527
+ }
2528
+ }
2450
2529
  function planMailingListChoicePresentation(targeting) {
2451
- if (targeting.mode === "default" || targeting.selection.tag === "fixed") return { tag: "hidden" };
2530
+ if (targeting.mode === "default" || targeting.selection.tag === "fixed" || targeting.selection.tag === "general") return { tag: "hidden" };
2452
2531
  switch (targeting.provider) {
2453
- case "native": return targeting.selection.offeredTopicIds.length === 1 ? {
2454
- tag: "automatic",
2455
- selection: {
2456
- provider: "native",
2457
- topicIds: targeting.selection.offeredTopicIds
2458
- }
2459
- } : {
2460
- tag: "visitor_choice",
2532
+ case "native": return presentVisitorSelection({
2533
+ provider: "native",
2534
+ topicIds: targeting.selection.offeredTopicIds
2535
+ }, targeting.selection.offeredTopicIds.length);
2536
+ case "brevo": return presentVisitorSelection({
2537
+ provider: "brevo",
2538
+ listIds: targeting.selection.offeredListIds
2539
+ }, targeting.selection.offeredListIds.length);
2540
+ case "mailchimp": return presentVisitorSelection({
2541
+ provider: "mailchimp",
2542
+ interestIds: targeting.selection.offeredInterestIds
2543
+ }, targeting.selection.offeredInterestIds.length);
2544
+ }
2545
+ }
2546
+ function presentVisitorSelection(selection, optionCount) {
2547
+ return optionCount === 1 ? {
2548
+ tag: "automatic",
2549
+ selection
2550
+ } : {
2551
+ tag: "visitor_choice",
2552
+ provider: selection.provider,
2553
+ optionCount
2554
+ };
2555
+ }
2556
+ //#endregion
2557
+ //#region ../core/src/mailing-list-targeting-availability.ts
2558
+ function resolveMailingListDefaultDestination(provider) {
2559
+ switch (provider.provider) {
2560
+ case "native": {
2561
+ const audience = provider.defaultAudience;
2562
+ return audience.kind === "general" ? {
2563
+ id: "general",
2564
+ name: "General mailing list"
2565
+ } : provider.topics.find((item) => item.id === audience.topicId) ?? null;
2566
+ }
2567
+ case "brevo": return provider.lists.find((item) => item.id === provider.defaultListId) ?? null;
2568
+ case "mailchimp": return provider.audiences.find((item) => item.id === provider.defaultAudienceId) ?? null;
2569
+ }
2570
+ }
2571
+ function assessMailingListTargeting(context, targeting) {
2572
+ if (context?.tag !== "configured") return {
2573
+ tag: "unavailable",
2574
+ reason: "setup_missing",
2575
+ targeting
2576
+ };
2577
+ const catalog = context.provider;
2578
+ if (targeting.mode === "custom") return assessCustomTargeting(catalog, targeting);
2579
+ const destination = resolveMailingListDefaultDestination(catalog);
2580
+ return destination === null ? {
2581
+ tag: "unavailable",
2582
+ reason: "default_missing",
2583
+ targeting
2584
+ } : {
2585
+ tag: "available",
2586
+ value: {
2587
+ mode: "default",
2588
+ targeting,
2589
+ provider: catalog.provider,
2590
+ destination
2591
+ }
2592
+ };
2593
+ }
2594
+ function assessCustomTargeting(catalog, targeting) {
2595
+ switch (targeting.provider) {
2596
+ case "native": return catalog.provider === "native" ? assessMatchedChoices({
2461
2597
  provider: "native",
2462
- optionCount: targeting.selection.offeredTopicIds.length
2598
+ catalog,
2599
+ targeting
2600
+ }, catalog.topics) : {
2601
+ tag: "unavailable",
2602
+ reason: "provider_mismatch",
2603
+ targeting
2463
2604
  };
2464
- case "brevo": return targeting.selection.offeredListIds.length === 1 ? {
2465
- tag: "automatic",
2466
- selection: {
2467
- provider: "brevo",
2468
- listIds: targeting.selection.offeredListIds
2469
- }
2470
- } : {
2471
- tag: "visitor_choice",
2605
+ case "brevo": return catalog.provider === "brevo" ? assessMatchedChoices({
2472
2606
  provider: "brevo",
2473
- optionCount: targeting.selection.offeredListIds.length
2607
+ catalog,
2608
+ targeting
2609
+ }, catalog.lists) : {
2610
+ tag: "unavailable",
2611
+ reason: "provider_mismatch",
2612
+ targeting
2474
2613
  };
2475
- case "mailchimp": return targeting.selection.offeredInterestIds.length === 1 ? {
2476
- tag: "automatic",
2477
- selection: {
2614
+ case "mailchimp": {
2615
+ if (catalog.provider !== "mailchimp") return {
2616
+ tag: "unavailable",
2617
+ reason: "provider_mismatch",
2618
+ targeting
2619
+ };
2620
+ const audience = catalog.audiences.find((item) => item.id === targeting.audienceId);
2621
+ return audience === void 0 ? {
2622
+ tag: "unavailable",
2623
+ reason: "audience_missing",
2624
+ value: {
2625
+ mode: "custom",
2626
+ provider: "mailchimp",
2627
+ catalog,
2628
+ targeting
2629
+ }
2630
+ } : assessMatchedChoices({
2478
2631
  provider: "mailchimp",
2479
- interestIds: targeting.selection.offeredInterestIds
2480
- }
2481
- } : {
2482
- tag: "visitor_choice",
2483
- provider: "mailchimp",
2484
- optionCount: targeting.selection.offeredInterestIds.length
2485
- };
2632
+ catalog,
2633
+ targeting,
2634
+ audience
2635
+ }, audience.interests);
2636
+ }
2486
2637
  }
2487
2638
  }
2639
+ function assessMatchedChoices(matched, choices) {
2640
+ const selected = new Set(mailingListTargetingChoiceIds(matched.targeting));
2641
+ const available = new Set(choices.map((item) => item.id));
2642
+ const missingIds = [...selected].filter((id) => !available.has(id));
2643
+ const value = {
2644
+ mode: "custom",
2645
+ ...matched,
2646
+ choices,
2647
+ selectedChoices: choices.filter((item) => selected.has(item.id))
2648
+ };
2649
+ return missingIds.length > 0 ? {
2650
+ tag: "unavailable",
2651
+ reason: "choices_missing",
2652
+ missingIds,
2653
+ value
2654
+ } : {
2655
+ tag: "available",
2656
+ value
2657
+ };
2658
+ }
2488
2659
  //#endregion
2489
2660
  //#region ../core/src/mailing-list-runtime.ts
2490
2661
  function parseMailingListRuntimeContext(value) {
@@ -2530,11 +2701,12 @@ function parseRuntimeProvider(value) {
2530
2701
  }
2531
2702
  }
2532
2703
  function parseNativeRuntimeProvider(value) {
2533
- const defaultTopicId = parseNewsletterListId(value.defaultTopicId);
2704
+ if ("defaultTopicId" in value) return null;
2705
+ const defaultAudience = parseNewsletterAudience(value.defaultAudience);
2534
2706
  const topics = parseNamedItems(value.topics, parseNewsletterListId);
2535
- return defaultTopicId !== null && topics !== null ? {
2707
+ return defaultAudience.ok && topics !== null ? {
2536
2708
  provider: "native",
2537
- defaultTopicId,
2709
+ defaultAudience: defaultAudience.value,
2538
2710
  topics
2539
2711
  } : null;
2540
2712
  }
@@ -2550,57 +2722,47 @@ function parseBrevoRuntimeProvider(value) {
2550
2722
  function parseMailchimpRuntimeProvider(value) {
2551
2723
  if (!Array.isArray(value.audiences)) return null;
2552
2724
  const defaultAudienceId = parseMailchimpAudienceId(value.defaultAudienceId);
2553
- if (defaultAudienceId === null) return null;
2554
- const audiences = [];
2555
- for (const audience of value.audiences) {
2556
- if (!isRecord$8(audience)) return null;
2557
- const id = parseMailchimpAudienceId(audience.id);
2558
- const name = parseName(audience.name);
2559
- if (id === null || name === null || !Array.isArray(audience.interests)) return null;
2560
- const interests = parseMailchimpInterests(audience.interests);
2561
- if (interests === null) return null;
2562
- audiences.push({
2563
- id,
2564
- name,
2565
- interests
2566
- });
2567
- }
2568
- return {
2725
+ const audiences = Array.from(value.audiences, parseMailchimpAudience);
2726
+ return defaultAudienceId !== null && audiences.every((audience) => audience !== null) ? {
2569
2727
  provider: "mailchimp",
2570
2728
  defaultAudienceId,
2571
2729
  audiences
2572
- };
2730
+ } : null;
2573
2731
  }
2574
- function parseMailchimpInterests(value) {
2575
- const interests = [];
2576
- for (const interest of value) {
2577
- if (!isRecord$8(interest)) return null;
2578
- const id = parseMailchimpInterestId(interest.id);
2579
- const name = parseName(interest.name);
2580
- const categoryName = parseName(interest.categoryName);
2581
- if (id === null || name === null || categoryName === null) return null;
2582
- interests.push({
2583
- id,
2584
- name,
2585
- categoryName
2586
- });
2587
- }
2588
- return interests;
2732
+ function parseMailchimpAudience(value) {
2733
+ if (!isRecord$8(value) || !Array.isArray(value.interests)) return null;
2734
+ const id = parseMailchimpAudienceId(value.id);
2735
+ const name = parseName(value.name);
2736
+ const interests = Array.from(value.interests, parseMailchimpInterest);
2737
+ return id !== null && name !== null && interests.every((interest) => interest !== null) ? {
2738
+ id,
2739
+ name,
2740
+ interests
2741
+ } : null;
2742
+ }
2743
+ function parseMailchimpInterest(value) {
2744
+ if (!isRecord$8(value)) return null;
2745
+ const id = parseMailchimpInterestId(value.id);
2746
+ const name = parseName(value.name);
2747
+ const categoryName = parseName(value.categoryName);
2748
+ return id !== null && name !== null && categoryName !== null ? {
2749
+ id,
2750
+ name,
2751
+ categoryName
2752
+ } : null;
2589
2753
  }
2590
2754
  function parseNamedItems(value, parseId) {
2591
2755
  if (!Array.isArray(value)) return null;
2592
- const items = [];
2593
- for (const item of value) {
2756
+ const items = Array.from(value, (item) => {
2594
2757
  if (!isRecord$8(item)) return null;
2595
2758
  const id = parseId(item.id);
2596
2759
  const name = parseName(item.name);
2597
- if (id === null || name === null) return null;
2598
- items.push({
2760
+ return id !== null && name !== null ? {
2599
2761
  id,
2600
2762
  name
2601
- });
2602
- }
2603
- return items;
2763
+ } : null;
2764
+ });
2765
+ return items.every((item) => item !== null) ? items : null;
2604
2766
  }
2605
2767
  function parseName(value) {
2606
2768
  if (typeof value !== "string") return null;
@@ -2616,6 +2778,7 @@ function invalidRuntimeContext() {
2616
2778
  //#endregion
2617
2779
  //#region ../core/src/mailing-list-targeting-parser.ts
2618
2780
  function parseMailingListPlacementTargeting(value) {
2781
+ if (value == null) return ok(DEFAULT_MAILING_LIST_TARGETING);
2619
2782
  if (!isRecord$7(value)) return targetingError("shape");
2620
2783
  if (value.mode === "default") return ok({ mode: "default" });
2621
2784
  if (value.mode !== "custom") return targetingError("mode");
@@ -2628,6 +2791,11 @@ function parseMailingListPlacementTargeting(value) {
2628
2791
  }
2629
2792
  }
2630
2793
  function parseNativeTargeting(selection) {
2794
+ if (selection.tag === "general") return Object.keys(selection).length === 1 ? ok({
2795
+ mode: "custom",
2796
+ provider: "native",
2797
+ selection: { tag: "general" }
2798
+ }) : targetingError("selection");
2631
2799
  if (selection.tag === "fixed") {
2632
2800
  const topicIds = parseNonEmptyUniqueIds(selection.topicIds, parseNewsletterListId);
2633
2801
  return topicIds.ok ? ok({
@@ -2714,14 +2882,11 @@ function parseNonEmptyUniqueIds(value, parseId) {
2714
2882
  }
2715
2883
  function parseUniqueIds(value, parseId) {
2716
2884
  if (!Array.isArray(value)) return targetingError("targets");
2717
- const parsed = [];
2718
- for (const item of value) {
2885
+ return Array.from(value).reduce((parsed, item) => {
2886
+ if (!parsed.ok) return parsed;
2719
2887
  const id = parseId(item);
2720
- if (id === null) return targetingError("targets");
2721
- if (parsed.includes(id)) return targetingError("duplicate_target");
2722
- parsed.push(id);
2723
- }
2724
- return ok(parsed);
2888
+ return id === null ? targetingError("targets") : parsed.value.includes(id) ? targetingError("duplicate_target") : ok([...parsed.value, id]);
2889
+ }, ok([]));
2725
2890
  }
2726
2891
  function isRecord$7(value) {
2727
2892
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3150,6 +3315,45 @@ function resolveSiteVerticalBookingConfig(rawConfig) {
3150
3315
  };
3151
3316
  }
3152
3317
  //#endregion
3318
+ //#region ../core/src/event-preset-vocabulary.ts
3319
+ const EVENT_PRESET_VOCABULARY = {
3320
+ event: {
3321
+ label: "Event",
3322
+ pluralLabel: "Events",
3323
+ description: "Any event, repeating or manually dated, that does not fit the other categories."
3324
+ },
3325
+ class: {
3326
+ label: "Class",
3327
+ pluralLabel: "Classes",
3328
+ description: "Regular scheduled classes and group sessions."
3329
+ },
3330
+ course: {
3331
+ label: "Course",
3332
+ pluralLabel: "Courses",
3333
+ description: "A fixed set of dates that people book together."
3334
+ },
3335
+ term: {
3336
+ label: "Term",
3337
+ pluralLabel: "Terms",
3338
+ description: "A fixed term of sessions that people book together."
3339
+ },
3340
+ workshop: {
3341
+ label: "Workshop",
3342
+ pluralLabel: "Workshops",
3343
+ description: "One-off workshops, day sessions, or special events."
3344
+ },
3345
+ retreat: {
3346
+ label: "Retreat",
3347
+ pluralLabel: "Retreats",
3348
+ description: "Multi-day or residential immersive experiences."
3349
+ },
3350
+ performance: {
3351
+ label: "Performance",
3352
+ pluralLabel: "Performances",
3353
+ description: "Audience-facing shows, concerts, recitals, or showcases."
3354
+ }
3355
+ };
3356
+ //#endregion
3153
3357
  //#region ../api/src/publicEventEndpointDefinitions.ts
3154
3358
  const PUBLIC_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3155
3359
  checkPublicEventRegistrationNextStep: {
@@ -7676,7 +7880,96 @@ const themeLookMetadataSchema = z.object({
7676
7880
  skinMarks: skinMarksSchema.optional()
7677
7881
  });
7678
7882
  //#endregion
7679
- //#region ../theme-core/src/lib/media.ts
7883
+ //#region ../media-core/src/identifiers.ts
7884
+ function slugifyMediaIdentifier(value) {
7885
+ return value.toLowerCase().trim().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-");
7886
+ }
7887
+ function parseMediaIdentifier(value) {
7888
+ if (typeof value !== "string") return err({ kind: "not_string" });
7889
+ const trimmed = value.trim();
7890
+ if (trimmed.length === 0) return err({ kind: "empty_identifier" });
7891
+ const normalized = slugifyMediaIdentifier(trimmed);
7892
+ if (normalized.length === 0) return err({
7893
+ kind: "empty_after_normalization",
7894
+ value
7895
+ });
7896
+ return ok(normalized);
7897
+ }
7898
+ /**
7899
+ * Admits identifiers read from trusted persistence without repairing dirty data in memory.
7900
+ * User input should use parseMediaIdentifier, which intentionally canonicalizes it.
7901
+ */
7902
+ function parseCanonicalMediaIdentifier(value) {
7903
+ if (typeof value !== "string") return err({ kind: "not_string" });
7904
+ const parsed = parseMediaIdentifier(value);
7905
+ if (!parsed.ok) return parsed;
7906
+ if (value !== parsed.value) return err({
7907
+ kind: "non_canonical_identifier",
7908
+ value,
7909
+ canonical: parsed.value
7910
+ });
7911
+ return parsed;
7912
+ }
7913
+ //#endregion
7914
+ //#region ../media-core/src/mediaIdentifierSchema.ts
7915
+ /** Strict stored-reference admission delegates the canonical identifier decision. */
7916
+ const mediaIdentifierSchema = z.string().transform((value, context) => {
7917
+ const parsed = parseCanonicalMediaIdentifier(value);
7918
+ if (parsed.ok) return parsed.value;
7919
+ context.addIssue({
7920
+ code: "custom",
7921
+ message: "Expected a canonical media identifier"
7922
+ });
7923
+ return z.NEVER;
7924
+ });
7925
+ //#endregion
7926
+ //#region ../media-core/src/mediaKinds.ts
7927
+ const mediaTypeDiscriminants = [
7928
+ "image",
7929
+ "video",
7930
+ "audio",
7931
+ "document",
7932
+ "spreadsheet",
7933
+ "archive"
7934
+ ];
7935
+ //#endregion
7936
+ //#region ../media-core/src/renderableMediaAsset.ts
7937
+ /** A portable reference never uses an incidental UUID, URL or storage snapshot for identity. */
7938
+ function resolveManagedMediaReference(reference, index) {
7939
+ const asset = index?.byIdentifier.get(reference.identifier);
7940
+ return asset ? {
7941
+ kind: "resolved",
7942
+ reference,
7943
+ asset
7944
+ } : {
7945
+ kind: "missing",
7946
+ reference
7947
+ };
7948
+ }
7949
+ //#endregion
7950
+ //#region ../media-core/src/renderableMediaAssetAdmission.ts
7951
+ const mediaAssetIdSchema = z.string().transform((value, context) => {
7952
+ const parsed = parseMediaAssetId(value);
7953
+ if (parsed.ok) return parsed.value;
7954
+ context.addIssue({
7955
+ code: "custom",
7956
+ message: "Expected a media asset ID"
7957
+ });
7958
+ return z.NEVER;
7959
+ });
7960
+ const nonemptyStorageValue = z.string().refine((value) => value.trim().length > 0);
7961
+ const renderableMediaAssetSchema = z.object({
7962
+ id: mediaAssetIdSchema,
7963
+ identifier: mediaIdentifierSchema,
7964
+ storageBucket: nonemptyStorageValue,
7965
+ storagePath: nonemptyStorageValue,
7966
+ mimeType: nonemptyStorageValue,
7967
+ filename: z.string().optional(),
7968
+ width: z.number().positive().nullable(),
7969
+ height: z.number().positive().nullable()
7970
+ });
7971
+ //#endregion
7972
+ //#region ../media-core/src/media.ts
7680
7973
  const hotspotSchema = z.object({
7681
7974
  x: z.number().min(0).max(1),
7682
7975
  y: z.number().min(0).max(1),
@@ -7710,56 +8003,149 @@ const transformSchema = z.object({
7710
8003
  rotation: rotationSchema.optional()
7711
8004
  });
7712
8005
  const mediaPosterSchema = z.discriminatedUnion("tag", [z.object({
7713
- tag: z.literal("storage"),
7714
- assetId: z.string().optional(),
7715
- storageBucket: z.string().min(1),
7716
- storagePath: z.string().min(1)
7717
- }), z.object({
8006
+ tag: z.literal("media"),
8007
+ identifier: mediaIdentifierSchema
8008
+ }).strict(), z.object({
7718
8009
  tag: z.literal("external-src"),
7719
- assetId: z.string().optional(),
7720
8010
  src: z.string().url()
7721
- })]);
7722
- const mediaBaseSchema = z.object({
8011
+ }).strict()]);
8012
+ const mediaUsageShape = {
7723
8013
  purpose: z.string().optional(),
7724
8014
  placeholder: z.boolean().default(true).optional(),
7725
- assetId: z.string().optional(),
7726
- identifier: z.string().optional(),
7727
- src: z.string().optional(),
7728
8015
  alt: z.string().optional(),
7729
8016
  filename: z.string().optional(),
7730
- mimeType: z.string().optional(),
7731
8017
  width: z.number().optional(),
7732
8018
  height: z.number().optional(),
7733
- storageBucket: z.string().optional(),
7734
- storagePath: z.string().optional(),
7735
8019
  poster: mediaPosterSchema.optional(),
7736
8020
  transform: transformSchema.optional()
7737
- });
7738
- const mediaIdentifierSchema = z.string().trim().min(1).brand();
8021
+ };
8022
+ const environmentExclusions = {
8023
+ assetId: z.never().optional(),
8024
+ id: z.never().optional(),
8025
+ src: z.never().optional(),
8026
+ storageBucket: z.never().optional(),
8027
+ storagePath: z.never().optional(),
8028
+ mimeType: z.never().optional()
8029
+ };
8030
+ const mediaBaseSchema = z.object({
8031
+ ...mediaUsageShape,
8032
+ ...environmentExclusions,
8033
+ identifier: mediaIdentifierSchema
8034
+ }).strict();
7739
8035
  const imageMediaSchema = mediaBaseSchema.extend({ type: z.literal("image") });
7740
8036
  const videoMediaSchema = mediaBaseSchema.extend({ type: z.literal("video") });
7741
- const audioMediaSchema = mediaBaseSchema.extend({ type: z.literal("audio") });
7742
- const documentMediaSchema = mediaBaseSchema.extend({ type: z.literal("document") });
7743
- const spreadsheetMediaSchema = mediaBaseSchema.extend({ type: z.literal("spreadsheet") });
7744
- const archiveMediaSchema = mediaBaseSchema.extend({ type: z.literal("archive") });
7745
- const mediaSchema = z.discriminatedUnion("type", [
7746
- imageMediaSchema,
7747
- videoMediaSchema,
7748
- audioMediaSchema,
7749
- documentMediaSchema,
7750
- spreadsheetMediaSchema,
7751
- archiveMediaSchema
7752
- ]);
7753
- const persistableMediaBaseSchema = mediaBaseSchema.extend({ identifier: mediaIdentifierSchema });
7754
- z.discriminatedUnion("type", [
7755
- persistableMediaBaseSchema.extend({ type: z.literal("image") }),
7756
- persistableMediaBaseSchema.extend({ type: z.literal("video") }),
7757
- persistableMediaBaseSchema.extend({ type: z.literal("audio") }),
7758
- persistableMediaBaseSchema.extend({ type: z.literal("document") }),
7759
- persistableMediaBaseSchema.extend({ type: z.literal("spreadsheet") }),
7760
- persistableMediaBaseSchema.extend({ type: z.literal("archive") })
8037
+ const mediaSchema = mediaBaseSchema.extend({ type: z.enum(mediaTypeDiscriminants) });
8038
+ const externalMediaSchema = z.object({
8039
+ ...mediaUsageShape,
8040
+ ...environmentExclusions,
8041
+ type: z.enum(mediaTypeDiscriminants),
8042
+ src: z.string().min(1),
8043
+ identifier: z.never().optional()
8044
+ }).strict();
8045
+ const assetMediaSchema = z.object({
8046
+ ...mediaUsageShape,
8047
+ ...environmentExclusions,
8048
+ type: z.enum(mediaTypeDiscriminants),
8049
+ asset: renderableMediaAssetSchema,
8050
+ identifier: z.never().optional()
8051
+ }).strict();
8052
+ const runtimeMediaSchema = z.union([
8053
+ mediaSchema,
8054
+ externalMediaSchema,
8055
+ assetMediaSchema
7761
8056
  ]);
7762
8057
  //#endregion
8058
+ //#region ../media-core/src/transformRect.ts
8059
+ function isValidTransformRectStructure(rect) {
8060
+ if (!Array.isArray(rect) || rect.length !== 4) return false;
8061
+ const [left, top, width, height] = rect;
8062
+ return Number.isFinite(left) && Number.isFinite(top) && Number.isFinite(width) && Number.isFinite(height) && left >= 0 && top >= 0 && width > 0 && height > 0;
8063
+ }
8064
+ //#endregion
8065
+ //#region ../media-core/src/aspectTransforms.ts
8066
+ const DEFAULT_RECT_EPSILON = .5;
8067
+ function parseAspectRatioValue(value) {
8068
+ if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : void 0;
8069
+ if (typeof value !== "string") return void 0;
8070
+ const normalized = value.trim();
8071
+ if (!normalized) return void 0;
8072
+ if (normalized === "auto") return void 0;
8073
+ if (normalized.includes(":") || normalized.includes("/")) {
8074
+ const delimiter = normalized.includes(":") ? ":" : "/";
8075
+ const [wRaw, hRaw] = normalized.split(delimiter);
8076
+ const width = Number(wRaw?.trim());
8077
+ const height = Number(hRaw?.trim());
8078
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
8079
+ return width / height;
8080
+ }
8081
+ const numeric = Number(normalized);
8082
+ if (!Number.isFinite(numeric) || numeric <= 0) return void 0;
8083
+ return numeric;
8084
+ }
8085
+ const MAX_ASPECT_DISTANCE = .25;
8086
+ function resolveMediaTransformForAspect(transform, targetAspect) {
8087
+ if (!transform) return {};
8088
+ const base = {
8089
+ rect: transform.rect,
8090
+ hotspot: transform.hotspot,
8091
+ rotation: transform.rotation
8092
+ };
8093
+ const target = parseAspectRatioValue(targetAspect);
8094
+ const crops = transform.aspectCrops;
8095
+ if (!target || !crops || Object.keys(crops).length === 0) return base;
8096
+ let best = null;
8097
+ let bestDistance = Number.POSITIVE_INFINITY;
8098
+ for (const [key, candidate] of Object.entries(crops)) {
8099
+ const candidateAspect = parseAspectRatioValue(candidate.aspect ?? key);
8100
+ if (!candidateAspect) continue;
8101
+ const distance = Math.abs(Math.log(candidateAspect / target));
8102
+ if (distance < bestDistance) {
8103
+ best = candidate;
8104
+ bestDistance = distance;
8105
+ }
8106
+ }
8107
+ if (!best) return base;
8108
+ if (bestDistance > MAX_ASPECT_DISTANCE) return base;
8109
+ return {
8110
+ rect: best.rect ?? base.rect,
8111
+ hotspot: best.hotspot ?? base.hotspot,
8112
+ rotation: best.rotation ?? base.rotation
8113
+ };
8114
+ }
8115
+ function isValidTransformRect(rect) {
8116
+ return isValidTransformRectStructure(rect);
8117
+ }
8118
+ function isExplicitRectCrop(rect, sourceWidth, sourceHeight, epsilon = DEFAULT_RECT_EPSILON) {
8119
+ if (!isValidTransformRect(rect)) return false;
8120
+ if (!(typeof sourceWidth === "number" && Number.isFinite(sourceWidth) && sourceWidth > 0 && typeof sourceHeight === "number" && Number.isFinite(sourceHeight) && sourceHeight > 0)) return true;
8121
+ const [x, y, width, height] = rect;
8122
+ return !(Math.abs(x) <= epsilon && Math.abs(y) <= epsilon && Math.abs(width - sourceWidth) <= epsilon && Math.abs(height - sourceHeight) <= epsilon);
8123
+ }
8124
+ //#endregion
8125
+ //#region ../media-core/src/rotation.ts
8126
+ function rotateNormalizedPoint(point, rotation) {
8127
+ const x = point.x;
8128
+ const y = point.y;
8129
+ switch (rotation) {
8130
+ case 0: return {
8131
+ x,
8132
+ y
8133
+ };
8134
+ case 90: return {
8135
+ x: 1 - y,
8136
+ y: x
8137
+ };
8138
+ case 180: return {
8139
+ x: 1 - x,
8140
+ y: 1 - y
8141
+ };
8142
+ case 270: return {
8143
+ x: y,
8144
+ y: 1 - x
8145
+ };
8146
+ }
8147
+ }
8148
+ //#endregion
7763
8149
  //#region ../theme-core/src/palette/schema.ts
7764
8150
  const colourHexString = z.string().regex(/^#([0-9A-Fa-f]{3}){1,2}$/);
7765
8151
  const paletteColorSchema = z.object({
@@ -16308,6 +16694,21 @@ const boxBackgroundSchema = z.discriminatedUnion("type", [
16308
16694
  showVideoOnMobile: z.boolean().nullable().optional()
16309
16695
  })
16310
16696
  ]);
16697
+ /** Application rendering sources are admitted separately from saved references. */
16698
+ const runtimeBoxBackgroundSchema = z.discriminatedUnion("type", [
16699
+ boxBackgroundSchema.options[0],
16700
+ boxBackgroundSchema.options[1],
16701
+ boxBackgroundSchema.options[2].extend({ image: z.union([
16702
+ imageMediaSchema,
16703
+ externalMediaSchema.extend({ type: z.literal("image") }),
16704
+ assetMediaSchema.extend({ type: z.literal("image") })
16705
+ ]).nullable().optional() }),
16706
+ boxBackgroundSchema.options[3].extend({ image: z.union([
16707
+ videoMediaSchema,
16708
+ externalMediaSchema.extend({ type: z.literal("video") }),
16709
+ assetMediaSchema.extend({ type: z.literal("video") })
16710
+ ]).nullable().optional() })
16711
+ ]);
16311
16712
  function normalizeAuthoringOpacity(value) {
16312
16713
  if (!isRecord$6(value)) return value;
16313
16714
  return {
@@ -23157,97 +23558,6 @@ function registerManifest(manifest) {
23157
23558
  return manifest;
23158
23559
  }
23159
23560
  //#endregion
23160
- //#region ../media-core/src/transformRect.ts
23161
- function isValidTransformRectStructure(rect) {
23162
- if (!Array.isArray(rect) || rect.length !== 4) return false;
23163
- const [left, top, width, height] = rect;
23164
- return Number.isFinite(left) && Number.isFinite(top) && Number.isFinite(width) && Number.isFinite(height) && left >= 0 && top >= 0 && width > 0 && height > 0;
23165
- }
23166
- //#endregion
23167
- //#region ../media-core/src/aspectTransforms.ts
23168
- const DEFAULT_RECT_EPSILON = .5;
23169
- function parseAspectRatioValue(value) {
23170
- if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : void 0;
23171
- if (typeof value !== "string") return void 0;
23172
- const normalized = value.trim();
23173
- if (!normalized) return void 0;
23174
- if (normalized === "auto") return void 0;
23175
- if (normalized.includes(":") || normalized.includes("/")) {
23176
- const delimiter = normalized.includes(":") ? ":" : "/";
23177
- const [wRaw, hRaw] = normalized.split(delimiter);
23178
- const width = Number(wRaw?.trim());
23179
- const height = Number(hRaw?.trim());
23180
- if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
23181
- return width / height;
23182
- }
23183
- const numeric = Number(normalized);
23184
- if (!Number.isFinite(numeric) || numeric <= 0) return void 0;
23185
- return numeric;
23186
- }
23187
- const MAX_ASPECT_DISTANCE = .25;
23188
- function resolveMediaTransformForAspect(transform, targetAspect) {
23189
- if (!transform) return {};
23190
- const base = {
23191
- rect: transform.rect,
23192
- hotspot: transform.hotspot,
23193
- rotation: transform.rotation
23194
- };
23195
- const target = parseAspectRatioValue(targetAspect);
23196
- const crops = transform.aspectCrops;
23197
- if (!target || !crops || Object.keys(crops).length === 0) return base;
23198
- let best = null;
23199
- let bestDistance = Number.POSITIVE_INFINITY;
23200
- for (const [key, candidate] of Object.entries(crops)) {
23201
- const candidateAspect = parseAspectRatioValue(candidate.aspect ?? key);
23202
- if (!candidateAspect) continue;
23203
- const distance = Math.abs(Math.log(candidateAspect / target));
23204
- if (distance < bestDistance) {
23205
- best = candidate;
23206
- bestDistance = distance;
23207
- }
23208
- }
23209
- if (!best) return base;
23210
- if (bestDistance > MAX_ASPECT_DISTANCE) return base;
23211
- return {
23212
- rect: best.rect ?? base.rect,
23213
- hotspot: best.hotspot ?? base.hotspot,
23214
- rotation: best.rotation ?? base.rotation
23215
- };
23216
- }
23217
- function isValidTransformRect(rect) {
23218
- return isValidTransformRectStructure(rect);
23219
- }
23220
- function isExplicitRectCrop(rect, sourceWidth, sourceHeight, epsilon = DEFAULT_RECT_EPSILON) {
23221
- if (!isValidTransformRect(rect)) return false;
23222
- if (!(typeof sourceWidth === "number" && Number.isFinite(sourceWidth) && sourceWidth > 0 && typeof sourceHeight === "number" && Number.isFinite(sourceHeight) && sourceHeight > 0)) return true;
23223
- const [x, y, width, height] = rect;
23224
- return !(Math.abs(x) <= epsilon && Math.abs(y) <= epsilon && Math.abs(width - sourceWidth) <= epsilon && Math.abs(height - sourceHeight) <= epsilon);
23225
- }
23226
- //#endregion
23227
- //#region ../media-core/src/rotation.ts
23228
- function rotateNormalizedPoint(point, rotation) {
23229
- const x = point.x;
23230
- const y = point.y;
23231
- switch (rotation) {
23232
- case 0: return {
23233
- x,
23234
- y
23235
- };
23236
- case 90: return {
23237
- x: 1 - y,
23238
- y: x
23239
- };
23240
- case 180: return {
23241
- x: 1 - x,
23242
- y: 1 - y
23243
- };
23244
- case 270: return {
23245
- x: y,
23246
- y: 1 - x
23247
- };
23248
- }
23249
- }
23250
- //#endregion
23251
23561
  //#region ../blocks/src/system/node/schema.ts
23252
23562
  const transformStepSchema = z.object({
23253
23563
  id: z.string().min(1, "Transform requires an identifier"),
@@ -24185,6 +24495,14 @@ z.object({
24185
24495
  icon: z.string().optional()
24186
24496
  });
24187
24497
  //#endregion
24498
+ //#region ../blocks/src/lib/typeGuards.ts
24499
+ /**
24500
+ * Strict record guard: excludes null, primitives, and arrays.
24501
+ */
24502
+ function isRecord$5(value) {
24503
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24504
+ }
24505
+ //#endregion
24188
24506
  //#region ../blocks/src/system/designCapabilities.ts
24189
24507
  function declareBlockDesignCapability(capability) {
24190
24508
  return {
@@ -26165,6 +26483,12 @@ function parseBackgroundOverlay(value) {
26165
26483
  const result = boxBackgroundOverlaySchema.safeParse(value);
26166
26484
  return result.success ? result.data : void 0;
26167
26485
  }
26486
+ /** Rendering accepts explicit application sources separately from stored references. */
26487
+ const runtimeBackgroundContentSchema = runtimeBoxBackgroundSchema;
26488
+ function parseRuntimeBackgroundContent(value) {
26489
+ const result = runtimeBackgroundContentSchema.safeParse(value);
26490
+ return result.success ? result.data : null;
26491
+ }
26168
26492
  //#endregion
26169
26493
  //#region ../blocks/src/system/transforms/registry/_utils/themeColorResolution.ts
26170
26494
  function resolveBackgroundColorValue(value, context) {
@@ -31038,6 +31362,15 @@ const formBlockDefinition = {
31038
31362
  runtime: formBlockRuntime
31039
31363
  };
31040
31364
  //#endregion
31365
+ //#region ../blocks/src/system/helpers/normalizeMailingListBlockContent.ts
31366
+ /** Fill the declared default without admitting or rewriting invalid custom settings. */
31367
+ function normalizeMailingListBlockContent(record) {
31368
+ return record.targeting == null ? {
31369
+ ...record,
31370
+ targeting: DEFAULT_MAILING_LIST_TARGETING
31371
+ } : record;
31372
+ }
31373
+ //#endregion
31041
31374
  //#region ../blocks/src/system/blocks/contact-newsletter.ts
31042
31375
  const contactNewsletterLayout = styledSection({
31043
31376
  children: sectionContainer([el("div", { className: "contact-newsletter-grid" }, [el("div", { className: "contact-newsletter-column contact-newsletter-contact" }, [el("div", { className: "contact-newsletter-copy" }, [text({
@@ -31201,6 +31534,10 @@ const contactNewsletterBlockDefinition = {
31201
31534
  ],
31202
31535
  icon: "MailPlus"
31203
31536
  }),
31537
+ contentNormalizer: {
31538
+ fieldIds: ["targeting"],
31539
+ normalize: normalizeMailingListBlockContent
31540
+ },
31204
31541
  dataSchemas: { form: formDataSchema.optional() },
31205
31542
  dataLoaders: { form: {
31206
31543
  endpoint: "getPublicFormById",
@@ -32618,14 +32955,6 @@ const mediaTransforms = [{
32618
32955
  }
32619
32956
  }];
32620
32957
  //#endregion
32621
- //#region ../blocks/src/lib/typeGuards.ts
32622
- /**
32623
- * Strict record guard: excludes null, primitives, and arrays.
32624
- */
32625
- function isRecord$5(value) {
32626
- return typeof value === "object" && value !== null && !Array.isArray(value);
32627
- }
32628
- //#endregion
32629
32958
  //#region ../blocks/src/system/transforms/registry/content.ts
32630
32959
  const supportedRichTextTokenSchema = z.enum(["year", "site_name"]);
32631
32960
  const interpolateRichTextTokensOptionsSchema = z.object({ tokens: z.array(supportedRichTextTokenSchema).default(["year", "site_name"]) });
@@ -35418,14 +35747,6 @@ const weeklySlotAvailabilityBlockDefinition = {
35418
35747
  } }
35419
35748
  };
35420
35749
  //#endregion
35421
- //#region ../blocks/src/lib/media.ts
35422
- const PUBLIC_DOWNLOAD_MEDIA_TYPES = [
35423
- "document",
35424
- "spreadsheet",
35425
- "archive",
35426
- "audio"
35427
- ];
35428
- //#endregion
35429
35750
  //#region ../blocks/src/system/manifest/richTextSchema.ts
35430
35751
  /**
35431
35752
  * Shared Rich Text Schema
@@ -35601,6 +35922,14 @@ bookingPaymentOptionSchemas.flexibleBalanceConfig;
35601
35922
  bookingPaymentOptionSchemas.paymentCollectionOption;
35602
35923
  const bookingPaymentTermsSchema = bookingPaymentOptionSchemas.paymentTerms;
35603
35924
  bookingPaymentOptionSchemas.paymentOptionsChoice;
35925
+ z.strictObject({
35926
+ currency: z.string().trim().length(3),
35927
+ totalCents: nonNegativeIntegerSchema,
35928
+ dueNowCents: nonNegativeIntegerSchema,
35929
+ laterPaymentAmountsCents: z.array(nonNegativeIntegerSchema),
35930
+ paymentOption: z.union([createBookingPaymentOptionSchemas("strict").paymentCollectionOption, z.strictObject({ kind: z.literal("not_required") })]),
35931
+ promisedDiscountId: z.string().min(1).nullable().optional()
35932
+ });
35604
35933
  //#endregion
35605
35934
  //#region ../blocks/src/system/blocks/shared/bookingPaymentTerms.ts
35606
35935
  const blockBookingPaymentTermsSchema = bookingPaymentTermsSchema;
@@ -35841,6 +36170,10 @@ const publicEventBaseSchema = z.object({
35841
36170
  eventDestinationMode: eventDestinationModeSchema.optional(),
35842
36171
  scheduleScope: eventScheduleScopeSchema$2.optional(),
35843
36172
  recurrence: publicEventRecurrenceSchema.optional(),
36173
+ scheduleSummary: z.object({
36174
+ recurrenceDescription: z.string(),
36175
+ startTime: z.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/).nullable()
36176
+ }).nullable().optional(),
35844
36177
  id: z.string(),
35845
36178
  occurrenceId: z.string(),
35846
36179
  seriesId: z.string(),
@@ -35849,7 +36182,7 @@ const publicEventBaseSchema = z.object({
35849
36182
  content: z.object({ body: RichTextDocSchema.nullable().optional() }).optional(),
35850
36183
  presentation: z.object({
35851
36184
  summary: z.string().nullable().optional(),
35852
- image: mediaSchema.nullable().optional()
36185
+ image: runtimeMediaSchema.nullable().optional()
35853
36186
  }).optional(),
35854
36187
  slug: z.string(),
35855
36188
  path: z.string().nullable(),
@@ -36251,67 +36584,266 @@ const occurrenceContextSchema = z.object({
36251
36584
  overrides: z.record(z.string(), z.unknown()).nullable().optional()
36252
36585
  }).nullable();
36253
36586
  //#endregion
36254
- //#region ../blocks/src/system/runtime/api/readAuthority.ts
36255
- const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
36256
- kind: z.literal("preview"),
36257
- token: z.string().min(1)
36258
- }).strict()]);
36259
- const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
36260
- kind: z.literal("unsupported"),
36261
- reason: z.enum([
36262
- "unsupportedSubject",
36263
- "invalidEventSeriesId",
36264
- "invalidEventSeriesBookingScope",
36265
- "invalidEventOfferingId",
36266
- "invalidEventOfferingBookingScope",
36267
- "invalidEventOfferingCatalogue"
36268
- ])
36269
- });
36270
- const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
36271
- const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
36272
- if (unsupported.success) return unsupported.data;
36273
- const parsed = parseEventPageSurfaceSubject(value);
36274
- if (parsed.ok) return parsed.value;
36275
- ctx.addIssue({
36276
- code: z.ZodIssueCode.custom,
36277
- message: `Invalid event page-surface subject (${parsed.error.kind})`
36278
- });
36279
- return z.NEVER;
36280
- });
36281
- function resolveEventRegistrationScope(context) {
36282
- const occurrenceSeriesId = context.occurrenceContext?.seriesId;
36283
- if (occurrenceSeriesId) return {
36284
- kind: "occurrenceSeries",
36285
- seriesId: occurrenceSeriesId
36587
+ //#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
36588
+ /**
36589
+ * Compute remaining spots for an event from `availableSpots` with a fallback
36590
+ * to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
36591
+ * for uncapped events so call sites can render no capacity indicator at all.
36592
+ *
36593
+ * The single source of truth for capacity math across event cards, agenda
36594
+ * rows, and timetable cells — keep wording variations in the call sites.
36595
+ */
36596
+ function getEventAvailability(event) {
36597
+ if (event.capacity == null) return {
36598
+ available: null,
36599
+ isFull: false
36286
36600
  };
36287
- if (context.subject?.kind === "eventSeries") return {
36288
- kind: "pageSurfaceSeries",
36289
- seriesId: context.subject.eventSeriesId
36601
+ const available = event.availableSpots ?? event.capacity - event.registeredCount;
36602
+ if (available <= 0) return {
36603
+ available: 0,
36604
+ isFull: true
36290
36605
  };
36291
- if (context.subject?.kind === "eventOffering") return {
36292
- kind: "pageSurfaceOffering",
36293
- offeringId: context.subject.eventOfferingId,
36294
- seriesIds: context.subject.catalogueEventSeriesIds
36606
+ return {
36607
+ available,
36608
+ isFull: false
36295
36609
  };
36296
- if (context.subject?.kind === "unsupported") return {
36297
- kind: "unsupportedPageSurfaceSubject",
36298
- reason: context.subject.reason
36610
+ }
36611
+ /**
36612
+ * Pick the capacity row a booking decision should read: the selected
36613
+ * attendance mode's availability when the visitor has chosen one and the
36614
+ * event publishes it, otherwise the event-level aggregate.
36615
+ *
36616
+ * The mode row is used wholesale. A mode with `capacity: null` on a capped
36617
+ * event is uncapped *by design*: the registration write path applies the
36618
+ * same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
36619
+ * back to the event's `availableSpots` here would block bookings the server
36620
+ * accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
36621
+ */
36622
+ function selectEventCapacitySource(event, attendanceMode) {
36623
+ if (!attendanceMode) return event;
36624
+ return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
36625
+ }
36626
+ /**
36627
+ * Whether the visitor's next step is the waitlist: the source is full *and*
36628
+ * a waitlist is open. The one place this is decided — cards, rows, the
36629
+ * wizard's steps and its action bar all route through here so they agree,
36630
+ * including for producers that omit `availableSpots` and rely on the
36631
+ * `capacity - registeredCount` fallback.
36632
+ */
36633
+ function canJoinEventWaitlist(event, options) {
36634
+ const source = selectEventCapacitySource(event, options?.attendanceMode);
36635
+ return getEventAvailability(source).isFull && source.waitlistEnabled === true;
36636
+ }
36637
+ /**
36638
+ * Remaining places at or below which an event reads as scarce and the exact
36639
+ * count is worth showing. Above it, no number is shown at all: "147 places
36640
+ * left" is not something a visitor acts on, and it is the figure most likely
36641
+ * to be stale on a cached page.
36642
+ *
36643
+ * max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
36644
+ *
36645
+ * The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
36646
+ * 5% floor stops large events staying silent until three remain.
36647
+ * capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
36648
+ */
36649
+ function lowCapacityThreshold(capacity) {
36650
+ return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
36651
+ }
36652
+ function getRemainingPlaces(source) {
36653
+ const { available, isFull } = getEventAvailability(source);
36654
+ if (available === null || source.capacity === null) return { kind: "unlimited" };
36655
+ if (isFull) return {
36656
+ kind: "full",
36657
+ waitlist: canJoinEventWaitlist(source)
36299
36658
  };
36300
- return { kind: "unscoped" };
36659
+ if (available <= lowCapacityThreshold(source.capacity)) return {
36660
+ kind: "low",
36661
+ remaining: available
36662
+ };
36663
+ return { kind: "open" };
36301
36664
  }
36302
- function resolveEventRegistrationScopedEvents(events, context) {
36303
- const scope = resolveEventRegistrationScope(context);
36304
- switch (scope.kind) {
36305
- case "occurrenceSeries":
36306
- case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
36307
- case "pageSurfaceOffering": {
36308
- const seriesIds = new Set(scope.seriesIds);
36309
- return events.filter((event) => seriesIds.has(event.seriesId));
36310
- }
36311
- case "unsupportedPageSurfaceSubject": return [];
36312
- case "unscoped": return [...events];
36665
+ /**
36666
+ * The one place a remaining-places count becomes text. Returns `null` when
36667
+ * nothing should be shown: uncapped events, and capped events above the
36668
+ * scarcity threshold. Surfaces that need their own full-state wording pass
36669
+ * it in rather than re-deriving the count.
36670
+ */
36671
+ function formatRemainingPlaces(source, wording = {}) {
36672
+ const remaining = getRemainingPlaces(source);
36673
+ switch (remaining.kind) {
36674
+ case "unlimited":
36675
+ case "open": return null;
36676
+ case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
36677
+ case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
36678
+ }
36679
+ }
36680
+ function formatPlacesLeft(count, compact) {
36681
+ if (compact) return `${count} left`;
36682
+ return count === 1 ? "1 place left" : `${count} places left`;
36683
+ }
36684
+ function formatTicketTypeRemaining(remainingCapacity) {
36685
+ if (remainingCapacity === null || remainingCapacity > 3) return null;
36686
+ if (remainingCapacity <= 0) return "None left for this ticket type";
36687
+ return `${formatPlacesLeft(remainingCapacity, false)} for this ticket type`;
36688
+ }
36689
+ /**
36690
+ * Which capacity figure wins when they disagree.
36691
+ *
36692
+ * For a hybrid event that publishes both attendance-mode rows, **the mode
36693
+ * rows are authoritative and the event-level aggregate is only a summary**.
36694
+ * The registration write path admits or refuses per mode
36695
+ * (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
36696
+ * aggregate can read full while a mode still has room, and can read open
36697
+ * while the mode the visitor wants is full. Believing the aggregate on a
36698
+ * card is how a "places available" event turns into a dead end at step two.
36699
+ *
36700
+ * Everything else — single-mode events, events that publish no rows, and the
36701
+ * aggregate-only helpers {@link getEventAvailability} /
36702
+ * {@link getRemainingPlaces} that existing callers rely on — keeps reading
36703
+ * the aggregate, unchanged.
36704
+ */
36705
+ function findHybridModeRows(event) {
36706
+ if (event.deliveryMode !== "hybrid") return null;
36707
+ const rows = event.attendanceModes ?? [];
36708
+ const inPerson = rows.find((row) => row.mode === "in_person");
36709
+ const online = rows.find((row) => row.mode === "online");
36710
+ return inPerson && online ? {
36711
+ inPerson,
36712
+ online
36713
+ } : null;
36714
+ }
36715
+ /** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
36716
+ function remainingPlacesRank(remaining) {
36717
+ switch (remaining.kind) {
36718
+ case "unlimited": return 4;
36719
+ case "open": return 3;
36720
+ case "low": return 2;
36721
+ case "full": return remaining.waitlist ? 1 : 0;
36722
+ }
36723
+ }
36724
+ function isMoreOpen(candidate, than) {
36725
+ const rank = remainingPlacesRank(candidate);
36726
+ const otherRank = remainingPlacesRank(than);
36727
+ if (rank !== otherRank) return rank > otherRank;
36728
+ return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
36729
+ }
36730
+ /** True when `candidate` has less room left than `than`. */
36731
+ function isScarcer(candidate, than) {
36732
+ return isMoreOpen(than, candidate);
36733
+ }
36734
+ const ATTENDANCE_MODE_LABEL = {
36735
+ in_person: "In person",
36736
+ online: "Online"
36737
+ };
36738
+ /** Wording for a mode with room to spare, where the count is deliberately withheld. */
36739
+ const PLACES_AVAILABLE = "Places available";
36740
+ /**
36741
+ * The browse-surface label for an event's remaining places. Single-mode
36742
+ * events get exactly the aggregate label {@link formatRemainingPlaces}
36743
+ * produces, and so does a hybrid event whose two modes tell the same story.
36744
+ * When the modes differ, both sides are named: "available" on a card is a
36745
+ * promise the registration wizard would otherwise have to break.
36746
+ *
36747
+ * Hybrid examples, in person full with 3 online places left:
36748
+ * default -> "Online: 3 places left · In person: Full"
36749
+ * { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
36750
+ * { compact: true } -> "Online: 3 left"
36751
+ */
36752
+ function formatEventRemainingPlaces(event, wording = {}) {
36753
+ const rows = findHybridModeRows(event);
36754
+ if (!rows) return formatRemainingPlaces(event, wording);
36755
+ const inPerson = formatRemainingPlaces(rows.inPerson, wording);
36756
+ const online = formatRemainingPlaces(rows.online, wording);
36757
+ if (inPerson === online) return inPerson;
36758
+ if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
36759
+ return formatBothHybridSides(rows, {
36760
+ inPerson,
36761
+ online
36762
+ });
36763
+ }
36764
+ /**
36765
+ * Both sides, the one with room first: that is the half a visitor can act on,
36766
+ * and it should not arrive after a full stop's worth of bad news.
36767
+ */
36768
+ function formatBothHybridSides(rows, labels) {
36769
+ return (isMoreOpen(getRemainingPlaces(rows.online), getRemainingPlaces(rows.inPerson)) ? [["online", labels.online], ["in_person", labels.inPerson]] : [["in_person", labels.inPerson], ["online", labels.online]]).map(([mode, label]) => namedModePlaces(mode, label)).join(" · ");
36770
+ }
36771
+ function namedModePlaces(mode, label) {
36772
+ return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
36773
+ }
36774
+ /**
36775
+ * Pills and timetable cells have room for one short phrase, so they name the
36776
+ * single mode that carries news rather than listing both: the scarce side
36777
+ * when there is a count to show, and otherwise the side that has closed —
36778
+ * "In person: Full", never "Online only", which reads as a statement about
36779
+ * how the event is delivered rather than about its remaining places.
36780
+ */
36781
+ function formatCompactHybridPlaces(rows, wording) {
36782
+ const inPerson = getRemainingPlaces(rows.inPerson);
36783
+ const online = getRemainingPlaces(rows.online);
36784
+ if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
36785
+ if (inPerson.kind === "full" || online.kind === "full") {
36786
+ const fullSide = inPerson.kind === "full" ? "in_person" : "online";
36787
+ const openSide = inPerson.kind === "full" ? "online" : "in_person";
36788
+ const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
36789
+ return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
36790
+ }
36791
+ const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
36792
+ return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
36793
+ }
36794
+ function rowFor(rows, mode) {
36795
+ return mode === "online" ? rows.online : rows.inPerson;
36796
+ }
36797
+ //#endregion
36798
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDisplay.ts
36799
+ const registrationDisplaySchema = z.object({
36800
+ venue: z.enum(["shown", "hidden"]),
36801
+ placesLeft: z.enum(["shown", "hidden"])
36802
+ });
36803
+ function resolveRegistrationDisplay(source) {
36804
+ return {
36805
+ venue: source.showVenue === false ? "hidden" : "shown",
36806
+ placesLeft: source.showCapacity === false ? "hidden" : "shown"
36807
+ };
36808
+ }
36809
+ /** Venue stays in domain data; views receive only the permitted presentation. */
36810
+ function shownVenue(event, display) {
36811
+ return display.venue === "shown" ? event.venue?.name ?? null : null;
36812
+ }
36813
+ /** Full/waitlist explanations remain visible even when optional counts are hidden. */
36814
+ function placesLeftLabel(source, display, wording = {}) {
36815
+ if ("kind" in source) return ticketPlacesLabel(source.remaining, display);
36816
+ if (source.deliveryMode === "hybrid" && source.attendanceModes?.length) return hybridPlacesLabel(source, display, wording);
36817
+ return capacityPlacesLabel(source, display, wording);
36818
+ }
36819
+ function capacityPlacesLabel(source, display, wording) {
36820
+ const remaining = getRemainingPlaces(source);
36821
+ if (display.placesLeft === "hidden" && remaining.kind !== "full") return null;
36822
+ switch (remaining.kind) {
36823
+ case "full": return formatRemainingPlaces(source, wording);
36824
+ case "low": return formatRemainingPlaces(source, wording);
36825
+ case "unlimited": return wording.unlimited ?? null;
36826
+ case "open": return wording.open ?? null;
36827
+ default: return assertNever$2(remaining);
36313
36828
  }
36314
36829
  }
36830
+ function hybridPlacesLabel(event, display, wording) {
36831
+ if (display.placesLeft === "shown") return formatEventRemainingPlaces(event, wording);
36832
+ return (event.attendanceModes ?? []).flatMap((mode) => {
36833
+ const label = placesLeftLabel(mode, display, wording);
36834
+ return label ? [`${mode.mode === "online" ? "Online" : "In person"}: ${label}`] : [];
36835
+ }).join(" · ") || null;
36836
+ }
36837
+ function ticketPlacesLabel(remaining, display) {
36838
+ if (remaining !== null && remaining > 0 && display.placesLeft === "hidden") return null;
36839
+ return formatTicketTypeRemaining(remaining);
36840
+ }
36841
+ //#endregion
36842
+ //#region ../blocks/src/system/runtime/api/readAuthority.ts
36843
+ const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
36844
+ kind: z.literal("preview"),
36845
+ token: z.string().min(1)
36846
+ }).strict()]);
36315
36847
  //#endregion
36316
36848
  //#region ../calendar/src/grid.ts
36317
36849
  const MONTH_NAMES = [
@@ -36399,6 +36931,11 @@ function buildCalendarViewport(input) {
36399
36931
  //#endregion
36400
36932
  //#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
36401
36933
  /**
36934
+ * Date and time presentation for events: the single place an event's
36935
+ * timestamps become strings or date buckets. Calendar-grid primitives are
36936
+ * re-exported from @riverbankcms/calendar so block code has one import.
36937
+ */
36938
+ /**
36402
36939
  * The English locale a site's public dates are written in, read from the
36403
36940
  * site's own time zone (Will, 2026-09-26: "date style from site time zone").
36404
36941
  *
@@ -36418,6 +36955,28 @@ function eventDateLocale(siteTimeZone) {
36418
36955
  if (siteTimeZone === "Pacific/Auckland" || siteTimeZone === "Pacific/Chatham") return "en-NZ";
36419
36956
  return "en-US";
36420
36957
  }
36958
+ function formatEventCompactDateRail(isoTimestamp, options, letterCase = "capitals") {
36959
+ const locale = options?.locale ?? "en-US";
36960
+ const dateTime = toScheduledBookingDateTime({
36961
+ startsAt: isoTimestamp,
36962
+ timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
36963
+ locale
36964
+ });
36965
+ const cased = (value) => letterCase === "capitals" ? value.toLocaleUpperCase(locale) : value;
36966
+ const weekday = cased(formatBookingDate(dateTime, { weekday: "short" }));
36967
+ const month = cased(formatBookingDate(dateTime, { month: "short" }).slice(0, 3));
36968
+ return {
36969
+ weekday,
36970
+ day: formatBookingDate(dateTime, { day: "numeric" }),
36971
+ month,
36972
+ label: formatBookingDate(dateTime, {
36973
+ weekday: "long",
36974
+ year: "numeric",
36975
+ month: "long",
36976
+ day: "numeric"
36977
+ })
36978
+ };
36979
+ }
36421
36980
  //#endregion
36422
36981
  //#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
36423
36982
  function isRecord$4(value) {
@@ -36437,6 +36996,15 @@ function resolveRuntimeEventDestination(event) {
36437
36996
  };
36438
36997
  }
36439
36998
  //#endregion
36999
+ //#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
37000
+ /**
37001
+ * Format an event's price directly from its pricing value (without the full
37002
+ * event). Same output as {@link formatEventPrice}.
37003
+ */
37004
+ function formatEventPriceDisplay(pricing) {
37005
+ return formatEventPricing(pricing, { decimals: "auto" });
37006
+ }
37007
+ //#endregion
36440
37008
  //#region ../blocks/src/system/runtime/nodes/events/domain/listWeek.ts
36441
37009
  /** Civil weeks use the site zone, including weeks containing a clock change. */
36442
37010
  function buildEventListWeek(anchor, startOfWeek, timeZone) {
@@ -36471,6 +37039,642 @@ function eventListWindowEvents(events, window) {
36471
37039
  });
36472
37040
  }
36473
37041
  //#endregion
37042
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
37043
+ function eventMatchesOccurrenceId(event, occurrenceId) {
37044
+ return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
37045
+ }
37046
+ function findPreSelectedEvent(input) {
37047
+ const occurrenceContext = input.occurrenceContext;
37048
+ if (!occurrenceContext) return input.scopedEvents.length === 1 ? input.scopedEvents[0] ?? null : null;
37049
+ const contextDate = getBookingLocalDateKey(toScheduledBookingDateTime({
37050
+ startsAt: occurrenceContext.startsAt,
37051
+ timeZone: occurrenceContext.timeZone
37052
+ }));
37053
+ return input.scopedEvents.find((event) => eventMatchesOccurrenceId(event, occurrenceContext.id) || getBookingLocalDateKey(toScheduledBookingDateTime({
37054
+ startsAt: event.startsAt,
37055
+ timeZone: event.timeZone
37056
+ })) === contextDate) ?? null;
37057
+ }
37058
+ function resolveEventRequiresPayment(input) {
37059
+ if (input.eligibility) return input.eligibility.requiresPayment;
37060
+ if (input.selectedTicketType) return input.selectedTicketType.priceAmount > 0;
37061
+ if (!input.event) return false;
37062
+ return publicEventPricingType(input.event.pricing) === "paid";
37063
+ }
37064
+ //#endregion
37065
+ //#region ../blocks/src/system/runtime/nodes/event-registration/utils.ts
37066
+ /**
37067
+ * The locale a site's booking dates are written in: the one the site's time
37068
+ * zone places it in (`eventDateLocale`, shared with event cards), or British
37069
+ * English when the site names no zone.
37070
+ */
37071
+ function bookingDateLocale(siteTimeZone) {
37072
+ return siteTimeZone ? eventDateLocale(siteTimeZone) : "en-GB";
37073
+ }
37074
+ function bookingInstant(startsAt, input) {
37075
+ return toScheduledBookingDateTime({
37076
+ startsAt,
37077
+ timeZone: toBookingTimeZone(input.timeZone),
37078
+ locale: bookingDateLocale(input.siteTimeZone)
37079
+ });
37080
+ }
37081
+ /** "7:00pm": a twelve-hour clock with a lowercase, unspaced day period. */
37082
+ function bookingClock(startsAt, input) {
37083
+ return formatBookingTime(bookingInstant(startsAt, input), {
37084
+ hour: "numeric",
37085
+ minute: "2-digit",
37086
+ hour12: true
37087
+ }).replace(/\s*([ap])\.?\s?m\.?$/i, (_match, period) => `${period.toLowerCase()}m`);
37088
+ }
37089
+ /** "7:00pm – 8:15pm", or just the start when there's no distinct end. */
37090
+ function clockRange(input) {
37091
+ const start = bookingClock(input.startsAt, input);
37092
+ const end = input.endsAt ? bookingClock(input.endsAt, input) : null;
37093
+ return end && end !== start ? `${start} – ${end}` : start;
37094
+ }
37095
+ /** A stored local clock has no date or DST offset; UTC preserves its digits. */
37096
+ function formatBookingScheduleSummary(event) {
37097
+ const summary = event.scheduleSummary;
37098
+ if (!summary) return null;
37099
+ const time = summary.startTime ? formatBookingWhen({
37100
+ startsAt: `1970-01-01T${summary.startTime}:00Z`,
37101
+ timeZone: "UTC",
37102
+ siteTimeZone: event.siteTimeZone
37103
+ }, "time") : null;
37104
+ return [summary.recurrenceDescription, time].filter(Boolean).join(" · ");
37105
+ }
37106
+ /** The one booking date formatter; every variant derives from it. */
37107
+ function formatBookingWhen(input, variant) {
37108
+ const instant = bookingInstant(input.startsAt, input);
37109
+ const day = formatBookingDate(instant, {
37110
+ weekday: "short",
37111
+ day: "numeric",
37112
+ month: "short"
37113
+ });
37114
+ switch (variant) {
37115
+ case "day": return day;
37116
+ case "dayLong": return formatBookingDate(instant, {
37117
+ weekday: "long",
37118
+ day: "numeric",
37119
+ month: "long"
37120
+ });
37121
+ case "month": return formatBookingDate(instant, { month: "long" });
37122
+ case "monthYear": return formatBookingDate(instant, {
37123
+ month: "long",
37124
+ year: "numeric"
37125
+ });
37126
+ case "time": return bookingClock(input.startsAt, input);
37127
+ case "timeRange": return clockRange(input);
37128
+ case "short": return `${day}, ${bookingClock(input.startsAt, input)}`;
37129
+ case "long": return `${day} · ${clockRange(input)}`;
37130
+ }
37131
+ }
37132
+ /** A single instant in the short form, e.g. "Mon 5 Oct, 7:00pm". */
37133
+ function formatEventDateTime(isoTimestamp, options) {
37134
+ return formatBookingWhen({
37135
+ startsAt: isoTimestamp,
37136
+ timeZone: options?.timeZone,
37137
+ siteTimeZone: options?.siteTimeZone
37138
+ }, "short");
37139
+ }
37140
+ /** The title an occurrence was given on its own, when it overrides the event's. */
37141
+ function occurrenceTitleOverride(occurrence) {
37142
+ const title = occurrence?.overrides?.title;
37143
+ return typeof title === "string" && title.trim() ? title.trim() : null;
37144
+ }
37145
+ /** The event as this page names it: an occurrence's own title wins. */
37146
+ function withOccurrenceTitle(event, occurrence) {
37147
+ const title = occurrenceTitleOverride(occurrence);
37148
+ return event && title ? {
37149
+ ...event,
37150
+ title
37151
+ } : event;
37152
+ }
37153
+ function courseSchedule(sessions, siteTimeZone) {
37154
+ const first = sessions[0];
37155
+ if (!first) return null;
37156
+ const last = sessions[sessions.length - 1] ?? first;
37157
+ const firstLabel = formatEventDateTime(first.startsAt, {
37158
+ timeZone: first.timeZone,
37159
+ siteTimeZone
37160
+ });
37161
+ if (last.startsAt === first.startsAt) return `${sessions.length} session${sessions.length === 1 ? "" : "s"} · ${firstLabel}`;
37162
+ const lastLabel = formatEventDateTime(last.startsAt, {
37163
+ timeZone: last.timeZone,
37164
+ siteTimeZone
37165
+ });
37166
+ return `${sessions.length} sessions · ${firstLabel} – ${lastLabel}`;
37167
+ }
37168
+ /**
37169
+ * When a booking happens, in one line: the occurrence's date and time, or a
37170
+ * course's session count and first and last dates. `short` by default (bar,
37171
+ * confirmation); `long` adds the end time (card, panel header).
37172
+ */
37173
+ function formatRegistrationSchedule(event, occurrence, variant = "short") {
37174
+ if (event.registrationSubject.kind === "wholeSeries") return courseSchedule(event.registrationSubject.sessions, event.siteTimeZone);
37175
+ const input = scheduleInput(event, occurrence ?? null);
37176
+ return input ? formatBookingWhen(input, variant) : null;
37177
+ }
37178
+ /** The occurrence's own times when the page names one, otherwise the event's. */
37179
+ function scheduleInput(event, occurrence) {
37180
+ const times = occurrence?.startsAt ? {
37181
+ startsAt: occurrence.startsAt,
37182
+ endsAt: occurrence.endsAt ?? null
37183
+ } : {
37184
+ startsAt: event.startsAt,
37185
+ endsAt: event.endsAt
37186
+ };
37187
+ if (!times.startsAt) return null;
37188
+ return {
37189
+ ...times,
37190
+ timeZone: occurrence?.timeZone ?? event.timeZone ?? null,
37191
+ siteTimeZone: event.siteTimeZone
37192
+ };
37193
+ }
37194
+ //#endregion
37195
+ //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
37196
+ function registrationRoute(input) {
37197
+ const base = input.chooseEvent ? [
37198
+ "select-event",
37199
+ "select-tickets",
37200
+ "enter-details"
37201
+ ] : ["select-tickets", "enter-details"];
37202
+ switch (input.payment) {
37203
+ case "none": return base;
37204
+ case "options": return [...base, "select-payment"];
37205
+ case "optionsAndPayment": return [
37206
+ ...base,
37207
+ "select-payment",
37208
+ "payment"
37209
+ ];
37210
+ }
37211
+ }
37212
+ const REGISTRATION_STEP_LABELS = [
37213
+ "Event",
37214
+ "Date",
37215
+ "Tickets",
37216
+ "Places",
37217
+ "Your details",
37218
+ "How you’ll pay",
37219
+ "Payment"
37220
+ ];
37221
+ /** Several dates of one series are a date choice; anything else is an event choice. */
37222
+ function getEventSelectionMode(events) {
37223
+ const firstSeriesId = events[0]?.seriesId;
37224
+ return events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId) ? "date" : "event";
37225
+ }
37226
+ /** The label facts for a surface: what the visitor chooses from, and what they book. */
37227
+ function registrationStepLabelContext(scopedEvents, bookedEvent) {
37228
+ return {
37229
+ eventSelection: getEventSelectionMode(scopedEvents),
37230
+ booksWholeSeries: bookedEvent?.registrationSubject.kind === "wholeSeries"
37231
+ };
37232
+ }
37233
+ /** One label owner for the live progress and the server placeholder. */
37234
+ function registrationStepLabel(step, context) {
37235
+ switch (step) {
37236
+ case "select-event": return context.eventSelection === "date" ? "Date" : "Event";
37237
+ case "select-tickets": return context.booksWholeSeries ? "Places" : "Tickets";
37238
+ case "enter-details": return "Your details";
37239
+ case "select-payment": return "How you’ll pay";
37240
+ case "payment": return "Payment";
37241
+ }
37242
+ }
37243
+ function bookingButtonText(buttonText) {
37244
+ return buttonText?.trim() || "Book your place";
37245
+ }
37246
+ function placesLabel(event, input, compact) {
37247
+ return placesLeftLabel(selectEventCapacitySource(event, input.attendanceMode), input.display, { compact });
37248
+ }
37249
+ function isFull(event, mode) {
37250
+ return formatRemainingPlaces(selectEventCapacitySource(event, mode), {
37251
+ full: "full",
37252
+ waitlist: "full"
37253
+ }) === "full";
37254
+ }
37255
+ function priceLabel(event) {
37256
+ return formatEventPriceDisplay(event.pricing).trim() || null;
37257
+ }
37258
+ /** The lowest price a display can mean; null when it names no paid amount. */
37259
+ function lowestPaidAmount(display) {
37260
+ switch (display.kind) {
37261
+ case "exact":
37262
+ case "from": return display.amount;
37263
+ case "range": return display.minAmount;
37264
+ case "mixed": return display.paidFromAmount;
37265
+ case "free":
37266
+ case "unavailable":
37267
+ case "external": return null;
37268
+ }
37269
+ }
37270
+ /**
37271
+ * One price for a set of dates: the shared label when every date costs the
37272
+ * same, otherwise "From" the lowest paid amount, through the one formatter.
37273
+ */
37274
+ function seriesPriceLabel(events) {
37275
+ const firstEvent = events[0];
37276
+ if (!firstEvent) return null;
37277
+ const displays = events.map((event) => publicEventPricingDisplay(event.pricing));
37278
+ const labels = new Set(displays.map((display) => formatBookingPriceDisplay(display, { decimals: "auto" })));
37279
+ if (labels.size === 1) return [...labels][0] || null;
37280
+ const lowest = displays.flatMap((display) => {
37281
+ const amount = lowestPaidAmount(display);
37282
+ return amount === null || !("currency" in display) ? [] : [{
37283
+ amount,
37284
+ currency: display.currency
37285
+ }];
37286
+ }).reduce((best, candidate) => best === null || candidate.amount < best.amount ? candidate : best, null);
37287
+ return lowest ? formatBookingPriceDisplay({
37288
+ kind: "from",
37289
+ amount: lowest.amount,
37290
+ currency: lowest.currency
37291
+ }, { decimals: "auto" }) : priceLabel(firstEvent);
37292
+ }
37293
+ function dateTile(event) {
37294
+ if (event.registrationSubject.kind === "wholeSeries" || !event.startsAt) return null;
37295
+ const rail = formatEventCompactDateRail(event.startsAt, { timeZone: event.timeZone });
37296
+ return {
37297
+ month: rail.month,
37298
+ day: rail.day
37299
+ };
37300
+ }
37301
+ /** "Mon 5 Oct": the compact date the chips and the bar share. */
37302
+ function formatBookingShortDate(event) {
37303
+ return formatBookingWhen(event, "day");
37304
+ }
37305
+ function byStart(left, right) {
37306
+ return Date.parse(left.startsAt) - Date.parse(right.startsAt);
37307
+ }
37308
+ function singleEntry(event, input) {
37309
+ const isCourse = event.registrationSubject.kind === "wholeSeries";
37310
+ return {
37311
+ kind: "single",
37312
+ title: event.title,
37313
+ when: formatRegistrationSchedule(event, null, "long"),
37314
+ shortWhen: formatRegistrationSchedule(event),
37315
+ venue: shownVenue(event, input.display),
37316
+ price: priceLabel(event),
37317
+ places: placesLabel(event, input, false),
37318
+ tile: dateTile(event),
37319
+ action: isCourse ? `Book the ${input.noun.label}` : bookingButtonText(input.buttonText)
37320
+ };
37321
+ }
37322
+ function chooseDateEntry(input) {
37323
+ const ordered = [...input.events].sort(byStart);
37324
+ const first = ordered[0];
37325
+ const shown = ordered.slice(0, 4);
37326
+ return {
37327
+ kind: "chooseDate",
37328
+ title: first?.title ?? "",
37329
+ schedule: (first ? formatBookingScheduleSummary(first) : null) ?? (ordered.length === 1 ? "1 date" : `${ordered.length} dates`),
37330
+ venue: first ? shownVenue(first, input.display) : null,
37331
+ price: seriesPriceLabel(ordered),
37332
+ nextDate: first ? formatBookingShortDate(first) : null,
37333
+ dateCount: ordered.length,
37334
+ dates: shown.map((event) => ({
37335
+ selectionId: event.selectionId,
37336
+ label: formatBookingShortDate(event),
37337
+ places: placesLabel(event, input, true),
37338
+ full: isFull(event, input.attendanceMode)
37339
+ })),
37340
+ moreCount: Math.max(0, ordered.length - shown.length),
37341
+ action: "Choose a date"
37342
+ };
37343
+ }
37344
+ /** The card before a booking starts; the server renders this too. */
37345
+ function idleBookingEntry(input) {
37346
+ if (input.event) return singleEntry(input.event, input);
37347
+ const only = input.events.length === 1 ? input.events[0] : void 0;
37348
+ if (only) return singleEntry(only, input);
37349
+ if (getEventSelectionMode(input.events) === "date") return chooseDateEntry(input);
37350
+ return {
37351
+ kind: "chooseEvent",
37352
+ title: input.events[0]?.title ?? "",
37353
+ count: input.events.length,
37354
+ noun: input.noun.pluralLabel,
37355
+ action: "Choose an event"
37356
+ };
37357
+ }
37358
+ //#endregion
37359
+ //#region ../blocks/src/system/blocks/offerings-preview/model.ts
37360
+ const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
37361
+ const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
37362
+ const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
37363
+ {
37364
+ preset: "class",
37365
+ sourceKey: "eventPreset:class",
37366
+ dataKey: "classEvents",
37367
+ label: "Classes"
37368
+ },
37369
+ {
37370
+ preset: "workshop",
37371
+ sourceKey: "eventPreset:workshop",
37372
+ dataKey: "workshopEvents",
37373
+ label: "Workshops"
37374
+ },
37375
+ {
37376
+ preset: "retreat",
37377
+ sourceKey: "eventPreset:retreat",
37378
+ dataKey: "retreatEvents",
37379
+ label: "Retreats"
37380
+ },
37381
+ {
37382
+ preset: "performance",
37383
+ sourceKey: "eventPreset:performance",
37384
+ dataKey: "performanceEvents",
37385
+ label: "Performances"
37386
+ }
37387
+ ];
37388
+ const OFFERINGS_PREVIEW_EVENT_PRESETS = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
37389
+ const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
37390
+ const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
37391
+ Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
37392
+ kind: "eventPreset",
37393
+ preset: definition.preset
37394
+ }]));
37395
+ function isOfferingsPreviewSourceKey(value) {
37396
+ return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
37397
+ }
37398
+ function normalizeOfferingsPreviewGroupOrder(value) {
37399
+ if (value == null) return { kind: "legacy" };
37400
+ if (value.length === 0) return { kind: "default" };
37401
+ const keys = uniqueOfferingsPreviewSourceKeys(value);
37402
+ return keys.length > 0 ? {
37403
+ kind: "custom",
37404
+ keys
37405
+ } : { kind: "default" };
37406
+ }
37407
+ function inferLegacyOfferingsPreviewGroupOrder(overrides) {
37408
+ const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
37409
+ return keys.length >= 2 ? keys : [];
37410
+ }
37411
+ function normalizeOfferingsPreviewContentForWrite(value) {
37412
+ if (!isRecord$3(value)) return null;
37413
+ const groupOrderValue = value.groupOrder;
37414
+ if (Array.isArray(groupOrderValue)) {
37415
+ const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
37416
+ return {
37417
+ ...value,
37418
+ groupOrder: normalized.kind === "custom" ? normalized.keys : []
37419
+ };
37420
+ }
37421
+ if (groupOrderValue != null) return {
37422
+ ...value,
37423
+ groupOrder: []
37424
+ };
37425
+ const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$3) : []);
37426
+ return legacyOrder.length > 0 ? {
37427
+ ...value,
37428
+ groupOrder: legacyOrder
37429
+ } : value;
37430
+ }
37431
+ /** Which offerings a site preset row names; one owner for "the course preset means whole-series runs". */
37432
+ function sourceFromPresetSummary(preset) {
37433
+ if (preset.engine === "event_series" && preset.canonicalBehaviour === "course") return { kind: "wholeSeries" };
37434
+ if (preset.engine !== "event_series") return null;
37435
+ if (isOfferingsPreviewEventPreset(preset.canonicalBehaviour)) return {
37436
+ kind: "eventPreset",
37437
+ preset: preset.canonicalBehaviour
37438
+ };
37439
+ return null;
37440
+ }
37441
+ function uniqueOfferingsPreviewSourceKeys(values) {
37442
+ const keys = [];
37443
+ for (const value of values) {
37444
+ if (!isOfferingsPreviewSourceKey(value)) continue;
37445
+ if (!keys.includes(value)) keys.push(value);
37446
+ }
37447
+ return keys;
37448
+ }
37449
+ function isRecord$3(value) {
37450
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37451
+ }
37452
+ function isOfferingsPreviewEventPreset(value) {
37453
+ return typeof value === "string" && OFFERINGS_PREVIEW_EVENT_PRESETS.includes(value);
37454
+ }
37455
+ //#endregion
37456
+ //#region ../blocks/src/system/runtime/nodes/event-registration/offeringNoun.ts
37457
+ function offeringKindForEvent(event) {
37458
+ return event.registrationSubject.kind === "wholeSeries" ? "course" : event.eventPreset ?? "event";
37459
+ }
37460
+ function presetKind(preset) {
37461
+ const source = sourceFromPresetSummary(preset);
37462
+ if (!source) return null;
37463
+ return source.kind === "wholeSeries" ? "course" : source.preset;
37464
+ }
37465
+ /**
37466
+ * What the site calls this kind of offering, in lower case for use mid-sentence
37467
+ * ("Book the course", "3 workshops"). Site preset labels win; the canonical
37468
+ * name stands in when the site has none.
37469
+ */
37470
+ function offeringNounForEvent(event, presets) {
37471
+ if (!event) return lowerCased(EVENT_PRESET_VOCABULARY.event);
37472
+ const kind = offeringKindForEvent(event);
37473
+ const preset = presets?.find((candidate) => presetKind(candidate) === kind);
37474
+ return lowerCased(preset?.label.trim() && preset.pluralLabel.trim() ? preset : EVENT_PRESET_VOCABULARY[kind]);
37475
+ }
37476
+ function lowerCased(noun) {
37477
+ return {
37478
+ label: noun.label.trim().toLocaleLowerCase(),
37479
+ pluralLabel: noun.pluralLabel.trim().toLocaleLowerCase()
37480
+ };
37481
+ }
37482
+ //#endregion
37483
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
37484
+ function toRegistrationEvent(event) {
37485
+ return {
37486
+ ...event,
37487
+ selectionId: event.id,
37488
+ registrationSubject: {
37489
+ kind: "occurrence",
37490
+ occurrenceId: event.occurrenceId
37491
+ }
37492
+ };
37493
+ }
37494
+ function toRegistrationEvents(events) {
37495
+ return events.filter((event) => event.bookingScope === "single_occurrence").map(toRegistrationEvent);
37496
+ }
37497
+ /**
37498
+ * Project one whole-series run onto the registration surface.
37499
+ *
37500
+ * Capacity, pricing, payment terms and consent come straight off the canonical
37501
+ * Event record; only the schedule is widened from the single occurrence to the
37502
+ * live run. A run without live sessions has no registration event.
37503
+ */
37504
+ function toWholeSeriesRegistrationEvent(group) {
37505
+ const { representative, run } = group;
37506
+ const sessions = orderedLiveRunSessions(run.sessions);
37507
+ const firstSession = sessions[0];
37508
+ if (!firstSession) return null;
37509
+ const lastSession = sessions[sessions.length - 1] ?? firstSession;
37510
+ return {
37511
+ ...representative,
37512
+ selectionId: run.eventSeriesId,
37513
+ registrationSubject: {
37514
+ kind: "wholeSeries",
37515
+ eventSeriesId: run.eventSeriesId,
37516
+ sessions
37517
+ },
37518
+ startsAt: firstSession.startsAt,
37519
+ endsAt: lastSession.endsAt,
37520
+ timeZone: firstSession.timeZone
37521
+ };
37522
+ }
37523
+ function toWholeSeriesRegistrationEvents(events) {
37524
+ return groupPublicEventsByRun(events).map(toWholeSeriesRegistrationEvent).filter((event) => event !== null);
37525
+ }
37526
+ //#endregion
37527
+ //#region ../blocks/src/system/runtime/nodes/event-registration/bookingCardDisplay.ts
37528
+ /**
37529
+ * The booking card the server renders on CMS pages (issue 1581), and the
37530
+ * offering noun it resolves for the wizard. Kept beside the island contract
37531
+ * so the server and the client agree on one card.
37532
+ */
37533
+ const bookingHostSchema = z.enum(["overlay", "inPlace"]);
37534
+ const tileSchema = z.object({
37535
+ month: z.string(),
37536
+ day: z.string()
37537
+ }).nullable();
37538
+ const idleBookingEntrySchema = z.discriminatedUnion("kind", [
37539
+ z.object({
37540
+ kind: z.literal("single"),
37541
+ title: z.string(),
37542
+ when: z.string().nullable(),
37543
+ shortWhen: z.string().nullable(),
37544
+ venue: z.string().nullable(),
37545
+ price: z.string().nullable(),
37546
+ places: z.string().nullable(),
37547
+ tile: tileSchema,
37548
+ action: z.string()
37549
+ }),
37550
+ z.object({
37551
+ kind: z.literal("chooseDate"),
37552
+ title: z.string(),
37553
+ schedule: z.string(),
37554
+ venue: z.string().nullable(),
37555
+ price: z.string().nullable(),
37556
+ nextDate: z.string().nullable(),
37557
+ dateCount: z.number(),
37558
+ dates: z.array(z.object({
37559
+ selectionId: z.string(),
37560
+ label: z.string(),
37561
+ places: z.string().nullable(),
37562
+ full: z.boolean()
37563
+ })),
37564
+ moreCount: z.number(),
37565
+ action: z.string()
37566
+ }),
37567
+ z.object({
37568
+ kind: z.literal("chooseEvent"),
37569
+ title: z.string(),
37570
+ count: z.number(),
37571
+ noun: z.string(),
37572
+ action: z.string()
37573
+ })
37574
+ ]);
37575
+ function toScopedEventRegistrationEvents(scopedSourceEvents) {
37576
+ return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
37577
+ }
37578
+ function scopedRegistrationEvents(source, scopedSourceEvents) {
37579
+ const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
37580
+ return {
37581
+ scopedEvents,
37582
+ preSelected: findPreSelectedEvent({
37583
+ scopedEvents,
37584
+ occurrenceContext: source.occurrenceContext
37585
+ })
37586
+ };
37587
+ }
37588
+ function nounFor(scoped, presets) {
37589
+ return offeringNounForEvent(scoped.preSelected ?? scoped.scopedEvents[0] ?? null, presets);
37590
+ }
37591
+ /** What the site calls the offering this block books, from its preset rows. */
37592
+ function resolveOfferingNoun(source, scopedSourceEvents) {
37593
+ return nounFor(scopedRegistrationEvents(source, scopedSourceEvents), source.bookingOfferingPresets);
37594
+ }
37595
+ function idleEntry(source, scopedSourceEvents, display) {
37596
+ const scoped = scopedRegistrationEvents(source, scopedSourceEvents);
37597
+ if (scoped.scopedEvents.length === 0) return null;
37598
+ return idleBookingEntry({
37599
+ events: scoped.scopedEvents,
37600
+ event: withOccurrenceTitle(scoped.preSelected, source.occurrenceContext),
37601
+ display,
37602
+ buttonText: source.buttonText,
37603
+ noun: nounFor(scoped, source.bookingOfferingPresets),
37604
+ attendanceMode: null
37605
+ });
37606
+ }
37607
+ /** The server's card on CMS pages, or null where the shell still applies. */
37608
+ function bookingCardDisplay(source, scopedSourceEvents, display) {
37609
+ if ((source.host ?? "overlay") !== "overlay") return null;
37610
+ const entry = idleEntry(source, scopedSourceEvents, display);
37611
+ if (!entry) return null;
37612
+ return {
37613
+ kind: "event-registration",
37614
+ state: "booking-card",
37615
+ className: source.className ?? null,
37616
+ entry,
37617
+ buttonVariant: source.buttonVariant ?? null,
37618
+ buttonText: source.buttonText?.trim() ?? ""
37619
+ };
37620
+ }
37621
+ const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
37622
+ kind: z.literal("unsupported"),
37623
+ reason: z.enum([
37624
+ "unsupportedSubject",
37625
+ "invalidEventSeriesId",
37626
+ "invalidEventSeriesBookingScope",
37627
+ "invalidEventOfferingId",
37628
+ "invalidEventOfferingBookingScope",
37629
+ "invalidEventOfferingCatalogue"
37630
+ ])
37631
+ });
37632
+ const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
37633
+ const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
37634
+ if (unsupported.success) return unsupported.data;
37635
+ const parsed = parseEventPageSurfaceSubject(value);
37636
+ if (parsed.ok) return parsed.value;
37637
+ ctx.addIssue({
37638
+ code: z.ZodIssueCode.custom,
37639
+ message: `Invalid event page-surface subject (${parsed.error.kind})`
37640
+ });
37641
+ return z.NEVER;
37642
+ });
37643
+ function resolveEventRegistrationScope(context) {
37644
+ const occurrenceSeriesId = context.occurrenceContext?.seriesId;
37645
+ if (occurrenceSeriesId) return {
37646
+ kind: "occurrenceSeries",
37647
+ seriesId: occurrenceSeriesId
37648
+ };
37649
+ if (context.subject?.kind === "eventSeries") return {
37650
+ kind: "pageSurfaceSeries",
37651
+ seriesId: context.subject.eventSeriesId
37652
+ };
37653
+ if (context.subject?.kind === "eventOffering") return {
37654
+ kind: "pageSurfaceOffering",
37655
+ offeringId: context.subject.eventOfferingId,
37656
+ seriesIds: context.subject.catalogueEventSeriesIds
37657
+ };
37658
+ if (context.subject?.kind === "unsupported") return {
37659
+ kind: "unsupportedPageSurfaceSubject",
37660
+ reason: context.subject.reason
37661
+ };
37662
+ return { kind: "unscoped" };
37663
+ }
37664
+ function resolveEventRegistrationScopedEvents(events, context) {
37665
+ const scope = resolveEventRegistrationScope(context);
37666
+ switch (scope.kind) {
37667
+ case "occurrenceSeries":
37668
+ case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
37669
+ case "pageSurfaceOffering": {
37670
+ const seriesIds = new Set(scope.seriesIds);
37671
+ return events.filter((event) => seriesIds.has(event.seriesId));
37672
+ }
37673
+ case "unsupportedPageSurfaceSubject": return [];
37674
+ case "unscoped": return [...events];
37675
+ }
37676
+ }
37677
+ //#endregion
36474
37678
  //#region ../blocks/src/system/constants/events.ts
36475
37679
  const eventCalendarDisplayModeValues = [
36476
37680
  "combined",
@@ -36593,95 +37797,6 @@ const calendarTabViewsSchema = z.enum([
36593
37797
  "week"
36594
37798
  ]);
36595
37799
  //#endregion
36596
- //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
36597
- /**
36598
- * Determine which wizard steps to show based on context and payment requirements.
36599
- *
36600
- * @param hasPreSelectedContext - Whether event is pre-selected (skips event selection)
36601
- * @param needsPaymentStep - Whether user must complete payment method selection
36602
- * @returns Array of wizard steps to display
36603
- */
36604
- function getWizardStepsForContext(hasPreSelectedContext, needsPaymentStep, paymentActive = false) {
36605
- const baseSteps = hasPreSelectedContext ? ["select-tickets", "enter-details"] : [
36606
- "select-event",
36607
- "select-tickets",
36608
- "enter-details"
36609
- ];
36610
- if (!needsPaymentStep) return baseSteps;
36611
- return paymentActive ? [
36612
- ...baseSteps,
36613
- "select-payment",
36614
- "payment"
36615
- ] : [...baseSteps, "select-payment"];
36616
- }
36617
- //#endregion
36618
- //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
36619
- function eventMatchesOccurrenceId(event, occurrenceId) {
36620
- return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
36621
- }
36622
- function findPreSelectedEvent(input) {
36623
- const occurrenceContext = input.occurrenceContext;
36624
- if (!occurrenceContext) return input.scopedEvents.length === 1 ? input.scopedEvents[0] ?? null : null;
36625
- const contextDate = getBookingLocalDateKey(toScheduledBookingDateTime({
36626
- startsAt: occurrenceContext.startsAt,
36627
- timeZone: occurrenceContext.timeZone
36628
- }));
36629
- return input.scopedEvents.find((event) => eventMatchesOccurrenceId(event, occurrenceContext.id) || getBookingLocalDateKey(toScheduledBookingDateTime({
36630
- startsAt: event.startsAt,
36631
- timeZone: event.timeZone
36632
- })) === contextDate) ?? null;
36633
- }
36634
- function resolveEventRequiresPayment(input) {
36635
- if (input.eligibility) return input.eligibility.requiresPayment;
36636
- if (input.selectedTicketType) return input.selectedTicketType.priceAmount > 0;
36637
- if (!input.event) return false;
36638
- return publicEventPricingType(input.event.pricing) === "paid";
36639
- }
36640
- //#endregion
36641
- //#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
36642
- function toRegistrationEvent(event) {
36643
- return {
36644
- ...event,
36645
- selectionId: event.id,
36646
- registrationSubject: {
36647
- kind: "occurrence",
36648
- occurrenceId: event.occurrenceId
36649
- }
36650
- };
36651
- }
36652
- function toRegistrationEvents(events) {
36653
- return events.filter((event) => event.bookingScope === "single_occurrence").map(toRegistrationEvent);
36654
- }
36655
- /**
36656
- * Project one whole-series run onto the registration surface.
36657
- *
36658
- * Capacity, pricing, payment terms and consent come straight off the canonical
36659
- * Event record; only the schedule is widened from the single occurrence to the
36660
- * live run. A run without live sessions has no registration event.
36661
- */
36662
- function toWholeSeriesRegistrationEvent(group) {
36663
- const { representative, run } = group;
36664
- const sessions = orderedLiveRunSessions(run.sessions);
36665
- const firstSession = sessions[0];
36666
- if (!firstSession) return null;
36667
- const lastSession = sessions[sessions.length - 1] ?? firstSession;
36668
- return {
36669
- ...representative,
36670
- selectionId: run.eventSeriesId,
36671
- registrationSubject: {
36672
- kind: "wholeSeries",
36673
- eventSeriesId: run.eventSeriesId,
36674
- sessions
36675
- },
36676
- startsAt: firstSession.startsAt,
36677
- endsAt: lastSession.endsAt,
36678
- timeZone: firstSession.timeZone
36679
- };
36680
- }
36681
- function toWholeSeriesRegistrationEvents(events) {
36682
- return groupPublicEventsByRun(events).map(toWholeSeriesRegistrationEvent).filter((event) => event !== null);
36683
- }
36684
- //#endregion
36685
37800
  //#region ../blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.ts
36686
37801
  const { limit: _rawLimit, eventSurface: _rawEventSurface, scheduleScope: _rawScheduleScope, detailLevel: _rawDetailLevel, ...stringEventQueryShape } = publicEventLoaderQuerySchemaShape;
36687
37802
  const eventRecoveryRequestSchema = z.object({
@@ -36724,27 +37839,17 @@ function parseEventLoaderRecoveryRequest(value) {
36724
37839
  const EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS = [
36725
37840
  "Date",
36726
37841
  "Tickets",
36727
- "Details"
36728
- ];
36729
- const EVENT_REGISTRATION_SHELL_STEP_LABELS = [
36730
- "Event",
36731
- "Date",
36732
- "Tickets",
36733
- "Details",
36734
- "Payment options",
36735
- "Payment"
37842
+ "Your details"
36736
37843
  ];
36737
37844
  const eventRegistrationPublicEventSchema = z.preprocess(normalizeEventRegistrationEventInput, publicEventSchema);
36738
- const eventRegistrationHydrationSchemaShape = {
37845
+ const eventRegistrationCommonSchemaShape = {
36739
37846
  demoEmailSuppressed: z.boolean().optional(),
36740
37847
  occurrenceContext: occurrenceContextSchema.optional(),
36741
37848
  subject: eventRegistrationPageSurfaceSubjectSchema.nullable().optional(),
36742
37849
  maxTickets: z.string().optional(),
36743
- showVenue: z.boolean().optional(),
36744
- showCapacity: z.boolean().optional(),
36745
37850
  successMessage: z.string().optional(),
36746
37851
  buttonVariant: registrationButtonVariantSchema.optional(),
36747
- overlayEnabled: z.boolean().optional(),
37852
+ host: bookingHostSchema.optional(),
36748
37853
  spamProtectionEnabled: z.boolean().optional(),
36749
37854
  supportEmail: z.string().optional(),
36750
37855
  sitePasses: publicPassProductCollectionSchema.nullable().optional(),
@@ -36769,32 +37874,51 @@ const eventRegistrationSourceSchema = z.object({
36769
37874
  portalToken: z.string().nullable().optional(),
36770
37875
  previewToken: z.string().nullable().optional(),
36771
37876
  readAuthority: browserReadAuthoritySchema.optional(),
36772
- ...eventRegistrationHydrationSchemaShape,
37877
+ ...eventRegistrationCommonSchemaShape,
37878
+ showVenue: z.boolean().optional(),
37879
+ showCapacity: z.boolean().optional(),
36773
37880
  events: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
36774
37881
  wholeSeriesRunEvents: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
36775
37882
  eventLoaderFailures: z.record(z.string(), z.unknown()).nullable().optional(),
36776
37883
  className: z.string().nullable().optional(),
36777
37884
  buttonText: z.string().nullable().optional(),
36778
37885
  maxTickets: z.string().nullable().optional(),
36779
- stage: previewStageSchema.optional()
37886
+ stage: previewStageSchema.optional(),
37887
+ bookingOfferingPresets: z.array(z.custom((value) => typeof value === "object" && value !== null && typeof value.label === "string")).nullable().optional()
36780
37888
  });
36781
- const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [z.object({
36782
- kind: z.literal("event-registration"),
36783
- state: z.literal("generic-shell"),
36784
- className: z.string().nullable(),
36785
- titleText: z.string(),
36786
- bodyText: z.string(),
36787
- stepLabels: z.array(z.enum(EVENT_REGISTRATION_SHELL_STEP_LABELS)).min(1),
36788
- summaryButtonText: z.string()
36789
- }), z.object({
36790
- kind: z.literal("event-registration"),
36791
- state: z.literal("registration-closed"),
36792
- className: z.string().nullable(),
36793
- titleText: z.string(),
36794
- bodyText: z.string()
36795
- })]);
37889
+ const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [
37890
+ z.object({
37891
+ kind: z.literal("event-registration"),
37892
+ state: z.literal("generic-shell"),
37893
+ className: z.string().nullable(),
37894
+ titleText: z.string(),
37895
+ bodyText: z.string(),
37896
+ stepLabels: z.array(z.enum(REGISTRATION_STEP_LABELS)).min(1),
37897
+ summaryButtonText: z.string()
37898
+ }),
37899
+ z.object({
37900
+ kind: z.literal("event-registration"),
37901
+ state: z.literal("registration-closed"),
37902
+ className: z.string().nullable(),
37903
+ titleText: z.string(),
37904
+ bodyText: z.string()
37905
+ }),
37906
+ z.object({
37907
+ kind: z.literal("event-registration"),
37908
+ state: z.literal("booking-card"),
37909
+ className: z.string().nullable(),
37910
+ entry: idleBookingEntrySchema,
37911
+ buttonVariant: registrationButtonVariantSchema.nullable(),
37912
+ buttonText: z.string()
37913
+ })
37914
+ ]);
36796
37915
  const eventRegistrationHydrationSchema = z.object({
36797
- ...eventRegistrationHydrationSchemaShape,
37916
+ ...eventRegistrationCommonSchemaShape,
37917
+ display: registrationDisplaySchema,
37918
+ offeringNoun: z.object({
37919
+ label: z.string(),
37920
+ pluralLabel: z.string()
37921
+ }).optional(),
36798
37922
  eventSource: eventRegistrationEventSourceSchema
36799
37923
  });
36800
37924
  const eventRegistrationRenderSchema = z.object({
@@ -36809,7 +37933,7 @@ z.object({
36809
37933
  readAuthority: browserReadAuthoritySchema.optional(),
36810
37934
  stage: previewStageSchema.optional()
36811
37935
  });
36812
- function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents) {
37936
+ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display) {
36813
37937
  const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
36814
37938
  if (selectedRun && (!selectedRun.run.enrollmentOpen || toWholeSeriesRegistrationEvent(selectedRun) === null)) return {
36815
37939
  kind: "event-registration",
@@ -36818,6 +37942,8 @@ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvent
36818
37942
  titleText: "Registration closed",
36819
37943
  bodyText: "This run is no longer open for bookings."
36820
37944
  };
37945
+ const card = bookingCardDisplay(source, scopedSourceEvents, display);
37946
+ if (card) return card;
36821
37947
  const stepLabels = buildEventRegistrationShellStepLabels(source, scopedSourceEvents);
36822
37948
  return {
36823
37949
  kind: "event-registration",
@@ -36834,11 +37960,14 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
36834
37960
  if (selectedRun) {
36835
37961
  const selectedEvent = toWholeSeriesRegistrationEvent(selectedRun);
36836
37962
  if (!selectedEvent) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
36837
- return getWizardStepsForContext(true, resolveEventRequiresPayment({
36838
- eligibility: null,
36839
- selectedTicketType: null,
36840
- event: selectedEvent
36841
- })).map((step) => shellLabelForRegistrationStep(step, [selectedEvent]));
37963
+ return shellStepLabels(registrationRoute({
37964
+ chooseEvent: false,
37965
+ payment: resolveEventRequiresPayment({
37966
+ eligibility: null,
37967
+ selectedTicketType: null,
37968
+ event: selectedEvent
37969
+ }) ? "optionsAndPayment" : "none"
37970
+ }), registrationStepLabelContext([selectedEvent], selectedEvent));
36842
37971
  }
36843
37972
  const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
36844
37973
  if (scopedEvents.length === 0) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
@@ -36847,14 +37976,15 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
36847
37976
  occurrenceContext: source.occurrenceContext
36848
37977
  });
36849
37978
  const hasPreSelectedContext = Boolean(source.occurrenceContext || preSelectedEvent);
36850
- return getWizardStepsForContext(hasPreSelectedContext, hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
37979
+ const needsPaymentStep = hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
36851
37980
  eligibility: null,
36852
37981
  selectedTicketType: null,
36853
37982
  event: preSelectedEvent
36854
- }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
36855
- }
36856
- function toScopedEventRegistrationEvents(scopedSourceEvents) {
36857
- return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
37983
+ }) : false;
37984
+ return shellStepLabels(registrationRoute({
37985
+ chooseEvent: !hasPreSelectedContext,
37986
+ payment: needsPaymentStep ? "optionsAndPayment" : "none"
37987
+ }), registrationStepLabelContext(scopedEvents, preSelectedEvent));
36858
37988
  }
36859
37989
  function scopeEventRegistrationSourceEvents(source, eventSource) {
36860
37990
  return resolveEventRegistrationScopedEvents(eventSource.kind === "loaded" ? eventSource.events : [], {
@@ -36870,29 +38000,12 @@ function resolveSelectedWholeSeriesRun(scopedSourceEvents) {
36870
38000
  const runs = groupPublicEventsByRun(scopedSourceEvents);
36871
38001
  return runs.length === 1 ? runs[0] : null;
36872
38002
  }
36873
- function shellLabelForRegistrationStep(step, events) {
36874
- switch (step) {
36875
- case "select-event": return isDateSelection(events) ? "Date" : "Event";
36876
- case "select-tickets": return "Tickets";
36877
- case "enter-details": return "Details";
36878
- case "select-payment": return "Payment options";
36879
- case "payment": return "Payment";
36880
- case "error":
36881
- case "success":
36882
- case "submitting":
36883
- case "verifying-payment":
36884
- case "verifying-timeout":
36885
- case "payment-status":
36886
- case "payment-failed": return "Details";
36887
- }
36888
- }
36889
- function isDateSelection(events) {
36890
- const firstSeriesId = events[0]?.seriesId;
36891
- return Boolean(events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId));
38003
+ function shellStepLabels(route, context) {
38004
+ return route.map((step) => registrationStepLabel(step, context));
36892
38005
  }
36893
38006
  function buildEventRegistrationShellBodyText(stepLabels) {
36894
- if (stepLabels.includes("Payment options")) return "Preparing tickets, details, and payment options.";
36895
- if (stepLabels[0] === "Tickets") return "Preparing tickets and booking details.";
38007
+ if (stepLabels.includes("How you’ll pay")) return "Preparing tickets, details, and payment options.";
38008
+ if (stepLabels[0] === "Tickets" || stepLabels[0] === "Places") return "Preparing tickets and booking details.";
36896
38009
  return "Preparing dates, tickets, and booking details.";
36897
38010
  }
36898
38011
  const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
@@ -36902,6 +38015,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36902
38015
  render: createZodCodec(eventRegistrationRenderSchema)
36903
38016
  },
36904
38017
  buildRender: (source) => {
38018
+ const display = resolveRegistrationDisplay(source);
36905
38019
  const eventSource = resolveEventRegistrationEventSource({
36906
38020
  events: source.events,
36907
38021
  wholeSeriesRunEvents: source.wholeSeriesRunEvents,
@@ -36909,7 +38023,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36909
38023
  });
36910
38024
  const scopedSourceEvents = scopeEventRegistrationSourceEvents(source, eventSource);
36911
38025
  return {
36912
- display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents),
38026
+ display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display),
36913
38027
  hydration: {
36914
38028
  occurrenceContext: source.occurrenceContext ?? null,
36915
38029
  subject: source.subject ?? null,
@@ -36919,29 +38033,29 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36919
38033
  } : eventSource,
36920
38034
  demoEmailSuppressed: source.demoEmailSuppressed,
36921
38035
  maxTickets: source.maxTickets ?? void 0,
36922
- showVenue: source.showVenue,
36923
- showCapacity: source.showCapacity,
38036
+ display,
36924
38037
  successMessage: source.successMessage,
36925
38038
  buttonVariant: source.buttonVariant,
36926
- overlayEnabled: source.overlayEnabled,
38039
+ host: source.host ?? "overlay",
36927
38040
  spamProtectionEnabled: source.spamProtectionEnabled,
36928
38041
  supportEmail: source.supportEmail,
36929
38042
  sitePasses: source.sitePasses ?? null,
36930
38043
  siteMemberships: source.siteMemberships ?? null,
36931
38044
  mailingList: source.mailingList,
36932
- siteName: source.siteName
38045
+ siteName: source.siteName,
38046
+ offeringNoun: resolveOfferingNoun(source, scopedSourceEvents)
36933
38047
  }
36934
38048
  };
36935
38049
  }
36936
38050
  });
36937
38051
  function normalizeEventRegistrationEventInput(value) {
36938
- if (!isRecord$3(value)) return value;
38052
+ if (!isRecord$2(value)) return value;
36939
38053
  return {
36940
38054
  ...value,
36941
38055
  destination: resolveRuntimeEventDestination(value)
36942
38056
  };
36943
38057
  }
36944
- function isRecord$3(value) {
38058
+ function isRecord$2(value) {
36945
38059
  return typeof value === "object" && value !== null && !Array.isArray(value);
36946
38060
  }
36947
38061
  const eventRegistrationBlockDefinition = {
@@ -36981,7 +38095,7 @@ const eventRegistrationBlockDefinition = {
36981
38095
  description: "Main heading for the registration form",
36982
38096
  required: false,
36983
38097
  multiline: false,
36984
- defaultValue: "Register for this event"
38098
+ defaultValue: ""
36985
38099
  },
36986
38100
  {
36987
38101
  id: "description",
@@ -37053,11 +38167,11 @@ const eventRegistrationBlockDefinition = {
37053
38167
  {
37054
38168
  id: "buttonText",
37055
38169
  type: "text",
37056
- label: "Submit button text",
37057
- description: "Text for the registration submit button",
38170
+ label: "Button text",
38171
+ description: "Text on the booking button",
37058
38172
  required: false,
37059
38173
  multiline: false,
37060
- defaultValue: "Complete Registration",
38174
+ defaultValue: "Book your place",
37061
38175
  maxLength: 40
37062
38176
  },
37063
38177
  createButtonVariantField()
@@ -37089,21 +38203,21 @@ const eventRegistrationBlockDefinition = {
37089
38203
  successMessage: { $bind: { from: "content.successMessage" } },
37090
38204
  buttonText: { $bind: {
37091
38205
  from: "content.buttonText",
37092
- fallback: "Complete Registration"
38206
+ fallback: "Book your place"
37093
38207
  } },
37094
38208
  buttonVariant: { $bind: {
37095
38209
  from: "content.buttonVariant",
37096
38210
  fallback: "primary"
37097
38211
  } },
37098
- overlayEnabled: { $bind: {
37099
- from: "$root.eventRegistrationOverlayEnabled",
37100
- fallback: false
37101
- } },
37102
38212
  events: { $bind: { from: "data.events" } },
37103
38213
  wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
37104
38214
  loaderFailures: { $bind: { from: "$root.loaderFailures" } },
37105
38215
  mailingList: { $bind: { from: "$root.mailingList" } },
37106
38216
  siteName: { $bind: { from: "$root.siteName" } },
38217
+ bookingOfferingPresets: { $bind: {
38218
+ from: "$root.bookingOfferingPresets",
38219
+ fallback: []
38220
+ } },
37107
38221
  sitePasses: { $bind: { from: "data.sitePasses" } },
37108
38222
  siteMemberships: { $bind: { from: "data.siteMemberships" } },
37109
38223
  stage: { $bind: {
@@ -39764,7 +40878,21 @@ const eventCalendarBlockDefinition = {
39764
40878
  list: { label: "List" }
39765
40879
  }
39766
40880
  }, eventCalendarContentShape),
39767
- normalizeContent: normalizeEventCalendarContent,
40881
+ contentNormalizer: {
40882
+ fieldIds: [
40883
+ "variant",
40884
+ "displayMode",
40885
+ "listLayout",
40886
+ "defaultCalendarView",
40887
+ "eventSurface",
40888
+ "filterEventPresets",
40889
+ "scheduleScope",
40890
+ "filterCategoryIds",
40891
+ "filterStaffMemberId",
40892
+ "filterVenueId"
40893
+ ],
40894
+ normalize: normalizeEventCalendarContent
40895
+ },
39768
40896
  dataSchemas: {
39769
40897
  events: publicEventsArraySchema.optional(),
39770
40898
  calendarWindow: calendarWindowPageSchema.optional()
@@ -40013,90 +41141,6 @@ const recurringScheduleBlockDefinition = {
40013
41141
  } }
40014
41142
  };
40015
41143
  //#endregion
40016
- //#region ../blocks/src/system/blocks/offerings-preview/model.ts
40017
- const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
40018
- const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
40019
- const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
40020
- {
40021
- preset: "class",
40022
- sourceKey: "eventPreset:class",
40023
- dataKey: "classEvents",
40024
- label: "Classes"
40025
- },
40026
- {
40027
- preset: "workshop",
40028
- sourceKey: "eventPreset:workshop",
40029
- dataKey: "workshopEvents",
40030
- label: "Workshops"
40031
- },
40032
- {
40033
- preset: "retreat",
40034
- sourceKey: "eventPreset:retreat",
40035
- dataKey: "retreatEvents",
40036
- label: "Retreats"
40037
- },
40038
- {
40039
- preset: "performance",
40040
- sourceKey: "eventPreset:performance",
40041
- dataKey: "performanceEvents",
40042
- label: "Performances"
40043
- }
40044
- ];
40045
- OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
40046
- const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
40047
- const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
40048
- Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
40049
- kind: "eventPreset",
40050
- preset: definition.preset
40051
- }]));
40052
- function isOfferingsPreviewSourceKey(value) {
40053
- return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
40054
- }
40055
- function normalizeOfferingsPreviewGroupOrder(value) {
40056
- if (value == null) return { kind: "legacy" };
40057
- if (value.length === 0) return { kind: "default" };
40058
- const keys = uniqueOfferingsPreviewSourceKeys(value);
40059
- return keys.length > 0 ? {
40060
- kind: "custom",
40061
- keys
40062
- } : { kind: "default" };
40063
- }
40064
- function inferLegacyOfferingsPreviewGroupOrder(overrides) {
40065
- const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
40066
- return keys.length >= 2 ? keys : [];
40067
- }
40068
- function normalizeOfferingsPreviewContentForWrite(value) {
40069
- if (!isRecord$2(value)) return null;
40070
- const groupOrderValue = value.groupOrder;
40071
- if (Array.isArray(groupOrderValue)) {
40072
- const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
40073
- return {
40074
- ...value,
40075
- groupOrder: normalized.kind === "custom" ? normalized.keys : []
40076
- };
40077
- }
40078
- if (groupOrderValue != null) return {
40079
- ...value,
40080
- groupOrder: []
40081
- };
40082
- const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$2) : []);
40083
- return legacyOrder.length > 0 ? {
40084
- ...value,
40085
- groupOrder: legacyOrder
40086
- } : value;
40087
- }
40088
- function uniqueOfferingsPreviewSourceKeys(values) {
40089
- const keys = [];
40090
- for (const value of values) {
40091
- if (!isOfferingsPreviewSourceKey(value)) continue;
40092
- if (!keys.includes(value)) keys.push(value);
40093
- }
40094
- return keys;
40095
- }
40096
- function isRecord$2(value) {
40097
- return typeof value === "object" && value !== null && !Array.isArray(value);
40098
- }
40099
- //#endregion
40100
41144
  //#region ../blocks/src/system/blocks/offerings-preview/fields.ts
40101
41145
  const groupOverrideFields = [
40102
41146
  {
@@ -40460,7 +41504,22 @@ const offeringsPreviewEventDataSchemas = Object.fromEntries(OFFERINGS_PREVIEW_EV
40460
41504
  const offeringsPreviewEventDataLoaders = Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.dataKey, eventPresetLoader(definition.preset)]));
40461
41505
  const offeringsPreviewBlockDefinition = {
40462
41506
  manifest: offeringsPreviewManifest,
40463
- normalizeContent: normalizeOfferingsPreviewBlockContent,
41507
+ contentNormalizer: {
41508
+ fieldIds: [
41509
+ "displayMode",
41510
+ "heading",
41511
+ "description",
41512
+ "itemLimit",
41513
+ "groupOrder",
41514
+ "groupOverrides",
41515
+ "_cardStyles",
41516
+ "buttonVariant",
41517
+ "buttonText",
41518
+ "emptyMessage",
41519
+ "_sectionStyles"
41520
+ ],
41521
+ normalize: normalizeOfferingsPreviewBlockContent
41522
+ },
40464
41523
  dataSchemas: {
40465
41524
  ...offeringsPreviewEventDataSchemas,
40466
41525
  routeCandidates: publicRouteCandidatesSchema.optional(),
@@ -42014,6 +43073,14 @@ const audioPlayerBlockDefinition = { manifest: createBlockManifest({
42014
43073
  }
42015
43074
  }) };
42016
43075
  //#endregion
43076
+ //#region ../blocks/src/lib/media.ts
43077
+ const PUBLIC_DOWNLOAD_MEDIA_TYPES = [
43078
+ "document",
43079
+ "spreadsheet",
43080
+ "archive",
43081
+ "audio"
43082
+ ];
43083
+ //#endregion
42017
43084
  //#region ../blocks/src/system/blocks/file-download.ts
42018
43085
  const bound = (from) => ({ $bind: { from } });
42019
43086
  const fileDownloadLayout = styledSection({
@@ -44058,11 +45125,11 @@ function buildNewsletterDisplay(source) {
44058
45125
  successMessage: DEFAULT_MAILING_LIST_SUCCESS_MESSAGE,
44059
45126
  privacyNote: null
44060
45127
  };
44061
- const targeting = source.targeting ?? { mode: "default" };
44062
- const choices = resolveRuntimeChoices(context, targeting);
45128
+ const assessment = assessMailingListTargeting(context, source.targeting ?? { mode: "default" });
45129
+ const choices = resolveRuntimeChoices(assessment);
44063
45130
  return {
44064
45131
  kind: "newsletter-form",
44065
- availability: runtimeTargetingIsAvailable(context, targeting, choices) ? "ready" : "unavailable",
45132
+ availability: assessment.tag === "available" ? "ready" : "unavailable",
44066
45133
  className: source.className ?? null,
44067
45134
  buttonLabel: form.buttonLabel,
44068
45135
  successMessage: form.successMessage,
@@ -44119,60 +45186,12 @@ function projectNewsletterFormHydrationTarget(source) {
44119
45186
  default: return assertNever$2(source);
44120
45187
  }
44121
45188
  }
44122
- function resolveRuntimeChoices(context, targeting) {
44123
- if (context?.tag !== "configured" || targeting.mode !== "custom" || targeting.selection.tag !== "visitor_choice" || context.provider.provider !== targeting.provider) return [];
44124
- switch (targeting.provider) {
44125
- case "native": {
44126
- const offered = new Set(targeting.selection.offeredTopicIds);
44127
- return context.provider.provider === "native" ? context.provider.topics.filter((item) => offered.has(item.id)).map((item) => ({
44128
- id: item.id,
44129
- label: item.name
44130
- })) : [];
44131
- }
44132
- case "brevo": {
44133
- const offered = new Set(targeting.selection.offeredListIds);
44134
- return context.provider.provider === "brevo" ? context.provider.lists.filter((item) => offered.has(item.id)).map((item) => ({
44135
- id: String(item.id),
44136
- label: item.name
44137
- })) : [];
44138
- }
44139
- case "mailchimp": {
44140
- const offered = new Set(targeting.selection.offeredInterestIds);
44141
- return (context.provider.provider === "mailchimp" ? context.provider.audiences.find((item) => item.id === targeting.audienceId) : void 0)?.interests.filter((item) => offered.has(item.id)).map((item) => ({
44142
- id: item.id,
44143
- label: item.name
44144
- })) ?? [];
44145
- }
44146
- }
44147
- }
44148
- function runtimeTargetingIsAvailable(context, targeting, choices) {
44149
- if (context?.tag !== "configured") return false;
44150
- const provider = context.provider;
44151
- if (targeting.mode === "default") switch (provider.provider) {
44152
- case "native": return provider.topics.some((item) => item.id === provider.defaultTopicId);
44153
- case "brevo": return provider.lists.some((item) => item.id === provider.defaultListId);
44154
- case "mailchimp": return provider.audiences.some((item) => item.id === provider.defaultAudienceId);
44155
- }
44156
- if (targeting.provider !== provider.provider) return false;
44157
- switch (targeting.provider) {
44158
- case "native": return provider.provider === "native" && nativeTargetIsAvailable(provider, targeting, choices);
44159
- case "brevo": return provider.provider === "brevo" && brevoTargetIsAvailable(provider, targeting, choices);
44160
- case "mailchimp": return provider.provider === "mailchimp" && mailchimpTargetIsAvailable(provider, targeting, choices);
44161
- }
44162
- }
44163
- function nativeTargetIsAvailable(provider, targeting, choices) {
44164
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
44165
- return ids.every((id) => provider.topics.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
44166
- }
44167
- function brevoTargetIsAvailable(provider, targeting, choices) {
44168
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
44169
- return ids.every((id) => provider.lists.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
44170
- }
44171
- function mailchimpTargetIsAvailable(provider, targeting, choices) {
44172
- const audience = provider.audiences.find((item) => item.id === targeting.audienceId);
44173
- if (!audience) return false;
44174
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
44175
- return ids.every((id) => audience.interests.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
45189
+ function resolveRuntimeChoices(assessment) {
45190
+ if (!("value" in assessment) || assessment.value.mode !== "custom" || !("selectedChoices" in assessment.value) || assessment.value.targeting.selection.tag !== "visitor_choice") return [];
45191
+ return assessment.value.selectedChoices.map((item) => ({
45192
+ id: String(item.id),
45193
+ label: item.name
45194
+ }));
44176
45195
  }
44177
45196
  function parseRequiredMailingListSignupBlockId(value) {
44178
45197
  const parsed = parseMailingListBlockId(value);
@@ -44276,6 +45295,10 @@ const newsletterSignupManifest = createBlockManifest({
44276
45295
  [...(newsletterSignupManifest.fields ?? []).map((field) => field.id)];
44277
45296
  const newsletterSignupBlockDefinition = {
44278
45297
  manifest: newsletterSignupManifest,
45298
+ contentNormalizer: {
45299
+ fieldIds: ["targeting"],
45300
+ normalize: normalizeMailingListBlockContent
45301
+ },
44279
45302
  runtime: newsletterFormBlockRuntime
44280
45303
  };
44281
45304
  //#endregion
@@ -44303,7 +45326,7 @@ const memberPhotoClassName = (baseClass) => cardRoleChildClass({
44303
45326
  baseClass,
44304
45327
  stylesFrom: "content._cardStyles"
44305
45328
  });
44306
- const teamMemberPhotoSchema = mediaSchema.refine((media) => media.type === "image", { message: "Team member photos must be image media" }).nullable();
45329
+ const teamMemberPhotoSchema = runtimeMediaSchema.refine((media) => media.type === "image", { message: "Team member photos must be image media" }).nullable();
44307
45330
  const teamMemberDataSchema = z.object({
44308
45331
  _type: z.literal("teamMember"),
44309
45332
  id: z.string(),
@@ -45008,7 +46031,20 @@ const locationMapBlockDefinition = {
45008
46031
  })], { gap: "md" })
45009
46032
  })
45010
46033
  }),
45011
- normalizeContent: normalizeLocationMapContent
46034
+ contentNormalizer: {
46035
+ fieldIds: [
46036
+ "heading",
46037
+ "locationSource",
46038
+ "venueId",
46039
+ "location",
46040
+ "showAddressText",
46041
+ "showDirections",
46042
+ "directionsLabel",
46043
+ "zoom",
46044
+ "heightPx"
46045
+ ],
46046
+ normalize: normalizeLocationMapContent
46047
+ }
45012
46048
  };
45013
46049
  //#endregion
45014
46050
  //#region ../blocks/src/system/blocks/index.ts
@@ -45832,11 +46868,14 @@ const offeringsPreviewDatesReadEnabled = {
45832
46868
  //#endregion
45833
46869
  //#region ../blocks/src/system/helpers/backgroundMedia.ts
45834
46870
  function resolveBackgroundMedia(input) {
45835
- const parsedMedia = mediaSchema.safeParse(input.image);
46871
+ const parsedMedia = runtimeMediaSchema.safeParse(input.image);
45836
46872
  if (!parsedMedia.success) return null;
45837
46873
  switch (parsedMedia.data.type) {
45838
46874
  case "image":
45839
- case "video": return parsedMedia.data;
46875
+ case "video": return {
46876
+ ...parsedMedia.data,
46877
+ type: parsedMedia.data.type
46878
+ };
45840
46879
  case "audio":
45841
46880
  case "document":
45842
46881
  case "spreadsheet":
@@ -45848,6 +46887,75 @@ function backgroundVideoMobileClassNames(input) {
45848
46887
  return input.showVideoOnMobile === true ? "" : "rb-hidden rb-md-block";
45849
46888
  }
45850
46889
  //#endregion
46890
+ //#region ../blocks/src/system/runtime/nodes/mediaContext.ts
46891
+ /**
46892
+ * Server-side: AsyncLocalStorage for request isolation.
46893
+ * Prevents race conditions when multiple requests are processed concurrently.
46894
+ *
46895
+ * Lazy initialization to avoid issues in environments that don't support async_hooks.
46896
+ */
46897
+ const serverStorageState = {
46898
+ storage: null,
46899
+ checked: false
46900
+ };
46901
+ function getServerStorage() {
46902
+ if (typeof window !== "undefined") return null;
46903
+ if (serverStorageState.checked) return serverStorageState.storage;
46904
+ serverStorageState.checked = true;
46905
+ try {
46906
+ const globalWithNonWebpackRequire = globalThis;
46907
+ const AsyncLocalStorage = (typeof globalWithNonWebpackRequire.__non_webpack_require__ === "function" ? globalWithNonWebpackRequire.__non_webpack_require__ : new Function("id", "return require(id)"))("async_hooks").AsyncLocalStorage;
46908
+ if (AsyncLocalStorage) {
46909
+ serverStorageState.storage = new AsyncLocalStorage();
46910
+ return serverStorageState.storage;
46911
+ }
46912
+ return null;
46913
+ } catch {
46914
+ if (process.env.NODE_ENV !== "production") console.warn("[blocks] AsyncLocalStorage not available - using module-level state");
46915
+ return null;
46916
+ }
46917
+ }
46918
+ /**
46919
+ * Client-side context storage.
46920
+ * Safe because browsers are single-threaded per tab.
46921
+ *
46922
+ * IMPORTANT: This must be shared across bundles.
46923
+ * SDK preview mode can load multiple independently-bundled entrypoints
46924
+ * (e.g. `@riverbankcms/sdk/rendering` + `@riverbankcms/sdk/preview-next/client`),
46925
+ * which can otherwise each get their own module-scoped singleton.
46926
+ */
46927
+ const RB_MEDIA_CONTEXT_KEY = Symbol.for("riverbankcms.blocks.mediaContext");
46928
+ function getClientContextStore() {
46929
+ const g = globalThis;
46930
+ const existing = g[RB_MEDIA_CONTEXT_KEY];
46931
+ if (existing && typeof existing === "object") return existing;
46932
+ const next = {};
46933
+ g[RB_MEDIA_CONTEXT_KEY] = next;
46934
+ return next;
46935
+ }
46936
+ /**
46937
+ * Get the current media context.
46938
+ * Server-side: Uses AsyncLocalStorage for request isolation.
46939
+ * Client-side: Uses module-level state (safe in single-threaded browser).
46940
+ */
46941
+ function getMediaContext() {
46942
+ const storage = getServerStorage();
46943
+ if (storage) return storage.getStore() ?? {};
46944
+ return getClientContextStore();
46945
+ }
46946
+ function getCurrentResolveImageUrlContext() {
46947
+ const context = getMediaContext();
46948
+ if (!context.supabaseUrl && !context.siteId && !context.mediaAssetIndex) return;
46949
+ return {
46950
+ supabaseUrl: context.supabaseUrl,
46951
+ siteId: context.siteId,
46952
+ mediaAssetIndex: context.mediaAssetIndex
46953
+ };
46954
+ }
46955
+ function getMediaAssetIndex(context) {
46956
+ return context?.mediaAssetIndex ?? getMediaContext().mediaAssetIndex;
46957
+ }
46958
+ //#endregion
45851
46959
  //#region ../blocks/src/system/runtime/nodes/media.tsx
45852
46960
  function getValidSourceDimension(value) {
45853
46961
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
@@ -45883,41 +46991,72 @@ function applyRectTransform(existingStyle, patch) {
45883
46991
  if (patch.transform) next.transform = appendTransform(existingStyle?.transform, patch.transform);
45884
46992
  return next;
45885
46993
  }
45886
- function computeMediaTransformStyle(value, style, targetAspectRatio, options) {
46994
+ /** Resolve portable content without exposing runtime metadata as saved values. */
46995
+ function resolveMediaWithContext(reference, context) {
46996
+ return resolveManagedMediaReference(reference, getMediaAssetIndex(context ?? void 0));
46997
+ }
46998
+ function sourceFromAsset(value, asset) {
46999
+ return {
47000
+ type: value.type,
47001
+ alt: value.alt,
47002
+ filename: value.filename,
47003
+ purpose: value.purpose,
47004
+ placeholder: value.placeholder,
47005
+ transform: value.transform,
47006
+ poster: value.poster,
47007
+ identifier: asset.identifier,
47008
+ assetId: asset.id,
47009
+ storageBucket: asset.storageBucket,
47010
+ storagePath: asset.storagePath,
47011
+ mimeType: asset.mimeType,
47012
+ width: asset.width,
47013
+ height: asset.height
47014
+ };
47015
+ }
47016
+ function resolveRuntimeMedia(value, context) {
47017
+ if ("asset" in value && value.asset) return sourceFromAsset(value, value.asset);
47018
+ if ("src" in value && value.src) return value;
47019
+ if (!value.identifier) return void 0;
47020
+ const resolved = resolveMediaWithContext(value, context);
47021
+ return resolved.kind === "resolved" ? sourceFromAsset(resolved.reference, resolved.asset) : void 0;
47022
+ }
47023
+ function usesProxyFraming(source, transform) {
47024
+ if (!source?.assetId || !isExplicitCropProxyEnabled()) return false;
47025
+ return Boolean(transform.rotation) || isExplicitRectCrop(transform.rect, getValidSourceDimension(source.width), getValidSourceDimension(source.height));
47026
+ }
47027
+ function rectFramingPatch(source, rect, proxy, style) {
47028
+ if (!source || !rect || proxy) return {};
47029
+ const patch = buildCssRectStylePatch(source, rect);
47030
+ return patch ? applyRectTransform(style, patch) : {};
47031
+ }
47032
+ function hotspotFramingPatch(transform, proxy, rectPatch, style) {
47033
+ if (!transform.hotspot || rectPatch.objectPosition !== void 0) return {};
47034
+ const fit = style?.objectFit;
47035
+ if (fit && fit !== "cover" && fit !== "contain") return {};
47036
+ const point = proxy && transform.rotation ? rotateNormalizedPoint(transform.hotspot, transform.rotation) : transform.hotspot;
47037
+ return { objectPosition: `${point.x * 100}% ${point.y * 100}%` };
47038
+ }
47039
+ function rotationFramingPatch(rotation, proxy, rectPatch, style) {
47040
+ if (!rotation || proxy) return {};
47041
+ return {
47042
+ transform: appendTransform(rectPatch.transform ?? style?.transform, `rotate(${rotation}deg)`),
47043
+ transformOrigin: rectPatch.transformOrigin ?? "center"
47044
+ };
47045
+ }
47046
+ /** CSS framing is a pure projection of the resolved source and per-use transform. */
47047
+ function deriveMediaFramingStyle(source, transform, proxy, style) {
47048
+ if (!transform.rect && !transform.hotspot && !transform.rotation) return style;
47049
+ const rectPatch = rectFramingPatch(source, transform.rect, proxy, style);
47050
+ return mergeStyles(style, {
47051
+ ...rectPatch,
47052
+ ...hotspotFramingPatch(transform, proxy, rectPatch, style),
47053
+ ...rotationFramingPatch(transform.rotation, proxy, rectPatch, style)
47054
+ });
47055
+ }
47056
+ function computeMediaTransformStyle(value, style, targetAspectRatio, options, context) {
47057
+ const source = resolveRuntimeMedia(value, context ?? getCurrentResolveImageUrlContext());
45887
47058
  const transform = options?.resolvedTransform ?? resolveMediaTransformForAspect(value.transform, targetAspectRatio);
45888
- const rect = transform.rect;
45889
- const hotspot = transform.hotspot;
45890
- const rotation = transform.rotation;
45891
- if (!rect && !hotspot && !rotation) return style;
45892
- const stylePatch = {};
45893
- const heuristicSuppressCss = Boolean(isExplicitCropProxyEnabled() && value.assetId && (typeof rotation === "number" && rotation !== 0 || isExplicitRectCrop(rect, getValidSourceDimension(value.width), getValidSourceDimension(value.height))));
45894
- const shouldSuppressCss = options?.isProxy ?? heuristicSuppressCss;
45895
- if (rect && !shouldSuppressCss) {
45896
- const rectPatch = buildCssRectStylePatch(value, rect);
45897
- if (rectPatch) {
45898
- const mergedRect = applyRectTransform(style, rectPatch);
45899
- Object.assign(stylePatch, mergedRect);
45900
- }
45901
- }
45902
- if (hotspot) {
45903
- const effectiveObjectFit = stylePatch.objectFit ?? style?.objectFit;
45904
- if ((!effectiveObjectFit || effectiveObjectFit === "cover" || effectiveObjectFit === "contain") && stylePatch.objectPosition === void 0) {
45905
- const resolved = shouldSuppressCss && rotation ? rotateNormalizedPoint({
45906
- x: hotspot.x,
45907
- y: hotspot.y
45908
- }, rotation) : {
45909
- x: hotspot.x,
45910
- y: hotspot.y
45911
- };
45912
- stylePatch.objectPosition = `${resolved.x * 100}% ${resolved.y * 100}%`;
45913
- }
45914
- }
45915
- if (rotation && !shouldSuppressCss) {
45916
- const existingTransform = stylePatch.transform ?? style?.transform;
45917
- stylePatch.transform = existingTransform ? `${existingTransform} rotate(${rotation}deg)` : `rotate(${rotation}deg)`;
45918
- if (!stylePatch.transformOrigin) stylePatch.transformOrigin = "center";
45919
- }
45920
- return mergeStyles(style, stylePatch);
47059
+ return deriveMediaFramingStyle(source, transform, options?.isProxy ?? usesProxyFraming(source, transform), style);
45921
47060
  }
45922
47061
  //#endregion
45923
47062
  //#region ../blocks/src/system/transforms/registry/background.ts
@@ -46066,11 +47205,11 @@ const backgroundTransforms = [
46066
47205
  }
46067
47206
  ];
46068
47207
  function parseMediaBackground(value) {
46069
- const background = parseBackgroundContent(value);
47208
+ const background = parseRuntimeBackgroundContent(value);
46070
47209
  return background?.type === "image" || background?.type === "video" ? background : null;
46071
47210
  }
46072
47211
  function resolveBackgroundLayerStyle(value, fallbackToken, context) {
46073
- const background = parseBackgroundContent(value);
47212
+ const background = parseRuntimeBackgroundContent(value);
46074
47213
  const style = {};
46075
47214
  const gradient = background?.type === "gradient" ? isString(background.gradient) ? background.gradient.trim() : gradientPresetCss(background.gradientPresetId) : null;
46076
47215
  const color = background?.type === "color" && isString(background.color) ? background.color.trim() : null;
@@ -46213,17 +47352,7 @@ const linksTransforms = [{
46213
47352
  id: "links.resolve",
46214
47353
  kind: "formatter",
46215
47354
  summary: "Resolve LinkValue objects to href strings using the route map",
46216
- run: (value, _options, context) => {
46217
- if (!value || typeof value !== "object") return null;
46218
- const link = value;
46219
- const kind = typeof link.kind === "string" ? link.kind : null;
46220
- if (kind === "internal") return resolveInternalLink(link, context);
46221
- if (kind === "page") return resolvePageLink(link, context);
46222
- if (kind === "entry") return resolveEntryLink(link, context);
46223
- if (kind === "external" || kind === "url") return resolveExternalLink(link);
46224
- if (!kind && typeof link.href === "string") return resolveExternalLink(link);
46225
- return null;
46226
- }
47355
+ run: (value, _options, context) => resolveAuthoredLinkHref(value, context)
46227
47356
  }, {
46228
47357
  id: "links.label",
46229
47358
  kind: "formatter",
@@ -46250,6 +47379,21 @@ const linksTransforms = [{
46250
47379
  return null;
46251
47380
  }
46252
47381
  }];
47382
+ function resolveAuthoredLinkHref(value, context = {}) {
47383
+ if (!value || typeof value !== "object") return null;
47384
+ const link = value;
47385
+ switch (link.kind) {
47386
+ case "internal": return resolveInternalLink(link, context);
47387
+ case "page": return resolvePageLink(link, context);
47388
+ case "entry": return resolveEntryLink(link, context);
47389
+ case "external":
47390
+ case "url": return resolveExternalLink(link);
47391
+ default: return resolvePreResolvedLink(link);
47392
+ }
47393
+ }
47394
+ function resolvePreResolvedLink(link) {
47395
+ return !(typeof link.kind === "string" ? link.kind : null) && typeof link.href === "string" ? resolveExternalLink(link) : null;
47396
+ }
46253
47397
  const eventCalendarSelectionParam = {
46254
47398
  id: "events.calendarSelectionParam",
46255
47399
  kind: "formatter",